본문 바로가기

Private

22.07.13 - 퍼저 프로젝트 구상, Flask 기초 (템플릿, 블루프린트)

이번에 스터디에서 팀 프로젝트를 진행하게 되었습니다. 프로젝트를 진행하며 공부한 내용을 정리하려고 합니다.

 

스터디 주제는 웹 애플리케이션 퍼저 개발 프로젝트입니다. 취약점 탐색을 위해 백터가 될 수 있을만한 엔트리 후보를 찾는 것을 자동화하는 스크립트 개발을 목표로 하고 있습니다. 


주별로 주제를 정해 공부하고 발표하는데, 이번 주 주제는 'OWASP ZAP API 사용'이다.

 

ZAP은 Owasp Top 10으로 유명한 OWASP(Open Web Application Security Project)에서 만든 프록시 도구이다. Fiddler, Burp Suite와 같은 기능을 하는데, 확장성이 좋고 오픈 소스여서 이 툴을 이용해 퍼저를 개발하려고 한다. 다만 내장 브라우저가 없어 HSTS 우회를 위해 CA 인증서를 설치하는 등의 작업이 필요하다. [각주:1][각주:2]

 

ZAP API는 웹을 기반으로 제공되는데, REST를 직접 때릴 필요 없이 대표적인 언어들에는 SDK가 마련되어 있다. 나는 파이썬을 이용해 개발할거니까, pypi에서 공식 SDK를 받아주었다. node.js 책 사놓고 또 파이썬이다.


 

나는 이 구조로 모듈을 나누어 개발을 진행할 예정이다. 인터페이스 모듈이랑 퍼징 모듈이랑 나누는 것이 확장성에 있어서 유리하다는 생각이 들었다. 다만 인터페이스-퍼징 모듈간의 통신은 어떻게 할 지 고민이다. 일단 MVP 구현 단계에서는 내부 함수 호출로 짜고, 추후에 확장하게 되면 REST나 PIPE 등의 방법을 고안해야 할 것 같다. 

 

퍼징모듈의 세부 기능들을 또 모듈화해서, 다양한 방식의 퍼징을 지원할 수 있도록 만들 계획이다 지금 염두에 두고 있는 기능은 에러메시지가 노출되는지 확인하는 퍼징(특수문자 파싱 에러, DB 길이 초과 에러 등을 퍼징으로 확인), Stored/Reflected User Interaction 벡터 확인, SSTI/XSS 가능성 점검 등의 기능이다.


오랜만에 Flask를 이용해보는데, Flask는 물론이고 파이썬도 까먹은 부분이 많아 간단히 정리해 보겠다.


@self.app.route('/')
def index():
    return render_template('main.html', title='webFuzzer')

우선 파이썬의 데코레이터 문법. 이 글이 글을 참고했다.

ㅁㄴㅇㄹ


그리고 Flask의 템플릿 개념. 이 글이 글을 참고했다.  RTFM이라고 하지만 구글신은 모든걸 알고있는걸?

Flask는 Jinja2 템플릿을 채택했다. 장고의 template, Express의 ejs와 같은 역할을 수행해서 동적으로 페이지를 생성할 수 있게 해 준다. 기본적으로 HTML 파일이지만, { }를 사용해 특수한 동작을 수행할 수 있다. 

  • Flask 내의 변수(ip 등의 환경변수 등)에 접근
  • 조건에 따른 분기
  • tuple을 전달받아 반복
  • 템플릿 상속

물론 이 외에도 많은 기능이 있지만, 이번 프로젝트에서는 나열한 기능들만 사용할 것 같다. 각각을 살펴보자. [각주:3]

 

Flask 변수 접근

기본적으로 app의 context 내에서 내부 변수에 접근하고 함수를 실행할 수 있다. 파이썬 기본 자료형과 함수도 사용 가능하다. 다음 예시를 보자. 

from flask import Flask, render_template_string
app = Flask(__name__)

@app.route("/")
def index():
    return render_template_string('{{ ", ".join(["a", "b", "c"]) }}<br />'
                                  '{{ request.url }}')

app.run()

이때 for, if 등 Jinja의 예약어는 사용할 수 없다는 것을 조심하자. 

 

변수 전달

템플릿 이후에 전달되는 파라미터들은 kargs로 전달된다. 원하는 변수를 탬플릿으로 전달해 페이지를 동적으로 생성할 수 있다. 사실상 템플릿의 핵심이다!

from flask import Flask, render_template_string
app = Flask(__name__)

@app.route("/")
def index():
    return render_template_string('<span style="color: {{ color }}">{{ msg1 }}</span> {{ msg2 }}', 
                                  color="red", msg1="Hello", msg2="World!")

app.run()

조건에 따른 분기, 변수 반복

변수의 전달 여부, 변수의 값 등을 기준으로 분기할 수 있고 전달받은 변수를 반복할 수 있다. 이 기능들을 활용해서 많은 부분을 단순화할 수 있다. 가장 직관적인 예시는 쿼리 리스트이다. 

from flask import Flask, render_template_string
app = Flask(__name__)

@app.route("/")
def index():
    return render_template_string('{% if errmsg %} <span style="color: red"> {{ errmsg }} </span>'
                                  '{% elif res %} {% for data in res %} <li> {{ data }} </li> {% endfor %}'
                                  '{% else %} <span style="color: gray"> Unknown Error. </span>'
                                  '{% endif %}', res=["a", "b", "c"])

app.run()

 

템플릿 상속

변수 전달과 더불어 템플릿 사용의 의미를 더하는 요소이다. 반복해서 사용하는 뼈대가 있을 때, 템플릿을 상속할 수 있으며 변화가 필요한 요소의 위치만 지정할 수 있다. 주로 앱의 기본 UI를 각 메뉴별로 상속받아 기능에 맞춰서 세부 내용을 추가하는 방식으로 사용한다. 

 

[base.html]

더보기
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    {% block title_area %}{% endblock %}
</head>
<body>
    <div id="navbar">
        <a href="{{ url_for('menu1') }}">menu1</a>
        {% block navbar_area %}
        {% endblock %}
    </div>
    <hr />
    <div id="content">
        {% block content_area %}{% endblock %}
    </div>

</body>
</html>

[menu1.html]

더보기
{% extends 'base.html' %}

{% block title_area %}
    {% if title %}
        <title>menu1 - {{ title }}</title>
    {% else %}
        <title>menu1</title>
    {% endif %}
{% endblock %}

{% block navbar_area %}
<a href="{{ url_for('index') }}">main</a>
{% endblock %}

{% block content_area %}
<h1>{{ content }}</h1>
{% endblock %}

이후 render_template("menu1.html")을 한다면 상속된 템플릿이 출력된다. 이 페이지의 코드는 다음과 같을 것이다: 

[출력결과 (title: "Hello, World", content: "nicknamemohaji")]

더보기
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>menu1  - Hello, World</title>
</head>
<body>
    <div id="navbar">
        <a href="/menu1/">menu1</a>
        <a href="/">main</a>
    </div>
    <hr />
    <div id="content">
        <h1>nicknamemohaji</h1>
    </div>

</body>
</html>

Flask의 blueprint 개념. 이 글공식문서의 예제를 참고했다.

 

blueprint[각주:4] 는 라우팅을 하는 방법이다. 하위경로의 라우팅을 하나의 청사진으로 묶어 관리할 수 있다. 모듈화를 돕는 클래스이다.

 

모듈을 만들어 하위경로를 라우팅한 후, Flask 객체에 이 청사진을 추가해주는 방식이다. 

 

[route.py]

from flask import Blueprint, render_template


bp = Blueprint('menu1', __name__, url_prefix='/menu1')


@bp.route('/', methods=['GET'])
def menu1_index():
    return render_template('menu1/main.html', title='menu1', content='Hello World')

Blueprint 객체를 만든 후, 이 객체를 이용해 데코레이터를 사용한다는 점 빼고는 Flask 객체에 직접 라우팅하는 방식과 다르지 않다. url_prefix를 변경하여 루트를 정할 수 있다. 즉, 이 경우에 menu1_index가 라우팅하는 경로에 접근하려면 /가 아니라 /menu1/ 으로 가야 한다. 

 

[main.py]

import menu1

app.register_blueprint(menu1.bp)

실제로 실행할 Flask 객체에 register_blueprint 함수를 이용해 Blueprint 객체를 연결하면 모든 준비가 끝난다. 


오늘은 여기까지. 내일은 실제로 SDK를 사용하고, 파서를 돌려 input 태그를 찾는 것을 MVP로 코어모듈을 구현하겠다.

 

흠.. 티스토리가 관리하기는 편한데 마크다운 지원이 이상하다거나, 코드 하이라이팅이 예쁘지 않은 등 아쉬운 점이 많다. 블로그 코드를 건드리거나 typora로 작성 후 pdf를 업로드하는 방법을 써야겠다. 

 

  1. Tools - Options - Dynamic SSL Certificates에서 발급받을 수 있다. [본문으로]
  2. 미세먼지팁 하나. 크로미움 기반 브라우저를 이용한다면 Proxy SwitchOmega 앱을 사용하자. 특정 탭만 프록시로 넘길 수 있다! [본문으로]
  3. render_template_string 함수를 이용해 템플릿을 사용하는 방식은 SSTI 등 인젝션 공격에 위험하다. render_template 변수를 사용하면 코드를 깔끔하게 유지할 수 있을 뿐 아니라 보안성도 챙길 수 있다. [본문으로]
  4. 편의상 '청사진'이라고 직역한다. 한국어 공식 번역자료가 있나? [본문으로]

'Private' 카테고리의 다른 글

22.08.01 - 스터디  (0) 2022.08.08