前几天看了redpwn的一道web题,node.js的web,涉及知识点是javascript 原型链污染,以前没咋接触过js,并且这个洞貌似也比较新,因此记录一下学习过程

1.本机node.js环境安装

题目都给了源码,所以本地就可以直接安装package.json依赖并本地模拟调试

首先subline安装node环境:

http://nodejs.org/

https://www.cnblogs.com/bluesky4485/p/3928364.html

https://github.com/tanepiper/SublimeText-Nodejs

2.题目源码

const crypto = require('crypto')
const http = require('http')
const mustache = require('mustache')
const getRawBody = require('raw-body')
const _ = require('lodash')
const flag = require('./flag') const indexTemplate = `
<!doctype html>
<style>
body {
background: #172159;
}
* {
color: #fff;
}
</style>
<h1>your public blueprints!</h1>
<i>(in compliance with military-grade security, we only show the public ones. you must have the unique URL to access private blueprints.)</i>
<br>
{{#blueprints}}
{{#public}}
<div><br><a href="/blueprints/{{id}}">blueprint</a>: {{content}}<br></div>
{{/public}}
{{/blueprints}}
<br><a href="/make">make your own blueprint!</a>
` const blueprintTemplate = `
<!doctype html>
<style>
body {
background: #172159;
color: #fff;
}
</style>
<h1>blueprint!</h1>
{{content}}
` const notFoundPage = `
<!doctype html>
<style>
body {
background: #172159;
color: #fff;
}
</style>
<h1>404</h1>
` const makePage = `
<!doctype html>
<style>
body {
background: #172159;
color: #fff;
}
</style>
<div>content:</div>
<textarea id="content"></textarea>
<br>
<span>public:</span>
<input type="checkbox" id="public">
<br><br>
<button id="submit">create blueprint!</button>
<script>
submit.addEventListener('click', () => {
fetch('/make', {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
content: content.value,
public: public.checked,
})
}).then(res => res.text()).then(id => location='/blueprints/' + id)
})
</script>
` // very janky, but it works
const parseUserId = (cookies) => {
if (cookies === undefined) {
return null
}
const userIdCookie = cookies.split('; ').find(cookie => cookie.startsWith('user_id='))
if (userIdCookie === undefined) {
return null
}
return decodeURIComponent(userIdCookie.replace('user_id=', ''))
} const makeId = () => crypto.randomBytes(16).toString('hex') // list of users and blueprints
const users = new Map() http.createServer((req, res) => {
let userId = parseUserId(req.headers.cookie)
let user = users.get(userId)
if (userId === null || user === undefined) {
// create user if one doesnt exist
userId = makeId()
user = {
blueprints: {
[makeId()]: {
content: flag,
},
},
}
users.set(userId, user)
} // send back the user id
res.writeHead(200, {
'set-cookie': 'user_id=' + encodeURIComponent(userId) + '; Path=/',
}) if (req.url === '/' && req.method === 'GET') {
// list all public blueprints
res.end(mustache.render(indexTemplate, {
blueprints: Object.entries(user.blueprints).map(([k, v]) => ({
id: k,
content: v.content,
public: v.public,
})),
}))
} else if (req.url.startsWith('/blueprints/') && req.method === 'GET') {
// show an individual blueprint, including private ones
const blueprintId = req.url.replace('/blueprints/', '')
if (user.blueprints[blueprintId] === undefined) {
res.end(notFoundPage)
return
}
res.end(mustache.render(blueprintTemplate, {
content: user.blueprints[blueprintId].content,
}))
} else if (req.url === '/make' && req.method === 'GET') {
// show the static blueprint creation page
res.end(makePage)
} else if (req.url === '/make' && req.method === 'POST') {
// API used by the creation page
getRawBody(req, {
limit: '1mb',
}, (err, body) => {
if (err) {
throw err
}
let parsedBody
try {
// default values are easier to do than proper input validation
parsedBody = _.defaultsDeep({
publiс: false, // default private
cоntent: '', // default no content
}, JSON.parse(body))
} catch (e) {
res.end('bad json')
return
} // make the blueprint
const blueprintId = makeId()
user.blueprints[blueprintId] = {
content: parsedBody.content,
public: parsedBody.public,
} res.end(blueprintId)
})
} else {
res.end(notFoundPage)
}
}).listen(80, () => {
console.log('listening on port 80')
})

题目的逻辑也比较清楚,通过一个随机hash作为索引来存储每个人的content和public,这里flag存在的对应hash里不存在public,函数在后面遍历所有存储的content时就不会输出带有flag的content,因此目的也很明确,就是让flag对应的public也为ture,从而遍历输出所有content,函数里面还提供了一个根据单独的hash id来返回content,刚开始我以为randbytes可能可以碰撞,google了一下似乎这个是不太安全,但是重复的几率几乎可以不用考虑,所以这里就要涉及到原型链污染,因为是第一次接触原型链勿扰,我也去看了几篇文章,先补补基础

3.基础部分

这张图来自先知上一个师傅的文章。

推荐p牛的文章https://www.freebuf.com/articles/web/200406.html

在js里面万物皆对象,而js提供了一些方法能够使类的对象来访问类中的属性,而原型prototype是类的一个属性,通过类名+prototype就能够访问类中所有的属性和方法,比如定义了类Foo,那么Foo.protype就是该类的原型,那么该原型有两个属性:构造器constructor和__proto__属性,constructor可以当成该类的构造函数,实际上就是它自身,而Foo类的原型的__proto__就是object类,即又向上找到了新的原型

我们可以通过Foo.prototype来访问Foo类的原型,但Foo实例化出来的对象,是不能通过prototype访问原型的。这时候,就该proto登场了。一个Foo类实例化出来的foo对象,可以通过foo.proto属性来访问Foo类的原型,也就是说:

即有以下两点:

1.prototype是一个类的属性,所有类对象在实例化的时候将会拥有prototype中的属性和方法

2.一个对象的proto属性,指向这个对象所在的类的prototype属性

而关于javascript继承要知道以下几点:

1.每个构造函数(constructor)都有一个原型对象(prototype)

2.对象的proto属性,指向类的原型对象prototype

3.JavaScript使用prototype链实现继承机制

4.Object.prototype的proto就是null

而关于原型链污染要知道以下几点:

我们已经知道类的对象的proto属性指向类的prototype,那么我们修改了proto属性的值,那么将对类中原有属性产生影响

这修改了foo.__proto__的bar属性实际上就是给foo的类添加了一个bar属性,那么修改了以后当第一次查找bar值时会首先在foo对象自身找这个值,如果没找到的话再在foo.__proto__中找,而从下图可以看出来__proto__的确存在bar属性

那么新建的对象也是{},因此他们实际上的__proto__指向是相同的,和python的基类有点像,因此新建的对象将在__proto__中找到bar值。

应用场景:

我们只要能够控制数据或对象的键名即可,p牛提到了两种

1.对象merge

2.对象clone(其实内核就是将待操作的对象merge到一个空对象中)

我在国外的pdf中发现了更多容易受到攻击的库,其中就包括4月份爆的ldhash的漏洞,也就是题目中要考察的,要看pdf的师傅可以留言邮箱,可以共同研究

3.简单例子分析

假如有以上merge()函数,那么其中我们要是能够控制target的key,使其为__proto__,那么就有可能导致原型链污染

这里执行merge以后理论上应该新建一个新的对象,其也存在b为2,但是如下

这是因为,我们用JavaScript创建o2的过程(let o2 = {a: 1, “proto“: {b: 2}})中,proto已经代表o2的原型了,此时遍历o2的所有键名,拿到的是[a, b],proto并不是一个key,自然也不会修改Object的原型。

所以这里必须配合上json.parse,也就是JSON解析的情况下,proto会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历o2的时候会存在这个键。

此时就成功对原型链进行了污染

4.回到题目

题目中使用的package.json中ldhash的版本过低

题目中使用defaultDeep来进行拷贝赋值操作,这里使用json.parse来接续body中的json数据,并且json数据是可控的,没有过滤的

注意到这里题目flag对应的键名为content,并且包含content的是{},所以这里应该通过defaultDeep来使原型指向到{}的原型,从而来对其属性进行注入。

这里如果只是注入单纯的content属性的话,相当于执行{}."content"={}."content",这里这样不能操控我们前面所谓的键名,于是我们可以多传递一个"constructor"的构造器属性,来构成

{}."constructor"."protype"

这里要注意令a为{},那么a的构造器即为object类即 f Object,那么再调用prototype即可访问{}中所有的值,即对象中所有值,这里a.__proto__和a.constructor.prototype是等价的,我们可以把let a={},可以看作类object的

对象,这样和前面说的Foo.prototype=foo.__proto__就是同样的道理了。

但是这里和题目又有点稍微的不一致,因为这里a是实例化的有名字的对象,而题目中是{},可以在F12 console中看到它是没有__proto__属性的,因此不能够通过{}.__proto__来访问到object原型,但是{}是有唯一一个构造器属性的来返回object类,所以这里payload可以构造为

"constructor":{"prototype":{"public":true}}

这样就相当于给{content:flag}多添加了一条public属性,那么当访问flag对应的hash id去取puclic的值时,就会通过继承链到object中找,就能找到我们注入的public:true

这样就能拿到flag

5.总结

  这篇文章只是介绍了利用原型污染来注入属性,那么利用该种攻击方式实际上还可以进行dos、命令执行攻击,但是原理都是相同的,我们可以通过控制键名来“向上爬”,根据继承链去找对象的原型,从而污染原型。

参考:

https://www.freebuf.com/articles/web/200406.html

https://xz.aliyun.com/t/2802

redpwnctf-web-blueprint-javascript 原型链污染学习总结的更多相关文章

  1. 初探JavaScript原型链污染

    18年p师傅在知识星球出了一些代码审计题目,其中就有一道难度为hard的js题目(Thejs)为原型链污染攻击,而当时我因为太忙了(其实是太菜了,流下了没技术的泪水)并没有认真看过,后续在p师傅写出w ...

  2. javascript 原型链污染

    原理①javascript中构造函数就相当于类,并且可以将其实例化 ②javascript的每一个函数都有一个prototype属性,用来指向该构造函数的原型同样的javascript的每一个实例对象 ...

  3. JavaScript原型链及其污染

    JavaScript原型链及其污染 一.什么是原型链? 1.JavaScript中,我们如果要define一个类,需要以define"构造函数"的方式来define: functi ...

  4. JavaScript学习总结(十七)——Javascript原型链的原理

    一.JavaScript原型链 ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法.其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法.在JavaScript中, ...

  5. 【web安全】Nodejs原型链污染分析

    Nodejs原型链污染分析 什么是js原型? 可以将js原型理解为其他OOP语言中的类,但还是有细微区别. 1. function F(){...} 2. var f = new F(); 分析: 1 ...

  6. Javascript 原型链资料收集

    Javascript 原型链资料收集 先收集,后理解. 理解JavaScript的原型链和继承 https://blog.oyanglul.us/javascript/understand-proto ...

  7. 原型链污染(Node.js污染,javasrcipt原型链污染的)

    学习链接: https://www.jianshu.com/p/6e623e9debe3 关于NJS  https://xz.aliyun.com/t/7184 相关题是 GYCTF  ez_expr ...

  8. javascript原型链中 this 的指向

    为了弄清楚Javascript原型链中的this指向问题,我写了个代码来测试: var d = { d: 40 }; var a = { x: 10, calculate: function (z) ...

  9. 明白JavaScript原型链和JavaScrip继承

    原型链是JavaScript的基础性内容之一.其本质是JavaScript内部的设计逻辑. 首先看一组代码: <script type="text/javascript"&g ...

随机推荐

  1. 9.18考试 第二题Dinner题解

    当时初步感觉是一个类似动归或者贪心的神题,然而由于本题已经给出顺序,贪心貌似并没有什么道理,所以放弃贪心.然后又由于这是一个环的问题,我想到了“合并石子”那种环转链的思路,然后就是一个O(n^2*m) ...

  2. jekyll搭建个人博客2

    目录 个性化 jekyll目录结构 修改个人信息 修改头像 修改背景颜色 关于头像的效果 图片问题 域名 个性化 jekyll目录结构 个性化就是要对文件内容作出修改,使得博客外观发生变化,在修改文件 ...

  3. Apache struts2 Freemarker标签远程命令执行_CVE-2017-12611(S2-053)漏洞复现

    Apache struts2 Freemarker标签远程命令执行_CVE-2017-12611(S2-053)漏洞复现 一.漏洞描述 Struts2在使用Freemarker模块引擎的时候,同时允许 ...

  4. KdTree && Octree 原理学习对比以及可视化分析--"索引树"

    1. Kdtree 原理 k-d树(k-dimensional树的简称),是一种分割k维数据空间的数据结构.主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索): 索引结构中相似性查询有两种基 ...

  5. 洛谷P4994 终于结束的起点 题解

    求赞,求回复,求关注~ 题目:https://www.luogu.org/problemnew/show/P4994 这道题和斐波那契数列的本质没有什么区别... 分析: 这道题应该就是一个斐波那契数 ...

  6. 个人永久性免费-Excel催化剂功能第27波-Excel工作表设置快捷操作

    Excel催化剂在完善了数据分析场景的插件需求后,决定再补充一些日常绝大多数Excel用户同样可以使用到的小功能,欢迎小白入场,在不违背太多Excel最佳实践的前提下,Excel催化剂乐意为广大Exc ...

  7. tablayout_不能左右滑动问题小计

    <android.support.design.widget.TabLayout android:id="@+id/tab_pic" android:layout_width ...

  8. Android studio 混淆打包安装后报错NullPointerException int java.util.List.size()

    菜鸟的我,尝试混淆打包app...打包之前没有什么问题,混淆打包之后遇到各种问题.首先,感谢原博主的分享.解决了我的问题.谢谢. 原文地址:http://blog.csdn.net/tou_star/ ...

  9. 【Gym - 101002F】Mountain Scenes(dp)

    Mountain Scenes Descriptions 给你一个长度为n的丝带,一个宽w一个高h 的 格子,用丝带去填充格子,这填充后只需要满足至少有一列的丝带长度与其他格子不同即可.丝带可以不全部 ...

  10. Flutter学习笔记(10)--容器组件、图片组件

    如需转载,请注明出处:Flutter学习笔记(10)--容器组件.图片组件 上一篇Flutter学习笔记(9)--组件Widget我们说到了在Flutter中一个非常重要的理念"一切皆为组件 ...