위의 문제를 다음과 같은 과정으로 풀어보았다.
1. 소스코드를 살펴보면 총 5개의 엔드포인트를 확인할 수 있다.
(1) 인덱스( / )
@app.route("/")
def index():
session_id = request.cookies.get('sessionid', None)
try:
username = session_storage[session_id]
except KeyError:
return render_template('index.html', text='please login')
return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not an admin"}')
맨 마지막 줄에서 username이 admin인 경우에 flag를 출력하는 코드를 확인할 수 있다.
(2) /vuln
@app.route("/vuln")
def vuln():
param = request.args.get("param", "").lower()
xss_filter = ["frame", "script", "on"]
for _ in xss_filter:
param = param.replace(_, "*")
return param
xss_filter로 xss 공격에 쓰일 수 있는 3개의 키워드를 *로 대체함으로써 xss 공격에 대한 방어를 하고 있는 것을 확인할 수 있지만, 최종적으로 param 파라미터를 그대로 return 하고 있어 3개의 키워드에 포함되지 않는 코드를 활용한 공격에 대애서는 취약점을 갖고 있음을 알 수 있다.
(3) /flag
@app.route("/flag", methods=["GET", "POST"])
def flag():
if request.method == "GET":
return render_template("flag.html")
elif request.method == "POST":
param = request.form.get("param", "")
session_id = os.urandom(16).hex()
session_storage[session_id] = 'admin'
if not check_csrf(param, {"name":"sessionid", "value": session_id}):
return '<script>alert("wrong??");history.go(-1);</script>'
return '<script>alert("good");history.go(-1);</script>'
/flag 에서는 GET과 POST 두가지 메소드에 대한 동작을 하고 있는데, 인덱스 페이지에서 login페이지의 링크를 타고 접속하면 GET으로 페이지가 요청되어 flag.html을 렌더링하여 보여주고, flag.html에서 url 값을 입력하여 제출하면 POST로 요청되어 session_id를 랜덤으로 생성하고 check_csrf() 함수를 실행하는 동작을 하는 것을 볼 수 있다.
def check_csrf(param, cookie={"name": "name", "value": "value"}):
url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}"
return read_url(url, cookie)
check_csrf() 함수에서는 param 파라미터로 전달받은 flag페이지에서의 입력값을 이용해 url을 생성하여 read_url() 함수를 통해 url을 방문하는 것을 확인할 수 있다. 이때 url은 취약점이 있는 vuln 페이지를 요청하고 있기 때문에 flag페이지에서 param 파라미터에 값을 입력할 때 익스플로잇 코드를 입력하면 vuln 페이지에 있는 취약점을 이용해 해당 코드를 실행시킬 수 있을 것이다.
(4) /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:
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(8).hex()
session_storage[session_id] = username
resp.set_cookie('sessionid', session_id)
return resp
return '<script>alert("wrong password");history.go(-1);</script>'
/login 에서도 /flag와 마찬가지로 GET, POST 두가지 메소드에 대한 동작을 하는데, GET으로 요청하여 렌더링된 페이지에 username, password를 올바르게 입력하여 로그인하면 index 페이지로 이동한다.
(5) /change_password
@app.route("/change_password")
def change_password():
pw = request.args.get("pw", "")
session_id = request.cookies.get('sessionid', None)
try:
username = session_storage[session_id]
except KeyError:
return render_template('index.html', text='please login')
users[username] = pw
return 'Done'
users = {
'guest': 'guest',
'admin': FLAG
}
/change_password에서는 사용자가 pw 파라미터로 입력한 값으로 users 딕셔너리 변수의 패스워드를 설정한다.
2. flag 페이지에서 익스플로잇 코드를 입력하여 제출한다.
flag 페이지의 url은 vuln 페이지를 요청하고 있다. 그러므로 /vuln에서 차단하고 있는 frame, script, on 키워드를 피해 img 태그를 활용하여 csrf 공격을 시도할 것이다. /change_password에 접속하여 admin 계정의 비밀번호를 'aa'로 지정하여 admin 계정을 탈취할 것이다.
<img src="/change_password?pw=aa">
위의 코드를 flag 페이지의 url 입력란에 작성하여 제출한다.
3. login 페이지에서 'username : admin, password : 설정한 pw'로 로그인한다.
username은 admin, password는 앞서 설정한 aa로 하여 로그인한다.
4. 인덱스 페이지에서 flag를 확인할 수 있다.
'드림핵' 카테고리의 다른 글
[드림핵 워게임] Mango (0) | 2022.10.01 |
---|---|
[드림핵 워게임] simple-sqli (1) | 2022.09.24 |
[드림핵 워게임] csrf-1 (1) | 2022.09.23 |
[드림핵 워게임] xss-2 (1) | 2022.09.21 |
[드림핵 워게임] xss-1 (1) | 2022.09.21 |