"그런데 외부로 요청하는 기능이 안전한 건지 모르겠다고 하네요…" 이 설명을 보고 SSRF 취약점을 사용하겠구나 생각했다.
외부로 요청하는 기능이 안전한지 모르겠다고 하니 우선 사이트에 들어가서 파일 업로드 페이지는 제쳐두고 이미지 요청 페이지를 집중적으로 파보기로 했다
이러한 페이지가 나오게 된다.
두 입력창에는 각각 'url'과 'title'이라는 이름으로 값이 전달되게 되는 것 같다
그렇다면 이제 소스코드를 살펴보자
from flask import Flask, request, render_template, url_for, redirect
from urllib.request import urlopen
import base64, os
app = Flask(__name__)
app.secret_key = os.urandom(32)
mini_database = []
@app.route('/')
def index():
return redirect(url_for('view'))
@app.route('/request')
def url_request():
url = request.args.get('url', '').lower()
title = request.args.get('title', '')
if url == '' or url.startswith("file://") or "flag" in url or title == '':
return render_template('request.html')
try:
data = urlopen(url).read()
mini_database.append({title: base64.b64encode(data).decode('utf-8')})
return redirect(url_for('view'))
except:
return render_template("request.html")
@app.route('/view')
def view():
return render_template('view.html', img_list=mini_database)
@app.route('/upload', methods=['GET', 'POST'])
def upload():
if request.method == 'POST':
f = request.files['file']
title = request.form.get('title', '')
if not f or title == '':
return render_template('upload.html')
en_data = base64.b64encode(f.read()).decode('utf-8')
mini_database.append({title: en_data})
return redirect(url_for('view'))
else:
return render_template('upload.html')
if __name__ == "__main__":
img_list = [
{'초록색 선글라스': "static/assetA#03.jpg"},
{'분홍색 선글라스': "static/assetB#03.jpg"},
{'보라색 선글라스': "static/assetC#03.jpg"},
{'파란색 선글라스': "static/assetD#03.jpg"}
]
for img in img_list:
for k, v in img.items():
data = open(v, 'rb').read()
mini_database.append({k: base64.b64encode(data).decode('utf-8')})
app.run(host="0.0.0.0", port=80, debug=False)
코드를 살펴보니
@app.route('/request')
def url_request():
url = request.args.get('url', '').lower()
title = request.args.get('title', '')
if url == '' or url.startswith("file://") or "flag" in url or title == '':
return render_template('request.html')
try:
data = urlopen(url).read()
mini_database.append({title: base64.b64encode(data).decode('utf-8')})
return redirect(url_for('view'))
except:
return render_template("request.html")
이 부분은 이미지 요청하는 페이지의 기능을 담당하는 것 같다
잘 보면 SSRF를 막으려고 일종의 필터링을 코드에 넣어놓았다
우선 url 입력창에 들어간 값을 소문자로 전달받게끔 하고, url과 title 입력창이 공백이 아니어야 하고, "file://"이라는 문자열로 시작하면 안되고, "flag"라는 문자열이 없어야한다
즉 정리하자면
- url과 title 공백 x
- url 입력값이 "file://"로 시작해선 안됨
- "flag"라는 문자열이 있어선 안됨
여기서 "file://"은 SSRF file url 취약점 공격에서 주로 사용하는 문자열로 로컬파일을 우리가 임의로 건드릴 수 있는 대표적인 문자열이기 때문에 이 문자열로 시작하지 못하게끔 필터링 하고 있는 것이다
또한 flag라는 문자열을 필터링 함으로서 우리가 원하는 플래그 파일을 열지 못하게끔 하기 위함이다
그렇다면 우리는 위의 필터링을 bypass하는 방법을 찾아 요청만 하면 될 것 같다
"SSRF file url bypass" 이런식으로 검색해보면 여러가지 우회 방법이 나오고, 공격하려는 웹 페이지의 서버가 java라면 특정하게 사용할 수 있는 방법도 있고 여러 방법이 존재한다
하지만 이 문제에서 사용할 수 있는 우회 방법은 "< >"이다
만약 일반적으로 필터링이 없는 페이지에 요청을 하는 익스플로잇 코드를 적어보면
file:///flag.txt&title=test
위 처럼 만들 수 있다
하지만 이 문제에서는 "file://"으로 시작하면 안되고, flag라는 문자열이 들어가면 안되기 때문에 우선 "file://"필터링 부터 우회해보자
우회 방법은 다음과 같다
<file:///flag.txt>&title=test
그리고 나서 flag문자열을 우회하기 위해서는 "url Double Encoding"을 사용한다
이 방법은 url이 하는 인코딩을 두 번 하여 필터링을 우회하는 방법이다
위 방법은 단순히 url encode를 두 번 하는게 아니라 만약 'a'라는 문자를 hex로 변환후 url encoding을 해주면 된다
그렇다면 'a'는 "%2561"가 된다
위의 방법을 flag문자열의 a에만 적용을 해준다면
?url=<file:///fl%2561g.txt>&title=test
위처럼 되며 위 코드가 바로 문제에 사용되는 익스플로잇 코드다
여기서 주의해야 될 것이 이 익스플로잇 코드를 주소창에 입력해야한다
페이지의 url 입력창에 입력하게 될 경우 요청이 반환되기 때문에 "꼭" 주소창에 입력하도록 하자
주소창에 입력한다면 요청을 성공적으로 하게 되고 드림 갤러리에 우리가 요청한 파일이 업로드 된 것을 볼 수 있다
요청이 성공했다면 우클릭 이후 검사를 눌러서 우리가 요청한 이미지의 소스코드를 보면 base64로 인코딩된 문자열이 나오게 되고 그 문자열을 base64로 디코딩시키면 플래그가 출력된다
'DreamHack > Web hacking' 카테고리의 다른 글
[LEVEL-2] web-deserialize-python (0) | 2024.01.20 |
---|---|
[LEVEL-2] Addition calculator (1) | 2024.01.05 |
[LEVEL-2] filestorage (1) | 2023.12.30 |
[LEVEL-1] NoSQL-CouchDB (1) | 2023.12.26 |
[LEVEL-1] Command Injection Advanced (1) | 2023.12.25 |