refactor(dynamic): standardize shell commands across fixtures, add __NYX_SINK_HIT__ markers, improve PHP support

This commit is contained in:
elipeter 2026-05-23 10:31:57 -05:00
parent ca075a7141
commit fe09986a25
32 changed files with 707 additions and 71 deletions

View file

@ -6,11 +6,11 @@
void UserService_run(const char *input, size_t len) {
(void)len;
/* Uses execve via fork; the shell never sees `input`. */
/* Uses execve via fork; the shell never sees or echoes `input`. */
pid_t pid = fork();
if (pid == 0) {
char *argv[] = { (char*)"/bin/echo", (char*)(input ? input : ""), NULL };
execv("/bin/echo", argv);
char *argv[] = { (char*)"/usr/bin/true", (char*)(input ? input : ""), NULL };
execv("/usr/bin/true", argv);
_exit(127);
}
}

View file

@ -10,7 +10,7 @@
void UserService_run(const char *input, size_t len) {
(void)len;
char buf[512];
snprintf(buf, sizeof(buf), "echo %s", input ? input : "");
snprintf(buf, sizeof(buf), "true %s", input ? input : "");
/* SINK: tainted input → system(3) */
system(buf);
}

View file

@ -9,8 +9,8 @@ public:
void run(const std::string& input) {
pid_t pid = fork();
if (pid == 0) {
const char* argv[] = { "/bin/echo", input.c_str(), nullptr };
execv("/bin/echo", const_cast<char* const*>(argv));
const char* argv[] = { "/usr/bin/true", input.c_str(), nullptr };
execv("/usr/bin/true", const_cast<char* const*>(argv));
_exit(127);
}
int status = 0;

View file

@ -10,7 +10,7 @@ class UserService {
public:
UserService() = default;
void run(const std::string& input) {
std::string cmd = std::string("echo ") + input;
std::string cmd = std::string("true ") + input;
// SINK: tainted input → system(3)
std::system(cmd.c_str());
}

View file

@ -6,6 +6,6 @@ import "os/exec"
type UserService struct{}
func (UserService) Run(input string) string {
out, _ := exec.Command("/bin/echo", input).Output()
out, _ := exec.Command("true", input).Output()
return string(out)
}

View file

@ -12,6 +12,6 @@ type UserService struct{}
func (UserService) Run(input string) string {
// SINK: tainted input → shell -c
out, _ := exec.Command("sh", "-c", "echo "+input).Output()
out, _ := exec.Command("sh", "-c", "true "+input).Output()
return string(out)
}

View file

@ -1,20 +1,16 @@
// Phase 19 (Track M.1) class-method benign control for Java.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
//
// The payload is passed as an argv element to true(1), so no shell parses or
// echoes marker bytes.
public class Benign {
public static class UserRepository {
public UserRepository() {}
public void findByName(String name) throws SQLException {
Connection c = DriverManager.getConnection("jdbc:sqlite::memory:");
PreparedStatement ps = c.prepareStatement("SELECT id FROM users WHERE name = ?");
ps.setString(1, name);
ps.execute();
ps.close();
c.close();
public void findByName(String name) throws Exception {
Process p = new ProcessBuilder("/usr/bin/true", name)
.redirectErrorStream(true)
.start();
p.waitFor();
}
}
}

View file

@ -1,25 +1,22 @@
// Phase 19 (Track M.1) class-method vuln fixture for Java.
//
// UserRepository.findByName concatenates user input into a JDBC SQL
// statement. Default constructor exists so the harness can build the
// receiver without stubbing dependencies.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.sql.SQLException;
// UserRepository.findByName concatenates user input into a shell command.
// The nested class has a default constructor so the ClassMethod harness can
// build the receiver reflectively.
import java.io.InputStream;
public class Vuln {
public static class UserRepository {
public UserRepository() {}
public void findByName(String name) throws SQLException {
Connection c = DriverManager.getConnection("jdbc:sqlite::memory:");
Statement s = c.createStatement();
// SINK: tainted concat into SQL
String sql = "SELECT id FROM users WHERE name = '" + name + "'";
s.execute(sql);
s.close();
c.close();
public void findByName(String name) throws Exception {
Process p = new ProcessBuilder("sh", "-c", "true " + name)
.redirectErrorStream(true)
.start();
try (InputStream in = p.getInputStream()) {
in.transferTo(System.out);
}
p.waitFor();
}
}
}

View file

@ -1,14 +1,14 @@
// Phase 19 (Track M.1) — class-method benign control for JavaScript.
//
// UserService.run routes the input through execFileSync with argv form so
// the shell never interprets the string.
// the shell never interprets the string or echoes marker bytes.
'use strict';
const { execFileSync } = require('child_process');
class UserService {
constructor() {}
run(input) {
return execFileSync('/bin/echo', [input]).toString();
return execFileSync('true', [input]).toString();
}
}

View file

@ -9,7 +9,7 @@ class UserService {
constructor() {}
run(input) {
// SINK: untrusted input → shell
return execSync('echo ' + input).toString();
return execSync('true ' + input).toString();
}
}

View file

@ -5,6 +5,6 @@ class UserService {
public function __construct() {}
public function run($input) {
return shell_exec('echo ' . escapeshellarg($input));
return shell_exec('true ' . escapeshellarg($input));
}
}

View file

@ -9,6 +9,6 @@ class UserService {
public function run($input) {
// SINK: tainted input → shell.
return shell_exec('echo ' . $input);
return shell_exec('true ' . $input);
}
}

View file

@ -6,6 +6,6 @@ class UserService
end
def run(input)
`echo #{Shellwords.escape(input)}`
`true #{Shellwords.escape(input)}`
end
end

View file

@ -8,6 +8,6 @@ class UserService
def run(input)
# SINK: tainted input → shell
`echo #{input}`
`true #{input}`
end
end

View file

@ -5,7 +5,7 @@ pub struct UserService;
impl UserService {
pub fn run(&self, input: &str) -> String {
let out = std::process::Command::new("/bin/echo")
let out = std::process::Command::new("true")
.arg(input)
.output()
.expect("exec");

View file

@ -10,7 +10,7 @@ pub struct UserService;
impl UserService {
pub fn run(&self, input: &str) -> String {
// SINK: tainted input → shell -c
let cmd = format!("echo {}", input);
let cmd = format!("true {}", input);
let out = std::process::Command::new("sh")
.arg("-c")
.arg(&cmd)

View file

@ -1,9 +1,12 @@
// Phase 19 (Track M.1) — class-method benign control for TypeScript.
import { execFileSync } from 'child_process';
'use strict';
const { execFileSync } = require('child_process');
export class UserService {
class UserService {
constructor() {}
run(input: string): string {
return execFileSync('/bin/echo', [input]).toString();
run(input) {
return execFileSync('true', [input]).toString();
}
}
module.exports = { UserService };

View file

@ -1,12 +1,17 @@
// Phase 19 (Track M.1) — class-method vuln fixture for TypeScript.
//
// UserService.run forwards user input directly to a shell. Default ctor.
import { execSync } from 'child_process';
// UserService.run forwards user input directly to a shell. The source
// stays CommonJS-compatible because the harness stages TS fixtures as
// entry.js for stock Node.
'use strict';
const { execSync } = require('child_process');
export class UserService {
class UserService {
constructor() {}
run(input: string): string {
run(input) {
// SINK: untrusted input flows into the shell
return execSync('echo ' + input).toString();
return execSync('true ' + input).toString();
}
}
module.exports = { UserService };