지난 글에서 쿠키와 세션에 대해 알아 보았는데요 간략히 요약하자면 쿠키와 세션의 동작과정은 이렇습니다.
- 서버에서 클라이언트에게 세션 ID를 발급한다
- 클라이언트는 발급 받은 세션 ID와 함께 로그인을 한다.
- 서버는 해당 로그인 정보를 세션 ID에 해당되는 인증 정보에 저장한다.
- 서버는 클라이언트에게 인증 상태를 전송한다.
- 클라이언트가 서버에게 요청을 전송할 때, 세션ID 가 저장되어 있는 쿠키와 인증 상태가 저장되어있는 쿠키를 함께 전송한다.
- 서버는 클라이언트에게 받은 세션ID로 이에 해당 되는 인증 정보와 인증 상태가 일치하는지 확인한다.
서버는 HTTP의 비연결성과 무상태성 때문에 클라이언트에게 상태 정보인 쿠키를 발급하고, 해당 쿠키의 변조를 막기 위해 세션이 존재한다는 사실까지 알 수 있었습니다. 하지만 이번 포스팅의 제목에서 알 수 있다시피 세션도 변조가 가능합니다.
Session Hijacking의 원리
만약 예를 들어 네이버에 로그인을 하는 상황을 가정해봅시다. 로그인 후에 Network 페이지에서 세션ID를 발급하는 메시지를 읽어볼 수 있고, Application 페이지에서도 해당 세션ID가 쿠키에 저장되었다는 사실을 확인할 수 있습니다.
그런데 공격자가 사용자의 쿠키 정보를 모두 훔쳐서 세션ID의 정보까지 얻어내게 된다면 어떨까요? 세션의 변조가 가능하게 되어 다른 사용자의 민감한 정보를 탈취할 수 있게 됩니다.
[Wargame] session-basic 해설
users = {
'guest': 'guest',
'user': 'user1234',
'admin': FLAG
}
# this is our session storage
session_storage = {
}
우선 app.py를 분석해보아야 한다. 해당 코드에서 사용자의 정보가 dictionary 형식으로 저장되어있는 것을 확인할 수 있다.
@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"}')
username(인증 정보)은 session_storage에 session_id 에 해당하는 곳에 인증 정보가 저장되는 것을 확인할 수 있다.
@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>'
로그인 페이지를 살펴보면, 비밀번호는 users 라는 곳에서 username과 일치하는 곳에 비밀번호가 저장된다는 것을 알 수 있다. 그렇게 치면 가장 먼저 확인했던 정보에서 guest 아이디의 비밀번호는 guest 라는 것을 알 수 있다.
그러면 우선 guest 로 로그인을 해보자 !
이렇게 게스트 아이디로 로그인이 완료된 것을 확인할 수 있었다. 개발자 도구를 통해 쿠키에 세션 아이디가 정상적으로 저장된 것도 확인할 수 있다.
그런데 코드에서 허점을 발견할 수 있었는데, 바로 로그인을 하지않아도 /admin 페이지에 접속할 수 있다는 것이다.
@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
admin 페이지는 서버에 저장된 세션ID 정보들을 반환하는 것을 알 수 있다. 그러면 /admin 페이지로 이동해보자!
이동하여 보니 admin의 세션 아이디를 얻을 수 있었다. 그러면 다시 개발자도구로 이동하여 세션ID 부분의 정보를 변조하여 session hijacking을 시도해보자.
이렇게 세션 아이디 변조 후 새로고침을 해주면 admin 계정으로 로그인에 성공한 것을 확인할 수 있다.