package application;
import gui.SamlMain;
import gui.SamlPanelInfo;
import gui.SignatureHelpWindow;
import gui.XSWHelpWindow;
import helpers.HTTPHelpers;
import helpers.XMLHelpers;
import helpers.XSWHelpers;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.zip.DataFormatException;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.parsers.ParserConfigurationException;
import model.BurpCertificate;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rtextarea.SearchContext;
import org.fife.ui.rtextarea.SearchEngine;
import org.fife.ui.rtextarea.SearchResult;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import burp.IBurpExtenderCallbacks;
import burp.IExtensionHelpers;
import burp.IMessageEditorTab;
import burp.IParameter;
import burp.IRequestInfo;
import burp.IResponseInfo;
public class SamlTabController implements IMessageEditorTab, Observer {
private static final String XML_CERTIFICATE_NOT_FOUND = "X509 Certificate not found";
private static final String XSW_ATTACK_APPLIED = "XSW Attack applied";
private static final String XML_COULD_NOT_SIGN = "Could not sign XML";
private static final String XML_COULD_NOT_SERIALIZE = "Could not serialize XML";
private static final String XML_NOT_WELL_FORMED = "XML isn't well formed or binding is not supported";
private static final String XML_NOT_SUITABLE_FOR_XSW = "This XML Message is not suitable for this particular XSW, is there a signature?";
private static final String NO_BROWSER = "Could not open diff in Browser. Path to file was copied to clipboard";
private static final String NO_DIFF_TEMP_FILE = "Could not create diff temp file.";
private IExtensionHelpers helpers;
private XMLHelpers xmlHelpers;
private byte[] message;
private String orgSAMLMessage;
private String SAMLMessage;
private boolean isInflated = true;
private boolean isGZip = false;
private boolean isWSSUrlEncoded = false;
private RSyntaxTextArea textArea;
private SamlMain samlGUI;
private boolean editable;
private boolean edited;
private boolean isSOAPMessage;
private boolean isWSSMessage;
private boolean isSAMLRequest; // otherwise it's a SAMLResponse
private String httpMethod; // So URI and POST Binding is supported
private CertificateTabController certificateTabController;
private XSWHelpers xswHelpers;
private HTTPHelpers httpHelpers;
public SamlTabController(IBurpExtenderCallbacks callbacks, boolean editable,
CertificateTabController certificateTabController) {
this.editable = editable;
this.helpers = callbacks.getHelpers();
samlGUI = new SamlMain(this);
textArea = samlGUI.getTextArea();
addTextAreaKeyListener();
textArea.setEditable(editable);
textArea.setEnabled(true);
xmlHelpers = new XMLHelpers();
xswHelpers = new XSWHelpers();
httpHelpers = new HTTPHelpers();
this.certificateTabController = certificateTabController;
this.certificateTabController.addObserver(this);
}
private void addTextAreaKeyListener() {
textArea.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent arg0) {
}
@Override
public void keyReleased(KeyEvent arg0) {
}
@Override
public void keyPressed(KeyEvent arg0) {
edited = true;
}
});
}
@Override
public byte[] getMessage() {
byte[] byteMessage = message;
if (edited) {
if (isSOAPMessage) {
try {
// TODO Only working with getString for both documents,
// otherwise namespaces and attributes are emptied -.-
IResponseInfo responseInfo = helpers.analyzeResponse(byteMessage);
int bodyOffset = responseInfo.getBodyOffset();
String HTTPHeader = new String(byteMessage, 0, bodyOffset, "UTF-8");
String soapMessage = new String(byteMessage, bodyOffset, byteMessage.length - bodyOffset, "UTF-8");
Document soapDocument = xmlHelpers.getXMLDocumentOfSAMLMessage(soapMessage);
Element soapBody = xmlHelpers.getSOAPBody(soapDocument);
xmlHelpers.getString(soapDocument);
Document samlDocumentEdited = xmlHelpers.getXMLDocumentOfSAMLMessage(SAMLMessage);
xmlHelpers.getString(samlDocumentEdited);
Element samlResponse = (Element) samlDocumentEdited.getFirstChild();
soapDocument.adoptNode(samlResponse);
Element soapFirstChildOfBody = (Element) soapBody.getFirstChild();
soapBody.replaceChild(samlResponse, soapFirstChildOfBody);
String wholeMessage = HTTPHeader + xmlHelpers.getString(soapDocument);
byteMessage = wholeMessage.getBytes("UTF-8");
} catch (IOException e) {
} catch (SAXException e) {
setInfoMessageText(XML_NOT_WELL_FORMED);
}
} else {
String textMessage = null;
try {
textMessage = xmlHelpers
.getStringOfDocument(xmlHelpers.getXMLDocumentOfSAMLMessage(textArea.getText()), 0, true);
} catch (IOException e) {
setInfoMessageText(XML_COULD_NOT_SERIALIZE);
} catch (SAXException e) {
setInfoMessageText(XML_NOT_WELL_FORMED);
}
String parameterToUpdate;
if (isSAMLRequest) {
parameterToUpdate = "SAMLRequest";
} else {
parameterToUpdate = "SAMLResponse";
}
if (isWSSMessage) {
parameterToUpdate = "wresult";
}
byte parameterType;
if (httpMethod.equals("GET")) {
parameterType = IParameter.PARAM_URL;
} else {
parameterType = IParameter.PARAM_BODY;
}
IParameter newParameter = helpers.buildParameter(parameterToUpdate, getEncodedSAMLMessage(textMessage),
parameterType);
byteMessage = helpers.updateParameter(byteMessage, newParameter);
}
}
return byteMessage;
}
@Override
public byte[] getSelectedData() {
try {
return (textArea.getSelectedText()).getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
}
return null;
}
@Override
public String getTabCaption() {
return "SAML Raider";
}
@Override
public Component getUiComponent() {
return samlGUI;
}
@Override
public boolean isEnabled(byte[] content, boolean isRequest) {
return isRequest && isSAMLMessage(content);
}
private boolean isSAMLMessage(byte[] content) {
IRequestInfo info = helpers.analyzeRequest(content);
httpMethod = helpers.analyzeRequest(content).getMethod();
if (info.getContentType() == IRequestInfo.CONTENT_TYPE_XML) {
isSOAPMessage = true;
try {
IRequestInfo requestInfo = helpers.analyzeRequest(content);
int bodyOffset = requestInfo.getBodyOffset();
String soapMessage = new String(content, bodyOffset, content.length - bodyOffset, "UTF-8");
Document document = xmlHelpers.getXMLDocumentOfSAMLMessage(soapMessage);
return xmlHelpers.getAssertions(document).getLength() != 0
|| xmlHelpers.getEncryptedAssertions(document).getLength() != 0;
} catch (UnsupportedEncodingException e) {
} catch (SAXException e) {
e.printStackTrace();
return false;
}
}
// WSS Security
else if (null != helpers.getRequestParameter(content, "wresult")) {
try {
IRequestInfo requestInfo = helpers.analyzeRequest(content);
isWSSUrlEncoded = requestInfo.getContentType() == IRequestInfo.CONTENT_TYPE_URL_ENCODED;
isWSSMessage = true;
IParameter parameter = helpers.getRequestParameter(content, "wresult");
String wssMessage = getDecodedSAMLMessage(parameter.getValue());
Document document;
document = xmlHelpers.getXMLDocumentOfSAMLMessage(wssMessage);
return xmlHelpers.getAssertions(document).getLength() != 0
|| xmlHelpers.getEncryptedAssertions(document).getLength() != 0;
} catch (SAXException e) {
e.printStackTrace();
return false;
}
} else {
isWSSMessage = false;
isSOAPMessage = false;
IParameter requestParameter;
requestParameter = helpers.getRequestParameter(content, "SAMLResponse");
if (requestParameter != null) {
isSAMLRequest = false;
return true;
}
requestParameter = helpers.getRequestParameter(content, "SAMLRequest");
if (requestParameter != null) {
isSAMLRequest = true;
return true;
}
}
return false;
}
@Override
public boolean isModified() {
return edited;
}
@Override
public void setMessage(byte[] content, boolean isRequest) {
resetInfoMessageText();
edited = false;
if (content == null) {
textArea.setText(null);
textArea.setEditable(false);
setGUIEditable(false);
resetInformationDisplay();
} else {
message = content;
try {
if (isSOAPMessage) {
IResponseInfo responseInfo = helpers.analyzeResponse(content);
int bodyOffset = responseInfo.getBodyOffset();
String soapMessage = new String(content, bodyOffset, content.length - bodyOffset, "UTF-8");
Document document = xmlHelpers.getXMLDocumentOfSAMLMessage(soapMessage);
Document documentSAML = xmlHelpers.getSAMLResponseOfSOAP(document);
SAMLMessage = xmlHelpers.getStringOfDocument(documentSAML, 0, false);
} else if (isWSSMessage) {
IParameter parameter = helpers.getRequestParameter(content, "wresult");
SAMLMessage = getDecodedSAMLMessage(parameter.getValue());
} else {
IParameter parameter;
if (isSAMLRequest) {
parameter = helpers.getRequestParameter(content, "SAMLRequest");
} else {
parameter = helpers.getRequestParameter(content, "SAMLResponse");
}
if (null != parameter) {
SAMLMessage = getDecodedSAMLMessage(parameter.getValue());
}
}
Document document = xmlHelpers.getXMLDocumentOfSAMLMessage(SAMLMessage);
SAMLMessage = xmlHelpers.getStringOfDocument(document, 2, true);
} catch (IOException e) {
e.printStackTrace();
setInfoMessageText(XML_COULD_NOT_SERIALIZE);
} catch (SAXException e) {
e.printStackTrace();
setInfoMessageText(XML_NOT_WELL_FORMED);
SAMLMessage = "<error>" + XML_NOT_WELL_FORMED + "</error>";
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
setInformationDisplay();
updateCertificateList();
updateXSWList();
orgSAMLMessage = SAMLMessage;
textArea.setText(SAMLMessage);
textArea.setEditable(editable);
setGUIEditable(editable);
}
}
private void setInformationDisplay() {
SamlPanelInfo infoPanel = samlGUI.getInfoPanel();
try {
Document document = xmlHelpers.getXMLDocumentOfSAMLMessage(SAMLMessage);
NodeList assertions = xmlHelpers.getAssertions(document);
if (assertions.getLength() > 0) {
Node assertion = assertions.item(0);
infoPanel.setIssuer(xmlHelpers.getIssuer(document));
infoPanel.setConditionNotBefore(xmlHelpers.getConditionNotBefore(assertion));
infoPanel.setConditionNotAfter(xmlHelpers.getConditionNotAfter(assertion));
infoPanel.setSubjectConfNotBefore(xmlHelpers.getSubjectConfNotBefore(assertion));
infoPanel.setSubjectConfNotAfter(xmlHelpers.getSubjectConfNotAfter(assertion));
infoPanel.setSignatureAlgorithm(xmlHelpers.getSignatureAlgorithm(assertion));
infoPanel.setDigestAlgorithm(xmlHelpers.getDigestAlgorithm(assertion));
} else {
assertions = xmlHelpers.getEncryptedAssertions(document);
Node assertion = assertions.item(0);
infoPanel.setEncryptionAlgorithm(xmlHelpers.getEncryptionMethod(assertion));
}
} catch (SAXException e) {
setInfoMessageText(XML_NOT_WELL_FORMED);
}
}
private void resetInformationDisplay() {
SamlPanelInfo infoPanel = samlGUI.getInfoPanel();
infoPanel.setIssuer("");
infoPanel.setConditionNotBefore("");
infoPanel.setConditionNotAfter("");
infoPanel.setSubjectConfNotBefore("");
infoPanel.setSubjectConfNotAfter("");
infoPanel.setSignatureAlgorithm("");
infoPanel.setDigestAlgorithm("");
infoPanel.setEncryptionAlgorithm("");
}
public String getEncodedSAMLMessage(String message) {
byte[] byteMessage;
try {
if (isWSSMessage) {
if (isWSSUrlEncoded) {
return URLEncoder.encode(message, "UTF-8");
} else {
return message;
}
}
byteMessage = message.getBytes("UTF-8");
if (isInflated) {
try {
byteMessage = httpHelpers.compress(byteMessage, isGZip);
} catch (IOException e) {
}
}
String base64Encoded = helpers.base64Encode(byteMessage);
return URLEncoder.encode(base64Encoded, "UTF-8");
} catch (UnsupportedEncodingException e1) {
}
return null;
}
public String getDecodedSAMLMessage(String message) {
if (isWSSMessage) {
if (isWSSUrlEncoded) {
return helpers.urlDecode(message);
} else {
return message;
}
}
String urlDecoded = helpers.urlDecode(message);
byte[] base64Decoded = helpers.base64Decode(urlDecoded);
isInflated = true;
isGZip = true;
// try normal Zip Inflate
try {
byte[] inflated = httpHelpers.decompress(base64Decoded, true);
return new String(inflated, "UTF-8");
} catch (IOException e) {
} catch (DataFormatException e) {
isGZip = false;
}
// try Gzip Inflate
try {
byte[] inflated = httpHelpers.decompress(base64Decoded, false);
return new String(inflated, "UTF-8");
} catch (IOException e) {
} catch (DataFormatException e) {
isInflated = false;
}
try {
return new String(base64Decoded, "UTF-8");
} catch (UnsupportedEncodingException e) {
}
return null;
}
public void removeSignature() {
resetInfoMessageText();
try {
Document document = xmlHelpers.getXMLDocumentOfSAMLMessage(textArea.getText());
if (xmlHelpers.removeAllSignatures(document) > 0) {
SAMLMessage = xmlHelpers.getStringOfDocument(document, 2, true);
textArea.setText(SAMLMessage);
edited = true;
setInfoMessageText("Message signature successful removed");
} else {
setInfoMessageText("No Signatures available to remove");
}
} catch (SAXException e1) {
setInfoMessageText(XML_NOT_WELL_FORMED);
} catch (IOException e) {
setInfoMessageText(XML_COULD_NOT_SERIALIZE);
}
}
public void resetMessage() {
SAMLMessage = orgSAMLMessage;
textArea.setText(SAMLMessage);
edited = false;
}
public void resignAssertion() {
try {
resetInfoMessageText();
BurpCertificate cert = samlGUI.getActionPanel().getSelectedCertificate();
if (cert != null) {
setInfoMessageText("Signing...");
Document document = xmlHelpers.getXMLDocumentOfSAMLMessage(textArea.getText());
NodeList assertions = xmlHelpers.getAssertions(document);
String signAlgorithm = xmlHelpers.getSignatureAlgorithm(assertions.item(0));
String digestAlgorithm = xmlHelpers.getDigestAlgorithm(assertions.item(0));
xmlHelpers.removeAllSignatures(document);
String string = xmlHelpers.getString(document);
Document doc = xmlHelpers.getXMLDocumentOfSAMLMessage(string);
xmlHelpers.removeEmptyTags(doc);
xmlHelpers.signAssertion(doc, signAlgorithm, digestAlgorithm, cert.getCertificate(),
cert.getPrivateKey());
SAMLMessage = xmlHelpers.getStringOfDocument(doc, 2, true);
textArea.setText(SAMLMessage);
edited = true;
setInfoMessageText("Assertions successfully signed");
} else {
setInfoMessageText("no certificate chosen to sign");
}
} catch (SAXException e) {
setInfoMessageText(XML_NOT_WELL_FORMED);
} catch (IOException e) {
setInfoMessageText(XML_COULD_NOT_SERIALIZE);
} catch (Exception e) {
setInfoMessageText(XML_COULD_NOT_SIGN);
}
}
public void resignMessage() {
try {
resetInfoMessageText();
if (isWSSMessage) {
setInfoMessageText("Message signing is not possible with WS-Security messages");
} else {
setInfoMessageText("Signing...");
BurpCertificate cert = samlGUI.getActionPanel().getSelectedCertificate();
if (cert != null) {
Document document = xmlHelpers.getXMLDocumentOfSAMLMessage(textArea.getText());
NodeList responses = xmlHelpers.getResponse(document);
String signAlgorithm = xmlHelpers.getSignatureAlgorithm(responses.item(0));
String digestAlgorithm = xmlHelpers.getDigestAlgorithm(responses.item(0));
xmlHelpers.removeOnlyMessageSignature(document);
xmlHelpers.signMessage(document, signAlgorithm, digestAlgorithm, cert.getCertificate(),
cert.getPrivateKey());
SAMLMessage = xmlHelpers.getStringOfDocument(document, 2, true);
textArea.setText(SAMLMessage);
edited = true;
setInfoMessageText("Message successfully signed");
} else {
setInfoMessageText("no certificate chosen to sign");
}
}
} catch (IOException e) {
setInfoMessageText(XML_COULD_NOT_SERIALIZE);
} catch (SAXException e) {
setInfoMessageText(XML_NOT_WELL_FORMED);
} catch (CertificateException e) {
setInfoMessageText(XML_COULD_NOT_SIGN);
} catch (NoSuchAlgorithmException e) {
setInfoMessageText(XML_COULD_NOT_SIGN + ", no such algorithm");
} catch (InvalidKeySpecException e) {
setInfoMessageText(XML_COULD_NOT_SIGN + ", invalid private key");
} catch (MarshalException e) {
setInfoMessageText(XML_COULD_NOT_SERIALIZE);
} catch (XMLSignatureException e) {
setInfoMessageText(XML_COULD_NOT_SIGN);
}
}
private void setInfoMessageText(String infoMessage) {
samlGUI.getActionPanel().getInfoMessageLabel().setText(infoMessage);
}
private void resetInfoMessageText() {
samlGUI.getActionPanel().getInfoMessageLabel().setText("");
}
private void updateCertificateList() {
List<BurpCertificate> list = certificateTabController.getCertificatesWithPrivateKey();
samlGUI.getActionPanel().setCertificateList(list);
}
private void updateXSWList() {
samlGUI.getActionPanel().setXSWList(XSWHelpers.xswTypes);
}
public void sendToCertificatesTab() {
try {
Document document = xmlHelpers.getXMLDocumentOfSAMLMessage(textArea.getText());
String cert = xmlHelpers.getCertificate(document.getDocumentElement());
if (cert != null) {
certificateTabController.importCertificateFromString(cert);
} else {
setInfoMessageText(XML_CERTIFICATE_NOT_FOUND);
}
} catch (SAXException e) {
setInfoMessageText(XML_NOT_WELL_FORMED);
}
}
public void showXSWPreview() {
try {
Document document = xmlHelpers.getXMLDocumentOfSAMLMessage(orgSAMLMessage);
xswHelpers.applyXSW(samlGUI.getActionPanel().getSelectedXSW(), document);
String after = xmlHelpers.getStringOfDocument(document, 2, true);
String diff = xswHelpers.diffLineMode(orgSAMLMessage, after);
File file = File.createTempFile("tmp", ".html", null);
FileOutputStream fileOutputStream = new FileOutputStream(file);
file.deleteOnExit();
fileOutputStream.write(diff.getBytes("UTF-8"));
fileOutputStream.flush();
fileOutputStream.close();
URI uri = new URL("file://" + file.getAbsolutePath()).toURI();
Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
desktop.browse(uri);
} else {
StringSelection stringSelection = new StringSelection(uri.toString());
Clipboard clpbrd = Toolkit.getDefaultToolkit().getSystemClipboard();
clpbrd.setContents(stringSelection, null);
setInfoMessageText(NO_BROWSER);
}
} catch (SAXException e) {
setInfoMessageText(XML_NOT_WELL_FORMED);
} catch (DOMException e) {
setInfoMessageText(XML_NOT_SUITABLE_FOR_XSW);
} catch (MalformedURLException e) {
} catch (URISyntaxException e) {
} catch (IOException e) {
setInfoMessageText(NO_DIFF_TEMP_FILE);
}
}
public void applyXSW() {
Document document;
try {
document = xmlHelpers.getXMLDocumentOfSAMLMessage(orgSAMLMessage);
xswHelpers.applyXSW(samlGUI.getActionPanel().getSelectedXSW(), document);
SAMLMessage = xmlHelpers.getStringOfDocument(document, 2, true);
textArea.setText(SAMLMessage);
edited = true;
setInfoMessageText(XSW_ATTACK_APPLIED);
} catch (SAXException e) {
setInfoMessageText(XML_NOT_WELL_FORMED);
} catch (IOException e) {
setInfoMessageText(XML_COULD_NOT_SERIALIZE);
} catch (DOMException | NullPointerException e) {
setInfoMessageText(XML_NOT_SUITABLE_FOR_XSW);
}
}
public void setGUIEditable(boolean editable) {
if (editable) {
samlGUI.getActionPanel().enableControls();
} else {
samlGUI.getActionPanel().disableControls();
}
}
public void searchInTextarea() {
String text = samlGUI.getActionPanel().getSearchText();
SearchContext context = new SearchContext();
context.setMatchCase(false);
context.setMarkAll(true);
context.setSearchFor(text);
context.setWholeWord(false);
SearchResult result = SearchEngine.find(textArea, context);
if (!result.wasFound()) {
textArea.setCaretPosition(0);
SearchEngine.find(textArea, context);
}
}
public void showSignatureHelp() {
SignatureHelpWindow window = new SignatureHelpWindow();
window.setVisible(true);
}
public void showXSWHelp() {
XSWHelpWindow window = new XSWHelpWindow();
window.setVisible(true);
}
@Override
public void update(Observable arg0, Object arg1) {
updateCertificateList();
}
}