[pitboss/grind] deferred session-0003 (20260520T233019Z-6958)

This commit is contained in:
pitboss 2026-05-20 21:07:23 -05:00
parent a1a8a2140c
commit 67ffeed780
5 changed files with 106 additions and 77 deletions

View file

@ -820,38 +820,36 @@ pub fn emit_ssti_harness(_spec: &HarnessSpec) -> HarnessSource {
let shim = probe_shim();
let source = format!(
r#"// Nyx dynamic harness — SSTI Thymeleaf (Phase 04 / Track J.2).
//
// Routes `NYX_PAYLOAD` through the real `org.thymeleaf.TemplateEngine`
// dependency. The corpus vuln payload `[[${{7*7}}]]` reaches
// Thymeleaf's SpEL evaluator and renders as `49`; the benign
// control `7*7` has no `[[${{ ... }}]]` markers so the engine echoes
// it verbatim.
//
// Compile + classpath bootstrap is handled by the brief's Maven
// addendum — the synthetic harness this replaces never linked
// Thymeleaf, so the build path needs `pom.xml` plumbing routed
// through `prepare_java` before a host without `org.thymeleaf`
// on the classpath can run the harness. Until that plumbing
// lands the e2e Java SSTI test SKIPs via the runner's BuildFailed
// branch.
import java.io.FileWriter;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
public class NyxHarness {{
{shim}
static String nyxThymeleafRender(String payload) {{
Pattern p = Pattern.compile("\\[\\[\\$\\{{(.+?)\\}}\\]\\]");
Matcher m = p.matcher(payload);
StringBuffer out = new StringBuffer(payload.length());
while (m.find()) {{
String expr = m.group(1).trim();
Matcher mul = Pattern.compile("^(\\d+)\\s*\\*\\s*(\\d+)$").matcher(expr);
Matcher add = Pattern.compile("^(\\d+)\\s*\\+\\s*(\\d+)$").matcher(expr);
String repl;
if (mul.matches()) {{
long a = Long.parseLong(mul.group(1));
long b = Long.parseLong(mul.group(2));
repl = Long.toString(a * b);
}} else if (add.matches()) {{
long a = Long.parseLong(add.group(1));
long b = Long.parseLong(add.group(2));
repl = Long.toString(a + b);
}} else {{
repl = Matcher.quoteReplacement(m.group(0));
}}
m.appendReplacement(out, Matcher.quoteReplacement(repl));
try {{
TemplateEngine engine = new TemplateEngine();
Context ctx = new Context();
return engine.process(payload, ctx);
}} catch (RuntimeException e) {{
return "<thymeleaf-error:" + e.getClass().getSimpleName() + ">";
}}
m.appendTail(out);
return out.toString();
}}
static void nyxSstiProbe(String rendered) {{

View file

@ -1074,20 +1074,30 @@ pub fn emit_ssti_harness(_spec: &HarnessSpec) -> HarnessSource {
let shim = probe_shim();
let body = format!(
r#"// Nyx dynamic harness — SSTI Handlebars (Phase 04 / Track J.2).
//
// Routes `NYX_PAYLOAD` through the real `handlebars` npm package's
// `compile(payload)({{}})` call. Handlebars does not evaluate
// arithmetic in `{{{{ ... }}}}` blocks by itself; the corpus vuln
// payload `{{{{multiply 7 7}}}}` invokes a registered `multiply`
// helper which returns `49`. The benign control `7*7` has no
// `{{{{` / `}}}}` markers so the engine echoes it verbatim.
{shim}
const Handlebars = require('handlebars');
Handlebars.registerHelper('multiply', function (a, b) {{
return String(Number(a) * Number(b));
}});
Handlebars.registerHelper('add', function (a, b) {{
return String(Number(a) + Number(b));
}});
function nyxHandlebarsRender(payload) {{
return payload.replace(/\{{\{{(.+?)\}}\}}/g, function (_, raw) {{
const expr = raw.trim();
const helperMatch = expr.match(/^(\w+)\s+(\d+)\s+(\d+)$/);
if (helperMatch) {{
const a = parseInt(helperMatch[2], 10);
const b = parseInt(helperMatch[3], 10);
if (helperMatch[1] === 'multiply') return String(a * b);
if (helperMatch[1] === 'add') return String(a + b);
}}
return _;
}});
try {{
return Handlebars.compile(payload)({{}});
}} catch (e) {{
return '<handlebars-error:' + (e && e.name ? e.name : 'Error') + '>';
}}
}}
function nyxSstiProbe(rendered) {{
@ -1119,7 +1129,12 @@ console.log(JSON.stringify({{ render: rendered }}));
source: body,
filename: "harness.js".to_owned(),
command: vec!["node".to_owned(), "harness.js".to_owned()],
extra_files: Vec::new(),
extra_files: vec![(
"package.json".to_owned(),
r#"{"name":"nyx-ssti-handlebars-harness","private":true,"dependencies":{"handlebars":"^4.7.8"}}
"#
.to_owned(),
)],
entry_subpath: None,
}
}

View file

@ -603,19 +603,25 @@ pub fn emit_ssti_harness(_spec: &HarnessSpec) -> HarnessSource {
let body = format!(
r#"<?php
// Nyx dynamic harness — SSTI Twig (Phase 04 / Track J.2).
//
// Routes `NYX_PAYLOAD` through the real `twig/twig` composer
// package's `Twig\Environment::createTemplate(...)->render([])`
// call. The corpus vuln payload `{{{{7*7}}}}` reaches Twig's
// expression evaluator and renders as `49`; the benign control
// `7*7` has no `{{{{` / `}}}}` markers so the engine echoes it
// verbatim.
require_once __DIR__ . '/vendor/autoload.php';
{shim}
function _nyx_twig_render(string $payload): string {{
return preg_replace_callback('/\{{\{{(.+?)\}}\}}/', function ($m) {{
$expr = trim($m[1]);
if (preg_match('/^(\d+)\s*\*\s*(\d+)$/', $expr, $mm)) {{
return (string) ((int) $mm[1] * (int) $mm[2]);
}}
if (preg_match('/^(\d+)\s*\+\s*(\d+)$/', $expr, $mm)) {{
return (string) ((int) $mm[1] + (int) $mm[2]);
}}
return $m[0];
}}, $payload) ?? $payload;
try {{
$twig = new \Twig\Environment(new \Twig\Loader\ArrayLoader([]));
$template = $twig->createTemplate($payload);
return $template->render([]);
}} catch (\Throwable $e) {{
return '<twig-error:' . get_class($e) . '>';
}}
}}
function _nyx_ssti_probe(string $rendered): void {{
@ -643,7 +649,20 @@ echo json_encode(["render" => $rendered]) . "\n";
source: body,
filename: "harness.php".to_owned(),
command: vec!["php".to_owned(), "harness.php".to_owned()],
extra_files: vec![],
extra_files: vec![(
"composer.json".to_owned(),
r#"{
"name": "nyx/ssti-twig-harness",
"require": {
"twig/twig": "^3.0"
},
"config": {
"preferred-install": "dist"
}
}
"#
.to_owned(),
)],
entry_subpath: None,
}
}

View file

@ -1380,24 +1380,22 @@ pub fn emit_ssti_harness(_spec: &HarnessSpec) -> HarnessSource {
let probe = probe_shim();
let body = format!(
r#"#!/usr/bin/env python3
"""Nyx dynamic harness — SSTI Jinja2 (Phase 04 / Track J.2)."""
import os, json, re, sys
"""Nyx dynamic harness — SSTI Jinja2 (Phase 04 / Track J.2).
Routes `NYX_PAYLOAD` through the real `jinja2.Template(...).render()`
call. The corpus vuln payload `{{{{7*7}}}}` reaches Jinja2's
expression evaluator and renders as `49`; the benign control `7*7`
has no `{{{{` / `}}}}` markers so the engine echoes it verbatim.
"""
import os, json, sys
{probe}
import jinja2
def _nyx_jinja2_render(payload):
# Concretised Jinja2 evaluator for the corpus payloads: substitutes
# arithmetic inside `{{` / `}}` markers and echoes everything else.
def _eval(match):
expr = match.group(1).strip()
m = re.match(r"^(\d+)\s*\*\s*(\d+)$", expr)
if m:
return str(int(m.group(1)) * int(m.group(2)))
m = re.match(r"^(\d+)\s*\+\s*(\d+)$", expr)
if m:
return str(int(m.group(1)) + int(m.group(2)))
return match.group(0)
return re.sub(r"\{{\{{(.+?)\}}\}}", _eval, payload)
template = jinja2.Template(payload)
return template.render()
def _nyx_ssti_probe(rendered):
rec = {{
@ -1416,13 +1414,12 @@ def __nyx_now_ns():
def _nyx_run():
payload = os.environ.get("NYX_PAYLOAD", "")
rendered = _nyx_jinja2_render(payload)
try:
rendered = _nyx_jinja2_render(payload)
except jinja2.TemplateError as exc:
rendered = "<jinja2-error:{{}}>".format(type(exc).__name__)
_nyx_ssti_probe(rendered)
# Sink-hit sentinel flips SandboxOutcome.sink_hit so the runner's
# `vuln_fired && sink_hit` gate clears.
print("__NYX_SINK_HIT__", flush=True)
# Render JSON body the TemplateEvalEqual predicate compares the
# `render` field's integer value against the corpus `expected`.
sys.stdout.write(json.dumps({{"render": rendered}}) + "\n")
sys.stdout.flush()
@ -1434,7 +1431,7 @@ if __name__ == "__main__":
source: body,
filename: "harness.py".to_owned(),
command: vec!["python3".to_owned(), "harness.py".to_owned()],
extra_files: Vec::new(),
extra_files: vec![("requirements.txt".to_owned(), "Jinja2\n".to_owned())],
entry_subpath: None,
}
}

View file

@ -921,20 +921,21 @@ pub fn emit_ssti_harness(_spec: &HarnessSpec) -> HarnessSource {
let shim = probe_shim();
let body = format!(
r#"# Nyx dynamic harness — SSTI ERB (Phase 04 / Track J.2).
#
# Routes `NYX_PAYLOAD` through the real stdlib `ERB.new(payload).result`
# call. The corpus vuln payload `<%= 7*7 %>` reaches ERB's Ruby
# expression evaluator and renders as `49`; the benign control `7*7`
# has no `<%= ... %>` markers so the engine echoes it verbatim.
require 'erb'
require 'json'
{shim}
def _nyx_erb_render(payload)
payload.gsub(/<%=\s*([^%]+?)\s*%>/) do
expr = Regexp.last_match(1).strip
if (m = expr.match(/\A(\d+)\s*\*\s*(\d+)\z/))
(m[1].to_i * m[2].to_i).to_s
elsif (m = expr.match(/\A(\d+)\s*\+\s*(\d+)\z/))
(m[1].to_i + m[2].to_i).to_s
else
Regexp.last_match(0)
end
begin
ERB.new(payload).result(binding)
rescue ScriptError, StandardError => e
"<erb-error:#{{e.class.name}}>"
end
end
@ -955,7 +956,6 @@ end
payload = ENV['NYX_PAYLOAD'] || ''
rendered = _nyx_erb_render(payload)
_nyx_ssti_probe(rendered)
# Sink-hit sentinel and render JSON body.
STDOUT.puts '__NYX_SINK_HIT__'
STDOUT.puts JSON.generate({{"render" => rendered}})
STDOUT.flush