pub mod differential_fuzzer;
pub mod taint_analyzer;
pub mod abstract_interpreter;
use crate::http_client::HttpClient;
use crate::types::{ScanConfig, ScanMode, Vulnerability};
use anyhow::Result;
use scraper::{Html, Selector};
use std::sync::Arc;
use std::time::Instant;
pub struct HybridXssDetector {
http_client: Arc<HttpClient>,
differential: differential_fuzzer::DifferentialFuzzer,
}
impl HybridXssDetector {
pub fn new(http_client: Arc<HttpClient>) -> Self {
Self {
differential: differential_fuzzer::DifferentialFuzzer::new(http_client.clone()),
http_client,
}
}
pub async fn scan_parallel(
&self,
urls: &[String],
config: &ScanConfig,
) -> Result<(Vec<Vulnerability>, usize)> {
let mut all_vulnerabilities = Vec::new();
let mut total_tests = 0;
let chunk_size = 50;
for chunk in urls.chunks(chunk_size) {
let futures: Vec<_> = chunk
.iter()
.map(|url| self.scan_single_url(url, config))
.collect();
let results = futures::future::join_all(futures).await;
for result in results {
if let Ok((vulns, tests)) = result {
all_vulnerabilities.extend(vulns);
total_tests += tests;
}
}
}
Ok((all_vulnerabilities, total_tests))
}
async fn scan_single_url(
&self,
url: &str,
config: &ScanConfig,
) -> Result<(Vec<Vulnerability>, usize)> {
let start = Instant::now();
let mut vulnerabilities = Vec::new();
let mut tests = 0;
if let Ok((vulns, test_count)) = self.differential.scan(url).await {
tests += test_count;
if !vulns.is_empty() {
vulnerabilities.extend(vulns);
return Ok((vulnerabilities, tests));
}
}
if let Ok(response) = self.http_client.get(url).await {
let html = Html::parse_document(&response.body);
let mut js_code = String::new();
if let Ok(selector) = Selector::parse("script") {
for element in html.select(&selector) {
js_code.push_str(&element.inner_html());
js_code.push('\n');
}
}
if !js_code.is_empty() {
let js_clone1 = js_code.clone();
let js_clone2 = js_code.clone();
let url_clone = url.to_string();
let (taint_result, abstract_result) = tokio::join!(
tokio::task::spawn_blocking(move || {
let mut analyzer = taint_analyzer::TaintAnalyzer::new();
analyzer.analyze(&js_clone1)
}),
tokio::task::spawn_blocking(move || {
let mut interpreter = abstract_interpreter::AbstractInterpreter::new();
interpreter.interpret(&js_clone2)
}),
);
if let Ok(Ok(mut vulns)) = taint_result {
for vuln in &mut vulns {
vuln.url = url.to_string();
}
vulnerabilities.extend(vulns);
tests += 1;
}
if let Ok(Ok(mut vulns)) = abstract_result {
for vuln in &mut vulns {
vuln.url = url.to_string();
}
vulnerabilities.extend(vulns);
tests += 1;
}
}
}
let elapsed = start.elapsed();
tracing::debug!(
"[Hybrid] Scanned {} in {:?}: {} vulns, {} tests",
url,
elapsed,
vulnerabilities.len(),
tests
);
Ok((vulnerabilities, tests))
}
}