읽기 안내
Dreamhack의 개인 문제 풀이 기록입니다. 원문에 남아 있는 실습 주소·플래그와 실패·미완료 결과를 그대로 보존했습니다.
본 문제의 서버는 파이썬 Flask 페임워크를 통해 구현됐다. 웹 프레임워크는 웹 페이지를 개발하는 과정에서 겪는 유지보수, 반복 작업 등의 어려움을 줄이기 위해 명명 및 스크립트 작성 규칙 또는 디렉터리 구조 등이 명시된 개발 표준 가이드를 정의하고, 개발자가 이에 맞춰 개발할 수 있도록 한다.
문제 목표 및 기능 요약
해당 문제의 목표는 관리자 권한을 획득해 FLAG를 획득하는 것이다. 문제에선 다음 두 페이지를 제공한다.
| 페이지 | 설명 |
|---|---|
/ |
이용자의 username을 출력하고 관리자 계정인지 확인합니다. |
/login |
username, password를 입력받고 로그인합니다. |
웹 서비스 분석
엔드포인트: /
다음 코드는 인덱스 페이지를 구성하는 코드다. 해당 페이지에서는 요청에 포함된 쿠키를 통해 이용자를 식별한다. 만약 쿠키에 존재하는 username이 "admin"일 경우 FLAG를 출력한다.
index 페이지 코드
@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
다음 코드는 로그인 페이지를 구성하는 코드다. 코드를 살펴보면, 메소드에 따른 요청마다 다른 기능을 수행하는 것을 알 수 있다.
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
username과 password를 입력할 수 있는 로그인 페이지를 제공한다.
POST
이용자가 입력한username과 password 입력 값을 users 변수값과 비교한다. 다음 코드는 users 변수가 선언된 코드다. 코드를 살펴보면, 손님 계정의 비밀번호는 "guest", 관리자 계정의 비밀번호는 파일에서 읽어온 FLAG 임을 알 수 있다.
users 변수 선언
try:
FLAG = open('./flag.txt', 'r').read() # flag.txt 파일로부터 FLAG 데이터를 가져옴.
except:
FLAG = '[**FLAG**]'
users = {
'guest': 'guest',
'admin': FLAG # FLAG 데이터를 패스워드로 선언
}
취약점 분석
취약점 분석
다음 코드를 살펴보면 이용자의 계정을 나타내는 username 변수가 요청에 포함된 쿠키에 의해 결정되어 문제가 발생한다. 쿠키는 클라이언트의 요청에 포함되는 정보로, 이용자가 임의로 조작할 수 있다. 서버는 별다른 검증 없이 이용자 요청에 포함된 쿠키를 신뢰하고, 이용자 인증 정보를 식별하기 때문에 공격자는 쿠키에 타 정보를 삽입해 계정을 탈취할 수 있다.
index 페이지 코드
@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')
익스플로잇
익스플로잇
쿠키에 존재하는 username을 "admin" 문자열로 조작해야 한다. 개발자 도구를 사용하면 쿠키의 정보를 확인하거나 수정할 수 있다.
username을 "admin"으로 변경해 서버에 요청하면 FLAG를 획득할 수 있다.


후기
이번 문제를 통해 서버가 검증 없이 쿠키를 신뢰하고 인증 정보를 식별할 때 발생 할 수 있는 문제점에 대해서 알아봤다. 실습에서는 해당 취약점을 이용하여 다른 계정의 권한을 획득할 수 있었다.
하지만 이러한 문제점은 세션을 사용해 해결할 수 있다. 세션은 인증 정보를 서버에 저장하고, 랜덤한 키를 클라이언트에게 발급한다. 클라이언트는 해당 키를 포함해 서버에게 요청하고, 서버는 저장한 세션 키와 대응하는 클라이언트인지 확인한다.
작성 기록과 문제 출처
원문과 출처
풀이와 설명: MadEyE / 이선용의 개인 Notion 기록.
문제에서 제공한 코드·서비스 화면과 외부 참고자료의 권리는 해당 작성자와 플랫폼에 있습니다.
원문 본문·코드·실습 증적을 보존했으며, 원문에 포함된 외부 글은 참고 링크로 안내합니다.
이미지 원본 출처
다음 글