HTTP/1 Sequencer
The HTTP/1 Sequencer (mrpf_http1_sequencer) executes ordered HTTP/1 requests using raw packet I/O. Each step targets its own endpoint, enabling multi-host sequences. It supports variable extraction between steps and simultaneous request delivery using the first-sequence-sync technique for race condition testing.
Unlike the HTTP/1.1 Scanner which fires requests at scale across many targets, the sequencer executes steps sequentially — extracting data from responses to feed into subsequent requests. This makes it suitable for multi-step attack chains, cross-host authentication flows, and race condition exploitation.
Architecture
The sequencer uses two long-lived threads that persist for the entire sequence. A single datalink channel is opened once and reused across all steps. Before each step, the executor sends a SetTarget control message to the RX thread so it filters packets for the current step’s target.
graph TB
subgraph Executor Thread
E1[Render templates with current variables]
E2[Create connections + send SYN packets]
E3[Forward RX packets via TX]
E4[Wait for RaceReady signals]
E5[Fire trigger packets in tight loop]
E6[Collect step results]
E7[Run extractors + update variables]
E8[Send SequenceStepResult to progress]
end
subgraph RX Thread
R1[Filter incoming ethernet frames]
R2[Drive TCP handshake]
R3[Drive TLS state machine]
R4[Send outgoing packets to executor]
R5[Race: send future byte + signal RaceReady]
R6[Emit completed request/response pairs]
end
subgraph Progress Thread
P1[Collect SequenceStepResults]
end
E1 --> E2 --> E3
E3 --> E4 --> E5 --> E6 --> E7 --> E8
E8 -->|next step| E1
R1 --> R2 --> R3 --> R4
R3 --> R5
R3 --> R6
R4 -->|SendPacket| E3
R5 -->|RaceReady + trigger packet| E4
R6 -->|StepResult| E6
E8 -->|SequenceStepResult| P1
Thread Communication
The RX thread communicates with the executor via RxMessage:
| Message | Direction | Purpose |
|---|---|---|
SendPacket(Vec<u8>) | RX -> Executor | Outgoing TCP/TLS packet to send via TX |
RaceReady { syn_cookie, trigger_packet } | RX -> Executor | Connection ready for race trigger |
StepResult { syn_cookie, request, response } | RX -> Executor | Completed request/response pair |
Error { syn_cookie, error } | RX -> Executor | Connection-level error |
The executor controls the RX thread via RxControl:
| Message | Direction | Purpose |
|---|---|---|
SetTarget { ip, port } | Executor -> RX | Update packet filter for the next step’s target |
Stop | Executor -> RX | Shut down the RX thread gracefully |
Execution Flow
For each step in the sequence:
- Set target — send
RxControl::SetTargetto the RX thread with this step’s IP and port - Render templates with current variables (two-pass rendering using the step’s target)
- Create
SequencerConnection(s) with rendered payloads - Send SYN packet(s) via the datalink TX
- Event loop: forward packets from RX, collect results
- Extract values from responses (single steps only), merge into variable map
- Report
SequenceStepResultto progress thread
sequenceDiagram
participant E as Executor
participant RX as RX Thread
participant T as Target
Note over E: Step N begins
E->>T: SYN packet(s)
T->>RX: SYN-ACK
RX->>E: SendPacket (ACK)
E->>T: ACK
Note over RX: TLS handshake (if HTTPS)
RX->>E: SendPacket (ClientHello)
E->>T: ClientHello
T->>RX: ServerHello + Cert + Done
RX->>E: SendPacket (Key Exchange + Finished)
E->>T: Key Exchange + Finished
Note over RX: Encrypt & send HTTP request
RX->>E: SendPacket (encrypted request data)
E->>T: Request data
T->>RX: Response data
RX->>E: StepResult (request, response)
Note over E: Run extractors, update variables
Note over E: Step N+1 begins...
First-Sequence-Sync (Race Conditions)
The key innovation of the sequencer is first-sequence-sync for race condition testing. When a step uses SequenceStepType::Race, all requests are delivered to the server simultaneously with sub-microsecond precision.
Why Raw Packets?
Standard sockets with Barrier synchronization have 1-5ms of jitter between threads. Raw packets through a single datalink TX in a tight loop achieve microsecond-level jitter. Combined with first-sequence-sync, the server’s TCP stack delivers data to ALL connections simultaneously.
How It Works
For N concurrent race requests:
- All N connections complete TCP + TLS handshakes normally
- The full HTTP request is encrypted (TLS) or staged (plain TCP)
- All data is sent except the last 2 bytes per connection
- A “future byte” (the last byte) is sent at TCP sequence number
S+1— the server’s TCP stack buffers it because there’s a 1-byte gap at sequenceS - When all N connections signal “race ready”, N trigger packets (1 byte each at sequence
S) are sent in a single tight TX loop - Each trigger fills the gap, causing TCP reassembly and simultaneous data delivery to the application layer
sequenceDiagram
participant E as Executor
participant T as Target Server
Note over E,T: N connections established + TLS complete
rect rgb(70, 70, 120)
Note over E,T: Hold-back Phase (per connection)
E->>T: Request data (all except last 2 bytes)
E->>T: Future byte at seq S+1 (out-of-order)
Note over T: TCP buffers future byte (gap at seq S)
end
Note over E: All N connections "race ready"
rect rgb(120, 70, 70)
Note over E,T: Trigger Phase (tight loop)
E->>T: Conn 1: trigger byte at seq S
E->>T: Conn 2: trigger byte at seq S
E->>T: Conn N: trigger byte at seq S
end
Note over T: TCP reassembly completes for ALL connections
Note over T: Application receives N requests simultaneously
For TLS connections, withholding even 1 byte of a TLS record prevents the server from decrypting ANY of the HTTP request until the trigger arrives. This makes the synchronization even tighter than plain TCP.
Template System
Templates use a two-pass rendering system:
Pass 1 — User variables via mrpf_templates::Engine:
${VAR}syntax withskip_missing=true- Single value replacement only (no cartesian product)
- Variables from config + extracted from previous steps
Pass 2 — Host variables via byte-level Aho-Corasick replacement:
${IPV4}— target IP address${PORT}— target port${SNI}— TLS Server Name Indication${CONTENT_LENGTH}— computed from body after\r\n\r\n
Extractors
Extractors pull values from HTTP responses and store them as template variables for subsequent steps. They are only available on Single steps (not Race steps).
| Source | Description | Example |
|---|---|---|
Header(name) | HTTP header value (case-insensitive) | Set-Cookie |
JsonPath(path) | Dot-notation JSON path | data.token, items[0].id |
Regex { pattern, group } | Regex capture group | csrf_token=([^;]+), group 1 |
StatusCode | HTTP status code as string | 200, 302 |
Body | Entire response body | Full text content |
Connection Types
SequencerConnection supports both plain TCP and TLS, implementing the mrpf_engine::Connection trait:
- Plain TCP: Stages raw HTTP bytes, emits in MSS-sized chunks
- TLS: Uses
rustls::UnbufferedClientConnectionfor raw packet TLS, encrypts HTTP payload into appdata staging - Race mode: Both types support hold-back of last 2 bytes for first-sequence-sync
File Structure
scanners/mrpf_http1_sequencer/src/
├── lib.rs # Public API: Http1Sequencer
├── config.rs # Http1Sequence, SequenceStep, SequenceStepType, Extractor, SequenceTarget
├── connection.rs # SequencerConnection: Connection impl, TLS+plain, race mode
├── error.rs # Error/Result types
├── executor.rs # Step orchestration, TX forwarding, trigger sending
├── extractor.rs # Response data extraction (header, JSON, regex, etc.)
├── progress.rs # SequencerMessage, SequenceStepResult, ProgressHandler
├── receive.rs # RX thread: packet processing, TLS driving, race signaling
└── template.rs # Two-pass template rendering