Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HTTP XML Setter and XML Setter operations #160

Merged
merged 5 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 90 additions & 2 deletions src/main/java/de/usd/cstchef/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.StringWriter;
import java.net.URISyntaxException;
import java.time.ZonedDateTime;
import java.util.ArrayList;
Expand All @@ -20,6 +21,23 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand Down Expand Up @@ -121,6 +139,7 @@
import de.usd.cstchef.operations.setter.HttpSetCookie;
import de.usd.cstchef.operations.setter.HttpSetUri;
import de.usd.cstchef.operations.setter.HttpXmlSetter;
import de.usd.cstchef.operations.setter.XmlSetter;
import de.usd.cstchef.operations.setter.JsonSetter;
import de.usd.cstchef.operations.setter.LineSetter;
import de.usd.cstchef.operations.signature.JWTDecode;
Expand Down Expand Up @@ -257,6 +276,75 @@ public static ByteArray jsonSetter(ByteArray input, String key, String value, bo
return ByteArray.byteArray(document.jsonString());
}

public static ByteArray xmlSetter(ByteArray input, String path, String value, boolean addIfNotPresent) throws Exception {

if(path.trim().isEmpty()) {
return input;
}

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// XXE prevention as per https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);

Document doc = dbf.newDocumentBuilder().parse(new ByteArrayInputStream(input.getBytes()));
doc.getDocumentElement().normalize();

Element toAdd;

XPath xPath = XPathFactory.newInstance().newXPath();
NodeList nodeList;

Node disableEscaping = doc.createProcessingInstruction(StreamResult.PI_DISABLE_OUTPUT_ESCAPING, "&");
// make sure disableEscaping is always the first child of the document element so the whole doc is escaped
doc.getDocumentElement().getParentNode().insertBefore(disableEscaping, doc.getDocumentElement().getParentNode().getFirstChild());

try {
nodeList = (NodeList) xPath.compile(path).evaluate(doc, XPathConstants.NODESET);
}
catch(Exception e) {
throw new IllegalArgumentException("Invalid XPath Syntax.");
}

for(int i = 0; i < nodeList.getLength(); i++) {
nodeList.item(i).setTextContent(value);
}

if(nodeList.getLength() == 0 && addIfNotPresent) {
if(path.matches(".*/@[a-zA-Z0-9-_.]*")) {
nodeList = (NodeList) xPath.compile(path.replaceAll("/@[a-zA-Z0-9-_.]*$", "")).evaluate(doc, XPathConstants.NODESET);
for(int i = 0; i < nodeList.getLength(); i++) {
((Element) nodeList.item(i)).setAttribute(path.split("@")[path.split("@").length - 1], value);
}
}
else {
nodeList = (NodeList) xPath.compile(path.replaceAll("/[a-zA-Z0-9-_.]*$", "")).evaluate(doc, XPathConstants.NODESET);
for(int i = 0; i < nodeList.getLength(); i++) {
toAdd = doc.createElement(path.split("/")[path.split("/").length - 1]);
toAdd.setTextContent(value);
nodeList.item(i).appendChild(toAdd);
}
}
}

TransformerFactory transformerFactory = TransformerFactory.newInstance();
// XXE prevention
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");

Transformer xformer = transformerFactory.newTransformer();
xformer.setOutputProperty(OutputKeys.INDENT, "no");
xformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "yes");

StringWriter output = new StringWriter();
xformer.transform(new DOMSource(doc), new StreamResult(output));
return ByteArray.byteArray(output.toString());
}

public static Class<? extends Operation>[] getOperationsBurp() {
ZipInputStream zip = null;
List<Class<? extends Operation>> operations = new ArrayList<Class<? extends Operation>>();
Expand Down Expand Up @@ -310,7 +398,7 @@ public static Class<? extends Operation>[] getOperationsDevOutgoingFormatting()
HttpMultipartSetter.class,
HttpPostExtractor.class, HttpPostSetter.class, PlainRequest.class, HttpSetBody.class,
HttpSetCookie.class, HttpSetUri.class, HttpUriExtractor.class, HttpXmlExtractor.class,
HttpXmlSetter.class, HtmlEncode.class, HtmlDecode.class, Inflate.class,
HttpXmlSetter.class, XmlSetter.class, HtmlEncode.class, HtmlDecode.class, Inflate.class,
JsonExtractor.class, JsonSetter.class, JWTDecode.class, JWTSign.class, Length.class,
LineExtractor.class,
LineSetter.class, MD2.class, MD4.class, MD5.class, Mean.class, Median.class,
Expand Down Expand Up @@ -339,7 +427,7 @@ public static Class<? extends Operation>[] getOperationsDevIncoming() {
GetVariable.class, Gost.class, GUnzip.class, Gzip.class, Hmac.class, HttpBodyExtractor.class,
HttpCookieExtractor.class, HttpHeaderExtractor.class, HttpHeaderSetter.class, HttpJsonExtractor.class,
HttpJsonSetter.class, HttpMultipartExtractor.class, HttpMultipartSetter.class, PlainRequest.class,
HttpSetBody.class, HttpSetCookie.class, HttpXmlExtractor.class, HttpXmlSetter.class, HtmlEncode.class,
HttpSetBody.class, HttpSetCookie.class, HttpXmlExtractor.class, HttpXmlSetter.class, XmlSetter.class, HtmlEncode.class,
HtmlDecode.class, Inflate.class, JsonExtractor.class, JsonSetter.class, JWTDecode.class, JWTSign.class,
Length.class, LineExtractor.class, LineSetter.class, MD2.class, MD4.class, MD5.class, Mean.class, Median.class,
Multiply.class, MultiplyList.class, NoOperation.class, NumberCompare.class, Prefix.class, RandomNumber.class,
Expand Down
87 changes: 30 additions & 57 deletions src/main/java/de/usd/cstchef/operations/setter/HttpXmlSetter.java
Original file line number Diff line number Diff line change
@@ -1,81 +1,54 @@
package de.usd.cstchef.operations.setter;

import java.io.ByteArrayInputStream;
import java.io.StringWriter;

import javax.swing.JCheckBox;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import burp.BurpUtils;
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.core.ByteArray;
import burp.api.montoya.http.message.params.HttpParameter;
import burp.api.montoya.http.message.params.HttpParameterType;
import burp.api.montoya.http.message.params.ParsedHttpParameter;
import burp.api.montoya.http.message.requests.HttpRequest;
import burp.api.montoya.http.message.responses.HttpResponse;
import de.usd.cstchef.Utils;
import de.usd.cstchef.Utils.MessageType;
import de.usd.cstchef.operations.Operation;
import de.usd.cstchef.operations.Operation.OperationInfos;
import de.usd.cstchef.operations.OperationCategory;
import de.usd.cstchef.view.ui.VariableTextField;
import de.usd.cstchef.operations.OperationCategory;

@OperationInfos(name = "Set HTTP XML", category = OperationCategory.SETTER, description = "Set a XML parameter to the specified value.\nUse XPath Syntax.")
public class HttpXmlSetter extends Operation {

@OperationInfos(name = "Set HTTP XML", category = OperationCategory.SETTER, description = "Set a XML parameter to the specified value.")
public class HttpXmlSetter extends SetterOperation {
private VariableTextField path;
private VariableTextField value;
private JCheckBox addIfNotPresent;

@Override
protected ByteArray perform(ByteArray input, MessageType messageType) throws Exception {

String parameterName = getWhere();
if (parameterName.equals(""))
String p = this.path.getText();
String v = this.value.getText();

if(p.trim().isEmpty()) {
return input;
}

if (messageType == MessageType.REQUEST) {
try {
HttpRequest request = HttpRequest.httpRequest(input);
if (request.hasParameter(parameterName, HttpParameterType.XML)) {
return request
.withParameter(HttpParameter.parameter(parameterName, getWhat(), HttpParameterType.XML))
.toByteArray();
} else {
return input;
}
} catch (Exception e) {
throw new IllegalArgumentException("Input is not a valid request");
}
} else if (messageType == MessageType.RESPONSE) {
HttpResponse response = HttpResponse.httpResponse(input);
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = builder.parse(new ByteArrayInputStream(response.bodyToString().getBytes()));
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getElementsByTagName(parameterName);
Element first = (Element) nodeList.item(0);
if (first != null) {
first.setTextContent(getWhat());
}
else{
throw new IllegalArgumentException("Parameter could not be found");
}
DOMSource domSource = new DOMSource(doc);
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(domSource, result);
return response.withBody(writer.toString()).toByteArray();
} else {
if(messageType == MessageType.REQUEST) {
return HttpRequest.httpRequest(input).withBody(Utils.xmlSetter(HttpRequest.httpRequest(input).body(), p, v, addIfNotPresent.isSelected())).toByteArray();
}
else if(messageType == MessageType.RESPONSE) {
return HttpResponse.httpResponse(input).withBody(Utils.xmlSetter(HttpResponse.httpResponse(input).body(), p, v, addIfNotPresent.isSelected())).toByteArray();
}
else {
return parseRawMessage(input);
}
}

@Override
public void createUI() {
this.path = new VariableTextField();
this.value = new VariableTextField();
this.addIfNotPresent = new JCheckBox("Add if not present");

this.addUIElement("Path", this.path);
this.addUIElement("Value", this.value);
this.addUIElement(null, this.addIfNotPresent, "checkbox1");
}

}
44 changes: 44 additions & 0 deletions src/main/java/de/usd/cstchef/operations/setter/XmlSetter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package de.usd.cstchef.operations.setter;

import javax.swing.JCheckBox;

import burp.api.montoya.core.ByteArray;
import de.usd.cstchef.Utils;
import de.usd.cstchef.Utils.MessageType;
import de.usd.cstchef.operations.Operation;
import de.usd.cstchef.operations.Operation.OperationInfos;
import de.usd.cstchef.view.ui.VariableTextField;
import de.usd.cstchef.operations.OperationCategory;

@OperationInfos(name = "Set XML", category = OperationCategory.SETTER, description = "Set a XML parameter to the specified value.\nUse XPath Syntax.")
public class XmlSetter extends Operation {

private VariableTextField path;
private VariableTextField value;
private JCheckBox addIfNotPresent;

@Override
protected ByteArray perform(ByteArray input, MessageType messageType) throws Exception {

String p = this.path.getText();
String v = this.value.getText();

if(p.trim().isEmpty()) {
return input;
}

return Utils.xmlSetter(input, p, v, addIfNotPresent.isSelected());
}

@Override
public void createUI() {
this.path = new VariableTextField();
this.value = new VariableTextField();
this.addIfNotPresent = new JCheckBox("Add if not present");

this.addUIElement("Path", this.path);
this.addUIElement("Value", this.value);
this.addUIElement(null, this.addIfNotPresent, "checkbox1");
}

}
Loading