SWLUG/CTF ๋ฌธ์ œ ํ’€์ด (2)

[Dreamhack/๋“œ๋ฆผํ•ต] pathtraversal

waterproof 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๋กœ ๋ฐ”๊ฟ”์ค„ ์ˆ˜ ์žˆ๋‹ค๋ฉด ๋ณด์•ˆ์ด ๋„ˆ๋ฌด ํ—ˆ์ˆ ํ•œ ๊ฒŒ ์•„๋‹Œ๊ฐ€...ํ•˜๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋Š”๋ฐ,

ํ•ญ์ƒ ์ด ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ธ์ง€ ๊ถ๊ธˆํ•˜๋‹ค.