1일 1문제 START
이번에 풀어볼 문제는 Path Traversal 취약점을 이용하여
/api/flag에 있는 플래그를 획득하는 것이다.
해당 Path Traversal 취약점이 무엇인지 우선 알아보자.
Path Traversal(Directory traversal)이란?
사용자로부터 입력 받은 path 형태의 백엔드에서 서비스 처리 로직이 존재할 때,
이를 조작하여 원하는 경로로 해커가 접근하여 동작을 수행하는 공격기법을 의미한다.
File을 처리하거나 절대, 상대경로와 같은 구문으로 상위 디렉토리로 접근하거나
허용된 디렉토리 범위를 벗어나 공격을 수행하여 Directory traversal 이라고도 하는것 같다.
✔️ 여기에서 나오는 traversal이란?
: 순회라는 뜻으로 보통 트리 순회(Tree traversal) 등의 단어에서 사용된다.
순회라는 것은 여러 곳으로 돌아다니는 것이라고 정의되어 있는데,
해당 단어의 어원인 traverse는 가로지르다, 횡단하다, 방해하다 정도의 의미를 가진다.
⏩ Path(Directory) Traversal은 경로 또는 디렉토리를 가로지르다, 순회하다 정도로
해석하여 해당 경로와 디렉토리를 통한 공격을 의미한다고 생각하면 될 것 같다. (아닌가)
그렇다면 문제에서 주어진 화면과 코드를 살펴보자.
가상 환경에 진입하여 나온 페이지에서 Get User Info 링크를 타고와서
userid == guest 인 유저 정보를 View하면 다음과 같이 나온다.
그 다음, 주어진 코드를 살펴보자.
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
맨 위에서 차례로 살펴보자.
users 라는 변수에 0에는 우리가 앞서본 userid가 guest에 해당하는 정보가,
1에는 userid가 admin에 해당하는 정보가 들어있다.
실제로 페이지에서 admin을 입력하고 View를 누르면 같은 정보가 나온다.
❗잠깐, internal_api에서 return하는 func은 *args, **kwargs로 여러개의 인자를 받을 수 있다.
*args는 함수에서 여러개의 인자 n개를 받을 때를 의미하고,
**kwargs는 함수에서 여러개의 인자 n개를 key-value 형태로 받을 때를 의미한다.
그 다음, 살펴봐야할 곳이 /get_info를 route하는 코드인데,
여기서 GET 메소드를 받으면 우리가 보고있는 html 파일을 리턴하고
POST 메소드를 받으면 userid를 받아서 {API_HOST}/api/user/{userid} 경로의 .text를 보여주는 것 같다.
여기서 API_HOST는 위에 나오는 127.0.0.1:8000이고, userid는 form에서 받은 userid이다.
여기까지의 코드가 userid를 guest, admin으로 넣었을 때 폼 위에 띄워주는 정보인듯 하다.
그렇다면, 마지막으로 남은 /api 경로와 /api/user/<uid> 경로,
/api/flag 경로가 남아있는데 해당 route 함수의 상단에 internal_api가 데코레이터 되어있다.
맨 위에 internal_api에 대한 함수가 정의되어 있는데,
@wraps(func)를 찾아보니까 함수가 잘 작동하도록 유지하기 위해
functools.wraps(func)을 작성되어 있는 것 같다.
그다음 넘어가서 args와 kwargs를 받아오고 접속자 IP가 127.0.0.1이면 리턴을 그게 아니면,
401을 보내주어 다음과 같은 권한이 없다는 페이지를 보여준다.
그렇다면 어떻게 들어가야할까..
생각해보니까 폼에서 이미 remote_addr를 API_HOST로 받고 있고,
뒤에 /api/user에 userid를 받고 있다.
그렇다면, <uid> 부분을 어떻게 해결하면 flag가 뜨지 않을까? 생각해보았다.
그래서 밑에 코드를 보니까 /api/user/<uid> route 부분에서
users[uid]를 info에 저장해서 예외가 아니면, json에 info 정보를 dump 시켜준다.
그리고 flag를 폼에 입력하면 될 것 같은 킹리적 갓심이 한 10분 고민하다가 들었다.
바로 한번 실행에 옮겨보자.
근데, 문제가 path traversal 경로 조작이 아닌가.
그러면 폼에 들어가는 값이 ../flag면
remote_addr인 로컬 주소의 /api/flag에 접근할 수 있을 것 같다.
해당 폼에서 조작하는데, 하나도 소용이 없어서 힌트를 보니까
프록시를 사용하는 것이였고 프록시로 잡아보았다.
해당 밑줄 친 값을 ../flag로 바꾸어 경로 조작을 해주자.
다음과 같이 flag 값이 잘 출력되는 것을 확인할 수 있다.
충분히 고민해보고, 답을 확인하지 말고 접근 방법이 맞다면
힌트를 통해 푸는 것도 하나의 방법이 될 수 있을 것 같다.
'CTF > 웹해킹' 카테고리의 다른 글
[Dreamhack] simple-ssti (0) | 2022.09.06 |
---|---|
[Dreamhack] php-1 (0) | 2022.09.01 |
[Dreamhack] proxy-1 (0) | 2022.08.30 |
[Dreamhack] Web Hacking 초급 강의 후기 (0) | 2022.07.14 |
[Dreamhack] xss-2 문제 풀이 (0) | 2022.07.11 |