-
[webhacking] cookie, session- basic 풀이해킹/wargame 2023. 3. 3. 21:25
이번엔 두 가지 문제를 한번에 가져오게 되었습니다.
각각 쿠키와 세션에 관한 문제로, 같이 살펴보면 좋을 것 같았습니다.
문제를 풀기 앞서, 쿠키와 세션에 대해 간단하게 짚어보고 넘어가도록 하겠습니다.
기본 개념
cookie와 session이란?
HTTP 프로토콜은 connectionless와 stateless라는 성격을 가지고 있습니다.
- Connectionless : 하나의 요청에 하나의 응답을 하고 연결을 종료하는 것.
- Stateless : 통신이 끝난 후 정보를 저장하지 않는 것.
따라서 http 통신을 하게 되면 기존에 유저에 대한 정보를 저장할 필요가 있습니다.
이 정보는 쿠키에 저장이 되며, key - value의 형태로 저장된 정보를 요청과 함께 서버에 전달해 줍니다.
하지만 이 쿠키는 클라이언트 브라우저에 저장되기 때문에 클라이언트는 쿠키를 변조할 수 있습니다.
클라이언트가 인증 정보를 변조할 수 없게 하기 위해 세션을 사용합니다.
세션은 인증정보를 서버에 저장하고, 이 데이터에 접근할 수 있는 키(유추 불가능한 문자열)를 클라이언트에게 전달해 줍니다.
브라우저는 해당 키를 쿠키에 저장해 서버에 전달해 주고, 서버는 전달받은 키를 가져와 인증 상태를 확인합니다.
정리하면 쿠키는 클라이언트 브라우저에 위치하기 때문에 클라이언트가 변조 가능하고,
세션은 서버에 저장되어 있어 이를 통해 사용자 인증을 합니다.
문제 1. cookie
https://dreamhack.io/wargame/challenges/6/
cookie
쿠키로 인증 상태를 관리하는 간단한 로그인 서비스입니다. admin 계정으로 로그인에 성공하면 플래그를 획득할 수 있습니다. 플래그 형식은 DH{...} 입니다. Reference Introduction of Webhacking
dreamhack.io
이 문제는 Flask로 구동된 사이트이기 때문에 python코드를 다운받아 살펴보아야 합니다.
문제 파일을 분석해 보겠습니다.
코드 분석
#!/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', 'admin': FLAG } @app.route('/') def index(): username = request.cookies.get('username', None) if username: return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not admin"}') return render_template('index.html') @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')) ) resp.set_cookie('username', username) return resp return '<script>alert("wrong password");history.go(-1);</script>' app.run(host='0.0.0.0', port=8000)
app.route()의 의미는 / 이후 주소로 페이지를 라우팅한다는 의미입니다.
app.route('/login')은 로그인을 구현한 창이 되고, 접속 주소 뒤에 /login을 붙이면 해당 주소로 이동할 수 있습니다.
코드를 보면 index()함수에서 쿠키 정보 중 username을 가져와, 그 값에 따른 처리를 하고 있습니다.
- username == 'admin' 이면 FLAG값을 반환합니다.
- else "you are not admin" 이라는 문자열을 반환합니다.
Exploit
해당 문제는 쿠키 정보 중 username에 대한 value가 admin일 경우 FLAG를 취득할 수 있다고 알았습니다.
위에서 쿠키는 클라이언트에 의해 변조될 수 있다는 것을 배웠으므로, 이를 공략해 봅시다.
우선 기본적으로 user 딕셔너리 안에 있는 guest / guest 사용자 정보를 이용해 guest로 로그인을 합니다.
이후 개발자 모드에서 Application 탭에 Cookies를 확인합니다.
username이라는 키에 guest가 들어있는 것을 볼 수 있습니다.
이제 이를 admin으로 바꾸어 쿠키를 변조합니다.
이후 사이트를 새로고침하면 FLAG 값을 획득한 것을 확인할 수 있습니다.
취약점 보완법
해당 사이트는 변조 가능한 쿠키를 통해 인증 정보를 확인하고 있으므로,
세션을 통한 인증 정보 확인을 한다면 취약점을 보완할 수 있습니다.
다음 문제로 넘어가도록 하겠습니다.
문제 2. session - basic
https://dreamhack.io/wargame/challenges/409/
session-basic
Description 쿠키와 세션으로 인증 상태를 관리하는 간단한 로그인 서비스입니다. admin 계정으로 로그인에 성공하면 플래그를 획득할 수 있습니다. 플래그 형식은 DH{...} 입니다. Reference Background: Cook
dreamhack.io
이 문제도 똑같이 Flask로 구동되는 웹사이트이므로, python 문제 파일을 받아 코드를 확인하도록 합시다.
코드 분석
#!/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 } # this is our session storage session_storage = { } @app.route('/') def index(): session_id = request.cookies.get('sessionid', None) try: # get username from session_storage 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: # you cannot know admin's pw 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(32).hex() session_storage[session_id] = username resp.set_cookie('sessionid', session_id) return resp return '<script>alert("wrong password");history.go(-1);</script>' @app.route('/admin') def admin(): # developer's note: review below commented code and uncomment it (TODO) #session_id = request.cookies.get('sessionid', None) #username = session_storage[session_id] #if username != 'admin': # return render_template('index.html') return session_storage if __name__ == '__main__': import os # create admin sessionid and save it to our storage # and also you cannot reveal admin's sesseionid by brute forcing!!! haha session_storage[os.urandom(32).hex()] = 'admin' print(session_storage) a
라우팅 되는 주소를 찾아보면,
- /
- /login
- /admin
이렇게 3가지 주소가 있는 것을 볼 수 있습니다.
그 중 / 과 /login은 문제 1과 비슷하게 로그인 인증 처리를 하는 코드인 것 같습니다.
다른 점은 로그인 인증을 쿠키에 들어있는 sessionid를 통해 한다는 점이고, 이에 대한 FLAG값은 웹서버가 실행되면서 session_storage에 저장되는 것 같습니다.
session에 대한 key는 os.urandom()함수로 랜덤 값으로 설정되기 때문에 유추하기는 어려워 보입니다.
Exploit
문제에 대한 답은 코드를 잘 보면 금방 나오게 됩니다.
/admin 사이트를 라우팅하는 과정을 보면, admin()함수가 session_storage를 반환하는 것을 볼 수 있습니다.
따라서 우리는 숨겨진 사이트인 /admin으로 이동한다면, session_storage를 확인할 수 있을 것입니다.
주소창 끝에 /admin을 추가해서 들어가면,
Sessionid가 딕셔너리 형태로 나와있는 걸 알 수 있습니다.
이제 이 sessionid로 쿠키를 변조해 봅시다.
위처럼 guest / guest로 로그인을 한 후 진행해도 되지만,
쿠키 값에 sessionid를 바로 추가해서 새로고침해도 됩니다.
새로고침을 하고 나면 위와 같이 admin으로 로그인이 되어있는 것을 확인할 수 있습니다.
취약점 보완법
이 사이트는 /admin이라는 숨겨진 사이트에 접속만 할 수 있다면 sessionid를 바로 얻어낼 수 있기 때문에,
sessionid가 노출되지 않게 하는 것이 중요합니다.
암호화해서 저장하거나, 클라이언트가 받아볼 수 없게 숨겨야 합니다.
'해킹 > wargame' 카테고리의 다른 글
[webhacking] XSS-1 / XSS-2 문제 풀이 (0) 2023.03.05 [web hacking] login filtering 풀이 (0) 2023.02.28 [web hacking] strcmp 풀이 (0) 2023.02.28