[Dreamhack/드림핵] pathtraversal
https://dreamhack.io/wargame/challenges/12
pathtraversal
사용자의 정보를 조회하는 API 서버입니다. Path Traversal 취약점을 이용해 /api/flag에 있는 플래그를 획득하세요! Reference Server-side Basic
dreamhack.io
[1] 문제 분석
문제 설명에 의하면 Path Traversal 취약점을 이용해야 한다.
따라서 먼저 Path Traversal 취약점에 대해 알아보고자 한다.
Path Traversal 취약점이란?
일반적으로 경로 순회(Path Traversal)라고도 불리며, 이 취약점을 이용하여 공격자는 응용 프로그램이나 서버의 파일 시스템 경로를 벗어나려고 시도한다. 보통은 파일 경로에 "../" 또는 상위 디렉토리 구분자를 삽입하여 파일에 대한 상대적인 경로를 변경하여 공격을 시도한다. 이를 통해 공격자는 일반적으로는 액세스할 수 없는 파일이나 디렉토리에 액세스할 수 있다.
예를 들어, 웹 응용 프로그램에서 파일을 로드할 때 사용자 입력을 경로에 직접 삽입하는 경우, 공격자가 해당 입력을 조작하여 시스템 파일에 액세스할 수 있다. 이를 방지하기 위해서는 입력을 검증하고, 신뢰할 수 있는 파일 경로만 허용하거나, 파일 시스템 액세스에 대한 적절한 권한을 설정하는 등의 보안 조치를 취할 수 있다.
참고: https://jini23-lamp.tistory.com/entry/PathDirectory-Traversal
https://owasp.org/www-community/attacks/Path_Traversal
# 웹 페이지 분석
문제 링크에 접속하면 처음 나오는 페이지이다.
Get User Info 를 눌러 보았다.
아마 저기에 입력값을 넣을 때
"../" --> 이런 형식을 넣어주어서 Path Traversal 취약점을 악용할 수 있게 되지 않을까 싶다.
우선 입력칸에 "guest" 가 기본값으로 입력이 되어있길래 그대로 View 를 눌러보았다.
이런 결과 화면이 나온다.
무슨 의미일까?...
"admin"도 입력해보았다.
이번엔 다른 결과물이 나왔다.
"userid"와 "admin"의 값으로는 userid 입력 칸에 들어간 값이 무조건 나오는 걸까?
"level"은 어떻게 정해지는 걸까?
Path Traversal 취약점이 "../" 형식을 입력하여 다른 디렉토리를 경로를 순회할 수 있는 방식으로 이루어진다고 했으니까
"../"를 입력해보았다.
{} 안에 아무런 값도 나오지 않는다.
아마 필터링 되는 무언가가 설정되어있지 않을까... 하는 생각이 들었다.
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="/static/css/non-responsive.css">
<title>Index Path Traversal</title>
<style type="text/css">
.important { color: #336699; }
</style>
</head>
<body>
<!-- Fixed navbar -->
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">Path Traversal</a>
</div>
<div id="navbar">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
<div class="container">
<a href="/get_info">Get User Info</a>
</div> <!-- /container -->
<!-- Bootstrap core JavaScript -->
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
</body>
</html>
웹 페이지 소스코드에서는 딱히 알아낼 수 있는 게 없어서 문제 파일을 다운받아보기로 한다.
# 문제 파일 분석
app.py 하나를 다운받을 수 있다.
#!/usr/bin/python3
from flask import Flask, request, render_template, abort
from functools import wraps
import requests
import os, json
users = {
'0': {
'userid': 'guest',
'level': 1,
'password': 'guest'
},
'1': {
'userid': 'admin',
'level': 9999,
'password': 'admin'
}
}
def internal_api(func):
@wraps(func)
def decorated_view(*args, **kwargs):
if request.remote_addr == '127.0.0.1':
return func(*args, **kwargs)
else:
abort(401)
return decorated_view
app = Flask(__name__)
app.secret_key = os.urandom(32)
API_HOST = 'http://127.0.0.1:8000'
try:
FLAG = open('./flag.txt', 'r').read() # Flag is here!!
except:
FLAG = '[**FLAG**]'
@app.route('/')
def index():
return render_template('index.html')
@app.route('/get_info', methods=['GET', 'POST'])
def get_info():
if request.method == 'GET':
return render_template('get_info.html')
elif request.method == 'POST':
userid = request.form.get('userid', '')
info = requests.get(f'{API_HOST}/api/user/{userid}').text
return render_template('get_info.html', info=info)
@app.route('/api')
@internal_api
def api():
return '/user/<uid>, /flag'
@app.route('/api/user/<uid>')
@internal_api
def get_flag(uid):
try:
info = users[uid]
except:
info = {}
return json.dumps(info)
@app.route('/api/flag')
@internal_api
def flag():
return FLAG
application = app # app.run(host='0.0.0.0', port=8000)
# Dockerfile
# ENTRYPOINT ["uwsgi", "--socket", "0.0.0.0:8000", "--protocol=http", "--threads", "4", "--wsgi-file", "app.py"]
전체 중에 입력 값 저장 부분의 코드를 분석해보자.
elif request.method == 'POST':
userid = request.form.get('userid', '')
info = requests.get(f'{API_HOST}/api/user/{userid}').text
return render_template('get_info.html', info=info)
입력된 userid 값을 위 그림에서 볼 수 있듯이, /api/user/{userid} 에 저장되는 것을 볼 수 있다.
@app.route('/api/flag')
@internal_api
def flag():
return FLAG
FLAG는 /api/flag에 저장이 된다.
저장된 값은 /api/user/{userid} 에,
FLAG 값은 /api/flag 에 저장이 되기 때문에,
../flag 를 입력하면 /api/flag 에 접근할 수 있을 것이라고 생각하고 ../flag를 입력해보았다.
안 됩니다...
여기서부터 구글링을 통해 문제 풀이를 진행하였다.
users = {
'0': {
'userid': 'guest',
'level': 1,
'password': 'guest'
},
'1': {
'userid': 'admin',
'level': 9999,
'password': 'admin'
}
}
이 부분을 보면 guest 와 admin의 key-value 쌍을 볼 수 있다.
console창을 이용해서 이 값을 ../flag로 바꿔주었다.
guest를 입력하면 flag가 뜨는 것을 확인할 수 있다.
[2] 느낀 점
웹 페이지의 변수값을 console로 바꿔줄 수 있다면 보안이 너무 허술한 게 아닌가...하는 생각이 들었는데,
항상 이 방법을 사용할 수 있는 것인지 궁금하다.