mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
[pitboss/grind] deferred session-0003 (20260520T233019Z-6958)
This commit is contained in:
parent
a1a8a2140c
commit
67ffeed780
5 changed files with 106 additions and 77 deletions
|
|
@ -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) {{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue