From 6c2e700082a42f2f93207f569f12f3ea1819dd8e Mon Sep 17 00:00:00 2001 From: Dharshan BJ Date: Wed, 8 Apr 2026 14:19:28 -0700 Subject: [PATCH 1/2] Escape username to protect against xml injection vulnerability --- msal/wstrust_request.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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' From 462877015600204eeedcf82065b916ff4e9348af Mon Sep 17 00:00:00 2001 From: Dharshan BJ Date: Wed, 8 Apr 2026 14:41:51 -0700 Subject: [PATCH 2/2] Add test --- tests/test_wstrust.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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) +