[pitboss/grind] deferred session-0025 (20260522T043516Z-29b8)

This commit is contained in:
pitboss 2026-05-22 07:54:57 -05:00
parent c751c4b07b
commit 853fd281c5
4 changed files with 508 additions and 8 deletions

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

@ -934,4 +934,87 @@ mod e2e_phase_08 {
.collect::<Vec<_>>(),
);
}
// Phase 08 tier-(b): Ruby raw-socket wire-frame fixture.
// `tests/dynamic_fixtures/header_injection/ruby_raw/vuln.rb` binds
// a `TCPServer` via `create_server` whose `run_once` handler writes
// raw bytes via `TCPSocket#write`, bypassing Rack's CRLF strip on
// `Rack::Response#set_header`. The harness boots the server on a
// loopback port, opens a client `TCPSocket`, reads the response-
// header block off the socket, and emits a
// `ProbeKind::HeaderWireFrame` record. Asserts the test exercises
// the wire-frame branch (not the synthetic fallback) by pinning
// `wire_frame_len` in the captured stdout — that literal only
// appears in the tier-(b) write path.
fn build_ruby_raw_spec(entry_name: &str) -> (HarnessSpec, TempDir) {
let fixture_src = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/dynamic_fixtures/header_injection/ruby_raw/vuln.rb");
let tmp = TempDir::new().expect("create tempdir");
let dst = tmp.path().join("vuln.rb");
std::fs::copy(&fixture_src, &dst).expect("copy ruby_raw fixture into tempdir");
let entry_file = dst.to_string_lossy().into_owned();
let mut digest = blake3::Hasher::new();
digest.update(b"phase08-e2e-header-injection|ruby_raw|vuln.rb");
let spec_hash = format!("{:016x}", {
let bytes = digest.finalize();
u64::from_le_bytes(bytes.as_bytes()[..8].try_into().unwrap())
});
let spec = HarnessSpec {
finding_id: spec_hash.clone(),
entry_file: entry_file.clone(),
entry_name: entry_name.to_owned(),
entry_kind: EntryKind::Function,
lang: Lang::Ruby,
toolchain_id: default_toolchain_id(Lang::Ruby).into(),
payload_slot: PayloadSlot::Param(0),
expected_cap: Cap::HEADER_INJECTION,
constraint_hints: vec![],
sink_file: entry_file,
sink_line: 1,
spec_hash: spec_hash.clone(),
derivation: SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
java_toolchain: nyx_scanner::dynamic::spec::JavaToolchain::default(),
};
(spec, tmp)
}
#[test]
fn ruby_raw_socket_vuln_confirms_via_wire_frame_probe() {
if !command_available("ruby") {
eprintln!("SKIP ruby_raw: missing ruby");
return;
}
let _guard = FIXTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let (spec, _tmp) = build_ruby_raw_spec("run");
let opts = SandboxOptions {
backend: SandboxBackend::Process,
..SandboxOptions::default()
};
let outcome = match run_spec(&spec, &opts) {
Ok(outcome) => outcome,
Err(RunError::BuildFailed { stderr, attempts }) => {
eprintln!(
"SKIP ruby_raw: harness build failed after {attempts} attempts: {stderr}",
);
return;
}
Err(e) => panic!("run_spec(ruby_raw) errored: {e:?}"),
};
assert_confirmed(Lang::Ruby, &outcome);
let any_wire_frame_marker = outcome.attempts.iter().any(|a| {
String::from_utf8_lossy(&a.outcome.stdout).contains("wire_frame_len")
});
assert!(
any_wire_frame_marker,
"ruby_raw fixture must exercise the tier-(b) wire-frame harness branch; \
expected `wire_frame_len` substring in at least one attempt's stdout, got attempts={:?}",
outcome
.attempts
.iter()
.map(|a| String::from_utf8_lossy(&a.outcome.stdout).into_owned())
.collect::<Vec<_>>(),
);
}
}