ctfshow nodejs篇
2021-7-2
| 2024-2-3
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
 
web342 很刺激 (
感觉对原型链污染理解又加深了呢~

web334 JS 大小写特性

搜集了半天信息,发现题目描述处有源码可以直接下载。。
user.js
login.js
即满足
我们输入的 name 不为 CTFSHOW 然后 name 转换后的大写为 CTFSHOW ,密码为 123456 即可。
notion image
网上看了一下,其实想要的考点是
toUpperCase() 函数,字符 ı 会转变为 I ,字符 ſ 会变为 S 。
toLowerCase() 函数中,字符 İ 会转变为 i ,字符 K 会转变为 k 。
可以参考
这样的应该是,比如字符 sS 不能出现在输入字符中就好了。

web335 RCE

f12 源码提示
即简单命令执行
获取 flag
notion image

web336 RCE 黑名单绕过

f12 源码提示
即简单命令执行
获取 flag
emm 好像就 flag 文件换了个名字?
顺便收集多几个命令执行
收集过程中发现,原来第 2、3 个 被禁了,2333 原来考察黑名单
先通过全局变量读取当前目录位置
notion image
当前目录
读文件
发现过滤了exec和load
notion image
绕过方法
因为 require('child_process') 方法返回一个对象,可以通过类型 Python数组的方式去访问里面的成员。
或者用其他模块读文件
当然还有其他姿势,比如变量拼接在执行
2023.11.6 更,发其他师傅那get到一种新姿势
notion image

web337 md5绕过

和 PHP 一样,数组绕过即可
notion image
发现这样更易于理解
payload: a[x]=1&b[x]=2
运行一下代码
notion image

web338 原型链污染

题目描述处蓝奏云下载源码
先看看这篇文章了解一波什么是 JS 的原型链污染
关键代码
utils/common.js
routes/login.js
也就是说,我们不用关心 secert 是否有 ctfshow 这个属性,因为当它找不到这个属性时,它会从它自己的原型里找。
这里的 secert 是一个数组,然后 utils.copy(user,req.body); 操作是 user 也是数组,也就是我们通过 req.body 即 POST 请求体传入参数,通过 user 污染数组的原型,那么 secert 数组找不到 ctfshow 属性时,会一直往原型找,直到在数组原型中发现 ctfshow 属性值为 36dboy 。那么 if 语句即判断成功,就会输出 flag 了。
payload
notion image
一般遇到这种整套代码,特别是带有 package.json 的,可以尝试 snyk
notion image
发现个 ejs 的 RCE
先污染参数
notion image
然后随便请求一下,触发 render 方法
notion image
成功反弹 shell
notion image
不过 snyk 给出的 poc 用 filename 参数利用好像有点问题, 不太清楚为啥。。即
顺便记录一下 node 项目本地启动方法,在项目文件运行下面命令即可。
notion image
默认端口为 3000

web339 原型链污染 +

node/v10.19.0
题目描述处蓝奏云下载源码
和 web338 有点相似,不过不同点是
flag 是变量,具体值不知
然后还多了个 api.js ,主要关注这行
是不是和 web338 里的参考链接 PHITHON 师傅出的 Code-Breaking 2018 Thejs 如出一辙?即可以 RCE,因为这里可污染点存在的匿名函数调用
notion image
仿照题目中的代码,先写个小demo
notion image
为什么 query 的值是 2233 呢?也就是为啥会被调用了呢?
首先看看 query 值是如何被改变的,其实就是通过 web338 的原型链污染,即 JS 中所有的对象的原型都可以继承到 Object,然后终点是 null 对象
notion image
如 web338 中所说的,当在当前上下文找不到相应对象时,会遍历 Object 对象是否存在相应的属性。
notion image
到这里就很清楚的知道了,为什么 query 的值是 "return 2233" ,因为在调用 copy 时,原型链被污染了。
至于 { query: Function(query)(query)} 为何为 { query: 2233 }
JS 的函数实际上都是一个 Function 对象,它的参数为
写个小demo
notion image
Function 对象传入构造函数里的前面参数是函数的形参,当然可以省略,最后的形参写函数体。
其实作用和 eval 有点类似,详细可以看
至此,利用思路明确了,只要污染了 query 对象,就可以执行任意我们想执行的代码,比如反弹个 shell 再获取 flag ~
然后污染点和 web338 一致,在 login.js 里的 utils.copy(user,req.body); ,代码执行的触发点在 api.jsres.render('api', { query: Function(query)(query)}); 处。
payload ,这里用 nodejs 原生 socket,防止因系统运行环境问题 shell 弹不回来,这里服务器的监听端口为 2233 ,然后如何是 windows 系统就把 /bin/sh 换成 cmd.exe 应该就可以了。
先本地试试,这里服务器监听端口为 2233
notion image
notion image
ok,没啥大问题。
先 POST 一下 login 接口,污染 query 对象
notion image
然后直接 POST 一下 api 接口即可。
emm,发现不行,之后访问 /login/api 接口都是 404 找不到文件 ,shell 也反弹不回来。
试试别人的
notion image
notion image
flag
notion image
仔细对比了一下,发现了,感觉是命名空间问题,即 require 可能不被识别,尝试把 require 改为 global.process.mainModule.constructor._load ,同样服务器监听端口为 2233
也是先 POST /login 接口污染 query 对象
notion image
访问下 /api 接口
notion image
反弹 shell 并获取 flag
notion image
然后顺便翻了个链接,好像确实如此~
因为 node 是基于 chrome v8 内核的,运行时,压根就不会有 require 这种关键字,模块加载不进来,自然 shell 就反弹不了了。但在 node交互环境,或者写 js 文件时,通过 node 运行会自动把 require 进行编译。
还有一个其他解,见 web338 中的 ejs RCE,一样的EXP、步骤和利用方式

web340 原型连污染 ++

nodejs/v10.19.0
题目描述处蓝奏云下载源码
 
提取关键部分
notion image
发现 userinfo 的原型不是 Object 对象, userinfo.__proto__.__proto__ 才是 Object 对象。
和 web339 一样,只不过要套两层才能污染 Object 对象,同样用原生 socket,服务端监听端口为 2233
同样 POST 一下 /login 接口污染 query 对象
notion image
POST 一下 /api 接口触发
notion image
获取 flag

web341 原型链污染 +++

题目描述处蓝奏云下载源码
 
这题和其他不同的是没有 /api 接口触发污染点了,所以使用 web338 中的 ejs RCE。
然后像 web340 一样污染要套两层。下面 EXP 也服务器监听端口是 2233
注意这里的前面的 _tmp1; 和后面的 var __tmp2 不能删,web338 evil0 的分析有体现到,是为了闭合代码。
同样 POST 一下 /login 接口污染数据
notion image
然后请求一个会调用 render 方法的接口
notion image
即可获得 flag
notion image

web342 原型链污染 ++++

题目描述处蓝奏云下载源码
 
然后还有个提示
审计了1个小时发现的,此链目前网上未公开,难度稍大
本题就不是用 ejs 进行模板渲染了,而是使用了 jade
一开始想看其他师傅的文章
https://xz.aliyun.com/t/7025
emm,但是发现说的每个字都认识,连起来就觉得有点莫名其妙了。。于是想着先自己手动分析一下,熟悉一波,方便自己和师傅在同一个频道上,防止师傅说的每句话都感觉莫名其妙…
详细的断点分析参考
这里用 VS Code 来 debug 分析
notion image
就会自动创建这样一个文件
然后下个断点,运行一下,访问网页就可以开始愉快的 debug 了
notion image
这里直接用 ctfshow web342 的代码进行
notion image
依次进入 res.render=>app.render=>tryRender=>view.render=>this.engine
notion image
入口是 rederFile 方法,注意 renderFile 函数返回值可执行
notion image
进入 handleTemplateCache
notion image
进入 compile 函数
notion image
205行 有个 parse 解析,可以看到结果返回到 parsed,又传递给了 fn,先不管 parse 方法,继续向下看代码
218行 这里就比较有趣了,有个看似可控的代码执行(new Function,这个在 web339 有提到)
进入 parse 看看返回值中是否有可控部分
notion image
parse 方法内部可以看到先内部 parse 再内部 compile,内部 parse 结果最终会被拼接到外层 parse 函数返回值部分
即 114行的 js对象 被拼接到 148行 或 149行的 js对象 里,最终拼接到 body键 返回
notion image
先跟进 114行compile 方法,发现本方法返回的是 buf
notion image
跟进 66行的 this.visit(this.node);
notion image
发现可控(因为 node.filenameutils.stringify() 了)的 node.line 可以被 pushbuf 中,条件是 this.debug=true
然后在 212行的 this.visitNode(node); 去遍历 ast 树,遍历完后回到 compile 方法
notion image
可以看到 node.line 被记录到 fn 中,然后通过 Function 调用执行这部分代码。
也就是说如果我们可以污染 node.line 就可以运行我们代码。
也就是说 jade 本身是没有漏洞的,因为模板的渲染逻辑是如何,但问题是,如果存在可控的原型链污染,就可以帮助我们污染 node.line
于是尝试 POST /login 接口
断点发现,, node.line 值还是 012 之类的?
突然醒悟,,还有一个问题,我们在调试过程中发现,node.line 是存在值的,比如上图中的 012 ,如果存在值的话,它会直接获取这个值,而不是获取我们污染过的 Object 对象里的值!
其实原型链污染的利用核心就是:访问对象的属性/值为 undefined 才行,不然我们污染 Object 对象就没有意义了。
现在看这篇文章就能看懂了,而且发现总结的非常好。。
好的,在同一频道上了。
先梳理上面分析的函数调用栈,我们断点入口在 routes/index.js res.render('index',{title:'ctfshow'}); ,调用栈如下
  1. routes/index.js :: res.render
  1. jade/lib/index.js :: exports.__express
  1. jade/lib/index.js :: exports.renderFile
  1. jade/lib/index.js :: handleTemplateCache
  1. jade/lib/index.js :: exports.compile
  1. jade/lib/index.js :: parse -> compiler.compile();
  1. jade/lib/compiler.js :: Compiler.compile -> this.visit(this.node)jade/lib/compiler.js :: this.visitjade/lib/compiler.js :: this.buf.push
  1. jade/lib/index.js :: parse -> options.selfjade/lib/index.js :: fn = new Function(‘locals, jade’, fn)jade/lib/index.js :: fn(locals, Object.create(runtime))
至 4. a. iii. 这部分调用栈用下图,4. b. c. d. 的在上面 debug 也有提及,因此调用栈这部分问题不大
notion image
问题有 3
  1. visit 方法中 this.debug=true ,不然 this.buf.push 调用不了
  1. 在上面提到的:node.line 在某处为 undefined 才行,不然我们污染的 Object 对象就没意义
  1. 保证能够执行到渲染阶段,因为覆盖某些属性会导致莫名其妙的异常
第 1 个问题容易解决,因为在 Jade 入口 exports.__express ,我们上面 deubg 分析时也看到
options.compileDebug 无初始值,可以覆盖开启 Debug 模式(经分析,this.debug 获取的就是这里的 debug 值),当然也有另外一种情况,部署时,没有正确配置 req.app.get('env') 导致 debug 模式开启,那么这个变量也可以不用覆盖,但为了确保通用性,这里还是覆盖一下,防止正确配置,2333。
这里因 utils.copy(user.userinfo,req.body); 与 web341一样,userinfo.__proto__.__proto__ 才是 Object 对象的原型,所以要套两层。
先打一下
notion image
第 3 个问题提前出现了,要先解决这个问题,保证代码能够执行到渲染阶段
notion image
和先知文章里不同,这里报错有点不一样,先跟进 Compiler.visitNode 225行看一下
notion image
拼接一下,发现没有这个方法
notion image
在遍历AST树时,通常是通过 "visit" + 节点类型 来遍历所有节点的,观察错误调用栈也知,比如 visitBlock,就是访问 Block 节点。那 Block 可用,我们就污染一下 type 就好了,当它当前上下文找不到就去找 Object 了。
当然选取时,最好是选的节点附带的上下文信息进入后,啥事也不会做的,不然也有走向奇奇怪怪的逻辑,先来看看 Block 节点。
notion image
image.png
这里加上这部分逻辑,来看看我们故意伪造 Block 节点会发生什么
notion image
这里 block.nodesundefined ,然后 undefined.length
notion image
image.png
明显,这样访问会报错,导致进入其他错误,然后 jade 没有对这块异常进行处理。
也就是说,如果 vist 的节点不是这种 block.子属性.孙子属性 问题应该不大。顶多一个 undefined 然后直接结束啥的,这里试试 visitCode
notion image
正常走到后面,没有报错,期间只触发了 this.buf.push(code.val) ,问题应该不大。
notion image
尝试污染 typeCode
notion image
好耶,像是解决了,因为这个报错和先知文章报错一致。
notion image
尝试解决这个报错,因为看错误调这里好像还没到渲染成功的地方。
分析这个错误栈,可以看到前 4 点还是属于 jade 范畴。先跟进 jade 模块最后报错的地方,即 jade/libindex.js 149行的 parse 方法,
notion image
发现,程序已经走完刚刚AST遍历部分了,已经差不多要返回了。
感觉只要避免进入149行的 addWith 方法,就可以渲染成功了!
然后进入149行的 addWith 方法是满足147行的 options.self 值为 False,尝试下看看这个 options.self 默认是不是 undefined,如果是就可以污染了。
notion image
nice,就是 undefined ,尝试污染 selftrue1 也可以
notion image
notion image
!!!终于走到渲染这一步了!
看到 undefined 好说,污染就完事了 (
到这 index 的页面渲染就没啥问题了,到 第 2 个问题,node.line 在某处为 undefined 才行,不然我们污染的 Object 对象就没意义
这里如果一步一步动态调试会比较麻烦,直接注入测试即可
notion image
发现都 nodeBlock 的时候 line 是不存在的。
notion image
理论上,只要覆盖了 node.line 即可达到代码执行的目的。
然后拼接上面避免报错的参数,得到
反弹 Shell
notion image
再 POST一次,发送的数据包不用变
notion image
不就少了个 message,那我污染你呗,,然后 message 后又说少了 error 继续污染,然后 error 是一个对象,就需要弄个 json 格式数据啦。
notion image
emmm,到这,其实一开始就犯了个很蠢的错。。。其实一开始就应该污染 line 先的,然后在试 compileDebugtypeself,蠢哭了,,因为,如果不污染 line 先的话,不能保证在 line 正确的情况下试其他的,就会导致,,其他对了,加上 line 就错了。。
其实这里,压根就不用污染 title,如果是 先污染 line,然后到 compileDebugtypeself 会发现,到 self 这就可以成功反弹 shell 了,人都傻了。。。
最终 EXP
notion image
不用变,在POST一次即可
notion image
原本想用原生Socket的,不知道为啥老是报错,奇怪,,难道闭合不了?
notion image

web343 原型链污染 +++++

说有过滤,但 web342一样的 EXP 也可以打,反弹shell后看源码才发现确实多了点东西
login.js
emmm 有点不懂这个 JSON.stringify(req.body).match(/Text/ig) 过滤有啥意义就是了。。
和web342一样的EXP
notion image
notion image

web344 HPP

代码
即 url 中不能包含大小写 8c2c逗号
先构造一个正常请求
发现题目会过滤掉逗号,尝试 URL 编码, urlencode(",") = %2c 发现 2c 也被过滤
HTTP协议中允许同名参数出现多次,不同服务端对同名参数处理都是不一样的,下面链接列举了一些
nodejs 会把同名参数以数组的形式存储,并且 JSON.parse 可以正常解析。
notion image
因此构造
这里把 c进行url编码,是因为 双引号 的url编码是 %22,和 c 连接起来就是 %22c,会匹配到正则表达式。
notion image

一些参考的题解
 
  • CTFSHOW
  • Writeup
  • 2021CSTC网络安全技术大赛 WriteUpXSS & SSRF组合拳
    • GitTalk
    目录