☰
Apusic任意文件上传漏洞自查:用Python脚本快速检测中间件风险
2026/10/22 16:07:46 网站建设 项目流程

企业安全自查这件事,最怕的就是“知道有风险,却不知道自家系统有没有中招”。正好前阵子帮某单位做中间件安全自查,遇到的就是Apusic应用服务器的任意文件上传漏洞,网上公开信息零零散散,工具也大多只针对某几个常见版本。后来我直接写了个Python检测脚本,把目标服务器的上传接口、HTTP方法、响应特征一次摸清,判断是否存在未授权写入的可能。这套思路和代码,至少能帮你在半天内完成一轮初步自查。

这篇文章就是把整个检测过程拆开讲:漏洞为什么需要重视、检测脚本的总体设计、代码怎么一步步实现、在企业内网怎么落地,以及自查之后该怎么修复、有哪些坑要避。如果你是安全运维、开发或者等保自查人员,这篇文章可以直接拿去参考。

1. 为什么Apusic应用服务器需要重点自查

1.1 Apusic应用服务器在企业中的位置

Apusic应用服务器是国内不少政企单位在信息化建设时期选用的基础中间件,承载着大量业务系统,比如OA、门户、综合管理平台、数据交换系统等。很多系统虽然前端换了一轮又一轮,底层仍然跑着老版本的Apusic。这就带来一个现实问题:中间件本身升级频率低,补丁跟不上,一旦公开的漏洞被人盯上,暴露面会非常大。

从我们排查过的案例来看,Apusic居多部署在内网,但又不是完全隔离。有的系统对外开放了管理端口,有的Web应用和后台接口做了映射,还有的在内网横向移动中被当作跳板。所以自查时不能只盯着“是不是放在公网”,内网环境同样需要排查,因为任意文件上传漏洞一旦被利用,攻击者不需要太多前置条件就能在服务器上落地文件。

1.2 任意文件上传漏洞的成因与危害

任意文件上传漏洞,归根结底是服务端缺少对上传文件的认证、类型校验和路径控制。正常的上传功能应该只允许已登录用户往特定目录传指定类型的文件,但在某些中间件版本里,上传接口本身暴露了过大的权限,攻击者可以未授权构造请求,把文件写入服务器任意路径,甚至写入可执行目录。

这类漏洞的破坏力不在于上传一个文本文件,而在于三步:上传、访问、执行。攻击者先传一个脚本文件到Web目录,再通过URL直接访问该文件,脚本就会在服务器上执行命令。操作得当的话,写入一个WebShell就能拿到服务器控制权限。如果服务器配置了内网访问关系,攻击者还能以此作为跳板,继续渗透数据库、文件服务器等内部资产。

做安全自查时,我们不需要真正去复现攻击链,但至少得确认“未授权上传”这个环节是否成立。如果连第一步都挡不住,后面就都别谈了。

1.3 影响范围自查评估

在动手扫描之前,先做一轮影响面梳理,自查效率会高很多。

几个关键问题需要提前列出来:

  • 当前网络环境里有多少台服务器部署了Apusic应用服务器?
  • 这些服务器分别运行在哪些版本,版本是否低于官方公告中修复的版本?
  • 哪些端口对外开放了HTTP服务,是否经过反向代理或负载均衡?
  • 上传接口的位置和路径是否已知,或者能否通过信息收集确定?

如果资产台账清晰,可以直接生成IP和服务端口清单。如果没有台账,建议先用网络探测工具扫一遍内网常见端口,再结合指纹特征判断是否为Apusic。注意,不要直接在公网批量扫描,避免法律风险。内部自查也要先取得授权。

2. 检测方案设计:Python脚本的思路

2.1 检测目标与检测原则

这次脚本的核心目标只有一个:在授权范围内,判断目标Apusic服务器是否存在未授权文件上传的风险。

检测原则有三条:

第一,最小侵入。不执行攻击性指令,不尝试写入可执行文件,不影响业务正常运行。默认只做HTTP方法探测和接口可达性检查,只有显式开启验证模式时才上传一个纯文本测试文件。

第二,结果可复现。脚本执行后能清晰输出目标URL、检测项、响应状态和风险结论,方便安全人员复核和记录。

第三,配置灵活。因为不同版本的Apusic可能开放不同的上传路径,脚本不能写死单一路径,而是通过命令行参数传入,由使用者根据资产信息或厂商公告填写待检测的路径。

2.2 技术选型:为什么用Python脚本做自查

市面上商业扫描器和企业漏洞平台也能检测中间件漏洞,但它们有几个问题:价格不低,规则库更新滞后,而且很多扫描器在“有漏洞”和“可利用”之间的判定不够透明。安全自查最需要的不是一份看不懂的报告,而是能自己控制检测过程、清楚每个判定依据的能力。

Python脚本的优势非常明显:

  • 依赖少,requests库基本就能完成所有HTTP交互。
  • 方便定制,可以根据目标系统调整请求头、方法、路径和Body结构。
  • 可以批量执行,配合并发既能提高效率,又不至于给服务器太大压力。
  • 便于留痕,脚本输出的日志可以直接存档,作为自查记录的原始证据。

对于安全自查这种低频但关键的任务,写一个小工具比依赖在线扫描更稳妥。

2.3 检测流程设计

检测流程我分成三个阶段:

第一个阶段是“指纹确认”。向目标发送GET请求,查看响应头中的Server字段、页面关键字、默认路径的返回特征,确认目标确实是Apusic。避免误把其他中间件当作检测对象。

第二个阶段是“接口探测”。向配置的上传路径发送OPTIONS请求,查看服务器的Allow头中是否包含POST、PUT等写方法。同时发送一个正常的GET请求,观察路径存在性以及返回状态码。如果返回200、405或某些特定响应,说明该路径被业务逻辑处理,可能具备上传能力。

第三个阶段是“验证模式”。如果前两步都显示该路径允许POST,且我们获得授权,就构造一次最小化的上传请求。文件内容为固定字符串,文件名包含随机前缀,检测上传后的响应是否包含成功标志。上传成功后立即提示清理测试文件,避免在服务器上遗留垃圾数据。

这三个阶段可以独立运行,也可以串联执行。默认情况下只执行前两个阶段,风险结论标记为“疑似”。只有加上--verify参数才执行第三阶段,并给出明确的“确认”结论。

3. 手把手实现Python检测脚本

3.1 环境准备与依赖安装

脚本基于Python 3.6以上运行,不需要额外安装复杂依赖,只需要requests库。

安装命令:

pip install requests

如果你习惯使用虚拟环境,建议先创建一个:

python -m venv apusic_scan_env source apusic_scan_env/bin/activate # Windows 下执行 apusic_scan_env\Scripts\activate pip install requests

脚本文件命名成apusic_upload_check.py,后面所有代码都写入这个文件里。

准备一个targets.txt,每一行写一个目标地址,格式为:

http://192.168.1.10:8088 http://192.168.1.11:7001

端口根据实际业务端口修改。如果目标路径相同,可以通过-p参数统一指定。

3.2 脚本主干代码拆解

完整代码我会分块讲,方便你看懂每一部分在做什么。

先导入依赖库和设置基础参数:

import argparse import random import ssl import urllib3 import requests from concurrent.futures import ThreadPoolExecutor, as_completed urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

引入urllib3是为了禁用掉未验证HTTPS证书时的告警信息,内网环境经常会有自签证书,不关掉告警会让输出日志变得很乱。

接下来定义核心检测函数:

def check_target(target_url, upload_path, verify_mode=False, timeout=10): """ 对单个目标执行检测 :param target_url: 目标地址,例如 http://192.168.1.10:8088 :param upload_path: 待检测的上传路径,例如 /file/upload :param verify_mode: 是否开启验证模式,默认False :param timeout: 超时时间 """ base_url = target_url.rstrip("/") upload_url = base_url + upload_path result = { "url": target_url, "upload_url": upload_url, "is_apusic": False, "allow_methods": [], "upload_verify": None, "risk_level": "unknown", "detail": [] } headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36", "Accept": "*/*", } # 1. 指纹检查:确认目标是Apusic try: resp = requests.get(base_url, headers=headers, timeout=timeout, verify=False, allow_redirects=True) server_header = resp.headers.get("Server", "") result["detail"].append(f"根路径响应状态码: {resp.status_code}") if "apusic" in server_header.lower() or "apusic" in resp.text[:500].lower(): result["is_apusic"] = True result["detail"].append("指纹匹配: 检测到Apusic标志") else: result["detail"].append(f"指纹特征不明显, Server头: {server_header}") except requests.exceptions.RequestException as e: result["detail"].append(f"根路径探测异常: {e}") return result # 2. 上传路径方法探测:检查是否允许POST/PUT try: options_resp = requests.options(upload_url, headers=headers, timeout=timeout, verify=False) allow_header = options_resp.headers.get("Allow", options_resp.headers.get("Access-Control-Allow-Methods", "")) result["detail"].append(f"OPTIONS {upload_path} 状态码: {options_resp.status_code}, Allow: {allow_header}") result["allow_methods"] = [m.strip().upper() for m in allow_header.split(",") if m.strip()] except requests.exceptions.RequestException as e: result["detail"].append(f"OPTIONS探测异常: {e}") # 3. 写方法预判 dangerous_methods = [m for m in result["allow_methods"] if m in ("POST", "PUT", "PATCH")] if dangerous_methods: result["risk_level"] = "suspected" result["detail"].append(f"目标上传路径允许方法: {dangerous_methods}, 存在未授权上传的可能性") else: result["detail"].append("上传路径未发现明显的POST/PUT方法开放") # 4. 验证模式:执行无害文件上传测试 if verify_mode and result["is_apusic"] and dangerous_methods: verify_result = perform_upload_check(upload_url, headers, timeout) result["upload_verify"] = verify_result["status"] result["detail"].append(verify_result["msg"]) if verify_result["status"]: result["risk_level"] = "confirmed" else: result["risk_level"] = "suspected" return result

这个函数是脚本的核心。有几个细节需要特别说明:

  • 根路径指纹检查使用的是前500字节,避免读取过大的页面内容拖慢执行。
  • OPTIONS请求不是所有服务器都支持,但支持的话通常会在Allow头中给出方法列表,这是判断是否存在写方法最直接的方式。
  • 如果Allow头为空,不代表不支持POST,很多中间件实现不标准,所以还需要结合后续验证模式。
  • 验证模式里调用了一个perform_upload_check函数,下面单独实现。
def perform_upload_check(upload_url, headers, timeout=10): """ 执行一次无害文件上传检测 上传内容为固定字符串,不包含任何脚本或可执行代码 仅用于证明未授权情况下能否写入文件 """ file_name = f"sec_check_{random.randint(10000, 99999)}.txt" file_content = b"SecurityCheck" upload_headers = headers.copy() upload_headers["Content-Type"] = "multipart/form-data" # 这里构造multipart/form-data的请求体 boundary = "----apusicSecurityCheck" body = ( f"--{boundary}\r\n" f'Content-Disposition: form-data; name="file"; filename="{file_name}"\r\n' f"Content-Type: text/plain\r\n\r\n" f"{file_content.decode()}\r\n" f"--{boundary}--\r\n" ) upload_headers["Content-Type"] = f"multipart/form-data; boundary={boundary}" try: resp = requests.post(upload_url, headers=upload_headers, data=body, timeout=timeout, verify=False) status_code = resp.status_code response_text = resp.text[:300] # 判断规则:状态码为2xx或3xx,且响应中包含常见的成功提示 success_keywords = ["success", "ok", "上传成功", "成功", "file", "path"] if 200 <= status_code < 400 and any(k in response_text.lower() for k in success_keywords): return {"status": True, "msg": f"验证上传返回{status_code},响应包含成功特征,疑似可未授权写入文件名: {file_name}"} elif status_code in (200, 201, 204): return {"status": True, "msg": f"验证上传返回{status_code},状态码表示请求已被受理,疑似可未授权写入"} else: return {"status": False, "msg": f"验证上传返回{status_code},未发现明确成功标志"} except requests.exceptions.RequestException as e: return {"status": False, "msg": f"验证上传请求异常: {e}"}

注意,这里只是为了证明“能不能写入”,上传的文件内容是一个纯文本字符串,没有任何可执行能力。文件名也带随机数,降低和正常业务文件冲突的概率。即便验证成功,也要在后续步骤提示清理这个测试文件。

然后需要批量检测和结果输出:

def batch_check(targets, upload_path, verify_mode=False, threads=10, timeout=10): all_results = [] with ThreadPoolExecutor(max_workers=threads) as executor: futures = {executor.submit(check_target, target, upload_path, verify_mode, timeout): target for target in targets} for fut in as_completed(futures): result = fut.result() all_results.append(result) return all_results def print_report(results): print("\n==================== Apusic 任意文件上传漏洞自查报告 ====================") for r in results: status_icon = "确认风险" if r["risk_level"] == "confirmed" else "疑似风险" if r["risk_level"] == "suspected" else "暂无结论" print(f"[*] 目标: {r['url']}") print(f" 检测路径: {r['upload_url']}") print(f" 是否Apusic指纹: {r['is_apusic']}") print(f" 允许方法: {r['allow_methods']}") print(f" 风险等级: {status_icon}") if r["detail"]: for d in r["detail"]: print(f" -> {d}") print("-" * 50) confirmed = sum(1 for r in results if r["risk_level"] == "confirmed") suspected = sum(1 for r in results if r["risk_level"] == "suspected") print(f"\n汇总: 检测目标 {len(results)} 个, 确认风险 {confirmed} 个, 疑似风险 {suspected} 个")

最后是命令行解析和主函数:

def main(): parser = argparse.ArgumentParser(description="Apusic应用服务器任意文件上传漏洞自查脚本") parser.add_argument("-u", "--url", help="单个目标地址, 如 http://192.168.1.10:8088") parser.add_argument("-f", "--file", help="目标列表文件, 每行一个地址") parser.add_argument("-p", "--path", default="/file/upload", help="上传检测路径, 默认使用 /file/upload") parser.add_argument("--verify", action="store_true", help="开启验证模式, 会向目标写入一个无害测试文件") parser.add_argument("-t", "--threads", type=int, default=10, help="并发线程数, 默认10") parser.add_argument("--timeout", type=int, default=10, help="请求超时时间, 默认10秒") args = parser.parse_args() if not args.url and not args.file: parser.error("请指定 -u 或 -f 参数") targets = [] if args.url: targets = [args.url] if args.file: with open(args.file, "r", encoding="utf-8") as f: targets.extend([line.strip() for line in f if line.strip()]) # 去重 targets = list(set(targets)) print(f"[*] 开始检测, 目标数量: {len(targets)}") if args.verify: print("[!] 已开启验证模式, 将向目标上传无害测试文件, 请确保已获得授权!") results = batch_check(targets, args.path, args.verify, args.threads, args.timeout) print_report(results) if __name__ == "__main__": main()

3.3 检测结果输出与风险评级

脚本运行完后会生成一份控制台报告,内容包含每个目标的检测细节。我习惯把这份报告重定向到文本文件存档:

python apusic_upload_check.py -f targets.txt -p /file/upload --verify -t 20 | tee apusic_scan_report.txt

风险评级分三档:

  • 确认风险:开了验证模式,且返回了上传成功标志。需要立即安排复核并整改。
  • 疑似风险:指纹匹配或路径允许POST/PUT,但未验证成功。这种情况可能是因为路径不对,或目标版本其实已修复,需要结合人工确认。
  • 暂无结论:指纹不匹配,或网络异常。这种情况不代表安全,也许目标使用了特殊配置或反代,需要换一种方式检查。

评级的意义在于帮助你们内部确定优先级。确认风险的要当日处理,疑似风险的要在一周内完成版本核对和补丁检查。

4. 企业内安全自查实操指南

4.1 自查前的授权与范围确认

安全自查脚本一旦开启验证模式,就会向目标服务器发起写请求。即使内容无害,也必须得到明确的授权。

在企业里,这个授权通常来自信息部门负责人或安全负责人。实际操作前建议以邮件或工单形式提交一份自查方案,内容包括:

  • 自查目的和依据
  • 检测目标IP、端口范围
  • 计划执行的检测动作说明
  • 预计影响时间窗口
  • 回滚和清理方案

审批通过后再开始扫描。如果是外部安全服务商,还需要签署授权协议,避免后续出现争议。

4.2 在测试环境验证脚本

不要一到生产环境就立刻跑完整漏洞验证。先找一台有代表性、可隔离的测试机,部署相同版本的Apusic,执行一次完整检测流程,确认脚本行为符合预期,不会误操作业务数据。

我在测试环境跑脚本时,通常分三步验证:

第一步,运行基础指纹探测,确认目标能被识别为Apusic。 第二步,运行接口探测,看OPTIONS结果和预期是否一致。 第三步,开启验证模式,上传一次测试文件,确认成功判定逻辑是准的。

测试环境验证通过后,再到生产环境用非验证模式先扫一遍,人工确认疑似目标,最后对少量高风险目标执行验证模式。能不在生产环境写文件就不写,减少风险。

4.3 扫描结果的复核与记录

脚本输出的报告是“线索”,不是“结论”。对于检测出“疑似风险”的目标,需要人工复核。

复核时重点看以下几个方面:

  • 该路径是不是业务功能实际使用的上传路径?如果是,说明这个入口本身就存在,触发报错不代表漏洞。
  • OPTIONS返回的Allow头是中间件默认配置,还是业务代码自定义的?有些反代组件会无条件返回所有方法,造成误报。
  • 验证模式上传成功的文件是否能通过URL访问?如果能访问,风险等级提升;如果不能访问,则需要确认写入目录是否有执行权限。
  • 目标Apusic版本是否已经包含官方修复补丁?如果版本较新,可能只是临时接口被暴露,不一定可被利用。

这些信息需要同步到企业漏洞管理表格中,记录检测时间、目标、结果、复核人、修复措施和完成日期。安全自查不能只做一次,补丁发布和版本升级后应该重新跑一遍。

5. 修复与加固建议

5.1 临时缓解措施

在还没有安装补丁的情况下,可以先采取应急手段降低风险。

最直接的是在网络层或中间件层面限制上传接口的访问来源,只允许已认证的业务系统IP段访问。可以使用防火墙ACL或反向代理的访问控制来实现。

比如在Nginx反代层增加一段location规则:

location ^~ /file/upload { allow 192.168.10.0/24; deny all; proxy_pass http://backend; }

这样即使接口本身存在缺陷,外部流量也无法触达。

另一个临时措施是关闭不必要的方法。如果业务确实不需要PUT、DELETE等方法,可以在中间件配置中禁止。不过要注意,Apusic的管理控制台迁移到独立端口管理,Web应用容器的方法限制可能需要通过部署描述符配置,具体参考当前版本的中间件文档。

5.2 永久修复方案

根治办法是升级到官方修复版本,并打上安全补丁。升级前一定要读取官方发布说明,了解升级步骤和兼容性影响,先在测试环境完成业务回归测试,再安排生产割接。

如果因为历史原因不能立刻升级,需要做一层深度防御:

  • 统一收口上传接口,所有上传行为经过自研上传服务校验,不允许直接走中间件默认路径。
  • 在代码层增加身份认证和文件类型白名单校验,文件名重命名为随机字符串,禁止拼接路径。
  • 关闭上传目录的脚本执行权限,比如在中间件中配置该目录不具备JSP/Servlet处理能力。
  • 使用WAF或RASP产品,对上传流量做行为检测。

5.3 自查后的持续监控

自查发现并修复问题不是终点。中间件版本在变,业务代码也在变,需要建立持续监控机制。

我的习惯是在监控系统中加入对上传路径的定时请求检查,记录HTTP状态码和响应时间。同时关注官方安全通告,一旦发布与Apusic相关的漏洞信息,第一时间对照资产台账,评估是否受影响,并及时安排针对性检测。

日志审计也很重要。排查上传漏洞是否已被利用,可以重点看是否有异常上传请求记录,比如:

  • 上传文件名包含特殊字符或较长随机字符串
  • 上传接口来源IP反复变化
  • 上传后紧接着出现访问上传文件的请求
  • 写目录出现非业务类型的文件

如果发现这些迹象,要结合服务器文件变更审计和进程行为分析进一步确认。

6. 常见问题与避坑实录

6.1 脚本运行报错怎么办

常见问题之一是“ModuleNotFoundError: No module named 'requests'”。这个基本就是没有安装依赖,执行pip install requests就能解决。Windows下如果同时存在Python 2和3,需要确认pip指向的是不是当前解释器。

另一个问题是“ConnectionError: Max retries exceeded with url”。这通常是因为目标地址网络不可达,或者目标启用了防火墙拦截。可以先试试本机能否telnet通目标端口。如果目标确实通,但脚本报连接错误,可能是代理环境导致的。内网环境需要关闭系统代理,或者在requests.get中增加proxies={"http": None, "https": None}。

还有HTTPS证书报错,脚本里统一用了verify=False,内网自签证书不会导致中断。如果你们安全策略要求强制校验证书,可以去掉这个参数并配置正确的CA证书路径。

6.2 误报漏报问题

误报最典型的场景是:OPTIONS请求返回了Allow头,里面写了POST、PUT,但实际业务接口不允许未授权上传。这是因为某些中间件默认会对所有请求返回统一的Allow值,并不代表具体接口真的支持这些方法。所以脚本把这一级结果设为“疑似”而不是“确认”,就是为后续人工复核留了空间。

漏报则更容易出现在版本差异上。比如有的Apusic版本把上传接口封装在某个特定的后端路径,前端的路径是/file/upload,但实际写库的接口可能是/fileserver/upload。如果你只测了一个路径,就会漏过。解决办法是结合抓包或接口文档,多测几个常见路径。脚本的-p参数就是为了方便你逐个路径扫描。

另一个漏报原因是目标前面挂了负载均衡或反向代理。外部访问的URL经过反代转发,路径映射规则改变,导致原始漏洞路径被隐藏。这种情况下,脚本检测到的实际上是反代的响应,而不是后端Apusic的真实响应。因此,自查尽量在能够直连后端服务器的网络区域进行,或者至少明确测试请求确实到达了目标服务器。

6.3 合规注意事项与自查边界

安全自查最大的风险不是技术,而是合规。没有授权就发起的检测,哪怕无害,也可能被认定为违规行为。尤其在企业内网,不同部门系统之间的安全测试必须经由网络安全管理方统一协调。

我这里有几个实操建议:

  • 不要使用自己的个人电脑在内网跑扫描,统一使用安全部门提供的工作机,或者经批准的扫描器。
  • 不要尝试绕过身份验证、尝试多种payload爆破上传路径,这会远超自查范围。
  • 如果检测到风险,不要顺手写一句“可执行”,更不要尝试通过上传文件获取服务器权限,这不是自查该做的事。
  • 验证模式用的测试文件,检测完成后务必确认是否已删除。如果因为接口没有删除功能而无法清理,需要记录文件路径,走变更流程删除。

后续我会把脚本的日志格式调得更适合导入企业漏洞管理平台,这样每次自查结果都能自动归档,不用手工整理。如果你也在维护中间件安全,建议先从小范围试点开始,跑通一个流程后再扩大范围。安全自查本质上是个持续的过程,脚本只是帮你把“人肉看配置”变成了“批量自动探测”,真正的判断和整改,还是要人来闭环。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询