본문 바로가기

카테고리 없음

웹해킹 2주

쿠키

클라이언트의 IP 주소와 User-Agent는 매번 변경될 수 있는 고유하지 않은 정보일 뿐만 아니라, HTTP 프로토콜의 ConnectionlessStateless 특징 때문에 웹 서버는 클라이언트를 기억할 수 없습니다.

 

HTTP 프로토콜 특징

Connectionless 하나의 요청에 하나의 응답을 한 후 연결을 종료하는 것을 의미합니다. 특정 요청에 대한 연결은 이후의 요청과 이어지지 않고 새 요청이 있을 때 마다 항상 새로운 연결을 맺습니다.
Stateless 통신이 끝난 후 상태 정보를 저장하지 않는 것을 의미합니다. 이전 연결에서 사용한 데이터를 다른 연결에서 요구할 수 없습니다.

용도

일반적으로 쿠키는 클라이언트의 정보 기록과 상태 정보를 표현하는 용도로 사용합니다.

정보 기록

웹 서비스 사용 시 종종 등장하는 팝업 창에 “다시 보지 않기”, “7일 간 표시하지 않기” 버튼이 있는 것을 확인할 수 있습니다. 웹 서버는 각 클라이언트의 팝업 옵션을 기억하기 위해 쿠키에 해당 정보를 기록하고, 쿠키를 통해 팝업 창 표시 여부를 판단합니다.

상태 정보

많은 웹 사이트에서는 회원 가입과 로그인을 통해 개개인에게 맞춤형 서비스를 제공합니다.

 

쿠키가 없는 통신

HTTP 통신의 특징을 간략하게 표현한 그림입니다. 서버는 요청을 보낸 클라이언트가 누군지 알 수 없기 때문에 현재 어떤 클라이언트와 통신하는지 알 수 없습니다.

쿠키가 있는 통신

쿠키를 사용해 HTTP 통신이 이뤄지는 과정을 간략하게 표현한 그림입니다. 클라이언트는 서버에 요청을 보낼 때마다 쿠키를 포함하고, 서버는 해당 쿠키를 통해 클라이언트를 식별합니다.

쿠키 변조

쿠키는 클라이언트의 브라우저에 저장되고 요청에 포함되는 정보입니다. 따라서, 그림처럼 악의적인 클라이언트는 쿠키 정보를 변조해 서버에 요청을 보낼 수 있습니다. 만약 서버가 별다른 검증 없이 쿠키를 통해 이용자의 인증 정보를 식별한다면 공격자가 타 이용자를 사칭해 정보를 탈취할 수 있습니다.

 

세션

쿠키에 인증 상태를 저장하지만 클라이언트가 인증 정보를 변조할 수 없게 하기 위해 세션(Session)을 사용합니다. 세션은 인증 정보를 서버에 저장하고 해당 데이터에 접근할 수 있는 키(유추할 수 없는 랜덤한 문자열)를 만들어 클라이언트에 전달하는 방식으로 작동합니다. 해당 키를 일반적으로 Session ID라고 합니다. 브라우저는 해당 키를 쿠키에 저장하고 이후에 HTTP 요청을 보낼 때 사용합니다. 서버는 요청에 포함된 키에 해당하는 데이터를 가져와 인증 상태를 확인합니다.

 

실습

실습한 환경이다.

 

연습: 드림핵 세션

1. 드림핵 로그인 페이지에서 우클릭 후 검사를 클릭하고 Network 탭을 누릅니다. 응답을 살펴보면, 서버에서 set-cookie 헤더를 통해 브라우저의 쿠키에 세션 정보를 저장하는 것을 볼 수 있습니다.

 

2. 크롬 검사에서 Application을 누르고 Cookies 목록 안의 https://dreamhack.io를 누르면 서버의 set-cookie 헤더를 통해 설정된 쿠키를 확인할 수 있습니다.

 

3. sessionid 헤더의 값을 메모장에 복사합니다. 이후 사진과 같이 sessionid 헤더의 값을 우클릭한 후 Delete를 클릭하면 브라우저의 쿠키에 저장된 세션 값이 삭제됩니다.

 

4. 쿠키의 빈 칸을 더블 클릭해 sessionid 헤더를 추가하고, 이전에 복사한 세션 값을 입력하면 브라우저의 쿠키에 세션 값이 설정됩니다. 세션 값을 설정하고 드림핵 페이지를 새로고침하면 로그인이 되는 것을 확인할 수 있습니다.

 

5.서버는 이를 통해 이용자 식별하고 인증을 처리합니다. 공격자가 이용자의 쿠키를 훔칠 수 있으면 세션에 해당하는 이용자의 인증 상태를 훔칠 수 있는데, 이를 세션 하이재킹 (Session Hijacking)이라고 합니다.


Q1. 다음 중 세션이 인증 정보를 저장하는 곳은?

A.퀴즈

 

Q2. 다음 중 서버에 요청을 보내는 역할을 하는 것은?

A.클라이언

 

Q3. 통신이 끝난 후 상태 정보를 저장하지 않는 특성을 가리키는 용어는?

A. Stateless


엔드포인트: /

아래 코드는 인덱스 페이지를 구성하는 코드입니다. 해당 페이지에서는 요청에 포함된 쿠키를 통해 이용자를 식별합니다. 만약 쿠키에 존재하는 username이 "admin"일 경우 FLAG를 출력합니다.

@app.route('/') # / 페이지 라우팅 
def index():
    username = request.cookies.get('username', None) # 이용자가 전송한 쿠키의 username 입력값을 가져옴
    if username: # username 입력값이 존재하는 경우
        return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not admin"}') # "admin"인 경우 FLAG 출력, 아닌 경우 "you are not admin" 출력
    return render_template('index.html')

 

엔드 포인트: /login

@app.route('/login', methods=['GET', 'POST']) # login 페이지 라우팅, GET/POST 메소드로 접근 가능
def login():
    if request.method == 'GET': # GET 메소드로 요청 시
        return render_template('login.html') # login.html 페이지 출력
    elif request.method == 'POST': # POST 메소드로 요청 시
        username = request.form.get('username') # 이용자가 전송한 username 입력값을 가져옴
        password = request.form.get('password') # 이용자가 전송한 password 입력값을 가져옴
        try:
            pw = users[username] # users 변수에서 이용자가 전송한 username이 존재하는지 확인
        except: 
            return '<script>alert("not found user");history.go(-1);</script>' # 존재하지 않는 username인 경우 경고 출력
        if pw == password: # password 체크
            resp = make_response(redirect(url_for('index')) ) # index 페이지로 이동하는 응답 생성
            resp.set_cookie('username', username) # username 쿠키 설정
            return resp 
        return '<script>alert("wrong password");history.go(-1);</script>' # password가 동일하지 않은 경우

GET

usernamepassword를 입력할 수 있는 로그인 페이지를 제공합니다.

 

POST

이용자가 입력한 usernamepassword 입력 값을 users 변숫값과 비교합니다. 아래 코드users 변수가 선언된 코드입니다.

try:
    FLAG = open('./flag.txt', 'r').read() # flag.txt 파일로부터 FLAG 데이터를 가져옴.
except:
    FLAG = '[**FLAG**]'
users = {
    'guest': 'guest',
    'admin': FLAG # FLAG 데이터를 패스워드로 선언
}

취약점 분석

쿠키는 클라이언트의 요청에 포함되는 정보로, 이용자가 임의로 조작할 수 있습니다. 서버는 별다른 검증 없이 이용자 요청에 포함된 쿠키를 신뢰하고, 이용자 인증 정보를 식별하기 때문에 공격자는 쿠키에 타 계정 정보를 삽입해 계정을 탈취할 수 있습니다.

@app.route('/') # / 페이지 라우팅 
def index():
    username = request.cookies.get('username', None) # 이용자가 전송한 쿠키의 username 입력값을 가져옴
    if username: # username 입력값이 존재하는 경우
        return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not admin"}') # "admin"인 경우 FLAG 출력, 아닌 경우 "you are not admin" 출력
    return render_template('index.html')