nyx/tests/dynamic_fixtures/header_injection/ruby_raw/vuln.rb
2026-06-05 10:16:30 -05:00

54 lines
1.8 KiB
Ruby

# 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