This commit is contained in:
Eli Peter 2026-06-05 10:16:30 -05:00 committed by GitHub
parent 55247b7fcd
commit 991c84a1eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
1464 changed files with 225448 additions and 1985 deletions

View file

@ -0,0 +1,15 @@
// Phase 08 (Track J.6) — Go HEADER_INJECTION benign control fixture.
//
// Same shape as `vuln.go` but URL-encodes the value via
// `net/url.QueryEscape` before the header set, so CRLF bytes land as
// `%0D%0A` and the wire keeps a single header.
package benign
import (
"net/http"
"net/url"
)
func Run(w http.ResponseWriter, value string) {
w.Header().Set("Set-Cookie", url.QueryEscape(value))
}

View file

@ -0,0 +1,13 @@
// Phase 08 (Track J.6) — Go HEADER_INJECTION vuln fixture.
//
// The function assigns the attacker-controlled `value` directly into a
// `Set-Cookie` header via `http.ResponseWriter.Header().Set`. A
// payload carrying `\r\nSet-Cookie: nyx-injected=pwn` splits the
// single header into two on the wire.
package vuln
import "net/http"
func Run(w http.ResponseWriter, value string) {
w.Header().Set("Set-Cookie", value)
}

View file

@ -0,0 +1,16 @@
// Phase 08 (Track J.6) Java HEADER_INJECTION benign control fixture.
//
// Same shape as `Vuln.java` but URL-encodes the value via
// `URLEncoder.encode` (the OWASP-recommended defence), so any CRLF
// bytes in the value land as `%0D%0A` and the wire keeps a single
// header.
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletResponse;
public class Benign {
public static void run(HttpServletResponse response, String value) {
String encoded = URLEncoder.encode(value, StandardCharsets.UTF_8);
response.setHeader("Set-Cookie", encoded);
}
}

View file

@ -0,0 +1,13 @@
// Phase 08 (Track J.6) Java HEADER_INJECTION vuln fixture.
//
// The function string-concatenates the attacker-controlled `value`
// directly into a `Set-Cookie` header set via
// `HttpServletResponse.setHeader`. A payload carrying `\r\nSet-Cookie:
// nyx-injected=pwn` splits the single header into two on the wire.
import javax.servlet.http.HttpServletResponse;
public class Vuln {
public static void run(HttpServletResponse response, String value) {
response.setHeader("Set-Cookie", value);
}
}

View file

@ -0,0 +1,86 @@
// Phase 08 (Track J.6) Java raw-socket HEADER_INJECTION vuln fixture.
//
// Writes the response status line and headers directly to the wire via
// `OutputStream.write(byte[])` against the `java.net.Socket` returned
// by `ServerSocket.accept()`, bypassing the framework-level CRLF
// validator that Tomcat / Jetty / Undertow would otherwise interpose
// on `HttpServletResponse.setHeader`. A payload carrying
// `\r\nSet-Cookie: ...` splits the single Set-Cookie header into two
// on the wire, producing the canonical smuggled-second-header shape
// that `ProbeKind::HeaderWireFrame` is designed to catch.
//
// The harness (`src/dynamic/lang/java.rs::emit_header_injection_harness`)
// detects the `java.net.ServerSocket` + `setCookieValue` tokens in
// this file and routes through the tier-(b) wire-frame branch: bind
// a loopback `ServerSocket` via `createServer`, accept one client
// (`runOnce`) on a worker thread, issue one raw-socket
// `GET / HTTP/1.0` from the harness, read the bytes the fixture
// wrote to the response socket up to the CRLF-CRLF boundary, and
// emit them as a `ProbeKind::HeaderWireFrame` record.
//
// All three entry points are `public static` so the harness can
// resolve them via `Class.forName("Vuln").getDeclaredMethod(...)`
// reflective dispatch (same pattern as Phase 06 LDAP Java tier-(b)).
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Vuln {
// Bytes go straight onto the wire with no encoding pass. The
// harness installs the cookie value before booting the accept
// loop, mirroring the Python `Handler.cookie_value` and Ruby
// `set_cookie_value` setters.
private static byte[] nyxCookieValue = new byte[0];
public static void setCookieValue(byte[] value) {
nyxCookieValue = (value == null) ? new byte[0] : value;
}
public static ServerSocket createServer() throws IOException {
return new ServerSocket(0, 1, java.net.InetAddress.getByName("127.0.0.1"));
}
public static void runOnce(ServerSocket server) {
Socket client = null;
try {
server.setSoTimeout(5000);
client = server.accept();
client.setSoTimeout(1000);
// Drain whatever request bytes the client sent so the
// kernel does not stall the write that follows. Ignore
// read errors the client may have already shut its
// write side.
try {
InputStream in = client.getInputStream();
byte[] buf = new byte[4096];
int read = in.read(buf, 0, buf.length);
// discard
if (read < 0) {
// EOF, nothing to drain
}
} catch (IOException ignored) {
// ignore drain errors
}
byte[] body = "ok\n".getBytes(java.nio.charset.StandardCharsets.ISO_8859_1);
java.io.ByteArrayOutputStream raw = new java.io.ByteArrayOutputStream();
raw.write("HTTP/1.0 200 OK\r\n".getBytes(java.nio.charset.StandardCharsets.ISO_8859_1));
raw.write(("Content-Length: " + body.length + "\r\n")
.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1));
raw.write("Set-Cookie: ".getBytes(java.nio.charset.StandardCharsets.ISO_8859_1));
raw.write(nyxCookieValue);
raw.write("\r\n\r\n".getBytes(java.nio.charset.StandardCharsets.ISO_8859_1));
raw.write(body);
OutputStream out = client.getOutputStream();
out.write(raw.toByteArray());
out.flush();
} catch (IOException e) {
// ignore harness will time out reading and fall back
} finally {
if (client != null) {
try { client.close(); } catch (IOException ignored) {}
}
}
}
}

View file

@ -0,0 +1,13 @@
// Phase 08 (Track J.6) — JavaScript HEADER_INJECTION benign control
// fixture.
//
// Same shape as `vuln.js` but URL-encodes the value first via
// `encodeURIComponent`, so CRLF bytes land as `%0D%0A` and the wire
// keeps a single header.
const http = require('http');
function run(res, value) {
res.setHeader('Set-Cookie', encodeURIComponent(value));
}
module.exports = { run };

View file

@ -0,0 +1,13 @@
// Phase 08 (Track J.6) — JavaScript HEADER_INJECTION vuln fixture.
//
// The function assigns the attacker-controlled `value` directly into a
// Node response's `Set-Cookie` header via `http.ServerResponse
// #setHeader`. A payload carrying `\r\nSet-Cookie: nyx-injected=pwn`
// splits the single header into two on the wire.
const http = require('http');
function run(res, value) {
res.setHeader('Set-Cookie', value);
}
module.exports = { run };

View file

@ -0,0 +1,50 @@
// Phase 08 (Track J.6) — JavaScript raw-socket HEADER_INJECTION vuln fixture.
//
// Writes the response status line and headers directly to the wire via
// `socket.write`, bypassing the framework-level CRLF validator that
// Node's `http.ServerResponse#setHeader` / Express / axum / Tomcat
// would otherwise interpose. A payload carrying `\r\nSet-Cookie: ...`
// splits the single Set-Cookie header into two on the wire, producing
// the canonical smuggled-second-header shape that
// `ProbeKind::HeaderWireFrame` is designed to catch.
//
// The harness (`src/dynamic/lang/js_shared.rs::emit_header_injection_harness`)
// detects the `net.createServer` import in this file and routes
// through the tier-(b) wire-frame branch: boot a `net.Server` on a
// loopback port, issue one `GET /` over a raw socket, read the bytes
// the handler wrote to the response socket, and emit them as a
// `ProbeKind::HeaderWireFrame` record.
const net = require('net');
// Set by the harness before each request. Bytes go straight onto
// the wire with no encoding pass.
let cookieValue = Buffer.alloc(0);
function setCookieValue(value) {
if (Buffer.isBuffer(value)) {
cookieValue = value;
} else {
cookieValue = Buffer.from(String(value), 'utf8');
}
}
function createServer() {
return net.createServer((socket) => {
socket.once('data', () => {
const body = Buffer.from('ok\n');
const head = Buffer.concat([
Buffer.from('HTTP/1.0 200 OK\r\n'),
Buffer.from('Content-Length: ' + body.length + '\r\n'),
Buffer.from('Set-Cookie: '),
cookieValue,
Buffer.from('\r\n'),
Buffer.from('\r\n'),
]);
socket.write(Buffer.concat([head, body]));
socket.end();
});
socket.on('error', () => {});
});
}
module.exports = { setCookieValue, createServer };

View file

@ -0,0 +1,9 @@
<?php
// Phase 08 (Track J.6) — PHP HEADER_INJECTION benign control fixture.
//
// Same shape as `vuln.php` but URL-encodes the value first via
// `urlencode`, so CRLF bytes land as `%0D%0A` and the wire keeps a
// single header.
function run($value) {
header("Set-Cookie: " . urlencode($value));
}

View file

@ -0,0 +1,10 @@
<?php
// Phase 08 (Track J.6) — PHP HEADER_INJECTION vuln fixture.
//
// The function concatenates the attacker-controlled `$value` directly
// into a `Set-Cookie` header set via the built-in `header()` function.
// A payload carrying `\r\nSet-Cookie: nyx-injected=pwn` splits the
// single header into two on the wire.
function run($value) {
header("Set-Cookie: " . $value);
}

View file

@ -0,0 +1,68 @@
<?php
// Phase 08 (Track J.6) — PHP raw-socket HEADER_INJECTION vuln fixture.
//
// Writes the response status line and headers directly to the wire via
// `fwrite($conn, $raw)` against a `stream_socket_server` server-side
// stream, bypassing the framework-level CRLF validator that PHP's
// built-in `header()` (rejected since PHP 5.1.2) or a Slim / Symfony
// / Laravel response serializer would otherwise interpose. A payload
// carrying `\r\nSet-Cookie: ...` splits the single Set-Cookie header
// into two on the wire, producing the canonical smuggled-second-header
// shape that `ProbeKind::HeaderWireFrame` is designed to catch.
//
// The harness (`src/dynamic/lang/php.rs::emit_header_injection_harness`)
// detects the `stream_socket_server` token in this file and routes
// through the tier-(b) wire-frame branch: bind a loopback server via
// `create_server`, accept one client (`run_once`), issue one raw
// `GET / HTTP/1.0` from the harness, read the bytes the fixture wrote
// to the response stream up to the CRLF-CRLF boundary, and emit them
// as a `ProbeKind::HeaderWireFrame` record.
//
// Uses `stream_socket_server` rather than `socket_create` so the
// fixture has no hard dep on `ext-sockets` (which is core but not
// always built into minimal PHP images); `stream_socket_server`
// resolves through the always-available filesystem-and-network
// stream layer.
// Bytes go straight onto the wire with no encoding pass. The harness
// installs the cookie value before booting the accept loop, mirroring
// the Ruby `set_cookie_value` and Python `Handler.cookie_value` setters.
$GLOBALS['nyx_cookie_value'] = '';
function set_cookie_value($value) {
$GLOBALS['nyx_cookie_value'] = (string) $value;
}
function create_server() {
$errno = 0;
$errstr = '';
$server = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr);
if ($server === false) {
throw new \RuntimeException("stream_socket_server failed: $errno $errstr");
}
return $server;
}
function run_once($server) {
$conn = @stream_socket_accept($server, 5.0);
if ($conn === false) {
return;
}
try {
// Drain whatever request bytes the client sent so the kernel
// does not stall the write that follows. Ignore errors.
@stream_set_timeout($conn, 1, 0);
@fread($conn, 4096);
$body = "ok\n";
$cookie = isset($GLOBALS['nyx_cookie_value']) ? $GLOBALS['nyx_cookie_value'] : '';
$raw = "HTTP/1.0 200 OK\r\n"
. "Content-Length: " . strlen($body) . "\r\n"
. "Set-Cookie: " . $cookie . "\r\n"
. "\r\n"
. $body;
@fwrite($conn, $raw);
@fflush($conn);
} finally {
@fclose($conn);
}
}

View file

@ -0,0 +1,13 @@
# Phase 08 (Track J.6) — Python HEADER_INJECTION benign control fixture.
#
# Same shape as `vuln.py` but URL-encodes the value via
# `urllib.parse.quote` first, so CRLF bytes land as `%0D%0A` and the
# wire keeps a single header.
from urllib.parse import quote
from flask import Response
def run(value):
response = Response("ok")
response.headers["Set-Cookie"] = quote(value, safe="")
return response

View file

@ -0,0 +1,13 @@
# Phase 08 (Track J.6) — Python HEADER_INJECTION vuln fixture.
#
# The function assigns the attacker-controlled `value` directly into
# a Flask response's `Set-Cookie` header via `Response.headers
# .__setitem__`. A payload carrying `\r\nSet-Cookie: nyx-injected=pwn`
# splits the single header into two on the wire.
from flask import Response
def run(value):
response = Response("ok")
response.headers["Set-Cookie"] = value
return response

View file

@ -0,0 +1,37 @@
# Phase 08 (Track J.6) — Python raw-socket HEADER_INJECTION vuln fixture.
#
# Writes the response status line and headers directly to the wire via
# `self.wfile.write`, bypassing the framework-level CRLF validator that
# werkzeug / Flask / axum / Tomcat would otherwise interpose. A payload
# carrying `\r\nSet-Cookie: ...` splits the single Set-Cookie header
# into two on the wire, producing the canonical smuggled-second-header
# shape that `ProbeKind::HeaderWireFrame` is designed to catch.
#
# The harness (`src/dynamic/lang/python.rs::emit_header_injection_harness`)
# detects the `BaseHTTPRequestHandler` import in this file and routes
# through the tier-(b) wire-frame branch: boot `HTTPServer` on a
# loopback port, issue one `GET /` over a raw socket, read the bytes
# the handler wrote to the response socket, and emit them as a
# `ProbeKind::HeaderWireFrame` record.
from http.server import BaseHTTPRequestHandler
class VulnHandler(BaseHTTPRequestHandler):
# Set by the harness before each request. Bytes go straight onto
# the wire with no encoding pass.
cookie_value: bytes = b""
def do_GET(self):
body = b"ok\n"
raw = (
b"HTTP/1.0 200 OK\r\n"
b"Content-Length: " + str(len(body)).encode("ascii") + b"\r\n"
b"Set-Cookie: " + self.__class__.cookie_value + b"\r\n"
b"\r\n"
) + body
self.wfile.write(raw)
def log_message(self, *args, **kwargs):
# Silence default stderr logging so the harness captures only
# the probe + sink-hit sentinel.
return

View file

@ -0,0 +1,13 @@
# Phase 08 (Track J.6) — Ruby HEADER_INJECTION benign control fixture.
#
# Same shape as `vuln.rb` but URL-encodes the value first via
# `URI.encode_www_form_component`, so CRLF bytes land as `%0D%0A` and
# the wire keeps a single header.
require 'rack'
require 'uri'
def run(value)
response = Rack::Response.new
response.set_header('Set-Cookie', URI.encode_www_form_component(value))
response
end

View file

@ -0,0 +1,13 @@
# Phase 08 (Track J.6) — Ruby HEADER_INJECTION vuln fixture.
#
# The function assigns the attacker-controlled `value` directly into a
# Rack response's `Set-Cookie` header via `Rack::Response#set_header`.
# A payload carrying `\r\nSet-Cookie: nyx-injected=pwn` splits the
# single header into two on the wire.
require 'rack'
def run(value)
response = Rack::Response.new
response.set_header('Set-Cookie', value)
response
end

View file

@ -0,0 +1,54 @@
# Phase 08 (Track J.6) — Ruby raw-socket HEADER_INJECTION vuln fixture.
#
# Writes the response status line and headers directly to the wire via
# `socket.write`, bypassing the framework-level CRLF validator that
# Rack / Sinatra / Rails would otherwise interpose. A payload carrying
# `\r\nSet-Cookie: ...` splits the single Set-Cookie header into two on
# the wire, producing the canonical smuggled-second-header shape that
# `ProbeKind::HeaderWireFrame` is designed to catch.
#
# The harness (`src/dynamic/lang/ruby.rs::emit_header_injection_harness`)
# detects the `TCPServer.new` token in this file and routes through the
# tier-(b) wire-frame branch: bind a loopback `TCPServer` via
# `create_server`, accept one client (`run_once`), issue one raw
# `GET / HTTP/1.0` from the harness, read the bytes the fixture wrote
# to the response socket up to the CRLF-CRLF boundary, and emit them
# as a `ProbeKind::HeaderWireFrame` record.
require 'socket'
# Bytes go straight onto the wire with no encoding pass. The harness
# installs the cookie value before booting the accept loop, mirroring
# the JS `setCookieValue` and Python `Handler.cookie_value =` setters.
$nyx_cookie_value = String.new(encoding: 'BINARY')
def set_cookie_value(value)
$nyx_cookie_value = value.respond_to?(:b) ? value.b : value.to_s.b
end
def create_server
TCPServer.new('127.0.0.1', 0)
end
def run_once(server)
socket = server.accept
begin
socket.recv(4096)
rescue StandardError
# ignore client-side read errors
end
body = "ok\n".b
raw = String.new(encoding: 'BINARY')
raw << "HTTP/1.0 200 OK\r\n".b
raw << "Content-Length: #{body.bytesize}\r\n".b
raw << "Set-Cookie: ".b
raw << $nyx_cookie_value
raw << "\r\n\r\n".b
raw << body
socket.write(raw)
ensure
begin
socket.close if socket
rescue StandardError
# ignore close errors
end
end

View file

@ -0,0 +1,16 @@
// Phase 08 (Track J.6) — Rust HEADER_INJECTION benign control fixture.
//
// Same shape as `vuln.rs` but routes the value through the
// `percent-encoding` crate first, so CRLF bytes land as `%0D%0A` and
// the wire keeps a single header.
use axum::http::HeaderMap;
use axum::http::HeaderValue;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
pub fn run(headers: &mut HeaderMap, value: &str) {
let encoded: String = utf8_percent_encode(value, NON_ALPHANUMERIC).collect();
headers.insert(
"set-cookie",
HeaderValue::from_str(&encoded).unwrap(),
);
}

View file

@ -0,0 +1,17 @@
// Phase 08 (Track J.6) — Rust HEADER_INJECTION vuln fixture.
//
// The function inserts the attacker-controlled `value` into an axum
// `HeaderMap` via `headers_mut().insert`, bypassing
// `HeaderValue::from_str`'s newline check by passing the tainted
// bytes through `HeaderValue::from_bytes(...).unwrap()`. A payload
// carrying `\r\nSet-Cookie: nyx-injected=pwn` splits the single
// header into two on the wire.
use axum::http::HeaderMap;
use axum::http::HeaderValue;
pub fn run(headers: &mut HeaderMap, value: &str) {
headers.insert(
"set-cookie",
HeaderValue::from_bytes(value.as_bytes()).unwrap(),
);
}

View file

@ -0,0 +1,58 @@
// Phase 08 (Track J.6) — Rust raw-socket HEADER_INJECTION vuln fixture.
//
// Writes the response status line and headers directly to the wire via
// `TcpStream::write_all`, bypassing the framework-level CRLF validator
// that axum / Tomcat would otherwise interpose. A payload carrying
// `\r\nSet-Cookie: ...` splits the single Set-Cookie header into two on
// the wire, producing the canonical smuggled-second-header shape that
// `ProbeKind::HeaderWireFrame` is designed to catch.
//
// The harness (`src/dynamic/lang/rust.rs::emit_header_injection_harness`)
// detects the `TcpListener::bind` token in this file and routes through
// the tier-(b) wire-frame branch: bind a loopback `TcpListener` via
// `create_server`, spawn the accept loop on a thread (`run_once`),
// issue one raw `GET / HTTP/1.0\r\n` from the harness, read the bytes
// the fixture wrote to the response socket, and emit them as a
// `ProbeKind::HeaderWireFrame` record.
use std::io::{Read, Write};
use std::net::{Shutdown, TcpListener};
use std::sync::Mutex;
/// Bytes go straight onto the wire with no encoding pass. The harness
/// installs the cookie value before booting the accept loop, mirroring
/// the JS `setCookieValue` and Python `Handler.cookie_value =` setters.
static COOKIE_VALUE: Mutex<Vec<u8>> = Mutex::new(Vec::new());
pub fn set_cookie_value(value: &[u8]) {
let mut guard = COOKIE_VALUE.lock().expect("cookie mutex poisoned");
guard.clear();
guard.extend_from_slice(value);
}
pub fn create_server() -> TcpListener {
TcpListener::bind("127.0.0.1:0").expect("bind ephemeral port")
}
pub fn run_once(listener: TcpListener) {
let Ok((mut socket, _addr)) = listener.accept() else {
return;
};
let mut scratch = [0u8; 4096];
let _ = socket.read(&mut scratch);
let cookie = COOKIE_VALUE
.lock()
.expect("cookie mutex poisoned")
.clone();
let body = b"ok\n";
let mut raw = Vec::new();
raw.extend_from_slice(b"HTTP/1.0 200 OK\r\n");
raw.extend_from_slice(format!("Content-Length: {}\r\n", body.len()).as_bytes());
raw.extend_from_slice(b"Set-Cookie: ");
raw.extend_from_slice(&cookie);
raw.extend_from_slice(b"\r\n");
raw.extend_from_slice(b"\r\n");
raw.extend_from_slice(body);
let _ = socket.write_all(&raw);
let _ = socket.shutdown(Shutdown::Both);
}