From 9201ee38834dcf0004024b0f26ec5462be5347de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20B=C5=99ou=C5=A1ek?= <brousek@ics.muni.cz> Date: Fri, 26 Apr 2024 21:56:18 +0200 Subject: [PATCH] feat: SP-initiated login using discovery response endpoint --- README.md | 8 +- ...ml-detect-discovery-response-endpoint.yaml | 145 ++++++++++++++++++ ...=> saml-detect-request-init-endpoint.yaml} | 6 +- workflow/saml-discovery-response.yaml | 10 ++ ...p-workflow.yaml => saml-request-init.yaml} | 8 +- ...saml-signature-discovery-response-raw.yaml | 82 ++++++++++ 6 files changed, 248 insertions(+), 11 deletions(-) create mode 100644 workflow/saml-detect-discovery-response-endpoint.yaml rename workflow/{saml-detect-endpoints.yaml => saml-detect-request-init-endpoint.yaml} (92%) create mode 100644 workflow/saml-discovery-response.yaml rename workflow/{saml-sp-workflow.yaml => saml-request-init.yaml} (77%) create mode 100644 workflow/saml-signature-discovery-response-raw.yaml diff --git a/README.md b/README.md index cea5109..7310cd1 100644 --- a/README.md +++ b/README.md @@ -123,15 +123,16 @@ where Peforming SP-initiated login is more difficult than the IdP-initiated login used in previous templates. -You can use the included [nuclei workflow](https://docs.projectdiscovery.io/templates/workflows/overview) -to scan a server for available endpoints and perform SP-initiated login if possible: +You can use the included [nuclei workflows](https://docs.projectdiscovery.io/templates/workflows/overview) +to scan a server for available endpoints and perform SP-initiated login if possible. ```sh -nuclei -w workflow/saml-sp-workflow.yaml -u https://sp.example.com/ -code -sf secret-file.yaml +nuclei -w workflow/<workflow_name>.yaml -u https://sp.example.com/ -code -sf secret-file.yaml -c 1 ``` where +* `<workflow_name>` is either `saml-request-init` for request initiation endpoint or `saml-discovery-response` for discovery response endpoint * `https://sp.example.com/` is the address of a (potential) SP server (only the domain is relevant) ## Current limitations @@ -143,3 +144,4 @@ where * only SPs which accept unsolicited logins (IdP-initiated) or with a [Request Initiation](https://docs.oasis-open.org/security/saml/Post2.0/sstc-request-initiation.html) endpoint can be tested * headless browser test behaves differently than raw HTTP test (nuclei limitation) +* custom `returnIDParam` is not supported in discovery response endpoint detection diff --git a/workflow/saml-detect-discovery-response-endpoint.yaml b/workflow/saml-detect-discovery-response-endpoint.yaml new file mode 100644 index 0000000..8d35bda --- /dev/null +++ b/workflow/saml-detect-discovery-response-endpoint.yaml @@ -0,0 +1,145 @@ +id: saml-detect-discovery-response-endpoint +info: + name: SAML discovery response endpoint detection + author: T&I Incubator, GÉANT + severity: info + tags: saml,detect,tech + +variables: + CONFORMANCE_IDP_ENTITY_ID: "urn:x-simplesamlphp:geant:incubator:conformance-idp" + +http: + - method: GET + path: + - "{{RootURL}}{{discoresponseendpoint}}?entityID={{url_encode(CONFORMANCE_IDP_ENTITY_ID)}}" + redirects: false + attack: batteringram + payloads: + discoresponseendpoint: + - "" + - "/" + - "/AccountSaml/SignIn/" + - "/account/shibboleth/Login" + - "/adfs/ls/IdpInitiatedSignon.aspx" + - "/api/login/sso" + - "/auth/saml/meta" + - "/auth/saml/meta.xml" + - "/auth-saml/saml/login" + - "/auth/shibboleth.sso/ds" + - "/auth/shibexternal/Shibboleth.sso/DS" + - "/auth/shibexternal/Shibboleth.sso/Login" + - "/controls/login/AssertionConsumerService.aspx" + - "/edugain/disco" + - "/Edugain/disco" + - "/edugainlogin/Shibboleth.sso/Login" + - "/edugain/Shibboleth.sso/Login" + - "/eduid/Shibboleth.sso/Login" + - "/fastUnit/jsp/echo.do/Shibboleth.sso/Login" + - "/fastUnit/jsp/fast2.do/Shibboleth.sso/Login" + - "/FedAuth/Shibboleth/AssertionService" + - "/federation-manager/Shibboleth.sso/Login" + - "/fedreg/auth/login" + - "/grouper/grouperExternal/Shibboleth.sso/Login" + - "/hcc/Shibboleth.sso/Login" + - "/identity/self-service/bay/autologin.jsf/Shibboleth.sso/Login" + - "/idp/module.php/saml/disco.php" + - "/incommon/disco" + - "/linking/Shibboleth.sso/Login" + - "/lists/Shibboleth.sso/Login" + - "/login" + - "/login/saml/discovery" + - "/module.php/saml/sp/discoresp.php" + - "/oa/disco-ret" + - "/oidc/Shibboleth.sso/Login" + - "/otptokens/Shibboleth.sso/Login" + - "/ready/saml/meta.xml" + - "/research-and-scholarship/Shibboleth.sso/Login" + - "/research-and-scholarship/Shibboleth.sso/SeamlessAccess" + - "/research-and-scholarship/Shibboleth.sso/Wayfinder" + - "/SAML/AssertionConsumerService.aspx" + - "/saml/disco" + - "/saml/discover" + - "/saml/discovered" + - "/saml/login" + - "/SAML/Login" + - "/saml/module.php/saml/sp/discoresp.php" + - "/saml/2/auth" + - "/saml2/disco" + - "/Saml2/disco" + - "/Saml2_idps/disco" + - "/saml2/login" + - "/saml2sp/disco" + - "/Saml2SP/disco" + - "/secureSpace/jsp/fast2.do/Shibboleth.sso/Login" + - "/shibboleth" + - "/shibboleth/assert" + - "/shibboleth/Shibboleth.sso/Login" + - "/shibboleth-sp/Shibboleth.sso/DS" + - "/shibboleth-sp/Shibboleth.sso/Login" + - "/Shibboleth.sso/discovery" + - "/Shibboleth.sso/Discovery" + - "/shibboleth.sso/DS" + - "/Shibboleth.sso/DS" + - "/Shibboleth.sso/DS/Login" + - "/Shibboleth.sso/DS/seamless-access" + - "/Shibboleth.sso/DS/thiss.io" + - "/Shibboleth.sso/EDS" + - "/Shibboleth.sso/edugain" + - "/Shibboleth.sso/eduGAIN" + - "/Shibboleth.sso/EduGain" + - "/Shibboleth.sso/geant" + - "/-shibboleth.sso/Login" + - "/shibboleth.sso/Login" + - "/Shibboleth.sso/Login" + - "/Shibboleth.sso/login/shibboleth" + - "/Shibboleth.sso/MFA" + - "/Shibboleth.sso/SAML/POST" + - "/Shibboleth.sso/SeamlessAccess" + - "/Shibboleth.sso/SeamlessLogin" + - "/Shibboleth.sso/WAYF" + - "/Shibboleth.sso/Wayfinder" + - "/shib/sp/Login" + - "/simplesaml/module.php/saml/disco.php" + - "/simplesaml/module.php/saml/sp/discoresp.php" + - "/simplesamlphp/module.php/saml/sp/discoresp.php" + - "/sp/disco" + - "/sso/login" + - "/sso/module.php/saml/sp/discoresp.php" + - "/wayf/acs/post/disco" + stop-at-first-match: true + matchers: + - type: dsl + name: hasdiscoresponseendpoint + condition: and + dsl: + - "status_code == 302 || status_code == 303 || status_code == 307" + - "contains(location, 'SAMLRequest=')" + extractors: + - type: dsl + name: discoresponseendpoint + dsl: + - "discoresponseendpoint" + +code: + - engine: + - python3 + - python + - py + source: | + import base64 + import zlib + import os + import re + from urllib.parse import unquote + + saml_request = unquote(re.sub(r'^.*SAMLRequest=(.*?)(&|$).*', r'\1', os.getenv("http_location"))) + saml_request = zlib.decompress(base64.b64decode(saml_request), -zlib.MAX_WBITS).decode() + SP_ENTITY_ID = re.sub(r'^.*>(.*?)</\w+:Issuer.*$', r'\1', saml_request) + print(SP_ENTITY_ID) + + extractors: + - type: dsl + name: SP_ENTITY_ID + dsl: + - response +# digest: 4b0a00483046022100fe212de6b150396eef2dfbbca59589d70d206703594a8bb409aeea11138f678f022100a7f081c73485c4ff94a2a95f7d0f91d86e3c2ca62b252b7899295be1ff097b08:16ea744faf4d9956cf0107f1f3cb7c4c \ No newline at end of file diff --git a/workflow/saml-detect-endpoints.yaml b/workflow/saml-detect-request-init-endpoint.yaml similarity index 92% rename from workflow/saml-detect-endpoints.yaml rename to workflow/saml-detect-request-init-endpoint.yaml index aeb52c2..8542d5f 100644 --- a/workflow/saml-detect-endpoints.yaml +++ b/workflow/saml-detect-request-init-endpoint.yaml @@ -1,6 +1,6 @@ -id: saml-detect-endpoints +id: saml-detect-request-init-endpoint info: - name: SAML endpoints detection + name: SAML request initiation endpoint detection author: T&I Incubator, GÉANT severity: info tags: saml,detect,tech @@ -104,4 +104,4 @@ code: name: SP_ENTITY_ID dsl: - response -# digest: 4a0a0047304502205ecec927cfdf45f5d2711393ed7e8321517f98a969858659a1aa75db26d0b23b022100a390a5522fef78825412f4b60c38abb0f7f41d6f5ac970b24bb5b4c0308887ad:16ea744faf4d9956cf0107f1f3cb7c4c \ No newline at end of file +# digest: 4a0a00473045022065227d49bc58bd1a4583141f1568931f48e2add5a3fa2d2409624400040933b6022100e1fb29bd4bc84c2c353e214e2f13cdd8521d35a3d4f685d50045b11addfa8189:16ea744faf4d9956cf0107f1f3cb7c4c \ No newline at end of file diff --git a/workflow/saml-discovery-response.yaml b/workflow/saml-discovery-response.yaml new file mode 100644 index 0000000..dc0a8ad --- /dev/null +++ b/workflow/saml-discovery-response.yaml @@ -0,0 +1,10 @@ +id: saml-discovery-response +info: + name: SAML signature check using discovery response endpoint + author: T&I Incubator + description: Scan SAML SPs for SAML signature validation problems and vulnerabilities + severity: high +workflows: + - template: workflow/saml-detect-discovery-response-endpoint.yaml + subtemplates: + - template: workflow/saml-signature-discovery-response-raw.yaml diff --git a/workflow/saml-sp-workflow.yaml b/workflow/saml-request-init.yaml similarity index 77% rename from workflow/saml-sp-workflow.yaml rename to workflow/saml-request-init.yaml index 8d8d079..c74c90e 100644 --- a/workflow/saml-sp-workflow.yaml +++ b/workflow/saml-request-init.yaml @@ -1,11 +1,11 @@ -id: saml-sp-workflow +id: saml-request-init info: - name: SAML signature checks + name: SAML signature check using request initiation endpoint author: T&I Incubator description: Scan SAML SPs for SAML signature validation problems and vulnerabilities severity: high workflows: - - template: workflow/saml-detect-endpoints.yaml + - template: workflow/saml-detect-request-init-endpoint.yaml subtemplates: - template: workflow/saml-signature-request-init-raw.yaml # unfortunatelly it seems that is not possible to combine matchers and shared execution context like this: @@ -15,5 +15,3 @@ workflows: #- template: nuclei-templates/saml-signature-request-init-raw.yaml #subtemplates: # - template: nuclei-templates/saml-signature-request-init-invalid-signatures.yaml - #- tags: ssl,tls,dns,tech,detect - - tags: ssl,tls,dns,tech,detect diff --git a/workflow/saml-signature-discovery-response-raw.yaml b/workflow/saml-signature-discovery-response-raw.yaml new file mode 100644 index 0000000..3271740 --- /dev/null +++ b/workflow/saml-signature-discovery-response-raw.yaml @@ -0,0 +1,82 @@ +id: saml-signature-discovery-response-raw +info: + name: SP initiated SAML authentication endpoint check + author: T&I Incubator, GÉANT + severity: high + tags: saml,signature,raw +variables: + CONFORMANCE_IDP_BASE_URL: https://conformance-idp.maiv1.incubator.geant.org/ + CONFORMANCE_IDP_HOSTNAME: '{{replace_regex(CONFORMANCE_IDP_BASE_URL, "^https?://|/.*$", "")}}' + CONFORMANCE_IDP_ENTITY_ID: "urn:x-simplesamlphp:geant:incubator:conformance-idp" + TEST_CASES: + - noSignature + - invalidSignature + +flow: | + set("TEST_CASE", "standardResponse"); + http(); + for (let testcase of iterate(template["TEST_CASES"])) { + set("TEST_CASE", testcase); + http(); + } + +http: + - raw: + - | + @Host: https://{{CONFORMANCE_IDP_HOSTNAME}} + POST /module.php/conformance/test/setup?testId={{url_encode(TEST_CASE)}}&spEntityId={{url_encode(SP_ENTITY_ID)}} HTTP/1.1 + Host: {{CONFORMANCE_IDP_HOSTNAME}} + + - | + GET {{discoresponseendpoint}}?entityID={{url_encode(CONFORMANCE_IDP_ENTITY_ID)}} HTTP/1.1 + Host: {{Hostname}} + + # HTTP-POST binding + - | + @Host: {{replace_regex(trim(acs,"[]"), "^https?://|/.*$", "")}} + POST {{trim(acs,"[]")}} HTTP/1.1 + Host: {{replace_regex(trim(acs,"[]"), "^https?://|/.*$", "")}} + Content-Type: application/x-www-form-urlencoded + + SAMLResponse={{url_encode(trim(samlresponse,"[]"))}} + + disable-path-automerge: true + redirects: true + extractors: + - type: xpath + name: acs + internal: true + attribute: action + xpath: + - '/html/body/form' + - type: xpath + name: samlresponse + internal: true + attribute: value + xpath: + - '/html/body/form/input[2]' + # breaks the template :( + #- type: dsl + # dsl: + # - 'TEST_CASE' + matchers-condition: and + matchers: + - type: status + condition: or + status: + - 200 + - 302 + - 303 + - type: word + words: + - Welcome + - Hello + - Logged in + condition: or + part: body + - type: dsl + dsl: + # ignore the happy case + - '!contains(TEST_CASE, "standardResponse")' + condition: and + -- GitLab