웹해킹 문제를 풀고 공유했다.
문제1. Out of money

서버 접속 시 화면

이름 입력한 후 로그인


- 산타 사설 거래소
: 드핵 코인을 빌리거나 드핵 코인을 드냥 코인 또는 드멍 코인으로 바꿀 수 있음
- 드림 유동성 풀
: 담보를 내고 드멍 코인을 빌리거나 드냥 코인이나 드핵 코인을 예치할 수 있음
이 문제의 코드를 살펴보면 담보로 돈을 빌릴 때 한번에 빌릴 수 있는 값만을 설정하고 몇 번 빌리는 것에 대해서는 아무런 제약을 걸어놓지 않은 것을 확인할 수 있음

Flag 구매를 클릭하여 플래그를 알 수 있음
드핵 코인이 부족하거나 빌릴 돈이 남아 있을 때는
Flag 구매 클릭하면 '드핵 코인이 없어욧!', '빚을 먼저 갚으세욧!'과 같은 메시지 출력

먼저 3333 드핵 코인만큼 빌림
담보를 낼 때 드냥 코인으로 내야 하기 때문에 드핵 코인의 일부를 드냥 코인으로 전환

바꾼 드냥 코인을 3001만큼 담보로 내놓음
그 후 내놓은 담보보다 적은 금액으로 여러 번 빌려서 빌린 드핵 코인을 갚을 만큼 드멍 코인을 빌림

계속 빌리면 담보보다 가지고 있는 드멍 코인이 더 많은 것을 확인 가능

드핵 코인을 갚아야 하기 때문에 4333만큼의 드멍 코인을 드핵 코인으로 바꿔줌
-4333을 입력해서 드핵 코인 빌리기 버튼을 누르면 드핵 코인이 갚아짐

Flag 구매가 가능한 만큼 드핵 코인으로 바꿔준 후 Flag 구매 버튼 클릭

해결 완료 !!!!!
문제2. Logical

<코드 분석>
from flask import Flask, render_template, redirect, request, make_response
from random import randint
from hashlib import md5
from sqlite3 import connect
app = Flask(__name__)
## hidden_dir_XXXXXXX 랜덤 경로
hidden_dir = '/dir_' + str(randint(1, 99999999999999999))
#getpost부분
@app.route('/', methods=['GET','POST'])
def login(): ##로그인함수
# 요청하면 -> login.html 반환
if request.method == 'GET':
return render_template('login.html')
# 요청받으면 -> if문
elif request.method == 'POST':
#login_check 함수에 안맞으면 try again ..
if not login_check():
return render_template('login.html', error='Something wrong with the login details. Try again.')
#login_check 함수가 맞으면 리다이렉트 hidden_dir
else:
return redirect(hidden_dir)
#아니면 오류
else:
return make_response(405)
#hidden_dir은 flag.txt나오게 함!!
@app.route(hidden_dir)
def hidden_endpoint():
return render_template('hidden.html', FLAG=open('flag.txt').read())
def login_check():
uname = request.form.get('uname', '') #username
password = request.form.get('password', '') #password
#username 없고, 동시에 password 없으면 false
if not uname and not password:
return False
connection = connect("logical.db") #db에 연결
cursor = connection.cursor()
#db에 uname, password 있고 password는 mb5로 인코딩
query = ("SELECT uname, password FROM users WHERE password = '{}'").format(md5(password.encode()).hexdigest())
# usrname이 리스트 형식으로..
usrname = cursor.execute(query).fetchall()
#usrname이 비어있지 않고 usrname[0]도 존재, usrname[0][0]도 존재하면 name = usrname[0][0]
#그렇지 않다면 else (공백)
name = usrname[0][0] if usrname and usrname[0] and usrname[0][0] else ''
return name == uname
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80, debug=False)
문제풀이 핵심 코드
#username 없고, 동시에 password 없으면 false
if not uname and not password:
return False
둘 다 없으면 False인데
그렇다면 하나는 없어도 되지 않을까?,,,
query = ("SELECT uname, password FROM users WHERE password = '{}'").format(md5(password.encode()).hexdigest())
이 쿼리에서 uname, password를 셀렉할 때 오직 password만를 이용했다.
uname은 상관하지 않고,,,
=> 어떤 uname이어도 password만 같으면 가져올 수 있음..
name = usrname[0][0] if usrname and usrname[0] and usrname[0][0] else ''
이 코드 앞 부분의 fetchall()함수는 리스트 함수
usrname은 리스트이고
if문의 ,,
usrname이 비어있지 않고
usrname[0]이 존재하고
usrname[0][0]이 존재하면
name은 usrname[0][0]이다 //name = [uname, password]
else문이면 name은 ' '(공백)!!! 오류가 아니라 그냥 공백으로 처리해버렸다.
return name == uname
<문제풀이>
이렇게 분석한 것을 통해
username에는 공백으로 두고
password만 입력해서 해당 문제를 풀 수 있다고 생각했다.
근데 db에 있는 password를 알아낼 수 있는 방법이 없다.
아무 password값을 입력하면 앞서 본 코드에 의해 usrname에 해당하는 pwd가 없다고 처리되어 else문으로 공백으로 만들어버린다. ==> name이 공백이 됨
username으로 입력받은 uname도 공백으로 입력하면
return name == uname이 return ' ' == ' '이 되므로 access된다.
이것을 기반으로 익스플로잇
import requests
data = {
"uname": "",
"password": "password" #아무 문자나 입력해도 상관X
}
response = requests.post("http://host3.dreamhack.games:10665/", data=data)
print(response.text)

문제3. Base64 based


사이트에 들어가면 이 화면만 뜬다. 다른 방법이 없으니 코드를 확인한다.

코드를 보면 Li4v, ZnxhZ, aHA가 막혀있는 것을 확인할 수 있다.

확인해보면 hp, fla, ../가 막혀있는 것을 알 수 있다. ../과 flag, php를 막기 위해 설정된 것 같다. 그렇지만

이렇게 ./flag.php를 앞에 입력해주면? 이 값을 인코딩 해주면 위에 false에 막힌 값과 전혀 다른 값이 나오게 된다. 값: Li9mbGFnLnBocA==

base64 인코딩 값을 뒤에 넣어주면

flag 값을 찾을 수 있다.
DH{We1c0me_t0_Ba5e64:86cneX/wpG/aUprzn+ak7w==}
'4-1. 2025-1 심화 스터디 > 워게임 도장 깨기' 카테고리의 다른 글
[3주차] 250401 워게임 도장 깨기 (0) | 2025.04.02 |
---|---|
[2주차] 250326 워게임 도장 깨기 (0) | 2025.03.26 |