diff --git a/msal/wstrust_request.py b/msal/wstrust_request.py index 43a2804f..159375f3 100644 --- a/msal/wstrust_request.py +++ b/msal/wstrust_request.py @@ -60,8 +60,8 @@ def send_request( return parse_response(resp.text) -def escape_password(password): - return (password.replace('&', '&').replace('"', '"') +def escape_xml(s): + return (s.replace('&', '&').replace('"', '"') .replace("'", ''') # the only one not provided by cgi.escape(s, True) .replace('<', '<').replace('>', '>')) @@ -116,7 +116,7 @@ def _build_rst(username, password, cloud_audience_urn, endpoint_address, soap_ac endpoint_address=endpoint_address, time_now=wsu_time_format(now), time_expire=wsu_time_format(now + timedelta(minutes=10)), - username=username, password=escape_password(password), + username=escape_xml(username), password=escape_xml(password), wst=Mex.NS["wst"] if soap_action == Mex.ACTION_13 else Mex.NS["wst2005"], applies_to=cloud_audience_urn, key_type='http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer' diff --git a/tests/test_wstrust.py b/tests/test_wstrust.py index 1d909585..41f78e83 100644 --- a/tests/test_wstrust.py +++ b/tests/test_wstrust.py @@ -31,6 +31,7 @@ from xml.etree import ElementTree as ET import os +from msal.wstrust_request import _build_rst, escape_xml from msal.wstrust_response import * from tests import unittest @@ -96,3 +97,16 @@ def test_token_parsing_happy_path(self): self.assertEqual(result.get("type"), SAML_TOKEN_TYPE_V1) self.assertIn(b"&"\''), '<>&"'') + + def test_username_xml_injection_is_prevented(self): + malicious = 'adminINJECTED' + rst = _build_rst(malicious, 'pw', 'urn:x', 'https://x', + 'http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue') + self.assertEqual(rst.count(''), 1) + self.assertNotIn('INJECTED', rst) +