#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Lang {
Js,
Ts,
Python,
Rust,
Go,
Java,
CSharp,
C,
Cpp,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SinkKind {
Exec,
SqlExec,
Eval,
FsWritePath,
HtmlSink,
UrlFetch,
Deserialize,
Xxe,
NosqlQuery,
}
impl SinkKind {
pub fn as_str(&self) -> &'static str {
match self {
SinkKind::Exec => "exec",
SinkKind::SqlExec => "sql_exec",
SinkKind::Eval => "eval",
SinkKind::FsWritePath => "fs_write_path",
SinkKind::HtmlSink => "html_sink",
SinkKind::UrlFetch => "url_fetch",
SinkKind::Deserialize => "deserialize",
SinkKind::Xxe => "xxe",
SinkKind::NosqlQuery => "nosql_query",
}
}
}
struct Sink {
receiver: &'static str,
name: &'static str,
kind: SinkKind,
}
const JS_SINKS: &[Sink] = &[
Sink {
receiver: "child_process",
name: "exec",
kind: SinkKind::Exec,
},
Sink {
receiver: "child_process",
name: "execSync",
kind: SinkKind::Exec,
},
Sink {
receiver: "child_process",
name: "execFile",
kind: SinkKind::Exec,
},
Sink {
receiver: "child_process",
name: "execFileSync",
kind: SinkKind::Exec,
},
Sink {
receiver: "child_process",
name: "spawn",
kind: SinkKind::Exec,
},
Sink {
receiver: "child_process",
name: "spawnSync",
kind: SinkKind::Exec,
},
Sink {
receiver: "",
name: "eval",
kind: SinkKind::Eval,
},
Sink {
receiver: "",
name: "Function",
kind: SinkKind::Eval,
}, Sink {
receiver: "",
name: "setInterval",
kind: SinkKind::Eval,
},
Sink {
receiver: "",
name: "setTimeout",
kind: SinkKind::Eval,
},
Sink {
receiver: "fs",
name: "writeFile",
kind: SinkKind::FsWritePath,
},
Sink {
receiver: "fs",
name: "writeFileSync",
kind: SinkKind::FsWritePath,
},
Sink {
receiver: "fs",
name: "appendFile",
kind: SinkKind::FsWritePath,
},
Sink {
receiver: "fs",
name: "appendFileSync",
kind: SinkKind::FsWritePath,
},
Sink {
receiver: "fs",
name: "createWriteStream",
kind: SinkKind::FsWritePath,
},
Sink {
receiver: "fs",
name: "unlink",
kind: SinkKind::FsWritePath,
},
Sink {
receiver: "fs",
name: "rename",
kind: SinkKind::FsWritePath,
},
Sink {
receiver: "fs",
name: "rmdir",
kind: SinkKind::FsWritePath,
},
Sink {
receiver: "",
name: "fetch",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "axios",
name: "get",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "axios",
name: "post",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "axios",
name: "put",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "axios",
name: "request",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "http",
name: "get",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "http",
name: "request",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "https",
name: "get",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "https",
name: "request",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "got",
name: "get",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "document",
name: "write",
kind: SinkKind::HtmlSink,
},
Sink {
receiver: "document",
name: "writeln",
kind: SinkKind::HtmlSink,
},
];
const PY_SINKS: &[Sink] = &[
Sink {
receiver: "os",
name: "system",
kind: SinkKind::Exec,
},
Sink {
receiver: "os",
name: "popen",
kind: SinkKind::Exec,
},
Sink {
receiver: "os",
name: "execv",
kind: SinkKind::Exec,
},
Sink {
receiver: "os",
name: "execve",
kind: SinkKind::Exec,
},
Sink {
receiver: "os",
name: "execvp",
kind: SinkKind::Exec,
},
Sink {
receiver: "subprocess",
name: "call",
kind: SinkKind::Exec,
},
Sink {
receiver: "subprocess",
name: "run",
kind: SinkKind::Exec,
},
Sink {
receiver: "subprocess",
name: "Popen",
kind: SinkKind::Exec,
},
Sink {
receiver: "subprocess",
name: "check_output",
kind: SinkKind::Exec,
},
Sink {
receiver: "subprocess",
name: "check_call",
kind: SinkKind::Exec,
},
Sink {
receiver: "",
name: "eval",
kind: SinkKind::Eval,
},
Sink {
receiver: "",
name: "exec",
kind: SinkKind::Eval,
},
Sink {
receiver: "",
name: "open",
kind: SinkKind::FsWritePath,
},
Sink {
receiver: "pickle",
name: "loads",
kind: SinkKind::Deserialize,
},
Sink {
receiver: "pickle",
name: "load",
kind: SinkKind::Deserialize,
},
Sink {
receiver: "_pickle",
name: "loads",
kind: SinkKind::Deserialize,
},
Sink {
receiver: "_pickle",
name: "load",
kind: SinkKind::Deserialize,
},
Sink {
receiver: "yaml",
name: "load",
kind: SinkKind::Deserialize,
},
Sink {
receiver: "requests",
name: "get",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "requests",
name: "post",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "requests",
name: "put",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "requests",
name: "delete",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "requests",
name: "request",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "urllib",
name: "urlopen",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "urllib.request",
name: "urlopen",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "httpx",
name: "get",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "httpx",
name: "post",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "httpx",
name: "request",
kind: SinkKind::UrlFetch,
},
];
const RUST_SINKS: &[Sink] = &[
Sink {
receiver: "Command",
name: "new",
kind: SinkKind::Exec,
}, Sink {
receiver: "",
name: "system",
kind: SinkKind::Exec,
}, Sink {
receiver: "fs",
name: "write",
kind: SinkKind::FsWritePath,
},
Sink {
receiver: "fs",
name: "remove_file",
kind: SinkKind::FsWritePath,
},
Sink {
receiver: "File",
name: "create",
kind: SinkKind::FsWritePath,
},
Sink {
receiver: "reqwest",
name: "get",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "reqwest",
name: "post",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "reqwest",
name: "request",
kind: SinkKind::UrlFetch,
},
];
const GO_SINKS: &[Sink] = &[
Sink {
receiver: "exec",
name: "Command",
kind: SinkKind::Exec,
},
Sink {
receiver: "exec",
name: "CommandContext",
kind: SinkKind::Exec,
},
Sink {
receiver: "os",
name: "OpenFile",
kind: SinkKind::FsWritePath,
},
Sink {
receiver: "os",
name: "Create",
kind: SinkKind::FsWritePath,
},
Sink {
receiver: "os",
name: "Remove",
kind: SinkKind::FsWritePath,
},
Sink {
receiver: "os",
name: "WriteFile",
kind: SinkKind::FsWritePath,
},
Sink {
receiver: "ioutil",
name: "WriteFile",
kind: SinkKind::FsWritePath,
},
Sink {
receiver: "http",
name: "Get",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "http",
name: "Post",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "http",
name: "NewRequest",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "template",
name: "HTML",
kind: SinkKind::HtmlSink,
},
];
const JAVA_SINKS: &[Sink] = &[
Sink {
receiver: "Runtime",
name: "exec",
kind: SinkKind::Exec,
},
Sink {
receiver: "ProcessBuilder",
name: "command",
kind: SinkKind::Exec,
},
Sink {
receiver: "Statement",
name: "execute",
kind: SinkKind::SqlExec,
},
Sink {
receiver: "Statement",
name: "executeQuery",
kind: SinkKind::SqlExec,
},
Sink {
receiver: "Statement",
name: "executeUpdate",
kind: SinkKind::SqlExec,
},
Sink {
receiver: "Statement",
name: "executeLargeUpdate",
kind: SinkKind::SqlExec,
},
Sink {
receiver: "URL",
name: "openConnection",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "HttpClient",
name: "send",
kind: SinkKind::UrlFetch,
},
];
const CSHARP_SINKS: &[Sink] = &[
Sink {
receiver: "Process",
name: "Start",
kind: SinkKind::Exec,
},
Sink {
receiver: "SqlCommand",
name: "ExecuteReader",
kind: SinkKind::SqlExec,
},
Sink {
receiver: "SqlCommand",
name: "ExecuteNonQuery",
kind: SinkKind::SqlExec,
},
Sink {
receiver: "SqlCommand",
name: "ExecuteScalar",
kind: SinkKind::SqlExec,
},
Sink {
receiver: "HttpClient",
name: "GetAsync",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "HttpClient",
name: "PostAsync",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "HttpClient",
name: "SendAsync",
kind: SinkKind::UrlFetch,
},
Sink {
receiver: "CSharpScript",
name: "EvaluateAsync",
kind: SinkKind::Eval,
},
];
const C_SINKS: &[Sink] = &[
Sink {
receiver: "",
name: "system",
kind: SinkKind::Exec,
},
Sink {
receiver: "",
name: "popen",
kind: SinkKind::Exec,
},
Sink {
receiver: "",
name: "execve",
kind: SinkKind::Exec,
},
Sink {
receiver: "",
name: "execvp",
kind: SinkKind::Exec,
},
Sink {
receiver: "",
name: "execv",
kind: SinkKind::Exec,
},
];
fn sinks_for(lang: Lang) -> &'static [Sink] {
match lang {
Lang::Js | Lang::Ts => JS_SINKS,
Lang::Python => PY_SINKS,
Lang::Rust => RUST_SINKS,
Lang::Go => GO_SINKS,
Lang::Java => JAVA_SINKS,
Lang::CSharp => CSHARP_SINKS,
Lang::C | Lang::Cpp => C_SINKS,
}
}
pub fn classify_sink(lang: Lang, receiver: &str, name: &str) -> Option<SinkKind> {
sinks_for(lang)
.iter()
.find(|s| s.receiver == receiver && s.name == name)
.map(|s| s.kind)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classifies_known_sinks() {
assert_eq!(
classify_sink(Lang::Js, "child_process", "exec"),
Some(SinkKind::Exec)
);
assert_eq!(classify_sink(Lang::Js, "", "eval"), Some(SinkKind::Eval));
assert_eq!(
classify_sink(Lang::Python, "os", "system"),
Some(SinkKind::Exec)
);
assert_eq!(classify_sink(Lang::Python, "", "println"), None); assert_eq!(
classify_sink(Lang::Rust, "Command", "new"),
Some(SinkKind::Exec)
);
assert_eq!(SinkKind::Exec.as_str(), "exec");
}
#[test]
fn classifies_js_sinks_exhaustively() {
assert_eq!(
classify_sink(Lang::Js, "", "Function"),
Some(SinkKind::Eval)
);
assert_eq!(
classify_sink(Lang::Js, "fs", "writeFile"),
Some(SinkKind::FsWritePath)
);
assert_eq!(
classify_sink(Lang::Js, "fs", "createWriteStream"),
Some(SinkKind::FsWritePath)
);
assert_eq!(
classify_sink(Lang::Js, "fs", "unlink"),
Some(SinkKind::FsWritePath)
);
assert_eq!(
classify_sink(Lang::Js, "", "fetch"),
Some(SinkKind::UrlFetch)
);
assert_eq!(
classify_sink(Lang::Js, "axios", "get"),
Some(SinkKind::UrlFetch)
);
assert_eq!(
classify_sink(Lang::Js, "https", "get"),
Some(SinkKind::UrlFetch)
);
assert_eq!(
classify_sink(Lang::Js, "document", "write"),
Some(SinkKind::HtmlSink)
);
}
#[test]
fn classifies_python_sinks_exhaustively() {
assert_eq!(
classify_sink(Lang::Python, "subprocess", "run"),
Some(SinkKind::Exec)
);
assert_eq!(
classify_sink(Lang::Python, "subprocess", "Popen"),
Some(SinkKind::Exec)
);
assert_eq!(
classify_sink(Lang::Python, "os", "popen"),
Some(SinkKind::Exec)
);
assert_eq!(
classify_sink(Lang::Python, "", "eval"),
Some(SinkKind::Eval)
);
assert_eq!(
classify_sink(Lang::Python, "", "exec"),
Some(SinkKind::Eval)
);
assert_eq!(
classify_sink(Lang::Python, "", "open"),
Some(SinkKind::FsWritePath)
);
assert_eq!(
classify_sink(Lang::Python, "pickle", "loads"),
Some(SinkKind::Deserialize)
);
assert_eq!(
classify_sink(Lang::Python, "yaml", "load"),
Some(SinkKind::Deserialize)
);
assert_eq!(
classify_sink(Lang::Python, "requests", "get"),
Some(SinkKind::UrlFetch)
);
assert_eq!(
classify_sink(Lang::Python, "urllib", "urlopen"),
Some(SinkKind::UrlFetch)
);
}
#[test]
fn classifies_rust_sinks_exhaustively() {
assert_eq!(
classify_sink(Lang::Rust, "Command", "new"),
Some(SinkKind::Exec)
);
assert_eq!(
classify_sink(Lang::Rust, "fs", "write"),
Some(SinkKind::FsWritePath)
);
assert_eq!(
classify_sink(Lang::Rust, "File", "create"),
Some(SinkKind::FsWritePath)
);
assert_eq!(
classify_sink(Lang::Rust, "reqwest", "get"),
Some(SinkKind::UrlFetch)
);
}
#[test]
fn classifies_go_sinks_exhaustively() {
assert_eq!(
classify_sink(Lang::Go, "exec", "Command"),
Some(SinkKind::Exec)
);
assert_eq!(
classify_sink(Lang::Go, "os", "Create"),
Some(SinkKind::FsWritePath)
);
assert_eq!(
classify_sink(Lang::Go, "http", "Get"),
Some(SinkKind::UrlFetch)
);
assert_eq!(
classify_sink(Lang::Go, "template", "HTML"),
Some(SinkKind::HtmlSink)
);
}
#[test]
fn classifies_java_sinks_exhaustively() {
assert_eq!(
classify_sink(Lang::Java, "Runtime", "exec"),
Some(SinkKind::Exec)
);
assert_eq!(
classify_sink(Lang::Java, "Statement", "executeQuery"),
Some(SinkKind::SqlExec)
);
assert_eq!(
classify_sink(Lang::Java, "URL", "openConnection"),
Some(SinkKind::UrlFetch)
);
}
#[test]
fn classifies_csharp_sinks_exhaustively() {
assert_eq!(
classify_sink(Lang::CSharp, "Process", "Start"),
Some(SinkKind::Exec)
);
assert_eq!(
classify_sink(Lang::CSharp, "SqlCommand", "ExecuteReader"),
Some(SinkKind::SqlExec)
);
assert_eq!(
classify_sink(Lang::CSharp, "HttpClient", "GetAsync"),
Some(SinkKind::UrlFetch)
);
}
#[test]
fn classifies_c_and_cpp_sinks() {
assert_eq!(classify_sink(Lang::C, "", "system"), Some(SinkKind::Exec));
assert_eq!(classify_sink(Lang::C, "", "popen"), Some(SinkKind::Exec));
assert_eq!(classify_sink(Lang::Cpp, "", "system"), Some(SinkKind::Exec));
assert_eq!(classify_sink(Lang::Cpp, "", "execve"), Some(SinkKind::Exec));
}
#[test]
fn ts_shares_js_sinks() {
assert_eq!(
classify_sink(Lang::Ts, "child_process", "exec"),
Some(SinkKind::Exec)
);
assert_eq!(classify_sink(Lang::Ts, "", "eval"), Some(SinkKind::Eval));
assert_eq!(
classify_sink(Lang::Ts, "axios", "request"),
Some(SinkKind::UrlFetch)
);
}
#[test]
fn unknown_callees_return_none() {
assert_eq!(classify_sink(Lang::Js, "", "console.log"), None);
assert_eq!(classify_sink(Lang::Python, "", "print"), None);
assert_eq!(classify_sink(Lang::Rust, "", "println"), None);
assert_eq!(classify_sink(Lang::Go, "", "fmt.Println"), None);
}
#[test]
fn sink_kind_as_str_round_trips() {
assert_eq!(SinkKind::Exec.as_str(), "exec");
assert_eq!(SinkKind::SqlExec.as_str(), "sql_exec");
assert_eq!(SinkKind::Eval.as_str(), "eval");
assert_eq!(SinkKind::FsWritePath.as_str(), "fs_write_path");
assert_eq!(SinkKind::HtmlSink.as_str(), "html_sink");
assert_eq!(SinkKind::UrlFetch.as_str(), "url_fetch");
assert_eq!(SinkKind::Deserialize.as_str(), "deserialize");
assert_eq!(SinkKind::Xxe.as_str(), "xxe");
assert_eq!(SinkKind::NosqlQuery.as_str(), "nosql_query");
}
}