use crate::traits::PayloadOracle;
pub struct XssOracle;
const XSS_TAGS: &[&str] = &[
"<script",
"<img",
"<svg",
"<body",
"<iframe",
"<details",
"<video",
"<audio",
"<input",
"<marquee",
"<object",
"<a ",
"<div",
"<form",
"<select",
"<textarea",
"<embed",
"<link",
"<style",
"<math",
"<table",
"<noscript",
];
const XSS_EVENTS: &[&str] = &[
"onerror",
"onload",
"onclick",
"onfocus",
"onmouseover",
"ontoggle",
"onbegin",
"onstart",
"onmouseenter",
"onanimationend",
"onhashchange",
"onpageshow",
"onscroll",
"onwheel",
"onresize",
];
const XSS_EXEC_SINKS: &[&str] = &[
"alert",
"confirm",
"prompt",
"eval",
"setTimeout",
"setInterval",
"Function",
"constructor",
"import(",
"fetch(",
"document.cookie",
"window.name",
"location",
"innerHTML",
];
const JS_URI_SCHEMES: &[&str] = &["javascript:", "data:text/html"];
const DANGEROUS_SINKS: &[&str] = &[
"alert",
"confirm",
"prompt",
"eval",
"document.write",
"document.location",
"window.location",
"innerhtml",
"outerhtml",
"srcdoc",
];
fn has_xss_structure(payload: &str) -> bool {
let lower = payload.to_ascii_lowercase();
let has_tag = XSS_TAGS.iter().any(|tag| lower.contains(tag));
let has_event = XSS_EVENTS.iter().any(|evt| lower.contains(evt));
let has_exec = XSS_EXEC_SINKS.iter().any(|sink| lower.contains(sink));
let has_uri = JS_URI_SCHEMES.iter().any(|scheme| lower.contains(scheme));
let has_dangerous_sink = DANGEROUS_SINKS.iter().any(|sink| lower.contains(sink));
has_uri
|| (has_tag && has_exec && has_dangerous_sink)
|| (has_tag && has_event && has_dangerous_sink)
}
impl PayloadOracle for XssOracle {
fn is_semantically_valid(&self, _original: &str, transformed: &str) -> bool {
has_xss_structure(transformed)
}
fn name(&self) -> &'static str {
"XSS"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classic_script_tag_valid() {
let oracle = XssOracle;
assert!(
oracle.is_semantically_valid("<script>alert(1)</script>", "<script>alert(1)</script>",)
);
}
#[test]
fn img_onerror_valid() {
let oracle = XssOracle;
assert!(oracle.is_semantically_valid(
"<img src=x onerror=alert(1)>",
"<img src=x onerror=alert(1)>",
));
}
#[test]
fn svg_onload_valid() {
let oracle = XssOracle;
assert!(oracle.is_semantically_valid("<svg onload=alert(1)>", "<svg onload=alert(1)>",));
}
#[test]
fn javascript_uri_valid() {
let oracle = XssOracle;
assert!(oracle.is_semantically_valid("javascript:alert(1)", "javascript:alert(1)",));
}
#[test]
fn data_uri_valid() {
let oracle = XssOracle;
assert!(oracle.is_semantically_valid(
"data:text/html,<script>alert(1)</script>",
"data:text/html,<script>alert(1)</script>",
));
}
#[test]
fn broken_tag_invalid() {
let oracle = XssOracle;
assert!(!oracle.is_semantically_valid(
"<script>alert(1)</script>",
"%3Cscript%3Ealert%281%29%3C/script%3E",
));
}
#[test]
fn case_alternation_preserves_structure() {
let oracle = XssOracle;
assert!(
oracle.is_semantically_valid("<script>alert(1)</script>", "<ScRiPt>alert(1)</sCrIpT>",)
);
}
#[test]
fn empty_string_invalid() {
let oracle = XssOracle;
assert!(!oracle.is_semantically_valid("<script>alert(1)</script>", ""));
}
#[test]
fn plain_text_invalid() {
let oracle = XssOracle;
assert!(!oracle.is_semantically_valid("<script>alert(1)</script>", "hello world",));
}
#[test]
fn event_handler_without_explicit_exec_valid() {
let oracle = XssOracle;
assert!(oracle.is_semantically_valid(
"<img src=x onerror=alert(1)>",
"<details open ontoggle=alert(1)>",
));
}
#[test]
fn dom_clobber_valid() {
let oracle = XssOracle;
assert!(oracle.is_semantically_valid(
"<script>alert(1)</script>",
"<form name=body><input name=innerHTML value='<img src=x onerror=alert(1)>'></form>",
));
}
#[test]
fn constructor_chain_valid() {
let oracle = XssOracle;
assert!(oracle.is_semantically_valid(
"<script>alert(1)</script>",
"<img src=x onerror=constructor.constructor('alert(1)')()>",
));
}
}