From aa9f7662500d6256646bc8d2d28f0cd47a08e154 Mon Sep 17 00:00:00 2001 From: Matteo Bitussi Date: Thu, 9 Nov 2023 09:39:54 +0100 Subject: [PATCH] Edit operations supported on Operation - Added Edit Operations to edit messages in Operation - Removed IExtensionHelpers dependencies where possible - Fixed some bugs in HTTPReqRes methods - Added missing HTTPReqRes meethods to edit message - Added encode value in Edit Operation - Added tests for Edit Operation and new HTTPReqRes methods - new dependency for parsing urls --- doc/language.md | 267 ++++---- tool/pom.xml | 5 + tool/src/main/java/migt/BurpExtender.java | 2 - tool/src/main/java/migt/Check.java | 2 - tool/src/main/java/migt/DecodeOperation.java | 9 +- tool/src/main/java/migt/EditOperation.java | 596 +++++++++++------- .../main/java/migt/ExecuteActiveListener.java | 2 - tool/src/main/java/migt/ExecuteActives.java | 2 - tool/src/main/java/migt/ExecutePassives.java | 2 - tool/src/main/java/migt/ExecuteTrack.java | 2 - .../main/java/migt/ExecuteTrackListener.java | 2 - tool/src/main/java/migt/GUI.java | 3 - tool/src/main/java/migt/HTTPReqRes.java | 128 ++-- tool/src/main/java/migt/JWT.java | 1 - tool/src/main/java/migt/Marker.java | 2 - tool/src/main/java/migt/MessageOperation.java | 27 +- tool/src/main/java/migt/MessageType.java | 2 - tool/src/main/java/migt/Module.java | 9 +- tool/src/main/java/migt/Operation.java | 14 +- tool/src/main/java/migt/ParsingException.java | 2 - tool/src/main/java/migt/Session.java | 2 - tool/src/main/java/migt/SessionOperation.java | 2 - .../main/java/migt/SessionTrackAction.java | 2 - tool/src/main/java/migt/Test.java | 2 - tool/src/main/java/migt/TestSuite.java | 2 - tool/src/main/java/migt/Tools.java | 34 +- tool/src/main/java/migt/Var.java | 2 - tool/src/main/java/migt/XML.java | 2 - tool/src/test/java/EditOperation_test.java | 132 ++++ tool/src/test/java/HTTPReqRes_Test.java | 78 ++- 30 files changed, 868 insertions(+), 469 deletions(-) create mode 100644 tool/src/test/java/EditOperation_test.java diff --git a/doc/language.md b/doc/language.md index ad7555b..2816164 100644 --- a/doc/language.md +++ b/doc/language.md @@ -139,131 +139,27 @@ If you choose the body section, the meaning of the tags is different, infact: Note that the content lenght of the body section is automatically updated or removed if the content of the body is edited. -## Decode operation - -A list of decode operations can be added to each Operation (passive or active). These operations are used to decode encoded content taken from inside an intercepted message. A decode operation can have its own list of decode operations; these are called recursive decode ops. and they take as input the previously decoded content. - -The HTTP parameter containing the content to be decoded has to be specified with the `decode param` If the section of the message is 'body,' the `decode param` takes a regex (for more information, see the section below). - -Next, a list of encodings (or the `type`) has to be specified. You can specify an encodings list with the tag `encodings` the order of these encodings will be followed while decoding, and when the message is re-encoded, the order will be reversed. With the tag `type` the content is decoded following a fixed set of rules (e.g., jwts or XML). If you specify the `type` tag, the `encodings` tag will be ignored. If you use the `type` tag when using Edit Operations, you may be allowed to use more tags (e.g., with jwts or XML) to edit easily; otherwise, the decoded content will be used as plain text. - -In decode operations is possible to use check operations to check the decoded content, depending on the specified type, the allowed check operations differ. - -- JWT type -> See 'Checks on JSON content' section -- No type (encodings used) -> See check section - -Needed tags: - -- `type` and/or `encodings` -- `decode param` -- `from` select from where decode the content (HTTP message section or previous decode output) - -optional tags: - -- `decode operations` -- `checks` -- `edits` - -#### Body section - -An important note about the body (from) section is that the input of `decode param` has to be a regex, and whatever is matched with that regex is decoded. -An useful regex to match a parameter's value could be `(?<=SAMLResponse=)[^$\n& ]*` which searches the SAMLResponse= string in the body, and matches everything that is not $ or \n or & or whitespace - -#### Decoding JWTs +## Edit Operation -When decoding a jwt (by using tag type=jwt) it is possible to check the signature of that jwt by providing a public key. To do this, use the `jwt check sig` tag with value the PEM-encoded public key to be used to check. - -Note: The supported algorithms for signing are: - -- RS256 -- RS512 +The edit operation can be used to modify the content of a message, a jwt, or other sources. -#### Decoding JWE +### Using Edit Operation in a Operation -Note that when decrypting a JWE, a JWS is expected in the payload. Other payloads are not supproted. +By using an Edit Operation inside an Operation, you are able to edit the intercepted message content. There are multiple ways of editing it, in the following section they will be described. You can use the following tags: -To decrypt a JWE to access the JWT in its payload use these tags +- `from` to select the section of the message you need +- `edit` to edit the value of the given parameter. (only for url and head sections) use `value` to specify the new value. +- `edit regex` to edit with a regex the section of the message you selected. use `value` to specify the new value +- `add` to add some content to the given section. in case of url and head, you need to specify the name of the parameter in this tag, and the value with `value`. For the body section, the content will be always appended to the end of the body, so you can leave this tag value empty and put the content to append in the `value` tag. +- `remove` used to specify the name of a parameter to remove in url and head. Not available on body. +- `value` used to specify the new value for the edit operations +- `use` used in place of `value` to use the given variable value as new value. You should give a variable name to this tag. -- `jwe decrypt` with the private key in PEM string format -- `jwe encrypt` with the public key in PEM string format +>Note: the url section is the entire request url such as "" -Note: You can decrypt without specifying encrypt, this will prevent the JWE from being edited (as no encryption key is passed) -Note: You can't encrypt, without decrypt +>Note: if you use edit regex in the head, the regex is executed over all the headers -Note: Supported algorithms are: - -- RSA_OAEP -- RSA_OAEP_256 -- ECDH_ES_A128KW -- ECDH_ES_A256KW - -## Tag table - -In this table you can find a description of all the tags available for this Operation based on the input Module. - -| input module (container) | Available tags | Required | value type | allowed values | -| --------------------------- | ----------------- | -------- | ---------------------- | -------------------------------------- | -| standard Operation | from | yes | str | head, body, url | -| | decode param | yes | str | \* | -| decode Operation (type=jwt) | from | yes | str | jwt header, jwt payload, jwt signature | -| | decode param | yes | str(JSON path) | \* | -| \* | type | | str | xml, jwt | -| | encodings | | list[str] | base64, url, .. | -| | decode operations | | list[decode Operation] | \* | -| | checks | | list[check Operation] | \* | -| | edits | | list[edit Operation] | \* | -| | jwt check sig | | str(PEM) | PEM-encoded public key | -| | jwe decrypt | | str(PEM) | PEM-encoded private key | -| | jwe encrypt | | str(PEM) | PEM-encoded public key | - -### Recursive Decode operations - -When using the `from` tag in a recursive decode (one that is inside another), you can use - depending on the previous decode type - other sections, such as "jwt header" "jwt payload" .. - -Source: standard Operation (HTTP intercepted message) - -- `from`: (url, head, body) - -Source: decode Operation JWT - -- `from`: (jwt header, jwt payload, jwt signature) - -in recurdsive decode op, the decode param accepts different inputs, e.g. if previous decoded content is a jwt, decoded content will accept a JSON path - -Syntax example of a recursive decode operation: - -```json -"decode operations": [ - { - ..., - "decode operations": [ - { - "from": "somwhere in the previous decode", - "encodings": "asdasd", - } - ] - } -] -``` - -Example of decoding a jwt from the url of a message in the asd parameter, and then decode the jwt found inside of the jwt. - -```json -"decode operations": [ - { - "from": "url", - "type": "jwt", - "decode param": "asd", - "decode operations": [ - { - "from": "jwt header", - "type": "jwt", - "decode param": "$.something" - } - ] - } -] -``` +>Note: Save from message is not possible in edit, is should be done in message operations ### Using Edit Operation in Decode Operations @@ -425,10 +321,136 @@ It is possible to use the tag `use` instead of the tag `value` to use the text s #### SAML signature -There's the possibility to remove the signature from a saml request or response and resign it with a test private key, just specify `self-sign`: true in the message operation. +There's the possibility to remove the signature from a saml request or response and resign it with a test private key, just specify `self-sign`: true in the message operation.## Decode operation Another possibility is just to remove the signature, using `remove signature` set to true in the message operation. Note that these keys are avaiable and applied only on decoded parameters, also if `decode param` is defined. +## Decode operation + +A list of decode operations can be added to each Operation (passive or active). These operations are used to decode encoded content taken from inside an intercepted message. A decode operation can have its own list of decode operations; these are called recursive decode ops. and they take as input the previously decoded content. + +The HTTP parameter containing the content to be decoded has to be specified with the `decode param` If the section of the message is 'body,' the `decode param` takes a regex (for more information, see the section below). + +Next, a list of encodings (or the `type`) has to be specified. You can specify an encodings list with the tag `encodings` the order of these encodings will be followed while decoding, and when the message is re-encoded, the order will be reversed. With the tag `type` the content is decoded following a fixed set of rules (e.g., jwts or XML). If you specify the `type` tag, the `encodings` tag will be ignored. If you use the `type` tag when using Edit Operations, you may be allowed to use more tags (e.g., with jwts or XML) to edit easily; otherwise, the decoded content will be used as plain text. + +In decode operations is possible to use check operations to check the decoded content, depending on the specified type, the allowed check operations differ. + +- JWT type -> See 'Checks on JSON content' section +- No type (encodings used) -> See check section + +Needed tags: + +- `type` and/or `encodings` +- `decode param` +- `from` select from where decode the content (HTTP message section or previous decode output) + +optional tags: + +- `decode operations` +- `checks` +- `edits` + +#### Body section + +An important note about the body (from) section is that the input of `decode param` has to be a regex, and whatever is matched with that regex is decoded. +An useful regex to match a parameter's value could be `(?<=SAMLResponse=)[^$\n& ]*` which searches the SAMLResponse= string in the body, and matches everything that is not $ or \n or & or whitespace + +#### Decoding JWTs + +When decoding a jwt (by using tag type=jwt) it is possible to check the signature of that jwt by providing a public key. To do this, use the `jwt check sig` tag with value the PEM-encoded public key to be used to check. + +Note: The supported algorithms for signing are: + +- RS256 +- RS512 + +#### Decoding JWE + +Note that when decrypting a JWE, a JWS is expected in the payload. Other payloads are not supproted. + +To decrypt a JWE to access the JWT in its payload use these tags + +- `jwe decrypt` with the private key in PEM string format +- `jwe encrypt` with the public key in PEM string format + +Note: You can decrypt without specifying encrypt, this will prevent the JWE from being edited (as no encryption key is passed) +Note: You can't encrypt, without decrypt + +Note: Supported algorithms are: + +- RSA_OAEP +- RSA_OAEP_256 +- ECDH_ES_A128KW +- ECDH_ES_A256KW + +## Tag table + +In this table you can find a description of all the tags available for this Operation based on the input Module. + +| input module (container) | Available tags | Required | value type | allowed values | +| --------------------------- | ----------------- | -------- | ---------------------- | -------------------------------------- | +| standard Operation | from | yes | str | head, body, url | +| | decode param | yes | str | \* | +| decode Operation (type=jwt) | from | yes | str | jwt header, jwt payload, jwt signature | +| | decode param | yes | str(JSON path) | \* | +| \* | type | | str | xml, jwt | +| | encodings | | list[str] | base64, url, .. | +| | decode operations | | list[decode Operation] | \* | +| | checks | | list[check Operation] | \* | +| | edits | | list[edit Operation] | \* | +| | jwt check sig | | str(PEM) | PEM-encoded public key | +| | jwe decrypt | | str(PEM) | PEM-encoded private key | +| | jwe encrypt | | str(PEM) | PEM-encoded public key | + +### Recursive Decode operations + +When using the `from` tag in a recursive decode (one that is inside another), you can use - depending on the previous decode type - other sections, such as "jwt header" "jwt payload" .. + +Source: standard Operation (HTTP intercepted message) + +- `from`: (url, head, body) + +Source: decode Operation JWT + +- `from`: (jwt header, jwt payload, jwt signature) + +in recurdsive decode op, the decode param accepts different inputs, e.g. if previous decoded content is a jwt, decoded content will accept a JSON path + +Syntax example of a recursive decode operation: + +```json +"decode operations": [ + { + ..., + "decode operations": [ + { + "from": "somwhere in the previous decode", + "encodings": "asdasd", + } + ] + } +] +``` + +Example of decoding a jwt from the url of a message in the asd parameter, and then decode the jwt found inside of the jwt. + +```json +"decode operations": [ + { + "from": "url", + "type": "jwt", + "decode param": "asd", + "decode operations": [ + { + "from": "jwt header", + "type": "jwt", + "decode param": "$.something" + } + ] + } +] +``` + ## Session Operation Session operations are used to edit the session tracks given the actual test progress. @@ -608,7 +630,7 @@ Note that when saving a variable, if the value is empty (no match or no paramete ## Result and oracle -The result tag is used in active tests to specify the oracle to be used to verify the execution of the session. +The result tag is used in active tests to specify the oracle to be used to verify the execution of the session. it can be set to: @@ -626,7 +648,9 @@ Note that if correct (or incorrect) flow is used without specifying a session na Note for the definition of the track: to have a successfull oracle we suggest to define a track that not only does the login of the user, but also performs some actions on the final page, this way the result of the track is more complete. (i.e. if we just tell to login, the track will not try to act on the logged page, this way the plugin has no clue on if the final page contains an error or not) ### Understanding test results + A test can have one of three different results, that are: + - passed: based on the test description and objectives, the test execution was successful and the verified content met the pre-defined conditions. - failed: based on the test description and objectives, the test execution was successful but the verified content didn't met the pre-defined conditions. - not applicable: it was not possible to execute the test, the result cannot be determined with ceirtainty. In this case, it is not possible to know if the test failed because of external causes or due to the test itself, possible causes are: @@ -940,3 +964,6 @@ Examples:
- Removed OAuth metadata tag in test - Added signing of decoded jwt with private key inside Edit Operation - Added check of signature of jwt inside the decode operation +- Added edit operation also in Operations: message editing +- Added encode option to edit operation +- Removed "remove match word" from edit operation, just use edit regex with empty substitution diff --git a/tool/pom.xml b/tool/pom.xml index 5cbc139..4b50e11 100644 --- a/tool/pom.xml +++ b/tool/pom.xml @@ -65,6 +65,11 @@ httpclient 4.5.13 + + org.apache.httpcomponents + httpcore + 4.4.14 + diff --git a/tool/src/main/java/migt/BurpExtender.java b/tool/src/main/java/migt/BurpExtender.java index 945c997..ae2efbe 100644 --- a/tool/src/main/java/migt/BurpExtender.java +++ b/tool/src/main/java/migt/BurpExtender.java @@ -11,8 +11,6 @@ /** * Main class executed by Burp - * - * @author Matteo Bitussi */ public class BurpExtender implements IBurpExtender, ITab, IProxyListener { diff --git a/tool/src/main/java/migt/Check.java b/tool/src/main/java/migt/Check.java index f2e2b6f..9033b9c 100644 --- a/tool/src/main/java/migt/Check.java +++ b/tool/src/main/java/migt/Check.java @@ -17,8 +17,6 @@ /** * Check Object class. This object is used in Operations to check that a parameter or some text is in as specified. - * - * @author Matteo Bitussi */ public class Check extends Module { String what; // what to search diff --git a/tool/src/main/java/migt/DecodeOperation.java b/tool/src/main/java/migt/DecodeOperation.java index 90d725e..74d6601 100644 --- a/tool/src/main/java/migt/DecodeOperation.java +++ b/tool/src/main/java/migt/DecodeOperation.java @@ -242,7 +242,7 @@ public static String decode(List encodings, String encoded, IExtension * @param decoded the string to be encoded * @return the encoded string */ - public static String encode(List encodings, String decoded, IExtensionHelpers helpers) { + public static String encode(List encodings, String decoded) { String actual = decoded; byte[] actual_b = null; boolean isActualString = true; @@ -251,9 +251,9 @@ public static String encode(List encodings, String decoded, IExtension case BASE64: if (isActualString) { - actual = helpers.base64Encode(actual); + actual = Base64.getEncoder().encodeToString(actual.getBytes()); } else { - actual = helpers.base64Encode(actual_b); + Base64.getEncoder().encodeToString(actual_b); isActualString = true; } break; @@ -422,7 +422,7 @@ public void loader(DecodeOperation_API api, IExtensionHelpers helpers) { @Override public API exporter() throws ParsingException { Collections.reverse(encodings); // Set the right order for encoding - String encoded = encode(encodings, decoded_content, helpers); + String encoded = encode(encodings, decoded_content); if (imported_api instanceof Operation_API) { Tools.editMessageParam( @@ -476,6 +476,7 @@ public void execute(List vars) throws ParsingException { } decoded_content = decode(encodings, found, helpers); break; + default: throw new UnsupportedOperationException( "the from you selected in the recursive decode operation is not yet supported"); diff --git a/tool/src/main/java/migt/EditOperation.java b/tool/src/main/java/migt/EditOperation.java index 756a811..a2b8946 100644 --- a/tool/src/main/java/migt/EditOperation.java +++ b/tool/src/main/java/migt/EditOperation.java @@ -1,12 +1,15 @@ package migt; import com.jayway.jsonpath.PathNotFoundException; +import org.json.JSONArray; import org.json.JSONObject; import org.w3c.dom.Document; import org.xml.sax.SAXException; import samlraider.application.SamlTabController; import samlraider.helpers.XMLHelpers; +import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -40,6 +43,12 @@ public class EditOperation extends Module { // TXT TxtAction txt_action; String txt_action_name; + // Encode + List encodings; + + // Http message + MessageOperation.MessageOperationActions action; + HTTPReqRes.MessageSection msg_from; public EditOperation(JSONObject eop_json) throws ParsingException { init(); @@ -56,6 +65,7 @@ public EditOperation(JSONObject eop_json) throws ParsingException { break; case "value": // value of xml or other edits + // TODO add in doc, used also for messages value = eop_json.getString("value"); break; case "add tag": @@ -147,6 +157,56 @@ public EditOperation(JSONObject eop_json) throws ParsingException { txt_action = TxtAction.SAVE; txt_action_name = eop_json.getString("txt save"); break; + case "encodings": + JSONArray encodings = eop_json.getJSONArray("encodings"); + Iterator it = encodings.iterator(); + + while (it.hasNext()) { + String act_enc = (String) it.next(); + this.encodings.add( + DecodeOperation.Encoding.fromString(act_enc)); + } + break; + case "encode": + action = MessageOperation.MessageOperationActions.ENCODE; + what = eop_json.getString("encode"); + break; + case "from": + msg_from = HTTPReqRes.MessageSection.fromString(eop_json.getString("from")); + break; + case "edit": + action = MessageOperation.MessageOperationActions.EDIT; + what = eop_json.getString("edit"); + break; + case "edit regex": + action = MessageOperation.MessageOperationActions.EDIT_REGEX; + what = eop_json.getString("edit regex"); + break; + case "add": + action = MessageOperation.MessageOperationActions.ADD; + what = eop_json.getString("add"); + break; + case "remove": + action = MessageOperation.MessageOperationActions.REMOVE_PARAMETER; + what = eop_json.getString("remove"); + break; + // todo add action of message operation actions + default: + throw new ParsingException("Invalid key \"" + key + "\" in Edit operation"); + } + } + + validate(); + } + + /** + * Validate this object's content. Used to check if the parsed tags are valid. + */ + @Override + public void validate() throws ParsingException { + if (action == MessageOperation.MessageOperationActions.ENCODE) { + if (encodings.isEmpty()) { + throw new ParsingException("Using encode in Edit Operation, but not providing encodings"); } } } @@ -165,273 +225,341 @@ public void init() { sign = false; txt_action_name = ""; what = ""; + encodings = new ArrayList<>(); } - public void loader(DecodeOperation_API dop_api) { - // TODO - if (dop_api == null) { - throw new RuntimeException("loaded api is null"); - } - this.imported_api = dop_api; + public void setAPI(Operation_API api) { + imported_api = api; } public API exporter() { return imported_api; } - public void execute(List vars) throws ParsingException { - if (imported_api instanceof DecodeOperation_API) { - // the edit operation is being executed inside a Decode Operation - // If a variable value has to be used, read the value of the variable at execution time - if (!use.equals("")) { - Var v = getVariableByName(use, vars); - if (!v.isMessage) { - value = v.value; - } else { - throw new ParsingException("Error while using variable, expected text var, got message var"); - } - } - - DecodeOperation_API tmp_imported_api = (DecodeOperation_API) imported_api; - - switch (((DecodeOperation_API) imported_api).type) { - case XML: - //SAML Remove signatures - if (self_sign | remove_signature) { - Document document = null; - try { - XMLHelpers xmlHelpers = new XMLHelpers(); - document = xmlHelpers.getXMLDocumentOfSAMLMessage(((DecodeOperation_API) imported_api).xml); - saml_original_cert = xmlHelpers.getCertificate(document.getDocumentElement()); - if (saml_original_cert == null) { - System.out.println("SAML Certificate not found in decoded parameter"); - applicable = false; - } - edited_xml = SamlTabController.removeSignature_edit(((DecodeOperation_API) imported_api).xml); - } catch (SAXException e) { - e.printStackTrace(); - } - } - - switch (xml_action) { - case ADD_TAG: - edited_xml = XML.addTag(edited_xml, - xml_tag, - xml_action_name, - value, - xml_occurrency); - break; - case ADD_ATTR: - edited_xml = XML.addTagAttribute(edited_xml, - xml_tag, - xml_action_name, - value, - xml_occurrency); - break; - case EDIT_TAG: - edited_xml = XML.editTagValue(edited_xml, - xml_action_name, - value, - xml_occurrency); - break; - case EDIT_ATTR: - edited_xml = XML.editTagAttributes(edited_xml, - xml_tag, - xml_action_name, - value, - xml_occurrency); - break; - case REMOVE_TAG: - edited_xml = XML.removeTag(edited_xml, - xml_action_name, - xml_occurrency); - break; - case REMOVE_ATTR: - edited_xml = XML.removeTagAttribute(edited_xml, - xml_tag, - xml_action_name, - xml_occurrency); - break; - case SAVE_TAG: { - String to_save = XML.getTagValaue(edited_xml, - xml_action_name, - xml_occurrency); - Var v = new Var(); - v.name = save_as; - v.isMessage = false; - v.value = to_save; - vars.add(v); - break; + public void execute_decodeOperation_API(List vars) throws ParsingException { + // the edit operation is being executed inside a Decode Operation + DecodeOperation_API tmp_imported_api = (DecodeOperation_API) imported_api; + + switch (((DecodeOperation_API) imported_api).type) { + case XML: + //SAML Remove signatures + if (self_sign | remove_signature) { + Document document = null; + try { + XMLHelpers xmlHelpers = new XMLHelpers(); + document = xmlHelpers.getXMLDocumentOfSAMLMessage(((DecodeOperation_API) imported_api).xml); + saml_original_cert = xmlHelpers.getCertificate(document.getDocumentElement()); + if (saml_original_cert == null) { + System.out.println("SAML Certificate not found in decoded parameter"); + applicable = false; } - case SAVE_ATTR: - String to_save = XML.getTagAttributeValue(edited_xml, - xml_tag, xml_action_name, - xml_occurrency); - Var v = new Var(); - v.name = save_as; - v.isMessage = false; - v.value = to_save; - vars.add(v); - break; + edited_xml = SamlTabController.removeSignature_edit(((DecodeOperation_API) imported_api).xml); + } catch (SAXException e) { + e.printStackTrace(); } + } - if (self_sign && !edited_xml.equals("")) { - // SAML re-sign - edited_xml = SamlTabController.resignAssertion_edit(edited_xml, saml_original_cert); + switch (xml_action) { + case ADD_TAG: + edited_xml = XML.addTag(edited_xml, + xml_tag, + xml_action_name, + value, + xml_occurrency); + break; + case ADD_ATTR: + edited_xml = XML.addTagAttribute(edited_xml, + xml_tag, + xml_action_name, + value, + xml_occurrency); + break; + case EDIT_TAG: + edited_xml = XML.editTagValue(edited_xml, + xml_action_name, + value, + xml_occurrency); + break; + case EDIT_ATTR: + edited_xml = XML.editTagAttributes(edited_xml, + xml_tag, + xml_action_name, + value, + xml_occurrency); + break; + case REMOVE_TAG: + edited_xml = XML.removeTag(edited_xml, + xml_action_name, + xml_occurrency); + break; + case REMOVE_ATTR: + edited_xml = XML.removeTagAttribute(edited_xml, + xml_tag, + xml_action_name, + xml_occurrency); + break; + case SAVE_TAG: { + String to_save = XML.getTagValaue(edited_xml, + xml_action_name, + xml_occurrency); + Var v = new Var(); + v.name = save_as; + v.isMessage = false; + v.value = to_save; + vars.add(v); + break; } + case SAVE_ATTR: + String to_save = XML.getTagAttributeValue(edited_xml, + xml_tag, xml_action_name, + xml_occurrency); + Var v = new Var(); + v.name = save_as; + v.isMessage = false; + v.value = to_save; + vars.add(v); + break; + } - tmp_imported_api.xml = edited_xml; - applicable = true; - break; + if (self_sign && !edited_xml.equals("")) { + // SAML re-sign + edited_xml = SamlTabController.resignAssertion_edit(edited_xml, saml_original_cert); + } - case JWT: - if (jwt_section != null) { // if only sign, there will be no jwt section - try { - switch (jwt_section) { - case HEADER: - tmp_imported_api.jwt.header = Tools.editJson( - jwt_action, tmp_imported_api.jwt.header, what, vars, save_as, value); - break; - case PAYLOAD: - // TODO: pass newvalue - tmp_imported_api.jwt.payload = Tools.editJson( - jwt_action, tmp_imported_api.jwt.payload, what, vars, save_as, value); - break; - case SIGNATURE: - tmp_imported_api.jwt.signature = Tools.editJson( - jwt_action, tmp_imported_api.jwt.signature, what, vars, save_as, value); - break; - } - } catch (PathNotFoundException e) { - this.applicable = false; - this.result = false; - return; + tmp_imported_api.xml = edited_xml; + applicable = true; + break; + + case JWT: + if (jwt_section != null) { // if only sign, there will be no jwt section + try { + switch (jwt_section) { + case HEADER: + tmp_imported_api.jwt.header = Tools.editJson( + jwt_action, tmp_imported_api.jwt.header, what, vars, save_as, value); + break; + case PAYLOAD: + // TODO: pass newvalue + tmp_imported_api.jwt.payload = Tools.editJson( + jwt_action, tmp_imported_api.jwt.payload, what, vars, save_as, value); + break; + case SIGNATURE: + tmp_imported_api.jwt.signature = Tools.editJson( + jwt_action, tmp_imported_api.jwt.signature, what, vars, save_as, value); + break; } - applicable = true; - } else if (sign) { - applicable = true; - tmp_imported_api.jwt.sign = true; - tmp_imported_api.jwt.private_key_pem = jwt_private_key_pem; - } else { - throw new ParsingException("missing jwt section in Edit operation"); + } catch (PathNotFoundException e) { + this.applicable = false; + this.result = false; + return; } + applicable = true; + } else if (sign) { + applicable = true; + tmp_imported_api.jwt.sign = true; + tmp_imported_api.jwt.private_key_pem = jwt_private_key_pem; + } else { + throw new ParsingException("missing jwt section in Edit operation"); + } - break; - - case NONE: - Pattern p = Pattern.compile(Pattern.quote(txt_action_name)); - Matcher m = p.matcher(tmp_imported_api.txt); - - if (txt_action == null) { - throw new ParsingException("txt action not specified"); - } + break; - switch (txt_action) { - case REMOVE: - tmp_imported_api.txt = m.replaceAll(""); + case NONE: + Pattern p = Pattern.compile(Pattern.quote(txt_action_name)); + Matcher m = p.matcher(tmp_imported_api.txt); - break; - case EDIT: - tmp_imported_api.txt = m.replaceAll(value); + if (txt_action == null) { + throw new ParsingException("txt action not specified"); + } + switch (txt_action) { + case REMOVE: + tmp_imported_api.txt = m.replaceAll(""); + + break; + case EDIT: + tmp_imported_api.txt = m.replaceAll(value); + + break; + case ADD: + while (m.find()) { + int index = m.end(); + String before = tmp_imported_api.txt.substring(0, index); + String after = tmp_imported_api.txt.substring(index); + tmp_imported_api.txt = before + value + after; break; - case ADD: - while (m.find()) { - int index = m.end(); - String before = tmp_imported_api.txt.substring(0, index); - String after = tmp_imported_api.txt.substring(index); - tmp_imported_api.txt = before + value + after; - break; - } + } + break; + case SAVE: + String val = ""; + while (m.find()) { + val = m.group(); break; - case SAVE: - String val = ""; - while (m.find()) { - val = m.group(); - break; - } + } - Var v = new Var(); - v.name = save_as; - v.isMessage = false; - v.value = val; - vars.add(v); - break; - } - applicable = true; - break; - } - imported_api = tmp_imported_api; - } else if (imported_api instanceof Operation_API) { - HTTPReqRes message = ((Operation_API) imported_api).message; - boolean is_request = ((Operation_API) imported_api).is_request; // if the message to edit is the request - HTTPReqRes.MessageSection from = HTTPReqRes.MessageSection.HEAD; - MessageOperation.MessageOperationActions action = MessageOperation.MessageOperationActions.EDIT; - - switch (from) { - case URL: { - switch (action) { - case REMOVE_PARAMETER: - message.removeUrlParam(what); - break; - case REMOVE_MATCH_WORD: - // TODO - break; - case EDIT: - message.editUrlParam(what, value); - break; - case EDIT_REGEX: - // TODO - break; - case ADD: - message.addUrlParam(what, value); - break; - case ENCODE: - String old_value = message.getUrlParam(what); - String new_value = ""; //TODO - message.editUrlParam(what, new_value); - break; - } - break; + Var v = new Var(); + v.name = save_as; + v.isMessage = false; + v.value = val; + vars.add(v); + break; } + applicable = true; + break; + } + imported_api = tmp_imported_api; + } - case HEAD: { - switch (action) { - case REMOVE_PARAMETER: - message.removeHeadParameter(is_request, what); - break; - case REMOVE_MATCH_WORD: - // TODO - break; - case EDIT: - message.editHeadParam(is_request, what, value); - break; - case EDIT_REGEX: - // TODO - break; - case ADD: - message.addHeadParameter(is_request, what, value); - break; - } - break; + public void execute_Operation_API() throws ParsingException { + HTTPReqRes message = ((Operation_API) imported_api).message; + boolean is_request = ((Operation_API) imported_api).is_request; // if the message to edit is the request + + switch (msg_from) { + case URL: { + if (!is_request) { + throw new RuntimeException("trying to access the URL of a response message"); } - case BODY: { - switch (action) { - //TODO - } - break; + switch (action) { + case REMOVE_PARAMETER: + message.removeUrlParam(what); + break; + case REMOVE_MATCH_WORD: + // TODO: remove, can be done with edit regex + break; + case EDIT: + message.editUrlParam(what, value); + break; + case EDIT_REGEX: + String old_url = message.getUrl(); + Pattern p = Pattern.compile(what); + Matcher m = p.matcher(old_url); + String new_url = m.replaceAll(value); + message.setRequest_url(new_url); + break; + case ADD: + message.addUrlParam(what, value); + break; + case ENCODE: + String old_value = message.getUrlParam(what); + String new_value = DecodeOperation.encode( + encodings, + old_value + ); + message.editUrlParam(what, new_value); + break; } + break; + } + + case HEAD: { + switch (action) { + case REMOVE_PARAMETER: + message.removeHeadParameter(is_request, what); + break; + case REMOVE_MATCH_WORD: + // TODO: remove, can be done with edit regex + break; + case EDIT: + message.editHeadParam(is_request, what, value); + break; + case EDIT_REGEX: + // For each header applies regex + message.getHeaders(is_request).replaceAll(header -> { + Pattern p = Pattern.compile(what); + Matcher m = p.matcher(header); + header = m.replaceAll(value); + return header; + }); + break; + case ADD: + message.addHeadParameter(is_request, what, value); + break; + case ENCODE: + String old_value = message.getHeadParam(is_request, what); + String new_value = DecodeOperation.encode( + encodings, + old_value + ); + message.editHeadParam(is_request, what, new_value); + break; + } + break; + } - case RAW: { - switch (action) { + case BODY: { + switch (action) { + // TODO add also edits based on Content-Type? + case REMOVE_PARAMETER: + // nothing + break; + case REMOVE_MATCH_WORD: + // nothing + break; + case EDIT: + // nothing + break; + case EDIT_REGEX: + // edit matched value + message.editBodyRegex(is_request, what, value); + break; + case ADD: + // append value + message.addBody(is_request, value); + break; + case ENCODE: + // encode matched value + String old_value = message.getBodyRegex(is_request, what); + String new_value = DecodeOperation.encode( + encodings, + old_value + ); + message.editBodyRegex(is_request, what, new_value); + break; + } + break; + } + + case RAW: { + switch (action) { + //TODO + case REMOVE_PARAMETER: + break; + case REMOVE_MATCH_WORD: + break; + case EDIT: + break; + case EDIT_REGEX: + // TODO + break; + case ADD: + break; + case ENCODE: //TODO - } - break; + break; } + break; + } + } + applicable = true; // check if there is a better place for this + ((Operation_API) imported_api).message = message; + } + + public void execute(List vars) throws ParsingException { + // If a variable value has to be used, read the value of the variable at execution time + if (!use.equals("")) { + Var v = getVariableByName(use, vars); + if (!v.isMessage) { + value = v.value; + } else { + throw new ParsingException("Error while using variable, expected text var, got message var"); } + } - ((Operation_API) imported_api).message = message; + if (imported_api instanceof DecodeOperation_API) { + execute_decodeOperation_API(vars); + } else if (imported_api instanceof Operation_API) { + execute_Operation_API(); } } diff --git a/tool/src/main/java/migt/ExecuteActiveListener.java b/tool/src/main/java/migt/ExecuteActiveListener.java index 3487a4c..a339a12 100644 --- a/tool/src/main/java/migt/ExecuteActiveListener.java +++ b/tool/src/main/java/migt/ExecuteActiveListener.java @@ -2,8 +2,6 @@ /** * Listener class for ExecuteActive class - * - * @author Matteo Bitussi */ public interface ExecuteActiveListener { diff --git a/tool/src/main/java/migt/ExecuteActives.java b/tool/src/main/java/migt/ExecuteActives.java index 6ef2ff7..5b74a61 100644 --- a/tool/src/main/java/migt/ExecuteActives.java +++ b/tool/src/main/java/migt/ExecuteActives.java @@ -8,8 +8,6 @@ /** * Class which executes actives tests, has to be run as a thread - * - * @author Matteo Bitussi */ public class ExecuteActives implements Runnable { final Object waiting; // the lock on which the thread will wait diff --git a/tool/src/main/java/migt/ExecutePassives.java b/tool/src/main/java/migt/ExecutePassives.java index 411832b..427fecf 100644 --- a/tool/src/main/java/migt/ExecutePassives.java +++ b/tool/src/main/java/migt/ExecutePassives.java @@ -9,8 +9,6 @@ /** * Class used to execute passive tests, it implements Runnable, it should be executed as a Thread. To communicate with * the tread you can use the ExecutePassivesListener listener class. - * - * @author Matteo Bitussi */ public class ExecutePassives implements Runnable { final Object lock = new Object(); diff --git a/tool/src/main/java/migt/ExecuteTrack.java b/tool/src/main/java/migt/ExecuteTrack.java index 4094273..908c35c 100644 --- a/tool/src/main/java/migt/ExecuteTrack.java +++ b/tool/src/main/java/migt/ExecuteTrack.java @@ -15,8 +15,6 @@ /** * Class that executes a Session Track (series of user actions). It launches a browser with Selenium to automate the * actions - * - * @author Matteo Bitussi */ public class ExecuteTrack implements Runnable { private static String snapshot = ""; diff --git a/tool/src/main/java/migt/ExecuteTrackListener.java b/tool/src/main/java/migt/ExecuteTrackListener.java index df85888..f341947 100644 --- a/tool/src/main/java/migt/ExecuteTrackListener.java +++ b/tool/src/main/java/migt/ExecuteTrackListener.java @@ -2,8 +2,6 @@ /** * Listener for the ExectuteTrack Object - * - * @author Matteo Bitussi */ public interface ExecuteTrackListener { diff --git a/tool/src/main/java/migt/GUI.java b/tool/src/main/java/migt/GUI.java index ef01a0d..832c1a4 100644 --- a/tool/src/main/java/migt/GUI.java +++ b/tool/src/main/java/migt/GUI.java @@ -43,9 +43,6 @@ public void write(int b) { /** * This class contains the GUI for the plugin, also a lot of functionality methods - * - * @author Matteo Bitussi - * @author Wendy Barreto */ public class GUI extends JSplitPane { private static DefaultTableModel resultTableModel; diff --git a/tool/src/main/java/migt/HTTPReqRes.java b/tool/src/main/java/migt/HTTPReqRes.java index e152bda..d591ba3 100644 --- a/tool/src/main/java/migt/HTTPReqRes.java +++ b/tool/src/main/java/migt/HTTPReqRes.java @@ -21,8 +21,6 @@ /** * Class which is intended to substitute the IHTTPRequestResponse one, because of serialization support - * - * @author Matteo Bitussi */ public class HTTPReqRes implements Cloneable { static public int instances; @@ -161,11 +159,24 @@ public void setUrlHeader(String url_header) { this.headers_req.set(0, url_header); } + /** + * returns true if the message has the body + * + * @param isRequest select the request or the response message + * @return true if it has body, false otherwise + */ + public boolean hasBody(boolean isRequest) { + if (isRequest && this.body_offset_req == 0) { + return false; + } + return isRequest || this.body_offset_resp != 0; + } + public byte[] getBody(boolean isRequest) { - if (isRequest && (this.body_offset_req == 0 | this.request == null | this.request.length == 0)) { + if (isRequest && (!this.hasBody(isRequest) | this.request == null | this.request.length == 0)) { throw new RuntimeException("called getBody, but class is not properly initialized"); } - if (!isRequest && (this.body_offset_resp == 0 | this.response == null | this.response.length == 0)) { + if (!isRequest && (!this.hasBody(isRequest) | this.response == null | this.response.length == 0)) { throw new RuntimeException("called getBody, but class is not properly initialized"); } @@ -185,21 +196,6 @@ public List getHeaders(boolean isRequest) { return isRequest ? this.headers_req : this.headers_resp; } - /** - * Used to build the message based on the changes made - */ - private byte[] build_message(IExtensionHelpers helpers, boolean isRequest) { - // TODO: this could be written avoiding helpers class - // TODO: url is not updated - if (isRequest) { - this.request = helpers.buildHttpMessage(headers_req, getBody(true)); - return this.request; - } else { - this.response = helpers.buildHttpMessage(headers_resp, getBody(false)); - return this.response; - } - } - /** * Builds the message taking the headers and the body, without using the burp's helpers. * @@ -238,29 +234,18 @@ public byte[] build_message(boolean isRequest) { * @param isRequest to specify the request or the response * @return the message */ - public byte[] getMessage(boolean isRequest, IExtensionHelpers helpers) { + public byte[] getMessage(boolean isRequest) { if (isRequest && this.request == null) { throw new RuntimeException("Called getMessage on a message that is not initialized"); } else if (!isRequest && this.response == null) { throw new RuntimeException("Called getMessage on a message that is not initialized"); } - build_message(helpers, isRequest); + build_message(isRequest); - return isRequest ? request : response; - } - - /** - * Get the message without updating it with the changes - * - * @param isRequest - * @return - */ - public byte[] getMessage(boolean isRequest) { - // TODO: this is probably a source of bugs, called without noticing that it doesnt get the updated message - if (isRequest && this.request == null) { - throw new RuntimeException("Called getMessage on a message that is not initialized"); - } else if (!isRequest && this.response == null) { - throw new RuntimeException("Called getMessage on a message that is not initialized"); + if (isRequest) { + request = build_message(isRequest); + } else { + response = build_message(isRequest); } return isRequest ? request : response; @@ -333,7 +318,12 @@ public String getRequest_url() { } public void setRequest_url(String request_url) { - this.request_url = request_url; + if (this.request_url == null) { + this.request_url = request_url; + } else { + this.request_url = request_url; + updateHeadersWHurl(); + } } public String getHost() { @@ -441,7 +431,7 @@ public void editUrlParam(String param, String value) throws ParsingException { } request_url = request_url.replaceAll( - java.util.regex.Matcher.quoteReplacement(url.getQuery()), + "\\Q" + java.util.regex.Matcher.quoteReplacement(url.getQuery()) + "\\E", new_query); updateHeadersWHurl(); @@ -492,7 +482,7 @@ public void removeUrlParam(String name) throws ParsingException { } request_url = request_url.replaceAll( - java.util.regex.Matcher.quoteReplacement(url.getQuery()), + "\\Q" + java.util.regex.Matcher.quoteReplacement(url.getQuery()) + "\\E", new_query); updateHeadersWHurl(); @@ -531,7 +521,7 @@ public void addUrlParam(String name, String value) { } request_url = request_url.replaceAll( - java.util.regex.Matcher.quoteReplacement(url.getQuery()), + "\\Q" + java.util.regex.Matcher.quoteReplacement(url.getQuery()) + "\\E", new_query); updateHeadersWHurl(); @@ -622,11 +612,11 @@ public void removeHeadParameter(boolean isRequest, String name) { * everything matched will be returned as a value * * @param isRequest if the message is a request - * @param param the parameter to be searched as a regex, everything matched by this will be returned as a value + * @param regex the parameter to be searched as a regex, everything matched by this will be returned as a value * @return the value of the parameter */ - public String getBodyRegex(Boolean isRequest, String param) { - Pattern pattern = Pattern.compile(param); + public String getBodyRegex(Boolean isRequest, String regex) { + Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(new String(getBody(isRequest), StandardCharsets.UTF_8)); String res = ""; @@ -637,6 +627,43 @@ public String getBodyRegex(Boolean isRequest, String param) { return res; } + /** + * Edit the body of the message. Replaces the matched content of the regex with the new value. + * + * @param isRequest select the request or response message + * @param regex the regex to execute + * @param new_value the new value to substitute + */ + public void editBodyRegex(boolean isRequest, String regex, String new_value) { + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(new String(getBody(isRequest), StandardCharsets.UTF_8)); + + String new_body = matcher.replaceFirst(new_value); + setBody(isRequest, new_body); + } + + /** + * Append to the body of the message the given value. If the message doesn't have a body it creates it. + * + * @param isRequest select the request or response message + * @param new_value the value to append + */ + public void addBody(boolean isRequest, String new_value) { + if (hasBody(isRequest)) { + String body = new String(getBody(isRequest)); + body += new_value; + setBody(isRequest, body); + } else { + if (isRequest) { + body_offset_req = 1; // this is for recognizing body in hasBody method + body_req = new_value.getBytes(StandardCharsets.UTF_8); + } else { + body_offset_resp = 1; // this is for recognizing body in hasBody method + body_resp = new_value.getBytes(StandardCharsets.UTF_8); + } + } + } + /** * Updates the headers in this request message with the acctual url value */ @@ -652,6 +679,10 @@ public void updateHeadersWHurl() throws RuntimeException { throw new RuntimeException(e); } + if (headers_req.isEmpty()) { + throw new RuntimeException("Message headers not properly initialized"); + } + String[] header_0 = headers_req.get(0).split(" "); String new_header_0 = header_0[0] + " " + url.getPath(); @@ -719,6 +750,17 @@ public boolean matches_msg_type(MessageType msg_type) { return matchedMessage; } + /** + * Returns a string representation of all the headers of the message + * + * @param isRequest select the request or the response + * @return + */ + public String getHeadersString(boolean isRequest) { + List headers_string = getHeaders(isRequest); + return String.join("\r\n", headers_string); + } + /** * An enum representing the possible message sections */ diff --git a/tool/src/main/java/migt/JWT.java b/tool/src/main/java/migt/JWT.java index b3c8a15..f72739a 100644 --- a/tool/src/main/java/migt/JWT.java +++ b/tool/src/main/java/migt/JWT.java @@ -15,7 +15,6 @@ /** * Class to manage JWT tokens * Uses https://connect2id.com/products/nimbus-jose-jwt - * {@code @Author} Matteo Bitussi */ public class JWT { public String header; diff --git a/tool/src/main/java/migt/Marker.java b/tool/src/main/java/migt/Marker.java index 2309f5a..752bb1f 100644 --- a/tool/src/main/java/migt/Marker.java +++ b/tool/src/main/java/migt/Marker.java @@ -4,8 +4,6 @@ /** * Class used to mark User Actions to be managed by session actions - * - * @author Matteo Bitussi */ public class Marker { String name; diff --git a/tool/src/main/java/migt/MessageOperation.java b/tool/src/main/java/migt/MessageOperation.java index f91ca8a..5a83b1f 100644 --- a/tool/src/main/java/migt/MessageOperation.java +++ b/tool/src/main/java/migt/MessageOperation.java @@ -16,8 +16,6 @@ /** * The class storing a MessageOperation object - * - * @author Matteo Bitussi */ public class MessageOperation extends Module { HTTPReqRes.MessageSection from; @@ -182,12 +180,12 @@ public Operation execute(Operation op, matcher = pattern.matcher(url_header); String new_url = matcher.replaceFirst(""); op.api.message.setUrlHeader(new_url); - op.processed_message = op.api.message.getMessage(op.api.is_request, helpers); + op.processed_message = op.api.message.getMessage(op.api.is_request); break; case HEAD: op.api.message.removeHeadParameter(op.api.is_request, mop.what); - op.processed_message = op.api.message.getMessage(op.api.is_request, helpers); + op.processed_message = op.api.message.getMessage(op.api.is_request); break; case BODY: @@ -196,7 +194,7 @@ public Operation execute(Operation op, matcher = pattern.matcher(body); op.api.message.setBody(op.api.is_request, matcher.replaceAll("")); //Automatically update content-lenght - op.processed_message = op.api.message.getMessage(op.api.is_request, helpers); + op.processed_message = op.api.message.getMessage(op.api.is_request); break; } break; @@ -209,7 +207,7 @@ public Operation execute(Operation op, switch (mop.from) { case HEAD: { op.api.message.addHeadParameter(op.api.is_request, mop.what, getAdding(mop, op.api.vars)); - op.processed_message = op.api.message.getMessage(op.api.is_request, helpers); + op.processed_message = op.api.message.getMessage(op.api.is_request); break; } case BODY: { @@ -217,7 +215,7 @@ public Operation execute(Operation op, tmp = tmp + getAdding(mop, op.api.vars); op.api.message.setBody(op.api.is_request, tmp); //Automatically update content-lenght - op.processed_message = op.api.message.getMessage(op.api.is_request, helpers); + op.processed_message = op.api.message.getMessage(op.api.is_request); break; } case URL: @@ -238,7 +236,7 @@ public Operation execute(Operation op, found = true; } op.api.message.setUrlHeader(newHeader_0); - op.processed_message = op.api.message.getMessage(op.api.is_request, helpers); + op.processed_message = op.api.message.getMessage(op.api.is_request); break; } break; @@ -277,7 +275,7 @@ public Operation execute(Operation op, } op.api.message.setHeaders(op.api.is_request, new_headers); - op.processed_message = op.api.message.getMessage(op.api.is_request, helpers); + op.processed_message = op.api.message.getMessage(op.api.is_request); break; } case BODY: { @@ -286,7 +284,7 @@ public Operation execute(Operation op, op.api.message.setBody(op.api.is_request, matcher.replaceAll("")); //Automatically update content-lenght - op.processed_message = op.api.message.getMessage(op.api.is_request, helpers); + op.processed_message = op.api.message.getMessage(op.api.is_request); break; } case URL: @@ -301,7 +299,7 @@ public Operation execute(Operation op, String newHeader_0 = matcher.replaceFirst(""); op.api.message.setUrlHeader(newHeader_0); - op.processed_message = op.api.message.getMessage(op.api.is_request, helpers); + op.processed_message = op.api.message.getMessage(op.api.is_request); break; } break; @@ -412,7 +410,8 @@ public enum MessageOperationActions { EDIT_REGEX, ADD, SAVE, - SAVE_MATCH; + SAVE_MATCH, + ENCODE; /** * From a string get the corresponding enum value @@ -438,8 +437,10 @@ public static MessageOperationActions fromString(String input) throws ParsingExc return SAVE; case "save match": return SAVE_MATCH; + case "encode": + return ENCODE; default: - throw new ParsingException("invalid check operation"); + throw new ParsingException("invalid Message operation action \"" + input + "\""); } } else { throw new NullPointerException(); diff --git a/tool/src/main/java/migt/MessageType.java b/tool/src/main/java/migt/MessageType.java index b07d331..4c9b5d8 100644 --- a/tool/src/main/java/migt/MessageType.java +++ b/tool/src/main/java/migt/MessageType.java @@ -5,8 +5,6 @@ /** * Class storing a MessageType - * - * @author Matteo Bitussi */ public class MessageType implements Cloneable { String name; diff --git a/tool/src/main/java/migt/Module.java b/tool/src/main/java/migt/Module.java index a4ccc74..e17bcc1 100644 --- a/tool/src/main/java/migt/Module.java +++ b/tool/src/main/java/migt/Module.java @@ -34,9 +34,7 @@ public Module(IExtensionHelpers helpers) { /** * This function should be called to check that after an initialization of a module all the necessary parameters - * are set correctly. - * - * @return + * are set correctly. And the JSON has been parsed correctly with all the required tags present. */ public void validate() throws ParsingException { @@ -63,7 +61,10 @@ public void setAPI(API api) { * @param api the imported API */ public void loader(API api) { - + if (api == null) { + throw new RuntimeException("loaded api is null"); + } + this.imported_api = api; } /** diff --git a/tool/src/main/java/migt/Operation.java b/tool/src/main/java/migt/Operation.java index dacad3c..b143353 100644 --- a/tool/src/main/java/migt/Operation.java +++ b/tool/src/main/java/migt/Operation.java @@ -13,8 +13,6 @@ /** * Class storing an Operation in a Test - * - * @author Matteo Bitussi */ public class Operation extends Module { public List messageOperations; @@ -34,6 +32,7 @@ public class Operation extends Module { public List session_operations; // Decode operations public List decodeOperations; + public List editOperations; // Session operation // API Operation_API api; @@ -140,6 +139,11 @@ public Operation(JSONObject operation_json, decodeOperations.add(decode_op); } } + + // Edit operations + if (operation_json.has("edits")) { + editOperations = Tools.parseEditsFromJSON(operation_json.getJSONArray("edits")); + } } private void init() { @@ -151,6 +155,7 @@ private void init() { this.session_operations = new ArrayList<>(); this.log_messages = new ArrayList<>(); this.decodeOperations = new ArrayList<>(); + editOperations = new ArrayList<>(); this.from_session = ""; this.to_session = ""; this.save_name = ""; @@ -446,7 +451,10 @@ public void execute() { // execute the message operations and the decode ops try { applicable = true; - executeMessageOperations(this, helpers); // TOOD: change to edits + executeMessageOperations(this, helpers); + if (!applicable | !result) + return; + executeEditOps(this, api.vars); if (!applicable | !result) return; executeDecodeOps(this, helpers, api.vars); diff --git a/tool/src/main/java/migt/ParsingException.java b/tool/src/main/java/migt/ParsingException.java index 9afda57..4083c35 100644 --- a/tool/src/main/java/migt/ParsingException.java +++ b/tool/src/main/java/migt/ParsingException.java @@ -2,8 +2,6 @@ /** * Exception raised when the parsing of the language fails - * - * @author Matteo Bitussi */ public class ParsingException extends Exception { /** diff --git a/tool/src/main/java/migt/Session.java b/tool/src/main/java/migt/Session.java index 1f173e7..0b5ee2f 100644 --- a/tool/src/main/java/migt/Session.java +++ b/tool/src/main/java/migt/Session.java @@ -10,8 +10,6 @@ /** * Class to manage Sessions - * - * @author Matteo Bitussi */ public class Session { // Session actions diff --git a/tool/src/main/java/migt/SessionOperation.java b/tool/src/main/java/migt/SessionOperation.java index ead878d..9d9e6e1 100644 --- a/tool/src/main/java/migt/SessionOperation.java +++ b/tool/src/main/java/migt/SessionOperation.java @@ -11,8 +11,6 @@ /** * Class containing a session Operation - * - * @author Matteo Bitussi */ public class SessionOperation { public String from_session; diff --git a/tool/src/main/java/migt/SessionTrackAction.java b/tool/src/main/java/migt/SessionTrackAction.java index 92b6a09..aa201c7 100644 --- a/tool/src/main/java/migt/SessionTrackAction.java +++ b/tool/src/main/java/migt/SessionTrackAction.java @@ -6,8 +6,6 @@ /** * This class represents an user action in a session - * - * @author Matteo Bitussi */ public class SessionTrackAction { public SessionOperation.SessAction action; diff --git a/tool/src/main/java/migt/Test.java b/tool/src/main/java/migt/Test.java index 074ef13..0331357 100644 --- a/tool/src/main/java/migt/Test.java +++ b/tool/src/main/java/migt/Test.java @@ -17,8 +17,6 @@ /** * Class to store a test - * - * @author Matteo Bitussi */ public class Test { public ResultType result; diff --git a/tool/src/main/java/migt/TestSuite.java b/tool/src/main/java/migt/TestSuite.java index 5740d2e..ae780c3 100644 --- a/tool/src/main/java/migt/TestSuite.java +++ b/tool/src/main/java/migt/TestSuite.java @@ -5,8 +5,6 @@ /** * Class to store a TestSuite - * - * @author Matteo Bitussi */ public class TestSuite { String name; diff --git a/tool/src/main/java/migt/Tools.java b/tool/src/main/java/migt/Tools.java index 60b9b95..1c1444e 100644 --- a/tool/src/main/java/migt/Tools.java +++ b/tool/src/main/java/migt/Tools.java @@ -19,8 +19,6 @@ /** * Class with methods to process messages and execute tests - * - * @author Matteo Bitussi */ public class Tools { /** @@ -194,6 +192,26 @@ public static DecodeOperation executeEditOps(DecodeOperation op, return op; } + /** + * Executes the edit operations inside a standard Operation + * + * @param op the Operation to run the edit operations from + * @return the Operation (edited) + * @throws ParsingException if something goes wrong + */ + public static Operation executeEditOps(Operation op, List vars) throws ParsingException { + Operation_API api = op.getAPI(); + for (EditOperation eop : op.editOperations) { + eop.loader(api); + eop.execute(vars); + if (!op.setResult(eop)) + break; + op.setAPI((Operation_API) eop.exporter()); + } + + return op; + } + public static Operation executeMessageOperations(Operation op, IExtensionHelpers helpers) throws ParsingException { for (MessageOperation mop : op.messageOperations) { mop.loader(op.api); @@ -668,7 +686,7 @@ public static byte[] editMessage(IExtensionHelpers helpers, new_head.add(matcher.replaceAll(new_value)); } messageInfo.setHeaders(isRequest, new_head); - return messageInfo.getMessage(isRequest, helpers); + return messageInfo.getMessage(isRequest); case BODY: pattern = Pattern.compile(regex); @@ -676,7 +694,7 @@ public static byte[] editMessage(IExtensionHelpers helpers, matcher = pattern.matcher(new String(messageInfo.getBody(isRequest))); messageInfo.setBody(isRequest, matcher.replaceAll(new_value)); //Automatically update content-lenght - return messageInfo.getMessage(isRequest, helpers); + return messageInfo.getMessage(isRequest); case URL: if (!isRequest) { @@ -688,7 +706,7 @@ public static byte[] editMessage(IExtensionHelpers helpers, String replaced = matcher.replaceAll(new_value); messageInfo.setUrlHeader(replaced); - return messageInfo.getMessage(isRequest, helpers); + return messageInfo.getMessage(isRequest); } return null; } @@ -719,7 +737,7 @@ public static byte[] editMessageParam(IExtensionHelpers helpers, switch (message_section) { case HEAD: messageInfo.editHeadParam(isRequest, param_name, new_value); - byte[] message = messageInfo.getMessage(isRequest, helpers); + byte[] message = messageInfo.getMessage(isRequest); messageInfo.setHost(new_value); // this should be set when the message is converted to the burp class return message; @@ -734,7 +752,7 @@ public static byte[] editMessageParam(IExtensionHelpers helpers, String new_body = matcher.replaceFirst(new_value); messageInfo.setBody(isRequest, new_body); //Automatically update content-lenght - return messageInfo.getMessage(isRequest, helpers); + return messageInfo.getMessage(isRequest); case URL: if (!isRequest) { @@ -747,7 +765,7 @@ public static byte[] editMessageParam(IExtensionHelpers helpers, messageInfo.setUrlHeader(matcher.replaceAll(param_name + "=" + new_value)); // problema - return messageInfo.getMessage(isRequest, helpers); + return messageInfo.getMessage(isRequest); } return null; } diff --git a/tool/src/main/java/migt/Var.java b/tool/src/main/java/migt/Var.java index 0815b64..e6e1575 100644 --- a/tool/src/main/java/migt/Var.java +++ b/tool/src/main/java/migt/Var.java @@ -4,8 +4,6 @@ /** * The class storing the variables used in the test and sessions - * - * @author Matteo Bitussi */ public class Var { public String name; diff --git a/tool/src/main/java/migt/XML.java b/tool/src/main/java/migt/XML.java index 53905cf..464d7bc 100644 --- a/tool/src/main/java/migt/XML.java +++ b/tool/src/main/java/migt/XML.java @@ -20,8 +20,6 @@ /** * Class used to parse and edit xml strings - * - * @author Matteo Bitussi */ public class XML { diff --git a/tool/src/test/java/EditOperation_test.java b/tool/src/test/java/EditOperation_test.java new file mode 100644 index 0000000..35db897 --- /dev/null +++ b/tool/src/test/java/EditOperation_test.java @@ -0,0 +1,132 @@ +import migt.EditOperation; +import migt.HTTPReqRes; +import migt.Operation_API; +import migt.ParsingException; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class EditOperation_test { + HTTPReqRes message = HTTPReqRes_Test.initMessage_ok(); + HTTPReqRes message_w_body; + + @Test + public void test_encode_url_param() throws ParsingException { + String input = "{\"from\": \"url\", \"encode\": \"format\"," + "\"encodings\": [\"base64\"]}"; + EditOperation eop = new EditOperation(new JSONObject(input)); + Operation_API api = new Operation_API(message, true); + + eop.setAPI(api); + eop.execute(null); + assertTrue(eop.getResult()); + Operation_API res = (Operation_API) eop.exporter(); + assertEquals("anNvbg==", res.message.getUrlParam("format")); + } + + @Test + public void test_encode_head_param() throws ParsingException { + String input = "{\"from\": \"head\", \"encode\": \"Host\"," + "\"encodings\": [\"base64\"]}"; + EditOperation eop = new EditOperation(new JSONObject(input)); + Operation_API api = new Operation_API(message, true); + + eop.setAPI(api); + eop.execute(null); + assertTrue(eop.getResult()); + Operation_API res = (Operation_API) eop.exporter(); + assertEquals("cGxheS5nb29nbGUuY29t", res.message.getHeadParam(true, "Host")); + } + + @Test + public void test_encode_body_param() throws ParsingException { + String input = "{\"from\": \"body\", \"encode\": \".*\"," + "\"encodings\": [\"base64\"]}"; + EditOperation eop = new EditOperation(new JSONObject(input)); + Operation_API api = new Operation_API(message, true); + + eop.setAPI(api); + eop.execute(null); + assertTrue(eop.getResult()); + Operation_API res = (Operation_API) eop.exporter(); + assertEquals("Ym9keWNvbnRlbnQ=", new String(res.message.getBody(true))); + } + + @Test + public void test_edit_url_regex() throws ParsingException { + String input = "{\"from\": \"url\", \"edit regex\": \"format=json\"," + "\"value\": \"test=testone\"}"; + EditOperation eop = new EditOperation(new JSONObject(input)); + Operation_API api = new Operation_API(message, true); + + eop.setAPI(api); + eop.execute(null); + assertTrue(eop.getResult()); + Operation_API res = (Operation_API) eop.exporter(); + assertEquals("testone", res.message.getUrlParam("test")); + } + + @Test + public void test_edit_head_regex() throws ParsingException { + String input = "{\"from\": \"head\", \"edit regex\": \"Host:\"," + "\"value\": \"Hosted:\"}"; + EditOperation eop = new EditOperation(new JSONObject(input)); + Operation_API api = new Operation_API(message, true); + + eop.setAPI(api); + eop.execute(null); + assertTrue(eop.getResult()); + Operation_API res = (Operation_API) eop.exporter(); + assertEquals("play.google.com", res.message.getHeadParam(true, "Hosted")); + } + + @Test + public void test_edit_body_regex() throws ParsingException { + String input = "{\"from\": \"body\", \"edit regex\": \"ent\"," + "\"value\": \"123\"}"; + EditOperation eop = new EditOperation(new JSONObject(input)); + Operation_API api = new Operation_API(message, true); + + eop.setAPI(api); + eop.execute(null); + assertTrue(eop.getResult()); + Operation_API res = (Operation_API) eop.exporter(); + assertEquals("bodycont123", new String(res.message.getBody(true))); + } + + @Test + public void test_add_url_param() throws ParsingException { + String input = "{\"from\": \"url\", \"add\": \"codechallenge\"," + "\"value\": \"12345\"}"; + EditOperation eop = new EditOperation(new JSONObject(input)); + Operation_API api = new Operation_API(message, true); + + eop.setAPI(api); + eop.execute(null); + assertTrue(eop.getResult()); + Operation_API res = (Operation_API) eop.exporter(); + assertEquals("12345", res.message.getUrlParam("codechallenge")); + } + + @Test + public void test_add_head_param() throws ParsingException { + String input = "{\"from\": \"head\", \"add\": \"Magicheader\"," + "\"value\": \"123123\"}"; + EditOperation eop = new EditOperation(new JSONObject(input)); + Operation_API api = new Operation_API(message, true); + + eop.setAPI(api); + eop.execute(null); + assertTrue(eop.getResult()); + Operation_API res = (Operation_API) eop.exporter(); + assertEquals("123123", res.message.getHeadParam(true,"Magicheader")); + } + + @Test + public void test_add_body() throws ParsingException { + String input = "{\"from\": \"body\", \"add\": \"anything\"," + "\"value\": \"&appended\"}"; + EditOperation eop = new EditOperation(new JSONObject(input)); + Operation_API api = new Operation_API(message, true); + + eop.setAPI(api); + eop.execute(null); + assertTrue(eop.getResult()); + Operation_API res = (Operation_API) eop.exporter(); + assertEquals("bodycontent&appended", + new String(res.message.getBody(true))); + } +} diff --git a/tool/src/test/java/HTTPReqRes_Test.java b/tool/src/test/java/HTTPReqRes_Test.java index 8790627..58c564b 100644 --- a/tool/src/test/java/HTTPReqRes_Test.java +++ b/tool/src/test/java/HTTPReqRes_Test.java @@ -14,7 +14,7 @@ public class HTTPReqRes_Test { - public HTTPReqRes initMessage_ok() { + public static HTTPReqRes initMessage_ok() { String raw = "POST /log?format=json&hasfast=true&authuser=0 HTTPS/2\r\n" + "Host: play.google.com\r\n" + "Cookie: CONSENT=PENDING+392; SOCS=CAISHAgCEhJnd3NfMjAyMzAyMjgtMF9SQzIaAml0IAEaBgiA2pSgBg; AEC=AUEFqZdSS4hmP6dNNRrldXefJFuHK2ldiLrZLJG24hUqaFA2L0jJxZwSBA; NID=511=SPj3DZBbWBMVstxl414okznEMUOaUHRzxZehEHxoaTi0Fr_X9RQ6UmFDBvI6wWn1Iivh7lzi_q7Ktri2q8hHc9nVY3XNgQP-IQ4AHNz7lCKra72IjxzhBvEBQFdXy7lEaIVC3wK5TfPIXLX3TWhKwrZAVEg77UkqV2oHYohcSXg\r\n" + @@ -57,6 +57,46 @@ public HTTPReqRes initMessage_ok() { return message; } + @Test + public HTTPReqRes init_message_no_body() { + String raw = "POST /log?format=json&hasfast=true&authuser=0 HTTPS/2\r\n" + + "Host: play.google.com\r\n" + + "Cookie: CONSENT=PENDING+392; SOCS=CAISHAgCEhJnd3NfMjAyMzAyMjgtMF9SQzIaAml0IAEaBgiA2pSgBg; AEC=AUEFqZdSS4hmP6dNNRrldXefJFuHK2ldiLrZLJG24hUqaFA2L0jJxZwSBA; NID=511=SPj3DZBbWBMVstxl414okznEMUOaUHRzxZehEHxoaTi0Fr_X9RQ6UmFDBvI6wWn1Iivh7lzi_q7Ktri2q8hHc9nVY3XNgQP-IQ4AHNz7lCKra72IjxzhBvEBQFdXy7lEaIVC3wK5TfPIXLX3TWhKwrZAVEg77UkqV2oHYohcSXg\r\n" + + "Content-Length: 11\r\n" + + "Sec-Ch-Ua: \"Chromium\";v=\"111\", \"Not(A:Brand\";v=\"8\"\r\n" + + "Content-Type: application/x-www-form-urlencoded;charset=UTF-8\r\n" + + "X-Goog-Authuser: 0\r\n" + + "Sec-Ch-Ua-Mobile: ?0\r\n" + + "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.5563.65 Safari/537.36\r\n" + + "Sec-Ch-Ua-Platform: \"Linux\"\r\n" + + "Accept: */*\r\n" + + "Origin: https://www.google.com\r\n" + + "X-Client-Data: CNiKywE=\r\n" + + "Sec-Fetch-Site: same-site\r\n" + + "Sec-Fetch-Mode: cors\r\n" + + "Sec-Fetch-Dest: empty\r\n" + + "Referer: https://www.google.com/\r\n" + + "Accept-Encoding: gzip, deflate\r\n" + + "Accept-Language: en-US,en;q=0.9\r\n" + + "\r\n"; + + List headers = new ArrayList<>(); + + Collections.addAll(headers, raw.split("\r\n")); + + byte[] raw_b = raw.getBytes(StandardCharsets.UTF_8); + + HTTPReqRes message = new HTTPReqRes(raw_b, null); + + message.body_offset_req = 0; + message.setHeaders(true, headers); + message.isRequest = true; + message.isResponse = false; + message.setRequest_url("https://play.google.com/log?format=json&hasfast=true&authuser=0"); + + return message; + } + @Test @DisplayName("") public void test_build() { @@ -186,7 +226,7 @@ public void test_getUrlHeader() { HTTPReqRes message = initMessage_ok(); String header_0 = message.getUrlHeader(); - assertEquals("POST /log?format=json&hasfast=true&authuser=0 HTTP/2", header_0); + assertEquals("POST /log?format=json&hasfast=true&authuser=0 HTTPS/2", header_0); } @Test @@ -210,6 +250,15 @@ public void test_editUrlParam() throws ParsingException { message.setRequest_url("https://play.google.com:8080/log?format=new&hasfast=true&authuser=0#123123123"); message.editUrlParam("format", "newnew"); assertEquals("https://play.google.com:8080/log?format=newnew&hasfast=true&authuser=0#123123123", message.getUrl()); + assertEquals("POST /log?format=newnew&hasfast=true&authuser=0 HTTPS/2", message.getUrlHeader()); + } + + @Test + public void test_url_update_header_0() { + HTTPReqRes message = initMessage_ok(); + message.setRequest_url("https://play.google.com:8080/log?format=newnew&hasfast=true&authuser=0#123123123"); + assertEquals("https://play.google.com:8080/log?format=newnew&hasfast=true&authuser=0#123123123", message.getUrl()); + assertEquals("POST /log?format=newnew&hasfast=true&authuser=0 HTTPS/2", message.getUrlHeader()); } @Test @@ -277,4 +326,29 @@ public void test_removeHeadParameter() { String value = message.getHeadParam(true, "Origin"); assertEquals("", value); } + + @Test + public void test_add_body() { + // add body to a message that does not have it + HTTPReqRes message = init_message_no_body(); + assertFalse(message.hasBody(true)); + + message.addBody(true, "testbodycontent"); + + assertTrue(message.hasBody(true)); + assertEquals("testbodycontent", new String(message.getBody(true))); + + message.addBody(true, "1"); + assertEquals("testbodycontent1", new String(message.getBody(true))); + } + + @Test + public void test_edit_body_regex() { + HTTPReqRes message = initMessage_ok(); + + message.editBodyRegex(true, "conte", "1234"); + assertEquals("body1234nt", new String(message.getBody(true))); + } + + }