Apusic任意文件上传漏洞自查:用Python脚本快速检测中间件风险
企业安全自查这件事最怕的就是“知道有风险却不知道自家系统有没有中招”。正好前阵子帮某单位做中间件安全自查遇到的就是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_modeFalse, timeout10): 对单个目标执行检测 :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, headersheaders, timeouttimeout, verifyFalse, allow_redirectsTrue) 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, headersheaders, timeouttimeout, verifyFalse) allow_header options_resp.headers.get(Allow, options_resp.headers.get(Access-Control-Allow-Methods, )) result[detail].append(fOPTIONS {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(fOPTIONS探测异常: {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, timeout10): 执行一次无害文件上传检测 上传内容为固定字符串不包含任何脚本或可执行代码 仅用于证明未授权情况下能否写入文件 file_name fsec_check_{random.randint(10000, 99999)}.txt file_content bSecurityCheck upload_headers headers.copy() upload_headers[Content-Type] multipart/form-data # 这里构造multipart/form-data的请求体 boundary ----apusicSecurityCheck body ( f--{boundary}\r\n fContent-Disposition: form-data; namefile; filename{file_name}\r\n fContent-Type: text/plain\r\n\r\n f{file_content.decode()}\r\n f--{boundary}--\r\n ) upload_headers[Content-Type] fmultipart/form-data; boundary{boundary} try: resp requests.post(upload_url, headersupload_headers, databody, timeouttimeout, verifyFalse) 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_modeFalse, threads10, timeout10): all_results [] with ThreadPoolExecutor(max_workersthreads) 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(descriptionApusic应用服务器任意文件上传漏洞自查脚本) 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, actionstore_true, help开启验证模式, 会向目标写入一个无害测试文件) parser.add_argument(-t, --threads, typeint, default10, help并发线程数, 默认10) parser.add_argument(--timeout, typeint, default10, 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, encodingutf-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证书报错脚本里统一用了verifyFalse内网自签证书不会导致中断。如果你们安全策略要求强制校验证书可以去掉这个参数并配置正确的CA证书路径。6.2 误报漏报问题误报最典型的场景是OPTIONS请求返回了Allow头里面写了POST、PUT但实际业务接口不允许未授权上传。这是因为某些中间件默认会对所有请求返回统一的Allow值并不代表具体接口真的支持这些方法。所以脚本把这一级结果设为“疑似”而不是“确认”就是为后续人工复核留了空间。漏报则更容易出现在版本差异上。比如有的Apusic版本把上传接口封装在某个特定的后端路径前端的路径是/file/upload但实际写库的接口可能是/fileserver/upload。如果你只测了一个路径就会漏过。解决办法是结合抓包或接口文档多测几个常见路径。脚本的-p参数就是为了方便你逐个路径扫描。另一个漏报原因是目标前面挂了负载均衡或反向代理。外部访问的URL经过反代转发路径映射规则改变导致原始漏洞路径被隐藏。这种情况下脚本检测到的实际上是反代的响应而不是后端Apusic的真实响应。因此自查尽量在能够直连后端服务器的网络区域进行或者至少明确测试请求确实到达了目标服务器。6.3 合规注意事项与自查边界安全自查最大的风险不是技术而是合规。没有授权就发起的检测哪怕无害也可能被认定为违规行为。尤其在企业内网不同部门系统之间的安全测试必须经由网络安全管理方统一协调。我这里有几个实操建议不要使用自己的个人电脑在内网跑扫描统一使用安全部门提供的工作机或者经批准的扫描器。不要尝试绕过身份验证、尝试多种payload爆破上传路径这会远超自查范围。如果检测到风险不要顺手写一句“可执行”更不要尝试通过上传文件获取服务器权限这不是自查该做的事。验证模式用的测试文件检测完成后务必确认是否已删除。如果因为接口没有删除功能而无法清理需要记录文件路径走变更流程删除。后续我会把脚本的日志格式调得更适合导入企业漏洞管理平台这样每次自查结果都能自动归档不用手工整理。如果你也在维护中间件安全建议先从小范围试点开始跑通一个流程后再扩大范围。安全自查本质上是个持续的过程脚本只是帮你把“人肉看配置”变成了“批量自动探测”真正的判断和整改还是要人来闭环。