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둜 바꿔쀄 수 μžˆλ‹€λ©΄ λ³΄μ•ˆμ΄ λ„ˆλ¬΄ ν—ˆμˆ ν•œ 게 μ•„λ‹Œκ°€...ν•˜λŠ” 생각이 λ“€μ—ˆλŠ”λ°,

항상 이 방법을 μ‚¬μš©ν•  수 μžˆλŠ” 것인지 κΆκΈˆν•˜λ‹€.