Hacking (CTF)/CTF

[Dreamhack/드림핵] pathtraversal

gapsoo 2024. 5. 15. 08:46

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로 바꿔줄 수 있다면 보안이 너무 허술한 게 아닌가...하는 생각이 들었는데,

항상 이 방법을 사용할 수 있는 것인지 궁금하다.