Jenkins动态路由漏洞分析与利用:从ACL绕过到RCE
引言
在软件工程中,持续集成与持续交付(CI/CD)是开发者减少常规工作的最佳实践。Jenkins作为最知名的CI/CD工具,因其易用性、强大的Pipeline系统和容器集成,成为全球使用最广泛的CI/CD应用。根据Snyk 2018年的JVM生态系统报告,Jenkins在CI/CD服务器市场中占据约60%的份额。
对于红队而言,Jenkins是每个黑客都希望控制的战场。一旦控制Jenkins服务器,攻击者可以获得大量源代码和凭据,甚至控制Jenkins节点!在DEVCORE红队案例中,有多起案例是从Jenkins服务器作为入口点入侵整个企业。
漏洞概述
在去年的安全审查中,我们发现了7个漏洞,包括:
- CVE-2018-1999002 - 任意文件读取漏洞
- CVE-2018-1000600 - GitHub插件中的CSRF和权限检查缺失
- CVE-2018-1999046 - 未授权用户可访问代理日志
- CVE-2018-1000861 - 通过精心构造的URL执行代码
- CVE-2019-1003000 - 脚本安全与Pipeline插件中的沙箱绕过
- CVE-2019-1003001 - 脚本安全与Pipeline插件中的沙箱绕过
- CVE-2019-1003002 - 脚本安全与Pipeline插件中的沙箱绕过
本文将重点讨论CVE-2018-1000861,这是一个通过动态路由机制实现的ACL绕过漏洞。
权限级别
在Jenkins中,ACL角色分为三种类型:
- 完全访问权限:可以完全控制Jenkins,执行任意Groovy代码。
- 只读模式(ANONYMOUS_READ=True):所有内容可见可读,但无法执行脚本。
- 认证模式(ANONYMOUS_READ=False):默认模式,无有效凭据无法查看任何信息。
漏洞分析
Jenkins使用命名约定动态解析URL并调用方法。URL按/
分割,从jenkins.model.Jenkins
作为入口点逐级匹配令牌。如果令牌匹配以下命名约定,则递归调用:
get<token>()
get<token>(String)
get<token>(Int)
get<token>(Long)
get<token>(StaplerRequest)
getDynamic(String, …)
doDynamic(…)
do<token>(…)
js<token>(…)
- 带有
@WebMethod
注解的类方法 - 带有
@JavaScriptMethod
注解的类方法
问题点
- 一切皆是java.lang.Object的子类:所有对象都存在
getClass()
方法,该方法符合命名约定,可在动态路由中调用。 - 白名单绕过:在ANONYMOUS_READ=False模式下,入口点会进行白名单URL前缀检查。但如果能从白名单入口跳转到其他对象,仍可绕过此检查。
利用方式
通过交叉引用对象绕过ACL策略,利用以下gadget:
|
|
该gadget调用以下方法序列:
jenkins.model.Jenkins.getSecurityRealm()
.getUser([username])
.getDescriptorByName([descriptor_name])
在Jenkins中,所有可配置对象都扩展hudson.model.Descriptor
类型,约500个类类型可通过此方法访问。尽管大多数开发者在危险操作前会再次检查权限,但此漏洞仍可作为绕过ACL限制的垫脚石。
漏洞链案例
- 预认证用户信息泄露:通过搜索功能缺乏权限检查,列出所有用户。
- PoC:
http://jenkins.local/securityRealm/user/admin/search/index?q=[keyword]
- PoC:
- 与CVE-2018-1000600链接触发全响应SSRF:通过GitHub插件提取凭据或触发SSRF。
- PoC:
http://jenkins.local/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.github.config.GitHubTokenCredentialsCreator/createTokenByPassword?apiUrl=http://169.254.169.254/%23&login=orange&password=tsai
- PoC:
- 预认证远程代码执行:通过链接触发RCE(详情见第二部分)。
待办事项
- 在ANONYMOUS_READ=False模式下获取Plugin对象引用,以绕过CVE-2018-1999002和CVE-2018-6356的ACL限制。
- 在ANONYMOUS_READ=False模式下找到另一种gadget调用
getDescriptorByName(String)
方法。
致谢
感谢Jenkins安全团队特别是Daniel Beck的协调和漏洞修复。时间线如下:
- 2018年5月30日 - 向Jenkins报告漏洞
- 2018年6月15日 - Jenkins修复漏洞并分配CVE-2018-1000600
- 2018年7月18日 - Jenkins修复漏洞并分配CVE-2018-1999002
- 2018年8月15日 - Jenkins修复漏洞并分配CVE-2018-1999046
- 2018年12月5日 - Jenkins修复漏洞并分配CVE-2018-1000861
- 2018年12月20日 - 向Jenkins报告Groovy漏洞
- 2019年1月8日 - Jenkins修复Groovy漏洞并分配CVE-2019-1003000、CVE-2019-1003001和CVE-2019-1003002
本文详细分析了Jenkins动态路由机制的安全问题,提供了漏洞利用的实践案例和修复建议,为安全研究人员和开发者提供了深入的技术参考。