외로운 Nova의 작업실

dreamhack 웹해킹 - 1(session-basic) 본문

Web Penetesting/Web Vulnerability

dreamhack 웹해킹 - 1(session-basic)

Nova_ 2022. 11. 26. 23:31

안녕하세요. 웹해킹을 dreamhack에서 배우면서 실습했던 내용들을 정리해볼까합니다. 웹 해킹 첫번째 로드맵에서 stage 3의 session-basic 실습했던 내용을 정리하겠습니다. 답이 나와있으니 주의하시기바랍니다.

 

- dreamhack 모의해킹 사용법

먼저 dreamhack에서 모의해킹을 어떻게 실습하는지 알아보겠습니다.

문제 정보란 밑에 접속 정보부분란에 처음에 들어가게되면 접속 정보보기라고 뜹니다. 그것을 눌러주면 dreamhack 서버에 실습을 위한 서버가 생성이되며 링크를 타고 들어가면 실습할 페이지를 볼 수 있습니다. 저는 http://host3.dreamhack.games:19599/ 을 들어가서 실습하게 되는 것입니다.

또한 문제 파일을 다운로드하여 이 서버가 어떻게 작성되었는지 알 수 있습니다.

 

- 문제 파일 분석

문제 파일을 다운로드하고 비쥬얼 스튜디오 코드로 열어서 보게되면 아래와 같습니다.

#!/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():
    # what is it? Does this page tell you session? 
    # It is weird... TODO: the developer should add a routine for checking privilege 
    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)
    app.run(host='0.0.0.0', port=8000)

flask를 통해 서버를 운영하는 것으로 확인됩니다. 하나하나 천천히 보도록 하겠습니다.

 

<플래그 설정 코드>

try:
    FLAG = open('./flag.txt', 'r').read()
except:
    FLAG = '[**FLAG**]'

users = {
    'guest': 'guest',
    'user': 'user1234',
    'admin': FLAG
}


# this is our session storage 
session_storage = {
}

위 코드를 통해 서버쪽에있는 ./flag.txt 파일을 읽어 그 값을 FLAG로 설정해줍니다. 이 플래그값은 users라는 딕셔너리 자료형 admin 부분에 저장됩니다. 또한, 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"}')

flask는 route 데코레이터를 통해 route() 괄호안에 있는 경로로 접속이 오면 def로 정의한 함수를 실행시켜줍니다. 여기서는 / 경로로 오게되면 session_id를 클라이언트로부터 받아서 index.html을 구성하게됩니다. 만약 session_id가 admin의 session_id 라면 hello flag is ~~~ 라고 뜰것이라는 것을 유추해볼 수 있습니다. 

 

</login 데코레이터>

@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>'

 

login 페이지는 get 방식이면 login.html을 전송하고 post 방식이면 username 인자와 password 인자값을 검사합니다. usersname을 통해 users 객체에서 해당하는 값을 가져와 password와 비교하고, 만약 같다면 로그인에 성공했으니 session_id를 하난 생성해 클라이언트에게 쿠키값으로 주게됩니다. 이후 클라이언트는 이 세션값으로 페이지를 로그인 없이 이용할 수 있습니다. 예를들면 클라이언트 측면에서 users 객체에 user : user1234가 있었으니 username에는 user를 password 부분에는 user1234를 넣으면 로그인에 성공하고 세션값을 받을 수 있을 것입니다.

 

</admin 데코레이터>

@app.route('/admin')
def admin():
    # what is it? Does this page tell you session? 
    # It is weird... TODO: the developer should add a routine for checking privilege 
    return session_storage

./admin 페이지로 들어가면 session_storage를 html으로 클라이언트에게 반환 하는 것을 확인할 수 있습니다. 이 것을 이용해서 admin 세션값을 알아낼 수 있을것 같다는 생각이 듭니다.

 

<서버 실행 코드>

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)
    app.run(host='0.0.0.0', port=8000)

서버를 실행하자마자 랜덤값을 hex값으로 만들어 session_storage에 admin으로 저장하는 것으로 볼 수 있습니다. 즉, admin 세션은 랜덤값이며 session_storage에 저장되어 있습니다. 

 

<세션 하이제킹 계획>

서버가 실행되자마자 admin 세션이 session_storage에 저장됩니다. 우리는 /admin 페이지로 접속하여 session_storeage를 볼 수 있습니다. 따라서 /admin에 접속후 세션을 빼앗고 세션을 생성한후 flag를 확인할 수 있는 / 경로로 가보겠습니다.

 

<계획 실행>

첫 페이지는 이렇게 생성됩니다. 이후 /admin 경로로 접속해보겠습니다.

저기에 어드민 session_id가 보입니다. 복사한후 메모장에 적어놓겠습니다.

08db6cd02e96e013926b1aa412088f962299d99b26e3dd789bec75833b354bbe

 

 

이후 개발자 도구를 통해 session_id 쿠키를 생성하여 세션값을 넣어줍니다.

이제 / 경로로 가보도록 하겠습니다.

플래그를 확인할 수 있습니다. 

Comments