mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-06 19:35:13 +02:00
96 lines
4.2 KiB
Rust
96 lines
4.2 KiB
Rust
|
|
//! demo.rs — realistic taint-tracking playground
|
||
|
|
//! `cargo add html-escape shell-escape` before compiling.
|
||
|
|
|
||
|
|
use std::{env, process::Command, fs};
|
||
|
|
|
||
|
|
#[derive(Default)]
|
||
|
|
struct UserCtx {
|
||
|
|
query: String, // potentially tainted
|
||
|
|
sanitized: String, // should remain clean
|
||
|
|
}
|
||
|
|
|
||
|
|
/// ---------- helper wrappers so we get nice Source / Sink labels ----------
|
||
|
|
fn source_env(var: &str) -> String {
|
||
|
|
env::var(var).unwrap_or_default() // Source(env-var)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn source_file(path: &str) -> String {
|
||
|
|
fs::read_to_string(path).unwrap_or_default() // Source(file-io)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn sink_shell(arg: &str) {
|
||
|
|
Command::new("sh").arg(arg).status().unwrap(); // Sink(process-spawn)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn sink_html(out: &str) {
|
||
|
|
println!("{out}"); // Sink(html-out)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn sanitize_html(s: &str) -> String {
|
||
|
|
html_escape::encode_safe(s) // Sanitizer(html-escape)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn sanitize_shell(s: &str) -> String {
|
||
|
|
shell_escape::unix::escape(s.into()).into_owned() // Sanitizer(shell-escape)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// ---------- 1. Main demo fuction ----------
|
||
|
|
fn main() {
|
||
|
|
// FLOW A ────────────────────────────────────────────────────────────────
|
||
|
|
// env → sanitized → safe shell
|
||
|
|
let raw = source_env("USER_CMD");
|
||
|
|
let clean = sanitize_shell(&raw);
|
||
|
|
sink_shell(&clean); // EXPECT: SAFE
|
||
|
|
|
||
|
|
// FLOW B ────────────────────────────────────────────────────────────────
|
||
|
|
// env → if-else, only one branch escapes
|
||
|
|
let arg = source_env("ANOTHER");
|
||
|
|
if arg.len() > 5 {
|
||
|
|
sink_shell(&arg); // EXPECT: UNSAFE (branch tainted)
|
||
|
|
} else {
|
||
|
|
let escaped = sanitize_shell(&arg);
|
||
|
|
sink_shell(&escaped); // safe
|
||
|
|
}
|
||
|
|
|
||
|
|
// FLOW C ────────────────────────────────────────────────────────────────
|
||
|
|
// file → while loop → HTML sanitizer cleared
|
||
|
|
let mut data = source_file("/tmp/input.txt");
|
||
|
|
while data.len() < 32 {
|
||
|
|
data.push('x');
|
||
|
|
}
|
||
|
|
let html_ok = sanitize_html(&data);
|
||
|
|
sink_html(&html_ok); // safe
|
||
|
|
|
||
|
|
// FLOW D ────────────────────────────────────────────────────────────────
|
||
|
|
// file → struct field → match → unsanitised HTML
|
||
|
|
let mut ctx = UserCtx::default();
|
||
|
|
ctx.query = source_file("/tmp/q.txt");
|
||
|
|
// overwrite the clean field; `ctx.sanitized` is *not* tainted
|
||
|
|
ctx.sanitized = sanitize_html("constant");
|
||
|
|
match ctx {
|
||
|
|
UserCtx { query, sanitized } if query.contains("DROP") => {
|
||
|
|
sink_html(&query); // EXPECT: UNSAFE
|
||
|
|
}
|
||
|
|
_ => {
|
||
|
|
sink_html(&ctx.sanitized); // safe
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// FLOW E ────────────────────────────────────────────────────────────────
|
||
|
|
// source → function call → reassignment clears taint
|
||
|
|
let mut name = source_env("USER"); // tainted
|
||
|
|
greet(&name); // just prints
|
||
|
|
name = "anonymous".into(); // kills taint
|
||
|
|
greet(&name); // safe
|
||
|
|
|
||
|
|
// FLOW F ────────────────────────────────────────────────────────────────
|
||
|
|
// Multiple sanitizers, only the *right* one matters
|
||
|
|
let cmd = source_env("MIXED");
|
||
|
|
let partly = sanitize_html(&cmd); // wrong sanitizer
|
||
|
|
sink_shell(&partly); // EXPECT: UNSAFE
|
||
|
|
}
|
||
|
|
|
||
|
|
/// helper (non-sink) function
|
||
|
|
fn greet(who: &str) {
|
||
|
|
println!("Hello, {who}");
|
||
|
|
}
|