Skip to content

Commit 93ec1c0

Browse files
authored
Merge pull request #160 from usdAG/feat/xml-setter
Add HTTP XML Setter and XML Setter operations
2 parents 75d3421 + d8932d4 commit 93ec1c0

File tree

3 files changed

+164
-59
lines changed

3 files changed

+164
-59
lines changed

src/main/java/de/usd/cstchef/Utils.java

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.io.IOException;
99
import java.io.ObjectInputStream;
1010
import java.io.ObjectOutputStream;
11+
import java.io.StringWriter;
1112
import java.net.URISyntaxException;
1213
import java.time.ZonedDateTime;
1314
import java.util.ArrayList;
@@ -20,6 +21,23 @@
2021
import java.util.zip.ZipEntry;
2122
import java.util.zip.ZipInputStream;
2223

24+
import javax.xml.XMLConstants;
25+
import javax.xml.parsers.DocumentBuilderFactory;
26+
import javax.xml.parsers.ParserConfigurationException;
27+
import javax.xml.transform.OutputKeys;
28+
import javax.xml.transform.Transformer;
29+
import javax.xml.transform.TransformerFactory;
30+
import javax.xml.transform.dom.DOMSource;
31+
import javax.xml.transform.stream.StreamResult;
32+
import javax.xml.xpath.XPath;
33+
import javax.xml.xpath.XPathConstants;
34+
import javax.xml.xpath.XPathFactory;
35+
36+
import org.w3c.dom.Document;
37+
import org.w3c.dom.Element;
38+
import org.w3c.dom.Node;
39+
import org.w3c.dom.NodeList;
40+
2341
import com.fasterxml.jackson.core.JsonProcessingException;
2442
import com.fasterxml.jackson.databind.JsonMappingException;
2543
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -121,6 +139,7 @@
121139
import de.usd.cstchef.operations.setter.HttpSetCookie;
122140
import de.usd.cstchef.operations.setter.HttpSetUri;
123141
import de.usd.cstchef.operations.setter.HttpXmlSetter;
142+
import de.usd.cstchef.operations.setter.XmlSetter;
124143
import de.usd.cstchef.operations.setter.JsonSetter;
125144
import de.usd.cstchef.operations.setter.LineSetter;
126145
import de.usd.cstchef.operations.signature.JWTDecode;
@@ -257,6 +276,75 @@ public static ByteArray jsonSetter(ByteArray input, String key, String value, bo
257276
return ByteArray.byteArray(document.jsonString());
258277
}
259278

279+
public static ByteArray xmlSetter(ByteArray input, String path, String value, boolean addIfNotPresent) throws Exception {
280+
281+
if(path.trim().isEmpty()) {
282+
return input;
283+
}
284+
285+
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
286+
// XXE prevention as per https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
287+
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
288+
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
289+
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
290+
dbf.setXIncludeAware(false);
291+
dbf.setExpandEntityReferences(false);
292+
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
293+
294+
Document doc = dbf.newDocumentBuilder().parse(new ByteArrayInputStream(input.getBytes()));
295+
doc.getDocumentElement().normalize();
296+
297+
Element toAdd;
298+
299+
XPath xPath = XPathFactory.newInstance().newXPath();
300+
NodeList nodeList;
301+
302+
Node disableEscaping = doc.createProcessingInstruction(StreamResult.PI_DISABLE_OUTPUT_ESCAPING, "&");
303+
// make sure disableEscaping is always the first child of the document element so the whole doc is escaped
304+
doc.getDocumentElement().getParentNode().insertBefore(disableEscaping, doc.getDocumentElement().getParentNode().getFirstChild());
305+
306+
try {
307+
nodeList = (NodeList) xPath.compile(path).evaluate(doc, XPathConstants.NODESET);
308+
}
309+
catch(Exception e) {
310+
throw new IllegalArgumentException("Invalid XPath Syntax.");
311+
}
312+
313+
for(int i = 0; i < nodeList.getLength(); i++) {
314+
nodeList.item(i).setTextContent(value);
315+
}
316+
317+
if(nodeList.getLength() == 0 && addIfNotPresent) {
318+
if(path.matches(".*/@[a-zA-Z0-9-_.]*")) {
319+
nodeList = (NodeList) xPath.compile(path.replaceAll("/@[a-zA-Z0-9-_.]*$", "")).evaluate(doc, XPathConstants.NODESET);
320+
for(int i = 0; i < nodeList.getLength(); i++) {
321+
((Element) nodeList.item(i)).setAttribute(path.split("@")[path.split("@").length - 1], value);
322+
}
323+
}
324+
else {
325+
nodeList = (NodeList) xPath.compile(path.replaceAll("/[a-zA-Z0-9-_.]*$", "")).evaluate(doc, XPathConstants.NODESET);
326+
for(int i = 0; i < nodeList.getLength(); i++) {
327+
toAdd = doc.createElement(path.split("/")[path.split("/").length - 1]);
328+
toAdd.setTextContent(value);
329+
nodeList.item(i).appendChild(toAdd);
330+
}
331+
}
332+
}
333+
334+
TransformerFactory transformerFactory = TransformerFactory.newInstance();
335+
// XXE prevention
336+
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
337+
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
338+
339+
Transformer xformer = transformerFactory.newTransformer();
340+
xformer.setOutputProperty(OutputKeys.INDENT, "no");
341+
xformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "yes");
342+
343+
StringWriter output = new StringWriter();
344+
xformer.transform(new DOMSource(doc), new StreamResult(output));
345+
return ByteArray.byteArray(output.toString());
346+
}
347+
260348
public static Class<? extends Operation>[] getOperationsBurp() {
261349
ZipInputStream zip = null;
262350
List<Class<? extends Operation>> operations = new ArrayList<Class<? extends Operation>>();
@@ -310,7 +398,7 @@ public static Class<? extends Operation>[] getOperationsDevOutgoingFormatting()
310398
HttpMultipartSetter.class,
311399
HttpPostExtractor.class, HttpPostSetter.class, PlainRequest.class, HttpSetBody.class,
312400
HttpSetCookie.class, HttpSetUri.class, HttpUriExtractor.class, HttpXmlExtractor.class,
313-
HttpXmlSetter.class, HtmlEncode.class, HtmlDecode.class, Inflate.class,
401+
HttpXmlSetter.class, XmlSetter.class, HtmlEncode.class, HtmlDecode.class, Inflate.class,
314402
JsonExtractor.class, JsonSetter.class, JWTDecode.class, JWTSign.class, Length.class,
315403
LineExtractor.class,
316404
LineSetter.class, MD2.class, MD4.class, MD5.class, Mean.class, Median.class,
@@ -339,7 +427,7 @@ public static Class<? extends Operation>[] getOperationsDevIncoming() {
339427
GetVariable.class, Gost.class, GUnzip.class, Gzip.class, Hmac.class, HttpBodyExtractor.class,
340428
HttpCookieExtractor.class, HttpHeaderExtractor.class, HttpHeaderSetter.class, HttpJsonExtractor.class,
341429
HttpJsonSetter.class, HttpMultipartExtractor.class, HttpMultipartSetter.class, PlainRequest.class,
342-
HttpSetBody.class, HttpSetCookie.class, HttpXmlExtractor.class, HttpXmlSetter.class, HtmlEncode.class,
430+
HttpSetBody.class, HttpSetCookie.class, HttpXmlExtractor.class, HttpXmlSetter.class, XmlSetter.class, HtmlEncode.class,
343431
HtmlDecode.class, Inflate.class, JsonExtractor.class, JsonSetter.class, JWTDecode.class, JWTSign.class,
344432
Length.class, LineExtractor.class, LineSetter.class, MD2.class, MD4.class, MD5.class, Mean.class, Median.class,
345433
Multiply.class, MultiplyList.class, NoOperation.class, NumberCompare.class, Prefix.class, RandomNumber.class,
Lines changed: 30 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,54 @@
11
package de.usd.cstchef.operations.setter;
22

3-
import java.io.ByteArrayInputStream;
4-
import java.io.StringWriter;
5-
63
import javax.swing.JCheckBox;
7-
import javax.xml.parsers.DocumentBuilder;
8-
import javax.xml.parsers.DocumentBuilderFactory;
9-
import javax.xml.transform.Transformer;
10-
import javax.xml.transform.TransformerFactory;
11-
import javax.xml.transform.dom.DOMSource;
12-
import javax.xml.transform.stream.StreamResult;
13-
14-
import org.w3c.dom.Document;
15-
import org.w3c.dom.Element;
16-
import org.w3c.dom.NodeList;
174

18-
import burp.BurpUtils;
19-
import burp.api.montoya.MontoyaApi;
205
import burp.api.montoya.core.ByteArray;
21-
import burp.api.montoya.http.message.params.HttpParameter;
22-
import burp.api.montoya.http.message.params.HttpParameterType;
23-
import burp.api.montoya.http.message.params.ParsedHttpParameter;
246
import burp.api.montoya.http.message.requests.HttpRequest;
257
import burp.api.montoya.http.message.responses.HttpResponse;
268
import de.usd.cstchef.Utils;
279
import de.usd.cstchef.Utils.MessageType;
10+
import de.usd.cstchef.operations.Operation;
2811
import de.usd.cstchef.operations.Operation.OperationInfos;
29-
import de.usd.cstchef.operations.OperationCategory;
3012
import de.usd.cstchef.view.ui.VariableTextField;
13+
import de.usd.cstchef.operations.OperationCategory;
14+
15+
@OperationInfos(name = "Set HTTP XML", category = OperationCategory.SETTER, description = "Set a XML parameter to the specified value.\nUse XPath Syntax.")
16+
public class HttpXmlSetter extends Operation {
3117

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

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

38-
String parameterName = getWhere();
39-
if (parameterName.equals(""))
25+
String p = this.path.getText();
26+
String v = this.value.getText();
27+
28+
if(p.trim().isEmpty()) {
4029
return input;
30+
}
4131

42-
if (messageType == MessageType.REQUEST) {
43-
try {
44-
HttpRequest request = HttpRequest.httpRequest(input);
45-
if (request.hasParameter(parameterName, HttpParameterType.XML)) {
46-
return request
47-
.withParameter(HttpParameter.parameter(parameterName, getWhat(), HttpParameterType.XML))
48-
.toByteArray();
49-
} else {
50-
return input;
51-
}
52-
} catch (Exception e) {
53-
throw new IllegalArgumentException("Input is not a valid request");
54-
}
55-
} else if (messageType == MessageType.RESPONSE) {
56-
HttpResponse response = HttpResponse.httpResponse(input);
57-
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
58-
Document doc = builder.parse(new ByteArrayInputStream(response.bodyToString().getBytes()));
59-
doc.getDocumentElement().normalize();
60-
NodeList nodeList = doc.getElementsByTagName(parameterName);
61-
Element first = (Element) nodeList.item(0);
62-
if (first != null) {
63-
first.setTextContent(getWhat());
64-
}
65-
else{
66-
throw new IllegalArgumentException("Parameter could not be found");
67-
}
68-
DOMSource domSource = new DOMSource(doc);
69-
StringWriter writer = new StringWriter();
70-
StreamResult result = new StreamResult(writer);
71-
TransformerFactory tf = TransformerFactory.newInstance();
72-
Transformer transformer = tf.newTransformer();
73-
transformer.transform(domSource, result);
74-
return response.withBody(writer.toString()).toByteArray();
75-
} else {
32+
if(messageType == MessageType.REQUEST) {
33+
return HttpRequest.httpRequest(input).withBody(Utils.xmlSetter(HttpRequest.httpRequest(input).body(), p, v, addIfNotPresent.isSelected())).toByteArray();
34+
}
35+
else if(messageType == MessageType.RESPONSE) {
36+
return HttpResponse.httpResponse(input).withBody(Utils.xmlSetter(HttpResponse.httpResponse(input).body(), p, v, addIfNotPresent.isSelected())).toByteArray();
37+
}
38+
else {
7639
return parseRawMessage(input);
7740
}
41+
}
7842

43+
@Override
44+
public void createUI() {
45+
this.path = new VariableTextField();
46+
this.value = new VariableTextField();
47+
this.addIfNotPresent = new JCheckBox("Add if not present");
48+
49+
this.addUIElement("Path", this.path);
50+
this.addUIElement("Value", this.value);
51+
this.addUIElement(null, this.addIfNotPresent, "checkbox1");
7952
}
8053

8154
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package de.usd.cstchef.operations.setter;
2+
3+
import javax.swing.JCheckBox;
4+
5+
import burp.api.montoya.core.ByteArray;
6+
import de.usd.cstchef.Utils;
7+
import de.usd.cstchef.Utils.MessageType;
8+
import de.usd.cstchef.operations.Operation;
9+
import de.usd.cstchef.operations.Operation.OperationInfos;
10+
import de.usd.cstchef.view.ui.VariableTextField;
11+
import de.usd.cstchef.operations.OperationCategory;
12+
13+
@OperationInfos(name = "Set XML", category = OperationCategory.SETTER, description = "Set a XML parameter to the specified value.\nUse XPath Syntax.")
14+
public class XmlSetter extends Operation {
15+
16+
private VariableTextField path;
17+
private VariableTextField value;
18+
private JCheckBox addIfNotPresent;
19+
20+
@Override
21+
protected ByteArray perform(ByteArray input, MessageType messageType) throws Exception {
22+
23+
String p = this.path.getText();
24+
String v = this.value.getText();
25+
26+
if(p.trim().isEmpty()) {
27+
return input;
28+
}
29+
30+
return Utils.xmlSetter(input, p, v, addIfNotPresent.isSelected());
31+
}
32+
33+
@Override
34+
public void createUI() {
35+
this.path = new VariableTextField();
36+
this.value = new VariableTextField();
37+
this.addIfNotPresent = new JCheckBox("Add if not present");
38+
39+
this.addUIElement("Path", this.path);
40+
this.addUIElement("Value", this.value);
41+
this.addUIElement(null, this.addIfNotPresent, "checkbox1");
42+
}
43+
44+
}

0 commit comments

Comments
 (0)