2021. 8. 8. 11:17ㆍWeb
소개딩 대회 연관해서 횸니형이 추천해서 풀어보고자한다.
전에 비슷한 유형 진짜 본거같은데 어디에서 본지 기억이 안나서...
먼저 제공된 코드는 아래와 같다.
#!/usr/bin/python3
from flask import Flask, request, render_template_string, g, session, jsonify
import sqlite3
import os, hashlib
app = Flask(__name__)
app.secret_key = "Th1s_1s_V3ry_secret_key"
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(os.environ['DATABASE'])
db.row_factory = sqlite3.Row
return db
def query_db(query, args=(), one=False):
cur = get_db().execute(query, args)
rv = cur.fetchall()
cur.close()
return (rv[0] if rv else None) if one else rv
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
@app.route('/')
def index():
return "api-server"
@app.route('/api/me')
def me():
if session.get('uid'):
return jsonify(userid=session['uid'])
return jsonify(userid=None)
@app.route('/api/login', methods=['POST'])
def login():
userid = request.form.get('userid', '')
password = request.form.get('password', '')
if userid and password:
ret = query_db(f"SELECT * FROM users where userid='{userid}' and password='{hashlib.sha256(password.encode()).hexdigest()}'" , one=True)
if ret:
session['uid'] = ret[0]
return jsonify(result="success", userid=ret[0])
return jsonify(result="fail")
@app.route('/api/logout')
def logout():
session.pop('uid', None)
return jsonify(result="success")
@app.route('/api/join', methods=['POST'])
def join():
userid = request.form.get('userid', '')
password = request.form.get('password', '')
if userid and password:
conn = get_db()
cur = conn.cursor()
cur.execute("Insert into users values(?, ?);", (userid, hashlib.sha256(password.encode()).hexdigest()))
conn.commit()
return jsonify(result="success")
return jsonify(result="error")
@app.route('/api/memo/add', methods=['PUT'])
def memoAdd():
if not session.get('uid'):
return jsonify(result="no login")
userid = session.get('uid')
title = request.form.get('title')
contents = request.form.get('contents')
if title and contents:
conn = get_db()
cur = conn.cursor()
ret = cur.execute("Insert into memo(userid, title, contents) values(?, ?, ?);", (userid, title, contents))
conn.commit()
return jsonify(result="success", memoidx=ret.lastrowid)
return jsonify(result="error")
@app.route('/api/memo/<idx>', methods=['GET'])
def memoView(idx):
mode = request.args.get('mode', 'json')
ret = query_db("SELECT * FROM memo where idx=" + idx)[0]
if ret:
userid = ret['userid']
title = ret['title']
contents = ret['contents']
if mode == 'html':
template = ''' Written by {userid}<h3>{title}</h3>
<pre>{contents}</pre>
'''.format(title=title, userid=userid, contents=contents)
return render_template_string(template)
else:
return jsonify(result="success",
userid=userid,
title=title,
contents=contents)
return jsonify(result="error")
@app.route('/api/memo/<int:idx>', methods=['PUT'])
def memoUpdate(idx):
if not session.get('uid'):
return jsonify(result="no login")
ret = query_db('SELECT * FROM memo where idx=?', [idx,])[0]
userid = session.get('uid')
title = request.form.get('title')
contents = request.form.get('contents')
if ret and title and contents:
conn = get_db()
cur = conn.cursor()
updateRet = cur.execute("UPDATE memo SET title=?, contents=? WHERE idx=?",(title, contents, idx))
conn.commit()
if updateRet:
return jsonify(result="success")
return jsonify(result="error")
위 코드들 중에서 취약한 코드를 찾아서 안전하게 수정하면 된다.
먼저 막돌려봤더니 어떤 취약점들이 발생하는지 친절하게 알려준다.
[FAIL]
- SLA(7/7): SLA PASS
- VULN(5)
- Hard-coded Key
- SQL Injection
- Server-Side Template Injection
- Cross Site Scripting
- Memo Update IDOR
Hard-coded Key
같은 경우는 초반에 key를 바꿔 주면 된다.
해당 사이트에서 import된 os를 이용한 난수 해결법을 찾았다.
7번째줄
before
> app.secret_key = "Th1s_1s_V3ry_secret_key"
after
> app.secret_key = os.urandom(32)
해당 코드를 수정하니 4개로 줄었다
[FAIL]
- SLA(7/7): SLA PASS
- VULN(4)
- SQL Injection
- Server-Side Template Injection
- Cross Site Scripting
- Memo Update IDOR
XSS
Xss도 친숙하지만 어케 막아야 할지 고민하다가
- https://flask.palletsprojects.com/en/1.1.x/security/#cross-site-scripting-xss
- https://item4.blog/2016-04-21/Print-Number-with-Comma-in-Flask/
두개 사이트에서 정보 조합해서 아래와 같이 수정해줬다.
원본
@app.route('/api/memo/<idx>', methods=['GET'])
def memoView(idx):
mode = request.args.get('mode', 'json')
ret = query_db("SELECT * FROM memo where idx=" + idx)[0]
if ret:
userid = ret['userid']
title = ret['title']
contents = ret['contents']
if mode == 'html':
template = ''' Written by {userid}<h3>{title}</h3>
<pre>{contents}</pre>
'''.format(title=title, userid=userid, contents=contents)
return render_template_string(template)
else:
return jsonify(result="success",
userid=userid,
title=title,
contents=contents)
return jsonify(result="error")
수정본
@app.route('/api/memo/<idx>', methods=['GET'])
def memoView(idx):
mode = request.args.get('mode', 'json')
ret = query_db("SELECT * FROM memo where idx=" + idx)[0]
if ret:
userid = ret['userid']
title = ret['title']
contents = ret['contents']
if mode == 'html':
template = ''' Written by {{userid}}<h3>{{title}}</h3>
<pre>{{contents}}</pre> #
'''
return render_template_string(template, title=title, userid=userid, contents=contents)
else:
return jsonify(result="success",
userid=userid,
title=title,
contents=contents)
return jsonify(result="error")
근데 신기하게 ssti랑 xss둘다 한번에 잡혔다.
[FAIL]
- SLA(7/7): SLA PASS
- VULN(2)
- SQL Injection
- Memo Update IDOR
Memo Update IDOR
memo update 에 IDOR이 발생한다고 하는데
@app.route('/api/memo/<int:idx>', methods=['PUT'])
def memoUpdate(idx):
if not session.get('uid'):
return jsonify(result="no login")
ret = query_db('SELECT * FROM memo where idx=?', [idx,])[0]
userid = session.get('uid')
title = request.form.get('title')
contents = request.form.get('contents')
if ret and title and contents:
conn = get_db()
cur = conn.cursor()
updateRet = cur.execute("UPDATE memo SET title=?, contents=? WHERE idx=?",(title, contents, idx))
conn.commit()
if updateRet:
return jsonify(result="success")
return jsonify(result="error")
해당 구문에서 idor을 발생시킬만한 것이 idx인데
idx값은 이미 넘어온 상태에서 visual_studio의 userid가 참조되지 않은 객체로 보여서
해당 부분이 존재하는 값인지 확인하는 조건을 추가해줬다.
@app.route('/api/memo/<int:idx>', methods=['PUT'])
def memoUpdate(idx):
if not session.get('uid'):
return jsonify(result="no login")
ret = query_db('SELECT * FROM memo where idx=?', [idx,])[0]
userid = session.get('uid')
title = request.form.get('title')
contents = request.form.get('contents')
if (ret and title and contents and (ret['userid'] == userid)) :
conn = get_db()
cur = conn.cursor()
updateRet = cur.execute("UPDATE memo SET title=?, contents=? WHERE idx=?",(title, contents, idx))
conn.commit()
if updateRet:
return jsonify(result="success")
return jsonify(result="error")
이렇게 하니 sqli만 방어하면 된다고 한다.
[FAIL]
- SLA(7/7): SLA PASS
- VULN(1)
- SQL Injection
SQL Injection —> 삽질하다 문법오류 keep
sqli경우 비교적 친숙한 웹 취약점인데 sql구문들을 주로 사용하는 부분을 살피면 될 것 같다.
if userid and password:
ret = query_db(f"SELECT * FROM users where userid='{userid}' and password='{hashlib.sha256(password.encode()).hexdigest()}'" , one=True) #
if ret:
session['uid'] = ret[0]
return jsonify(result="success", userid=ret[0])
42번째 해당 줄이 다른 sqli구문하고 다르게 생겼다.
다른 구문들은
cur.execute("Insert into users values(?, ?);", (userid, hashlib.sha256(password.encode()).hexdigest()))
와 같이 앞에 ?를 넣은 후에 뒤에 괄호로 덮어서 넣어줬는데 해당 구문은 그냥 넣어줬다.
그래서 해당 부분을 아래와 같이 수정하고
if userid and password:
ret = query_db("SELECT * FROM users where userid= ? and password= ?", (userid, hashlib.sha256(password.encode()).hexdigest()), one=True)
if ret:
session['uid'] = ret[0]
return jsonify(result="success", userid=ret[0])
하단에 취약해 보이는 부분이 하나 더 보여서
ret = query_db("SELECT * FROM memo where idx=" + idx)[0]
해당 코드 또한
ret = query_db("SELECT * FROM memo where idx=?",(idx))[0]
로 변환해보았다.
그랬더니
ALL PASS
를 받을 수 있었다!!
어디서 틀리는지 모르는데 계속 문법 오류 나서 고생했다..
최종 코드
#!/usr/bin/python3
from flask import Flask, request, render_template_string, g, session, jsonify
import sqlite3
import os, hashlib
app = Flask(__name__)
app.secret_key = os.urandom(32)
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(os.environ['DATABASE'])
db.row_factory = sqlite3.Row
return db
def query_db(query, args=(), one=False):
cur = get_db().execute(query, args)
rv = cur.fetchall()
cur.close()
return (rv[0] if rv else None) if one else rv
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
@app.route('/')
def index():
return "api-server"
@app.route('/api/me')
def me():
if session.get('uid'):
return jsonify(userid=session['uid'])
return jsonify(userid=None)
@app.route('/api/login', methods=['POST'])
def login():
userid = request.form.get('userid', '')
password = request.form.get('password', '')
if userid and password:
ret = query_db("SELECT * FROM users where userid= ? and password= ?", (userid, hashlib.sha256(password.encode()).hexdigest()), one=True)
if ret:
session['uid'] = ret[0]
return jsonify(result="success", userid=ret[0])
return jsonify(result="fail")
@app.route('/api/logout')
def logout():
session.pop('uid', None)
return jsonify(result="success")
@app.route('/api/join', methods=['POST'])
def join():
userid = request.form.get('userid', '')
password = request.form.get('password', '')
if userid and password:
conn = get_db()
cur = conn.cursor()
cur.execute("Insert into users values(?, ?);", (userid, hashlib.sha256(password.encode()).hexdigest()))
conn.commit()
return jsonify(result="success")
return jsonify(result="error")
@app.route('/api/memo/add', methods=['PUT'])
def memoAdd():
if not session.get('uid'):
return jsonify(result="no login")
userid = session.get('uid')
title = request.form.get('title')
contents = request.form.get('contents')
if title and contents:
conn = get_db()
cur = conn.cursor()
ret = cur.execute("Insert into memo(userid, title, contents) values(?, ?, ?);", (userid, title, contents))
conn.commit()
return jsonify(result="success", memoidx=ret.lastrowid)
return jsonify(result="error")
@app.route('/api/memo/<idx>', methods=['GET'])
def memoView(idx):
mode = request.args.get('mode', 'json')
ret = query_db("SELECT * FROM memo where idx=?",(idx))[0]
if ret:
userid = ret['userid']
title = ret['title']
contents = ret['contents']
if mode == 'html':
template = ''' Written by {{userid}}<h3>{{title}}</h3>
<pre>{{contents}}</pre> #
'''
return render_template_string(template, title=title, userid=userid, contents=contents)
else:
return jsonify(result="success",
userid=userid,
title=title,
contents=contents)
return jsonify(result="error")
@app.route('/api/memo/<int:idx>', methods=['PUT'])
def memoUpdate(idx):
if not session.get('uid'):
return jsonify(result="no login")
ret = query_db('SELECT * FROM memo where idx=?', [idx,])[0]
userid = session.get('uid')
title = request.form.get('title')
contents = request.form.get('contents')
if (ret and title and contents and (ret['userid'] == userid)) :
conn = get_db()
cur = conn.cursor()
updateRet = cur.execute("UPDATE memo SET title=?, contents=? WHERE idx=?",(title, contents, idx))
conn.commit()
if updateRet:
return jsonify(result="success")
return jsonify(result="error")
'Web' 카테고리의 다른 글
dreamhack.io - web-HTTP-CLI (0) | 2021.09.02 |
---|---|
dreamhack.io - file-csp-1 (0) | 2021.08.24 |
dreamhack.io - mongoboard (0) | 2021.08.23 |
Dreamhack - funjs (0) | 2021.08.19 |
dreamhack.io - web-blind-command (0) | 2021.07.30 |