[CTF] TokyoWesternsCTF2020

urlcheck v1(webhacking)

レ(゚∀゚;)ヘ=З=З=З 2023. 12. 21. 10:41
728x90

주어진 코드 (app.py)

import os, re, requests, flask
from urllib.parse import urlparse

app = flask.Flask(__name__)
app.flag = '***CENSORED***'
app.re_ip = re.compile('\A(\d+)\.(\d+)\.(\d+)\.(\d+)\Z')

def valid_ip(ip): #Server-Side Request Forgery
    matches = app.re_ip.match(ip)
    if matches == None:
        return False

    ip = list(map(int, matches.groups()))
    if any(i > 255 for i in ip) == True:
        return False
    # Stay out of my private!
    if ip[0] in [0, 10, 127] \
        or (ip[0] == 172 and (ip[1] > 15 or ip[1] < 32)) \
        or (ip[0] == 169 and ip[1] == 254) \
        or (ip[0] == 192 and ip[1] == 168):
        return False
    return True

def get(url, recursive_count=0):
    r = requests.get(url, allow_redirects=False)
    if 'location' in r.headers:
        if recursive_count > 2:
            return '&#x1f914;'
        url = r.headers.get('location')
        if valid_ip(urlparse(url).netloc) == False:
            return '&#x1f914;'
        return get(url, recursive_count + 1) 
    return r.text

@app.route('/admin-status')
def admin_status():
    if flask.request.remote_addr != '127.0.0.1':
        return '&#x1f97a;'
    return app.flag

@app.route('/check-status')
def check_status():
    url = flask.request.args.get('url', '')
    if valid_ip(urlparse(url).netloc) == False: #scheme://username:passwd@netloc:port/path;params?query#fragment
        return '&#x1f97a;'
    return get(url)

 

처음 들어갔을 때의 화면이다. 특정 URL을 입력하는 것 같아 보인다. 

1)

check_status()에서 43번째 줄인 url = flask.request.args.get('url', ' ')부분을 먼저 해석해보았다.

flask은 파이썬 플라스크이며 url 피라미터로 값을 입력받는다. 또한 request모듈이 필요로 하여 위에 import로 추가되었고, request.args를 사용한다. 여기서 request.args는 url피라미터 값을 '키 = 값'으로 갖고있는 딕셔너리이다. flask.request.args.get(‘url’, ‘’)을 통해 ‘url’이라는 키에 값을 넣는 것을 알 수 있다.

44번째 줄에 있는 urlparse(url)은 url을 튜플의 하위 클래스인 ParseResult 클래스로 변환해준다. scheme://username:passwd@netloc:port/path;params?query#fragment 이것과 같은 형태로 나타나며 소스코드에서는 변환된 url의 속성들 중 네트워크 위치를 나타내는 netloc(username:passwd@netloc:port부분)에 접근한다. 그 값을 valid_ip함수에 넣어주고 false이면 이모티콘을 return하고 아니면 get 함수에 url을 매개변수로 넘겨주며 실행한다.

admin_status()에서는 주소값처럼 보이는 ‘127.0.0.1’의 값이 현재의 ip주소를 반환해주는 flask.request.remote_addr의 값과 동일하지 않으면 이모티콘을 return하고 아니면 get함수에 url을 매개변수로 넘겨주며 실행한다.

즉, 입력된 URL check_status()의urlparse(url).netloc함수로 전달되어서 URL을 해석하여 스키마(ex-http, ftp) 뒤부터 경로부분(/path) 앞까지 반환하고, get함수에 전달된다.

2)

쌍으로(키와 값)으로 저장하는 map으로 ip를 저장하는 valid_ip(ip)함수에 ip가 전달된다. valid_ip(ip)함수는 Server-Side Request Forgery라는 공격을 피하기 위해서 다시 확인하는 과정이라고 한다. Server-Side Request Forgery 은 Sever Side에서 이루어지는 요청을 변조해 해커가 의도한 서버로 요청을 가게 되거나 요청 자체를 변경할 수 있는 공격이다.

3)

2번 과정에서 SSRF에 대해 통과가 되면 서버는 URL을 요청하고 플래그를 가져 오기 위해 localhost인 127.0.0.1(check_status()함수의 44번째 줄에 나와있음)을 /admin-status에 요청해야한다. ip는 10진수로 표기되기 때문에 8진수 표기법을 사용해서 valid_ip(ip)함수 검사를 우회한다.

4)

127.0.0.1을 8진수로 바꾸기 위해 일단 2진수로 바꾸어 주었더니 1111111.0.0.1이 나왔다. 이를 다시 8진수로 바꾸면 (1/111/111 => 1+7+7) 0177.0.0.1이 나왔다. 따라서 8진수인 0177.0.0.1을 로컬 호스트에 요청을 보내게 된다.

모든 조건을 통과하 서버가 url을 요청하면 8진수 표기되어있던 ip가 일반ip인 10진수로 변환이 된다.

5)

http://urlcheck1.chal.ctf.westerns.tokyo/에 나와있는 예시와 같은 형태로 http://0177.0.0.1/admin-status을 입력을 해준다.

사진과 같이 TWCTF{4r3_y0u_r34dy?n3x7_57463_15_r34l_55rf!}이라는 Flag를 얻었다.

 

 

'[CTF] TokyoWesternsCTF2020' 카테고리의 다른 글

Mask(misc)  (0) 2023.12.21
Easy_hash(crypto)  (0) 2023.12.21