use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PayloadTest {
pub value: String,
pub encoding: String,
pub method: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Matcher {
#[serde(rename = "type")]
pub matcher_type: String,
pub condition: String,
pub patterns: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PayloadInfo {
pub name: String,
pub severity: crate::types::Severity,
pub category: String,
pub description: String,
pub references: Vec<String>,
pub tags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Payload {
pub id: String,
pub info: PayloadInfo,
pub payloads: Vec<PayloadTest>,
pub matchers: Vec<Matcher>,
}
pub struct PayloadManager {
payloads: Vec<Payload>,
}
impl PayloadManager {
pub fn with_defaults() -> crate::error::Result<Self> {
const XSS: &str = include_str!("../payloads/xss.json");
const SQLI: &str = include_str!("../payloads/sqli.json");
const LFI: &str = include_str!("../payloads/lfi.json");
const RCE: &str = include_str!("../payloads/rce.json");
const PATH_TRAVERSAL: &str = include_str!("../payloads/path-traversal.json");
const CMD_INJECTION: &str = include_str!("../payloads/command-injection.json");
const SSRF: &str = include_str!("../payloads/ssrf.json");
const XXE: &str = include_str!("../payloads/xxe.json");
const NOSQL: &str = include_str!("../payloads/nosql-injection.json");
const SSTI: &str = include_str!("../payloads/ssti.json");
const OWASP_A01: &str = include_str!("../payloads/owasp-a01-broken-access-control.json");
const OWASP_A02: &str = include_str!("../payloads/owasp-a02-security-misconfiguration.json");
const OWASP_A05: &str = include_str!("../payloads/owasp-a05-injection-advanced.json");
const OWASP_A07: &str = include_str!("../payloads/owasp-a07-authentication-bypass.json");
const OWASP_A10: &str = include_str!("../payloads/owasp-a10-error-handling.json");
const HTTP2_ADFS: &str = include_str!("../payloads/http2-adfs-bypass.json");
const LLM01: &str = include_str!("../payloads/llm01-prompt-injection.json");
const LLM02: &str = include_str!("../payloads/llm02-sensitive-information-disclosure.json");
const LLM03: &str = include_str!("../payloads/llm03-supply-chain.json");
const LLM04: &str = include_str!("../payloads/llm04-data-model-poisoning.json");
const LLM05: &str = include_str!("../payloads/llm05-improper-output-handling.json");
const LLM06: &str = include_str!("../payloads/llm06-excessive-agency.json");
const LLM07: &str = include_str!("../payloads/llm07-system-prompt-leakage.json");
const LLM08: &str = include_str!("../payloads/llm08-vector-embedding.json");
const LLM09: &str = include_str!("../payloads/llm09-misinformation.json");
const LLM10: &str = include_str!("../payloads/llm10-unbounded-consumption.json");
let mut all_payloads = Vec::new();
for (name, content) in &[
("xss", XSS),
("sqli", SQLI),
("lfi", LFI),
("rce", RCE),
("path-traversal", PATH_TRAVERSAL),
("command-injection", CMD_INJECTION),
("ssrf", SSRF),
("xxe", XXE),
("nosql-injection", NOSQL),
("ssti", SSTI),
("owasp-a01-broken-access-control", OWASP_A01),
("owasp-a02-security-misconfiguration", OWASP_A02),
("owasp-a05-injection-advanced", OWASP_A05),
("owasp-a07-authentication-bypass", OWASP_A07),
("owasp-a10-error-handling", OWASP_A10),
("http2-adfs-bypass", HTTP2_ADFS),
("llm01-prompt-injection", LLM01),
("llm02-sensitive-information-disclosure", LLM02),
("llm03-supply-chain", LLM03),
("llm04-data-model-poisoning", LLM04),
("llm05-improper-output-handling", LLM05),
("llm06-excessive-agency", LLM06),
("llm07-system-prompt-leakage", LLM07),
("llm08-vector-embedding", LLM08),
("llm09-misinformation", LLM09),
("llm10-unbounded-consumption", LLM10),
] {
match serde_json::from_str::<Vec<Payload>>(content) {
Ok(mut payloads) => all_payloads.append(&mut payloads),
Err(e) => {
tracing::error!("Failed to parse {} payloads: {}", name, e);
return Err(crate::error::ScanError::InvalidPayload(format!(
"Failed to parse {} payloads: {}",
name, e
)));
}
}
}
if all_payloads.is_empty() {
return Err(crate::error::ScanError::NoPayloads);
}
tracing::info!("Loaded {} default payloads", all_payloads.len());
Ok(Self {
payloads: all_payloads,
})
}
pub async fn from_file(path: impl AsRef<std::path::Path>) -> crate::error::Result<Self> {
let content = tokio::fs::read_to_string(path.as_ref()).await?;
let payloads: Vec<Payload> = serde_json::from_str(&content)?;
if payloads.is_empty() {
return Err(crate::error::ScanError::NoPayloads);
}
tracing::info!("Loaded {} custom payloads from file", payloads.len());
Ok(Self { payloads })
}
pub fn payloads(&self) -> &[Payload] {
&self.payloads
}
pub fn by_category(&self, category: &str) -> Vec<&Payload> {
self.payloads
.iter()
.filter(|p| p.info.category == category)
.collect()
}
pub fn by_severity(&self, severity: crate::types::Severity) -> Vec<&Payload> {
self.payloads
.iter()
.filter(|p| p.info.severity == severity)
.collect()
}
}