diff --git a/README.md b/README.md index 6ecd5fe..6205b28 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # MIG-T Pentesting Tool + MIG-T Pentesting Tool is a plugin for BurpSuite that helps security testers automate their testing activities. It allows the tester to define automations to edit or check correctnees of HTTP messages. It integrates an automated browser used to simulate user actions on a webpage, to trigger specific messages. It uses MIG-L language to define tests to be executed by MIG-T
## Quickstart +
Details We suggest you to download the lastest release of the tool from the release page, otherwise you can compile the last version from the source code by following the steps described in the "how to compile the plugin" section. @@ -10,15 +12,17 @@ We suggest you to download the lastest release of the tool from the release page 1. download from the release page the last version of the tool select the one which ends with `with-dependencies`, or compile the source code. 2. Download the last version of [Burp Suite Community Edition](https://portswigger.net/burp/releases/community/latest) -3. Start Burp and go in the *Exstensions* tab -4. Press *Add* button -5. In the *Extension file (.jar)* select the tool jar you downloaded before +3. Start Burp and go in the _Exstensions_ tab +4. Press _Add_ button +5. In the _Extension file (.jar)_ select the tool jar you downloaded before 6. Now the plugin should be loaded, go to the "MIG-T" tab ## Download and add browser driver + Depending on the browser you want to use (firefox or chrome), you will need to specify the corresponding driver. Note that you have to download the driver for the corresponding browser version To download the driver go to: + - [Driver for chrome](https://chromedriver.chromium.org/home) - [Driver for firefox](https://github.com/mozilla/geckodriver/releases) @@ -43,6 +47,7 @@ If you want to see the entire history of the messages go to "proxy" tab in Burp,

## How to compile the plugin + The project is based on maven, you have two ways of compiling it ### With IntelliJ IDEA @@ -53,6 +58,7 @@ The folder tool is an intelliJ project, if you open it with intelliJ IDEA it sho
### Without IntelliJ IDEA +
Details You don't need to use IDEA to compile the project, you can install maven, go to the project direcotry `tool` mentioned before and type @@ -72,20 +78,23 @@ Two jar will be generated: ``` You have to use the jar that has "-with-dependencies" in its name, the other will not work in burp. +

## Documentation + You can find the documentation about the language used by the tool in the `doc/` folder. The documentation about the code is not yet finished, but all the functions are documented in the code. ## Known Bugs -- Sometimes when re-executing a suite of active tests, the messages are not edited. Restart the plugin -- On windows, the re-signing of the SAML messages sometimes will fail +- On windows, the re-signing of the SAML messages sometimes will fail # External / Related projects + Extended in the context of the industrial collaboration with IPZS and Futuro&Conoscenza # License + ``` Copyright 2023, Fondazione Bruno Kessler @@ -104,7 +113,12 @@ limitations under the License. Developed within [Security & Trust](https://st.fbk.eu/) Research Unit at [Fondazione Bruno Kessler](https://www.fbk.eu/en/) (Italy) -## Other software licenses -### SAMLRaider License +## Other software + +### SAMLRaider + Some parts of the tool that manages SAML certificates has been built by using portions of SAMLRaider code (https://github.com/CompassSecurity/SAMLRaider). +### nimbus-jose-jwt + +https://connect2id.com/products/nimbus-jose-jwt diff --git a/doc/language.md b/doc/language.md index 1545e01..0095c98 100644 --- a/doc/language.md +++ b/doc/language.md @@ -29,46 +29,28 @@ A test is defined by: ## Operation -An operation could be a message interception or a session operation, it could also be a validate operation (more info down here). -The most basic and useful type of operation is the one that makes you able to intercept message that you can then edit, just specify message type and the required tags and you are ready to go. -The use of the operation tag differ based on the type of test is defined into: +An operation can be either a message interception or a session operation. The most basic and useful type of operation is message interception, which allows you to intercept messages and then edit them. To do this, you simply need to specify the message type and the required tags ### Message type -With the tag `message type` it has to be specified to which message execute the given operation. There are various possible standard types: +The message type tag specifies the message to which the given operation should be applied. Message types can be defined in the msg_def.json file, which is created in the Burp folder at the first execution of the plugin. By default, some OAuth message types are present, but you can add or modify them as needed. -- `request`, all requests -- `response`, all responses -- `oauth request`, all the oauth-related requests -- `oauth response`, all the oauth-related responses - -Other message types can be defined in the file _"msg_def.json"_ which will be created at the first execution of the plugin in the Burp folder. By default, some oauth message types are present, feel free to add or modify them. -The way a message type is defined is by the use of regex or checks (like in passive tests), the regex or checks are then evaluated over a message, and if the evaluation is successful, the message is then processed. +A message type is defined by a set of checks. These checks are evaluated against a message, and if all of the checks pass, then the message is processed by the operation. To define a message type you have to tell: - The `name` - `is request` if the message is a request or not (true, false) -- `response name` (optional) if you want to also associate a name to the response of that message (often useful when you know the request but not the response) -- `request name` (optional) if you need to intercept a message by its response, but you want do access the request, just put the name of the request to use it in the language. Note that if you intercept the response, you can not edit the request anymore, because it has already been sent. -- `regex` or `checks` list (see check section for details) - -### Operation in passive tests - -When an operation in a passive test is defined with the `message type` tag, the other possible tags are (in an exclusive way): - -- `regex`: which will specify a regex to be tested to the given `message section` in the message -- `checks`: which is a list of `check` +- `response name` (optional) if you want to also associate a name to the response of that message (often useful when you know how a request is, but not the response) +- `request name` (optional) if you need to intercept a message by its response, but you want to access the request, just put the name of the request to use it in the language. Note that if you intercept the response, you can not edit the request anymore, because it has already been sent. +- `checks` list (see check section for details) -To consider a test successful the regex has to match at least one occurrence in the specified `message section`, or all the checks has to be evaluated to true. -Note that where you are asked to insert a regex you have to backslash (\) all the regex operators such as (?{}.\*) if you need them to be searched instead of executed. +### Operation tags -### Operation in active tests - -An operation in an active test can be used with these tags: +An operation can be used with these tags: - `message type` which specifies a message type -- `action` The action to be done on this operation, can be `intercept` or `validate` or `clear cookies` +- `action` The action to be done on this operation, can be `intercept` or `clear cookies` - `from session` specify which session has to be sniffed to search for the message, default is the standard session - `then` the action to be done on the intercepted message after the execution of the operation, `forward` or `drop` (default forward) - `save` saves the intercepted message to the given variable name @@ -77,29 +59,26 @@ An operation in an active test can be used with these tags: - `message operations` List of `message operation` to do on the message. See the proper section for details - `replace request` used with the name of a variable containing a message. It replaces the request of the intercepted message with a saved message. - `replace response` used with the name of a variable containing a message. It replaces the response of the intercepted message with a saved message. +- `checks`: which is a list of `check`. To consider an operation successful all the checks has to be evaluated to true. -Note that in active tests the operations are evaluated sequencially, this means that until an operation has not been finished (i.e. the message is not arrived yet) the next operation will not be available. This eventually means that if you have more message filtering operations, only one at the time is executed. - -#### Note for the Validate action +### Differences between operations inside active and passives tests -If you use the `validate` action, the only tags you can use are the same as the passives checks, you can use: +The execution of the operations differs depending on the type of test: -- `regex` -- `checks` - The way they are used is the same as passive checks. - The purpose of the validate action is to built an oracle, in a way that if all validate operations are evaluated to true, and the result is passed, then the test is passed. Note that all the validate operations are combined with the result, see the result section for more info +- active tests: The operations are executed sequentially one after the other. This means that until an operation has not been finished (i.e. the message is not arrived yet) the next operation will not be available +- passive tests: All the operation are executed over all the messages passed trough the proxy ### Operation and session in active tests There is the possibility to handle different sessions in an active test, for example to replay messages. -A session has to be defined in the `sessions` tag in a `Test`, then it can be started with an operation with: +The sessions have to be defined in the `sessions` tag inside a `Test`, then, each session can be started or stopped with an operation having: - tag `session` associated with the name of which session is reffering to, - tag `action` the action to do on that session, like _start_, _stop_, _clear cookies_, _pause_, _resume_ -#### Note with sessions in Burp +#### Note on using sessions in Burp -Burp has to be manually configured to open more proxy ports, based on the number of contemporary sessions to be executed. To do so, go to the proxy tab in burp, options, and then add enough port for your sessions. +Each session uses a different port on the proxy, this is done to differentiate the messages inside the proxy. Burp has to be manually configured to open more proxy ports based on the number of contemporary sessions to be executed. To do so, go to the proxy tab in burp, options, and then add enough port for your sessions. You have also to link each of your sessions to a port, to do so, go to the plugin, after you read your JSON input test suite, in the session config tab write the ports you have previously defined. Note that the port has to be _different for each contemporary session_, otherwise the tests will not work. ## Message section @@ -107,15 +86,14 @@ You have also to link each of your sessions to a port, to do so, go to the plugi A message is divided in three parts - `url` only for requests, is the message whithout head and without body -- `head` is the message without body and without url -- `body` is the message without head and without url +- `head` contains all the message except the body +- `body` is the message without head ## Message Operations The message operations are operations to be done on the intercepted message in an operation. The syntax contains always the `from` tag, which specifies the message section where to search to do the given action (url, body, head). Then we have the possible actions: -- `decode parameter` where you can decode and do things on a parameter, see dedicated section - `remove match word` it removes the matched string, can be fed with a regex - `remove parameter` with value the name of the parameter to be removed. A variant of the previous, it removes the parameter searched with its value - `edit` edits the given parameter's value, the tag `in` specifies in what has to be edited @@ -126,6 +104,8 @@ The syntax contains always the `from` tag, which specifies the message section w An example could be: +Edit the parameter state inside the url of a message with the new value "newstatecode" + ```json "message operations": [ { @@ -157,43 +137,215 @@ If you choose the body section, the meaning of the tags is different, infact: - `save` is associated with a regex, it saves what is matched by that regex, saving it in a variable with name specified in `as` tag - `add`is associated with an empty string, teh value will be appended to the body, the value do add is specified with the tag `this` (or `use` in case of a value from a variable) -Note that the content lenght of the body section is automatically updated or removed +Note that the content lenght of the body section is automatically updated or removed if the content of the body is edited. -### Encoding/Decoding in message operations +## Decode operation -In message operations there is the possibility of specifing a parameter to be decoded and to be processed. The HTTP parameter containing the encoded text has to be specified with the `decode param`, if the section of the message is the body the `decode param` takes a regex (for more info see below section). Then, a list of encodings has to be specified with the tag `encoding`, the order of these encodings will be followed while decoding. Note that when the message is encoded, the order will be reversed. +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. -An example, where we take the parameter `SAMLRequest` from a message's url, and then specifing the encodings (url, base64 and deflate). +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). -```json -"message operations": [ - { - "from": "url", - "decode param": "SAMLRequest", - "encoding": [ - "url", - "base64", - "deflate" - ], - "type": "xml", - "xml tag": "samlp:AuthnRequest", - "edit attribute": "ID", - "value": "newIDValue" - } -] -``` +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 -### Specific actions for decoded parameters +#### 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" + } + ] + } +] +``` + +### Using Edit Operation in Decode Operations + +It is possible to edit the decoded content of decode Ops by using edit Operations. A list of edit Operations has to be specified. When using Edit Op. inside Decode Operations, depending on the `type` specified in Decode Operations, you can use different keys. + +- `type`: JWT + - `jwt from` specifies the section of the jwt to edit (header, payload, signature) + - `jwt edit` JSON path of the key to edit + - `jwt remove` JSON path of the key to remove + - `jwt add` JSON path to the key to add + `value` with name + - `jwt save` JSON path to the key to save + - `value` used with edit or add, to specify the value of the key +- `type`: XML + - see next XML section +- no `type` specified, (treated as plain text): + - Using `txt remove` the matched text will be removed from the text + - Using `txt edit` the matched text will be edited with the text specified in `value` tag + - Using `txt add` the text specified in `value` will be inserted at the end of the matched text + - Using `txt save` the matched text will be saved in a variable having the name specified in tag `as` + +#### JWT type + +This type is used to edit decoded JWT tokens. This way is possible to edit, add, save or resign them. The possible actions are: + +- `jwt from` Used to specify the section of the token to execute the given action, choose btween: + - `header` + - `payload` + - `signature` +- `jwt remove` If used on signature removes the entire signature +- `jwt edit` `value` If used on signature edits the entire signature. +- `jwt add` `value` +- `jwt save` `as` if used on sinature saves the entire signature +- `jwt sign` Specifying a PEM-encoded private key that will be used to sign the jwt + +##### Note on signing + +When signing a jwt, the supported algorithms are: + +- RS256 +- RS512 + +##### Example -There is the possibility of manipulating the decoded parameter, various types of languages-types are available, like xml. -The type of the decoded param has to be specified with tag `type`. +Edit the scope claim inside an OAuth request jwt -> Note that for decoded parameters standard message operation actions will not work. +```json +"decode operations": [ + { + "from": "url", + "type": "jwt", + "encodings": [], + "decode param": "(?<=authz_request_object=)[^$\n& ]*", + "edits": [ + { + "jwt from": "payload", + "jwt edit": "$.scope", + "value": "wrong_scope" + } + ] + } +] +``` + +Check the scope claim inside an OAuth request jwt + +```json +"decode operations": [ + { + "from": "body", + "decode param": "(?<=authz_request_object=)[^$\n& ]*", + "encodings": [], + "type": "jwt", + "checks": [ + { + "in": "payload", + "check": "$.scope", + "is": "openid" + } + ] + } +] +``` #### XML type @@ -230,18 +382,22 @@ An example: ```json "message operations": [ - { - "from": "url", - "decode param": "SAMLRequest", - "encoding": [ - "url", - "base64", - "deflate" - ], - "type": "xml", + { + "from": "url", + "decode param": "SAMLRequest", + "encoding": [ + "url", + "base64", + "deflate" + ], + "type": "xml", + "edits": [ + { "edit tag": "samlp:AuthnRequest", "value": "new tag value" - } + } + ] + } ] ``` @@ -255,40 +411,23 @@ in the tag name you have to specify "Something:apple" #### TXT type -It is a type used to edit, remove, add, or save pieces of a decoded param that is treated as a text. -You hae to specify a regex using the parameter "txt ..." with the associated action, the possible usages are: +It is a type used to edit, remove, add, or save pieces of a decoded content that is treated as a text. +You have to specify a regex using one of the following tags with the associated action, the possible usages are: - Using `txt remove` the matched text will be removed from the text - Using `txt edit` the matched text will be edited with the text specified in `value` tag - Using `txt add` the text specified in `value` will be inserted at the end of the matched text - Using `txt save` the matched text will be saved in a variable having the name specified in tag `as` -#### JWT type - -Thi type is used to edit decoded JWT tokens. This way is possible to edit, add, save or resign them. The possible actions are: - -- `jwt from` Used to specify the section of the token to execute the given action, choose btween: - - `header` - - `payload` - - `signature` - - `raw header` - - `raw payload` - - `raw signature` -- `jwt remove` If used on signature removes the entire signature -- `jwt edit` `value` If used on signature edits the entire signature. -- `jwt add` `value` -- `jwt save` `as` if used on sinature saves the entire signature -- `jwt sign` Used to sign the jwt with another invalid key. - #### Note for using saved variables It is possible to use the tag `use` instead of the tag `value` to use the text saved in the variable specified using the `use` tag. #### SAML signature -There's the possibility of 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. Another possibility is just to remove the signature, using `remove signature` set to true in the message operation. -Note that this keys are avaiable and applied only on decoded parameters, also if `decode param` is defined. +Note that these keys are avaiable and applied only on decoded parameters, also if `decode param` is defined. ## Session Operation @@ -324,8 +463,6 @@ Note that during insertion, the element inserted is always positioned after the } ``` -Not sure about this ^^^ , not yet implemented ^^^^ - ```json "session operation": [ { @@ -365,48 +502,106 @@ Not sure about this ^^^ , not yet implemented ^^^^ ## Checks and Check -The Checks tag is a list of Check elements, which can be defined with: +The Checks tag inside an operation has a list of Check elements, which can be defined with: - `in` says were to check the given parameter, can be _head_, _body_, _url_ -- `check` checks if the given string is present in the specified message section +- `check` checks if the given string is present in the specified message section. - `check param` specifies the name of the parameter to be checked, depending on the section choosed, the tool will search for the parameter using a pattern. (for the url, it will search for a query parameter, for the head, it will search for a head parameter) -- The actual check on the value, which are self explanatory. (if none of these are specified, the check will only check if the given parameter is present) +- `check regex` specify a regex that checks the selected content by matching it. + . `use variable` (true or false) set to true if you want to specify a variable name on the following tags, to check wrt to that variable value. +- `url decode` if you want to disable url decoding in http messages, see the note below for details. +- The actual check on the value. (if none of these are specified, the check will only check if the given parameter is present) - `is` - `not is` - `contains` - `not contains` - `is present` specifying true or false, to check whether is present or not + - `is in` the value is between a list of values + - `is not in` the value is not between a list of values + - `is subset of` used to check that a matched JSON array is a subset of the given array. Is subset means that all the values in the matched array are present in the given array. + - `matches regex` when using `check param` or `check` in json, you can use this tag to execute a regex to the matched value of the parameter or the value of the json key specified with the jsonpath + - `not matches regex` as `match regex` but the result is true when the regex doesn't match -you can use `check` OR `check param` tag. If you use the `check` tag, you can use all the other tags to verify the value, otherwise, if you use `check param` you can just use `is present`. +> Note that you can use `check regex` OR `check` OR `check param`. + +> Note that `check` accepts only the `is present` tag. + +> Note that by default, all the values read from a message (only message, not json) are URL-decoded before the checks are executed. You can disable this behaviour by using `url decode` = false In passive tests the checks's result are intended as the entire test result, so all the checks has to pass to have a successfull test. +### Checks on JSON content + +In case a check operation is executed inside an operation that gives a JSON as an output (e.g. decode operations with type=jwt), the check operation is enabled to use JSON paths to identify keys and values specified with `check` tag. The `in` tag specifies the section of the JWT (header, payload, signature). Note that signature is treated as plain text. + +Note: when using `check regex` with json content, the specified content will be treated as plain text. e.g the header of a jwt will be treated as plain text, and the regex will be executed over the entire header. + +>Note: matched array contents will always be converted to string for simplicity. This means that if you want to check an integer or float, you should write it as a string (e.g. 12.3 -> "12.3") + +```json +"decode operations": [ + { + "tagsofadecode": "decodejwt", + "checks": [ + { + "in": "header", + "check": "jsonpath", + "is": "something" + } + ] + } +] +``` + +#### Note on JSON values types + +When checking the value of json keys, you have to consider the type of the value. + +- mig-t will convert each int or float type as string, this means that when using the check operators, you should always use a string, not an int or a double. +- checking a value that is a JSON Array is only allowed when using the `contains` or `not contains` or `is subset of` operators +- When matching a JSON Array, the values of the array are all converted to string + ### Note for the active tests -If you need to do a check on an active test, you have to do a `validate` operation, which is basically an operation when you can do checks and regex +If you need to do a check on an active test, you have to do a `validate` operation, which is basically an operation where you can do checks + +### Examples + +Check using a variable value: check that the value of the header "Host" is equal to the value of the variable "var1" + +```json +"checks" : [ + { + "in": "head", + "check param": "Host", + "use variable": true, + "is": "var1" + } +] +``` ## Preconditions -Preconditions are used in an operation of an active test to check something in the intercepted message before the execution of the message operations. If the checks in the preconditions are evaluated to false, the test is considered unsupported, not failed. Basically preconditions are a list of checks. +Preconditions are used in an operation of an active test to check something in the intercepted message before the execution of the rest of the operation. If the preconditions are evaluated to false, the test is considered unsupported, not failed. The preconditions are basically a list of checks. To use a precondition just write ```json "preconditions" : [] ``` -filling the list wit the checks or regex you need. -Over the list of check or regex a AND operation is made, so all of the checks (or regex) in the list has to be successful to continue the test. +filling the list wit the checks you need. +> Note: Over the list of check or regex a AND operation is made, so all of the checks (or regex) in the list has to be successful to continue the test. ## Save -A message or a string can be saved with this tag. It can be used both in an operation, to save a message, and in a _Message Operation_ to save the value of a found parameter. +A message or a string can be saved with this tag. It can be used both in an operation, to save a message, in a _Message Operation_ to save the value of a found parameter and also inside Edit operations. - `Save` associated with the name of the variable There are two ways of using the value of a variable, depending of its type: - Using a message-type variable: it can be used in an operation with the tag `action` set to intercept there is the possibility of use `replace request` (or `replace response`) with the name of the variable. This way the intercepted message's request (or response) is replaced with the specified variable value. Note that when a message is replaced, all the message operations in that operation will be ignored. -- Using a string-type variable: can be used in Message Operations, where you have to add or edit a parameter's value, writing `use` and the name of the variable +- Using a string-type variable: can be used in Message Operations or Edit Operations, where you have to add or edit a parameter's value, writing `use` and the name of the variable
When saving and using variables, take care to assign a variable before its use Note that when saving a variable, if the value is empty (no match or no parameter found), the MessageOperation (edit, add, ..) where the variable is used will not be executed, the execution will continue without errors. @@ -421,8 +616,8 @@ it can be set to: - `incorrect flow \[sessionname\]` opposite of `correct flow`, the test succedes only if there is an error - `assert_only` the test result ignores the validation of the session flow but gives a result depending on the assertions defined in the track. This means, that if the execution of the session fails, the result will not take it into account. -The result can be combined with the result of the checks or regex in an operation with action set to `validate`. -The succes is evaluated with the Boolean operator AND between the result and all the validates +The result can be combined with the result of the checks in an operation. +The succes is evaluated with the Boolean operator AND between the result and all the results of the operations Note that if correct (or incorrect) flow is used without specifying a session name, all the sessions are checked. @@ -434,7 +629,7 @@ Note that if you are filling a field where a regex is expected, you have to back ## Test examples -### Example of with active tests +### Example of active tests ```json { @@ -449,7 +644,7 @@ Note that if you are filling a field where a regex is expected, you have to back "name": "PKCE plain method", "description": "Finds an authRequest and remove the parameter code_challenge_method", "type": "active", // active test type - "sessions": ["main"], + "sessions": ["s1"], "operations": [ // List of operations to do { @@ -474,8 +669,8 @@ Note that if you are filling a field where a regex is expected, you have to back "result": "correct flow" // it has to be a correct flow for a successful attack }, "test": { - "name": "PKCE plain method", - "description": "Finds an authRequest and remove the parameter code_challenge_method", + "name": "replay message parameter to other message", + "description": "Find the parameter state in the auth. request in s1 and replace it in the authorization request in s2", "type": "active", "sessions": ["s1", "s2"], "operations": [ @@ -541,7 +736,7 @@ Note that if you are filling a field where a regex is expected, you have to back ### Example of with passive tests -#### Example 1: PKCE is used +#### Example 1: PKCE is used (TO BE CHANGED TO NEW LANGUAGE) This passive test checks whether PKCE is used in an OAuth flow, checking if the authorization grant message contains the parameters "code_challenge" or "code_challenge_method", which are necessary to use PKCE. More precisely, the test: @@ -555,8 +750,7 @@ This passive test checks whether PKCE is used in an OAuth flow, checking if the { "test suite": { "name": "Test Suite 01", - "description": "Only Passive Test", - "metadata": true + "description": "Only Passive Test" }, "tests": [ { @@ -579,7 +773,7 @@ This passive test checks whether PKCE is used in an OAuth flow, checking if the } ``` -#### Other passive tests +#### Other passive tests (TO BE CHANGED TO NEW LANGUAGE) ```json { @@ -711,10 +905,25 @@ Examples:
`assert element content is | xpath=/body/div/label | text to match`
`assert element class has | xpath=/body/... | class_to_match`
-### snapshot - -// TODO - -### Setvar - -// TODO +# Changelog + +## v1.4.0 + +- Added decode operations +- Removed "decode parameter" from Message Operation +- Added checks in decode operations +- Added support for Checks to work with JSON content +- Changed tag `encoding` in `encodings` +- Added edit Operation +- Moved tags used to modify decoded content in Message Operations to Edit Operation inside Decode Operation +- Removed `raw header` `raw payload` `raw signature` from `jwt from` tag in Decode Operation +- Added supprot of regex in checks (in future they will substitute existing regex) +- Remove support for hardcoded standard message types such as oauth request and oauth response +- Removed support for hardcoded identification of OAuth flow +- removed `request`, `response`, `oauth request` `oauth response` from Message type +- removed `regex` and `message section` tag from Message Type, now only checks are available (that contains also regex) +- removed `regex` from Operation in passive tests +- Removed validate Operation, now just use checks inside an intercept Operation +- 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 diff --git a/doc/progress_tracker.md b/doc/progress_tracker.md index 3b8f516..8870218 100644 --- a/doc/progress_tracker.md +++ b/doc/progress_tracker.md @@ -2,128 +2,128 @@ ## TO-DO -- [x] Add messages file as input, instead of the execution of the track -- [x] Learn the oracle from the previous implementation -- [x] Define the **variable saving** in the language -- [x] Define in the language some session-conrol tag, such as: pause, stop, run, resume, clear cookies -- [x] Define in the language the possible actions associated to the intercept tag (drop, forward,..) -- [x] Add a tab in the plugin with the metadata of the OAuth authentication server -- [x] Define all the basic operations for the language -- [x] Add the buttons to save and load a saved message track for passive tests -- [x] Add the possibility of negating the search of a string in a check (if string not present) -- [x] Test the functionality of the GUI and the logic of the buttons -- [x] Comment code -- [x] Add and implement the message type "oauth request" "oauth response" -- [x] Adapt old code to the new parsing for static tests -- [x] Implement passive tests with new language -- [x] Add the visualization of messages that maked fail the test -- [x] Define sessions and active test in the markdown -- [x] Define all passive tests in JSON -- [x] Define all the classes for storing the parsed objects -- [x] Implement active tests with new language -- [x] Add new message types for filtering messages (define them) -- [x] Implement the different ports -- [x] Implement the oracle using checks/regex in actives -- [x] Define the oracle for active tests -- [x] Implement the pause of the session -- [x] Add message definition JSON -- [x] Write documentation for language (actives, msgtype JSON, ..) -- [x] Write documentation for code -- [x] Move the association of the port to the session out of the json, in a tab of the GUI -- [x] Make invalid the URL section in the response -- [x] For the body section, make possible the use of a regex to find and do (add, substitute,...) -- [x] Add encode/ decode functionality to operations -- [x] Add JWT token as a message section, implement the decoding -- [x] Multiple operation execution to be tested -- [x] Implement XML operations -- [x] Finish and test XML Operation execution -- [x] resign assertion instead of message -- [x] resign with the incoming certificate -- [x] Add a command to specify a delay in the web drivertrough the track -- [x] Passive tests with SAML are ok if failing, but if they should pass there is an error -- [x] Add an option in the track to wait tot seconds, and add more selecting options to click (i.e. classname) -- [x] add decoded param section to passives tests (checks and regex) -- [x] fix getBodyParameter() -- [x] In Decode parameter, when the section is body, use a regex instead of a parameter (as in other body sections) -- [x] Add clear cookies functionality both by language or by track -- [x] Add a note to the documentation saying that when typing something in a field that accepts regex, the regex operators should be backslashed (\?) -- [x] Implement the saving of the position of the last used driver, in a way that it automatically use it -- [x] Build a graphic visualization of the language components -- [x] Remove the session tabs if changed in the json, and fix other session tabs issues -- [x] Verify that is possible to save a decoded parameter: wiith txt save -- [x] Verify that is possible (or find a way) to save pieces of a decoded parameter -- [x] Verify that is possible to add a saved variable over a decoded parameter -- [x] Implement "use" of saved txt in decoded parameters operations -- [x] Implement the removing of the SAML signature without the re-sign -- [x] Move all dependencies to maven -- [x] Fix deprecated dependency having conflicts (org.apache.xml.serialize.OutputFormat and org.apache.xml.serialize.XMLSerializer) -- [x] Session configuration tab won't add classnames -- [x] Xml tag actions, how to deal with multiple tags having the same name? add a variable to identify which occurency to edit -- [x] Fix the fact that message operations don't keep the changes made on the message -- [x] Keep track if there are active executions, if not, clear variables -- [x] Define and fix windows problem -- [x] Remove debug options in the code (default track/driver/ecc) -- [x] Release a stable version -- [x] Edit the selection of the URL, this method should return the entire URL, not the first row of the message. The headers should contain the first row. -- [x] Sometimes if the browser cannot continue the session track, the test result is not displayed -- [x] Reload msg_def file before reading the test suite -- [x] Set the sessions proxy ports to default sequential value -- [x] Fix the starting dimension of the browser, if it starts with small dimensions it could not allow some types of buttons to be shown (and pressed). -- [x] Add a listener method for ExecuteTrack that deals with Errors that are not related to the execution of the track (for example an error on the driver selection) -- [x] Implement a better message drop -- [x] Fix the host editing not working -- [x] Regex: add message section "raw" (all message) -- [x] Add or edit content type when adding body in message Operations (BurpExtender@921) -- [x] the last actions are currently not filtered based on the session they are made -- [x] Fix save actions from-to, atm the save action cannot be empty, a new target has to be implemented -- [x] Last_url does not take the right last url, it is one url after. -- [x] implement the adding of things to the track when starting a session -- [x] Add the decode of a parameter in passives -- [x] Add columns to test results (Test Name | Test Description | References | Violated Properties | Mitigation |) -- [ ] Add oracle description on the test suite -- [ ] Add check's from option, from decoded param, in a way that checks could be executed on a decoded param -- [ ] Improve the error feedback on the plugin, for example by highlighting the part of the plugin that failed or by writing a string - - [x] JSON Test track - - [x] Tabs of JSON and sessions configs to higlhight red in case of errors - - [ ] Browser track -- [ ] Check the add action in message operations -- [ ] sessions list in test are updated incorrectly -- [ ] Add the anti-proxybypass localhost rule to firefox -- [ ] JWT edit and add is applicable only to root keys in the jwt JSON, it is not possible to edit the child elements with these commands. -- [ ] JWT raw string methods are not jet implemented -- [ ] Implement new session objects in the test -- [ ] If session is stopped before the update of the track, the session operations does not have any effect -- [ ] sessions list in test are updated incorrectly -- [ ] Improve error feedbacks with detailed informations -- [ ] Intermittent problem: burp.ParsingException: variable "var_host" not defined. It could be caused by a race condition between threads, has to be checked -- [ ] Add an assert on the session to check the text content of an element in the page -- [ ] Parse the jwt sections as JSON to avoid inaccessibility of child elements (?) -- [ ] Add an optional session action (if the element is not found skip the action without counting as an error) -- [ ] Add assert text inside element -- [ ] Add assert color -- [ ] Think of a way to inject cookies in sessions -- [ ] Add a button in the tool to resume the execution after a certain pause action, for example to solve captcha -- [ ] Allow passives tests to be executed on a session different from mainSession +- [x] Add messages file as input, instead of the execution of the track +- [x] Learn the oracle from the previous implementation +- [x] Define the **variable saving** in the language +- [x] Define in the language some session-conrol tag, such as: pause, stop, run, resume, clear cookies +- [x] Define in the language the possible actions associated to the intercept tag (drop, forward,..) +- [x] Add a tab in the plugin with the metadata of the OAuth authentication server +- [x] Define all the basic operations for the language +- [x] Add the buttons to save and load a saved message track for passive tests +- [x] Add the possibility of negating the search of a string in a check (if string not present) +- [x] Test the functionality of the GUI and the logic of the buttons +- [x] Comment code +- [x] Add and implement the message type "oauth request" "oauth response" +- [x] Adapt old code to the new parsing for static tests +- [x] Implement passive tests with new language +- [x] Add the visualization of messages that maked fail the test +- [x] Define sessions and active test in the markdown +- [x] Define all passive tests in JSON +- [x] Define all the classes for storing the parsed objects +- [x] Implement active tests with new language +- [x] Add new message types for filtering messages (define them) +- [x] Implement the different ports +- [x] Implement the oracle using checks/regex in actives +- [x] Define the oracle for active tests +- [x] Implement the pause of the session +- [x] Add message definition JSON +- [x] Write documentation for language (actives, msgtype JSON, ..) +- [x] Write documentation for code +- [x] Move the association of the port to the session out of the json, in a tab of the GUI +- [x] Make invalid the URL section in the response +- [x] For the body section, make possible the use of a regex to find and do (add, substitute,...) +- [x] Add encode/ decode functionality to operations +- [x] Add JWT token as a message section, implement the decoding +- [x] Multiple operation execution to be tested +- [x] Implement XML operations +- [x] Finish and test XML Operation execution +- [x] resign assertion instead of message +- [x] resign with the incoming certificate +- [x] Add a command to specify a delay in the web drivertrough the track +- [x] Passive tests with SAML are ok if failing, but if they should pass there is an error +- [x] Add an option in the track to wait tot seconds, and add more selecting options to click (i.e. classname) +- [x] add decoded param section to passives tests (checks and regex) +- [x] fix getBodyParameter() +- [x] In Decode parameter, when the section is body, use a regex instead of a parameter (as in other body sections) +- [x] Add clear cookies functionality both by language or by track +- [x] Add a note to the documentation saying that when typing something in a field that accepts regex, the regex operators should be backslashed (\?) +- [x] Implement the saving of the position of the last used driver, in a way that it automatically use it +- [x] Build a graphic visualization of the language components +- [x] Remove the session tabs if changed in the json, and fix other session tabs issues +- [x] Verify that is possible to save a decoded parameter: wiith txt save +- [x] Verify that is possible (or find a way) to save pieces of a decoded parameter +- [x] Verify that is possible to add a saved variable over a decoded parameter +- [x] Implement "use" of saved txt in decoded parameters operations +- [x] Implement the removing of the SAML signature without the re-sign +- [x] Move all dependencies to maven +- [x] Fix deprecated dependency having conflicts (org.apache.xml.serialize.OutputFormat and org.apache.xml.serialize.XMLSerializer) +- [x] Session configuration tab won't add classnames +- [x] Xml tag actions, how to deal with multiple tags having the same name? add a variable to identify which occurency to edit +- [x] Fix the fact that message operations don't keep the changes made on the message +- [x] Keep track if there are active executions, if not, clear variables +- [x] Define and fix windows problem +- [x] Remove debug options in the code (default track/driver/ecc) +- [x] Release a stable version +- [x] Edit the selection of the URL, this method should return the entire URL, not the first row of the message. The headers should contain the first row. +- [x] Sometimes if the browser cannot continue the session track, the test result is not displayed +- [x] Reload msg_def file before reading the test suite +- [x] Set the sessions proxy ports to default sequential value +- [x] Fix the starting dimension of the browser, if it starts with small dimensions it could not allow some types of buttons to be shown (and pressed). +- [x] Add a listener method for ExecuteTrack that deals with Errors that are not related to the execution of the track (for example an error on the driver selection) +- [x] Implement a better message drop +- [x] Fix the host editing not working +- [x] Regex: add message section "raw" (all message) +- [x] Add or edit content type when adding body in message Operations (BurpExtender@921) +- [x] the last actions are currently not filtered based on the session they are made +- [x] Fix save actions from-to, atm the save action cannot be empty, a new target has to be implemented +- [x] Last_url does not take the right last url, it is one url after. +- [x] implement the adding of things to the track when starting a session +- [x] Add the decode of a parameter in passives +- [x] Add columns to test results (Test Name | Test Description | References | Violated Properties | Mitigation |) +- [ ] Add oracle description on the test suite +- [ ] Add check's from option, from decoded param, in a way that checks could be executed on a decoded param +- [ ] Improve the error feedback on the plugin, for example by highlighting the part of the plugin that failed or by writing a string + - [x] JSON Test track + - [x] Tabs of JSON and sessions configs to higlhight red in case of errors + - [ ] Browser track +- [ ] Check the add action in message operations +- [ ] sessions list in test are updated incorrectly +- [ ] Add the anti-proxybypass localhost rule to firefox +- [ ] JWT edit and add is applicable only to root keys in the jwt JSON, it is not possible to edit the child elements with these commands. +- [ ] JWT raw string methods are not jet implemented +- [ ] Implement new session objects in the test +- [ ] If session is stopped before the update of the track, the session operations does not have any effect +- [ ] sessions list in test are updated incorrectly +- [ ] Improve error feedbacks with detailed informations +- [ ] Intermittent problem: burp.ParsingException: variable "var_host" not defined. It could be caused by a race condition between threads, has to be checked +- [ ] Add an assert on the session to check the text content of an element in the page +- [ ] Parse the jwt sections as JSON to avoid inaccessibility of child elements (?) +- [ ] Add an optional session action (if the element is not found skip the action without counting as an error) +- [ ] Add assert text inside element +- [ ] Add assert color +- [ ] Think of a way to inject cookies in sessions +- [ ] Add a button in the tool to resume the execution after a certain pause action, for example to solve captcha +- [ ] Allow passives tests to be executed on a session different from mainSession ## Discarded -- [ ] Implement the delay test for token expiration -- [ ] Add server's metadata specific passive checks -- [ ] divide the self self-sign Assertion or Message command in language -- [ ] ??? Fix operation.applicable on all message operations (now is just at the first) -- [ ] Save last JSON test in config.json and reload it at the next startup -- [ ] Add to the documentation how to disable chrome's warnings over non secure connections (chrome://flags/#allow-insecure-localhost) enable +- [ ] Implement the delay test for token expiration +- [ ] Add server's metadata specific passive checks +- [ ] divide the self self-sign Assertion or Message command in language +- [ ] ??? Fix operation.applicable on all message operations (now is just at the first) +- [ ] Save last JSON test in config.json and reload it at the next startup +- [ ] Add to the documentation how to disable chrome's warnings over non secure connections (chrome://flags/#allow-insecure-localhost) enable ## IDEAS -- Think about a way to specify the OR in the check -- Define the replay tag as a macro of elementar operations -- Define some type of test where given a timeout, a token (or other?) is tested to see if is still valid waiting for the timeout. -- make json to specify the checks to be done associated with a message type, i.e. Authentication Request -- A JSON config file, like for the filterings -- Should remove parameter be available to the body section?? -- In message section "all" when deleting a param, if the name of a parameter is present more of one time, what should i do? delete them all? -- New Actives oracle make sense? -- In actives, in message operations, is it really useful to apply an action to the entire message? -- in actives, in message operations, what are the possible params? url parameters and HTTP parameters? or url parameters inside the value of an HTTP parameter too? -- In actives, in message operations, in ADD, what should i add to BODY? +- Think about a way to specify the OR in the check +- Define the replay tag as a macro of elementar operations +- Define some type of test where given a timeout, a token (or other?) is tested to see if is still valid waiting for the timeout. +- make json to specify the checks to be done associated with a message type, i.e. Authentication Request +- A JSON config file, like for the filterings +- Should remove parameter be available to the body section?? +- In message section "all" when deleting a param, if the name of a parameter is present more of one time, what should i do? delete them all? +- New Actives oracle make sense? +- In actives, in message operations, is it really useful to apply an action to the entire message? +- in actives, in message operations, what are the possible params? url parameters and HTTP parameters? or url parameters inside the value of an HTTP parameter too? +- In actives, in message operations, in ADD, what should i add to BODY? diff --git a/templates/msg_def_template.json b/templates/msg_def_template.json index 1c4b997..d9a2149 100644 --- a/templates/msg_def_template.json +++ b/templates/msg_def_template.json @@ -1,211 +1,67 @@ { - "message_types": [ + "message_types": [ + { + "name": "authorization request", + "is request": true, + "response name": "authorization response", + "checks": [ { - "name": "authorization request", - "is request": true, - "response name": "authorization response", - "checks": [ - { - "in": "url", - "check param": "response_type", - "is present": "true" - } - ] - }, - { - "name": "token request", - "is request": true, - "response name": "token response", - "checks": [ - { - "in": "url", - "check param": "code", - "is present": "true" - } - ] - }, - { - "name": "coda landing request", - "is request": true, - "response name": "coda landing response", - "checks": [ - { - "in": "url", - "check": "/welcome", - "is present": "true" - }, - { - "in": "head", - "check param": "Host", - "is": "coda.io" - } - ] - }, - { - "name": "saml request", - "is request": true, - "checks": [ - { - "in": "url", - "check param": "SAMLRequest", - "is present": true - } - ] - }, - { - "name": "saml response", - "is request": true, - "checks": [ - { - "in": "body", - "check param": "SAMLResponse", - "is present": true - } - ] - }, - { - "name": "fb_login", - "is request": true, - "response name": "fb_login_resp", - "checks": [ - { - "in": "url", - "check": "/auth/facebook-login-callback", - "is present": true - }, - { - "in": "url", - "check param": "code", - "is present": true - } - ] - }, - { - "name": "fb_sso", - "is request": true, - "checks": [ - { - "in": "url", - "check": "login.php", - "is present": true - }, - { - "in": "head", - "check param": "Host", - "contains": "facebook" - } - ] - }, - { - "name": "nytimes_settings_page", - "is request": true, - "checks": [ - { - "in": "url", - "check": "/seg/settings", - "is present": true - }, - { - "in": "head", - "check param": "Host", - "contains": "nytimes" - } - ] - }, - { - "name": "nytimes_accoun_info_req", - "is request": true, - "response name": "nytimes_account_info_resp", - "checks": [ - { - "in": "url", - "check": "/svc/account/query", - "is present": true - }, - { - "in": "head", - "check param": "Host", - "contains": "nytimes" - }, - { - "in": "body", - "check": "getSettingsPageInfo", - "is present": true - } - ] - }, + "in": "url", + "check param": "response_type", + "is present": "true" + } + ] + }, + { + "name": "token request", + "is request": true, + "response name": "token response", + "checks": [ { - "name": "fb_acc_link_confirm_req", - "is request": true, - "response name": "fb_acc_link_confirm_resp", - "checks": [ - { - "in": "url", - "check": "/dialog/oauth", - "is present": true - }, - { - "in": "head", - "check param": "Host", - "contains": "facebook.com" - } - ] - }, + "in": "url", + "check param": "code", + "is present": "true" + } + ] + }, + { + "name": "saml request", + "is request": true, + "checks": [ { - "name": "linking_url", - "is request": true, - "checks": [ - { - "in": "url", - "check param": "code", - "is present": true - }, - { - "in": "head", - "check param": "Referer", - "contains": "facebook.com" - } - ] - }, + "in": "url", + "check param": "SAMLRequest", + "is present": true + } + ] + }, + { + "name": "saml response", + "is request": true, + "checks": [ { - "name": "message_insert_point", - "is request": true, - "checks": [ - { - "in": "url", - "check": "insertmessagehere", - "is present": "true" - } - ] - }, + "in": "body", + "check param": "SAMLResponse", + "is present": true + } + ] + }, + { + "name": "fb_login", + "is request": true, + "response name": "fb_login_resp", + "checks": [ { - "name": "association_link_req", - "is request": true, - "response name": "association_link_resp", - "checks": [ - { - "in": "url", - "check": "dialog/oauth?", - "is present": "true" - }, - { - "in": "head", - "check param": "Host", - "contains": "facebook.com" - } - ] + "in": "url", + "check": "/auth/facebook-login-callback", + "is present": true }, { - "name": "association_inter_link_res", - "is request": false, - "request name": "association_inter_link_req", - "checks": [ - { - "in": "head", - "check": "dialog/oauth?", - "is present": true - } - ] + "in": "url", + "check param": "code", + "is present": true } - ] + ] + } + ] } \ No newline at end of file diff --git a/.gitignore b/tool/.gitignore similarity index 96% rename from .gitignore rename to tool/.gitignore index 8a8471c..4ac0fc7 100644 --- a/.gitignore +++ b/tool/.gitignore @@ -52,6 +52,9 @@ cmake-build-*/ # IntelliJ out/ +# Build files +target/ + # mpeltonen/sbt-idea plugin .idea_modules/ @@ -74,6 +77,4 @@ fabric.properties .idea/httpRequests # Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -/tool/target/* \ No newline at end of file +.idea/caches/build_file_checksums.ser \ No newline at end of file diff --git a/tool/.idea/compiler.xml b/tool/.idea/compiler.xml deleted file mode 100644 index 6465c9d..0000000 --- a/tool/.idea/compiler.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/tool/.idea/jarRepositories.xml b/tool/.idea/jarRepositories.xml deleted file mode 100644 index 712ab9d..0000000 --- a/tool/.idea/jarRepositories.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/tool/.idea/misc.xml b/tool/.idea/misc.xml index 56c7380..b6a965c 100644 --- a/tool/.idea/misc.xml +++ b/tool/.idea/misc.xml @@ -1,4 +1,3 @@ - @@ -8,5 +7,5 @@ - + \ No newline at end of file diff --git a/tool/.idea/uiDesigner.xml b/tool/.idea/uiDesigner.xml deleted file mode 100644 index e96534f..0000000 --- a/tool/.idea/uiDesigner.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tool/.idea/workspace.xml b/tool/.idea/workspace.xml deleted file mode 100644 index 80ce4b5..0000000 --- a/tool/.idea/workspace.xml +++ /dev/null @@ -1,257 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1654595126983 - - - - - - - - - - - - - - - - - - - - - - - - - - - file://$PROJECT_DIR$/src/main/java/burp/GUI.java - 1812 - - - - - - - - - - - - - - - - - - - \n" + - " \n"; - - String TEMPLATE_BODY_PARAMS = "" + - " \n" + - " $PARAM_NAME$\n" + - " \n" + - " \n" + - " \n" + - " "; - - List headers = Utils.getHeaders(message, true, helpers); - String encoding = getHeadParameterValue(headers, "Content-Type").strip(); - String body = splitMessage(message,helpers,true).get(2); - String url = helpers.analyzeRequest(message).getUrl().toString(); - String method = splitMessage(message,helpers,true).get(0).split(" ")[0]; - - Pattern p = Pattern.compile(""); - Matcher m = p.matcher(body); - - String res = ""; - - res = POST_TEMPLATE; - p = Pattern.compile("\\$ENCODING_TYPE\\$"); - m = p.matcher(res); - res = m.replaceAll(encoding); - - p = Pattern.compile("\\$METHOD\\$"); - m = p.matcher(res); - res = m.replaceAll(method); - - if (method.equals("POST")) { - p = Pattern.compile("([^=]*)=([^&\\n$]*)(&|\\n|$)"); - m = p.matcher(body); - String out_body_params = ""; - - if (body.length() != 0) { - Map body_params = new HashMap<>(); - while (m.find()) { - String name = m.group(1); - String value = m.group(2); - if (name.length() != 0 ) { - body_params.put(name, - value.length() != 0 ? value : ""); - } - } - for (String key : body_params.keySet()) { - String tmp = TEMPLATE_BODY_PARAMS; - p = Pattern.compile("\\$PARAM_NAME\\$"); - m = p.matcher(tmp); - - tmp = m.replaceAll(key); - - p = Pattern.compile("\\$PARAM_VALUE\\$"); - m = p.matcher(tmp); - - tmp = m.replaceAll(body_params.get(key)); - - out_body_params += tmp; - } - } - - p = Pattern.compile("\\$URL\\$"); - m = p.matcher(res); - res = m.replaceAll(url); - - p = Pattern.compile("\\$BODY_PARAMETERS\\$"); - m = p.matcher(res); - res = m.replaceAll(out_body_params); - } else { - boolean has_query_params = url.split("\\?").length > 1; - String out_query_params = ""; - - if (has_query_params) { - String raw_query_params = url.split("\\?")[1]; - - p = Pattern.compile("([^=\\n&]*)=([^=\\n&]*)"); - m = p.matcher(raw_query_params); - - Map query_params = new HashMap<>(); - while (m.find()) { - String name = m.group(1); - String value = m.group(2); - if (name.length() != 0) { - query_params.put(name, value.length() != 0 ? value : ""); - } - } - for (String key : query_params.keySet()) { - String tmp = TEMPLATE_BODY_PARAMS; - p = Pattern.compile("\\$PARAM_NAME\\$"); - m = p.matcher(tmp); - - tmp = m.replaceAll(key); - - p = Pattern.compile("\\$PARAM_VALUE\\$"); - m = p.matcher(tmp); - - tmp = m.replaceAll(query_params.get(key)); - - out_query_params += tmp; - } - } - - p = Pattern.compile("\\$URL\\$"); - m = p.matcher(res); - res = m.replaceAll( has_query_params ? url.split("\\?")[0] : url); - - p = Pattern.compile("\\$BODY_PARAMETERS\\$"); - m = p.matcher(res); - res = m.replaceAll(out_query_params); - } - - String tmp = CSFR_TEMPLATE; - p = Pattern.compile("\\$INSERT_HERE\\$"); - m = p.matcher(tmp); - tmp = m.replaceAll(res); - - return tmp; - } - - /** - * Create batches of passive tests, grouping them by the session they need to execute. - * @return An HashMap object having as keys, Strings representing the sessions names, and as value a list of tests - * that need to execute that session - */ - public static HashMap> batchPassivesFromSession(List testList) throws ParsingException { - HashMap> batch = new HashMap<>(); - for (Test t : testList) { - if (t.sessions.size() == 0) { - throw new ParsingException("Undefined session in test " + t.name); - } - - if(!batch.containsKey(t.sessions.get(0).name)){ - List n = new ArrayList<>(); - n.add(t); - batch.put(t.sessions.get(0).name, n); - } else { - List tmp = batch.get(t.sessions.get(0).name); - tmp.add(t); - batch.put(t.sessions.get(0).name, tmp); - } - } - return batch; - } - - /** - * From a batch of tests grouped by sessions, return a list containing all the tests - * @param batch the batch of tests in the form of a MAP> - * @return - * @throws ParsingException - */ - public static List debatchPassive(HashMap> batch) { - List res = new ArrayList<>(); - for (String sessionName : batch.keySet()){ - for (Test t : batch.get(sessionName)) { - res.add(t); - } - } - return res; - } - - /** - * Given a message, get the given parameter value from the url - * - * @param message the message to search into - * @param isRequest if the message is a request - * @param param the parameter name to be searched - * @return the value of the parameter - */ - public static String getUrlParam(IExtensionHelpers helpers, IHttpRequestResponse message, Boolean isRequest, String param) { - List parts = Utils.splitMessage(message, helpers, isRequest); - //Pattern pattern = Pattern.compile("(?=&?)" + param + "=[^& ]*((?=&)|(?= ))"); - - Pattern pattern = Pattern.compile("(?<=" + param + "=)[^$\\n&\\s]*"); - Matcher matcher = pattern.matcher(parts.get(0)); - String res = ""; - while (matcher.find()) { - res = matcher.group(); - break; - } - return res; - } - - /** - * Given a message, get the given parameter value from the url - * - * @param message the message to search into - * @param isRequest if the message is a request - * @param param the parameter name to be searched - * @return the value of the parameter - */ - public static String getUrlParam(IExtensionHelpers helpers, HTTPReqRes message, Boolean isRequest, String param) { - List parts = Utils.splitMessage(message, helpers, isRequest); - //Pattern pattern = Pattern.compile("(?=&?)" + param + "=[^& ]*((?=&)|(?= ))"); - - Pattern pattern = Pattern.compile("(?<=" + param + "=)[^$\\n&\\s]*"); - Matcher matcher = pattern.matcher(parts.get(0)); - String res = ""; - while (matcher.find()) { - res = matcher.group(); - break; - } - return res; - } - - /** - * Given a message, get the given parameter value from the head - * - * @param message the message to search into - * @param isRequest if the message is a request - * @param param the parameter name to be searched - * @return the value of the parameter - */ - public static String getHeadParam(IExtensionHelpers helpers, IHttpRequestResponse message, Boolean isRequest, String param) { - List parts = Utils.splitMessage(message, helpers, isRequest); - - Pattern pattern = Pattern.compile("(?<=" + param + ":)[^$\\n]*", Pattern.MULTILINE); - Matcher matcher = pattern.matcher(parts.get(1)); - String res = ""; - while (matcher.find()) { - res = matcher.group(); - res = res; - break; - } - return res; - } - - /** - * Given a message, get the given parameter value from the head - * - * @param message the message to search into - * @param isRequest if the message is a request - * @param param the parameter name to be searched - * @return the value of the parameter - */ - public static String getHeadParam(IExtensionHelpers helpers, HTTPReqRes message, Boolean isRequest, String param) { - List parts = Utils.splitMessage(message, helpers, isRequest); - - Pattern pattern = Pattern.compile("(?<=" + param + ":)[^$\\n]*", Pattern.MULTILINE); - Matcher matcher = pattern.matcher(parts.get(1)); - String res = ""; - while (matcher.find()) { - res = matcher.group(); - res = res; - break; - } - return res; - } - - /** - * Given a message, get the given parameter value from the body, note that it accepts a regular expression, and - * everything matched will be returned as a value - * - * @param message the message to search into - * @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 - * @return the value of the parameter - */ - public static String getBodyParam(IExtensionHelpers helpers, IHttpRequestResponse message, Boolean isRequest, String param) { - List parts = Utils.splitMessage(message, helpers, isRequest); - - //Pattern pattern = Pattern.compile("(?<=" + param + "=)[^$\\n&]*"); - Pattern pattern = Pattern.compile(param); - Matcher matcher = pattern.matcher(parts.get(2)); - //parts.set(2, matcher.replaceAll("")); - - String res = ""; - while (matcher.find()) { - res = matcher.group(); - break; - } - return res; - } - - /** - * Given a message, get the given parameter value from the body, note that it accepts a regular expression, and - * everything matched will be returned as a value - * - * @param message the message to search into - * @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 - * @return the value of the parameter - */ - public static String getBodyParam(IExtensionHelpers helpers, HTTPReqRes message, Boolean isRequest, String param) { - List parts = Utils.splitMessage(message, helpers, isRequest); - - //Pattern pattern = Pattern.compile("(?<=" + param + "=)[^$\\n&]*"); - Pattern pattern = Pattern.compile(param); - Matcher matcher = pattern.matcher(parts.get(2)); - //parts.set(2, matcher.replaceAll("")); - - String res = ""; - while (matcher.find()) { - res = matcher.group(); - break; - } - return res; - } - - /** - * Edit a message treating it as a string using a regex - * @param helpers an instance of Burp's IExtensionHelper - * @param regex the regex used to match the things to change - * @param mop the message operation containing information about the section to match the regex - * @param messageInfo the message as IHttpRequestResponse object - * @param isRequest specify if the message to consider is the request or response - * @param new_value the new value to substitute to the message section - * @param isBodyRegex not used, to remove - * @return the edited message as byte array - * @throws ParsingException if problems are encountered in editing the message - */ - public static byte[] editMessage(IExtensionHelpers helpers, - String regex, - MessageOperation mop, - IHttpRequestResponse messageInfo, - boolean isRequest, - String new_value, - boolean isBodyRegex) throws ParsingException { - List splitted = null; - Pattern pattern = null; - Matcher matcher = null; - switch (mop.from) { - case HEAD: - splitted = Utils.splitMessage(messageInfo, helpers, isRequest); - - pattern = Pattern.compile(regex); - matcher = pattern.matcher(splitted.get(1)); - splitted.set(1, matcher.replaceAll(new_value)); - - return Utils.buildMessage(splitted, helpers); - - case BODY: - splitted = Utils.splitMessage(messageInfo, helpers, isRequest); - - pattern = Pattern.compile(regex); - - matcher = pattern.matcher(splitted.get(2)); - splitted.set(2, matcher.replaceAll(new_value)); - - List head = Utils.getHeaders(messageInfo, isRequest, helpers); - //Automatically update content-lenght - return helpers.buildHttpMessage(head, helpers.stringToBytes(splitted.get(2))); - - case URL: - if (!isRequest) { - throw new ParsingException("Encoding URL in response"); - } - splitted = Utils.splitMessage(messageInfo, helpers, isRequest); - - pattern = Pattern.compile(regex); - matcher = pattern.matcher(splitted.get(0)); - - String replaced = matcher.replaceAll(new_value); - - splitted.set(0, replaced); // problema - - return Utils.buildMessage(splitted, helpers); - } - - return null; - } - - /** - * Edit a message parameter - * @param helpers an instance of Burp's IExtensionHelper - * @param param_name the name of the parameter to edit - * @param mop the message operation containing information about the section containing the parameter to edit - * @param messageInfo the message as IHttpRequestResponse object - * @param isRequest specify if the message to consider is the request or response - * @param new_value the new value of the parameter - * @param isBodyRegex when the section is body, set it to true if you want to use a regex to substitute the value, - * otherwise a parameter param=... is searched - * @return the edited message as byte array - * @throws ParsingException if problems are encountered in editing the message - */ - public static byte[] editMessageParam(IExtensionHelpers helpers, - String param_name, - MessageOperation mop, - IHttpRequestResponse messageInfo, - boolean isRequest, - String new_value, - boolean isBodyRegex) throws ParsingException { - List splitted = null; - Pattern pattern = null; - Matcher matcher = null; - switch (mop.from) { - case HEAD: - List headers = Utils.getHeaders(messageInfo, isRequest, helpers); - headers = Utils.editHeadParameter(headers, mop.what, new_value); - byte[] message = helpers.buildHttpMessage( - headers, - Utils.getBody(messageInfo, isRequest, helpers)); - - if (param_name.equals("Host")) { - messageInfo.setHttpService( - helpers.buildHttpService( - new_value, - messageInfo.getHttpService().getPort(), - messageInfo.getHttpService().getProtocol() - ) - ); - } - - return message; - - case BODY: - splitted = Utils.splitMessage(messageInfo, helpers, isRequest); - - if (!isBodyRegex) { - pattern = Pattern.compile("(?<=" + param_name + "=)[^$\\n& ]*"); - } else { - pattern = Pattern.compile(param_name); - } - - matcher = pattern.matcher(splitted.get(2)); - splitted.set(2, matcher.replaceAll(new_value)); - - List head = Utils.getHeaders(messageInfo, isRequest, helpers); - //Automatically update content-lenght - return helpers.buildHttpMessage(head, helpers.stringToBytes(splitted.get(2))); - - case URL: - if (!isRequest) { - throw new ParsingException("Encoding URL in response"); - } - splitted = Utils.splitMessage(messageInfo, helpers, isRequest); - - pattern = Pattern.compile(param_name + "=[^& ]*((?=&)|(?= ))"); - matcher = pattern.matcher(splitted.get(0)); - - splitted.set(0, matcher.replaceAll(param_name + "=" + new_value)); // problema - - return Utils.buildMessage(splitted, helpers); - } - - return null; - } -} diff --git a/tool/src/main/java/migt/API.java b/tool/src/main/java/migt/API.java new file mode 100644 index 0000000..a62f80a --- /dev/null +++ b/tool/src/main/java/migt/API.java @@ -0,0 +1,7 @@ +package migt; + +/** + * The API class, this class is inherited by all the other APIs + */ +public class API { +} diff --git a/tool/src/main/java/migt/BurpExtender.java b/tool/src/main/java/migt/BurpExtender.java new file mode 100644 index 0000000..425c0fa --- /dev/null +++ b/tool/src/main/java/migt/BurpExtender.java @@ -0,0 +1,200 @@ +package migt; + +import burp.*; + +import javax.swing.*; +import java.awt.*; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Objects; + +/** + * Main class executed by Burp + * + * @author Matteo Bitussi + */ +public class BurpExtender implements IBurpExtender, ITab, IProxyListener { + + public static IExtensionHelpers helpers; + public static PrintStream printStream; + public static PrintStream errorStream; + public IBurpExtenderCallbacks callbacks; + private GUI mainPane; // The GUI + + /** + * Main function creating the extension + * + * @param callbacks The callbacks received by Burp + */ + public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { + /* + try { + System.setOut(new PrintStream("output_log.txt")); // Changes the default outstream with this file + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + + try { + System.setErr(new PrintStream("error_log.txt")); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + */ + + System.out.println("Initializing extension"); + + this.callbacks = callbacks; + helpers = callbacks.getHelpers(); + + callbacks.setExtensionName("MIG Testing tool"); + + //The UI is created + SwingUtilities.invokeLater(() -> { + // setup output stream + OutputStream stdOut = callbacks.getStdout(); + OutputStream stdErr = callbacks.getStderr(); + printStream = new PrintStream(stdOut); + errorStream = new PrintStream(stdErr); + + mainPane = new GUI(); + mainPane.helpers = callbacks.getHelpers(); + mainPane.callbacks = callbacks; + mainPane.messageViewer = callbacks.createMessageEditor(mainPane.controller, false); + mainPane.splitPane.setRightComponent(mainPane.messageViewer.getComponent()); + + // add the custom tab to Burp's UI + callbacks.addSuiteTab(BurpExtender.this); + + // register ourselves as an HTTP listener + callbacks.registerProxyListener(BurpExtender.this); + //callbacks.registerHttpListener(BurpExtender.this); + }); + } + + @Override + public String getTabCaption() { + return "MIG-T"; + } + + @Override + public Component getUiComponent() { + return mainPane; + } + + /** + * Proxy's listener function which is called wheter a new message arrives. Note that if the received message is a + * request, you cannot access the response + * + * @param messageIsRequest Indicates whether the HTTP message is a request + * or a response. + * @param proxy_message An + * IInterceptedProxyMessage object that extensions can use to + * query and update details of the message, and control whether the message + * should be intercepted and displayed to the user for manual review or + */ + @Override + public void processProxyMessage(boolean messageIsRequest, IInterceptedProxyMessage proxy_message) { + String port = proxy_message.getListenerInterface().split(":")[1]; + IHttpRequestResponse messageInfo = proxy_message.getMessageInfo(); + + HTTPReqRes message = new HTTPReqRes( + messageInfo, + helpers, + messageIsRequest, + proxy_message.getMessageReference() + ); + + if (mainPane.ACTIVE_ENABLED) { + if (!port.equals(mainPane.act_active_op.session_port)) { + return; + } + + log_message(messageIsRequest, proxy_message); + + MessageType msg_type = null; + try { + msg_type = MessageType.getFromList(mainPane.messageTypes, + mainPane.act_active_op.getMessageType()); + } catch (Exception e) { + e.printStackTrace(); + mainPane.act_active_op.applicable = false; + } + + boolean matchMessage = message.matches_msg_type(msg_type); + + if (matchMessage) { + // If the operation's action is an intercept + if (Objects.requireNonNull(mainPane.act_active_op.getAction()) == Operation.Action.INTERCEPT) { + try { + processMatchedMsg(msg_type, messageInfo, message); + if (mainPane.act_active_op.then != null & + mainPane.act_active_op.then == Operation.Then.DROP) { + proxy_message.setInterceptAction(IInterceptedProxyMessage.ACTION_DROP); + } + } catch (Exception e) { + e.printStackTrace(); + mainPane.act_active_op.applicable = false; + } + } + } + } + + if (mainPane.recording) { + if (!messageIsRequest) { // do not remove + synchronized (mainPane.interceptedMessages) { + IHttpRequestResponsePersisted actual = callbacks.saveBuffersToTempFiles(messageInfo); + mainPane.interceptedMessages.add( + new HTTPReqRes(actual, helpers) + ); + if (mainPane.defaultSession != null) { + mainPane.defaultSession.addMessage(actual, helpers, mainPane.FILTERING); + } + } + } + } + } + + /** + * @param msg_type the message type to be used + * @param messageInfo the original intercepted messageInfo to being able to edit the message + * @param message a custom parsed message to be used in opeations + */ + private void processMatchedMsg(MessageType msg_type, + IHttpRequestResponse messageInfo, + HTTPReqRes message) { + messageInfo.setHighlight("red"); + + mainPane.act_active_op.helpers = helpers; + mainPane.act_active_op.setAPI(new Operation_API(message, msg_type.msg_to_process_is_request)); + mainPane.act_active_op.execute(); + + // if message has been edited inside operation update the value + try { + if (mainPane.act_active_op.processed_message != null) { + if (msg_type.msg_to_process_is_request) { + messageInfo.setRequest(mainPane.act_active_op.processed_message); + } else { + messageInfo.setResponse(mainPane.act_active_op.processed_message); + } + } + } catch (UnsupportedOperationException e) { + // This is thrown when an already issued request is being substituted + System.err.println("Warning, edited message that has already been sent"); + } + resume(); + } + + /** + * Tells the lock on the Execute Actives process to resume the execution + */ + private void resume() { + // Resume the execution thread + synchronized (mainPane.waiting) { + mainPane.waiting.notify(); + } + } + + private void log_message(boolean isRequest, IInterceptedProxyMessage message) { + mainPane.act_active_op.log_messages.add(message); + } +} diff --git a/tool/src/main/java/migt/Check.java b/tool/src/main/java/migt/Check.java new file mode 100644 index 0000000..0216f01 --- /dev/null +++ b/tool/src/main/java/migt/Check.java @@ -0,0 +1,685 @@ +package migt; + +import com.jayway.jsonpath.JsonPath; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static migt.Check.CheckOps.*; + +/** + * 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 + CheckOps op; // the check operations + CheckIn in; // the section over which to search + String op_val; + List value_list; // the eventual list of values to check between + boolean isParamCheck; // specifies if what is declared in what is a parameter name + String regex; // the eventual regex to use + boolean use_variable; // if a variable name will be used in the check operation + boolean url_decode = true; // this can be used to disable url decoding + + public Check() { + init(); + } + + /** + * Instantiate a new Check object given its parsed JSONObject + * + * @param json_check the check as JSONObject + * @throws ParsingException if the input is not compliant with the language + */ + public Check(JSONObject json_check) throws ParsingException { + init(); + Iterator keys = json_check.keys(); + while (keys.hasNext()) { + String key = keys.next(); + try { + switch (key) { + case "in": + this.in = CheckIn.fromString(json_check.getString("in")); + break; + case "check param": + this.isParamCheck = true; + this.setWhat(json_check.getString("check param")); + break; + case "check": + this.setWhat(json_check.getString("check")); + break; + case "check regex": + regex = json_check.getString("check regex"); + break; + case "use variable": + use_variable = json_check.getBoolean("use variable"); + break; + case "is": + this.setOp(CheckOps.IS); + this.op_val = json_check.getString("is"); + break; + case "is not": + this.setOp(CheckOps.IS_NOT); + this.op_val = json_check.getString("is not"); + break; + case "contains": + this.setOp(CheckOps.CONTAINS); + try { + this.op_val = json_check.getString("contains"); + } catch (JSONException e) { + // if not a string try an array + JSONArray jsonArr = json_check.getJSONArray("contains"); + Iterator it = jsonArr.iterator(); + + while (it.hasNext()) { + String act_enc = (String) it.next(); + value_list.add(act_enc); + } + } + break; + case "not contains": + this.setOp(CheckOps.NOT_CONTAINS); + try { + this.op_val = json_check.getString("not contains"); + } catch (JSONException e) { + // if not a string try an array + JSONArray jsonArr = json_check.getJSONArray("not contains"); + Iterator it = jsonArr.iterator(); + + while (it.hasNext()) { + String act_enc = (String) it.next(); + value_list.add(act_enc); + } + } + break; + case "is present": + this.op = json_check.getBoolean("is present") ? CheckOps.IS_PRESENT : + IS_NOT_PRESENT; + this.op_val = json_check.getBoolean("is present") ? + "is present" : "is not present"; + break; + case "is in": + this.op = CheckOps.IS_IN; + JSONArray jsonArr = json_check.getJSONArray("is in"); + Iterator it = jsonArr.iterator(); + + while (it.hasNext()) { + String act_enc = (String) it.next(); + value_list.add(act_enc); + } + break; + case "is not in": + this.op = CheckOps.IS_NOT_IN; + JSONArray jsonArr2 = json_check.getJSONArray("is not in"); + Iterator it2 = jsonArr2.iterator(); + + while (it2.hasNext()) { + String act_enc = (String) it2.next(); + value_list.add(act_enc); + } + break; + case "is subset of": + this.op = IS_SUBSET_OF; + JSONArray jsonArr3 = json_check.getJSONArray("is subset of"); + Iterator it3 = jsonArr3.iterator(); + + while (it3.hasNext()) { + String act_enc = (String) it3.next(); + value_list.add(act_enc); + } + break; + case "matches regex": + this.op = MATCHES_REGEX; + this.op_val = json_check.getString("matches regex"); + break; + case "not matches regex": + this.op = NOT_MATCHES_REGEX; + this.op_val = json_check.getString("not matches regex"); + break; + case "url decode": + url_decode = json_check.getBoolean("url decode"); + break; + } + } catch (JSONException e) { + throw new ParsingException("error in parsing check: " + e); + } catch (ClassCastException e) { + throw new ParsingException("Only allowed values in arrays are Strings, if you are using integers or " + + "floats, please convert them as strings"); + } + + } + + if (regex.equals("") && what.equals("")) + throw new ParsingException("Error in parsing check"); + } + + public void init() { + what = ""; + op_val = ""; + isParamCheck = false; + regex = ""; + value_list = new ArrayList<>(); + use_variable = false; + } + + /** + * Loads a Decode operation's API into the check + * + * @param api the Decode operation's api to load + */ + public void loader(DecodeOperation_API api) { + this.imported_api = api; + } + + /** + * Loads an Operation's API into the check + * + * @param api the Operation's API to load + */ + public void loader(Operation_API api) { + this.imported_api = api; + } + + /** + * Executes the regex version of the check + * + * @param input the input content + * @return the result of the check + */ + private boolean execute_regex(String input) throws ParsingException { + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(input); + applicable = true; + + String val = ""; + if (m.find()) { + val = m.group(); + } + + if (this.op == null) { + // Return result based on matched or not + return (val.length() > 0); + } else { + // execute op against matched value + return do_check(val); + } + } + + /** + * Execute the check over a message (in an Operation) + * + * @param message the message to check + * @param isRequest tells if the message is a request or a response + * @return the result of the check + * @throws ParsingException if something wrong is found wrt the language + */ + private boolean execute_http(HTTPReqRes message, + boolean isRequest) throws ParsingException { + String msg_str = ""; + if (this.in == null) { + throw new ParsingException("from tag in checks is null"); + } + + switch (this.in) { + case URL: + if (!isRequest) { + throw new ParsingException("Searching URL in response"); + } + msg_str = message.getUrlHeader(); + break; + case BODY: + msg_str = new String(message.getBody(isRequest), StandardCharsets.UTF_8); + break; + case HEAD: + msg_str = String.join("\r\n", message.getHeaders(isRequest)); + break; + default: + System.err.println("no valid \"in\" specified in check"); + return false; + } + + if (msg_str.length() == 0) { + return false; + } + + // URL-decode matched content + if (url_decode) + msg_str = URLDecoder.decode(msg_str, StandardCharsets.UTF_8); + + // if a regex is present, execute it + if (!regex.equals("")) { + return execute_regex(msg_str); + } + + if (this.isParamCheck) { + if (in == CheckIn.BODY) { + applicable = false; + throw new ParsingException("Invalid check operation, cannot do \"check param\" over body, " + + "use \"check_regex instead\""); + } + + Pattern p = this.in == CheckIn.URL ? + Pattern.compile("(?<=[?&]" + this.what + "=)[^\\r\\n&]*") : + Pattern.compile("(?<=" + this.what + ":\\s?)[^\\r\\n]*"); + // TODO: this could be done better by using message methods + Matcher m = p.matcher(msg_str); + + applicable = true; + + String val = ""; + if (m.find()) { + val = m.group(); + val = val.trim(); + } else { + //return false; // TODO: check if correct, is not present? + } + + return do_check(val); + } else { + applicable = true; + if (!msg_str.contains(this.what)) { + if (this.op != null) { + return this.op == IS_NOT_PRESENT; + } else { + return false; + } + } else { + if (this.op != null) { + return this.op != IS_NOT_PRESENT; + } + } + } + return true; + } + + /** + * Execute the json version of the check + * + * @return the result of the execution + * @throws ParsingException if something wrong is found wrt the language + */ + private boolean execute_json() throws ParsingException { + DecodeOperation_API tmp = ((DecodeOperation_API) this.imported_api); + + if (isParamCheck) { + throw new ParsingException("Cannot execute a 'check param' in a json, please use 'check'"); + } + + String j = ""; + + switch (in) { + case JWT_HEADER: { + j = tmp.jwt.header; + break; + } + case JWT_PAYLOAD: { + j = tmp.jwt.payload; + break; + } + case JWT_SIGNATURE: { + j = tmp.jwt.signature; + break; + } + } + + // if a regex is present, execute it + if (!regex.equals("")) { + return execute_regex(j); + } + + String found = ""; + List found_array = null; + boolean value_is_array = false; + // https://github.com/json-path/JsonPath + try { + Object found_obj = JsonPath.read(j, what); + + if (op == IS_PRESENT | op == IS_NOT_PRESENT) { + // whatever is the type of the value, if it is found return the result + applicable = true; + return op == IS_PRESENT; + } + + if (found_obj instanceof net.minidev.json.JSONArray) { + // the value is a list, allowed ops are: contains/not-contains + if (!(op == CONTAINS | op == NOT_CONTAINS | op == IS_SUBSET_OF)) { + throw new ParsingException("Check error, used " + op.toString() + " over a matched list"); + } + + Iterator i = ((net.minidev.json.JSONArray) found_obj).iterator(); + + List new_array = new ArrayList<>(); + while (i.hasNext()) { + try { + String elem = String.valueOf(i.next()); + new_array.add(elem); + } catch (java.lang.ClassCastException e) { + throw new ParsingException("Cannot convert element in jwt matched array to string"); + } + } + found_array = new_array; + value_is_array = true; + + } else if (found_obj instanceof java.lang.String) { + // the value is a string, can do all ops + found = (String) found_obj; + + } else if (found_obj instanceof java.lang.Double | + found_obj instanceof java.lang.Integer) { + // the value is an double or integer, convert to string + found = String.valueOf(found_obj); + } + + } catch (com.jayway.jsonpath.PathNotFoundException e) { + applicable = true; + return op == IS_NOT_PRESENT; + } catch (java.lang.ClassCastException e) { + throw new ParsingException("Error in check, json matched value cast exception: " + e); + } + + applicable = true; // at this point the path has been found so the check is applicable + + switch (op) { + case IS: + return op_val.equals(found); + case IS_NOT: + return !op_val.equals(found); + case CONTAINS: + if (!value_is_array) + return found.contains(op_val); + else { + // the matched value is an array + if (!value_list.isEmpty()) { + // check against a value array + for (String elem : value_list) { + if (!found_array.contains(elem)) { + return false; + } + } + return true; + } else { + // check against single string value + return found_array.contains(op_val); + } + } + case NOT_CONTAINS: + if (!value_is_array) + return !found.contains(op_val); + else { + //the matched value is an array + if (!value_list.isEmpty()) { + // check against a value array + for (String elem : value_list) { + if (found_array.contains(elem)) { + return false; + } + } + return true; + } else { + // check against single string value + return found_array.contains(op_val); + } + } + case IS_PRESENT: + return !found.isEmpty(); + case IS_NOT_PRESENT: + return found.isEmpty(); + case IS_IN: + return value_list.contains(found); + case IS_NOT_IN: + return !value_list.contains(found); + case IS_SUBSET_OF: + if (!value_is_array) + throw new ParsingException("Matched single element in jwt, but should be an array when using IS SUBSET OF"); + + return value_list.containsAll(found_array); + case MATCHES_REGEX: { + if (value_is_array) throw new ParsingException("Check error: cannot execute a regex over a list"); + Pattern p = Pattern.compile(op_val); + Matcher m = p.matcher(found); + return m.find(); + } + case NOT_MATCHES_REGEX: { + if (value_is_array) throw new ParsingException("Check error: cannot execute a regex over a list"); + Pattern p = Pattern.compile(op_val); + Matcher m = p.matcher(found); + return !m.find(); + } + } + + return false; + } + + /** + * Executes check operations over the selected value, and returns the result + * + * @param val_to_check the value to check + * @return the result of the check + */ + public boolean do_check(String val_to_check) throws ParsingException { + try { + if (this.op == null && val_to_check.length() != 0) { + // if it passed all the splits without errors, the param is present, but no checks are specified + // so result is true + return true; + } + switch (this.op) { + case IS: + if (!this.op_val.equals(val_to_check)) { + return false; + } + break; + case IS_NOT: + if (this.op_val.equals(val_to_check)) { + return false; + } + break; + case CONTAINS: + if (!val_to_check.contains(this.op_val)) { + return false; + } + break; + case NOT_CONTAINS: + if (val_to_check.contains(this.op_val)) { + return false; + } + break; + case IS_PRESENT: + return !val_to_check.isEmpty(); // if it gets to this, the searched param is already found + case IS_NOT_PRESENT: + return val_to_check.isEmpty(); + case IS_IN: + return value_list.contains(val_to_check); // TODO check + case IS_NOT_IN: + return !value_list.contains(val_to_check); + case MATCHES_REGEX: { + Pattern p = Pattern.compile(op_val); + Matcher m = p.matcher(val_to_check); + return m.find(); + } + case NOT_MATCHES_REGEX: { + Pattern p = Pattern.compile(op_val); + Matcher m = p.matcher(val_to_check); + return !m.find(); + } + default: + throw new ParsingException("Unsupported operand for Check in a message: " + op); + } + } catch (ArrayIndexOutOfBoundsException e) { + //e.printStackTrace(); + if (this.op != null) { + if (this.op != IS_NOT_PRESENT) { + return false; + } + } else { + return false; + } + } + return true; + } + + /** + * Executes the given check (without API). Used to match messages with msg_types usually. + * + * @param message the message to check + * @param isRequest if the message is a request or a response + * @return the result of the check (passed or not passed) + */ + public boolean execute(HTTPReqRes message, + boolean isRequest, + List vars) throws ParsingException { + + if (use_variable) { + // Substitute to the op_val variable (that contains the name), the value of the variable + op_val = Tools.getVariableByName(op_val, vars).value; + } + result = execute_http(message, isRequest); + return result; + } + + /** + * Execute the check by using API + * + * @param vars the variables of the actual operation (test) + */ + public void execute(List vars) throws ParsingException { + if (use_variable) { + // Substitute to the op_val variable (that contains the name), the value of the variable + op_val = Tools.getVariableByName(op_val, vars).value; + } + + if (imported_api instanceof Operation_API) { + // If is inside a standard Operation + result = execute_http( + ((Operation_API) imported_api).message, + ((Operation_API) imported_api).is_request + ); + } else if (imported_api instanceof DecodeOperation_API) { + // if inside a decode operation + switch (((DecodeOperation_API) imported_api).type) { + case JWT: + result = execute_json(); + break; + case NONE: + break; + //TODO + case XML: + //TODO + break; + } + } + } + + public void setWhat(String what) { + this.what = what; + } + + public void setOp(CheckOps op) { + this.op = op; + } + + @Override + public String toString() { + return "check: " + what + (op == null ? "" : " " + op + ": " + op_val); + } + + /** + * enum containing all the possible check operations + */ + public enum CheckOps { + IS, + IS_NOT, + CONTAINS, + NOT_CONTAINS, + IS_PRESENT, + IS_NOT_PRESENT, + IS_IN, + IS_NOT_IN, + IS_SUBSET_OF, + MATCHES_REGEX, + NOT_MATCHES_REGEX; + + /** + * Function that given a String, returns the corresponding CheckOps enum's value + * + * @param input the input string + * @return the CheckOps enum value + * @throws ParsingException if the input string does not correspond to any of the possible check operations + */ + public static CheckOps fromString(String input) throws ParsingException { + if (input != null) { + switch (input) { + case "is": + return IS; + case "is not": + return IS_NOT; + case "contains": + return CONTAINS; + case "not contains": + return NOT_CONTAINS; + case "is in": + return IS_IN; + case "is not in": + return IS_NOT_IN; + case "is subset of": + return IS_SUBSET_OF; + case "matches regex": + return MATCHES_REGEX; + case "not matches regex": + return NOT_MATCHES_REGEX; + default: + throw new ParsingException("invalid check operation"); + } + } else { + throw new NullPointerException(); + } + } + } + + /** + * Used in the Check operation, to specify where is the content to check. + */ + public enum CheckIn { + // standard message + HEAD, + BODY, + URL, + // jwt + JWT_HEADER, + JWT_PAYLOAD, + JWT_SIGNATURE; + + public static CheckIn fromString(String input) throws ParsingException { + if (input != null) { + switch (input) { + case "head": + return HEAD; + case "body": + return BODY; + case "url": + return URL; + case "header": + return JWT_HEADER; + case "payload": + return JWT_PAYLOAD; + case "signature": + return JWT_SIGNATURE; + default: + throw new ParsingException("invalid in '" + input + "' for check"); + } + } else { + throw new NullPointerException(); + } + } + } +} diff --git a/tool/src/main/java/migt/DecodeOperation.java b/tool/src/main/java/migt/DecodeOperation.java new file mode 100644 index 0000000..7fa5ebe --- /dev/null +++ b/tool/src/main/java/migt/DecodeOperation.java @@ -0,0 +1,626 @@ +package migt; + +import burp.IExtensionHelpers; +import com.jayway.jsonpath.JsonPath; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +import static migt.Tools.executeDecodeOps; +import static migt.Tools.executeEditOps; + +/** + * This class stores a decode operation + */ +public class DecodeOperation extends Module { + public String decoded_content; // the decoded content + public String decode_target; // aka decode_param how to decode the raw content + public DecodeOperationFrom from; // where the raw content is. Depending on the containing module, can be other things + public List encodings; // the list of encoding to decode and rencode + public DecodeOpType type; // the type of the decoded param (used only to edit its content) + public List checks; // the list of checks to be executed + public List decodeOperations; // a list of decode operations to execute them recursevly + public List editOperations; // a list of edit operations + public boolean check_jwt = false; + JWT jwt; + String what; + + public DecodeOperation() { + init(); + } + + /** + * Instantiate a decode operation object parsing a json object + * + * @param decode_op_json the json object + * @throws ParsingException + */ + public DecodeOperation(JSONObject decode_op_json) throws ParsingException { + init(); + java.util.Iterator keys = decode_op_json.keys(); + while (keys.hasNext()) { + String key = keys.next(); + + switch (key) { + case "type": + type = DecodeOpType.fromString(decode_op_json.getString("type")); + break; + case "decode param": + decode_target = decode_op_json.getString("decode param"); + break; + case "encodings": + JSONArray encodings = decode_op_json.getJSONArray("encodings"); + Iterator it = encodings.iterator(); + + while (it.hasNext()) { + String act_enc = (String) it.next(); + this.encodings.add( + Encoding.fromString(act_enc)); + } + break; + case "from": + String f = decode_op_json.getString("from"); + from = DecodeOperationFrom.fromString(f); + break; + case "decode operations": + // Recursion goes brr + JSONArray decode_ops = decode_op_json.getJSONArray("decode operations"); + for (int k = 0; k < decode_ops.length(); k++) { + JSONObject act_decode_op = decode_ops.getJSONObject(k); + DecodeOperation decode_op = new DecodeOperation(act_decode_op); + decodeOperations.add(decode_op); + } + break; + case "checks": + checks = Tools.parseChecksFromJSON(decode_op_json.getJSONArray("checks")); + break; + case "edits": + editOperations = Tools.parseEditsFromJSON(decode_op_json.getJSONArray("edits")); + break; + case "jwt check sig": + check_jwt = true; + jwt.public_key_pem = decode_op_json.getString("jwt check sig"); + break; + case "jwe decrypt": + jwt.decrypt = true; + jwt.private_key_pem_enc = decode_op_json.getString("jwe decrypt"); + break; + case "jwe encrypt": + // if encrypt specified, also decrypt has to be specified + jwt.decrypt = true; + jwt.private_key_pem_enc = decode_op_json.getString("jwe encrypt"); + jwt.public_key_pem_enc = decode_op_json.getString("jwe decrypt"); + } + } + } + + /** + * Decodes a parameter from a message, given the message section and the list of encodings to be applied during + * decoding + * + * @param helpers IExtensionHelpers helpers object from Burp + * @param ms The message section that contains the parameter to be decoded + * @param encodings The list of encodings to be applied to decode the parameter + * @param messageInfo The message to be decoded + * @param isRequest True if the message containing the parameter is a request + * @param decode_param The name of the parameter to be decoded + * @return The decoded parameter as a string + * @throws ParsingException If problems are encountered during decoding + */ + public static String decodeParam(IExtensionHelpers helpers, + DecodeOperationFrom ms, + List encodings, + HTTPReqRes messageInfo, + Boolean isRequest, + String decode_param) throws ParsingException { + String decoded_param = ""; + switch (ms) { + case HEAD: + decoded_param = decode( + encodings, messageInfo.getHeadParam(isRequest, decode_param), helpers); + break; + case BODY: + decoded_param = decode( + encodings, messageInfo.getBodyRegex(isRequest, decode_param), helpers); + break; + case URL: + decoded_param = decode( + encodings, messageInfo.getUrlParam(decode_param), helpers); + break; + } + + decoded_param = Tools.removeNewline(decoded_param); + + return decoded_param; + } + + /** + * Decode the given string, with the given ordered encodings + * Example taken from + * Saml Raider + * + * @param encodings the ordered list of encodings to be applied + * @param encoded the string to be decoded + * @return the decoded string + * @throws ParsingException if the decoding fails + */ + public static String decode(List encodings, String encoded, IExtensionHelpers helpers) throws ParsingException { + // TODO: remove dependency from helpers + String actual = encoded; + byte[] actual_b = null; + boolean isActualString = true; + + if (encoded.length() == 0) { + return ""; + } + + for (Encoding e : encodings) { + switch (e) { + case BASE64: + if (isActualString) { + actual_b = helpers.base64Decode(actual); + isActualString = false; + } else { + actual_b = helpers.base64Decode(actual_b); + } + break; + case URL: + + if (isActualString) { + actual = helpers.urlDecode(actual); + } else { + actual = helpers.urlDecode(new String(actual_b)); + isActualString = true; + } + break; + case DEFLATE: + boolean done = false; + if (isActualString) { + byte[] data = actual.getBytes(); + + try { + actual_b = decompress(data, true); + done = true; + isActualString = false; + } catch (IOException | DataFormatException ioException) { + ioException.printStackTrace(); + //ioException.printStackTrace(); + } + + try { + if (!done) { + actual_b = decompress(data, false); + done = true; + isActualString = false; + } + } catch (IOException | DataFormatException ioException) { + //ioException.printStackTrace(); + + } + } else { + try { + actual_b = decompress(actual_b, true); + done = true; + } catch (IOException | DataFormatException ioException) { + + //ioException.printStackTrace(); + } + + try { + if (!done) { + actual_b = decompress(actual_b, false); + done = true; + } + } catch (IOException | DataFormatException ioException) { + + } + } + break; + } + } + if (isActualString) { + return actual; + } else { + return new String(actual_b, StandardCharsets.UTF_8); + } + } + + /** + * Encode the given string, with the given encodings (in the specified order) + * Example taken from + * Saml Raider + * + * @param encodings the ordered list of encodings to be applied + * @param decoded the string to be encoded + * @return the encoded string + */ + public static String encode(List encodings, String decoded, IExtensionHelpers helpers) { + String actual = decoded; + byte[] actual_b = null; + boolean isActualString = true; + for (Encoding e : encodings) { + switch (e) { + case BASE64: + + if (isActualString) { + actual = helpers.base64Encode(actual); + } else { + actual = helpers.base64Encode(actual_b); + isActualString = true; + } + break; + + case URL: + + if (isActualString) { + actual = URLEncoder.encode(actual); + } else { + actual = new String(actual_b); + actual = URLEncoder.encode(actual); + isActualString = true; + } + break; + + case DEFLATE: + + if (isActualString) { + try { + actual_b = compress(actual.getBytes(StandardCharsets.UTF_8), true); + } catch (IOException ioException) { + + //ioException.printStackTrace(); + } + isActualString = false; + } else { + try { + actual_b = compress(actual_b, true); + } catch (IOException ioException) { + + //ioException.printStackTrace(); + } + isActualString = false; + } + } + } + + if (isActualString) { + return actual; + } else { + return new String(actual_b, StandardCharsets.UTF_8); + } + } + + /** + * Also named Inflate, taken from + * here + * + * @param data the data to be decompressed (inflated) + * @param gzip true to use gzip + * @return returns the decompressed data + * @throws IOException if something goes wrong + * @throws DataFormatException if something goes wrong + */ + public static byte[] decompress(byte[] data, boolean gzip) throws IOException, DataFormatException { + Inflater inflater = new Inflater(true); + inflater.setInput(data); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length); + byte[] buffer = new byte[1024]; + while (!inflater.finished()) { + int count = inflater.inflate(buffer); + outputStream.write(buffer, 0, count); + } + outputStream.close(); + byte[] output = outputStream.toByteArray(); + + inflater.end(); + + return output; + } + + /** + * Also named Deflate, taken from + * here + * + * @param data data to be compressed (deflated) + * @param gzip true to use gzip + * @return the compressed data + * @throws IOException if the compression goes wrong + */ + public static byte[] compress(byte[] data, boolean gzip) throws IOException { + Deflater deflater = new Deflater(5, gzip); + deflater.setInput(data); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length); + + deflater.finish(); + byte[] buffer = new byte[1024]; + while (!deflater.finished()) { + int count = deflater.deflate(buffer); + outputStream.write(buffer, 0, count); + } + outputStream.close(); + byte[] output = outputStream.toByteArray(); + + deflater.end(); + + return output; + } + + public void init() { + decoded_content = ""; + decode_target = ""; + checks = new ArrayList<>(); + encodings = new ArrayList<>(); + decodeOperations = new ArrayList<>(); + what = ""; + type = DecodeOpType.NONE; + editOperations = new ArrayList<>(); + jwt = new JWT(); + } + + @Override + public DecodeOperation_API getAPI() { + api = new DecodeOperation_API(this); + return (DecodeOperation_API) api; + } + + public void setAPI(DecodeOperation_API dop_api) { + this.api = dop_api; + // assign values returned from the api + switch (type) { + case JWT: + this.jwt = dop_api.jwt; + break; + case NONE: + this.decoded_content = dop_api.txt; + break; + case XML: + this.decoded_content = dop_api.xml; + break; + } + } + + /** + * Loads an Operation API + * + * @param api + * @param helpers + * @throws ParsingException + */ + public void loader(Operation_API api, IExtensionHelpers helpers) { + // load api, extract needed things + this.helpers = helpers; + this.imported_api = api; + } + + /** + * Loads a decode operation API + * + * @param api + */ + public void loader(DecodeOperation_API api, IExtensionHelpers helpers) { + this.imported_api = api; + this.helpers = helpers; + + } + + /** + * Exports the API of this decode operation to be used by another operation + * + * @return the API + * @throws ParsingException + */ + @Override + public Operation_API exporter() throws ParsingException { + Collections.reverse(encodings); // Set the right order for encoding + String encoded = encode(encodings, decoded_content, helpers); + + Tools.editMessageParam( + helpers, + decode_target, + from, + ((Operation_API) imported_api).message, + ((Operation_API) imported_api).is_request, + encoded, + true); + + // the previous function should already have updated the message inside api + return ((Operation_API) imported_api); + } + + /** + * Executes this decode operation + * + * @throws ParsingException + */ + public void execute(List vars) throws ParsingException { + if (imported_api instanceof Operation_API) { + decoded_content = decodeParam( + helpers, + from, + encodings, + ((Operation_API) imported_api).message, + ((Operation_API) imported_api).is_request, + decode_target); + + } else if (imported_api instanceof DecodeOperation_API) { + switch (from) { + case JWT_HEADER: + case JWT_PAYLOAD: + case JWT_SIGNATURE: + // recursevly decode from a jwt + String j = ((DecodeOperation_API) imported_api).getDecodedContent(from); + + String found = ""; + // https://github.com/json-path/JsonPath + try { + found = JsonPath.read(j, what); // select what to decode + } catch (com.jayway.jsonpath.PathNotFoundException e) { + applicable = false; + result = false; + return; + } + decoded_content = decode(encodings, found, helpers); + break; + default: + throw new UnsupportedOperationException( + "the from you selected in the recursive decode operation is not yet supported"); + //TODO implement missing + } + } + + // If type is jwt, parse + if (Objects.requireNonNull(type) == DecodeOpType.JWT) { + jwt.parse(decoded_content); + if (check_jwt) { + if (!jwt.check_sig()) { + applicable = true; + result = false; + return; + } + } + } + + // execute edit operations + if (editOperations.size() > 0) { + executeEditOps(this, vars); + } + + // executes recursive decode operations + if (decodeOperations.size() != 0) { + executeDecodeOps(this, helpers, vars); + } + + // execute checks + if (checks.size() != 0) { + executeChecks(vars); + } + + // Rebuild JWT before encoding it + if (Objects.requireNonNull(type) == DecodeOpType.JWT) { + decoded_content = jwt.build(); + } + applicable = true; + } + + /** + * Execute a list of checks inside a decode operation. This function uses the APIs Sets also the result to the + * decode op + * + * @return the result, for convenience + * @throws ParsingException if errors are found + */ + public boolean executeChecks(List vars) throws ParsingException { + for (Check c : checks) { + c.loader(getAPI()); + c.execute(vars); + if (!setResult(c)) { + return false; + } + } + return true; + } + + /** + * Used in decode operation to specify where to search for the content to decode + */ + public enum DecodeOperationFrom { + // standard message + HEAD, + BODY, + URL, + // jwt + JWT_HEADER, + JWT_PAYLOAD, + JWT_SIGNATURE; + + public static DecodeOperationFrom fromString(String input) throws ParsingException { + if (input != null) { + switch (input) { + case "head": + return HEAD; + case "body": + return BODY; + case "url": + return URL; + case "jwt header": + return JWT_HEADER; + case "jwt payload": + return JWT_PAYLOAD; + case "jwt signature": + return JWT_SIGNATURE; + default: + throw new ParsingException("invalid decode operation from '" + input + "'"); + } + } else { + throw new NullPointerException(); + } + } + } + + /** + * The possible encodings to be used + */ + public enum Encoding { + BASE64, + URL, + DEFLATE; + + /** + * From a string get the corresponding enum value + * + * @param input the string + * @return the enum value + * @throws ParsingException if the input is malformed + */ + public static Encoding fromString(String input) throws ParsingException { + if (input != null) { + switch (input) { + case "base64": + return BASE64; + case "url": + return URL; + case "deflate": + return DEFLATE; + default: + throw new ParsingException("invalid encoding"); + } + } else { + throw new NullPointerException(); + } + } + } + + /** + * Used to specify the type of decoded content, only when that content has to be edited. + */ + public enum DecodeOpType { + JWT, + NONE, + XML; + + public static DecodeOpType fromString(String input) throws ParsingException { + if (input != null) { + switch (input) { + case "jwt": + return JWT; + case "xml": + return XML; + default: + throw new ParsingException("invalid message Op Type"); + } + } else { + throw new NullPointerException(); + } + } + } +} diff --git a/tool/src/main/java/migt/DecodeOperation_API.java b/tool/src/main/java/migt/DecodeOperation_API.java new file mode 100644 index 0000000..c95d164 --- /dev/null +++ b/tool/src/main/java/migt/DecodeOperation_API.java @@ -0,0 +1,60 @@ +package migt; + +public class DecodeOperation_API extends API { + public DecodeOperation.DecodeOpType type; // the type of the decoded param + + public JWT jwt; // TODO: use this instead of single strings + public String txt; + public String xml; + + public DecodeOperation_API() { + init(); + } + + public DecodeOperation_API(DecodeOperation dop) { + init(); + type = dop.type; + switch (dop.type) { + case NONE: + txt = dop.decoded_content; + break; + case JWT: + jwt = dop.jwt; + break; + case XML: + xml = dop.decoded_content; + break; + } + } + + public void init() { + jwt = new JWT(); + } + + public String getDecodedContent(DecodeOperation.DecodeOperationFrom dopfrom) throws ParsingException { + switch (dopfrom) { + case HEAD: + throw new ParsingException("cannot decode from header in a recursive decode"); + case BODY: + throw new ParsingException("cannot decode from body in a recursive decode"); + case URL: + throw new ParsingException("cannot decode from url in a recursive decode"); + case JWT_HEADER: + if (type != DecodeOperation.DecodeOpType.JWT) + throw new ParsingException("cannot decode in a jwt header if previous decode was not a jwt"); + return jwt.header; + + case JWT_PAYLOAD: + if (type != DecodeOperation.DecodeOpType.JWT) + throw new ParsingException("cannot decode in a jwt payload if previous decode was not a jwt"); + return jwt.payload; + + case JWT_SIGNATURE: + if (type != DecodeOperation.DecodeOpType.JWT) + throw new ParsingException("cannot decode in a jwt signature if previous decode was not a jwt"); + return jwt.signature; + default: + throw new UnsupportedOperationException("invalid Decode operation from"); + } + } +} diff --git a/tool/src/main/java/migt/EditOperation.java b/tool/src/main/java/migt/EditOperation.java new file mode 100644 index 0000000..7b70872 --- /dev/null +++ b/tool/src/main/java/migt/EditOperation.java @@ -0,0 +1,489 @@ +package migt; + +import com.jayway.jsonpath.PathNotFoundException; +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.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static migt.Tools.getVariableByName; + +public class EditOperation extends Module { + // XML + XmlAction xml_action; + String xml_action_name; + String xml_tag; + String xml_attr; + String value; + Integer xml_occurrency; + Boolean self_sign; + Boolean remove_signature; + String saml_original_cert; + String edited_xml; + + String use; // say which parameter use as value + String save_as; // say the name of the parameter to save the value + + // JWT + Jwt_section jwt_section; + Jwt_action jwt_action; + boolean sign; + String jwt_private_key_pem; + + String what; + + // TXT + TxtAction txt_action; + String txt_action_name; + + public EditOperation(JSONObject eop_json) throws ParsingException { + init(); + java.util.Iterator keys = eop_json.keys(); + while (keys.hasNext()) { + String key = keys.next(); + + switch (key) { + case "use": + use = eop_json.getString("use"); + break; + case "as": + save_as = eop_json.getString("as"); + break; + case "value": + // value of xml or other edits + value = eop_json.getString("value"); + break; + case "add tag": + xml_action = XmlAction.ADD_TAG; + xml_action_name = eop_json.getString(key); + break; + case "add attribute": + xml_action = XmlAction.ADD_ATTR; + xml_action_name = eop_json.getString(key); + break; + case "edit tag": + xml_action = XmlAction.EDIT_TAG; + xml_action_name = eop_json.getString(key); + break; + case "edit attribute": + xml_action = XmlAction.EDIT_ATTR; + xml_action_name = eop_json.getString(key); + break; + case "remove tag": + xml_action = XmlAction.REMOVE_TAG; + xml_action_name = eop_json.getString(key); + break; + case "remove attribute": + xml_action = XmlAction.REMOVE_ATTR; + xml_action_name = eop_json.getString(key); + break; + case "save tag": + xml_action = XmlAction.SAVE_TAG; + xml_action_name = eop_json.getString(key); + break; + case "save attribute": + xml_action = XmlAction.SAVE_ATTR; + xml_action_name = eop_json.getString(key); + break; + case "self-sign": + self_sign = eop_json.getBoolean("self-sign"); + break; + case "remove signature": + remove_signature = eop_json.getBoolean("remove signature"); + break; + case "xml tag": + xml_tag = eop_json.getString("xml tag"); + break; + case "xml occurrency": + xml_occurrency = eop_json.getInt("xml occurrency"); + break; + case "xml attribute": + xml_attr = eop_json.getString("xml attribute"); + break; + // JWT + case "jwt from": + jwt_section = Jwt_section.getFromString( + eop_json.getString("jwt from")); + break; + case "jwt remove": + jwt_action = Jwt_action.REMOVE; + what = eop_json.getString("jwt remove"); + break; + case "jwt edit": + jwt_action = Jwt_action.EDIT; + what = eop_json.getString("jwt edit"); + break; + case "jwt add": + jwt_action = Jwt_action.ADD; + what = eop_json.getString("jwt add"); + break; + case "jwt save": + jwt_action = Jwt_action.SAVE; + what = eop_json.getString("jwt save"); + break; + case "jwt sign": + sign = true; + jwt_private_key_pem = eop_json.getString("jwt sign"); + break; + + case "txt remove": + txt_action = TxtAction.REMOVE; + txt_action_name = eop_json.getString("txt remove"); + break; + case "txt edit": + txt_action = TxtAction.EDIT; + txt_action_name = eop_json.getString("txt edit"); + break; + case "txt add": + txt_action = TxtAction.ADD; + txt_action_name = eop_json.getString("txt add"); + break; + case "txt save": + txt_action = TxtAction.SAVE; + txt_action_name = eop_json.getString("txt save"); + break; + } + } + } + + public void init() { + use = ""; + save_as = ""; + xml_action_name = ""; + xml_tag = ""; + xml_attr = ""; + value = ""; + self_sign = false; + remove_signature = false; + saml_original_cert = ""; + edited_xml = ""; + sign = false; + txt_action_name = ""; + what = ""; + } + + 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 DecodeOperation_API exporter() { + return (DecodeOperation_API) 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; + } + 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; + } + + if (self_sign && !edited_xml.equals("")) { + // SAML re-sign + edited_xml = SamlTabController.resignAssertion_edit(edited_xml, saml_original_cert); + } + + 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; + } + } catch (PathNotFoundException e) { + this.applicable = false; + this.result = false; + return; + } + } + applicable = true; + + if (sign) { + tmp_imported_api.jwt.sign = true; + tmp_imported_api.jwt.private_key_pem = jwt_private_key_pem; + } + + break; + + case NONE: + Pattern p = Pattern.compile(txt_action_name); + Matcher m = p.matcher(tmp_imported_api.txt); + + 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; + } + 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; + } + } + + /** + * The possible XML actions are the ones described in this enum + */ + public enum XmlAction { + ADD_TAG, + ADD_ATTR, + EDIT_TAG, + EDIT_ATTR, + REMOVE_TAG, + REMOVE_ATTR, + SAVE_TAG, + SAVE_ATTR; + + /** + * From a string get the corresponding value + * + * @param input the input string + * @return the enum value + * @throws ParsingException if the string does not correspond to any of the values + */ + public static XmlAction fromString(String input) throws ParsingException { + if (input != null) { + switch (input) { + case "add tag": + return ADD_TAG; + case "add attribute": + return ADD_ATTR; + case "edit tag": + return EDIT_TAG; + case "edit attribute": + return EDIT_ATTR; + case "remove tag": + return REMOVE_TAG; + case "remove attribute": + return REMOVE_ATTR; + case "save tag": + return SAVE_TAG; + case "save attribute": + return SAVE_ATTR; + default: + throw new ParsingException("invalid xml action"); + } + } else { + throw new NullPointerException(); + } + } + } + + /** + * Defines the possible actions to be done on a decoded parameter interpreted as plain text + */ + public enum TxtAction { + REMOVE, + EDIT, + ADD, + SAVE; + + /** + * From a string get the corresponding value + * + * @param input the input string + * @return the enum value + * @throws ParsingException if the string does not correspond to any of the values + */ + public static TxtAction fromString(String input) throws ParsingException { + if (input != null) { + switch (input) { + case "txt remove": + return REMOVE; + case "txt edit": + return EDIT; + case "txt add": + return ADD; + case "txt save": + return SAVE; + default: + throw new ParsingException("invalid xml action"); + } + } else { + throw new NullPointerException(); + } + } + } + + /** + * Defines the possible actions to be done on a JWT token + */ + public enum Jwt_action { + REMOVE, + EDIT, + ADD, + SAVE + } + + /** + * Defines the possible JWT token sections + */ + public enum Jwt_section { + HEADER, + PAYLOAD, + SIGNATURE; + + /** + * Get the JWT section enum value from a string + * + * @param s the string to parse + * @return the enum value + * @throws ParsingException if the string is invalid + */ + public static Jwt_section getFromString(String s) throws ParsingException { + switch (s) { + case "header": + return HEADER; + case "payload": + return PAYLOAD; + case "signature": + return SIGNATURE; + default: + throw new ParsingException("Invalid jwt section"); + } + } + } +} diff --git a/tool/src/main/java/burp/ExecuteActiveListener.java b/tool/src/main/java/migt/ExecuteActiveListener.java similarity index 69% rename from tool/src/main/java/burp/ExecuteActiveListener.java rename to tool/src/main/java/migt/ExecuteActiveListener.java index e5f9a96..3487a4c 100644 --- a/tool/src/main/java/burp/ExecuteActiveListener.java +++ b/tool/src/main/java/migt/ExecuteActiveListener.java @@ -1,6 +1,4 @@ -package burp; - -import java.util.List; +package migt; /** * Listener class for ExecuteActive class @@ -44,39 +42,22 @@ public interface ExecuteActiveListener { /** * This method is called during an ExecuteActive execution, when a new test is being executed + * * @param actual_test The test that was being executed */ void onNewTest(Test actual_test); /** * This method is called during an ExecuteActive execution, when a test has finished + * * @param actual_test The test that was being executed */ void onTestDone(Test actual_test); /** * This method is called whether an error occurs during the execution of the active tests + * * @param actual_test The test that was being executed */ void onError(Test actual_test); - - /** - * This method is called before the execution of the SessionOperations in a test. It is used to update the list - * of variables with the thread - * @return the updated list of variables - */ - List onBeforeExSessionOps(); - - /** - * This method is called after the execution of the SessionOperations in a test. It is used to updated the list of - * variables with the thread - * @param re the list of variables to update - */ - void onAfterExSessionOps(List re); - - /** - * This method is called when a variable has to been set during the test - * @param v the new set variable - */ - void onAddVar(Var v); } diff --git a/tool/src/main/java/burp/ExecuteActives.java b/tool/src/main/java/migt/ExecuteActives.java similarity index 89% rename from tool/src/main/java/burp/ExecuteActives.java rename to tool/src/main/java/migt/ExecuteActives.java index 7d5f9d7..6ef2ff7 100644 --- a/tool/src/main/java/burp/ExecuteActives.java +++ b/tool/src/main/java/migt/ExecuteActives.java @@ -1,4 +1,4 @@ -package burp; +package migt; import java.util.HashMap; import java.util.List; @@ -69,7 +69,7 @@ public void run() { case START: { Session selected = actual_test.getSession(op.getSession()); if (selected == null) { - actual_test.error_srt = "Invalid session name, maybe you didn't declare it?"; + actual_test.error_str = "Invalid session name, maybe you didn't declare it?"; actual_test.error = true; break; } @@ -79,14 +79,13 @@ public void onExecuteDone(boolean errors, String current_url, String sessionName if (actual_test.resultSession.equals("") || actual_test.resultSession.equals(sessionName)) { - if (actual_test.result == Utils.ResultType.CORRECT_FLOW) { + if (actual_test.result == Test.ResultType.CORRECT_FLOW) { if (errors || current_url.contains("error")) { actual_test.success = false; } - } else if (actual_test.result == Utils.ResultType.INCORRECT_FLOW) { + } else if (actual_test.result == Test.ResultType.INCORRECT_FLOW) { actual_test.success = errors; // Difficult to read - } else if (actual_test.result == Utils.ResultType.ASSERT_ONLY) { - actual_test.success = true; + } else if (actual_test.result == Test.ResultType.ASSERT_ONLY) { //at this point, all the asserts have been executed, and if they failed // they already returned a false result } @@ -172,7 +171,7 @@ public Track onUpdateTrack(String sessionName) throws ParsingException { @Override public void onSetVar(Var v) { - listener.onAddVar(v); + actual_test.vars.add(v); } }); @@ -195,7 +194,7 @@ public void onSetVar(Var v) { case STOP: { Session selected = actual_test.getSession(op.getSession()); if (selected == null) { - actual_test.error_srt = "Invalid session name, maybe you didn't declare it?"; + actual_test.error_str = "Invalid session name, maybe you didn't declare it?"; actual_test.error = true; break; } @@ -206,7 +205,7 @@ public void onSetVar(Var v) { case CLEAR_COOKIES: Session selected = actual_test.getSession(op.getSession()); if (selected == null) { - actual_test.error_srt = "Invalid session name, maybe you didn't declare it?"; + actual_test.error_str = "Invalid session name, maybe you didn't declare it?"; actual_test.error = true; break; } @@ -214,10 +213,9 @@ public void onSetVar(Var v) { break; } - List act_vars = listener.onBeforeExSessionOps(); - Object[] res = Utils.executeSessionOps(actual_test, op, act_vars); - op = (Operation) res[0]; - listener.onAfterExSessionOps((List) res[1]); + List act_vars = actual_test.vars; + List updated_vars = op.executeSessionOps(actual_test, act_vars); + actual_test.vars = updated_vars; } else { //if it is a normal operation @@ -230,6 +228,12 @@ public void onSetVar(Var v) { op.session_port = "8080"; } + if (op.api == null) { + op.api = new Operation_API(actual_test.vars); + } else { + op.api.vars = actual_test.vars; + } + listener.onNewProcessOperation(op); synchronized (this.waiting) { @@ -242,23 +246,13 @@ public void onSetVar(Var v) { op = listener.onOperationDone(); // Take the operation from the caller - List act_vars = listener.onBeforeExSessionOps(); - if (act_vars.size() == 0) { - try { - Thread.sleep(500); - act_vars = listener.onBeforeExSessionOps(); - } catch (InterruptedException e) { - } - } - - Object[] res = Utils.executeSessionOps(actual_test, op, act_vars); - op = (Operation) res[0]; - listener.onAfterExSessionOps((List) res[1]); + actual_test.vars = op.api.vars; + actual_test.vars = op.executeSessionOps(actual_test, actual_test.vars); if (op.applicable) { - actual_test.success = op.passed; + actual_test.success = op.result; actual_test.applicable = true; - if (!op.passed) { + if (!op.result) { for (String key : executions.keySet()) { executions.get(key).interrupt(); } diff --git a/tool/src/main/java/burp/ExecutePassiveListener.java b/tool/src/main/java/migt/ExecutePassiveListener.java similarity index 97% rename from tool/src/main/java/burp/ExecutePassiveListener.java rename to tool/src/main/java/migt/ExecutePassiveListener.java index 3eeac4b..dbb4652 100644 --- a/tool/src/main/java/burp/ExecutePassiveListener.java +++ b/tool/src/main/java/migt/ExecutePassiveListener.java @@ -1,4 +1,4 @@ -package burp; +package migt; import java.util.ArrayList; import java.util.List; @@ -10,6 +10,7 @@ public interface ExecutePassiveListener { /** * This method is called when the ExecutePassive thread is started, it is used to tell when the thread should start * executing the passives test. if it returns true, the thread will start + * * @return true if you want the thread to start */ boolean onWaitToStart(); @@ -21,12 +22,14 @@ public interface ExecutePassiveListener { /** * Called when the ExecutePassives thread has ended + * * @param passives_test The list of executed passive tests */ void onExecuteDone(List passives_test); /** * Called when there is an error in the execution + * * @param msg the error message */ void onError(String msg); @@ -34,6 +37,7 @@ public interface ExecutePassiveListener { /** * When a new session has to be executed this method is called. This is thought to fill the session with * the right values from the GUI class. + * * @param s the session to be initiated * @return the session with the filled values */ diff --git a/tool/src/main/java/burp/ExecutePassives.java b/tool/src/main/java/migt/ExecutePassives.java similarity index 88% rename from tool/src/main/java/burp/ExecutePassives.java rename to tool/src/main/java/migt/ExecutePassives.java index 1483cf9..411832b 100644 --- a/tool/src/main/java/burp/ExecutePassives.java +++ b/tool/src/main/java/migt/ExecutePassives.java @@ -1,4 +1,6 @@ -package burp; +package migt; + +import burp.IExtensionHelpers; import java.util.ArrayList; import java.util.HashMap; @@ -11,20 +13,21 @@ * @author Matteo Bitussi */ public class ExecutePassives implements Runnable { + final Object lock = new Object(); public List passives; IExtensionHelpers helpers; ExecutePassiveListener listener; List messageTypes; - final Object lock = new Object(); boolean finished; boolean execution_error; /** * Used to instantiate an ExecutePassives object - * @param helpers IExtensionHelpers instance of Burp + * + * @param helpers IExtensionHelpers instance of Burp * @param passiveTests The list of passive tests to execute - * @param listener the listener for this ExecutePassives Object, used to communicate with the thread - * @param msg_types the list of message types needed by the tests + * @param listener the listener for this ExecutePassives Object, used to communicate with the thread + * @param msg_types the list of message types needed by the tests */ public ExecutePassives(IExtensionHelpers helpers, List passiveTests, @@ -58,7 +61,7 @@ public void run() { HashMap> batch = null; try { - batch = Utils.batchPassivesFromSession(passives); + batch = Tools.batchPassivesFromSession(passives); } catch (ParsingException e) { e.printStackTrace(); //lblOutput.setText(e.getMessage()); @@ -86,7 +89,7 @@ public void run() { executedSession = executePassiveTestSession(act_session); for (Test t : actual_batch) { // TODO: limit one session for active tests - t.sessions.set(0,executedSession); + t.sessions.set(0, executedSession); } batch.put(sessionName, actual_batch); if (execution_error) { @@ -98,11 +101,16 @@ public void run() { for (Test actual_test : actual_batch) { System.out.println("Actual test name: " + actual_test.getName()); - boolean res = Tools.executePassiveTest( - actual_test, - executedSession.messages, - helpers, - messageTypes); + boolean res = false; + try { + res = Tools.executePassiveTest( + actual_test, + executedSession.messages, + helpers, + messageTypes); + } catch (ParsingException e) { + actual_test.applicable = false; + } System.out.println("Actual test result: " + res); actual_test.success = res; @@ -110,13 +118,14 @@ public void run() { // TODO: Remove used session executedSession = null; } - passives = Utils.debatchPassive(batch); + passives = Tools.debatchPassive(batch); listener.onExecuteDone(passives); } /** * Executes a passive test's session, to gather messages needed to execute the passive * tests. + * * @param session the session to be executed * @return the same session, executed, that will contain the intercepted messages */ diff --git a/tool/src/main/java/burp/ExecuteTrack.java b/tool/src/main/java/migt/ExecuteTrack.java similarity index 93% rename from tool/src/main/java/burp/ExecuteTrack.java rename to tool/src/main/java/migt/ExecuteTrack.java index aa7b042..4094273 100644 --- a/tool/src/main/java/burp/ExecuteTrack.java +++ b/tool/src/main/java/migt/ExecuteTrack.java @@ -1,4 +1,4 @@ -package burp; +package migt; import org.openqa.selenium.*; import org.openqa.selenium.chrome.ChromeDriver; @@ -33,12 +33,13 @@ public class ExecuteTrack implements Runnable { /** * Instantiate the ExecuteTrack Object + * * @param isHeadless * @param chrome_selected tells if chrome is selected as a browser to be used, otherwise firefox is used - * @param driver_path the path to the chosen browser's driver - * @param track the track to be executed - * @param port the port set in the browser that the HTTP(S) proxy is listening to - * @param sessionName The name of the session to be executed + * @param driver_path the path to the chosen browser's driver + * @param track the track to be executed + * @param port the port set in the browser that the HTTP(S) proxy is listening to + * @param sessionName The name of the session to be executed */ public ExecuteTrack(boolean isHeadless, boolean chrome_selected, @@ -76,6 +77,7 @@ public void run() { options.addArguments("ignore-certificate-errors"); options.addArguments("window-size=1280,1400"); options.addArguments("--proxy-bypass-list=<-loopback>"); + options.addArguments("--remote-allow-origins=*"); Proxy proxy = new Proxy(); proxy.setHttpProxy("localhost:" + port); proxy.setSslProxy("localhost:" + port); @@ -130,10 +132,10 @@ public void run() { } catch (WebDriverException e) { } - if (track.getTrack().get(i).action == Utils.SessAction.CLICK) { + if (track.getTrack().get(i).action == SessionOperation.SessAction.CLICK) { last_click = track.getTrack().get(i); } - if (track.getTrack().get(i).action == Utils.SessAction.OPEN) { + if (track.getTrack().get(i).action == SessionOperation.SessAction.OPEN) { last_open = track.getTrack().get(i); } listener.onNextSessionAction(last_action, last_open, last_click, last_url, sessionName); @@ -178,15 +180,14 @@ public void run() { continue; } case ALERT: { - if (action.elem != null ) { + if (action.elem != null) { Alert alert = null; - int c=0; - while(c++<10) { + int c = 0; + while (c++ < 10) { try { alert = driver.switchTo().alert(); break; - } - catch(NoAlertPresentException e) { + } catch (NoAlertPresentException e) { Thread.sleep(1000); continue; } @@ -229,16 +230,15 @@ public void run() { case ASSERT_ELEM_CLASS_HAS: case ASSERT_ELEM_CLASS_IS: case ASSERT_ELEM_HAS_ATTRIBUTE: - case ASSERT_ELEM_NOT_HAS_ATTRIBUTE: - { + case ASSERT_ELEM_NOT_HAS_ATTRIBUTE: { String searchBy = action.elem_type; String identifier = action.elem_source; while (windows_checked != windows_count) { try { - boolean is_snapshot = action.action == Utils.SessAction.SNAPSHOT || - action.action == Utils.SessAction.DIFF || - action.action == Utils.SessAction.EQUALS; + boolean is_snapshot = action.action == SessionOperation.SessAction.SNAPSHOT || + action.action == SessionOperation.SessAction.DIFF || + action.action == SessionOperation.SessAction.EQUALS; By by = null; // Checks for the presence of a valid item to search @@ -262,7 +262,7 @@ public void run() { throw new ParsingException("invalid session track command"); } - if (action.action == Utils.SessAction.ASSERT_VISIBLE) { + if (action.action == SessionOperation.SessAction.ASSERT_VISIBLE) { new WebDriverWait(driver, Duration.ofSeconds(TIMEOUT)).until( ExpectedConditions.visibilityOfElementLocated(by)); } else if (is_snapshot) { @@ -291,8 +291,8 @@ public void run() { if (currentElement != null) break; } if (currentElement == null) { - if (action.action == Utils.SessAction.ASSERT_CLICKABLE - || action.action == Utils.SessAction.ASSERT_VISIBLE) { + if (action.action == SessionOperation.SessAction.ASSERT_CLICKABLE + || action.action == SessionOperation.SessAction.ASSERT_VISIBLE) { listener.onExecuteDone(false, sessionName); driver.close(); return; @@ -334,7 +334,7 @@ public void run() { String diff = currentElement.getScreenshotAs(OutputType.BASE64); File f2 = currentElement.getScreenshotAs(OutputType.FILE); f2.renameTo(new File("./diff.png")); - if (action.action == Utils.SessAction.DIFF) { + if (action.action == SessionOperation.SessAction.DIFF) { if (diff.equals(snapshot)) { listener.onExecuteDone(true, current_url, sessionName); driver.close(); diff --git a/tool/src/main/java/burp/ExecuteTrackListener.java b/tool/src/main/java/migt/ExecuteTrackListener.java similarity index 90% rename from tool/src/main/java/burp/ExecuteTrackListener.java rename to tool/src/main/java/migt/ExecuteTrackListener.java index 6cfba95..df85888 100644 --- a/tool/src/main/java/burp/ExecuteTrackListener.java +++ b/tool/src/main/java/migt/ExecuteTrackListener.java @@ -1,4 +1,4 @@ -package burp; +package migt; /** * Listener for the ExectuteTrack Object @@ -44,6 +44,7 @@ public interface ExecuteTrackListener { /** * The thread asks if it has to stop the execution, the listener responds + * * @param sessionName The name of the session that is executing * @return true if the execution should stop */ @@ -51,6 +52,7 @@ public interface ExecuteTrackListener { /** * The thread asks if it has to clear the cookies, the listener responds + * * @param sessionName The name of the session that is executing * @return true if the browser that is executing should clear the cookies */ @@ -59,10 +61,11 @@ public interface ExecuteTrackListener { /** * Called whether a new session action is executed. This is used to update the listener on the actions that are * being executed - * @param last_action the last User action executed at this point - * @param last_open the last open User action executed to this point - * @param last_click the last click User action executed to this point - * @param last_url the last url User action executed to this point + * + * @param last_action the last User action executed at this point + * @param last_open the last open User action executed to this point + * @param last_click the last click User action executed to this point + * @param last_url the last url User action executed to this point * @param session_name the name of the session that is executing * @throws ParsingException If problems are encounter retrieving these parameters */ @@ -74,6 +77,7 @@ void onNextSessionAction(SessionTrackAction last_action, /** * With this method is possible to update the Session track during execution + * * @param sessionName the name of the session that is executing * @return the updated track * @throws ParsingException if problems are encountered in updating the session track @@ -82,6 +86,7 @@ void onNextSessionAction(SessionTrackAction last_action, /** * Called whether a variable is set using a User action + * * @param v the variable that has been set */ void onSetVar(Var v); diff --git a/tool/src/main/java/burp/GUI.java b/tool/src/main/java/migt/GUI.java similarity index 66% rename from tool/src/main/java/burp/GUI.java rename to tool/src/main/java/migt/GUI.java index 3c3af32..ef01a0d 100644 --- a/tool/src/main/java/burp/GUI.java +++ b/tool/src/main/java/migt/GUI.java @@ -1,5 +1,6 @@ -package burp; +package migt; +import burp.*; import com.google.gson.Gson; import org.json.JSONArray; import org.json.JSONException; @@ -16,6 +17,30 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * Sets a JTextArea as an output of an OutputStream, used by the redirect of the stdout and stderr + */ +class CustomOutputStream extends OutputStream { + private final JTextArea textArea; + + public CustomOutputStream(JTextArea textArea) { + this.textArea = textArea; + } + + @Override + public void write(int b) { + // Redirect the byte written to the OutputStream to the JTextArea + retry: + try { + textArea.append(String.valueOf((char) b)); + textArea.setCaretPosition(textArea.getDocument().getLength()); // Auto-scroll to the bottom + } catch (java.lang.Error e) { + // for some reason write lock acquire is interrupted sometimes + break retry; // TODO: fix + } + } +} + /** * This class contains the GUI for the plugin, also a lot of functionality methods * @@ -23,12 +48,9 @@ * @author Wendy Barreto */ public class GUI extends JSplitPane { - private static DefaultTableModel resultTableModel; private static DefaultTableModel testTableModel; - public final ArrayList interceptedMessages; final Object waiting = new Object(); - final Object lock = new Object(); final String LOG_FOLDER = "logs/"; private final String[] foundTableColNames = {"Op. num", "Message Type", "message section", "check/regex", "index", "result"}; private final String[] testSuiteColNames = { @@ -40,9 +62,8 @@ public class GUI extends JSplitPane { "Mitigations", "Result"}; private final Object[][] foundData = {}; - private List passives; - private final List actives; - private final Map sessions_text; + private final Object lock2 = new Object(); + public ArrayList interceptedMessages; //GUI JTable resultTable; JTable testTable; @@ -66,6 +87,8 @@ public class GUI extends JSplitPane { JTextArea txtScript; JTextArea txtSearch; JTextArea txtSessionConfig; + JTextArea txt_out_debug_tab; + JTextArea txt_err_debug_tab; JFileChooser driverSelector; JSplitPane splitPane; IMessageEditor messageViewer; @@ -91,11 +114,12 @@ public class GUI extends JSplitPane { String CONFIG_FILE_PATH = "config.json"; Operation act_active_op; ExecuteActives ex; - List act_test_vars; List messageTypes; + private List actives; + private Map sessions_text; + private List passives; private String DRIVER_PATH = ""; private Thread active_ex; - private Object lock2 = new Object(); private boolean active_ex_finished = false; /** @@ -103,1926 +127,1608 @@ public class GUI extends JSplitPane { */ public GUI() { super(JSplitPane.VERTICAL_SPLIT); - //initialize vars - interceptedMessages = new ArrayList<>(); - testSuite = new TestSuite(); - passives = new ArrayList<>(); - actives = new ArrayList<>(); - sessions_names = new ArrayList<>(); - ACTIVE_ENABLED = false; - act_test_vars = new ArrayList<>(); - sessions_text = new HashMap<>(); - messageTypes = new ArrayList<>(); - session_port = new HashMap<>(); - bot_tabs_index = new HashMap<>(); + init(); + + set_std_out_redirect(); this.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); // Top part of the UI ------------------------------------------------------------------------------------------ - GridBagLayout gridBagLayout = new GridBagLayout(); - gridBagLayout.columnWidths = new int[]{230, 230, 230, 230, 100, 100, 100}; - gridBagLayout.rowHeights = new int[]{20, 48, 48, 48, 48}; - gridBagLayout.columnWeights = new double[]{1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0}; - gridBagLayout.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0}; + GridBagLayout top_layout = new GridBagLayout(); + top_layout.columnWidths = new int[]{230, 230, 230, 230, 100, 100, 100}; + top_layout.rowHeights = new int[]{20, 48, 48, 48, 48}; + top_layout.columnWeights = new double[]{1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0}; + top_layout.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0}; trackContainer = new JPanel(); - trackContainer.setLayout(gridBagLayout); + trackContainer.setLayout(top_layout); - lblTrack = new JLabel("Session track "); - GridBagConstraints gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.insets = new Insets(10, 0, 0, 0); - gbc.gridx = 0; - gbc.gridy = 0; - trackContainer.add(lblTrack, gbc); + setup_tab_track(); + setup_tab_butons(); - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.EAST; - gbc.insets = new Insets(10, 0, 0, 0); - gbc.gridx = 1; - gbc.gridy = 0; - trackContainer.add(new JLabel("Download Driver for your browser"), gbc); + this.setTopComponent(trackContainer); - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.insets = new Insets(10, 0, 0, 0); - gbc.gridx = 2; - gbc.gridy = 0; - trackContainer.add(new JLabel(" https://www.selenium.dev/downloads/"), gbc); + // Bottom part ------------------------------------------------------------------------------------------------- - txtScript = new JTextArea(); - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.fill = GridBagConstraints.BOTH; - gbc.insets = new Insets(10, 0, 0, 10); - gbc.gridx = 0; - gbc.gridy = 1; - gbc.gridwidth = 4; - gbc.gridheight = 4; + GridBagLayout bot_layout = new GridBagLayout(); + bot_layout.columnWidths = new int[]{500, 500, 100}; + bot_layout.rowHeights = new int[]{15, 20, 20, 20, 30}; + bot_layout.columnWeights = new double[]{Double.MIN_VALUE, 0.0}; + bot_layout.rowWeights = new double[]{0.0, Double.MIN_VALUE, 0.0, 0.0, 0.0}; - top_tabbed = new JTabbedPane(); + bot_tabbed = new JTabbedPane(); - JScrollPane scrollPane1 = new JScrollPane(txtScript, - JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, - JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); - top_tabbed.add("main", scrollPane1); - trackContainer.add(top_tabbed, gbc); + JPanel tab_input_json = setup_tab_input_json(bot_layout); + bot_tabs_index.put("Input JSON", 0); + bot_tabbed.addTab("Input JSON", tab_input_json); - driverSelector = new JFileChooser(); - btndriverSelector = new JButton("Select Driver"); + JScrollPane tab_suite_result = setup_tab_suite_result(bot_layout); + bot_tabs_index.put("Test Suite Result", 1); + bot_tabbed.addTab("Test Suite Result", tab_suite_result); - btndriverSelector.addActionListener(actionEvent -> { - int returnVal = driverSelector.showOpenDialog(GUI.this); - if (returnVal == JFileChooser.APPROVE_OPTION) { - File file = driverSelector.getSelectedFile(); - DRIVER_PATH = file.getPath(); - editConfigFile("last_driver_path", DRIVER_PATH); - lbldriver.setText("Driver Selected"); - btndriverSelector.setBackground(Color.GREEN); - btnTestTrack.setEnabled(true); - } else if ((returnVal == JFileChooser.ERROR) || (returnVal == JFileChooser.ERROR_OPTION)) { - lbldriver.setText("Driver:error during file selection"); - System.out.println("error during file selection"); - btnTestTrack.setEnabled(false); + JSplitPane tab_test_result = setup_tab_test_result(bot_layout); + bot_tabs_index.put("Test Result", 2); + bot_tabbed.addTab("Test Result", tab_test_result); - btndriverSelector.setBackground(Color.RED); - } else { - lbldriver.setText("Driver file still not selected"); - btnTestTrack.setEnabled(false); - btndriverSelector.setBackground(Color.RED); - } - }); + JPanel tab_session_config = setup_tab_session_config(bot_layout); + bot_tabs_index.put("session config", 3); + bot_tabbed.addTab("session config", tab_session_config); - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.insets = new Insets(0, 0, 0, 10); - gbc.gridx = 4; - gbc.gridy = 1; - trackContainer.add(btndriverSelector, gbc); + JPanel tab_debug_panel = setub_tab_debug(bot_layout); + bot_tabs_index.put("debug tab", 4); + bot_tabbed.addTab("debug tab", tab_debug_panel); - lbldriver = new JLabel("Driver file still not selected"); - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.SOUTHEAST; - gbc.insets = new Insets(10, 0, 0, 30); - gbc.gridx = 3; - gbc.gridy = 0; - trackContainer.add(lbldriver, gbc); + //Set Bottom Part + this.setBottomComponent(bot_tabbed); - btnTestTrack = new JButton("Test track"); - btnTestTrack.setEnabled(true); + readMsgDefFile(); + readConfigFile(); + if (!DRIVER_PATH.equals("")) { + lbldriver.setText("Driver Selected"); + btndriverSelector.setBackground(Color.GREEN); + btnTestTrack.setEnabled(true); + } + } - btnTestTrack.addActionListener(e -> { - ExecuteTrackListener listener = new ExecuteTrackListener() { - @Override - public void onExecuteDone(boolean errors, String current_url, String sessionName) { - if (errors) { - lblOutput.setText("Error in executing track"); - } else { - lblOutput.setText("Track Executed correctly"); - } - } + /** + * Function used to add an item to the resultTableModel. Contains the results of the tests + * + * @param data the string array containing the data, also a row + */ + private static void addItem(String[] data) { + resultTableModel.addRow(data); + } - @Override - public void onExecuteDone(boolean forceResult, String sessionName) { - if (forceResult) { - lblOutput.setText("Track Executed correctly"); - } else { - lblOutput.setText("Error in executing track"); - } - } + public void init() { + interceptedMessages = new ArrayList<>(); + testSuite = new TestSuite(); + passives = new ArrayList<>(); + actives = new ArrayList<>(); + sessions_names = new ArrayList<>(); + ACTIVE_ENABLED = false; + sessions_text = new HashMap<>(); + messageTypes = new ArrayList<>(); + session_port = new HashMap<>(); + bot_tabs_index = new HashMap<>(); + txt_out_debug_tab = new JTextArea(); + txt_err_debug_tab = new JTextArea(); + } - @Override - public void onError(String sessionName) { - lblOutput.setText("Error in executing track"); - } + /** + * Set a redirect of the stdout and stderr to the txtboxes in the GUI + */ + private void set_std_out_redirect() { + // Stdout out redirect + PrintStream printStream = new PrintStream( + new CustomOutputStream(txt_out_debug_tab)); + System.setOut(printStream); + + // stderr out redirect + PrintStream printStream_err = new PrintStream( + new CustomOutputStream(txt_err_debug_tab)); + System.setErr(printStream_err); + } - @Override - public Boolean onAskPause(String sessionName) { - return false; + /** + * Function used to read the message definition file + */ + private void readMsgDefFile() { + File msg_def_file = new File(MSG_DEF_PATH); + try { + if (!msg_def_file.createNewFile()) { + Scanner myReader = null; + String tmp = ""; + try { + myReader = new Scanner(msg_def_file); + while (myReader.hasNextLine()) { + tmp += myReader.nextLine(); + } + myReader.close(); + messageTypes = Tools.readMsgTypeFromJson(tmp); + } catch (ParsingException e) { + lblOutput.setText("Invalid message type in message type definition file"); + e.printStackTrace(); + } catch (FileNotFoundException e) { + lblOutput.setText("Cannot find message definition file"); } + } else { + FileWriter w = new FileWriter(MSG_DEF_PATH); + w.write(Tools.getDefaultJSONMsgType()); + w.close(); + messageTypes = Tools.readMsgTypeFromJson(Tools.getDefaultJSONMsgType()); + } + } catch (ParsingException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + lblOutput.setText("cannot create message definition file"); + } + } - @Override - public Boolean onAskStop(String sessionName) { - return false; - } + /** + * Function used to read the json config file + */ + private void readConfigFile() { + File config_file = new File(CONFIG_FILE_PATH); + try { + if (!config_file.createNewFile()) { + Scanner myReader = null; + String tmp = ""; + try { + myReader = new Scanner(config_file); + while (myReader.hasNextLine()) { + tmp += myReader.nextLine(); + } + myReader.close(); - @Override - public Boolean onAskClearCookie(String sessionName) { - return null; - } + JSONObject obj = new JSONObject(tmp); + String last_driver_path = obj.getString("last_driver_path"); + String last_used_browser = obj.getString("last_browser_used"); - @Override - public void onNextSessionAction(SessionTrackAction last_action, - SessionTrackAction last_open, - SessionTrackAction last_click, - String last_url, - String session_name) { + if (!last_driver_path.equals("")) { + DRIVER_PATH = last_driver_path; + } - } + switch (last_used_browser) { + case "firefox": { + btnselectChrome.setEnabled(true); + btnselectFirefox.setEnabled(false); + break; + } + case "chrome": { + btnselectChrome.setEnabled(false); + btnselectFirefox.setEnabled(true); + break; + } + } - @Override - public Track onUpdateTrack(String sessionName) throws ParsingException { - return null; + } catch (JSONException e) { + lblOutput.setText("Invalid config file"); + } catch (FileNotFoundException e) { + lblOutput.setText("Cannot find config file"); } + } else { + FileWriter w = new FileWriter(CONFIG_FILE_PATH); + w.write(Tools.getDefaultJSONConfig()); + w.close(); + } + } catch (IOException e) { + e.printStackTrace(); + lblOutput.setText("cannot create message definition file"); + } + } - @Override - public void onSetVar(Var v) { - } - }; - recording = false; - defaultSession = new Session("temp"); - - Track track = null; - try { - track = defaultSession.setTrackFromString(txtScript.getText()); - } catch (ParsingException exc) { - lblOutput.setText("Error in parsing session track"); - } - - defaultSession = null; - ExecuteTrack ex = new ExecuteTrack(false, - !btnselectChrome.isEnabled(), - DRIVER_PATH, - track, - "8080", - "test"); - ex.registerExecuteTrackListener(listener); - new Thread(ex).start(); - }); - - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.insets = new Insets(0, 0, 0, 0); - gbc.gridx = 5; - gbc.gridy = 1; - btnTestTrack.setPreferredSize(new Dimension(100, 20)); - trackContainer.add(btnTestTrack, gbc); - - btnselectChrome = new JButton("Use Chrome"); - btnselectChrome.setEnabled(false); - btnselectChrome.addActionListener(actionEvent -> { - btnTestTrack.setEnabled(false); - lbldriver.setText("Driver file still not selected"); - btndriverSelector.setBackground(Color.RED); - - btnselectChrome.setEnabled(false); - btnselectFirefox.setEnabled(true); - lblnextTestBrowser.setText("Chrome"); - }); - - lblnextTestBrowser = new JLabel("Firefox"); - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.insets = new Insets(0, 0, 0, 0); - gbc.gridx = 4; - gbc.gridy = 2; - trackContainer.add(btnselectChrome, gbc); - - btnselectFirefox = new JButton("Use Firefox"); - btnselectFirefox.setEnabled(true); - btnselectFirefox.addActionListener(actionEvent -> { - btnTestTrack.setEnabled(false); - lbldriver.setText("Driver file still not selected"); - btndriverSelector.setBackground(Color.RED); - - btnselectFirefox.setEnabled(false); - btnselectChrome.setEnabled(true); - lblnextTestBrowser.setText("Firefox"); - }); - - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.insets = new Insets(0, 0, 0, 0); - gbc.gridx = 4; - gbc.gridy = 3; - btnselectFirefox.setPreferredSize(new Dimension(100, 20)); - trackContainer.add(btnselectFirefox, gbc); - - JFileChooser messageSaver = new JFileChooser(); - btnSetRecording = new JButton("Record"); - btnSetRecording.setEnabled(true); - - // Recording button listener - btnSetRecording.addActionListener(actionEvent -> { - if (btnSetRecording.isEnabled()) { - int returnVal = messageSaver.showOpenDialog(GUI.this); - if (returnVal == JFileChooser.APPROVE_OPTION) { - File file = messageSaver.getSelectedFile(); - RECORD_FILE_PATH = file.getPath(); - lblOutput.setText("File selected"); - - SAVE_TO_FILE = true; - - defaultSession = new Session("default"); - try { - defaultSession.setTrackFromString(txtScript.getText()); - } catch (ParsingException e) { - lblOutput.setText("Error in parsing session track"); - return; + /** + * Function that edits the config file. + * + * @param key the key of the config to change + * @param value the new value of the config + */ + private void editConfigFile(String key, String value) { + File config_file = new File(CONFIG_FILE_PATH); + try { + if (!config_file.createNewFile()) { + Scanner myReader = null; + String tmp = ""; + try { + myReader = new Scanner(config_file); + while (myReader.hasNextLine()) { + tmp += myReader.nextLine(); } + myReader.close(); - btnSetRecording.setBackground(new Color(255, 0, 0)); - btnSetRecording.setText("recording.."); - - recording = true; - - ExecuteTrackListener listener = new ExecuteTrackListener() { - @Override - public void onExecuteDone(boolean errors, String current_url, String sessionName) { - recording = false; - - if (errors) { - lblOutput.setText("Errore nell'esecuzione della traccia"); - } - - lblOutput.setText("Track recorded"); - - if (SAVE_TO_FILE) { - FileWriter w; - try { - w = new FileWriter(RECORD_FILE_PATH); - - for (HTTPReqRes actual : defaultSession.messages) { - Gson geson = new Gson(); - String serialized = geson.toJson(actual); - w.write(serialized + "\n"); - } - w.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - btnSetRecording.setBackground(Color.white); - btnSetRecording.setText("record"); - } - - @Override - public void onExecuteDone(boolean forceResult, String sessionName) { - - } - - @Override - public void onError(String sessionName) { - lblOutput.setText("Errore nell'esecuzione della traccia"); - } - - @Override - public Boolean onAskPause(String sessionName) { - return false; - } - - @Override - public Boolean onAskStop(String sessionName) { - return false; - } - - @Override - public Boolean onAskClearCookie(String sessionName) { - return false; - } - - @Override - public void onNextSessionAction(SessionTrackAction last_action, - SessionTrackAction last_open, - SessionTrackAction last_click, - String last_url, - String session_name) { - - } - - @Override - public Track onUpdateTrack(String sessionName) throws ParsingException { - return null; - } + JSONObject obj = new JSONObject(tmp); + obj.remove(key); + obj.put(key, value); - @Override - public void onSetVar(Var v) { - } - }; + FileWriter w = new FileWriter(CONFIG_FILE_PATH); + w.write(obj.toString()); + w.close(); - ExecuteTrack ex = new ExecuteTrack(false, - !btnselectChrome.isEnabled(), - DRIVER_PATH, - defaultSession.track, - defaultSession.port, - "main"); - ex.registerExecuteTrackListener(listener); - new Thread(ex).start(); - } else if ((returnVal == JFileChooser.ERROR) || (returnVal == JFileChooser.ERROR_OPTION)) { - lblOutput.setText("error in selecting output file"); - System.out.println("error in selecting output file"); - } else { - lbldriver.setText("Messages still not loaded"); + } catch (JSONException e) { + lblOutput.setText("Invalid config file"); + } catch (FileNotFoundException e) { + lblOutput.setText("Cannot find config file"); } + } else { + FileWriter w = new FileWriter(CONFIG_FILE_PATH); + w.write(Tools.getDefaultJSONConfig()); + w.close(); } - }); - - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.insets = new Insets(0, 0, 0, 0); - gbc.gridx = 5; - gbc.gridy = 2; - btnSetRecording.setPreferredSize(new Dimension(100, 20)); - trackContainer.add(btnSetRecording, gbc); - - // Button Execute track - btnExecuteTrack = new JButton("execute track"); - btnExecuteTrack.addActionListener(actionEvent -> { - defaultSession = new Session("default"); - try { - defaultSession.setTrackFromString(txtScript.getText()); - } catch (ParsingException e) { - lblOutput.setText("Error in parsing session track"); - return; - } - btnExecuteTrack.setText("executing.."); - interceptedMessages.clear(); - recording = true; - - ExecuteTrackListener listener = new ExecuteTrackListener() { - @Override - public void onExecuteDone(boolean errors, String current_url, String sessionName) { - recording = false; - - if (errors) { - lblOutput.setText("Errore nell'esecuzione della traccia"); - } - - lblOutput.setText("Track executed"); - - btnExecuteTrack.setText("execute track"); - } - - @Override - public void onExecuteDone(boolean forceResult, String sessionName) { + } catch (IOException e) { + e.printStackTrace(); + lblOutput.setText("cannot create message definition file"); + } + } - } + /** + * This function parses the given jsonInput string of the language + * + * @param jsonInput the json input + */ + private void readJSONinput(String jsonInput) { + sessions_names.clear(); + txtSearch.setBorder(BorderFactory.createEmptyBorder()); + setJSONError(false, ""); - @Override - public void onError(String sessionName) { - lblOutput.setText("Errore nell'esecuzione della traccia"); - } + try { + JSONObject obj = new JSONObject(jsonInput); + List tests = new ArrayList<>(); - @Override - public Boolean onAskPause(String sessionName) { - return false; - } + //Getting Test suite data + String suite_name = obj.getJSONObject("test suite").getString("name"); + String suite_description = obj.getJSONObject("test suite").getString("description"); - @Override - public Boolean onAskStop(String sessionName) { - return false; - } + if (obj.getJSONObject("test suite").has("filter messages")) { + FILTERING = obj.getJSONObject("test suite").getBoolean("filter messages"); + } - @Override - public Boolean onAskClearCookie(String sessionName) { - return null; - } + //Array of Tests + JSONArray arrTests = obj.getJSONArray("tests"); - @Override - public void onNextSessionAction(SessionTrackAction last_action, - SessionTrackAction last_open, - SessionTrackAction last_click, - String last_url, - String session_name) { - } + //scorro tutti i test + for (int i = 0; i < arrTests.length(); i++) { + JSONObject act_test = arrTests.getJSONObject(i).getJSONObject("test"); - @Override - public Track onUpdateTrack(String sessionName) throws ParsingException { - return null; - } + Test test = new Test(act_test, messageTypes); + tests.add(test); - @Override - public void onSetVar(Var v) { + for (Session s : test.sessions) { + if (!sessions_names.contains(s.name)) { + sessions_names.add(s.name); + session_port.put(s.name, "8080"); // set default port to session + } } - }; - - editConfigFile("last_browser_used", btnselectChrome.isEnabled() ? "firefox" : "chrome"); - - ExecuteTrack ex = new ExecuteTrack(false, - !btnselectChrome.isEnabled(), - DRIVER_PATH, - defaultSession.track, - defaultSession.port, - "main"); - ex.registerExecuteTrackListener(listener); - new Thread(ex).start(); - }); - - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.insets = new Insets(0, 0, 0, 0); - gbc.gridx = 5; - gbc.gridy = 3; - btnSetRecording.setPreferredSize(new Dimension(100, 20)); - trackContainer.add(btnExecuteTrack, gbc); + } + updateSessionTabs(); + updateTxtSessionConfig(); + //JSONArray result = obj.getJSONArray("Test Suite Result Table"); - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.insets = new Insets(0, 0, 0, 0); - gbc.gridx = 4; - gbc.gridy = 4; - trackContainer.add(new JLabel("Next test will use"), gbc); + this.testSuite = new TestSuite(suite_name, suite_description, tests); + lblInfo.setText("JSON read successfully, Test Suite Object has been created"); - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.insets = new Insets(20, 0, 0, 0); - gbc.gridx = 4; - gbc.gridy = 4; - trackContainer.add(lblnextTestBrowser, gbc); + } catch (ParsingException e) { + e.printStackTrace(); - JFileChooser messageLoader = new JFileChooser(); - btnLoadMessages = new JButton("load messages"); - btnLoadMessages.setEnabled(true); - btnLoadMessages.addActionListener(actionEvent -> { - if (btnLoadMessages.getBackground() == Color.GREEN) { - btnSetOffline.setEnabled(false); - btnSetOffline.setBackground(Color.white); - btnLoadMessages.setBackground(Color.white); - btnLoadMessages.setText("load messages"); - OFFLINE = false; - SAVE_FILE_PATH = ""; + setJSONError(true, "Problem in parsing JSON: " + e.getMessage()); + } catch (Exception e) { + e.printStackTrace(); - } else { - int returnVal = messageLoader.showOpenDialog(GUI.this); - if (returnVal == JFileChooser.APPROVE_OPTION) { - File file = messageLoader.getSelectedFile(); - SAVE_FILE_PATH = file.getPath(); - lblOutput.setText("Messages selected"); - btnLoadMessages.setBackground(Color.GREEN); - btnLoadMessages.setText("unload"); - btnSetOffline.setEnabled(true); - } else if ((returnVal == JFileChooser.ERROR) || (returnVal == JFileChooser.ERROR_OPTION)) { - lblOutput.setText("error in selecting messages"); - System.out.println("error in selecting messages"); - btnSetOffline.setEnabled(false); - btnLoadMessages.setBackground(Color.RED); - } else { - lbldriver.setText("Messages still not loaded"); - btnSetOffline.setEnabled(false); - btnLoadMessages.setBackground(Color.RED); - } - } - }); + setJSONError(true, "PROBLEM IN READING JSON, check it please"); + } - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.insets = new Insets(0, 0, 0, 0); - gbc.gridx = 6; - gbc.gridy = 1; - trackContainer.add(btnLoadMessages, gbc); + } - btnSetOffline = new JButton("offline mode"); - btnSetOffline.setEnabled(false); - btnSetOffline.addActionListener(actionEvent -> { - if (btnSetOffline.getBackground() == Color.green) { - OFFLINE = false; - btnSetOffline.setBackground(Color.white); - } else { - btnSetOffline.setBackground(Color.green); - OFFLINE = true; - } - }); - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.insets = new Insets(0, 0, 0, 0); - gbc.gridx = 6; - gbc.gridy = 2; - trackContainer.add(btnSetOffline, gbc); + /** + * This function reads the selected file deserializing the messages and creating a new Session + * + * @return if the reading has been succesfull + */ + private boolean readSavedMessages() { + if (!SAVE_FILE_PATH.isEmpty()) { + try { + if (defaultSession == null) { + defaultSession = new Session("default"); + defaultSession.isOffline = true; - btnSaveToFile = new JButton("save"); - btnSaveToFile.addActionListener(actionEvent -> { - if (SAVE_TO_FILE) { - FileWriter w; - try { - w = new FileWriter(RECORD_FILE_PATH); + File f = new File(SAVE_FILE_PATH); + Scanner r = new Scanner(f); - for (HTTPReqRes actual : defaultSession.messages) { - Gson geson = new Gson(); - String serialized = geson.toJson(actual); - w.write(serialized + "\n"); + Gson json = new Gson(); + while (r.hasNextLine()) { + HTTPReqRes tmp = json.fromJson(r.nextLine(), HTTPReqRes.class); + defaultSession.messages.add(tmp); } - w.close(); - } catch (IOException e) { - e.printStackTrace(); + } else { + System.out.println("main session already created, skipping message reading from file"); } + return true; + } catch (FileNotFoundException fileNotFoundException) { + fileNotFoundException.printStackTrace(); + return false; } - }); - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.insets = new Insets(0, 0, 0, 0); - gbc.gridx = 6; - gbc.gridy = 3; - trackContainer.add(btnSaveToFile, gbc); - - this.setTopComponent(trackContainer); - - // Bottom part ------------------------------------------------------------------------------------------------- + } else { + return false; + } + } - bot_tabbed = new JTabbedPane(); + /** + * Method which executes the entire test suite + */ + private void executeSuite() { + // clears all previously saved tests + actives.clear(); + passives.clear(); + act_active_op = null; + ex = null; + active_ex_finished = false; - // Input Search Tabm - GridBagLayout gridBagLayout3 = new GridBagLayout(); - gridBagLayout3.columnWidths = new int[]{1000, 100}; - gridBagLayout3.rowHeights = new int[]{15, 20, 20, 20, 30}; - gridBagLayout3.columnWeights = new double[]{Double.MIN_VALUE, 0.0}; - gridBagLayout3.rowWeights = new double[]{0.0, Double.MIN_VALUE, 0.0, 0.0, 0.0}; + // clears the test suite result table + DefaultTableModel dm = (DefaultTableModel) resultTable.getModel(); + dm.getDataVector().removeAllElements(); + dm.fireTableDataChanged(); - inputContainer = new JPanel(); - inputContainer.setLayout(gridBagLayout3); + System.out.println("Number of test found: " + testSuite.getTests().size()); + for (Test t : testSuite.getTests()) { + if (t.isActive) { + actives.add(t); + } else { + passives.add(t); + } + } - lblInfo = new JLabel(" "); - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.insets = new Insets(10, 10, 10, 10); - gbc.gridx = 0; - gbc.gridy = 0; - gbc.gridwidth = 2; - inputContainer.add(lblInfo, gbc); + if (OFFLINE) { + if (!readSavedMessages()) { + System.err.println("Can't read message file"); + lblOutput.setText("Can't read message file"); + return; + } + }//TODO: re-enable OFFLINE mode + /* else if (passives.size() > 0 && defaultSession == null && actives.size() == 0) { + lblOutput.setText("Track need to be run for passive tests before executing tests"); + return; + } + */ - lblOutput = new JLabel(" "); - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.insets = new Insets(10, 10, 10, 10); - gbc.gridx = 0; - gbc.gridy = 4; - gbc.gridwidth = 2; - inputContainer.add(lblOutput, gbc); + if (actives.size() == 0) { + synchronized (lock2) { + active_ex_finished = true; + } + } - txtSearch = new JTextArea(); - txtSearch.append(""); + //FIXME: Passives thread starts without waiting for the end of actives one + // Execute active tests + if (actives.size() != 0) { + try { + for (String key : session_port.keySet()) { + if (session_port.get(key).equals("")) { + lblOutput.setText("session port not configured"); + return; + } + } + ex = new ExecuteActives(actives, waiting); - JScrollPane scrollPane2 = new JScrollPane(txtSearch, - JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.fill = GridBagConstraints.BOTH; - gbc.insets = new Insets(10, 10, 10, 10); - gbc.gridx = 0; - gbc.gridy = 1; - gbc.gridwidth = 1; - gbc.gridheight = 3; - inputContainer.add(scrollPane2, gbc); + editConfigFile("last_browser_used", btnselectChrome.isEnabled() ? "firefox" : "chrome"); - JButton btnReadJSON = new JButton("Read JSON"); - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.insets = new Insets(10, 10, 10, 10); - gbc.gridx = 1; - gbc.gridy = 2; - inputContainer.add(btnReadJSON, gbc); + ex.registerExecuteActivesListener(new ExecuteActiveListener() { + @Override + public void onExecuteStart() { + ACTIVE_ENABLED = false; + act_active_op = new Operation(); + lblOutput.setText("Executing active tests"); + } - btnReadJSON.addActionListener(e -> { - testSuite = new TestSuite(); + @Override + public void onExecuteDone() { + if (passives.size() == 0) { + update_gui_test_results(); - readMsgDefFile(); // Updates the Message Definitions + lblOutput.setText("Done. Executed Passive Tests: " + + (passives.isEmpty() ? 0 : passives.size()) + + " - Active Tests: " + + (testSuite.getTests().size() - (passives.isEmpty() ? 0 : passives.size()))); + } else { + lblOutput.setText("Executed Active tests, now doing passives"); + } + synchronized (lock2) { + active_ex_finished = true; + } + } - readJSONinput(txtSearch.getText()); - // if there's only a session, set the default port - // TODO: Check if this is useful or just call updateTxtSessionConfig - /* - if (session_port.size() > 0) { - Integer port = 8080; - for (String ses_name : session_port.keySet()) { - if (session_port.get(ses_name).equals("")) { - session_port.replace(ses_name, port.toString()); - port++; + @Override + public void onNewProcessOperation(Operation op) { + ACTIVE_ENABLED = true; + act_active_op = op; } - } - String tmp = ""; - for (String key : session_port.keySet()) { - tmp += key + ": " + session_port.get(key) + ";\n"; - } + @Override + public Operation onOperationDone() { + ACTIVE_ENABLED = false; + Operation tmp = act_active_op; - txtSessionConfig.setText(tmp); - } - */ - try { - updateTxtSessionConfig(); - } catch (ParsingException exc) { - setJSONError(true, "error in updating the session config"); - } + act_active_op = new Operation(); + return tmp; + } - lblOutput.setText("Number of Tests: " + testSuite.getTests().size()); + @Override + public Session onNewSession(Session s) { + Track track = null; + try { + track = s.setTrackFromString(getSessionTxt(s.name)); + } catch (ParsingException e) { + lblOutput.setText("error in parsing session track"); + return null; + } + String port = session_port.get(s.name); + s.port = port; + s.track = track; - if (testSuite.getTests().size() > 0) { - btnExecuteSuite.setEnabled(true); - } - }); + s.ex = new ExecuteTrack(false, + !btnselectChrome.isEnabled(), + DRIVER_PATH, + track, + port, + s.name); + return s; + } - JButton btnStop = new JButton("Stop"); - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.insets = new Insets(10, 10, 10, 10); - gbc.gridx = 1; - gbc.gridy = 1; - inputContainer.add(btnStop, gbc); + @Override + public void onNewTest(Test actual_test) { + act_active_op = null; + } - btnStop.addActionListener(e -> { - if (active_ex != null) { - active_ex.interrupt(); - ACTIVE_ENABLED = false; - active_ex.stop(); - } - }); + public void onTestDone(Test actual_test) { + int indx = testSuite.tests.indexOf(actual_test); + if (indx != -1) { + System.out.printf("Saving test %s in test results", actual_test.getName()); + //TODO: add log of sessions + testSuite.tests.set(indx, actual_test); + actual_test.logTest(LOG_FOLDER); + } + } - btnExecuteSuite = new JButton("Execute Test Suite"); - btnExecuteSuite.setEnabled(false); + @Override + public void onError(Test actual_test) { + System.err.println("Error executing the test:" + actual_test.name); + synchronized (lock2) { + active_ex_finished = true; + } + } + }); + active_ex = new Thread(ex); + active_ex.start(); - btnExecuteSuite.addActionListener(e -> { - executeSuite(); - }); + } catch (Exception er) { + er.printStackTrace(); + System.out.println(er.getLocalizedMessage() + "nad" + er.getMessage() + "2" + er); - gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.SOUTHWEST; - gbc.insets = new Insets(10, 10, 10, 10); - gbc.gridx = 1; - gbc.gridy = 3; - inputContainer.add(btnExecuteSuite, gbc); - bot_tabs_index.put("Input JSON", 0); - // Add Input Search tab - bot_tabbed.addTab("Input JSON", inputContainer); + lblOutput.setText("PROBLEM IN Executing Suite, check it please"); + } + } - // Test Suite Result Tab - resultTableModel = new DefaultTableModel(foundData, testSuiteColNames) { - @Override - public boolean isCellEditable(int row, int column) { - return false; + // Execute passive tests + if (passives.size() != 0) { + // TODO: Add offline clause + /* + if (defaultSession.messages.size() == 0) { + lblOutput.setText("no message found"); + return; } - }; + */ - resultTable = new JTable(resultTableModel) { - //Implement table cell tool tips. - public String getToolTipText(MouseEvent e) { - String tip = null; - java.awt.Point p = e.getPoint(); - int rowIndex = rowAtPoint(p); - int colIndex = columnAtPoint(p); + ExecutePassiveListener listener = new ExecutePassiveListener() { + @Override + public boolean onWaitToStart() { + synchronized (lock2) { + return active_ex_finished; + } + } - try { - tip = getValueAt(rowIndex, colIndex).toString(); - } catch (RuntimeException e1) { - //catch null pointer exception if mouse is over an empty line + @Override + public void onExecuteStart() { + lblOutput.setText("Executing passive tests"); } - return tip; - } - }; - resultTable.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() { - @Override - public Component getTableCellRendererComponent(JTable table, - Object value, - boolean isSelected, - boolean hasFocus, - int row, - int column) { - final Component c = super.getTableCellRendererComponent(table, - value, - isSelected, - hasFocus, - row, - column); - if (value == null) return c; - if (value.equals("failed")) { - c.setBackground(Color.RED); - } else { - c.setBackground(Color.WHITE); + @Override + public void onExecuteDone(List passives_test) { + //TODO: Check if this is ok + lblOutput.setText("Done. Executed Passive Tests: " + + (passives.isEmpty() ? 0 : passives.size()) + + " - Active Tests: " + + (testSuite.getTests().size() - (passives.isEmpty() ? 0 : passives.size()))); + + passives = passives_test; + + update_gui_test_results(); } - return c; - } - }); - JScrollPane scrollPane = new JScrollPane(resultTable, - JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + @Override + public void onError(String msg) { + lblOutput.setText(msg); + } - resultTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + @Override + public Session onNewSession(Session s) throws ParsingException { + //TODO: implement + s.setTrackFromString(getSessionTxt(s.name)); - // Adds all the test result to the result table - resultTable.getSelectionModel().addListSelectionListener(event -> { - if (resultTable.getSelectedRow() > -1) { + String port = session_port.get(s.name); + s.port = port; - int row = resultTable.getSelectedRow(); - //BurpSuite.getTests.get(row).getTable(); + s.ex = new ExecuteTrack(false, + !btnselectChrome.isEnabled(), + DRIVER_PATH, + s.track, + port, + s.name); - DefaultTableModel dm = (DefaultTableModel) testTable.getModel(); - dm.getDataVector().removeAllElements(); - dm.fireTableDataChanged(); + return s; + } - for (String[] act : testSuite.getTests().get(row).getRows()) { + @Override + public void onBeforeExecuteTrack() { + //Clear previous interceptedMessages if any + interceptedMessages.clear(); + //Tell Burp Extender class to record the intercepted messages from now on + recording = true; + } - ((DefaultTableModel) testTable.getModel()).addRow(act); + @Override + public ArrayList onTrackExecuteDone() { + recording = false; + return interceptedMessages; } - } - }); + }; - //Add Search Result Tab - bot_tabs_index.put("Test Suite Result", 1); - bot_tabbed.addTab("Test Suite Result", scrollPane); + ExecutePassives expa = new ExecutePassives(helpers, + passives, + listener, + messageTypes); - // Test Result Tab - testTableModel = new DefaultTableModel(foundData, foundTableColNames) { - @Override - public boolean isCellEditable(int row, int column) { - return false; + new Thread(expa).start(); + } + } + + /** + * Function used to get the text of the text area of a certain session using the session name + * + * @param session_name the name of the session to get the text + * @return the content of the session tab if it is not empty, otherwise the main session text tab + */ + private String getSessionTxt(String session_name) { + if (sessions_text.containsKey(session_name)) { + JTextArea t = (JTextArea) sessions_text.get(session_name); + if (t.getText().equals("")) { + return txtScript.getText(); + } else { + return t.getText(); } - }; + } + return null; + } - testTable = new JTable(testTableModel); - // Add all the operations result to the table - testTable.getSelectionModel().addListSelectionListener(listSelectionEvent -> { - if (!listSelectionEvent.getValueIsAdjusting()) { - if (testTable.getSelectedRow() == -1) return; - int index = Integer.parseInt((String) testTable.getModel().getValueAt(testTable.getSelectedRow(), 4)); - int op_index = Integer.parseInt((String) testTable.getModel().getValueAt(testTable.getSelectedRow(), 0)); + /** + * Update the session config tab, using the session_port variable, at the same time reads if there are changes, and + * updates the session_port variable + */ + private void updateTxtSessionConfig() throws ParsingException { + setSession_configError(false, ""); - Operation op = testSuite.tests.get(resultTable.getSelectedRow()).operations.get(op_index); - for (Operation.MatchedMessage m : op.matchedMessages) { - if (m.index == index) { - if (m.isRequest) { - messageViewer.setMessage(m.message.getRequest(), true); - } else { - messageViewer.setMessage(m.message.getResponse(), false); - } - break; - } - } + String text = txtSessionConfig.getText(); + Pattern p = Pattern.compile("\\n"); + Matcher m = p.matcher(text); + text = m.replaceAll(""); + + if (text.equals("")) { + String tmp = ""; + for (String s : session_port.keySet()) { + tmp += s; + tmp += ":" + session_port.get(s) + ";\n"; } - }); + txtSessionConfig.setText(tmp); + return; + } - JScrollPane scrollPane3 = new JScrollPane(testTable, - JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + String[] text_list = text.split(";"); - splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + for (String row : text_list) { + String[] splitted = row.trim().split(":"); + if (splitted.length == 0) continue; + if (splitted.length <= 1) { + String[] splitted2 = new String[]{"", ""}; + splitted2[0] = splitted[0]; + splitted = splitted2; + } + String port = splitted[1].trim(); + p = Pattern.compile("^\\d+$"); + m = p.matcher(splitted[1].trim()); + if (!m.find()) { + setSession_configError(true, "invalid port"); - splitPane.setLeftComponent(scrollPane3); + throw new ParsingException("Invalid port"); + } + if (session_port.containsKey(splitted[0].trim())) { + session_port.replace(splitted[0].trim(), splitted[1].trim()); + } else { + session_port.put((splitted[0]).trim(), splitted[1].trim()); + } + } + String tmp = ""; + for (String key : session_port.keySet()) { + tmp += key + ": " + session_port.get(key) + ";\n"; + } - controller = new IMessageEditorController() { - @Override - public IHttpService getHttpService() { - return new IHttpService() { - @Override - public String getHost() { - return null; - } + txtSessionConfig.setText(tmp); + } - @Override - public int getPort() { - return 0; - } + /** + * This function updates the session tabs in the gui to match the actual value in session_names + */ + private void updateSessionTabs() { + List present = new ArrayList<>(); - @Override - public String getProtocol() { - return null; - } - }; + for (int i = 1; i < top_tabbed.getTabCount(); i++) { + present.add(top_tabbed.getTitleAt(i)); + if (!sessions_names.contains(top_tabbed.getTitleAt(i))) { + top_tabbed.remove(i); } - - @Override - public byte[] getRequest() { - return viewedMessage.getRequest(); + } + for (String name : sessions_names) { + if (!present.contains(name)) { + JTextArea tmp = new JTextArea(); + JScrollPane sp = new JScrollPane(tmp, + JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, + JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + sessions_text.put(name, tmp); + top_tabbed.add(name, sp); } + } + JTextArea tmp = new JTextArea(); + JScrollPane sp = new JScrollPane(tmp, + JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, + JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + } - @Override - public byte[] getResponse() { - return viewedMessage.getResponse(); + /** + * Function used to set the JSON textbox with a red colour to highlight an error. + * + * @param isInError true to highlight an error, false to remove the highlight + * @param msg the error message to display + */ + private void setJSONError(boolean isInError, String msg) { + if (isInError) { + txtSearch.setBorder(BorderFactory.createLineBorder(Color.RED, 3)); + lblOutput.setText(msg); + bot_tabbed.setBackgroundAt(bot_tabs_index.get("Input JSON"), Color.RED); + } else { + txtSearch.setBorder(BorderFactory.createEmptyBorder()); + lblOutput.setText(""); + bot_tabbed.setBackgroundAt(bot_tabs_index.get("Input JSON"), Color.white); + } + } + + /** + * Function used to set the session config textbox with a red border to highlight an error. + * + * @param isInError true to highlight an error, false to remove highlight + * @param msg The error message to display + */ + private void setSession_configError(boolean isInError, String msg) { + if (isInError) { + txtSessionConfig.setBorder(BorderFactory.createLineBorder(Color.RED, 3)); + lblOutput.setText(msg); + bot_tabbed.setBackgroundAt(bot_tabs_index.get("session config"), Color.RED); + } else { + txtSessionConfig.setBorder(BorderFactory.createEmptyBorder()); + lblOutput.setText(""); + bot_tabbed.setBackgroundAt(bot_tabs_index.get("session config"), Color.white); + } + } + + /** + * Function used to update the gui test results after the tests are executed + */ + private void update_gui_test_results() { + for (Test t : testSuite.getTests()) { + String esito = ""; + if (t.applicable) { + esito = t.success ? "passed" : "failed"; + } else { + esito = "not applicable"; } - }; - - //Add Search Result Tab - bot_tabs_index.put("Test Result", 2); - bot_tabbed.addTab("Test Result", splitPane); + String[] tmp = new String[]{t.getName(), + t.getDescription(), + t.references, + t.violated_properties, + t.affected_entity, + t.mitigations, + esito}; + System.out.println(t.getName() + " " + esito); + addItem(tmp); + } + btnExecuteSuite.setEnabled(false); + } - // Metadata tab - JPanel sessionConfig = new JPanel(); - sessionConfig.setLayout(gridBagLayout3); + private void setup_tab_track() { + lblTrack = new JLabel("Session track "); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.insets = new Insets(10, 0, 0, 0); + gbc.gridx = 0; + gbc.gridy = 0; + trackContainer.add(lblTrack, gbc); - txtSessionConfig = new JTextArea(); - JScrollPane scrollPane5 = new JScrollPane(txtSessionConfig, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + txtScript = new JTextArea(); gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.BOTH; - gbc.insets = new Insets(10, 10, 10, 10); + gbc.insets = new Insets(10, 0, 0, 10); gbc.gridx = 0; gbc.gridy = 1; - gbc.gridwidth = 1; - gbc.gridheight = 3; - sessionConfig.add(scrollPane5, gbc); + gbc.gridwidth = 4; + gbc.gridheight = 4; - JButton btnSetSessionConfig = new JButton("save"); - btnSetSessionConfig.addActionListener(actionEvent -> { - try { - updateTxtSessionConfig(); - } catch (ParsingException e) { - e.printStackTrace(); + JScrollPane scrollPane1 = new JScrollPane(txtScript, + JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, + JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + + top_tabbed = new JTabbedPane(); + top_tabbed.add("main", scrollPane1); + trackContainer.add(top_tabbed, gbc); + } + + private void setup_tab_butons() { + driverSelector = new JFileChooser(); + btndriverSelector = new JButton("Select Driver"); + + btndriverSelector.addActionListener(actionEvent -> { + int returnVal = driverSelector.showOpenDialog(GUI.this); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File file = driverSelector.getSelectedFile(); + DRIVER_PATH = file.getPath(); + editConfigFile("last_driver_path", DRIVER_PATH); + lbldriver.setText("Driver Selected"); + btndriverSelector.setBackground(Color.GREEN); + btnTestTrack.setEnabled(true); + } else if ((returnVal == JFileChooser.ERROR) || (returnVal == JFileChooser.ERROR_OPTION)) { + lbldriver.setText("Driver:error during file selection"); + System.out.println("error during file selection"); + btnTestTrack.setEnabled(false); + + btndriverSelector.setBackground(Color.RED); + } else { + lbldriver.setText("Driver file still not selected"); + btnTestTrack.setEnabled(false); + btndriverSelector.setBackground(Color.RED); } }); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(0, 0, 0, 10); + gbc.gridx = 4; + gbc.gridy = 1; + trackContainer.add(btndriverSelector, gbc); + lbldriver = new JLabel("Driver file still not selected"); gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.fill = GridBagConstraints.BOTH; - gbc.insets = new Insets(10, 10, 10, 10); - gbc.gridx = 1; - gbc.gridy = 3; - gbc.gridwidth = 1; - gbc.gridheight = 1; - sessionConfig.add(btnSetSessionConfig, gbc); + gbc.anchor = GridBagConstraints.SOUTHEAST; + gbc.insets = new Insets(10, 0, 0, 30); + gbc.gridx = 3; + gbc.gridy = 0; + trackContainer.add(lbldriver, gbc); - bot_tabs_index.put("session config", 3); - bot_tabbed.addTab("session config", sessionConfig); + btnTestTrack = new JButton("Test track"); + btnTestTrack.setEnabled(true); - //Set Bottom Part - this.setBottomComponent(bot_tabbed); + btnTestTrack.addActionListener(e -> { + ExecuteTrackListener listener = new ExecuteTrackListener() { + @Override + public void onExecuteDone(boolean errors, String current_url, String sessionName) { + if (errors) { + lblOutput.setText("Error in executing track"); + } else { + lblOutput.setText("Track Executed correctly"); + } + } - readMsgDefFile(); - readConfigFile(); - if (!DRIVER_PATH.equals("")) { - lbldriver.setText("Driver Selected"); - btndriverSelector.setBackground(Color.GREEN); - btnTestTrack.setEnabled(true); - } - } + @Override + public void onExecuteDone(boolean forceResult, String sessionName) { + if (forceResult) { + lblOutput.setText("Track Executed correctly"); + } else { + lblOutput.setText("Error in executing track"); + } + } - /** - * Function used to add an item to the resultTableModel. Contains the results of the tests - * - * @param data the string array containing the data, also a row - */ - private static void addItem(String[] data) { - resultTableModel.addRow(data); - } + @Override + public void onError(String sessionName) { + lblOutput.setText("Error in executing track"); + } - /** - * Function used to read the message definition file - */ - private void readMsgDefFile() { - File msg_def_file = new File(MSG_DEF_PATH); - try { - if (!msg_def_file.createNewFile()) { - Scanner myReader = null; - String tmp = ""; - try { - myReader = new Scanner(msg_def_file); - while (myReader.hasNextLine()) { - tmp += myReader.nextLine(); - } - myReader.close(); - messageTypes = Utils.readMsgTypeFromJson(tmp); - } catch (ParsingException e) { - lblOutput.setText("Invalid message type in message type definition file"); - e.printStackTrace(); - } catch (FileNotFoundException e) { - lblOutput.setText("Cannot find message definition file"); + @Override + public Boolean onAskPause(String sessionName) { + return false; } - } else { - FileWriter w = new FileWriter(MSG_DEF_PATH); - w.write(Utils.getDefaultJSONMsgType()); - w.close(); - messageTypes = Utils.readMsgTypeFromJson(Utils.getDefaultJSONMsgType()); - } - } catch (ParsingException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - lblOutput.setText("cannot create message definition file"); - } - } - /** - * Function used to read the json config file - */ - private void readConfigFile() { - File config_file = new File(CONFIG_FILE_PATH); - try { - if (!config_file.createNewFile()) { - Scanner myReader = null; - String tmp = ""; - try { - myReader = new Scanner(config_file); - while (myReader.hasNextLine()) { - tmp += myReader.nextLine(); - } - myReader.close(); + @Override + public Boolean onAskStop(String sessionName) { + return false; + } - JSONObject obj = new JSONObject(tmp); - String last_driver_path = obj.getString("last_driver_path"); - String last_used_browser = obj.getString("last_browser_used"); + @Override + public Boolean onAskClearCookie(String sessionName) { + return null; + } - if (!last_driver_path.equals("")) { - DRIVER_PATH = last_driver_path; - } + @Override + public void onNextSessionAction(SessionTrackAction last_action, + SessionTrackAction last_open, + SessionTrackAction last_click, + String last_url, + String session_name) { - switch (last_used_browser) { - case "firefox": { - btnselectChrome.setEnabled(true); - btnselectFirefox.setEnabled(false); - break; - } - case "chrome": { - btnselectChrome.setEnabled(false); - btnselectFirefox.setEnabled(true); - break; - } - } + } - } catch (JSONException e) { - lblOutput.setText("Invalid config file"); - } catch (FileNotFoundException e) { - lblOutput.setText("Cannot find config file"); + @Override + public Track onUpdateTrack(String sessionName) throws ParsingException { + return null; } - } else { - FileWriter w = new FileWriter(CONFIG_FILE_PATH); - w.write(Utils.getDefaultJSONConfig()); - w.close(); + + @Override + public void onSetVar(Var v) { + } + }; + recording = false; + defaultSession = new Session("temp"); + + Track track = null; + try { + track = defaultSession.setTrackFromString(txtScript.getText()); + } catch (ParsingException exc) { + lblOutput.setText("Error in parsing session track"); } - } catch (IOException e) { - e.printStackTrace(); - lblOutput.setText("cannot create message definition file"); - } - } - /** - * Function that edits the config file. - * @param key the key of the config to change - * @param value the new value of the config - */ - private void editConfigFile(String key, String value) { - File config_file = new File(CONFIG_FILE_PATH); - try { - if (!config_file.createNewFile()) { - Scanner myReader = null; - String tmp = ""; - try { - myReader = new Scanner(config_file); - while (myReader.hasNextLine()) { - tmp += myReader.nextLine(); - } - myReader.close(); + defaultSession = null; + ExecuteTrack ex = new ExecuteTrack(false, + !btnselectChrome.isEnabled(), + DRIVER_PATH, + track, + "8080", + "test"); + ex.registerExecuteTrackListener(listener); + new Thread(ex).start(); + }); + + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(0, 0, 0, 0); + gbc.gridx = 5; + gbc.gridy = 1; + btnTestTrack.setPreferredSize(new Dimension(100, 20)); + trackContainer.add(btnTestTrack, gbc); - JSONObject obj = new JSONObject(tmp); - obj.remove(key); - obj.put(key, value); + btnselectChrome = new JButton("Use Chrome"); + btnselectChrome.setEnabled(false); + btnselectChrome.addActionListener(actionEvent -> { + btnTestTrack.setEnabled(false); + lbldriver.setText("Driver file still not selected"); + btndriverSelector.setBackground(Color.RED); - FileWriter w = new FileWriter(CONFIG_FILE_PATH); - w.write(obj.toString()); - w.close(); + btnselectChrome.setEnabled(false); + btnselectFirefox.setEnabled(true); + lblnextTestBrowser.setText("Chrome"); + }); + lblnextTestBrowser = new JLabel("Firefox"); + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(0, 0, 0, 0); + gbc.gridx = 4; + gbc.gridy = 2; + trackContainer.add(btnselectChrome, gbc); - } catch (JSONException e) { - lblOutput.setText("Invalid config file"); - } catch (FileNotFoundException e) { - lblOutput.setText("Cannot find config file"); - } - } else { - FileWriter w = new FileWriter(CONFIG_FILE_PATH); - w.write(Utils.getDefaultJSONConfig()); - w.close(); - } - } catch (IOException e) { - e.printStackTrace(); - lblOutput.setText("cannot create message definition file"); - } - } + btnselectFirefox = new JButton("Use Firefox"); + btnselectFirefox.setEnabled(true); + btnselectFirefox.addActionListener(actionEvent -> { + btnTestTrack.setEnabled(false); + lbldriver.setText("Driver file still not selected"); + btndriverSelector.setBackground(Color.RED); - /** - * This function parses the given jsonInput string of the language - * - * @param jsonInput the json input - */ - private void readJSONinput(String jsonInput) { - sessions_names.clear(); - //BurpExtender.printStream.println("" + obj.getJSONObject("Search").getString("Delete")); - txtSearch.setBorder(BorderFactory.createEmptyBorder()); - setJSONError(false, ""); - try { - JSONObject obj = new JSONObject(jsonInput); - List tests = new ArrayList<>(); + btnselectFirefox.setEnabled(false); + btnselectChrome.setEnabled(true); + lblnextTestBrowser.setText("Firefox"); + }); - //Getting Test suite data - String suite_name = obj.getJSONObject("test suite").getString("name"); - String suite_description = obj.getJSONObject("test suite").getString("description"); - boolean metadata = false; - if (obj.getJSONObject("test suite").has("metadata")) { - metadata = obj.getJSONObject("test suite").getBoolean("metadata"); + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(0, 0, 0, 0); + gbc.gridx = 4; + gbc.gridy = 3; + btnselectFirefox.setPreferredSize(new Dimension(100, 20)); + trackContainer.add(btnselectFirefox, gbc); - } + JFileChooser messageSaver = new JFileChooser(); + btnSetRecording = new JButton("Record"); + btnSetRecording.setEnabled(true); - if (obj.getJSONObject("test suite").has("filter messages")) { - FILTERING = obj.getJSONObject("test suite").getBoolean("filter messages"); - } + // Recording button listener + btnSetRecording.addActionListener(actionEvent -> { + if (btnSetRecording.isEnabled()) { + int returnVal = messageSaver.showOpenDialog(GUI.this); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File file = messageSaver.getSelectedFile(); + RECORD_FILE_PATH = file.getPath(); + lblOutput.setText("File selected"); - //Array of Tests - JSONArray arrTests = obj.getJSONArray("tests"); + SAVE_TO_FILE = true; - //scorro tutti i test - for (int i = 0; i < arrTests.length(); i++) { - JSONObject act_test = arrTests.getJSONObject(i).getJSONObject("test"); + defaultSession = new Session("default"); + try { + defaultSession.setTrackFromString(txtScript.getText()); + } catch (ParsingException e) { + lblOutput.setText("Error in parsing session track"); + return; + } + + btnSetRecording.setBackground(new Color(255, 0, 0)); + btnSetRecording.setText("recording.."); - Test test = new Test(); + recording = true; - test.setDescription(act_test.getString("description")); - test.setName(act_test.getString("name")); - test.setType(act_test.getString("type")); + ExecuteTrackListener listener = new ExecuteTrackListener() { + @Override + public void onExecuteDone(boolean errors, String current_url, String sessionName) { + recording = false; - Iterator keys = act_test.keys(); - while (keys.hasNext()) { - String key = keys.next(); + if (errors) { + lblOutput.setText("Errore nell'esecuzione della traccia"); + } - switch (key) { - case "name": - case "type": - case "description": - case "result": - case "operations": - case "sessions": - break; - case "references": - test.references = act_test.getString("references"); - break; - case "violated_properties": - test.violated_properties = act_test.getString("violated_properties"); - break; - case "mitigations": - test.mitigations = act_test.getString("mitigations"); - break; - case "affected_entity": - test.affected_entity = act_test.getString("affected_entity"); - break; - default: - throw new ParsingException("Invalid key \"" + key + "\""); - } - } + lblOutput.setText("Track recorded"); - if (test.isActive) { - if (act_test.has("result")) { - String tmp = act_test.getString("result"); - if (tmp.contains("assert_only")) { - test.result = Utils.ResultType.fromString(tmp); - } else { - tmp = tmp.trim(); - String[] splitted = tmp.split("flow"); + if (SAVE_TO_FILE) { + FileWriter w; + try { + w = new FileWriter(RECORD_FILE_PATH); - if (splitted.length > 1) { - test.resultSession = splitted[1].trim(); + for (HTTPReqRes actual : defaultSession.messages) { + Gson geson = new Gson(); + String serialized = geson.toJson(actual); + w.write(serialized + "\n"); + } + w.close(); + } catch (IOException e) { + e.printStackTrace(); + } } - test.result = Utils.ResultType.fromString(splitted[0].trim()); + btnSetRecording.setBackground(Color.white); + btnSetRecording.setText("record"); } - } - } - if (act_test.has("sessions")) { - JSONArray arrSess = act_test.getJSONArray("sessions"); - Iterator it = arrSess.iterator(); - while (it.hasNext()) { - String act_sess_name = (String) it.next(); + @Override + public void onExecuteDone(boolean forceResult, String sessionName) { - if (!sessions_names.contains(act_sess_name)) { - sessions_names.add(act_sess_name); - // Default is 8080 - session_port.put(act_sess_name, "8080"); } - test.sessions.add(new Session(act_sess_name)); - } + @Override + public void onError(String sessionName) { + lblOutput.setText("Errore nell'esecuzione della traccia"); + } - } else { - test.sessions.add(defaultSession); - } + @Override + public Boolean onAskPause(String sessionName) { + return false; + } - //Array of Operations - JSONArray arrOps = act_test.getJSONArray("operations"); + @Override + public Boolean onAskStop(String sessionName) { + return false; + } - //Reads all the operations - for (int j = 0; j < arrOps.length(); j++) { - JSONObject act_operation = arrOps.getJSONObject(j); + @Override + public Boolean onAskClearCookie(String sessionName) { + return false; + } - Operation op = new Operation(); + @Override + public void onNextSessionAction(SessionTrackAction last_action, + SessionTrackAction last_open, + SessionTrackAction last_click, + String last_url, + String session_name) { - // Test non attivo - if (!test.isActive) { - if (act_operation.has("decode param")) { - op.decode_param = act_operation.getString("decode param"); + } - JSONArray encodings = act_operation.getJSONArray("encoding"); - Iterator it = encodings.iterator(); + @Override + public Track onUpdateTrack(String sessionName) throws ParsingException { + return null; + } - while (it.hasNext()) { - String act_enc = (String) it.next(); - op.encodings.add( - Utils.Encoding.fromString(act_enc)); - } + @Override + public void onSetVar(Var v) { } - if (act_operation.has("regex")) { - // regex version - op.isRegex = true; - op.setRegex(act_operation.getString("regex")); - op.setMessageType(act_operation.getString("message type"), messageTypes); - op.setMessageSection(Utils.MessageSection.fromString(act_operation.getString("message section"))); - } else { - //non regex version - op.setMessageType(act_operation.getString("message type"), messageTypes); + }; - JSONArray checks = act_operation.getJSONArray("checks"); + ExecuteTrack ex = new ExecuteTrack(false, + !btnselectChrome.isEnabled(), + DRIVER_PATH, + defaultSession.track, + defaultSession.port, + "main"); + ex.registerExecuteTrackListener(listener); + new Thread(ex).start(); - if (act_operation.has("message section")) { - op.setMessageSection(Utils.MessageSection.fromString(act_operation.getString("message section"))); - } - op.setChecks(Utils.parseChecksFromJSON(checks)); - } - } else { - // If the test is active - if (act_operation.has("session")) { - // If is an operation to control a session - String session = act_operation.getString("session"); - String action = act_operation.getString("action"); - - List lsop = SessionOperation.parseFromJson(act_operation); - if (lsop != null) { - for (SessionOperation sop : lsop) { - op.session_operations.add(sop); - } - } + } else if ((returnVal == JFileChooser.ERROR) || (returnVal == JFileChooser.ERROR_OPTION)) { + lblOutput.setText("error in selecting output file"); + System.out.println("error in selecting output file"); + } else { + lbldriver.setText("Messages still not loaded"); + } + } + }); - op.setSession(session); - op.setSessionAction(action); - op.isSessionOp = true; - test.operations.add(op); - continue; - } + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(0, 0, 0, 0); + gbc.gridx = 5; + gbc.gridy = 2; + btnSetRecording.setPreferredSize(new Dimension(100, 20)); + trackContainer.add(btnSetRecording, gbc); - // If is a standard operation - String action = act_operation.getString("action"); - op.setAction(action); - - if (op.getAction() == Utils.Action.VALIDATE) { - if (act_operation.has("match")) { - String toMatch = act_operation.getString("match"); - if (toMatch.equals("all")) op.to_match = -1; - else op.to_match = Integer.parseInt(toMatch); - } else { - op.to_match = 1; - } + // Button Execute track + btnExecuteTrack = new JButton("execute track"); + btnExecuteTrack.addActionListener(actionEvent -> { + defaultSession = new Session("default"); + try { + defaultSession.setTrackFromString(txtScript.getText()); + } catch (ParsingException e) { + lblOutput.setText("Error in parsing session track"); + return; + } + btnExecuteTrack.setText("executing.."); + interceptedMessages.clear(); + recording = true; - if (act_operation.has("regex")) { - // regex version - op.isRegex = true; - op.setRegex(act_operation.getString("regex")); - op.setMessageType(act_operation.getString("message type"), messageTypes); - op.setMessageSection( - Utils.MessageSection.fromString( - act_operation.getString("message section"))); - } else { - //non regex version - op.setMessageType(act_operation.getString("message type"), messageTypes); - JSONArray checks = act_operation.getJSONArray("checks"); - - op.setChecks(Utils.parseChecksFromJSON(checks)); - } - } + ExecuteTrackListener listener = new ExecuteTrackListener() { + @Override + public void onExecuteDone(boolean errors, String current_url, String sessionName) { + recording = false; + + if (errors) { + lblOutput.setText("Errore nell'esecuzione della traccia"); + } + + lblOutput.setText("Track executed"); + + btnExecuteTrack.setText("execute track"); + } + + @Override + public void onExecuteDone(boolean forceResult, String sessionName) { - String message_type = act_operation.getString("message type"); + } - op.setMessageType(message_type, messageTypes); + @Override + public void onError(String sessionName) { + lblOutput.setText("Errore nell'esecuzione della traccia"); + } - if (act_operation.has("from session")) { - op.from_session = act_operation.getString("from session"); - } - if (act_operation.has("to session")) { - op.to_session = act_operation.getString("to session"); - } - if (act_operation.has("then")) { - op.then = Utils.Then.fromString(act_operation.getString("then")); - } - if (act_operation.has("save")) { - op.save_name = act_operation.getString("save"); - } - if (act_operation.has("replace request")) { - op.replace_request_name = act_operation.getString("replace request"); - } else if (act_operation.has("replace response")) { - op.replace_response_name = act_operation.getString("replace response"); - } + @Override + public Boolean onAskPause(String sessionName) { + return false; + } - // Preconditions - if (act_operation.has("preconditions")) { - JSONArray checks = act_operation.getJSONArray("preconditions"); - op.preconditions = Utils.parseChecksFromJSON(checks); - } + @Override + public Boolean onAskStop(String sessionName) { + return false; + } - // Message Operations - if (act_operation.has("message operations")) { - JSONArray message_ops = act_operation.getJSONArray("message operations"); - for (int k = 0; k < message_ops.length(); k++) { - JSONObject act_message_op = message_ops.getJSONObject(k); - MessageOperation message_op = new MessageOperation(); - keys = act_message_op.keys(); - while (keys.hasNext()) { - String key = keys.next(); - - switch (key) { - case "from": - message_op.from = Utils.MessageSection.fromString(act_message_op.getString("from")); - break; - case "remove parameter": - message_op.what = act_message_op.getString("remove parameter"); - message_op.action = Utils.MessageOperationActions.REMOVE_PARAMETER; - break; - case "remove match word": - message_op.what = act_message_op.getString("remove match word"); - message_op.action = Utils.MessageOperationActions.REMOVE_MATCH_WORD; - break; - case "edit": - message_op.what = act_message_op.getString("edit"); - message_op.action = Utils.MessageOperationActions.EDIT; - break; - case "edit regex": - message_op.what = act_message_op.getString("edit regex"); - message_op.action = Utils.MessageOperationActions.EDIT_REGEX; - break; - case "in": - message_op.to = act_message_op.getString("in"); - break; - case "add": - message_op.what = act_message_op.getString("add"); - message_op.action = Utils.MessageOperationActions.ADD; - break; - case "this": - message_op.to = act_message_op.getString("this"); - break; - case "save": - message_op.what = act_message_op.getString("save"); - message_op.action = Utils.MessageOperationActions.SAVE; - break; - case "save match": - message_op.what = act_message_op.getString("save match"); - message_op.action = Utils.MessageOperationActions.SAVE_MATCH; - break; - case "as": - message_op.save_as = act_message_op.getString("as"); - break; - case "use": - message_op.use = act_message_op.getString("use"); - break; - case "type": - message_op.type = Utils.MessageOpType.fromString( - act_message_op.getString("type")); - break; - case "decode param": - message_op.decode_param = act_message_op.getString("decode param"); - break; - - case "encoding": - JSONArray encodings = act_message_op.getJSONArray("encoding"); - Iterator it = encodings.iterator(); - - while (it.hasNext()) { - String act_enc = (String) it.next(); - message_op.encodings.add( - Utils.Encoding.fromString(act_enc)); - } - break; - - case "value": - // value of xml or other edits - message_op.value = act_message_op.getString("value"); - break; - case "add tag": - message_op.xml_action = Utils.XmlAction.ADD_TAG; - message_op.xml_action_name = act_message_op.getString(key); - break; - case "add attribute": - message_op.xml_action = Utils.XmlAction.ADD_ATTR; - message_op.xml_action_name = act_message_op.getString(key); - break; - case "edit tag": - message_op.xml_action = Utils.XmlAction.EDIT_TAG; - message_op.xml_action_name = act_message_op.getString(key); - break; - case "edit attribute": - message_op.xml_action = Utils.XmlAction.EDIT_ATTR; - message_op.xml_action_name = act_message_op.getString(key); - break; - case "remove tag": - message_op.xml_action = Utils.XmlAction.REMOVE_TAG; - message_op.xml_action_name = act_message_op.getString(key); - break; - case "remove attribute": - message_op.xml_action = Utils.XmlAction.REMOVE_ATTR; - message_op.xml_action_name = act_message_op.getString(key); - break; - case "save tag": - message_op.xml_action = Utils.XmlAction.SAVE_TAG; - message_op.xml_action_name = act_message_op.getString(key); - break; - case "save attribute": - message_op.xml_action = Utils.XmlAction.SAVE_ATTR; - message_op.xml_action_name = act_message_op.getString(key); - break; - case "self-sign": - message_op.self_sign = act_message_op.getBoolean("self-sign"); - break; - case "remove signature": - message_op.remove_signature = act_message_op.getBoolean("remove signature"); - break; - case "xml tag": - message_op.xml_tag = act_message_op.getString("xml tag"); - break; - case "xml occurrency": - message_op.xml_occurrency = act_message_op.getInt("xml occurrency"); - break; - case "xml attribute": - message_op.xml_attr = act_message_op.getString("xml attribute"); - break; - case "txt remove": - message_op.txt_action = Utils.TxtAction.REMOVE; - message_op.txt_action_name = act_message_op.getString("txt remove"); - break; - case "txt edit": - message_op.txt_action = Utils.TxtAction.EDIT; - message_op.txt_action_name = act_message_op.getString("txt edit"); - break; - case "txt add": - message_op.txt_action = Utils.TxtAction.ADD; - message_op.txt_action_name = act_message_op.getString("txt add"); - break; - case "txt save": - message_op.txt_action = Utils.TxtAction.SAVE; - message_op.txt_action_name = act_message_op.getString("txt save"); - break; - case "jwt from": - message_op.jwt_section = Utils.Jwt_section.getFromString( - act_message_op.getString("jwt from")); - if (act_message_op.getString("jwt from").contains("raw")) { - message_op.isRawJWT = true; - } - break; - case "jwt remove": - message_op.jwt_action = Utils.Jwt_action.REMOVE; - message_op.what = act_message_op.getString("jwt remove"); - break; - case "jwt edit": - message_op.jwt_action = Utils.Jwt_action.EDIT; - message_op.what = act_message_op.getString("jwt edit"); - break; - case "jwt add": - message_op.jwt_action = Utils.Jwt_action.ADD; - message_op.what = act_message_op.getString("jwt add"); - break; - case "jwt save": - message_op.jwt_action = Utils.Jwt_action.SAVE; - message_op.what = act_message_op.getString("jwt save"); - break; - case "jwt sign": - message_op.sign = act_message_op.getBoolean("jwt sign"); - break; - case "template": - message_op.template = act_message_op.getString("template"); - break; - case "output_path": - message_op.output_path = act_message_op.getString("output_path"); - break; - default: - System.err.println(key); - throw new ParsingException("Message operation not valid"); - } - } - op.messageOerations.add(message_op); - } - } + @Override + public Boolean onAskClearCookie(String sessionName) { + return null; + } - // Session Operations - List lsop = SessionOperation.parseFromJson(act_operation); - if (lsop != null) { - for (SessionOperation sop : lsop) { - op.session_operations.add(sop); - } - } + @Override + public void onNextSessionAction(SessionTrackAction last_action, + SessionTrackAction last_open, + SessionTrackAction last_click, + String last_url, + String session_name) { + } - } - test.operations.add(op); + @Override + public Track onUpdateTrack(String sessionName) throws ParsingException { + return null; } - tests.add(test); - } - updateSessionTabs(); - updateTxtSessionConfig(); - //JSONArray result = obj.getJSONArray("Test Suite Result Table"); - this.testSuite = new TestSuite(suite_name, suite_description, tests); - this.testSuite.metadata = metadata; - lblInfo.setText("JSON read successfully, Test Suite Object has been created"); + @Override + public void onSetVar(Var v) { + } + }; - } catch (ParsingException e) { - e.printStackTrace(); + editConfigFile("last_browser_used", btnselectChrome.isEnabled() ? "firefox" : "chrome"); - setJSONError(true, "Problem in parsing JSON: " + e.getMessage()); - } catch (Exception e) { - e.printStackTrace(); + ExecuteTrack ex = new ExecuteTrack(false, + !btnselectChrome.isEnabled(), + DRIVER_PATH, + defaultSession.track, + defaultSession.port, + "main"); + ex.registerExecuteTrackListener(listener); + new Thread(ex).start(); + }); - setJSONError(true, "PROBLEM IN READING JSON, check it please"); - } + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(0, 0, 0, 0); + gbc.gridx = 5; + gbc.gridy = 3; + btnSetRecording.setPreferredSize(new Dimension(100, 20)); + trackContainer.add(btnExecuteTrack, gbc); - } + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(0, 0, 0, 0); + gbc.gridx = 4; + gbc.gridy = 4; + trackContainer.add(new JLabel("Next test will use"), gbc); - /** - * This function reads the selected file deserializing the messages and creating a new Session - * - * @return if the reading has been succesfull - */ - private boolean readSavedMessages() { - if (!SAVE_FILE_PATH.isEmpty()) { - try { - if (defaultSession == null) { - defaultSession = new Session("default"); - defaultSession.isOffline = true; + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(20, 0, 0, 0); + gbc.gridx = 4; + gbc.gridy = 4; + trackContainer.add(lblnextTestBrowser, gbc); - File f = new File(SAVE_FILE_PATH); - Scanner r = new Scanner(f); + JFileChooser messageLoader = new JFileChooser(); + btnLoadMessages = new JButton("load messages"); + btnLoadMessages.setEnabled(true); + btnLoadMessages.addActionListener(actionEvent -> { + if (btnLoadMessages.getBackground() == Color.GREEN) { + btnSetOffline.setEnabled(false); + btnSetOffline.setBackground(Color.white); + btnLoadMessages.setBackground(Color.white); + btnLoadMessages.setText("load messages"); + OFFLINE = false; + SAVE_FILE_PATH = ""; - Gson json = new Gson(); - while (r.hasNextLine()) { - HTTPReqRes tmp = json.fromJson(r.nextLine(), HTTPReqRes.class); - defaultSession.messages.add(tmp); - } + } else { + int returnVal = messageLoader.showOpenDialog(GUI.this); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File file = messageLoader.getSelectedFile(); + SAVE_FILE_PATH = file.getPath(); + lblOutput.setText("Messages selected"); + btnLoadMessages.setBackground(Color.GREEN); + btnLoadMessages.setText("unload"); + btnSetOffline.setEnabled(true); + } else if ((returnVal == JFileChooser.ERROR) || (returnVal == JFileChooser.ERROR_OPTION)) { + lblOutput.setText("error in selecting messages"); + System.out.println("error in selecting messages"); + btnSetOffline.setEnabled(false); + btnLoadMessages.setBackground(Color.RED); } else { - System.out.println("main session already created, skipping message reading from file"); + lbldriver.setText("Messages still not loaded"); + btnSetOffline.setEnabled(false); + btnLoadMessages.setBackground(Color.RED); } - return true; - } catch (FileNotFoundException fileNotFoundException) { - fileNotFoundException.printStackTrace(); - return false; } - } else { - return false; - } - } - - /** - * Method which executes the entire test suite - */ - private void executeSuite() { - // clears all previously saved tests - actives.clear(); - passives.clear(); - act_active_op = null; - ex = null; - synchronized (lock) { - act_test_vars = new ArrayList<>(); - } - active_ex_finished = false; + }); - // clears the test suite result table - DefaultTableModel dm = (DefaultTableModel) resultTable.getModel(); - dm.getDataVector().removeAllElements(); - dm.fireTableDataChanged(); + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(0, 0, 0, 0); + gbc.gridx = 6; + gbc.gridy = 1; + trackContainer.add(btnLoadMessages, gbc); - System.out.println("Number of test found: " + testSuite.getTests().size()); - for (Test t : testSuite.getTests()) { - if (t.isActive) { - actives.add(t); + btnSetOffline = new JButton("offline mode"); + btnSetOffline.setEnabled(false); + btnSetOffline.addActionListener(actionEvent -> { + if (btnSetOffline.getBackground() == Color.green) { + OFFLINE = false; + btnSetOffline.setBackground(Color.white); } else { - passives.add(t); - } - } - - if (OFFLINE) { - if (!readSavedMessages()) { - System.err.println("Can't read message file"); - lblOutput.setText("Can't read message file"); - return; + btnSetOffline.setBackground(Color.green); + OFFLINE = true; } - }//TODO: re-enable OFFLINE mode - /* else if (passives.size() > 0 && defaultSession == null && actives.size() == 0) { - lblOutput.setText("Track need to be run for passive tests before executing tests"); - return; - } - */ + }); + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(0, 0, 0, 0); + gbc.gridx = 6; + gbc.gridy = 2; + trackContainer.add(btnSetOffline, gbc); - if (actives.size() == 0) { - synchronized (lock2) { - active_ex_finished = true; - } - } + btnSaveToFile = new JButton("save"); + btnSaveToFile.addActionListener(actionEvent -> { + if (SAVE_TO_FILE) { + FileWriter w; + try { + w = new FileWriter(RECORD_FILE_PATH); - //FIXME: Passives thread starts without waiting for the end of actives one - // Execute active tests - if (actives.size() != 0) { - try { - for (String key : session_port.keySet()) { - if (session_port.get(key).equals("")) { - lblOutput.setText("session port not configured"); - return; + for (HTTPReqRes actual : defaultSession.messages) { + Gson geson = new Gson(); + String serialized = geson.toJson(actual); + w.write(serialized + "\n"); } + w.close(); + } catch (IOException e) { + e.printStackTrace(); } - ex = new ExecuteActives(actives, waiting); - - editConfigFile("last_browser_used", btnselectChrome.isEnabled() ? "firefox" : "chrome"); + } + }); + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(0, 0, 0, 0); + gbc.gridx = 6; + gbc.gridy = 3; + trackContainer.add(btnSaveToFile, gbc); + } - ex.registerExecuteActivesListener(new ExecuteActiveListener() { - @Override - public void onExecuteStart() { - ACTIVE_ENABLED = false; - act_active_op = new Operation(); - } + private JPanel setup_tab_input_json(GridBagLayout bottom_layout) { + inputContainer = new JPanel(); + inputContainer.setLayout(bottom_layout); - @Override - public void onExecuteDone() { - if (passives.size() == 0){ - update_gui_test_results(); + lblInfo = new JLabel(" "); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.insets = new Insets(10, 10, 10, 10); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.gridwidth = 2; + inputContainer.add(lblInfo, gbc); - lblOutput.setText("Passive Tests: " - + (passives.isEmpty() ? 0 : passives.size()) - + " - Active Tests: " - + (testSuite.getTests().size() - (passives.isEmpty() ? 0 : passives.size()))); - } - lblOutput.setText("Executed Active tests, now doing passives"); - synchronized (lock2) { - active_ex_finished = true; - } - } + lblOutput = new JLabel(" "); + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.insets = new Insets(10, 10, 10, 10); + gbc.gridx = 0; + gbc.gridy = 4; + gbc.gridwidth = 2; + inputContainer.add(lblOutput, gbc); + txtSearch = new JTextArea(); - @Override - public void onNewProcessOperation(Operation op) { - ACTIVE_ENABLED = true; - act_active_op = op; - } + JScrollPane scrollPane2 = new JScrollPane(txtSearch, + JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.BOTH; + gbc.insets = new Insets(10, 10, 10, 10); + gbc.gridx = 0; + gbc.gridy = 1; + gbc.gridwidth = 2; + gbc.gridheight = 3; + inputContainer.add(scrollPane2, gbc); - @Override - public Operation onOperationDone() { - ACTIVE_ENABLED = false; - Operation tmp = act_active_op; + JButton btnReadJSON = new JButton("Read JSON"); + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(10, 10, 10, 10); + gbc.gridx = 2; + gbc.gridy = 2; + inputContainer.add(btnReadJSON, gbc); - act_active_op = new Operation(); - return tmp; - } + btnReadJSON.addActionListener(e -> { + testSuite = new TestSuite(); - @Override - public Session onNewSession(Session s) { - Track track = null; - try { - track = s.setTrackFromString(getSessionTxt(s.name)); - } catch (ParsingException e) { - lblOutput.setText("error in parsing session track"); - return null; - } - String port = session_port.get(s.name); - s.port = port; - s.track = track; + readMsgDefFile(); // Updates the Message Definitions + readJSONinput(txtSearch.getText()); - s.ex = new ExecuteTrack(false, - !btnselectChrome.isEnabled(), - DRIVER_PATH, - track, - port, - s.name); - return s; - } + try { + updateTxtSessionConfig(); + } catch (ParsingException exc) { + setJSONError(true, "error in updating the session config"); + } - @Override - public void onNewTest(Test actual_test) { - synchronized (lock) { - act_test_vars = new ArrayList<>(); - } - act_active_op = null; - } + lblOutput.setText("Number of Tests: " + testSuite.getTests().size()); - public void onTestDone(Test actual_test) { - int indx = testSuite.tests.indexOf(actual_test); - if (indx != -1) { - System.out.printf("Saving test %s in test results", actual_test.getName()); - //TODO: add log of sessions - testSuite.tests.set(indx, actual_test); - actual_test.logTest(LOG_FOLDER); - } - } + if (testSuite.getTests().size() > 0) { + btnExecuteSuite.setEnabled(true); + } + }); - @Override - public void onError(Test actual_test) { - System.err.println("Error executing the test:" + actual_test.name); - synchronized (lock2) { - active_ex_finished = true; - } - } + JButton btnStop = new JButton("Stop"); + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(10, 10, 10, 10); + gbc.gridx = 2; + gbc.gridy = 1; + inputContainer.add(btnStop, gbc); - @Override - public List onBeforeExSessionOps() { - synchronized (lock) { - return act_test_vars; - } - } + btnStop.addActionListener(e -> { + if (active_ex != null) { + active_ex.interrupt(); + ACTIVE_ENABLED = false; + active_ex.stop(); + } + }); - @Override - public void onAfterExSessionOps(List re) { - synchronized (lock) { - act_test_vars = re; - } - } + btnExecuteSuite = new JButton("Execute Test Suite"); + btnExecuteSuite.setEnabled(false); - @Override - public void onAddVar(Var v) { - synchronized (lock) { - act_test_vars.add(v); - } - } - }); - active_ex = new Thread(ex); - active_ex.start(); + btnExecuteSuite.addActionListener(e -> { + executeSuite(); + }); - } catch (Exception er) { - er.printStackTrace(); - System.out.println("" + er.getLocalizedMessage() + "nad" + er.getMessage() + "2" + er); + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.SOUTHWEST; + gbc.insets = new Insets(10, 10, 10, 10); + gbc.gridx = 2; + gbc.gridy = 3; + inputContainer.add(btnExecuteSuite, gbc); + return inputContainer; + } - lblOutput.setText("PROBLEM IN Executing Suite, check it please"); + private JScrollPane setup_tab_suite_result(GridBagLayout bottom_layout) { + resultTableModel = new DefaultTableModel(foundData, testSuiteColNames) { + @Override + public boolean isCellEditable(int row, int column) { + return false; } - } + }; - // Execute passive tests - if (passives.size() != 0) { - // TODO: Add offline clause - /* - if (defaultSession.messages.size() == 0) { - lblOutput.setText("no message found"); - return; + resultTable = new JTable(resultTableModel) { + //Implement table cell tool tips. + public String getToolTipText(MouseEvent e) { + String tip = null; + java.awt.Point p = e.getPoint(); + int rowIndex = rowAtPoint(p); + int colIndex = columnAtPoint(p); + + try { + tip = getValueAt(rowIndex, colIndex).toString(); + } catch (RuntimeException e1) { + //catch null pointer exception if mouse is over an empty line + } + return tip; } - */ + }; - ExecutePassiveListener listener = new ExecutePassiveListener() { - @Override - public boolean onWaitToStart() { - synchronized (lock2) { - return active_ex_finished; - } + resultTable.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() { + @Override + public Component getTableCellRendererComponent(JTable table, + Object value, + boolean isSelected, + boolean hasFocus, + int row, + int column) { + final Component c = super.getTableCellRendererComponent(table, + value, + isSelected, + hasFocus, + row, + column); + if (value == null) return c; + if (value.equals("failed")) { + c.setBackground(Color.RED); + } else { + c.setBackground(Color.WHITE); } + return c; + } + }); - @Override - public void onExecuteStart() { + JScrollPane scrollPane = new JScrollPane(resultTable, + JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); - } + resultTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - @Override - public void onExecuteDone(List passives_test) { - //TODO: Check if this is ok - lblOutput.setText("Passive Tests: " - + (passives.isEmpty() ? 0 : passives.size()) - + " - Active Tests: " - + (testSuite.getTests().size() - (passives.isEmpty() ? 0 : passives.size()))); + // Adds all the test result to the result table + resultTable.getSelectionModel().addListSelectionListener(event -> { + if (resultTable.getSelectedRow() > -1) { - passives = passives_test; + int row = resultTable.getSelectedRow(); + //BurpSuite.getTests.get(row).getTable(); - update_gui_test_results(); - } + DefaultTableModel dm = (DefaultTableModel) testTable.getModel(); + dm.getDataVector().removeAllElements(); + dm.fireTableDataChanged(); - @Override - public void onError(String msg) { - lblOutput.setText(msg); - } + for (String[] act : testSuite.getTests().get(row).getRows()) { - @Override - public Session onNewSession(Session s) throws ParsingException { - //TODO: implement - s.setTrackFromString(getSessionTxt(s.name)); + ((DefaultTableModel) testTable.getModel()).addRow(act); + } + } + }); + return scrollPane; + } - String port = session_port.get(s.name); - s.port = port; + private JSplitPane setup_tab_test_result(GridBagLayout bottom_layout) { + // Test Result Tab + testTableModel = new DefaultTableModel(foundData, foundTableColNames) { + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + }; - s.ex = new ExecuteTrack(false, - !btnselectChrome.isEnabled(), - DRIVER_PATH, - s.track, - port, - s.name); + testTable = new JTable(testTableModel); + // Add all the operations result to the table + testTable.getSelectionModel().addListSelectionListener(listSelectionEvent -> { + if (!listSelectionEvent.getValueIsAdjusting()) { + if (testTable.getSelectedRow() == -1) return; + int index = Integer.parseInt((String) testTable.getModel().getValueAt(testTable.getSelectedRow(), 4)); + int op_index = Integer.parseInt((String) testTable.getModel().getValueAt(testTable.getSelectedRow(), 0)); - return s; - } + Operation op = testSuite.tests.get(resultTable.getSelectedRow()).operations.get(op_index); - @Override - public void onBeforeExecuteTrack() { - //Clear previous interceptedMessages if any - interceptedMessages.clear(); - //Tell Burp Extender class to record the intercepted messages from now on - recording = true; + MessageType msg_type; + try { + msg_type = MessageType.getFromList(messageTypes, op.getMessageType()); + } catch (ParsingException e) { + throw new RuntimeException(e); } - @Override - public ArrayList onTrackExecuteDone() { - recording = false; - return interceptedMessages; + for (HTTPReqRes m : op.matchedMessages) { + if (m.index == index) { + if (msg_type.msg_to_process_is_request) { + messageViewer.setMessage(m.getRequest(), true); + } else { + messageViewer.setMessage(m.getResponse(), false); + } + break; + } } - }; + } + }); - ExecutePassives expa = new ExecutePassives(helpers, - passives, - listener, - messageTypes); + JScrollPane scrollPane3 = new JScrollPane(testTable, + JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); - new Thread(expa).start(); - } - } + splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); - /** - * Function used to get the text of the text area of a certain session using the session name - * - * @param session_name the name of the session to get the text - * @return the content of the session tab if it is not empty, otherwise the main session text tab - */ - private String getSessionTxt(String session_name) { - if (sessions_text.containsKey(session_name)) { - JTextArea t = (JTextArea) sessions_text.get(session_name); - if (t.getText().equals("")) { - return txtScript.getText(); - } else { - return t.getText(); - } - } - return null; - } + splitPane.setLeftComponent(scrollPane3); - /** - * Update the session config tab, using the session_port variable, at the same time reads if there are changes, and - * updates the session_port variable - */ - private void updateTxtSessionConfig() throws ParsingException { - setSession_configError(false, ""); + controller = new IMessageEditorController() { + @Override + public IHttpService getHttpService() { + return new IHttpService() { + @Override + public String getHost() { + return null; + } - String text = txtSessionConfig.getText(); - Pattern p = Pattern.compile("\\n"); - Matcher m = p.matcher(text); - text = m.replaceAll(""); + @Override + public int getPort() { + return 0; + } - if (text.equals("")) { - String tmp = ""; - for (String s : session_port.keySet()) { - tmp += s; - tmp += ":"+ session_port.get(s) +";\n"; + @Override + public String getProtocol() { + return null; + } + }; } - txtSessionConfig.setText(tmp); - return; - } - String[] text_list = text.split(";"); - - for (String row : text_list) { - String[] splitted = row.trim().split(":"); - if (splitted.length == 0) continue; - if (splitted.length <= 1) { - String[] splitted2 = new String[]{"", ""}; - splitted2[0] = splitted[0]; - splitted = splitted2; + @Override + public byte[] getRequest() { + return viewedMessage.getRequest(); } - String port = splitted[1].trim(); - p = Pattern.compile("^\\d+$"); - m = p.matcher(splitted[1].trim()); - if (!m.find()) { - setSession_configError(true, "invalid port"); - throw new ParsingException("Invalid port"); + @Override + public byte[] getResponse() { + return viewedMessage.getResponse(); } - if (session_port.containsKey(splitted[0].trim())) { - session_port.replace(splitted[0].trim(), splitted[1].trim()); - } else { - session_port.put((splitted[0]).trim(), splitted[1].trim()); + }; + return splitPane; + } + + private JPanel setup_tab_session_config(GridBagLayout bottom_layout) { + JPanel sessionConfig = new JPanel(); + sessionConfig.setLayout(bottom_layout); + + txtSessionConfig = new JTextArea(); + JScrollPane scrollPane5 = new JScrollPane(txtSessionConfig, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.BOTH; + gbc.insets = new Insets(10, 10, 10, 10); + gbc.gridx = 0; + gbc.gridy = 1; + gbc.gridwidth = 2; + gbc.gridheight = 3; + sessionConfig.add(scrollPane5, gbc); + + JButton btnSetSessionConfig = new JButton("save"); + btnSetSessionConfig.addActionListener(actionEvent -> { + try { + updateTxtSessionConfig(); + } catch (ParsingException e) { + e.printStackTrace(); } - } - String tmp = ""; - for (String key : session_port.keySet()) { - tmp += key + ": " + session_port.get(key) + ";\n"; - } + }); - txtSessionConfig.setText(tmp); + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.BOTH; + gbc.insets = new Insets(10, 10, 10, 10); + gbc.gridx = 2; + gbc.gridy = 3; + gbc.gridwidth = 1; + gbc.gridheight = 1; + sessionConfig.add(btnSetSessionConfig, gbc); + + return sessionConfig; } - /** - * This function updates the session tabs in the gui to match the actual value in session_names - */ - private void updateSessionTabs() { - List present = new ArrayList<>(); + private JPanel setub_tab_debug(GridBagLayout bottom_layout) { + // Debug Tab + JPanel debugPanel = new JPanel(); - for (int i = 1; i < top_tabbed.getTabCount(); i++) { - present.add(top_tabbed.getTitleAt(i)); - if (!sessions_names.contains(top_tabbed.getTitleAt(i))) { - top_tabbed.remove(i); - } - } - for (String name : sessions_names) { - if (!present.contains(name)) { - JTextArea tmp = new JTextArea(); - JScrollPane sp = new JScrollPane(tmp, - JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, - JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); - sessions_text.put(name, tmp); - top_tabbed.add(name, sp); - } - } - JTextArea tmp = new JTextArea(); - JScrollPane sp = new JScrollPane(tmp, + debugPanel.setLayout(bottom_layout); + + txt_out_debug_tab.setEditable(false); + JScrollPane scrollPane6 = new JScrollPane(txt_out_debug_tab, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); - } + GridBagConstraints constraints1 = new GridBagConstraints(); + constraints1.gridx = 0; + constraints1.gridy = 1; + constraints1.gridwidth = 1; // One cell wide + constraints1.gridheight = 3; // One cell high + constraints1.fill = GridBagConstraints.BOTH; + constraints1.weightx = 0.5; // Half of the horizontal space + constraints1.weighty = 0.5; // Half of the vertical space + debugPanel.add(scrollPane6, constraints1); + + txt_err_debug_tab.setEditable(false); + JScrollPane scrollPane7 = new JScrollPane(txt_err_debug_tab, + JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, + JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + GridBagConstraints constraints2 = new GridBagConstraints(); + constraints2.gridx = 1; + constraints2.gridy = 1; + constraints2.gridwidth = 1; // One cell wide + constraints2.gridheight = 3; // One cell high + constraints2.fill = GridBagConstraints.BOTH; + constraints2.weightx = 0.5; // Half of the horizontal space + constraints2.weighty = 0.5; // Half of the vertical space + debugPanel.add(scrollPane7, constraints2); + + JButton btn_clear = new JButton("clear"); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(10, 10, 10, 10); + gbc.gridx = 2; + gbc.gridy = 1; + debugPanel.add(btn_clear, gbc); - /** - * Function used to set the JSON textbox with a red colour to highlight an error. - * @param isInError true to highlight an error, false to remove the highlight - * @param msg the error message to display - */ - private void setJSONError(boolean isInError, String msg) { - if (isInError) { - txtSearch.setBorder(BorderFactory.createLineBorder(Color.RED, 3)); - lblOutput.setText(msg); - bot_tabbed.setBackgroundAt(bot_tabs_index.get("Input JSON"), Color.RED); - } else { - txtSearch.setBorder(BorderFactory.createEmptyBorder()); - lblOutput.setText(""); - bot_tabbed.setBackgroundAt(bot_tabs_index.get("Input JSON"), Color.white); - } - } + btn_clear.addActionListener(e -> { + txt_out_debug_tab.setText(""); + txt_err_debug_tab.setText(""); + }); - /** - * Function used to set the session config textbox with a red border to highlight an error. - * @param isInError true to highlight an error, false to remove highlight - * @param msg The error message to display - */ - private void setSession_configError(boolean isInError, String msg) { - if (isInError) { - txtSessionConfig.setBorder(BorderFactory.createLineBorder(Color.RED, 3)); - lblOutput.setText(msg); - bot_tabbed.setBackgroundAt(bot_tabs_index.get("session config"), Color.RED); - } else { - txtSessionConfig.setBorder(BorderFactory.createEmptyBorder()); - lblOutput.setText(""); - bot_tabbed.setBackgroundAt(bot_tabs_index.get("session config"), Color.white); - } - } + JLabel lbl_left = new JLabel("Output log"); + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.insets = new Insets(10, 10, 10, 10); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.gridwidth = 2; + debugPanel.add(lbl_left, gbc); - /** - * Function used to update the gui test results after the tests are executed - */ - private void update_gui_test_results() { - for (Test t : testSuite.getTests()) { - String esito = ""; - if (t.applicable) { - esito = t.success ? "passed" : "failed"; - } else { - esito = "not applicable"; - } - String[] tmp = new String[]{t.getName(), - t.getDescription(), - t.references, - t.violated_properties, - t.affected_entity, - t.mitigations, - esito}; - System.out.println(t.getName() + " " + esito); - addItem(tmp); - } + JLabel lbl_right = new JLabel("Error log"); + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.insets = new Insets(10, 10, 10, 10); + gbc.gridx = 1; + gbc.gridy = 0; + gbc.gridwidth = 2; + debugPanel.add(lbl_right, gbc); - btnExecuteSuite.setEnabled(false); + return debugPanel; } } \ No newline at end of file diff --git a/tool/src/main/java/migt/HTTPReqRes.java b/tool/src/main/java/migt/HTTPReqRes.java new file mode 100644 index 0000000..d5aed22 --- /dev/null +++ b/tool/src/main/java/migt/HTTPReqRes.java @@ -0,0 +1,540 @@ +package migt; + +import burp.IExtensionHelpers; +import burp.IHttpRequestResponse; +import burp.IHttpRequestResponsePersisted; +import burp.IHttpService; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 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; + public Integer index = -1; // index of the message wrt the burp proxy + public boolean isRequest = false; + public boolean isResponse = false; + public int body_offset_req; // identifies the index where the body ends in the request + public int body_offset_resp; // the index where teh body of the response starts + byte[] body_req = null; // the body of the request message + byte[] body_resp = null; // the body of the response message + // host data + private String host; + private int port = 0; + private String protocol; + // message data + private String request_url; // The url of the request (not the header) + private byte[] request; + private byte[] response; + private List headers_req; // the headers of the request + private List headers_resp; // the headers of the response + + /** + * Instantiate an HTTPReqRes element + * + * @param request the request in byte[] form + * @param response the response in byte[] form + */ + public HTTPReqRes(byte[] request, byte[] response) { + this.isRequest = true; + this.isResponse = true; + this.setRequest(request); + this.setResponse(response); + instances++; + } + + /** + * Instantiate an HTTPReqRes element from a IHttpRequestResponsePersisted message + * + * @param message the message to be created from + * @param helpers the helpers + */ + public HTTPReqRes(IHttpRequestResponsePersisted message, IExtensionHelpers helpers) { + this.isRequest = true; + this.isResponse = true; + this.setRequest(message.getRequest()); + this.setResponse(message.getResponse()); + this.setRequest_url(helpers.analyzeRequest(message).getUrl().toString()); + IHttpService service = message.getHttpService(); + this.setHost(service.getHost()); + this.setPort(service.getPort()); + this.setProtocol(service.getProtocol()); + this.body_offset_req = helpers.analyzeRequest(message.getRequest()).getBodyOffset(); + this.body_offset_resp = helpers.analyzeResponse(message.getResponse()).getBodyOffset(); + this.headers_req = helpers.analyzeRequest(message.getRequest()).getHeaders(); + this.headers_resp = helpers.analyzeResponse(message.getResponse()).getHeaders(); + + instances++; + } + + /** + * Instantiate an HTTPReqRes element. If a message is a request it does not gather the response + * + * @param message an IHTTPRequestResponse message + * @param helpers an istance of the IExtensionHelpers + * @param isRequest true if the message is a request, false otherwise + */ + public HTTPReqRes(IHttpRequestResponse message, IExtensionHelpers helpers, Boolean isRequest, int index) { + if (!isRequest) { + this.isResponse = true; + this.setResponse(message.getResponse()); + this.headers_resp = helpers.analyzeResponse(message.getResponse()).getHeaders(); + this.body_offset_resp = helpers.analyzeRequest(message.getResponse()).getBodyOffset(); + } + + this.index = index; + + // the request is always present in a IHTTPRequestResponse + this.isRequest = true; + this.setRequest(message.getRequest()); + this.setRequest_url(helpers.analyzeRequest(message).getUrl().toString()); + this.headers_req = helpers.analyzeRequest(message.getRequest()).getHeaders(); + this.request_url = helpers.analyzeRequest(message).getUrl().toString(); + this.body_offset_req = helpers.analyzeRequest(message.getRequest()).getBodyOffset(); + + // set host info + IHttpService service = message.getHttpService(); + this.setHost(service.getHost()); + this.setPort(service.getPort()); + this.setProtocol(service.getProtocol()); + + instances++; + } + + /** + * Function used to replace an IHttpRequestResponse message with the values contained in this object + * + * @param message the message to be replaced + * @param helpers the burp helpers + * @return the edited message with the request and/or response replaced + */ + public IHttpRequestResponse replaceBurpMessage(IHttpRequestResponse message, IExtensionHelpers helpers) { + // TODO: eventually rebuild the message with the edited parts, here or when edited ? + if (isRequest) { + message.setRequest(request); + } + if (isResponse) { + message.setResponse(response); + } + if (host != null && port != 0 && protocol != null) { + message.setHttpService( + this.getHttpService(helpers) + ); + } + return message; + } + + public IHttpService getHttpService(IExtensionHelpers helpers) { + return helpers.buildHttpService( + host, + port, + protocol + ); + } + + public String getUrlHeader() { + if (!isRequest) + throw new RuntimeException("called getUrlHeader on a response message"); + + return this.headers_req.get(0); + } + + public void setUrlHeader(String url_header) { + if (!isRequest) + throw new RuntimeException("called setUrlHeader on a response message"); + + this.headers_req.set(0, url_header); + } + + public byte[] getBody(boolean isRequest) { + if (isRequest && (this.body_offset_req == 0 | 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)) { + throw new RuntimeException("called getBody, but class is not properly initialized"); + } + + if (isRequest) { + // if asking for the first time, take the body from the message + if (this.body_req == null) + this.body_req = Arrays.copyOfRange(this.request, this.body_offset_req, this.request.length); + return this.body_req; + } else { + if (this.body_resp == null) + this.body_resp = Arrays.copyOfRange(this.response, this.body_offset_resp, this.response.length); + return this.body_resp; + } + } + + 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. + * + * @param isRequest true if message is a request message + * @return + */ + public byte[] build_message(boolean isRequest) { + String builded = ""; + byte[] body = getBody(isRequest); + + List headers = getHeaders(isRequest); + String content_header = "Content-Length: " + body.length; + + for (String header : headers) { + if (header.contains("Content-Length:")) { + if (body.length == 0) + continue;// if Content-Length header found, but message has no body, remove. + builded += content_header; + + } else { + builded += header; + } + builded += "\r\n"; + } + builded += "\r\n"; // last row of header before body + + if (body.length != 0) + builded += new String(body); + + return builded.getBytes(StandardCharsets.UTF_8); + } + + /** + * Get the message in bytes with the changes made + * + * @param isRequest to specify the request or the response + * @return the message + */ + public byte[] getMessage(boolean isRequest, IExtensionHelpers helpers) { + 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); + + 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"); + } + + return isRequest ? request : response; + } + + public void setHeaders(boolean isRequest, List headers) { + if (isRequest) { + this.headers_req = headers; + } else { + this.headers_resp = headers; + } + } + + /** + * Set the body of the request or response message with a new value + * + * @param isRequest + * @param body + */ + public void setBody(boolean isRequest, byte[] body) { + if (isRequest) { + this.body_req = body; + } else { + this.body_resp = body; + } + } + + /** + * Set the body of the request or response message with a new value + * + * @param isRequest + * @param body + */ + public void setBody(boolean isRequest, String body) { + setBody(isRequest, body.getBytes()); + } + + /** + * Get the original un-edited request message + * + * @return + */ + public byte[] getRequest() { + return request; + } + + public void setRequest(byte[] request) { + this.request = request; + } + + /** + * Get the original un-edited response message + * + * @return + */ + public byte[] getResponse() { + return response; + } + + public void setResponse(byte[] response) { + this.response = response; + } + + public String getUrl() { + return this.request_url; + } + + public String getRequest_url() { + return request_url; + } + + public void setRequest_url(String request_url) { + this.request_url = request_url; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + /** + * Get the given parameter value from the url of the request messsage + * + * @param param the parameter name to be searched + * @return the value of the parameter + */ + public String getUrlParam(String param) { + if (!isRequest || request_url == null) { + throw new RuntimeException("Trying to access the url of a response message"); + } + + Pattern pattern = Pattern.compile("(?<=" + param + "=)[^$\\n&\\s]*"); + Matcher matcher = pattern.matcher(this.request_url); + String res = ""; + while (matcher.find()) { + res = matcher.group(); + break; + } + return res; + } + + /** + * Get the given parameter value from the head + * + * @param isRequest if the message is a request + * @param param the parameter name to be searched + * @return the value of the parameter + */ + public String getHeadParam(Boolean isRequest, String param) { + List headers = isRequest ? this.headers_req : this.headers_resp; + + for (String s : headers) { + if (s.contains(param)) { + String value = s.substring(s.indexOf(":") + 1); + return value.strip(); + } + } + return ""; + } + + + public void editHeadParam(Boolean isRequest, String param, String new_value) { + List headers = isRequest ? this.headers_req : this.headers_resp; + + int indx = -1; + + for (String s : headers) { + if (s.contains(param)) { + indx = headers.indexOf(s); + break; + } + } + + if (isRequest) { + headers_req.set(indx, param + ": " + new_value); + } else { + headers_resp.set(indx, param + ": " + new_value); + } + } + + public void addHeadParameter(boolean isRequest, String name, String value) { + (isRequest ? this.headers_req : this.headers_resp).add(name + ": " + value); + } + + public void removeHeadParameter(boolean isRequest, String name) { + List headers = isRequest ? this.headers_req : this.headers_resp; + + for (String h : headers) { + if (h.contains(name)) { + headers.remove(h); + break; + } + } + + if (isRequest) { + this.headers_req = headers; + } else { + this.headers_resp = headers; + } + } + + /** + * Given a message, get the given parameter value from the body, note that it accepts a regular expression, and + * 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 + * @return the value of the parameter + */ + public String getBodyRegex(Boolean isRequest, String param) { + Pattern pattern = Pattern.compile(param); + Matcher matcher = pattern.matcher(new String(getBody(isRequest), StandardCharsets.UTF_8)); + + String res = ""; + while (matcher.find()) { + res = matcher.group(); + break; + } + return res; + } + + /** + * Function to check if the given message matches a message_type + * + * @param msg_type the message type to check against it + * @return true or false, if matched or not respectively + */ + public boolean matches_msg_type(MessageType msg_type) { + boolean matchedMessage = false; + try { + /* If the response message name is searched, the getByResponse will be true. + * so messageIndex have to search for the request, and then evaluate the response + */ + if (msg_type.getByResponse) { + if (!isResponse) return false; // both request and response have to be present + matchedMessage = Tools.executeChecks( + msg_type.checks, + this, + true, + new ArrayList<>() // TODO: fix + ); + } else if (msg_type.getByRequest) { + if (!isResponse) return false; // both request and response have to be present + matchedMessage = Tools.executeChecks( + msg_type.checks, + this, + false, + new ArrayList<>() // TODO: fix + ); + } else { + if (!msg_type.isRequest && !isResponse) return false; // this message is not containing a response + matchedMessage = Tools.executeChecks( + msg_type.checks, + this, + msg_type.isRequest, + new ArrayList<>() // TODO: fix + ); + } + } catch (Exception e) { + e.printStackTrace(); + } + return matchedMessage; + } + + /** + * An enum representing the possible message sections + */ + public enum MessageSection { + HEAD, + BODY, + URL, + RAW; + + /** + * Function that given a message section in form of String, returns the corresponding MessageSection enum value + * + * @param input the input string + * @return the MessageSection enum value + * @throws ParsingException if the input does not correspond to any of the possible messagesections + */ + public static MessageSection fromString(String input) throws ParsingException { + if (input != null) { + switch (input) { + case "head": + return HEAD; + case "body": + return BODY; + case "url": + return URL; + case "raw": + return RAW; + default: + throw new ParsingException("message section not valid"); + } + } else { + throw new NullPointerException(); + } + } + } +} diff --git a/tool/src/main/java/migt/JWT.java b/tool/src/main/java/migt/JWT.java new file mode 100644 index 0000000..c2083f6 --- /dev/null +++ b/tool/src/main/java/migt/JWT.java @@ -0,0 +1,295 @@ +package migt; + +import com.nimbusds.jose.*; +import com.nimbusds.jose.crypto.*; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jwt.SignedJWT; +import org.json.JSONException; + +import java.text.ParseException; +import java.util.Base64; + + +/** + * Class to manage JWT tokens + * Uses https://connect2id.com/products/nimbus-jose-jwt + * {@code @Author} Matteo Bitussi + */ +public class JWT { + public String header; + public String payload; + public String signature; + public String raw; + public boolean sign; // put to true if you want to sign the jwt after edit (need private key) + public String private_key_pem; // PEM the private key used to sign the jwt + public String public_key_pem; // PEM public key used to check the signature of the jwt + public boolean decrypt; // put to true if the raw is a JWE, and you want to decrypt it + public String private_key_pem_enc; + public String public_key_pem_enc; + public JWEObject jwe; + JOSEObject parsed_jwt; + EncryptingAlg e_alg; + SigningAlgs signing_alg; + + /** + * Constructor that instantiate a JWT object + */ + public JWT() { + raw = ""; + signature = ""; + header = ""; + payload = ""; + sign = false; + private_key_pem = ""; + public_key_pem = ""; + private_key_pem_enc = ""; + public_key_pem_enc = ""; + decrypt = false; + jwe = null; + e_alg = null; + } + + /** + * Parse a JWT token from a string and stores header, payload, and signature string values inside the JWT object + * + * @param raw_jwt the raw JWT in string format + * @throws ParsingException if there are problems in the parsing + */ + public void parse(String raw_jwt) throws ParsingException { + this.raw = raw_jwt; + + if (decrypt) { + // it is a JWE containing a JWT + try { + jwe = JWEObject.parse(raw_jwt); + e_alg = EncryptingAlg.fromString(jwe.getHeader().getAlgorithm().getName()); + JWK jwk_private_enc = JWK.parseFromPEMEncodedObjects(private_key_pem_enc); + + switch (e_alg) { + case RSA_OAEP: + case RSA_OAEP_256: + jwe.decrypt(new RSADecrypter(jwk_private_enc.toRSAKey())); + break; + case ECDH_ES_A128KW: + case ECDH_ES_A256KW: + jwe.decrypt(new ECDHDecrypter(jwk_private_enc.toECKey())); + break; + } + + parsed_jwt = jwe; + + if (parsed_jwt == null) { + throw new ParsingException("Error, JWE payload is not a JWS"); + } + } catch (ParseException | JOSEException e) { + throw new ParsingException("problem in decrypting JWE: " + e); + } + } + + try { + if (!decrypt) // otherwise it is already parsed + parsed_jwt = SignedJWT.parse(raw_jwt); + if (parsed_jwt instanceof JWSObject) { + Header header = parsed_jwt.getHeader(); + signing_alg = SigningAlgs.fromString(header.getAlgorithm().getName()); + } + } catch (ParseException e) { + throw new ParsingException("Error while parsing jwt: " + e); + } + + try { + header = parsed_jwt.getHeader().toString(); + payload = parsed_jwt.getPayload().toString(); + signature = parsed_jwt instanceof JWSObject ? + ((JWSObject) parsed_jwt).getSignature().toString() : null; + } catch (JSONException e) { + throw new ParsingException("Error parsing JWT tokens"); + } + } + + /** + * Check the signature of the jws using the public_key_pem + * + * @return true if the jwt signature is valid, false otherwise + * @throws ParsingException if something goes wrong while checking signature of the jwt + */ + public boolean check_sig() throws ParsingException { + boolean res = false; + if (parsed_jwt == null) { + throw new RuntimeException("JWT need to be parsed before checking signature"); + } + + if (!(parsed_jwt instanceof JWSObject)) + throw new RuntimeException("trying to check the signature of a JWE"); + + JWK pub_key_jwk = null; + try { + pub_key_jwk = JWK.parseFromPEMEncodedObjects(public_key_pem); + } catch (JOSEException e) { + throw new ParsingException("Problem in loading public key, " + e); + } + switch (signing_alg) { + case RS256: + case RS512: + JWSVerifier verifier = null; + try { + verifier = new RSASSAVerifier(pub_key_jwk.toRSAKey()); + } catch (JOSEException e) { + throw new ParsingException("Invalid public key used do verify jwt. " + e); + } + try { + res = ((JWSObject) parsed_jwt).verify(verifier); + } catch (JOSEException e) { + throw new ParsingException("The jws could not be verified. " + e); + } + break; + } + return res; + } + + /** + * Builds a JWT token from the string values in this class + * + * @return A JWT as a string + * @throws ParsingException if there are problems building the jwt + */ + public String build() throws ParsingException { + String res = ""; + + if (this.parsed_jwt == null) { + throw new ParsingException("error in building jwt, no jwt have been parsed"); + } + + if (this.header.equals("") || this.payload.equals("")) + throw new ParsingException("error in building jwt, empty values"); + + res += Base64.getEncoder().encodeToString(header.getBytes()); + res += "." + Base64.getEncoder().encodeToString(payload.getBytes()); + + if (sign) { + // sign the jwt with sk + JWSObject signed_jws = null; + JWK private_key_jwk = null; + try { + JWSHeader header_j = JWSHeader.parse(header); + Payload payload_j = new Payload(payload); + signed_jws = new JWSObject(header_j, payload_j); + private_key_jwk = JWK.parseFromPEMEncodedObjects(private_key_pem); + } catch (ParseException e) { + throw new ParsingException("unable to build jwt: " + e); + } catch (JOSEException e) { + throw new ParsingException("Unable to load public key to sign jwt" + e); + } + JWSSigner signer = null; + + try { + switch (signing_alg) { + case RS256: + case RS512: + signer = new RSASSASigner(private_key_jwk.toRSAKey()); + break; + default: + throw new ParsingException("unsupported signing algorithm" + signing_alg); + } + } catch (JOSEException e) { + throw new ParsingException("unable to use public key to sign jwt: " + e); + } catch (IllegalArgumentException e) { + throw new ParsingException("invalid private key: " + e); + } + + try { + signed_jws.sign(signer); + } catch (JOSEException e) { + throw new ParsingException("unable to sign jwt: " + e); + } + + res = signed_jws.serialize(); + } else { + if (signature != null && !signature.equals("")) { + res += "." + signature; + } + } + + if (decrypt) { + if (!(parsed_jwt instanceof JWEObject)) + throw new RuntimeException("tried to encrypt a JWT"); + + if (public_key_pem_enc.length() != 0) { + // if the JWE has been decrypted, now it needs to be re-encrypted + try { + JWEObject editedJWE = new JWEObject( + JWEHeader.parse(header), + new Payload(payload) + ); + + switch (e_alg) { + case RSA_OAEP: + case RSA_OAEP_256: + editedJWE.encrypt( + new RSAEncrypter(JWK.parseFromPEMEncodedObjects(public_key_pem_enc).toRSAKey())); + break; + case ECDH_ES_A128KW: + case ECDH_ES_A256KW: + editedJWE.encrypt( + new ECDHEncrypter(JWK.parseFromPEMEncodedObjects(public_key_pem_enc).toECKey())); + break; + } + + res = editedJWE.serialize(); + } catch (JOSEException | java.text.ParseException e) { + throw new ParsingException("Unable to encrypt JWE " + e); + } + } else { + // if no public key is provided, the jwe will not be edited + res = raw; + } + } + + res = res.replaceAll("=", ""); + return res; + } + + /** + * All JWS signing algs supported + */ + public enum SigningAlgs { + RS256, + RS512; + + public static SigningAlgs fromString(String algStr) throws ParsingException { + switch (algStr) { + case "RS256": + return RS256; + case "RS512": + return RS512; + default: + throw new ParsingException("Unsupported signing algorithm \"" + algStr + "\""); + } + } + } + + /** + * All JWE encrypting algs supported + */ + public enum EncryptingAlg { + RSA_OAEP, + RSA_OAEP_256, + ECDH_ES_A128KW, + ECDH_ES_A256KW; + + public static EncryptingAlg fromString(String algStr) throws ParsingException { + switch (algStr) { + case "RSA-OAEP": + return RSA_OAEP; + case "RSA-OAEP-256": + return RSA_OAEP_256; + case "ECDH-ES+A128KW": + return ECDH_ES_A128KW; + case "ECDH-ES+A256KW": + return ECDH_ES_A256KW; + default: + throw new ParsingException("Encrypting algorithm " + algStr + " not supported"); + } + } + } +} diff --git a/tool/src/main/java/burp/Marker.java b/tool/src/main/java/migt/Marker.java similarity index 84% rename from tool/src/main/java/burp/Marker.java rename to tool/src/main/java/migt/Marker.java index 468b649..2309f5a 100644 --- a/tool/src/main/java/burp/Marker.java +++ b/tool/src/main/java/migt/Marker.java @@ -1,4 +1,6 @@ -package burp; +package migt; + +import java.util.Objects; /** * Class used to mark User Actions to be managed by session actions @@ -10,6 +12,7 @@ public class Marker { /** * Constructor to instantiate a new marker object + * * @param _name name of the marker */ public Marker(String _name) { @@ -23,6 +26,6 @@ public boolean equals(Object o) { Marker marker = (Marker) o; - return name != null ? name.equals(marker.name) : marker.name == null; + return Objects.equals(name, marker.name); } } \ No newline at end of file diff --git a/tool/src/main/java/migt/MessageOperation.java b/tool/src/main/java/migt/MessageOperation.java new file mode 100644 index 0000000..24e2b20 --- /dev/null +++ b/tool/src/main/java/migt/MessageOperation.java @@ -0,0 +1,479 @@ +package migt; + +import burp.IExtensionHelpers; +import org.json.JSONObject; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static migt.Tools.getAdding; + +/** + * The class storing a MessageOperation object + * + * @author Matteo Bitussi + */ +public class MessageOperation extends Module { + HTTPReqRes.MessageSection from; + String what; + String to; + MessageOperationActions action; + String save_as; // The name of the variable to save the parameter's value + String use; + MessageOpType type; + + // GENERATE POC + String template; + String output_path; + + /** + * Used to Instantiate the class + */ + public MessageOperation() { + init(); + } + + public MessageOperation(JSONObject message_op_json) throws ParsingException { + init(); + + java.util.Iterator keys = message_op_json.keys(); + while (keys.hasNext()) { + String key = keys.next(); + + switch (key) { + case "from": + from = HTTPReqRes.MessageSection.fromString(message_op_json.getString("from")); + break; + case "remove parameter": + what = message_op_json.getString("remove parameter"); + action = MessageOperationActions.REMOVE_PARAMETER; + break; + case "remove match word": + what = message_op_json.getString("remove match word"); + action = MessageOperationActions.REMOVE_MATCH_WORD; + break; + case "edit": + what = message_op_json.getString("edit"); + action = MessageOperationActions.EDIT; + break; + case "edit regex": + what = message_op_json.getString("edit regex"); + action = MessageOperationActions.EDIT_REGEX; + break; + case "in": + to = message_op_json.getString("in"); + break; + case "add": + what = message_op_json.getString("add"); + action = MessageOperationActions.ADD; + break; + case "this": + to = message_op_json.getString("this"); + break; + case "save": + what = message_op_json.getString("save"); + action = MessageOperationActions.SAVE; + break; + case "save match": + what = message_op_json.getString("save match"); + action = MessageOperationActions.SAVE_MATCH; + break; + case "as": + save_as = message_op_json.getString("as"); + break; + case "use": + use = message_op_json.getString("use"); + break; + case "type": + type = MessageOpType.fromString( + message_op_json.getString("type")); + break; + case "template": + template = message_op_json.getString("template"); + break; + case "output_path": + output_path = message_op_json.getString("output_path"); + break; + default: + System.err.println(key); + throw new ParsingException("Message operation not valid"); + } + } + } + + public void init() { + this.what = ""; + this.to = ""; + this.save_as = ""; + this.use = ""; + this.type = MessageOpType.HTTP; + this.template = ""; + this.output_path = ""; + } + + public void loader(Operation_API api) { + this.imported_api = api; + } + + public Operation_API exporter() { + if (imported_api instanceof Operation_API) { + return (Operation_API) this.imported_api; + } + return null; + } + + /** + * Given an operation, and a message, execute the Message operations contained in the operation + * + * @param op the operation containing the message operation + * @return the updated Operation with the result + * @throws ParsingException if parsing of names is not successfull + */ + public Operation execute(Operation op, + IExtensionHelpers helpers) throws ParsingException { + for (MessageOperation mop : op.getMessageOperations()) { + Pattern pattern; + Matcher matcher; + try { + if (mop.type == MessageOperation.MessageOpType.GENERATE_POC) { + if (!op.api.is_request) { + throw new ParsingException("Invalid POC generation, message should be a request"); + } + + if (!mop.template.equals("csrf")) { + continue; // other templates not supported yet + } + + String poc = Tools.generate_CSRF_POC(op.api.message); + + try { + File myObj = new File(mop.output_path); + myObj.createNewFile(); + } catch (IOException e) { + throw new ParsingException("Invalid POC generation output path: " + + mop.output_path + " " + e.getMessage()); + } + try { + FileWriter myWriter = new FileWriter(mop.output_path); + myWriter.write(poc); + myWriter.close(); + } catch (IOException e) { + throw new ParsingException("Something went wrong while writing output file for POC generator: " + + mop.output_path + " " + e.getMessage()); + } + } else { + if (mop.action != null) { + switch (mop.action) { + case REMOVE_PARAMETER: + switch (mop.from) { + case URL: + // Works + if (!op.api.is_request) { + throw new ParsingException("Searching URL in response"); + } + String url_header = op.api.message.getUrlHeader(); + pattern = Pattern.compile("&?" + mop.what + "=[^& ]*((?=&)|(?= ))"); + 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); + 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); + break; + + case BODY: + String body = new String(op.api.message.getBody(op.api.is_request)); + pattern = Pattern.compile(mop.what); + 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); + break; + } + break; + + case ADD: + if (getAdding(mop, op.api.vars) == null | getAdding(mop, op.api.vars).equals("")) { + // TODO: should raise exception or set operation not applicable? + break; + } + 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); + break; + } + case BODY: { + String tmp = new String(op.api.message.getBody(op.api.is_request)); + 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); + break; + } + case URL: + if (!op.api.is_request) { + throw new ParsingException("Searching URL in response"); + } + String header_0 = op.api.message.getUrlHeader(); + + pattern = Pattern.compile("&?" + mop.what + "=[^& ]*((?=&)|(?= ))"); + matcher = pattern.matcher(header_0); + + String newHeader_0 = ""; + boolean found = false; + while (matcher.find() & !found) { + String before = header_0.substring(0, matcher.end()); + String after = header_0.substring(matcher.end()); + newHeader_0 = before + getAdding(mop, op.api.vars) + after; + found = true; + } + op.api.message.setUrlHeader(newHeader_0); + op.processed_message = op.api.message.getMessage(op.api.is_request, helpers); + break; + } + break; + + case EDIT: + op.processed_message = Tools.editMessageParam( + helpers, + mop.what, + mop.from, + op.api.message, + op.api.is_request, + getAdding(mop, op.api.vars), + true); + break; + + case EDIT_REGEX: + op.processed_message = Tools.editMessage( + helpers, + mop.what, + mop, + op.api.message, + op.api.is_request, + getAdding(mop, op.api.vars)); + break; + + case REMOVE_MATCH_WORD: + switch (mop.from) { + case HEAD: { + List headers = op.api.message.getHeaders(op.api.is_request); + pattern = Pattern.compile(mop.what); + List new_headers = new ArrayList<>(); + + for (String header : headers) { + matcher = pattern.matcher(header); + new_headers.add(matcher.replaceAll("")); + } + + op.api.message.setHeaders(op.api.is_request, new_headers); + op.processed_message = op.api.message.getMessage(op.api.is_request, helpers); + break; + } + case BODY: { + pattern = Pattern.compile(mop.what); + matcher = pattern.matcher(new String(op.api.message.getBody(op.api.is_request))); + 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); + break; + } + case URL: + // Works + if (!op.api.is_request) { + throw new ParsingException("Searching URL in response"); + } + String header_0 = op.api.message.getUrlHeader(); + + pattern = Pattern.compile(mop.what); + matcher = pattern.matcher(header_0); + String newHeader_0 = matcher.replaceFirst(""); + + op.api.message.setUrlHeader(newHeader_0); + op.processed_message = op.api.message.getMessage(op.api.is_request, helpers); + break; + } + break; + + case SAVE: + case SAVE_MATCH: + switch (mop.from) { + case HEAD: { + String value = ""; + if (mop.action == MessageOperation.MessageOperationActions.SAVE) { + value = op.api.message.getHeadParam(op.api.is_request, mop.what).trim(); + } else { + List headers = op.api.message.getHeaders(op.api.is_request); + pattern = Pattern.compile(mop.what); + for (String h : headers) { + matcher = pattern.matcher(h); + value = ""; + while (matcher.find()) { + value = matcher.group(); + break; + } + } + } + + Var v = new Var(); + v.name = mop.save_as; + v.isMessage = false; + v.value = value; + op.api.vars.add(v); + break; + } + case BODY: { + String tmp = new String(op.api.message.getBody(op.api.is_request), StandardCharsets.UTF_8); + pattern = Pattern.compile(mop.what); + matcher = pattern.matcher(tmp); + Var v = new Var(); + + while (matcher.find()) { + v.name = mop.save_as; + v.isMessage = false; + v.value = matcher.group(); + break; + } + op.api.vars.add(v); + break; + } + case URL: { + // works + if (!op.api.is_request) { + throw new ParsingException("Searching URL in response"); + } + String header_0 = op.api.message.getUrlHeader(); + + pattern = mop.action == MessageOperation.MessageOperationActions.SAVE ? + Pattern.compile(mop.what + "=[^& ]*(?=(&| ))") : + Pattern.compile(mop.what); + + matcher = pattern.matcher(header_0); + String value = ""; + + if (matcher.find()) { + String matched = matcher.group(); + value = mop.action == MessageOperation.MessageOperationActions.SAVE ? + matched.split("=")[1] : + matched; + + Var v = new Var(); + v.name = mop.save_as; + v.isMessage = false; + v.value = value; + op.api.vars.add(v); + } + break; + } + } + break; + } + } + } + + applicable = true; + + if (op.processed_message != null) { + if (op.api.is_request) { + op.api.message.setRequest(op.processed_message); + } else { + op.api.message.setResponse(op.processed_message); + } + if (op.processed_message_service != null) { + // TODO: check if ok to remove + //op.api.message.setHttpService(op.processed_message_service); + } + } + } catch (StackOverflowError e) { + e.printStackTrace(); + } + } + return op; + } + + /** + * All the possible actions of a MessageOperation + */ + public enum MessageOperationActions { + REMOVE_PARAMETER, + REMOVE_MATCH_WORD, + EDIT, + EDIT_REGEX, + ADD, + SAVE, + SAVE_MATCH; + + /** + * From a string get the corresponding enum value + * + * @param input the string + * @return the enum value + * @throws ParsingException if the input is malformed + */ + public static MessageOperationActions fromString(String input) throws ParsingException { + if (input != null) { + switch (input) { + case "remove parameter": + return REMOVE_PARAMETER; + case "remove match word": + return REMOVE_MATCH_WORD; + case "edit": + return EDIT; + case "edit regex": + return EDIT_REGEX; + case "add": + return ADD; + case "save": + return SAVE; + case "save match": + return SAVE_MATCH; + default: + throw new ParsingException("invalid check operation"); + } + } else { + throw new NullPointerException(); + } + } + } + + /** + * The possible types of messageOps + */ + public enum MessageOpType { + HTTP, + GENERATE_POC; + + /** + * From a string get the corresponding enum value + * + * @param input the string + * @return the enum value + * @throws ParsingException if the input is malformed + */ + public static MessageOpType fromString(String input) throws ParsingException { + if (input != null) { + switch (input) { + case "http": + return HTTP; + case "generate_poc": + return GENERATE_POC; + default: + throw new ParsingException("invalid message Op Type"); + } + } else { + throw new NullPointerException(); + } + } + } +} diff --git a/tool/src/main/java/migt/MessageType.java b/tool/src/main/java/migt/MessageType.java new file mode 100644 index 0000000..b07d331 --- /dev/null +++ b/tool/src/main/java/migt/MessageType.java @@ -0,0 +1,71 @@ +package migt; + +import java.util.ArrayList; +import java.util.List; + +/** + * Class storing a MessageType + * + * @author Matteo Bitussi + */ +public class MessageType implements Cloneable { + String name; + Boolean isRequest; // this tells if the message where the checks are executed is a request or a response + List checks; + String responseName; + String requestName; + + Boolean getByResponse; // this tells if you are getting a response by checking a request + Boolean getByRequest; // this tells if you are getting a request by checking a response + + boolean msg_to_process_is_request; // this tells whether the message to be intercepted is a request or a response + + /** + * Instantiate a MessageType + * + * @param name the name of that message + * @param isRequest if the message is a request + */ + public MessageType(String name, Boolean isRequest) { + this.name = name; + this.isRequest = isRequest; + this.checks = new ArrayList<>(); + this.responseName = ""; + this.requestName = ""; + this.getByResponse = false; + this.getByRequest = false; + this.msg_to_process_is_request = isRequest; // init with this, if getByResponse or getByRequest, then change + // accordingly + } + + /** + * From a list of message types, get the corresponding MessageType from the name + * + * @param list the list of message types + * @param name the name of the message type + * @return the corresponding MessageType (if found) + * @throws Exception if the MessageType can not be found + */ + public static MessageType getFromList(List list, String name) throws ParsingException { + for (MessageType act : list) { + try { + if (act.name.equals(name)) { + return act; + } else if (act.responseName.equals(name)) { + MessageType tmp = (MessageType) act.clone(); + tmp.getByResponse = true; + tmp.msg_to_process_is_request = false; + return tmp; + } else if (act.requestName.equals(name)) { + MessageType tmp = (MessageType) act.clone(); + tmp.getByRequest = true; + tmp.msg_to_process_is_request = true; + return tmp; + } + } catch (CloneNotSupportedException e) { + throw new ParsingException(e.getMessage()); + } + } + throw new ParsingException("cannot find the specified message type \" " + name + "\""); + } +} diff --git a/tool/src/main/java/migt/Module.java b/tool/src/main/java/migt/Module.java new file mode 100644 index 0000000..a4ccc74 --- /dev/null +++ b/tool/src/main/java/migt/Module.java @@ -0,0 +1,104 @@ +package migt; + +import burp.IExtensionHelpers; +import org.json.JSONObject; + +/** + * This class is the Parent class inherited by all modules. It provides some methods and parameters to be + * used by other classes + */ +public class Module { + // These variables should be present in each module + boolean result = true; + boolean applicable = false; + IExtensionHelpers helpers; + API api; // the api of this module + API imported_api; // the api imported from a previous module + + public Module() { + + } + + /** + * Instantiate the module by parsing a JSONObject + * + * @param json_module + */ + public Module(JSONObject json_module) { + // Parse + } + + public Module(IExtensionHelpers helpers) { + this.helpers = helpers; + } + + /** + * This function should be called to check that after an initialization of a module all the necessary parameters + * are set correctly. + * + * @return + */ + public void validate() throws ParsingException { + + } + + /** + * Method used to get the API object of this module to be used in other modules. + */ + public T getAPI() { + return null; + } + + /** + * Method used to set the API object of this module, when it is edited or simply to initiate it. + */ + public void setAPI(API api) { + + } + + /** + * Placeholder of a loader method for the API. This method should load all the things needed by the module + * from the previous module where the API is imported from. + * + * @param api the imported API + */ + public void loader(API api) { + + } + + /** + * Placeholder of a exporter function. This function should return the API object to where it has been loaded after + * it has been edited. There is no need to call the exporter if the API is not edited. + * + * @return the edited API + * @throws ParsingException + */ + public API exporter() throws ParsingException { + return null; + } + + /** + * Update the result of this module from a child module. For convenience, returns the result of the module + * + * @param module the module to save the result from + * @param The module class + */ + public boolean setResult(T module) { + if (!module.applicable) { + this.applicable = false; + this.result = false; + } else if (!module.result) { + this.result = false; + } + return module.result; + } + + /** + * Get the result of this module + * + * @return the result of the module + */ + public boolean getResult() { + return this.result; + } +} diff --git a/tool/src/main/java/migt/Operation.java b/tool/src/main/java/migt/Operation.java new file mode 100644 index 0000000..3919e38 --- /dev/null +++ b/tool/src/main/java/migt/Operation.java @@ -0,0 +1,513 @@ +package migt; + +import burp.IHttpService; +import burp.IInterceptedProxyMessage; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.PatternSyntaxException; + +import static migt.Tools.*; + +/** + * Class storing an Operation in a Test + * + * @author Matteo Bitussi + */ +public class Operation extends Module { + public List messageOperations; + public String from_session; + public String to_session; + public Then then; + public String save_name; + public String session_port; + public List preconditions; + public String replace_request_name; + public String replace_response_name; + public boolean isSessionOp = false; + public List matchedMessages; + public byte[] processed_message; + public IHttpService processed_message_service; // null if it is not changed + public List log_messages; + public List session_operations; + // Decode operations + public List decodeOperations; + // Session operation + // API + Operation_API api; + private List checks; + private String messageType; + private Action action; + private String session; + private SessionOperation.SessionAction sessionAction; + + /** + * Instantiate an operation + */ + public Operation() { + init(); + } + + /** + * Instantiate an Operation parsing a JSON object + * + * @param operation_json the operation defined in MIG-L as JSONObject + * @param isActive if the operation is used inside an active or passive test + * @param messageTypes All the message types imported + * @throws Exception + */ + public Operation(JSONObject operation_json, + boolean isActive, + List messageTypes) throws Exception { + init(); + + if (isActive) { + // If the test is active parse also these + if (operation_json.has("session")) { + // If is a Session Operation + String session = operation_json.getString("session"); + String action = operation_json.getString("action"); + + List lsop = SessionOperation.parseFromJson(operation_json); + if (lsop != null) { + for (SessionOperation sop : lsop) { + session_operations.add(sop); + } + } + + setSession(session); + setSessionAction(action); + isSessionOp = true; + return; + } + + // If is a standard operation + String action = operation_json.getString("action"); + setAction(action); + + if (operation_json.has("from session")) { + from_session = operation_json.getString("from session"); + } + if (operation_json.has("to session")) { + to_session = operation_json.getString("to session"); + } + if (operation_json.has("then")) { + then = Then.fromString(operation_json.getString("then")); + } + if (operation_json.has("save")) { + save_name = operation_json.getString("save"); + } + if (operation_json.has("replace request")) { + replace_request_name = operation_json.getString("replace request"); + } else if (operation_json.has("replace response")) { + replace_response_name = operation_json.getString("replace response"); + } + + // Preconditions + if (operation_json.has("preconditions")) { + JSONArray checks = operation_json.getJSONArray("preconditions"); + preconditions = Tools.parseChecksFromJSON(checks); + } + } + + setMessageType(operation_json.getString("message type"), messageTypes); + + // Message Operations + if (operation_json.has("message operations")) { + JSONArray message_ops = operation_json.getJSONArray("message operations"); + for (int k = 0; k < message_ops.length(); k++) { + JSONObject act_message_op = message_ops.getJSONObject(k); + MessageOperation message_op = new MessageOperation(act_message_op); + messageOperations.add(message_op); + } + } + + // checks + if (operation_json.has("checks")) { + JSONArray checks = operation_json.getJSONArray("checks"); + setChecks(Tools.parseChecksFromJSON(checks)); + } + + // Decode Operations + if (operation_json.has("decode operations")) { + JSONArray decode_ops = operation_json.getJSONArray("decode operations"); + for (int k = 0; k < decode_ops.length(); k++) { + JSONObject act_decode_op = decode_ops.getJSONObject(k); + // recursion managed inside + DecodeOperation decode_op = new DecodeOperation(act_decode_op); + decodeOperations.add(decode_op); + } + } + } + + private void init() { + this.messageOperations = new ArrayList<>(); + this.preconditions = new ArrayList<>(); + this.checks = new ArrayList<>(); + this.setChecks(new ArrayList<>()); + this.matchedMessages = new ArrayList<>(); + this.session_operations = new ArrayList<>(); + this.log_messages = new ArrayList<>(); + this.decodeOperations = new ArrayList<>(); + this.from_session = ""; + this.to_session = ""; + this.save_name = ""; + this.session_port = ""; + this.replace_response_name = ""; + this.replace_request_name = ""; + this.messageType = ""; + this.session = ""; + this.processed_message_service = null; + this.processed_message = null; + } + + public String getMessageType() { + return messageType; + } + + /** + * Set the message type of the message the operation needs to deal with + * + * @param messageType the name of the message type + * @param msg_types the list of message types + * @throws Exception Thrown if the message type is not found + */ + public void setMessageType(String messageType, List msg_types) throws Exception { + if (MessageType.getFromList(msg_types, messageType) != null) { + this.messageType = messageType; + } else { + throw new ParsingException("Message type not found"); + } + this.messageType = messageType; + } + + public List getMessageOperations() { + return messageOperations; + } + + public List getChecks() { + return checks; + } + + public void setChecks(List checks) { + this.checks = checks; + } + + public Action getAction() { + return action; + } + + public void setAction(String action) throws ParsingException { + this.setAction(Action.fromString(action)); + } + + public void setAction(Action action) { + this.action = action; + } + + public String getSession() { + return session; + } + + public void setSession(String sessionName) { + this.session = sessionName; + } + + public SessionOperation.SessionAction getSessionAction() { + return sessionAction; + } + + public void setSessionAction(String sessionAction) throws ParsingException { + this.setSessionAction(SessionOperation.SessionAction.fromString(sessionAction)); + } + + public void setSessionAction(SessionOperation.SessionAction sessionAction) { + this.sessionAction = sessionAction; + } + + public List getDecodeOperations() { + return decodeOperations; + } + + public void setDecodeOperations(List decodeOperations) { + this.decodeOperations = decodeOperations; + } + + /** + * Used to process session operations of a given operation + * + * @return An array of Object elements, the first is the edited operation, the second is the updated variables + */ + public List executeSessionOps(Test t, + List vars) throws ParsingException { + List updated_vars = vars; + for (SessionOperation sop : this.session_operations) { +/* + List vars_new = eal.onBeforeExSessionOps(); + + for (Var v : vars_new) { + if (!updated_vars.contains(v)) { + updated_vars.inse + } + } + */ + Session session = t.getSession(sop.from_session); + Track track = session.track; + + switch (sop.action) { + case SAVE: + Var v = new Var(); + v.name = sop.as; + v.isMessage = false; + v.value = ""; + switch (sop.target) { + case TRACK: + for (SessionTrackAction sa : t.getSession(sop.from_session).track + .getStasFromMarkers(sop.at, sop.to, sop.is_from_included, sop.is_to_included)) { + v.value += sa.toString() + "\n"; + } + break; + case LAST_ACTION: + v.value = session.last_action.toString(); + break; + case LAST_ACTION_ELEM: + v.value = session.last_action.elem; + break; + case LAST_ACTION_ELEM_PARENT: + v.value = findParentDiv(session.last_action.elem); + break; + case LAST_CLICK: + v.value = session.last_click.toString(); + break; + case LAST_CLICK_ELEM: + v.value = session.last_click.elem; + break; + case LAST_CLICK_ELEM_PARENT: + v.value = findParentDiv(session.last_click.elem); + break; + case LAST_OPEN: + v.value = session.last_open.toString(); + break; + case LAST_OPEN_ELEM: + v.value = session.last_open.elem; + break; + case LAST_URL: + v.value = session.last_url; + break; + case ALL_ASSERT: + for (SessionTrackAction sa : t.getSession(sop.from_session).track.getTrack()) { + if (sa.isAssert) { + v.value += sa + "\n"; + } + } + break; + } + updated_vars.add(v); + break; + + case INSERT: + String to_be_added = buildStringWithVars(updated_vars, sop.what); + track.insert(new Marker(sop.at), to_be_added); + break; + + case MARKER: + switch (sop.target) { + case LAST_ACTION: + case LAST_ACTION_ELEM: + track.mark(session.last_action, sop.mark_name); + break; + case LAST_CLICK: + case LAST_CLICK_ELEM: + track.mark(session.last_click, sop.mark_name); + break; + case LAST_OPEN: + case LAST_OPEN_ELEM: + track.mark(session.last_open, sop.mark_name); + break; + case ALL_ASSERT: + for (SessionTrackAction sa : t.getSession(sop.from_session).track.getTrack()) { + if (sa.isAssert) { + track.mark(sa, sop.mark_name); + } + } + break; + case TRACK: + case LAST_URL: + throw new ParsingException("Invalid session operation target: " + sop.target); + default: + throw new ParsingException("Invalid session operation target"); + } + break; + case REMOVE: + if (sop.to != null && !sop.to.equals("")) { + // TODO: remove interval of indices instead of using the remove construct of lists, because it + // removes duplicated things + + int[] range = t.getSession(sop.from_session).track. + getStasIndexFromRange(sop.at, sop.to, sop.is_from_included, sop.is_to_included); + + + t.getSession(sop.from_session).track.getTrack().subList(range[0], range[1] + 1).clear(); + } else { + track.remove(new Marker(sop.at)); + } + break; + } + } + + return updated_vars; + } + + public Operation_API getAPI() { + return this.api; + // TODO: check if the api should be updated with the processed message before returning it + // TODO: the api should be updated i.e. if the message is edited before making it available + } + + public void setAPI(Operation_API api) { + this.api = api; + + // add the intercepted message to the matched messages to be displayed + if (!matchedMessages.contains(api.message)) { + // it could be added multiple times because this method is called by other Modules that returns this api + // edited + matchedMessages.add(api.message); + } + + // updates the processed message from the api + this.processed_message = api.message.build_message(api.is_request); + } + + public void execute() { + if (!preconditions.isEmpty()) { + try { + applicable = Tools.executeChecks( + preconditions, + api.message, + api.is_request, + api.vars + ); + if (!applicable) return; + } catch (ParsingException e) { + applicable = false; + e.printStackTrace(); + return; + } + } + + // Replace the message with the saved one if asked + if (api.is_request) { + if (!replace_request_name.equals("")) { + try { + applicable = true; + processed_message = getVariableByName(replace_request_name, api.vars).message; + processed_message_service = getVariableByName(replace_request_name, api.vars).service_info; + //return op; + } catch (ParsingException e) { + e.printStackTrace(); + applicable = false; + return; + } + } + } else { + if (!replace_response_name.equals("")) { + try { + applicable = true; + processed_message = getVariableByName(replace_response_name, api.vars).message; + processed_message_service = getVariableByName(replace_response_name, api.vars).service_info; + //return op; + } catch (ParsingException e) { + e.printStackTrace(); + applicable = false; + return; + } + } + } + + // execute the message operations and the decode ops + try { + applicable = true; + executeMessageOperations(this, helpers); // TOOD: change to edits + if (!applicable | !result) + return; + executeDecodeOps(this, helpers, api.vars); + if (!applicable | !result) + return; + executeChecks(this, api.vars); + if (!applicable | !result) + return; + + } catch (ParsingException | PatternSyntaxException e) { + applicable = false; + e.printStackTrace(); + return; + } + + if (!save_name.equals("")) { + Var v = new Var(); + v.name = save_name; + v.isMessage = true; + v.message = api.is_request ? api.message.getRequest() : api.message.getResponse(); + v.service_info = api.message.getHttpService(helpers); + api.vars.add(v); + } + } + + /** + * Enum containing all the possible Active operation actions + */ + public enum Action { + INTERCEPT; + + /** + * From a string get the corresponding enum value + * + * @param input the string + * @return the enum value + * @throws ParsingException if the input is malformed + */ + public static Action fromString(String input) throws ParsingException { + if (input != null) { + if (input.equals("intercept")) { + return INTERCEPT; + } + throw new ParsingException("invalid check operation"); + } else { + throw new NullPointerException(); + } + } + } + + /** + * Enum that contains all the possible action to do after a message is received + */ + public enum Then { + FORWARD, + DROP; + + /** + * From a string get the corresponding enum value + * + * @param input the string + * @return the enum value + * @throws ParsingException if the input is malformed + */ + public static Then fromString(String input) throws ParsingException { + if (input != null) { + switch (input) { + case "forward": + return FORWARD; + case "drop": + return DROP; + default: + throw new ParsingException("invalid check operation"); + } + } else { + throw new NullPointerException(); + } + } + } +} + diff --git a/tool/src/main/java/migt/Operation_API.java b/tool/src/main/java/migt/Operation_API.java new file mode 100644 index 0000000..bb8a955 --- /dev/null +++ b/tool/src/main/java/migt/Operation_API.java @@ -0,0 +1,23 @@ +package migt; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class provides an API for an Operation module, to be used by other modules. + */ +public class Operation_API extends API { + public HTTPReqRes message; + public List vars; + boolean is_request; + + public Operation_API(HTTPReqRes message, boolean is_request) { + this.message = message; + this.is_request = is_request; + this.vars = new ArrayList<>(); + } + + public Operation_API(List vars) { + this.vars = vars; + } +} diff --git a/tool/src/main/java/burp/ParsingException.java b/tool/src/main/java/migt/ParsingException.java similarity index 96% rename from tool/src/main/java/burp/ParsingException.java rename to tool/src/main/java/migt/ParsingException.java index 2b45fe2..9afda57 100644 --- a/tool/src/main/java/burp/ParsingException.java +++ b/tool/src/main/java/migt/ParsingException.java @@ -1,4 +1,4 @@ -package burp; +package migt; /** * Exception raised when the parsing of the language fails diff --git a/tool/src/main/java/burp/Session.java b/tool/src/main/java/migt/Session.java similarity index 97% rename from tool/src/main/java/burp/Session.java rename to tool/src/main/java/migt/Session.java index 970519a..1f173e7 100644 --- a/tool/src/main/java/burp/Session.java +++ b/tool/src/main/java/migt/Session.java @@ -1,4 +1,7 @@ -package burp; +package migt; + +import burp.IExtensionHelpers; +import burp.IHttpRequestResponsePersisted; import java.util.ArrayList; import java.util.List; @@ -18,11 +21,6 @@ public class Session { public String last_url; public String name = ""; String port; - - public Track getTrack() { - return track; - } - Track track; int index = 0; List messages; @@ -76,6 +74,10 @@ public Session(String name, String port) { } } + public Track getTrack() { + return track; + } + /** * Tells if the session's track has an element * @@ -129,6 +131,7 @@ public HTTPReqRes addMessage(IHttpRequestResponsePersisted message, IExtensionHe /** * Set the track of this session by parsing a string track + * * @param raw_track the track in string format * @return the parsed track as a Track object * @throws ParsingException if the track is malformed diff --git a/tool/src/main/java/migt/SessionOperation.java b/tool/src/main/java/migt/SessionOperation.java new file mode 100644 index 0000000..ead878d --- /dev/null +++ b/tool/src/main/java/migt/SessionOperation.java @@ -0,0 +1,344 @@ +package migt; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Class containing a session Operation + * + * @author Matteo Bitussi + */ +public class SessionOperation { + public String from_session; + public SessOperationAction action; + public String what; + public String as; + public String at; + public String to; // until to which ession action save + public boolean is_from_included = false; + public boolean is_to_included = false; + public SessOperationTarget target; + public String mark_name; + + /** + * Parses a list of session operations from json + * + * @param act_operation the session operations as JSON object + * @return the list of tession operations + * @throws ParsingException if the parsing goes wrong + */ + public static List parseFromJson(JSONObject act_operation) throws ParsingException { + List lsop = new ArrayList<>(); + if (act_operation.has("session operations")) { + JSONArray session_ops = act_operation.getJSONArray("session operations"); + for (int l = 0; l < session_ops.length(); l++) { + JSONObject act_session_op = session_ops.getJSONObject(l); + SessionOperation sop = new SessionOperation(); + Iterator keys = act_session_op.keys(); + while (keys.hasNext()) { + String key = keys.next(); + + switch (key) { + case "session": + sop.from_session = act_session_op.getString("session"); + break; + case "save": + sop.action = SessOperationAction.SAVE; + sop.target = SessOperationTarget + .getFromString(act_session_op.getString("save")); + break; + case "as": + sop.as = act_session_op.getString("as"); + break; + case "insert": + sop.action = SessOperationAction.INSERT; + sop.what = act_session_op.getString("insert"); + break; + case "at": + sop.at = act_session_op.getString("at"); + break; + case "mark": + sop.action = SessOperationAction.MARKER; + sop.target = SessOperationTarget + .getFromString(act_session_op.getString("mark")); + sop.mark_name = act_session_op.getString("name"); + break; + case "name": + //Already processed in mark + break; + case "remove": + sop.action = SessOperationAction.REMOVE; + if (sop.at == null || sop.at.length() == 0) { + sop.at = act_session_op.getString("remove"); + } + break; + + case "range": + List res = parseRange(act_session_op.getString("range")); + sop.is_from_included = (boolean) res.get(0); + sop.is_to_included = (boolean) res.get(1); + sop.at = (String) res.get(2); + sop.to = (String) res.get(3); + break; + + default: + throw new ParsingException("Unexpected value: " + key); + } + } + lsop.add(sop); + } + return lsop; + } + return null; + } + + /** + * Parse a string containing a range in the form of [number, number] or (number,number], based on the type of + * parenthesis you can say that the rance is included [ or excluded ( + * + * @param range The string containing the range to parse + * @return Position 0, true if from included. + * Position 1: true if to included. + * Position 2: from, + * Position 3: to + */ + public static List parseRange(String range) throws ParsingException { + Pattern p = Pattern.compile("^(\\(|\\[)\\s*([^\\[\\],]*)\\s*,\\s*([^\\[\\],]*)\\s*(\\)|\\])$"); + Matcher m = p.matcher(range); + + List l = new ArrayList<>(); + + List tmp = new ArrayList<>(); + + int count = m.groupCount(); + + if (count != 4) + throw new ParsingException("invalid range in session operation: \"" + range + "\""); + + while (m.find()) { + tmp.add(m.group(1)); + tmp.add(m.group(2)); + tmp.add(m.group(3)); + tmp.add(m.group(4)); + } + + if (tmp.size() != 4) + throw new ParsingException("invalid range in session operation: \"" + range + "\""); + + l.add(tmp.get(0).equals("[")); + l.add(tmp.get(3).equals("]")); + l.add(tmp.get(1)); + l.add(tmp.get(2)); + + return l; + } + + /** + * Enum containing all the possible session operation actions + */ + public enum SessionAction { + START, + PAUSE, + RESUME, + STOP, + CLEAR_COOKIES; + + /** + * From a string get the corresponding enum value + * + * @param input the string + * @return the enum value + * @throws ParsingException if the input is malformed + */ + public static SessionAction fromString(String input) throws ParsingException { + if (input != null) { + switch (input) { + case "start": + return START; + case "pause": + return PAUSE; + case "resume": + return RESUME; + case "stop": + return STOP; + case "clear cookies": + return CLEAR_COOKIES; + default: + throw new ParsingException("invalid Session action"); + } + } else { + throw new NullPointerException(); + } + } + } + + /** + * Defines the action of a session action + */ + public enum SessAction { + CLICK, + OPEN, + TYPE, + SNAPSHOT, + DIFF, + EQUALS, + WAIT, + SET_VAR, + CLEAR_COOKIES, + ASSERT_CLICKABLE, + ASSERT_NOT_CLICKABLE, + ASSERT_VISIBLE, + ASSERT_NOT_VISIBLE, + ASSERT_ELEM_CONTENT_IS, + ASSERT_ELEM_CONTENT_HAS, + ASSERT_ELEM_CLASS_IS, + ASSERT_ELEM_CLASS_HAS, + ASSERT_ELEM_HAS_ATTRIBUTE, + ASSERT_ELEM_NOT_HAS_ATTRIBUTE, + ALERT; + + /** + * Get a session action enum value from a string + * + * @param s the string + * @return the enum value + * @throws ParsingException if the string is invalid + */ + public static SessAction getFromString(String s) throws ParsingException { + switch (s) { + case "assert click": + case "click": + return CLICK; + case "open": + case "assert open": // just an alias of open + return OPEN; + case "type": + return TYPE; + case "snapshot": + return SNAPSHOT; + case "diff": + return DIFF; + case "equals": + return EQUALS; + case "wait": + return WAIT; + case "set var": + return SET_VAR; + case "clear cookies": + return CLEAR_COOKIES; + case "assert clickable": + return ASSERT_CLICKABLE; + case "assert not clickable": + return ASSERT_NOT_CLICKABLE; + case "assert visible": + return ASSERT_VISIBLE; + case "assert not visible": + return ASSERT_NOT_VISIBLE; + case "assert element content is": + return ASSERT_ELEM_CONTENT_IS; + case "assert element content has": + return ASSERT_ELEM_CONTENT_HAS; + case "assert element class is": + return ASSERT_ELEM_CLASS_IS; + case "assert element class has": + return ASSERT_ELEM_CLASS_HAS; + case "assert element has attribute": + return ASSERT_ELEM_HAS_ATTRIBUTE; + case "assert element not has attribute": + return ASSERT_ELEM_NOT_HAS_ATTRIBUTE; + case "alert": + return ALERT; + default: + throw new ParsingException("Invalid session action \"" + s + "\""); + } + } + } + + /** + * Defines the action of a session operation + */ + public enum SessOperationAction { + SAVE, + INSERT, + MARKER, + REMOVE + } + + /** + * Defines the target of a session operation. + * Is it better to use js or just build a form? if a form is used, body has to be interpreted + */ + public enum SessOperationTarget { + LAST_ACTION, + LAST_ACTION_ELEM, + LAST_ACTION_ELEM_PARENT, + LAST_CLICK, + LAST_CLICK_ELEM, + LAST_CLICK_ELEM_PARENT, + LAST_OPEN, + LAST_OPEN_ELEM, + LAST_URL, + ALL_ASSERT, + TRACK; + + /** + * Parse a string containing a session operation target + * + * @param s the string to parse + * @throws ParsingException if the string is malformed, or no session operation target is found + */ + public static SessOperationTarget getFromString(String s) throws ParsingException { + + if (s.contains(".")) { + String[] splitted; + splitted = s.split("\\."); + boolean parent = false; + if (splitted.length == 3) { + if (splitted[2].equals("parent")) { + parent = true; + } + } + + switch (s) { + case "last_action.elem": + case "last_action.elem.parent": + return parent ? LAST_ACTION_ELEM_PARENT : LAST_ACTION_ELEM; + case "last_click.elem": + case "last_click.elem.parent": + return parent ? LAST_CLICK_ELEM_PARENT : LAST_CLICK_ELEM; + case "last_open.elem": + return LAST_OPEN_ELEM; + case "last_url": + return LAST_URL; + case "all_assert": + return ALL_ASSERT; + default: + throw new ParsingException("invalid target in session operation"); + } + } else { + switch (s) { + case "track": + return TRACK; + case "last_action": + return LAST_ACTION; + case "last_click": + return LAST_CLICK; + case "last_open": + return LAST_OPEN; + case "last_url": + return LAST_URL; + case "all_assert": + return ALL_ASSERT; + default: + throw new ParsingException("invalid target in session operation"); + } + } + } + } +} diff --git a/tool/src/main/java/burp/SessionTrackAction.java b/tool/src/main/java/migt/SessionTrackAction.java similarity index 83% rename from tool/src/main/java/burp/SessionTrackAction.java rename to tool/src/main/java/migt/SessionTrackAction.java index 4488e2e..92b6a09 100644 --- a/tool/src/main/java/burp/SessionTrackAction.java +++ b/tool/src/main/java/migt/SessionTrackAction.java @@ -1,7 +1,8 @@ -package burp; +package migt; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * This class represents an user action in a session @@ -9,7 +10,7 @@ * @author Matteo Bitussi */ public class SessionTrackAction { - public Utils.SessAction action; + public SessionOperation.SessAction action; public String elem_type; public String elem_source; public String elem; @@ -33,6 +34,7 @@ public SessionTrackAction() { /** * Constructor parsing a raw action in string format + * * @param raw_action The user action in string format * @throws ParsingException If the user action is written wrongly */ @@ -43,6 +45,7 @@ public SessionTrackAction(String raw_action) throws ParsingException { /** * Function used to parse a string containing a raw user action in string format + * * @param raw_action the user action in string format * @throws ParsingException if user action is not written properly */ @@ -53,18 +56,18 @@ public void parse_raw_action(String raw_action) throws ParsingException { throw new ParsingException("invalid session action \"" + raw_action + "\""); } - action = Utils.SessAction.getFromString(splitted[0].trim()); + action = SessionOperation.SessAction.getFromString(splitted[0].trim()); if (splitted[0].trim().contains("assert")) { isAssert = true; } - if (action == Utils.SessAction.CLEAR_COOKIES) return; + if (action == SessionOperation.SessAction.CLEAR_COOKIES) return; elem = splitted[1].trim(); - if (!(action == Utils.SessAction.OPEN) && - action != Utils.SessAction.WAIT && - action != Utils.SessAction.ALERT && - action != Utils.SessAction.SET_VAR) { + if (!(action == SessionOperation.SessAction.OPEN) && + action != SessionOperation.SessAction.WAIT && + action != SessionOperation.SessAction.ALERT && + action != SessionOperation.SessAction.SET_VAR) { String[] tmp = elem.split("="); elem_type = tmp[0].trim(); elem_source = tmp[1].trim(); @@ -79,6 +82,7 @@ public void parse_raw_action(String raw_action) throws ParsingException { /** * Prints the user action as it was in string format. Compliant with the language. + * * @return */ @Override @@ -151,7 +155,7 @@ public String toString() { case CLEAR_COOKIES: break; } - if (action == Utils.SessAction.TYPE) { + if (action == SessionOperation.SessAction.TYPE) { res += " " + content; } @@ -167,10 +171,10 @@ public boolean equals(Object o) { if (isAssert != that.isAssert) return false; if (action != that.action) return false; - if (elem_type != null ? !elem_type.equals(that.elem_type) : that.elem_type != null) return false; - if (elem_source != null ? !elem_source.equals(that.elem_source) : that.elem_source != null) return false; - if (elem != null ? !elem.equals(that.elem) : that.elem != null) return false; - if (content != null ? !content.equals(that.content) : that.content != null) return false; - return markers != null ? markers.equals(that.markers) : that.markers == null; + if (!Objects.equals(elem_type, that.elem_type)) return false; + if (!Objects.equals(elem_source, that.elem_source)) return false; + if (!Objects.equals(elem, that.elem)) return false; + if (!Objects.equals(content, that.content)) return false; + return Objects.equals(markers, that.markers); } } \ No newline at end of file diff --git a/tool/src/main/java/burp/Test.java b/tool/src/main/java/migt/Test.java similarity index 66% rename from tool/src/main/java/burp/Test.java rename to tool/src/main/java/migt/Test.java index 13eb0d3..66506a7 100644 --- a/tool/src/main/java/burp/Test.java +++ b/tool/src/main/java/migt/Test.java @@ -1,4 +1,8 @@ -package burp; +package migt; + +import burp.IInterceptedProxyMessage; +import org.json.JSONArray; +import org.json.JSONObject; import java.io.BufferedWriter; import java.io.File; @@ -8,6 +12,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashSet; +import java.util.Iterator; import java.util.List; /** @@ -16,33 +21,122 @@ * @author Matteo Bitussi */ public class Test { - public Utils.ResultType result; + public ResultType result; public String resultSession; public List sessions; public String references; public String violated_properties; public String affected_entity; public String mitigations; + public List vars; + public String error_str; + public Boolean error; Boolean isActive; List operations; boolean success = false; boolean applicable = true; - boolean error = false; - String error_srt; // Infos String name; String description; /** - * Instantiate a test + * Empty constructor for tests */ public Test() { + init(); + } + + /** + * Instantiate a test + */ + public Test(JSONObject test_json, + List messageTypes) throws Exception { + init(); + + description = test_json.getString("description"); + name = test_json.getString("name"); + setType(test_json.getString("type")); + + Iterator keys = test_json.keys(); + while (keys.hasNext()) { + String key = keys.next(); + + switch (key) { + case "name": + case "type": + case "description": + case "result": + case "operations": + case "sessions": + break; + case "references": + references = test_json.getString("references"); + break; + case "violated_properties": + violated_properties = test_json.getString("violated_properties"); + break; + case "mitigations": + mitigations = test_json.getString("mitigations"); + break; + case "affected_entity": + affected_entity = test_json.getString("affected_entity"); + break; + default: + throw new ParsingException("Invalid key \"" + key + "\""); + } + } + + // set result + if (isActive) { + if (test_json.has("result")) { + String tmp = test_json.getString("result"); + if (tmp.contains("assert_only")) { + result = ResultType.fromString(tmp); + } else { + tmp = tmp.trim(); + String[] splitted = tmp.split("flow"); + + if (splitted.length > 1) { + resultSession = splitted[1].trim(); + } + result = ResultType.fromString(splitted[0].trim()); + } + } + } + + if (test_json.has("sessions")) { + JSONArray arrSess = test_json.getJSONArray("sessions"); + Iterator it = arrSess.iterator(); + + while (it.hasNext()) { + String act_sess_name = (String) it.next(); + sessions.add(new Session(act_sess_name)); + } + } else { + throw new ParsingException("session tag is missing"); + } + + //Array of Operations + JSONArray arrOps = test_json.getJSONArray("operations"); + + //Reads all the operations + for (int j = 0; j < arrOps.length(); j++) { + JSONObject act_operation = arrOps.getJSONObject(j); + + Operation op = new Operation(act_operation, isActive, messageTypes); + operations.add(op); + } + } + + public void init() { + vars = new ArrayList<>(); this.resultSession = ""; this.name = ""; this.description = ""; - this.error_srt = ""; this.operations = new ArrayList<>(); this.sessions = new ArrayList<>(); + this.error_str = ""; + this.error = false; this.success = false; this.isActive = false; @@ -83,14 +177,14 @@ public List getRows() { int count = 0; for (Operation op : operations) { - for (Operation.MatchedMessage msg : op.matchedMessages) { + for (HTTPReqRes msg : op.matchedMessages) { String[] tmp = new String[]{ String.valueOf(count), String.valueOf(op.getMessageType()), - String.valueOf(op.getMessageSection()), - op.isRegex ? op.getRegex() : op.getChecks().toString(), + "", + op.getChecks().toString(), msg.index.toString(), - msg.isFail ? "failed" : "passed"}; + "-"}; // TODO: somehow put if the message made the test fail res.add(tmp); } count++; @@ -136,6 +230,7 @@ public Session getSession(String session_name) throws ParsingException { /** * Function used to log the test informations, such as matched messages, all the messages intercepted, and sessions + * * @param log_folder The folder where to log the test */ public void logTest(String log_folder) { @@ -190,28 +285,28 @@ public void logTest(String log_folder) { "/operation_" + op_count + "_" + o.getMessageType(); - for (Operation.MatchedMessage m : o.matchedMessages) { - if (m.message != null) { - if (m.message.getRequest() != null) { + for (HTTPReqRes m : o.matchedMessages) { + if (m != null) { + if (m.getRequest() != null) { File log_message = new File(base_path + "_request.raw"); try { FileWriter fw = new FileWriter(log_message.getAbsoluteFile()); BufferedWriter bw = new BufferedWriter(fw); bw.write(header); - bw.write(new String(m.message.getRequest(), StandardCharsets.UTF_8)); + bw.write(new String(m.getRequest(), StandardCharsets.UTF_8)); bw.close(); } catch (IOException e) { e.printStackTrace(); } message_count++; } - if (m.message.getResponse() != null) { + if (m.getResponse() != null) { File log_message = new File(base_path + "_response.raw"); try { FileWriter fw = new FileWriter(log_message.getAbsoluteFile()); BufferedWriter bw = new BufferedWriter(fw); bw.write(header); - bw.write(new String(m.message.getResponse(), StandardCharsets.UTF_8)); + bw.write(new String(m.getResponse(), StandardCharsets.UTF_8)); bw.close(); } catch (IOException e) { e.printStackTrace(); @@ -279,4 +374,37 @@ public void logTest(String log_folder) { op_count++; } } + + /** + * The result type of (also the oracle) of an Active test + */ + public enum ResultType { + CORRECT_FLOW, + INCORRECT_FLOW, + ASSERT_ONLY; + + /** + * From a string get the corresponding enum value + * + * @param input the string + * @return the enum value + * @throws ParsingException if the input is malformed + */ + public static ResultType fromString(String input) throws ParsingException { + if (input != null) { + switch (input) { + case "correct": + return CORRECT_FLOW; + case "incorrect": + return INCORRECT_FLOW; + case "assert_only": + return ASSERT_ONLY; + default: + throw new ParsingException("invalid result"); + } + } else { + throw new NullPointerException(); + } + } + } } diff --git a/tool/src/main/java/burp/TestSuite.java b/tool/src/main/java/migt/TestSuite.java similarity index 86% rename from tool/src/main/java/burp/TestSuite.java rename to tool/src/main/java/migt/TestSuite.java index b6afbd0..5740d2e 100644 --- a/tool/src/main/java/burp/TestSuite.java +++ b/tool/src/main/java/migt/TestSuite.java @@ -1,4 +1,4 @@ -package burp; +package migt; import java.util.ArrayList; import java.util.List; @@ -12,8 +12,6 @@ public class TestSuite { String name; String description; List tests; - Test currentTest; - boolean metadata = false; /** * Instantiate the TestSuite @@ -22,8 +20,6 @@ public TestSuite() { this.name = ""; this.description = ""; this.tests = new ArrayList<>(); - - this.currentTest = null; } /** @@ -37,8 +33,6 @@ public TestSuite(String name, String description, List tests) { this.name = name; this.description = description; this.tests = tests; - - this.currentTest = null; } public List getTests() { diff --git a/tool/src/main/java/migt/Tools.java b/tool/src/main/java/migt/Tools.java new file mode 100644 index 0000000..8e8be5d --- /dev/null +++ b/tool/src/main/java/migt/Tools.java @@ -0,0 +1,869 @@ +package migt; + +import burp.IExtensionHelpers; +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.PathNotFoundException; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Class with methods to process messages and execute tests + * + * @author Matteo Bitussi + */ +public class Tools { + /** + * Function that execute the given passive test. + * + * @param test a Test element, it has to be a passive test + * @param messageList a list of HTTPReqRes messages + * @param helpers an istance of IExtensionHelpers + * @param msg_types the message types used by the test + * @return true if a test is passed, false otherwise + */ + public static boolean executePassiveTest(Test test, + List messageList, + IExtensionHelpers helpers, + List msg_types) throws ParsingException { + int i, j; + boolean res = true; + + for (i = 0; i < messageList.size(); i++) { + j = 0; + while (j < test.operations.size() && res) { + Operation currentOP = test.operations.get(j); + MessageType msg_type = MessageType.getFromList(msg_types, currentOP.getMessageType()); + + if (currentOP.api == null) { + currentOP.api = new Operation_API(test.vars); + } else { + currentOP.api.vars = test.vars; + } + + if (messageList.get(i).matches_msg_type(msg_type)) { + currentOP.helpers = helpers; + + currentOP.setAPI(new Operation_API(messageList.get(i), msg_type.msg_to_process_is_request)); + currentOP.execute(); + res = currentOP.getResult(); + } + + test.vars = currentOP.api.vars; + j++; + } + } + + for (Operation op : test.operations) { + if (!op.applicable) { + res = false; + test.applicable = false; + break; + } + } + + return res; + } + + /** + * Function that given a list of headers, concatenates them in a single string + * + * @param headers the list of headers + * @return the string + */ + public static String getAllHeaders(List headers) { + StringBuilder out = new StringBuilder(); + for (Object o : headers) { + out.append(o.toString()); + out.append("\n"); + } + return out.toString(); + } + + /** + * This function execute a list of checks over a message, returning true if all the checks are successful + * + * @param checks a List of checks + * @param message the message to be checked + * @param isRequest set true if the request has to be checked, false for the response + * @return returns the result of the checks (true if all the tests are successful) + */ + public static boolean executeChecks(List checks, + HTTPReqRes message, + boolean isRequest, + List vars) throws ParsingException { + for (Check c : checks) { + if (!c.execute(message, isRequest, vars)) { + return false; + } + } + return true; + } + + /** + * Execute a list of checks in an operation. Uses API. + * + * @param op the operation to execute checks from + * @return the result of the checks + * @throws ParsingException if something goes wrong related to the definition of the test + */ + public static Operation executeChecks(Operation op, List vars) throws ParsingException { + for (Check c : op.getChecks()) { + c.loader(op.api); + c.execute(vars); + if (!op.setResult(c)) + break; + } + return op; + } + + /** + * Executes the decode operations in an operation. Uses APIs. Sets the result to the operation + * + * @param op the operation to execute the decode operations from + * @param helpers the Burp helpers + * @return The operation (edited) + * @throws ParsingException if something goes wrong + */ + public static Operation executeDecodeOps(Operation op, + IExtensionHelpers helpers, + List vars) throws ParsingException { + Operation_API api = op.getAPI(); + for (DecodeOperation dop : op.getDecodeOperations()) { + dop.loader(api, helpers); + dop.execute(vars); + if (!op.setResult(dop)) + break; + op.setAPI(dop.exporter()); + } + + return op; + } + + /** + * Executes the decode operations in a decode operation. This is the recursive step. + * + * @param op the decode operation executing its child decode operations + * @param helpers the burp helpers + * @return The operation (edited) + * @throws ParsingException if something goes wrong + */ + public static DecodeOperation executeDecodeOps(DecodeOperation op, + IExtensionHelpers helpers, + List vars) throws ParsingException { + DecodeOperation_API api = op.getAPI(); + for (DecodeOperation dop : op.decodeOperations) { + dop.loader(api, helpers); + dop.execute(vars); + if (!op.setResult(dop)) + break; + op.setAPI(dop.exporter()); + } + + return op; + } + + /** + * Executes the edit operations inside a decode operation + * + * @param op the decode operation to run the edit operations from + * @return the Decode operation (edited) + * @throws ParsingException if something goes wrong + */ + public static DecodeOperation executeEditOps(DecodeOperation op, + List vars) throws ParsingException { + DecodeOperation_API api = op.getAPI(); + for (EditOperation eop : op.editOperations) { + eop.loader(api); + eop.execute(vars); + if (!op.setResult(eop)) + break; + op.setAPI(eop.exporter()); + } + + return op; + } + + public static Operation executeMessageOperations(Operation op, IExtensionHelpers helpers) throws ParsingException { + for (MessageOperation mop : op.messageOperations) { + mop.loader(op.api); + mop.execute(op, helpers); + op.setAPI(mop.exporter()); + if (op.setResult(op)) + break; + } + return op; + } + + /** + * Function that parses checks from a JSON array + * + * @param checks_array the JSONarray that should contain checks + * @return a List of Check elements + * @throws ParsingException if the input is malformed + */ + public static List parseChecksFromJSON(JSONArray checks_array) throws ParsingException { + List res = new ArrayList<>(); + for (int k = 0; k < checks_array.length(); k++) { + JSONObject act_check = checks_array.getJSONObject(k); + Check check = new Check(act_check); + + if (check.in == null) { + throw new ParsingException("In tag cannot be empty"); + } + res.add(check); + } + return res; + } + + /** + * Parses a list of Edit operations from a JSON array + * + * @param edits_array the input JSON array containing the edit operations + * @return the parsed list of Edit operations + * @throws ParsingException if there are problems parsing the JSON array + */ + public static List parseEditsFromJSON(JSONArray edits_array) throws ParsingException { + List res = new ArrayList<>(); + for (int i = 0; i < edits_array.length(); i++) { + JSONObject act_edit = edits_array.getJSONObject(i); + EditOperation edit = new EditOperation(act_edit); + res.add(edit); + } + return res; + } + + /** + * Function used to parse the message types from a string + * + * @param input a string containing the msg types in JSON + * @return a List of messagetype objects + * @throws ParsingException if the input is malformed + */ + public static List readMsgTypeFromJson(String input) throws ParsingException { + List msg_types = new ArrayList<>(); + + JSONObject obj = new JSONObject(input); + JSONArray message_types = obj.getJSONArray("message_types"); + + for (int i = 0; i < message_types.length(); i++) { + JSONObject act_msg_type = message_types.getJSONObject(i); + + String name = act_msg_type.getString("name"); + Boolean isRequest = act_msg_type.getBoolean("is request"); + + MessageType msg_obj = new MessageType(name, isRequest); + + if (act_msg_type.has("response name")) { + msg_obj.responseName = act_msg_type.getString("response name"); + } + if (act_msg_type.has("request name")) { + msg_obj.requestName = act_msg_type.getString("request name"); + } + + if (act_msg_type.has("checks")) { + msg_obj.checks = parseChecksFromJSON(act_msg_type.getJSONArray("checks")); + } else { + throw new ParsingException("message type definition is invalid, no checks or regex found"); + } + msg_types.add(msg_obj); + } + + return msg_types; + } + + /** + * Returns the adding of a message operation, decides if the value to be inserted/edited should be a variable or + * a typed value and return it + * + * @param m the message operation which has to be examined + * @return the adding to be used in add/edit + * @throws ParsingException if the variable name is not valid or the variable has not been initiated + */ + public static String getAdding(MessageOperation m, List vars) throws ParsingException { + if (!m.use.isEmpty()) { + return getVariableByName(m.use, vars).value; + } else { + + return m.to; + } + } + + /** + * Returns the default string that contains the default message types that fill a msg_def.json file + * + * @return the string + */ + public static String getDefaultJSONMsgType() { + return "{\n" + + " \"message_types\": [\n" + + " {\n" + + " \"name\": \"authorization request\",\n" + + " \"is request\": true,\n" + + " \"response name\": \"authorization response\",\n" + + " \"checks\": [\n" + + " {\n" + + " \"in\": \"url\",\n" + + " \"check param\": \"response_type\",\n" + + " \"is present\": \"true\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"name\": \"token request\",\n" + + " \"is request\": true,\n" + + " \"response name\": \"token response\",\n" + + " \"checks\": [\n" + + " {\n" + + " \"in\": \"url\",\n" + + " \"check param\": \"code\",\n" + + " \"is present\": \"true\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"name\": \"coda landing request\",\n" + + " \"is request\": true,\n" + + " \"response name\": \"coda landing response\",\n" + + " \"checks\": [\n" + + " {\n" + + " \"in\": \"url\",\n" + + " \"check\": \"/welcome\",\n" + + " \"is present\": \"true\"\n" + + " },\n" + + " {\n" + + " \"in\": \"head\",\n" + + " \"check\": \"Host\",\n" + + " \"is\": \"coda.io\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"name\": \"saml request\",\n" + + " \"is request\": true,\n" + + " \"checks\": [\n" + + " {\n" + + " \"in\": \"url\",\n" + + " \"check param\": \"SAMLRequest\",\n" + + " \"is present\": true\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"name\": \"saml response\",\n" + + " \"is request\": true,\n" + + " \"checks\": [\n" + + " {\n" + + " \"in\": \"body\",\n" + + " \"check param\": \"SAMLResponse\",\n" + + " \"is present\": true\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + } + + /** + * Returns the default string that contains the default config for the config.json file + * + * @return the string + */ + public static String getDefaultJSONConfig() { + return "{\n" + + " \"last_driver_path\":\"\",\n" + + " \"last_browser_used\": \"\"\n" + + "}"; + } + + /** + * Removes all the newlines from a string + * + * @return the edited message + */ + public static String removeNewline(String input) { + Pattern p = Pattern.compile("\n"); + Matcher m = p.matcher(input); + + String out = m.replaceAll(""); + return out; + } + + /** + * Builds a string, substituting variables names with values + * + * @param vars the list of variables to use + * @param s the string + * @return the builded string + * @throws ParsingException if a variable is not found + */ + public static String buildStringWithVars(List vars, String s) throws ParsingException { + Pattern p = Pattern.compile("\\$[^\\$]*\\$"); + Matcher m = p.matcher(s); + + String res = s; + + HashMap req_var = new HashMap<>(); + + while (m.find()) { + String act_match = m.group(); + act_match = act_match.replaceAll("\\$", ""); + req_var.put(act_match, getVariableByName(act_match, vars).value); + } + + if (req_var.size() == 0) { + return s; + } + + for (String key : req_var.keySet()) { + res = res.replaceAll("\\$" + key + "\\$", Matcher.quoteReplacement(req_var.get(key))); + } + return res; + } + + /** + * Given a name, returns the corresponding variable + * + * @param name the name of the variable + * @return the Var object + * @throws ParsingException if the variable cannot be found + */ + public static Var getVariableByName(String name, List vars) throws ParsingException { + for (Var act : vars) { + if (act.name.equals(name)) { + return act; + } + } + throw new ParsingException("variable \"" + name + "\" not defined"); + } + + /** + * Generates a CSRF POC from an HTTP request message + * + * @param message the message to generate the POC from + * @return the html poc as a string + */ + public static String generate_CSRF_POC(HTTPReqRes message) { + + String CSFR_TEMPLATE = "\n" + + "\n" + + " \n" + + "

Attack Page

\n" + + "

Service Provider (SP) is your service.

\n" + + "

Identity Provider (IdP) is the provider with which the SP allows to associate the account.

\n" + + "

These are the steps to reproduce the attack:

\n" + + "

1. The victim clicks on button to initiate force-login to IdP and victim logs in as the attacker because IdP suffering of Pre-Authentication Login CSRF. To simulate this step, the victim logs in with the attacker's IdP credentials.

\n" + + "

2. The victim logins at SP with victim credentials.

\n" + + "

3. The victim clicks on following link which suffers of CSRF, to associate attacker IdP account with the Victim SP account.

\n" + + " $INSERT_HERE$\n" + + "
\n" + + "

4. If the IdP attacker account has been associated with the victim SP account then the vulnerability has been properly exploited.

\n" + + " \n" + + ""; + + String POST_TEMPLATE = + "
\n" + + " \n" + + " $BODY_PARAMETERS$\n" + + "
\n" + + " \n" + + "
\n"; + + String TEMPLATE_BODY_PARAMS = " \n" + + " $PARAM_NAME$\n" + + " \n" + + " \n" + + " \n" + + " "; + + String encoding = message.getHeadParam(true, "Content-Type").strip(); + String body = new String(message.getBody(true), StandardCharsets.UTF_8); // splitMessage(message, helpers, true).get(2); + String url = message.getUrl(); + String method = message.getUrlHeader().split(" ")[0]; + + Pattern p = Pattern.compile(""); + Matcher m = p.matcher(body); + + String res = ""; + + res = POST_TEMPLATE; + p = Pattern.compile("\\$ENCODING_TYPE\\$"); + m = p.matcher(res); + res = m.replaceAll(encoding); + + p = Pattern.compile("\\$METHOD\\$"); + m = p.matcher(res); + res = m.replaceAll(method); + + if (method.equals("POST")) { + p = Pattern.compile("([^=]*)=([^&\\n$]*)(&|\\n|$)"); + m = p.matcher(body); + String out_body_params = ""; + + if (body.length() != 0) { + Map body_params = new HashMap<>(); + while (m.find()) { + String name = m.group(1); + String value = m.group(2); + if (name.length() != 0) { + body_params.put(name, + value.length() != 0 ? value : ""); + } + } + for (String key : body_params.keySet()) { + String tmp = TEMPLATE_BODY_PARAMS; + p = Pattern.compile("\\$PARAM_NAME\\$"); + m = p.matcher(tmp); + + tmp = m.replaceAll(key); + + p = Pattern.compile("\\$PARAM_VALUE\\$"); + m = p.matcher(tmp); + + tmp = m.replaceAll(body_params.get(key)); + + out_body_params += tmp; + } + } + + p = Pattern.compile("\\$URL\\$"); + m = p.matcher(res); + res = m.replaceAll(url); + + p = Pattern.compile("\\$BODY_PARAMETERS\\$"); + m = p.matcher(res); + res = m.replaceAll(out_body_params); + } else { + boolean has_query_params = url.split("\\?").length > 1; + String out_query_params = ""; + + if (has_query_params) { + String raw_query_params = url.split("\\?")[1]; + + p = Pattern.compile("([^=\\n&]*)=([^=\\n&]*)"); + m = p.matcher(raw_query_params); + + Map query_params = new HashMap<>(); + while (m.find()) { + String name = m.group(1); + String value = m.group(2); + if (name.length() != 0) { + query_params.put(name, value.length() != 0 ? value : ""); + } + } + for (String key : query_params.keySet()) { + String tmp = TEMPLATE_BODY_PARAMS; + p = Pattern.compile("\\$PARAM_NAME\\$"); + m = p.matcher(tmp); + + tmp = m.replaceAll(key); + + p = Pattern.compile("\\$PARAM_VALUE\\$"); + m = p.matcher(tmp); + + tmp = m.replaceAll(query_params.get(key)); + + out_query_params += tmp; + } + } + + p = Pattern.compile("\\$URL\\$"); + m = p.matcher(res); + res = m.replaceAll(has_query_params ? url.split("\\?")[0] : url); + + p = Pattern.compile("\\$BODY_PARAMETERS\\$"); + m = p.matcher(res); + res = m.replaceAll(out_query_params); + } + + String tmp = CSFR_TEMPLATE; + p = Pattern.compile("\\$INSERT_HERE\\$"); + m = p.matcher(tmp); + tmp = m.replaceAll(res); + + return tmp; + } + + /** + * Create batches of passive tests, grouping them by the session they need to execute. + * + * @return An HashMap object having as keys, Strings representing the sessions names, and as value a list of tests + * that need to execute that session + */ + public static HashMap> batchPassivesFromSession(List testList) throws ParsingException { + HashMap> batch = new HashMap<>(); + for (Test t : testList) { + if (t.sessions.size() == 0) { + throw new ParsingException("Undefined session in test " + t.name); + } + + if (!batch.containsKey(t.sessions.get(0).name)) { + List n = new ArrayList<>(); + n.add(t); + batch.put(t.sessions.get(0).name, n); + } else { + List tmp = batch.get(t.sessions.get(0).name); + tmp.add(t); + batch.put(t.sessions.get(0).name, tmp); + } + } + return batch; + } + + /** + * From a batch of tests grouped by sessions, return a list containing all the tests + * + * @param batch the batch of tests in the form of a MAP> + * @return a list containing all the tests + */ + public static List debatchPassive(HashMap> batch) { + List res = new ArrayList<>(); + for (String sessionName : batch.keySet()) { + for (Test t : batch.get(sessionName)) { + res.add(t); + } + } + return res; + } + + /** + * Edit a message treating it as a string using a regex + * + * @param helpers an instance of Burp's IExtensionHelper + * @param regex the regex used to match the things to change + * @param mop the message operation containing information about the section to match the regex + * @param messageInfo the message as IHttpRequestResponse object + * @param isRequest specify if the message to consider is the request or response + * @param new_value the new value to substitute to the message section + * @return the edited message as byte array + * @throws ParsingException if problems are encountered in editing the message + */ + public static byte[] editMessage(IExtensionHelpers helpers, + String regex, + MessageOperation mop, + HTTPReqRes messageInfo, + boolean isRequest, + String new_value) throws ParsingException { + // TODO: remove dependency from Helpers + Pattern pattern = null; + Matcher matcher = null; + switch (mop.from) { + case HEAD: + List head = messageInfo.getHeaders(isRequest); + pattern = Pattern.compile(regex); + List new_head = new ArrayList<>(); + + for (String act_header : head) { + matcher = pattern.matcher(act_header); + new_head.add(matcher.replaceAll(new_value)); + } + messageInfo.setHeaders(isRequest, new_head); + return messageInfo.getMessage(isRequest, helpers); + + case BODY: + pattern = Pattern.compile(regex); + + matcher = pattern.matcher(new String(messageInfo.getBody(isRequest))); + messageInfo.setBody(isRequest, matcher.replaceAll(new_value)); + //Automatically update content-lenght + return messageInfo.getMessage(isRequest, helpers); + + case URL: + if (!isRequest) { + throw new ParsingException("Encoding URL in response"); + } + + pattern = Pattern.compile(regex); + matcher = pattern.matcher(messageInfo.getUrlHeader()); + String replaced = matcher.replaceAll(new_value); + messageInfo.setUrlHeader(replaced); + + return messageInfo.getMessage(isRequest, helpers); + } + return null; + } + + /** + * Edit a message parameter + * + * @param helpers an instance of Burp's IExtensionHelper + * @param param_name the name of the parameter to edit + * @param message_section the message section to edit + * @param messageInfo the message as IHttpRequestResponse object + * @param isRequest specify if the message to consider is the request or response + * @param new_value the new value of the parameter + * @param isBodyRegex when the section is body, set it to true if you want to use a regex to substitute the value, + * otherwise a parameter param=... is searched + * @return the edited message as byte array + * @throws ParsingException if problems are encountered in editing the message + */ + public static byte[] editMessageParam(IExtensionHelpers helpers, + String param_name, + HTTPReqRes.MessageSection message_section, + HTTPReqRes messageInfo, + boolean isRequest, + String new_value, + boolean isBodyRegex) throws ParsingException { + Pattern pattern = null; + Matcher matcher = null; + switch (message_section) { + case HEAD: + messageInfo.editHeadParam(isRequest, param_name, new_value); + byte[] message = messageInfo.getMessage(isRequest, helpers); + messageInfo.setHost(new_value); // this should be set when the message is converted to the burp class + return message; + + case BODY: + if (!isBodyRegex) { + pattern = Pattern.compile("(?<=" + param_name + "=)[^$\\n& ]*"); + } else { + pattern = Pattern.compile(param_name); + } + + matcher = pattern.matcher(new String(messageInfo.getBody(isRequest))); + String new_body = matcher.replaceFirst(new_value); + messageInfo.setBody(isRequest, new_body); + //Automatically update content-lenght + return messageInfo.getMessage(isRequest, helpers); + + case URL: + if (!isRequest) { + throw new ParsingException("Encoding URL in response"); + } + String url_header = messageInfo.getUrlHeader(); + + pattern = Pattern.compile(param_name + "=[^& ]*((?=&)|(?= ))"); + matcher = pattern.matcher(url_header); + + messageInfo.setUrlHeader(matcher.replaceAll(param_name + "=" + new_value)); // problema + + return messageInfo.getMessage(isRequest, helpers); + } + return null; + } + + public static byte[] editMessageParam(IExtensionHelpers helpers, + String param_name, + DecodeOperation.DecodeOperationFrom decodeOperationFrom, + HTTPReqRes messageInfo, + boolean isRequest, + String new_value, + boolean isBodyRegex) throws ParsingException { + + HTTPReqRes.MessageSection ms = null; + + switch (decodeOperationFrom) { + case HEAD: + ms = HTTPReqRes.MessageSection.HEAD; + break; + case BODY: + ms = HTTPReqRes.MessageSection.BODY; + break; + case URL: + ms = HTTPReqRes.MessageSection.URL; + break; + case JWT_HEADER: + case JWT_PAYLOAD: + case JWT_SIGNATURE: + throw new ParsingException("invalid from section in decode operation should be a message section"); + } + + return editMessageParam( + helpers, + param_name, + ms, + messageInfo, + isRequest, + new_value, + isBodyRegex + ); + } + + /** + * Finds the parent div of an http element + * + * @param in the http element in xpath format + * @return the xpath of the parent div + * @throws ParsingException if no parent div present or input is malformed + */ + public static String findParentDiv(String in) throws ParsingException { + String[] split1 = in.split("="); + if (split1.length != 2) { + throw new ParsingException("invalid input \"" + in + "\" for finding parent div"); + } + String[] split = split1[1].split("/"); + if (split.length == 0) { + return in; + } + + int cut_indx = -1; + + //-2 because if the last element is a div i don't take it, otherwise is not a div, so i don't take it + for (int i = split.length - 2; i > 0; i--) { + if (split[i].contains("div")) { + cut_indx = i; + break; + } + } + if (cut_indx == -1) return in; + + String res = split1[0] + "="; + + for (int i = 1; i <= cut_indx; i++) { + if (i == cut_indx) { + // removes the index of the div element + res += "/" + split[i].replaceAll("\\[.*\\]", ""); + } else { + res += "/" + split[i]; + } + } + return res; + } + + /** + * Given a json string and a json path, edit the json. + * + * @param action the action to do, (edit, remove, add, or save) + * @param content the json content as string + * @param j_path the json path as string + * @param save_as the name of the variable if the action is save + * @return the edited json + * @throws PathNotFoundException if the path in the json is not found + */ + public static String editJson(EditOperation.Jwt_action action, + String content, + String j_path, + List vars, + String save_as, + String newValue) throws PathNotFoundException { + Object document = Configuration.defaultConfiguration().jsonProvider().parse(content); + JsonPath jsonPath = JsonPath.compile(j_path); + + switch (action) { + case REMOVE: + document = jsonPath.delete(document, Configuration.defaultConfiguration()); + break; + case EDIT: + case ADD: + document = jsonPath.set(document, newValue, Configuration.defaultConfiguration()); + //TODO: check if set also adds in case it is not found + break; + case SAVE: + Var v = new Var(); + v.name = save_as; + v.isMessage = false; + v.value = JsonPath.read(content, j_path); //TODO could rise errors + vars.add(v); + break; + } + return Configuration.defaultConfiguration().jsonProvider().toJson(document); //basically converts to string + } +} diff --git a/tool/src/main/java/burp/Track.java b/tool/src/main/java/migt/Track.java similarity index 87% rename from tool/src/main/java/burp/Track.java rename to tool/src/main/java/migt/Track.java index 084195d..2c8a9ce 100644 --- a/tool/src/main/java/burp/Track.java +++ b/tool/src/main/java/migt/Track.java @@ -1,4 +1,4 @@ -package burp; +package migt; import java.util.ArrayList; import java.util.List; @@ -53,7 +53,8 @@ public List getTrack() { /** * Get the index of the first User Action having the given marker. - * @param marker_name The name of the marker to search + * + * @param marker_name The name of the marker to search * @param start_from_last true if you want to start searching from the last element * @return the index */ @@ -65,7 +66,7 @@ public int indexOfStaFromMarker(String marker_name, boolean start_from_last) { Marker m = new Marker(marker_name); if (start_from_last) { - int indx = track.size() -1; + int indx = track.size() - 1; ListIterator listIterator = track.listIterator(track.size()); while (listIterator.hasPrevious()) { SessionTrackAction sta = listIterator.previous(); @@ -93,17 +94,18 @@ public int indexOfStaFromMarker(String marker_name, boolean start_from_last) { /** * Return the index of the first user action which has the markerFrom, and the index of the first user action having * the marker to (searched from the end of the track) - * @param markerFrom The left range marker to search for - * @param markerTo The right range marker to search for + * + * @param markerFrom The left range marker to search for + * @param markerTo The right range marker to search for * @param is_from_included true if the left range element is included - * @param is_to_included true if the right range element is included + * @param is_to_included true if the right range element is included * @return an array of int of length 2, containing the left index in position 0, and right index in position 1 * @throws ParsingException If the markers are not found */ public int[] getStasIndexFromRange(String markerFrom, - String markerTo, - boolean is_from_included, - boolean is_to_included) throws ParsingException { + String markerTo, + boolean is_from_included, + boolean is_to_included) throws ParsingException { int indx_from = is_from_included ? indexOfStaFromMarker(markerFrom, false) : indexOfStaFromMarker(markerFrom, false) + 1; @@ -122,10 +124,11 @@ public int[] getStasIndexFromRange(String markerFrom, /** * Given a range represented by two markers, return the User actions in that range from the track. - * @param markerFrom the left range marker to search for - * @param markerTo the right range marker to search for (starting from the end of the track backward) + * + * @param markerFrom the left range marker to search for + * @param markerTo the right range marker to search for (starting from the end of the track backward) * @param is_from_included true if the left range element should be included - * @param is_to_included true if the right range element should be included + * @param is_to_included true if the right range element should be included * @return a list of User Actions in that range * @throws ParsingException if markers are not found */ @@ -136,7 +139,7 @@ public List getStasFromMarkers(String markerFrom, int[] range = getStasIndexFromRange(markerFrom, markerTo, is_from_included, is_to_included); - int indx_from= range[0]; + int indx_from = range[0]; int indx_to = range[1]; List res = new ArrayList<>(); @@ -152,8 +155,9 @@ public List getStasFromMarkers(String markerFrom, /** * Mark a given user action with a marker + * * @param to_be_marked the User Action to be marked - * @param marker_name the name of the marker + * @param marker_name the name of the marker * @throws ParsingException If the User Action is not found in the track */ public void mark(SessionTrackAction to_be_marked, @@ -169,7 +173,8 @@ public void mark(SessionTrackAction to_be_marked, /** * Insert an User Action in a given position of the track - * @param at the marker used as a reference to insert the user action to + * + * @param at the marker used as a reference to insert the user action to * @param to_be_inserted the User Action to insert in string format * @throws ParsingException if marker non present in track or action malformed */ @@ -197,6 +202,7 @@ public void insert(Marker at, String to_be_inserted) throws ParsingException { /** * Removes all User Action in track having the specified marker + * * @param at the marker telling which actions to remove * @throws ParsingException if no actions are found with that marker. */ @@ -225,6 +231,7 @@ public boolean equals(Object o) { /** * Return the track in string format complaint with the language. + * * @return the track in string format */ @Override diff --git a/tool/src/main/java/burp/Var.java b/tool/src/main/java/migt/Var.java similarity index 69% rename from tool/src/main/java/burp/Var.java rename to tool/src/main/java/migt/Var.java index 86cdb17..0815b64 100644 --- a/tool/src/main/java/burp/Var.java +++ b/tool/src/main/java/migt/Var.java @@ -1,4 +1,6 @@ -package burp; +package migt; + +import burp.IHttpService; /** * The class storing the variables used in the test and sessions @@ -20,4 +22,10 @@ public Var() { this.value = ""; this.isMessage = false; } + + public Var(String name, String value, Boolean isMessage) { + this.name = name; + this.value = value; + this.isMessage = isMessage; + } } diff --git a/tool/src/main/java/burp/XML.java b/tool/src/main/java/migt/XML.java similarity index 98% rename from tool/src/main/java/burp/XML.java rename to tool/src/main/java/migt/XML.java index 1975451..53905cf 100644 --- a/tool/src/main/java/burp/XML.java +++ b/tool/src/main/java/migt/XML.java @@ -1,4 +1,4 @@ -package burp; +package migt; import org.w3c.dom.Document; import org.w3c.dom.Node; @@ -204,10 +204,10 @@ public static String removeTagAttribute(String input, String tag, String attribu /** * This function adds an attribute in a tag from an xml string * - * @param input the xml string - * @param tag the tag to add the attribute - * @param attribute the attribute name to be added - * @param value the attribute value to be added + * @param input the xml string + * @param tag the tag to add the attribute + * @param attribute the attribute name to be added + * @param value the attribute value to be added * @param occurrency tells the index of the matched occurency to do the thing on, set it to -1 to do for all occurencies * @return the edited xml string */ diff --git a/tool/src/test/java/Checks_Test.java b/tool/src/test/java/Checks_Test.java new file mode 100644 index 0000000..958604c --- /dev/null +++ b/tool/src/test/java/Checks_Test.java @@ -0,0 +1,337 @@ +import migt.*; +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class Checks_Test { + + public Check initCheck_json(String check_str) throws ParsingException { + String input = "{\n" + + " \"pageInfo\": {\n" + + " \"pageName\": \"abc\",\n" + + " \"pagePic\": \"http://example.com/content.jpg\",\n" + + " \"entry\": [123, \"abc\",\"cde\"],\n" + + " \"imaninteger\": 123,\n" + + " \"imafloat\": 123.321,\n" + + " },\n" + + " \"posts\": [\n" + + " {\n" + + " \"post_id\": \"123456789012_123456789012\",\n" + + " \"actor_id\": \"1234567890\",\n" + + " \"picOfPersonWhoPosted\": \"http://example.com/photo.jpg\",\n" + + " \"nameOfPersonWhoPosted\": \"Jane Doe\",\n" + + " \"message\": \"Sounds cool. Can't wait to see it!\",\n" + + " \"likesCount\": \"2\",\n" + + " \"comments\": [\"abc\",\"cde\"],\n" + + " \"timeOfPost\": \"1234567890\"\n" + + " }\n" + + " ]\n" + + "}"; + + Check c = new Check(new JSONObject(check_str)); + + DecodeOperation_API dopapi = new DecodeOperation_API(); + dopapi.type = DecodeOperation.DecodeOpType.JWT; + dopapi.jwt.header = input; + c.loader(dopapi); + + return c; + } + + @Test + @DisplayName("check") + void test_check() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.pageInfo.pageName\",\n" + + " \"is\": \"abc\"\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + assertTrue(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_json_is_not() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.pageInfo.pageName\",\n" + + " \"is\": \"notabc\"\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + assertFalse(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_json_contains() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.pageInfo.pageName\",\n" + + " \"contains\": \"abc\"\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + assertTrue(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_json_not_contains() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.pageInfo.pageName\",\n" + + " \"not contains\": \"abc\"\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + assertFalse(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_json_not_found() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.pageInfo.notexisting\",\n" + + " \"is\": \"abc\"\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + assertFalse(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_json_not_present() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.pageInfo.notexisting\",\n" + + " \"is present\": \"false\"\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + assertTrue(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_json_not_present_wrong() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.pageInfo.notexisting\",\n" + + " \"is present\": \"true\"\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + assertFalse(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_json_is_inside_list() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.posts[0].actor_id\",\n" + + " \"is\": \"1234567890\"\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + assertTrue(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_json_set_result() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.posts[0].actor_id\",\n" + + " \"is\": \"1234567890\"\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + + DecodeOperation dop = new DecodeOperation(); + dop.setResult(c); + + assertTrue(dop.getResult()); + } + + @Test + @DisplayName("check") + void test_check_use_variable() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"use variable\": true,\n" + + " \"check\": \"$.pageInfo.pageName\",\n" + + " \"is\": \"variablename\"\n" + + "}"; + + List vars = new ArrayList(); + vars.add(new Var("variablename", "abc", false)); + + Check c = initCheck_json(check_str); + c.execute(vars); + assertTrue(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_use_variable_wrong() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"use variable\": true,\n" + + " \"check\": \"$.pageInfo.pageName\",\n" + + " \"is\": \"variablename\"\n" + + "}"; + + List vars = new ArrayList(); + vars.add(new Var("variablename", "ac", false)); + + Check c = initCheck_json(check_str); + c.execute(vars); + assertFalse(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_json_string_contains_elem() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.pageInfo.entry\",\n" + + " \"contains\": [\"123\"]\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + assertTrue(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_json_string_not_contains_elem_wrong() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.pageInfo.entry\",\n" + + " \"not contains\": [\"123\"]\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + assertFalse(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_json_string_not_contains_elem() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.pageInfo.entry\",\n" + + " \"not contains\": [\"aaa\"]\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + assertTrue(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_json_array_is_subset_of_ok() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.pageInfo.entry\",\n" + + " \"is subset of\": [\"123\", \"abc\",\"cde\", \"altro\"]\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + assertTrue(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_json_array_is_subset_of_wrong() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.pageInfo.entry\",\n" + + " \"is subset of\": [\"123\", \"abc\",\"aaa\", \"altro\"]\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + assertFalse(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_json_matches_regex() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.pageInfo.pagePic\",\n" + + " \"matches regex\": \"example\"\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + assertTrue(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_json_matches_regex_wrong() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.pageInfo.pagePic\",\n" + + " \"matches regex\": \"exampsle\"\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + assertFalse(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_json_not_matches_regex() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.pageInfo.pagePic\",\n" + + " \"not matches regex\": \"exampsle\"\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + assertTrue(c.getResult()); + } + + @Test + @DisplayName("check") + void test_check_json_not_matches_regex_wrong() throws ParsingException { + String check_str = "{\n" + + " \"in\": \"header\",\n" + + " \"check\": \"$.pageInfo.pagePic\",\n" + + " \"not matches regex\": \"example\"\n" + + "}"; + + Check c = initCheck_json(check_str); + c.execute(new ArrayList()); + assertFalse(c.getResult()); + } +} diff --git a/tool/src/test/java/DecodeOperation_Test.java b/tool/src/test/java/DecodeOperation_Test.java new file mode 100644 index 0000000..a89f34b --- /dev/null +++ b/tool/src/test/java/DecodeOperation_Test.java @@ -0,0 +1,97 @@ +import migt.DecodeOperation; +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 DecodeOperation_Test { + String input = "{\n" + + " \"from\": \"url\",\n" + + " \"type\": \"jwt\",\n" + + " \"decode param\": \"asd\",\n" + + " \"decode operations\": [\n" + + " {\n" + + " \"from\": \"jwt header\",\n" + + " \"type\": \"jwt\",\n" + + " \"decode param\": \"$.something\"\n" + + " }\n" + + " ]\n" + + " }"; + + String input_w_checks = "{\n" + + " \"from\": \"body\",\n" + + " \"decode param\": \"(?<=authz_request_object=)[^$\\n& ]*\",\n" + + " \"type\": \"jwt\",\n" + + " \"checks\": [\n" + + " {\n" + + " \"in\": \"payload\",\n" + + " \"check\": \"$.scope\",\n" + + " \"is\": \"openid\"\n" + + " }\n" + + " ]\n" + + " }"; + + String input_w_edits = "{\n" + + " \"from\": \"body\",\n" + + " \"decode param\": \"(?<=authz_request_object=)[^$\\n& ]*\",\n" + + " \"type\": \"jwt\",\n" + + " \"edits\": [\n" + + " {\n" + + " \"jwt from\": \"payload\",\n" + + " \"jwt edit\": \"$.scope\",\n" + + " \"value\": \"qualcosaltro\"\n" + + " }\n" + + " ]\n" + + " }"; + + String input_w_edits_save = "{\n" + + " \"from\": \"body\",\n" + + " \"decode param\": \"(?<=authz_request_object=)[^$\\n& ]*\",\n" + + " \"type\": \"jwt\",\n" + + " \"edits\": [\n" + + " {\n" + + " \"jwt from\": \"payload\",\n" + + " \"jwt save\": \"$.scope\",\n" + + " \"as\": \"varname\"\n" + + " }\n" + + " ]\n" + + " }"; + + @Test + void test_parse() throws ParsingException { + DecodeOperation dop = new DecodeOperation(new JSONObject(input)); + + assertEquals(DecodeOperation.DecodeOperationFrom.URL, dop.from); + assertEquals("asd", dop.decode_target); + assertTrue(dop.decodeOperations.size() == 1); + + DecodeOperation child_dop = dop.decodeOperations.get(0); + assertEquals(DecodeOperation.DecodeOperationFrom.JWT_HEADER, child_dop.from); + assertEquals("$.something", child_dop.decode_target); + } + + @Test + void test_parse_w_checks() throws ParsingException { + DecodeOperation dop = new DecodeOperation(new JSONObject(input_w_checks)); + + assertEquals(DecodeOperation.DecodeOperationFrom.BODY, dop.from); + assertEquals(1, dop.checks.size()); + + } + + @Test + void test_parse_w_edits() throws ParsingException { + DecodeOperation dop = new DecodeOperation(new JSONObject(input_w_edits)); + + assertEquals(1, dop.editOperations.size()); + } + + @Test + void test_parse_w_edits_save() throws ParsingException { + DecodeOperation dop = new DecodeOperation(new JSONObject(input_w_edits_save)); + + assertEquals(1, dop.editOperations.size()); + } +} diff --git a/tool/src/test/java/GUI_Test.java b/tool/src/test/java/GUI_Test.java index 0faa7c9..51f3486 100644 --- a/tool/src/test/java/GUI_Test.java +++ b/tool/src/test/java/GUI_Test.java @@ -1,4 +1,6 @@ -import burp.*; +import migt.ParsingException; +import migt.Session; +import migt.Tools; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -6,17 +8,17 @@ import java.util.HashMap; import java.util.List; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; public class GUI_Test { @Test @DisplayName("ParsingRawSessionAction test") void test_batchPassivesFromSession() throws ParsingException { - List tests = new ArrayList<>(); + List tests = new ArrayList<>(); - for (int i=0; i< 8; i++) { - burp.Test t1 = new burp.Test(); + for (int i = 0; i < 8; i++) { + migt.Test t1 = new migt.Test(); t1.sessions.add(new Session()); tests.add(t1); } @@ -30,7 +32,7 @@ void test_batchPassivesFromSession() throws ParsingException { tests.get(6).sessions.get(0).name = "4"; tests.get(7).sessions.get(0).name = "1"; - HashMap> hm = Utils.batchPassivesFromSession(tests); + HashMap> hm = Tools.batchPassivesFromSession(tests); assertEquals(3, hm.get("1").size()); assertEquals(2, hm.get("2").size()); diff --git a/tool/src/test/java/HTTPReqRes_Test.java b/tool/src/test/java/HTTPReqRes_Test.java new file mode 100644 index 0000000..1dada6d --- /dev/null +++ b/tool/src/test/java/HTTPReqRes_Test.java @@ -0,0 +1,238 @@ +import migt.HTTPReqRes; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class HTTPReqRes_Test { + + public HTTPReqRes initMessage_ok() { + String raw = "POST /log?format=json&hasfast=true&authuser=0 HTTP/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")); + + int body_offset = raw.length(); + + raw += "bodycontent"; + + byte[] raw_b = raw.getBytes(StandardCharsets.UTF_8); + + HTTPReqRes message = new HTTPReqRes(raw_b, null); + + message.body_offset_req = body_offset; + 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() { + String raw = "POST /log?format=json&hasfast=true&authuser=0 HTTP/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 = List.of(raw.split("\r\n")); + + int body_offset = raw.length(); + + raw += "bodycontent"; + + byte[] raw_b = raw.getBytes(StandardCharsets.UTF_8); + + HTTPReqRes message = new HTTPReqRes(raw_b, null); + message.body_offset_req = body_offset; + message.setHeaders(true, headers); + + byte[] builded = message.build_message(message.isRequest); + String builded_str = new String(builded, StandardCharsets.UTF_8); + assertEquals(raw, builded_str); + assertArrayEquals(builded, raw_b); + } + + @Test + @DisplayName("") + public void test_build_no_body() { + String raw = "POST /log?format=json&hasfast=true&authuser=0 HTTP/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" + + "Sec-Ch-Ua: \"Chromium\";v=\"111\", \"Not(A:Brand\";v=\"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 = List.of(raw.split("\r\n")); + + int body_offset = raw.length(); + + byte[] raw_b = raw.getBytes(StandardCharsets.UTF_8); + + HTTPReqRes message = new HTTPReqRes(raw_b, null); + message.body_offset_req = body_offset; + message.setHeaders(true, headers); + + byte[] builded = message.build_message(message.isRequest); + String builded_str = new String(builded, StandardCharsets.UTF_8); + assertEquals(raw, builded_str); + assertArrayEquals(builded, raw_b); + } + + @Test + @DisplayName("") + public void test_build_no_body_with_content_len() { + String raw = "POST /log?format=json&hasfast=true&authuser=0 HTTP/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" + + "Sec-Ch-Ua: \"Chromium\";v=\"111\", \"Not(A:Brand\";v=\"8\"\r\n" + + "Content-Length: 11\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 = List.of(raw.split("\r\n")); + + int body_offset = raw.length(); + + byte[] raw_b = raw.getBytes(StandardCharsets.UTF_8); + + HTTPReqRes message = new HTTPReqRes(raw_b, null); + message.body_offset_req = body_offset; + message.setHeaders(true, headers); + + byte[] builded = message.build_message(message.isRequest); + String builded_str = new String(builded, StandardCharsets.UTF_8); + + raw = raw.replace("Content-Length: 11\r\n", ""); + raw_b = raw.getBytes(StandardCharsets.UTF_8); + + assertEquals(raw, builded_str); + assertArrayEquals(builded, raw_b); + } + + @Test + @DisplayName("") + 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); + } + + @Test + @DisplayName("") + public void test_getUrlParam() { + HTTPReqRes message = initMessage_ok(); + String value = message.getUrlParam("format"); + assertEquals("json", value); + value = message.getUrlParam("hasfast"); + assertEquals("true", value); + value = message.getUrlParam("authuser"); + assertEquals("0", value); + } + + @Test + @DisplayName("") + public void test_getHeadParam() { + HTTPReqRes message = initMessage_ok(); + String value = message.getHeadParam(true, "Origin"); + assertEquals("https://www.google.com", value); + } + + @Test + @DisplayName("") + public void test_editHeadParam() { + HTTPReqRes message = initMessage_ok(); + message.editHeadParam(true, "Origin", "www.another.com"); + String value = message.getHeadParam(true, "Origin"); + assertEquals("www.another.com", value); + } + + + @Test + @DisplayName("") + public void test_addHeadParameter() { + HTTPReqRes message = initMessage_ok(); + message.addHeadParameter(true, "Test", "valuetest"); + String value = message.getHeadParam(true, "Test"); + assertEquals("valuetest", value); + } + + + @Test + @DisplayName("") + public void test_removeHeadParameter() { + HTTPReqRes message = initMessage_ok(); + message.removeHeadParameter(true, "Origin"); + String value = message.getHeadParam(true, "Origin"); + assertEquals("", value); + } +} diff --git a/tool/src/test/java/JWT_Test.java b/tool/src/test/java/JWT_Test.java index a870e75..126c254 100644 --- a/tool/src/test/java/JWT_Test.java +++ b/tool/src/test/java/JWT_Test.java @@ -1,35 +1,147 @@ -import burp.JWT; -import burp.ParsingException; -import burp.Utils; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.impl.DefaultClaims; +import com.nimbusds.jose.*; +import com.nimbusds.jose.crypto.RSAEncrypter; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import migt.JWT; +import migt.ParsingException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import java.util.Date; + import static org.junit.jupiter.api.Assertions.*; public class JWT_Test { - burp.JWT j; - String raw_jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ"; + JWT j; + String raw_jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ"; - String raw_header = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"; + String raw_header = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9"; String raw_payload = "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0"; String raw_signature = "NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ"; - String header = "{\"alg\": \"HS256\",\"typ\": \"JWT\"\n}"; - String payload = ""; - String signature = ""; + String public_pem_ed = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAPq3ZL01cG1DHZ4iZLiRlRJIlupb5MGfHipSBq1hG2Jo=\n-----END PUBLIC KEY-----\n"; + String private_pem_ed = "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIFlqmiu8kHunEywNZhbZjdZcT1YGTUCoOlh9aHF+43UE\n-----END PRIVATE KEY-----\n"; + + String private_pem_rsa = "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIIJKQIBAAKCAgEAlD5LtoIK0+dFO2bEaGWRdK3yO4BXOty05yv61WTJO8l8gl1X\n" + + "LoQZS35bXYmsrh/4a58Wr+d2KrFx71ZzBrx0hJsJ/5+Ia+9q7zUCAmyuv+A73e/k\n" + + "kf/CIRSyg2tq++etsFoyUtx8AEw7IrGcLzhvy4R4h2vmgtqaln7Nw55FoLKJ8QiE\n" + + "Tq9UPz9KiV3OhA6ks07y6Brj63fCv6G9sX7uxDoflxVTiH7CaimlLv0h0hrA8s1O\n" + + "D3VfZjsKIGP+bTtsGzeBzGhxPCkg5DRGTRM/ST1OBMe3Swf9/kZ0ZsbcM2RliEVi\n" + + "OigVm4gYoVyIHbt+/Sig438qcrc581w1jdOzvmrPjXRlj60iKBjXnhPwT8UGZn4O\n" + + "WFu9NypVSfMjHgAIdEs+rOVp0YDxPhRU41DbRXci7TefZmCguaoir+7S7em5vPVO\n" + + "73fnYFrTEKTGdhlsYo43t5I9NkWlRZigg3UsdSREDZCdBlDcPx7UQDiBs9u3Uh4+\n" + + "2RT7R/Cdp4aPihq2lI4+iWVUk51xdWRJHeI4vbUYj3bwn4OrkXsb0PgzSz2Ss84f\n" + + "VIsFd6oLprxQn2OOj5Ra6P3ZpoosSvyD4J8zVZGUhi4sHzKDL3B7/wHTjXvHYbRW\n" + + "kWnvde7YV7aHMY+RlT+4LscDozIYRp5fPoh/DLNWfy8zTwGDWiiZGrB9RrcCAwEA\n" + + "AQKCAgA0R2PcETBQWpcHw84wIuGRDGcIpNIeaAdEHzZuWwS8mOnX76L3PI7PGNiP\n" + + "vCWxooSxL4GIt0/s7ncHuK0ICx3sReDYzSIHLn+/rCnxQPK/qAx00E0DT/beQ7ZQ\n" + + "smkgPSv7rVNh9W+lizyvl4NFA9opI6Z924eHTiCGQmG+Quq7KTuMTTyboylKxL88\n" + + "gmB6Ic/jjEwNnq4SNEHx4tBK8ECz4uuRFGxJDqrxVY5za8GpntW8yrpkqTfjjZ6c\n" + + "nab0TqhpUMHtnEeSt85prCW+uLLw2TXSabwyMbdZHO+f7zFozlcgH5fseoZkOzK0\n" + + "dTVrhtvZ26IhmI8XtZYyRKp+QdJ5IwF3XpXWt7zafUdYCu8Z45HZhEe+aTpNIDvv\n" + + "z2nkqiJ0zAMH188YqbgawCdWow6D3/vqoyr5IlZcj9qE2wKunT5eqr248rNQsLA5\n" + + "2nOwz4fPcN4RHyiBygD8G4867di7Q+qRBdMAfRx5DrG0ottL2oNrr5G9Wv94Dsu8\n" + + "+A0q8EpwAvwYsxCNVnrjd2ypfJhyS30Pktz/LWj/MWbee1BP83C3zveqwBQ+zxTd\n" + + "03surmBlNS7tZOZvThMbBlWIO66wQWCzkZwi6zbrJwHCf4B30jTepERsiLSv2jyI\n" + + "oHNGOa84i2IEdWtXGVVyAET9E+pzNVQjFHsembIS6H1W4GTjwQKCAQEAzkB4lLkx\n" + + "6gm5o5IhRC2ZAL9RaZGSrT9sXJThtq7qVqQiLItvNRa633eqfxzlkgBgmH2dGgc+\n" + + "OqZMOHE2u0PSWBUinkrzatFTyjn99H6jYtG70rxLMZcS9GD5padP2mFklrM9+WaN\n" + + "f7fuvZjsiIanVxhykyEqB7y43JSR5vUSab9tltbnG2+V2ZMmh+8GWEDwozhyOZW9\n" + + "cjhu+eI+7FqyU1JBydArO3Ds8ZpWrjfZvxmS7LzoJEkJ4hdfMmeAWFNv+tLhYGAT\n" + + "Et7d6atoxPL6bId+F+ncqgCF9SVmYpq9faoXsvOr7yxR3n6MLY5Fvz2fX0tvBeSv\n" + + "skn/a1mei1yqHQKCAQEAt//yZzEjCgi49QeT01CHPbkogbtkG+tVuB5V2zpLArj1\n" + + "AXXKSNZn96+UjgScEY/DTWm8ljjDP8UYZXmYgkBm+oEdYnyLa60u/ZSdP4E1ICJT\n" + + "32XHrbkt1Z9SdMDFwhitKuA9uyUIuLf2OfIvZQQDU6BPmr18OX82ODxZXqCMdGJH\n" + + "3rml16joa5u9KDPNLfei2NEEKc9L7szvtBwQYf2DKd4jgpZMOg8EFQBd3+szl7UD\n" + + "gTPRT7sZX035mNYfZkFTjj0z4vqEu1ARCkKz0fp05uEQnQLOTUGEgECDTh7jZnfn\n" + + "PDDio4/vW8qEPEfUcfpetbNr3i8hhAeA5xHyBij74wKCAQEAvPo1gY9uPJJMlaL+\n" + + "+AkPd6/UWHYZfsPt9aY0ab462MfqyAW6D1qUPszWW0GO1wehehceKwsX6YUVsWGK\n" + + "VGr//9Tds0vZXLYPn+si1TJzYcfp4FzGSNmzdFamZzG16NHz6GCzGCDu5WcSSIYl\n" + + "s7ItAZBU6pooeI5ikzlNteA2zs2nC948Qtcq5f/9/e70UUivM940Sq74tf8fL7Yt\n" + + "EULIwa9MuC0Ub5I4h+ZyJY7m5EH6bQ9pZFXHyHDBuN08q7FHmPo/pp5g25l4mvGD\n" + + "PXGkImzDDAYrOVjhZIywEwjVNp7yt/SsRKjHGqW4qsUBAwjjTd1ADJZMpX9HmIS0\n" + + "z9xHwQKCAQEAp3O0LHuIcupLQRvbSZXg7qhil+ZtjgcXZM+evTwI5fpjZyfGp5EQ\n" + + "31YYcUL6sfTO/dW7vk78Sj3aHQeTZv6reVEl5+qGi8D5oeetUA0LxynWgNnE5nI/\n" + + "p0kupniFwUXp2rpnE7j5ffpViJjCz0Desi2UJLRLqJwAQR+TCc485PJIjAcSSfk7\n" + + "RCtg84RpN2tF9eIK0u4IIdS6VYSw2Cz6QJEcagzUZIYj5eUGifEoa+ldvijlVZVl\n" + + "2tlAzPoZa1sKasmCPhBV2Y5dY6QeuHsiBrhPAUV7cM2ug3WyydbMhwWaGKo4qDgm\n" + + "0re0rpOEYRJFPUGDapoj+19EzYYEZ9zGlwKCAQBdOy3ctg82hG8y2GR2S8abgVI1\n" + + "b9S4s8FOYxxujzM6nkB+m8J1el4Sk8n4HuYDY9kivfI9/sbR7wUX9fV0QJHlr70t\n" + + "z8aTKNQVxzGzO+OLEGs66ieAI8uCOByCEbyqPUZZAg0YN7BWtfFsqdAwOdEdYCMO\n" + + "CR0axKnVhkIV7Oj9JQ1+mucB8gL5N8miSxc9lsWuOz2tGEM8rcTAoGSmvwMkgcfK\n" + + "P34FFHdGk4fZnRYA6pGfgVZD3ZyRYF9cWnNgw823JlsYhzGGX4pQbFFGZ4rGuQ9z\n" + + "ursu/oNFRzKSZrb1FHOHvv5DfkINWXPVL8EgQG2HN2AF+LSZllSrStef9Urw\n" + + "-----END RSA PRIVATE KEY-----"; + + String public_pem_rsa = "-----BEGIN PUBLIC KEY-----\n" + + "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlD5LtoIK0+dFO2bEaGWR\n" + + "dK3yO4BXOty05yv61WTJO8l8gl1XLoQZS35bXYmsrh/4a58Wr+d2KrFx71ZzBrx0\n" + + "hJsJ/5+Ia+9q7zUCAmyuv+A73e/kkf/CIRSyg2tq++etsFoyUtx8AEw7IrGcLzhv\n" + + "y4R4h2vmgtqaln7Nw55FoLKJ8QiETq9UPz9KiV3OhA6ks07y6Brj63fCv6G9sX7u\n" + + "xDoflxVTiH7CaimlLv0h0hrA8s1OD3VfZjsKIGP+bTtsGzeBzGhxPCkg5DRGTRM/\n" + + "ST1OBMe3Swf9/kZ0ZsbcM2RliEViOigVm4gYoVyIHbt+/Sig438qcrc581w1jdOz\n" + + "vmrPjXRlj60iKBjXnhPwT8UGZn4OWFu9NypVSfMjHgAIdEs+rOVp0YDxPhRU41Db\n" + + "RXci7TefZmCguaoir+7S7em5vPVO73fnYFrTEKTGdhlsYo43t5I9NkWlRZigg3Us\n" + + "dSREDZCdBlDcPx7UQDiBs9u3Uh4+2RT7R/Cdp4aPihq2lI4+iWVUk51xdWRJHeI4\n" + + "vbUYj3bwn4OrkXsb0PgzSz2Ss84fVIsFd6oLprxQn2OOj5Ra6P3ZpoosSvyD4J8z\n" + + "VZGUhi4sHzKDL3B7/wHTjXvHYbRWkWnvde7YV7aHMY+RlT+4LscDozIYRp5fPoh/\n" + + "DLNWfy8zTwGDWiiZGrB9RrcCAwEAAQ==\n" + + "-----END PUBLIC KEY-----"; + + String raw_jwe = "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.df14swiSheMDVazjpF60-Vi1Qk46HBJtVhlBOhnAeouJGu0Qf5q_-ENHNVC0ur7pza3n0o3dUodgx_vTNcJq5irnfOiBk4Vq0D9r8uh9eRQCgZVTCX8fNDhGl0gLlcvOIhL9JTn6bj4KGh4MrYsEHRfI7XD-PZyDIDMliZlwesrw3D4wQaHEs3QYAYn7VpKkOXZ0uYOPcDEZKJZgfQx1bSCjEYOxrBp0-9N7Vb4iwUaDzXDQtYbzGdAfvaU9LuEci0l8PkXDjk2z28xVOEXR1biWMhci3FZoqY47CaSjHEeJStAVOm5mEWy76ype4aBJ6e1f1JNr5xvOPdQMLeGzlg.QAK60NWaZVLHOWGd.MAPO8dBIwdnCxQLPeHvPBhMISbxHcLlmX6SsUyznnTA_rL9oaX-lbwUaMnJG8JEsOQhXEpRDL8R8wGqSf_euZUFPxjMVtuJXhlnYBulNju-Ce20nq72xPq5TsPsvUUjwms_Z9VRcJiyq9t88KU-SbWY4D7A8zlIj_QYu7UE7jwP-5rJ9ZJ_9BiLQ4nxoueQARTs8IbOy-W38YU8gCpXJqkIc9FIA5sEiQE2YTppisXrUajeffARk7-0wGQTxodWTOoMSEQBVnBtYr0AglnzYOl89PmDjNhf_5ViJJV_AxbKCrTXjd_akDVkhPDAad6qtJFGJVNNdXfnd2Q3UwKGp2vTp52xY6q66t3aELZKHIM861Qry0tQZaagBXJDg2JKZCOG6ylj0a3ZVtKyworBLdbj6tvrS6lJ1LqivaahCCyXAGoWq203VR55l9k7BPW325AUTto8eUQ2qOGSB3h-nQF9rnbKdIp5VQj4o0ub7QARuhoZ7cyfLPWEs013xQnuXwdLTFAN4dV42WxTo6ZWAMwFWRSRllRRhBHCtWd1XU-y0P6rZ1AvuJDGvX5YlSwcVnkmczueEcIxlucOmwRo9JkkZ31Lck6WKVnLx_yS-7zXKOcMNdPR5dEWZGENFLiRqns3q4654zygPt9N0WZbVcPZmyVqO0EYiVor_wfij9HUyZzjVb4YiYCNAKM_QJb1c0P6xYgiCqxSaWw-BUqYPrUZXEimxo9XWrIpWBBBNXtCMZqrTNKFXmOODQT-sDLVDj4C1X1bbtgr6D02G7gzvy2lpE3u4yB7PdL4peltuP0dvGqgg19oUw3JaJK-n-mzNXy5w_gwDr2YCcZc9lpvLqZX5QnIsk6_WIhgRBC5T5jurefnj_rvTQCEdCC2hDQxFhsXOlPMXbrYzQ7MpmxnN1JJFa5W3hzMhG8SXTNdlvI9IuqF1tDA_NY6AGUkzegmdiyARa-GXq3ZsWW9idfLYYWxc5yHfiDJzjqBBRlQZKjwNopfbOiFoNe_7KfB98Kt7cf2s41eM6LeSPtynyw.yN7-uTC9ihTAVuQLA5rJUg"; + + String jwe_private_key_pem = "-----BEGIN PRIVATE KEY-----\n" + + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCZ92bxCHSFfY1V\n" + + "f7RPsj6sdvUhFsgOBjPMSuFxmpIk9L5+ps7dKpuoVgID5rfT6GT3l/ze3RQ16orp\n" + + "Oy0+iwuVCTFJlFs/L3i2dmWfvVyyiwOWp+9/MMvIVNUT7wDKGJLC9onoqAhnKEqI\n" + + "ERv080cD5NUns7BoUNytuEgWK+J3fRCsHGfeu+Vdl3C0hIE0grOerpWTYAZQQf3H\n" + + "vX/Q7PeGbDCKS+/1eOSBqJ67YiqSn66Qs4g/A89GzMMKuGZ6Lh33x/1kGvT5P8fk\n" + + "FzbqTJWPwN+tu9npw06MFM70/rabztNg1I6uMK/s/H85NjO4DB6SzrVcQu2mhNRG\n" + + "AncNuYLNAgMBAAECggEAAhUlpQYM6fxWQOphUm8a3FSrJB68J2yXR9IfBYAdrf+c\n" + + "I2Y0LbdEk8nzT+Q+G4s1tvcEUN+wc5dubItvwk2/UZwDVgvAKQmGUxfe2LcM5Q5d\n" + + "RsQ1/QFswsd4QEkpT9KBreGqmxya7zhHdaB8WJ5kE1DuVgBB7mbuJw3cAiA4s5n/\n" + + "QxyY3w0Lh89CTJx8IqlNihbjzY/OBanEK8IJe03twdGuGp4fiBnkPMvIzcBhdeXP\n" + + "+8a4NR//KaNvLGC+FcONLEtR0T2/bkjvUrMyBY2gngxYgSCldVXguuU5jGhezkmu\n" + + "+7gP0ipSwyRdR8G36twrpCv5fGruRoGdKqCr+viecQKBgQDINf+NGQpeoFf82UjU\n" + + "b27xz/4YP1td1iIyfNtTdBodC7h6o7Ce/+QNt2b0rVltXtXQ8Ci3uorBih9bMirX\n" + + "F6MBOj4cgdhKySDMaaex5zFr4LOYCJ2nK18sa7QobvkfN3htUcimD3/B6yiEHbpI\n" + + "9Tf71Ix0HOYKL1k7/JsM4TaYHQKBgQDE3o/LmeUM95PJCFAWbB+ZKM3A3x8klZ6m\n" + + "mntSzAFB8yLjXtN0Y34E6+tyXJvW20AE4EWdvjduC/JaxF51sBulnaZbZwDU4Q2G\n" + + "lJkXx7l/iHUaHedxQDvGzhtiiStd7kzh5XLckC8dvsR306z/035fic35VQhTAANv\n" + + "1vzFGx92cQKBgGgzWm7YMoJvV3v8pqAR4x8tjmSWTPo4oZG/U/NKQPEPEZOasCkA\n" + + "q3PMGWSM+DcpHYViCP8esmrqdUlkgdFytt7DrmHt3mGF7nEVKDc6SYmI6E/fZBYG\n" + + "R8F5yMkmgLgTibTz1MdA19BYkLy6MCMapWmHBRbFl6CDZiEHZrc8W8qtAoGBAMQi\n" + + "7GYvO8lQe3dFBe1g6ZZA1cS7Rp6/ReG8dPNHdlVLM84NMmR5nxquJNO6OjS0GTMC\n" + + "cbk3wqer1Vfi3i0oOFMnHo9frq9oTH5xW5kajc/mlqxfcK8fDK8DtrrT6FXbzdMd\n" + + "MvNV3usmnTy4slnqTrRGaeRneDShBcuOCCUj4ZOxAoGBAMGlZqGPjaMZKA4Ub/1y\n" + + "T9wwy40H2DjBfkvOd0+GGYNZkpPlMf6+OR4eaXIhR94g/jDB5rNWMOi3G54J/qsa\n" + + "4iqeWVRpP8kmb3NBJ/Wu0n6JaE2oMOygaMQdpSggPDU6kh9o2Q6Xm8Kc4XP4vR9f\n" + + "cQuZkb1AlVAWiCUHSL8mpDFC\n" + + "-----END PRIVATE KEY-----"; + + String jwe_public_key_pem = "-----BEGIN PUBLIC KEY-----\n" + + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmfdm8Qh0hX2NVX+0T7I+\n" + + "rHb1IRbIDgYzzErhcZqSJPS+fqbO3SqbqFYCA+a30+hk95f83t0UNeqK6TstPosL\n" + + "lQkxSZRbPy94tnZln71csosDlqfvfzDLyFTVE+8AyhiSwvaJ6KgIZyhKiBEb9PNH\n" + + "A+TVJ7OwaFDcrbhIFivid30QrBxn3rvlXZdwtISBNIKznq6Vk2AGUEH9x71/0Oz3\n" + + "hmwwikvv9Xjkgaieu2Iqkp+ukLOIPwPPRszDCrhmei4d98f9ZBr0+T/H5Bc26kyV\n" + + "j8DfrbvZ6cNOjBTO9P62m87TYNSOrjCv7Px/OTYzuAweks61XELtpoTURgJ3DbmC\n" + + "zQIDAQAB\n" + + "-----END PUBLIC KEY-----"; @BeforeEach void setUp() { - j = new burp.JWT(); + j = new JWT(); } @Test @DisplayName("Testing default values") - void testDefaultValues(){ - burp.JWT j = new JWT(); + void testDefaultValues() { + JWT j = new JWT(); assertEquals("", j.raw); assertEquals("", j.signature); assertEquals("", j.header); @@ -38,13 +150,13 @@ void testDefaultValues(){ @Test @DisplayName("Testing jwt decode and encode") - void testJWTParse() { - burp.JWT j = new burp.JWT(); + void testJWTParse_build() { + JWT j = new JWT(); boolean errors = false; - try{ - j.parseJWT(raw_jwt); + try { + j.parse(raw_jwt); - String out = j.buildJWT(); + String out = j.build(); assertEquals(raw_jwt, out); String[] splitted = out.split("\\."); @@ -64,24 +176,29 @@ void testJWTParse() { } @Test - @DisplayName("Testing jwt remove claim") - void testJWTRemoveClaim(){ + @DisplayName("Testing jwt signing and verify") + void testJWTSign_and_verify() { + JWT j = new JWT(); boolean errors = false; - try{ - burp.JWT j = new burp.JWT(); - j.parseJWT(raw_jwt); - j.removeClaim(Utils.Jwt_section.HEADER, "typ"); - String out = j.buildJWT(); - assertEquals("eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ", out); - - j = new burp.JWT(); - j.parseJWT(raw_jwt); - j.removeClaim(Utils.Jwt_section.PAYLOAD, "name"); - out = j.buildJWT(); - assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ", out); + try { + j.parse(raw_jwt); + j.sign = true; + j.private_key_pem = private_pem_rsa; + String out = j.build(); + assertNotEquals(raw_jwt, out); String[] splitted = out.split("\\."); + assertEquals(3, splitted.length); + + assertEquals(raw_header, splitted[0]); + assertEquals(raw_payload, splitted[1]); + assertNotEquals(raw_signature, splitted[2]); + + JWT j2 = new JWT(); + j2.parse(out); + j2.public_key_pem = public_pem_rsa; + assertTrue(j2.check_sig()); } catch (ParsingException e) { errors = true; } @@ -89,39 +206,14 @@ void testJWTRemoveClaim(){ } @Test - @DisplayName("Testing jwt edit claim") - void testJWTEditClaim(){ + @DisplayName("Testing jwt decode and encode") + void test_jwt_wrong_signature() { + JWT j = new JWT(); boolean errors = false; - try{ - burp.JWT j = new burp.JWT(); - j.parseJWT(raw_jwt); - j.editAddClaim(Utils.Jwt_section.HEADER, "typ", "asdasd"); - String out = j.buildJWT(); - assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCI6ImFzZGFzZCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ", out); - - j = new burp.JWT(); - j.parseJWT(raw_jwt); - j.editAddClaim(Utils.Jwt_section.PAYLOAD, "name", "peppe"); - out = j.buildJWT(); - assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6InBlcHBlIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ", out); - - j = new burp.JWT(); - j.parseJWT(raw_jwt); - j.editAddClaim(Utils.Jwt_section.SIGNATURE, "", "peppe"); - out = j.buildJWT(); - assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.peppe", out); - - j = new burp.JWT(); - j.parseJWT(raw_jwt); - j.editAddClaim(Utils.Jwt_section.HEADER, "prova", "provona"); - out = j.buildJWT(); - assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInByb3ZhIjoicHJvdm9uYSJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ", out); - - j = new burp.JWT(); - j.parseJWT(raw_jwt); - j.editAddClaim(Utils.Jwt_section.PAYLOAD, "prova", "provona"); - out = j.buildJWT(); - assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwicHJvdmEiOiJwcm92b25hIn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ", out); + try { + j.parse(raw_jwt); + j.public_key_pem = public_pem_rsa; + assertFalse(j.check_sig()); } catch (ParsingException e) { errors = true; } @@ -129,37 +221,193 @@ void testJWTEditClaim(){ } @Test - @DisplayName("Claims edit") - void test_claimEdit(){ - String in = "eyJhbGciOiJSUzI1NiIsImtpZCI6IllodUlKVTZvMTVFVUN5cUEwTEhFcUpkLXhWUEpnb3lXNXdaMW80cGFkV3MifQ.eyJzY29wZSI6Im9wZW5pZCIsInJlZGlyZWN0X3VyaSI6Imh0dHA6Ly9yZWx5aW5nLXBhcnR5Lm9yZzo4MDAxL29pZGMvcnAvY2FsbGJhY2siLCJyZXNwb25zZV90eXBlIjoiY29kZSIsIm5vbmNlIjoidUNhQkJ6RDNPa3VPbEVVenZUSGJOcWFoOHVZdTRVa3UiLCJzdGF0ZSI6IjZFY3JwdzlYNThZaFVXMVlYSHF4bEVEVUhvbXczNUlxIiwiY2xpZW50X2lkIjoiaHR0cDovL3JlbHlpbmctcGFydHkub3JnOjgwMDEvIiwiZW5kcG9pbnQiOiJodHRwOi8vY2llLXByb3ZpZGVyLm9yZzo4MDAyL29pZGMvb3AvYXV0aG9yaXphdGlvbiIsImFjcl92YWx1ZXMiOiJodHRwczovL3d3dy5zcGlkLmdvdi5pdC9TcGlkTDIiLCJpYXQiOjE2NTM5ODM4NTksImF1ZCI6WyJodHRwOi8vY2llLXByb3ZpZGVyLm9yZzo4MDAyL29pZGMvb3AvIiwiaHR0cDovL2NpZS1wcm92aWRlci5vcmc6ODAwMi9vaWRjL29wL2F1dGhvcml6YXRpb24iXSwiY2xhaW1zIjp7ImlkX3Rva2VuIjp7ImZhbWlseV9uYW1lIjp7ImVzc2VudGlhbCI6dHJ1ZX0sImVtYWlsIjp7ImVzc2VudGlhbCI6dHJ1ZX19LCJ1c2VyaW5mbyI6eyJnaXZlbl9uYW1lIjpudWxsLCJmYW1pbHlfbmFtZSI6bnVsbCwiZW1haWwiOm51bGwsImZpc2NhbF9udW1iZXIiOm51bGx9fSwicHJvbXB0IjoiY29uc2VudCBsb2dpbiIsImNvZGVfY2hhbGxlbmdlIjoiU2hOX0t0U3ZhMEtwS1pZUFZ2MEhVd0lFM1lHclhZeHBuVS1Vb1BGTEluZyIsImNvZGVfY2hhbGxlbmdlX21ldGhvZCI6IlMyNTYiLCJpc3MiOiJodHRwOi8vcmVseWluZy1wYXJ0eS5vcmc6ODAwMS8iLCJzdWIiOiJodHRwOi8vcmVseWluZy1wYXJ0eS5vcmc6ODAwMS8ifQ.mETftfWL9MYrf3BVnahWOilFYItkBSaTw3nhKu0UzfiAI5lFy1orNGatNIR-Dg4hgsFCXgaY9rJSi2TVRSqIsHAJPe0HC5sKfXJ-mka0_w4koGDjbmYRZVN3yI05QWsLpENlsuCk2JEgZfz5BvAuX_MgxytIQHhUgy7DsdoJW-6Bk2DPDUiG_bDrBBjdFYgVocaQrxW49NmVIwtVz3dbhdslGA6g0uX7Dp9lQ9HqyWr1YnHtxUdyfuM2wdwPf11fhZNI8Nu_tpgVUxUMQgyEFA1nAscos2FuvLhpNovuciyh0BAlrYTpbXpZ-hjBv5rbfIrv5wytRNhlK2VxP7DA2g"; + @DisplayName("Testing jwt decode and encode") + void test_decrypt_encrypt_jwe() { + JWT j = new JWT(); boolean errors = false; try { - j = new burp.JWT(); - j.parseJWT(in); - - boolean a = j.jwt.getBody().containsKey("family_name"); - String s = (String) j.jwt.getBody().get("family_name"); - if (!a) { - Object c = j.jwt.getBody().get("claims"); - } - } catch (ParsingException e){ + j.decrypt = true; + j.private_key_pem_enc = jwe_private_key_pem; + j.public_key_pem_enc = jwe_public_key_pem; + j.parse(raw_jwe); + + String parsed_head = j.header; + String parsed_payload = j.payload; + String parsed_signature = j.signature; + + String out = j.build(); + + JWT j2 = new JWT(); + j2.decrypt = true; + j2.private_key_pem_enc = jwe_private_key_pem; + j2.public_key_pem_enc = jwe_public_key_pem; + j2.parse(out); + + assertEquals(parsed_head, j2.header); + assertEquals(parsed_payload, j2.payload); + assertEquals(parsed_signature, j2.signature); + } catch (ParsingException e) { errors = true; } assertFalse(errors); } @Test - @DisplayName("Decode raw jwt") - void test_decodeRawJwt() { - String in = "eyJhbGciOiJSUzI1NiIsImtpZCI6IllodUlKVTZvMTVFVUN5cUEwTEhFcUpkLXhWUEpnb3lXNXdaMW80cGFkV3MifQ.eyJzY29wZSI6Im9wZW5pZCIsInJlZGlyZWN0X3VyaSI6Imh0dHA6Ly9yZWx5aW5nLXBhcnR5Lm9yZzo4MDAxL29pZGMvcnAvY2FsbGJhY2siLCJyZXNwb25zZV90eXBlIjoiY29kZSIsIm5vbmNlIjoiOUZKcWczZDBBS0FYTWpEcDRVRnpkbGJUdG5kazgxanUiLCJzdGF0ZSI6ImhURHVRS0t1YUY4dnVxRk1XSVN4NWlxaTBlOXlmRGJiIiwiY2xpZW50X2lkIjoiaHR0cDovL3JlbHlpbmctcGFydHkub3JnOjgwMDEvIiwiZW5kcG9pbnQiOiJodHRwOi8vY2llLXByb3ZpZGVyLm9yZzo4MDAyL29pZGMvb3AvYXV0aG9yaXphdGlvbiIsImFjcl92YWx1ZXMiOiJodHRwczovL3d3dy5zcGlkLmdvdi5pdC9TcGlkTDIiLCJpYXQiOjE2NTY0MDMxNzEsImF1ZCI6WyJodHRwOi8vY2llLXByb3ZpZGVyLm9yZzo4MDAyL29pZGMvb3AvIiwiaHR0cDovL2NpZS1wcm92aWRlci5vcmc6ODAwMi9vaWRjL29wL2F1dGhvcml6YXRpb24iXSwiY2xhaW1zIjp7ImlkX3Rva2VuIjp7ImZhbWlseV9uYW1lIjp7ImVzc2VudGlhbCI6dHJ1ZX0sImVtYWlsIjp7ImVzc2VudGlhbCI6dHJ1ZX19LCJ1c2VyaW5mbyI6eyJnaXZlbl9uYW1lIjpudWxsLCJmYW1pbHlfbmFtZSI6bnVsbCwiZW1haWwiOm51bGwsImZpc2NhbF9udW1iZXIiOm51bGx9fSwicHJvbXB0IjoiY29uc2VudCBsb2dpbiIsImNvZGVfY2hhbGxlbmdlIjoiLXJQSkJfNDFPaUVzUmtXSTNQeDJmNkdaVjdpdWNOQkVReTZXVzRaenVTOCIsImNvZGVfY2hhbGxlbmdlX21ldGhvZCI6IlMyNTYiLCJpc3MiOiJodHRwOi8vcmVseWluZy1wYXJ0eS5vcmc6ODAwMS8ifQ.hZQdNZoZeLNJrIezuXQIV0C5a9ZOubiYTOUYdtmbsR4F_NFZFKDZccbjYk-ntYa2O7_DgcwQ083kAv5dutwU6nhiHBh3K__W4zct2yxcsLspE2pvBbmMjvq7IqmEYgIR2NEBwtCz9RrV6srnjzygm3XHb7kpfu-Z2eVPzxRTqi1C5l-ZX-xPDr2YFFdpHVB17G3lXTEj_Mm6zr6uNeJkS8Ytscq6SXyni3OTj_bRLTLONjoypLRO-qw8z2d8lY7bYgx9mZCAuUtgS75yRlrHuGu4zsE3Bg3UigfnCO_Pqouq-HZOGEZ_7_Hra0S5V8BPek_fRhRH6K534rFWlApRMQ"; - - String out = JWT.decode_raw_jwt(in); - - assertEquals("{" + - "\"alg\":\"RS256\"," + - "\"kid\":\"YhuIJU6o15EUCyqA0LHEqJd-xVPJgoyW5wZ1o4padWs\"" + - "}.{\"scope\":\"openid\",\"redirect_uri\":\"http://relying-party.org:8001/oidc/rp/callback\",\"response_type\":\"code\",\"nonce\":\"9FJqg3d0AKAXMjDp4UFzdlbTtndk81ju\",\"state\":\"hTDuQKKuaF8vuqFMWISx5iqi0e9yfDbb\",\"client_id\":\"http://relying-party.org:8001/\",\"endpoint\":\"http://cie-provider.org:8002/oidc/op/authorization\",\"acr_values\":\"https://www.spid.gov.it/SpidL2\",\"iat\":1656403171,\"aud\":[\"http://cie-provider.org:8002/oidc/op/\",\"http://cie-provider.org:8002/oidc/op/authorization\"],\"claims\":{\"id_token\":{\"family_name\":{\"essential\":true},\"email\":{\"essential\":true}},\"userinfo\":{\"given_name\":null,\"family_name\":null,\"email\":null,\"fiscal_number\":null}},\"prompt\":\"consent login\",\"code_challenge\":\"-rPJB_41OiEsRkWI3Px2f6GZV7iucNBEQy6WW4ZzuS8\",\"code_challenge_method\":\"S256\",\"iss\":\"http://relying-party.org:8001/\"}" + - ".hZQdNZoZeLNJrIezuXQIV0C5a9ZOubiYTOUYdtmbsR4F_NFZFKDZccbjYk-ntYa2O7_DgcwQ083kAv5dutwU6nhiHBh3K__W4zct2yxcsLspE2pvBbmMjvq7IqmEYgIR2NEBwtCz9RrV6srnjzygm3XHb7kpfu-Z2eVPzxRTqi1C5l-ZX-xPDr2YFFdpHVB17G3lXTEj_Mm6zr6uNeJkS8Ytscq6SXyni3OTj_bRLTLONjoypLRO-qw8z2d8lY7bYgx9mZCAuUtgS75yRlrHuGu4zsE3Bg3UigfnCO_Pqouq-HZOGEZ_7_Hra0S5V8BPek_fRhRH6K534rFWlApRMQ", - out); + void test_check_key() throws JOSEException { + + JWK senderJWK = JWK.parseFromPEMEncodedObjects(private_pem_rsa); + JWK recipientPublicJWK = JWK.parseFromPEMEncodedObjects(jwe_public_key_pem); + + // Create JWT + SignedJWT signedJWT = new SignedJWT( + new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(senderJWK.getKeyID()).build(), + new JWTClaimsSet.Builder() + .subject("alice") + .issueTime(new Date()) + .issuer("https://c2id.com") + .build()); + + // Sign the JWT + signedJWT.sign(new RSASSASigner(senderJWK.toRSAKey())); + + // Create JWE object with signed JWT as payload + JWEObject jweObject = new JWEObject( + new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM) + .contentType("JWT") // required to indicate nested JWT + .build(), + new Payload(signedJWT)); + + // Encrypt with the recipient's public key + jweObject.encrypt(new RSAEncrypter(recipientPublicJWK.toRSAKey())); + + // Serialise to JWE compact form + String jweString = jweObject.serialize(); + System.out.println(jweString); } + + /** + @Test void test_complete() throws NoSuchAlgorithmException, ParsingException { + String public_pem = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAPq3ZL01cG1DHZ4iZLiRlRJIlupb5MGfHipSBq1hG2Jo=\n-----END PUBLIC KEY-----\n"; + String private_pem = "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIFlqmiu8kHunEywNZhbZjdZcT1YGTUCoOlh9aHF+43UE\n-----END PRIVATE KEY-----\n"; + + //KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + //keyGen.initialize(2048); + //KeyPair pair = keyGen.generateKeyPair(); + //PrivateKey sk = pair.getPrivate(); + //PublicKey pk = pair.getPublic(); + + String jws = Jwts.builder() + .setSubject("Bob") + .signWith(sk) + .compact(); + + assertFalse(jws.equals("")); + + JWT j = new JWT(); + j.check_sig = true; + j.public_key = "pk_string"; + j.parse(raw_jwt); + } + + + @Test + @DisplayName("Testing jwt remove claim") + void testJWTRemoveClaim() { + boolean errors = false; + try { + JWT j = new JWT(); + j.parse(raw_jwt); + j.removeClaim(Utils.Jwt_section.HEADER, "typ"); + String out = j.build(); + assertEquals("eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ", out); + + j = new JWT(); + j.parse(raw_jwt); + j.removeClaim(Utils.Jwt_section.PAYLOAD, "name"); + out = j.build(); + assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ", out); + + String[] splitted = out.split("\\."); + + } catch (ParsingException e) { + errors = true; + } + assertFalse(errors); + } + + @Test + @DisplayName("Testing jwt edit claim") + void testJWTEditClaim() { + boolean errors = false; + try { + JWT j = new JWT(); + j.parse(raw_jwt); + j.editAddClaim(Utils.Jwt_section.HEADER, "typ", "asdasd"); + String out = j.build(); + assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCI6ImFzZGFzZCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ", out); + + j = new JWT(); + j.parse(raw_jwt); + j.editAddClaim(Utils.Jwt_section.PAYLOAD, "name", "peppe"); + out = j.build(); + assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6InBlcHBlIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ", out); + + j = new JWT(); + j.parse(raw_jwt); + j.editAddClaim(Utils.Jwt_section.SIGNATURE, "", "peppe"); + out = j.build(); + assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.peppe", out); + + j = new JWT(); + j.parse(raw_jwt); + j.editAddClaim(Utils.Jwt_section.HEADER, "prova", "provona"); + out = j.build(); + assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInByb3ZhIjoicHJvdm9uYSJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ", out); + + j = new JWT(); + j.parse(raw_jwt); + j.editAddClaim(Utils.Jwt_section.PAYLOAD, "prova", "provona"); + out = j.build(); + assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwicHJvdmEiOiJwcm92b25hIn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ", out); + } catch (ParsingException e) { + errors = true; + } + assertFalse(errors); + } + + @Test + @DisplayName("Claims edit") + void test_claimEdit() { + String in = "eyJhbGciOiJSUzI1NiIsImtpZCI6IllodUlKVTZvMTVFVUN5cUEwTEhFcUpkLXhWUEpnb3lXNXdaMW80cGFkV3MifQ.eyJzY29wZSI6Im9wZW5pZCIsInJlZGlyZWN0X3VyaSI6Imh0dHA6Ly9yZWx5aW5nLXBhcnR5Lm9yZzo4MDAxL29pZGMvcnAvY2FsbGJhY2siLCJyZXNwb25zZV90eXBlIjoiY29kZSIsIm5vbmNlIjoidUNhQkJ6RDNPa3VPbEVVenZUSGJOcWFoOHVZdTRVa3UiLCJzdGF0ZSI6IjZFY3JwdzlYNThZaFVXMVlYSHF4bEVEVUhvbXczNUlxIiwiY2xpZW50X2lkIjoiaHR0cDovL3JlbHlpbmctcGFydHkub3JnOjgwMDEvIiwiZW5kcG9pbnQiOiJodHRwOi8vY2llLXByb3ZpZGVyLm9yZzo4MDAyL29pZGMvb3AvYXV0aG9yaXphdGlvbiIsImFjcl92YWx1ZXMiOiJodHRwczovL3d3dy5zcGlkLmdvdi5pdC9TcGlkTDIiLCJpYXQiOjE2NTM5ODM4NTksImF1ZCI6WyJodHRwOi8vY2llLXByb3ZpZGVyLm9yZzo4MDAyL29pZGMvb3AvIiwiaHR0cDovL2NpZS1wcm92aWRlci5vcmc6ODAwMi9vaWRjL29wL2F1dGhvcml6YXRpb24iXSwiY2xhaW1zIjp7ImlkX3Rva2VuIjp7ImZhbWlseV9uYW1lIjp7ImVzc2VudGlhbCI6dHJ1ZX0sImVtYWlsIjp7ImVzc2VudGlhbCI6dHJ1ZX19LCJ1c2VyaW5mbyI6eyJnaXZlbl9uYW1lIjpudWxsLCJmYW1pbHlfbmFtZSI6bnVsbCwiZW1haWwiOm51bGwsImZpc2NhbF9udW1iZXIiOm51bGx9fSwicHJvbXB0IjoiY29uc2VudCBsb2dpbiIsImNvZGVfY2hhbGxlbmdlIjoiU2hOX0t0U3ZhMEtwS1pZUFZ2MEhVd0lFM1lHclhZeHBuVS1Vb1BGTEluZyIsImNvZGVfY2hhbGxlbmdlX21ldGhvZCI6IlMyNTYiLCJpc3MiOiJodHRwOi8vcmVseWluZy1wYXJ0eS5vcmc6ODAwMS8iLCJzdWIiOiJodHRwOi8vcmVseWluZy1wYXJ0eS5vcmc6ODAwMS8ifQ.mETftfWL9MYrf3BVnahWOilFYItkBSaTw3nhKu0UzfiAI5lFy1orNGatNIR-Dg4hgsFCXgaY9rJSi2TVRSqIsHAJPe0HC5sKfXJ-mka0_w4koGDjbmYRZVN3yI05QWsLpENlsuCk2JEgZfz5BvAuX_MgxytIQHhUgy7DsdoJW-6Bk2DPDUiG_bDrBBjdFYgVocaQrxW49NmVIwtVz3dbhdslGA6g0uX7Dp9lQ9HqyWr1YnHtxUdyfuM2wdwPf11fhZNI8Nu_tpgVUxUMQgyEFA1nAscos2FuvLhpNovuciyh0BAlrYTpbXpZ-hjBv5rbfIrv5wytRNhlK2VxP7DA2g"; + boolean errors = false; + try { + j = new JWT(); + j.parse(in); + + boolean a = j.jwt.getBody().containsKey("family_name"); + String s = (String) j.jwt.getBody().get("family_name"); + if (!a) { + Object c = j.jwt.getBody().get("claims"); + } + } catch (ParsingException e) { + errors = true; + } + assertFalse(errors); + } + + @Test + @DisplayName("Decode raw jwt") + void test_decodeRawJwt() { + String in = "eyJhbGciOiJSUzI1NiIsImtpZCI6IllodUlKVTZvMTVFVUN5cUEwTEhFcUpkLXhWUEpnb3lXNXdaMW80cGFkV3MifQ.eyJzY29wZSI6Im9wZW5pZCIsInJlZGlyZWN0X3VyaSI6Imh0dHA6Ly9yZWx5aW5nLXBhcnR5Lm9yZzo4MDAxL29pZGMvcnAvY2FsbGJhY2siLCJyZXNwb25zZV90eXBlIjoiY29kZSIsIm5vbmNlIjoiOUZKcWczZDBBS0FYTWpEcDRVRnpkbGJUdG5kazgxanUiLCJzdGF0ZSI6ImhURHVRS0t1YUY4dnVxRk1XSVN4NWlxaTBlOXlmRGJiIiwiY2xpZW50X2lkIjoiaHR0cDovL3JlbHlpbmctcGFydHkub3JnOjgwMDEvIiwiZW5kcG9pbnQiOiJodHRwOi8vY2llLXByb3ZpZGVyLm9yZzo4MDAyL29pZGMvb3AvYXV0aG9yaXphdGlvbiIsImFjcl92YWx1ZXMiOiJodHRwczovL3d3dy5zcGlkLmdvdi5pdC9TcGlkTDIiLCJpYXQiOjE2NTY0MDMxNzEsImF1ZCI6WyJodHRwOi8vY2llLXByb3ZpZGVyLm9yZzo4MDAyL29pZGMvb3AvIiwiaHR0cDovL2NpZS1wcm92aWRlci5vcmc6ODAwMi9vaWRjL29wL2F1dGhvcml6YXRpb24iXSwiY2xhaW1zIjp7ImlkX3Rva2VuIjp7ImZhbWlseV9uYW1lIjp7ImVzc2VudGlhbCI6dHJ1ZX0sImVtYWlsIjp7ImVzc2VudGlhbCI6dHJ1ZX19LCJ1c2VyaW5mbyI6eyJnaXZlbl9uYW1lIjpudWxsLCJmYW1pbHlfbmFtZSI6bnVsbCwiZW1haWwiOm51bGwsImZpc2NhbF9udW1iZXIiOm51bGx9fSwicHJvbXB0IjoiY29uc2VudCBsb2dpbiIsImNvZGVfY2hhbGxlbmdlIjoiLXJQSkJfNDFPaUVzUmtXSTNQeDJmNkdaVjdpdWNOQkVReTZXVzRaenVTOCIsImNvZGVfY2hhbGxlbmdlX21ldGhvZCI6IlMyNTYiLCJpc3MiOiJodHRwOi8vcmVseWluZy1wYXJ0eS5vcmc6ODAwMS8ifQ.hZQdNZoZeLNJrIezuXQIV0C5a9ZOubiYTOUYdtmbsR4F_NFZFKDZccbjYk-ntYa2O7_DgcwQ083kAv5dutwU6nhiHBh3K__W4zct2yxcsLspE2pvBbmMjvq7IqmEYgIR2NEBwtCz9RrV6srnjzygm3XHb7kpfu-Z2eVPzxRTqi1C5l-ZX-xPDr2YFFdpHVB17G3lXTEj_Mm6zr6uNeJkS8Ytscq6SXyni3OTj_bRLTLONjoypLRO-qw8z2d8lY7bYgx9mZCAuUtgS75yRlrHuGu4zsE3Bg3UigfnCO_Pqouq-HZOGEZ_7_Hra0S5V8BPek_fRhRH6K534rFWlApRMQ"; + + String out = JWT.decode_raw_jwt(in); + + assertEquals("{" + + "\"alg\":\"RS256\"," + + "\"kid\":\"YhuIJU6o15EUCyqA0LHEqJd-xVPJgoyW5wZ1o4padWs\"" + + "}.{\"scope\":\"openid\",\"redirect_uri\":\"http://relying-party.org:8001/oidc/rp/callback\",\"response_type\":\"code\",\"nonce\":\"9FJqg3d0AKAXMjDp4UFzdlbTtndk81ju\",\"state\":\"hTDuQKKuaF8vuqFMWISx5iqi0e9yfDbb\",\"client_id\":\"http://relying-party.org:8001/\",\"endpoint\":\"http://cie-provider.org:8002/oidc/op/authorization\",\"acr_values\":\"https://www.spid.gov.it/SpidL2\",\"iat\":1656403171,\"aud\":[\"http://cie-provider.org:8002/oidc/op/\",\"http://cie-provider.org:8002/oidc/op/authorization\"],\"claims\":{\"id_token\":{\"family_name\":{\"essential\":true},\"email\":{\"essential\":true}},\"userinfo\":{\"given_name\":null,\"family_name\":null,\"email\":null,\"fiscal_number\":null}},\"prompt\":\"consent login\",\"code_challenge\":\"-rPJB_41OiEsRkWI3Px2f6GZV7iucNBEQy6WW4ZzuS8\",\"code_challenge_method\":\"S256\",\"iss\":\"http://relying-party.org:8001/\"}" + + ".hZQdNZoZeLNJrIezuXQIV0C5a9ZOubiYTOUYdtmbsR4F_NFZFKDZccbjYk-ntYa2O7_DgcwQ083kAv5dutwU6nhiHBh3K__W4zct2yxcsLspE2pvBbmMjvq7IqmEYgIR2NEBwtCz9RrV6srnjzygm3XHb7kpfu-Z2eVPzxRTqi1C5l-ZX-xPDr2YFFdpHVB17G3lXTEj_Mm6zr6uNeJkS8Ytscq6SXyni3OTj_bRLTLONjoypLRO-qw8z2d8lY7bYgx9mZCAuUtgS75yRlrHuGu4zsE3Bg3UigfnCO_Pqouq-HZOGEZ_7_Hra0S5V8BPek_fRhRH6K534rFWlApRMQ", + out); + } + */ } diff --git a/tool/src/test/java/Report_Test.java b/tool/src/test/java/Report_Test.java deleted file mode 100644 index 8ffe2b3..0000000 --- a/tool/src/test/java/Report_Test.java +++ /dev/null @@ -1,17 +0,0 @@ - -import burp.Report; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.io.IOException; - -public class Report_Test { - - @Test - @DisplayName("ParsingRawSessionAction test") - void test_toPdf() throws IOException { - Report r = new Report(); - - r.toPdf(); - } -} diff --git a/tool/src/test/java/SessionOperation_Test.java b/tool/src/test/java/SessionOperation_Test.java index 94cf5de..86ede08 100644 --- a/tool/src/test/java/SessionOperation_Test.java +++ b/tool/src/test/java/SessionOperation_Test.java @@ -1,7 +1,5 @@ -import burp.ParsingException; -import burp.SessionOperation; -import burp.SessionTrackAction; -import burp.Utils; +import migt.ParsingException; +import migt.SessionOperation; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/tool/src/test/java/SessionTrackAction_Test.java b/tool/src/test/java/SessionTrackAction_Test.java index d8d95e9..f50516c 100644 --- a/tool/src/test/java/SessionTrackAction_Test.java +++ b/tool/src/test/java/SessionTrackAction_Test.java @@ -1,6 +1,6 @@ -import burp.ParsingException; -import burp.SessionTrackAction; -import burp.Utils; +import migt.ParsingException; +import migt.SessionOperation; +import migt.SessionTrackAction; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -10,24 +10,24 @@ public class SessionTrackAction_Test { @Test @DisplayName("ParsingRawSessionAction test") - void test_parseRawSessionAction(){ + void test_parseRawSessionAction() { SessionTrackAction s = new SessionTrackAction(); try { s.parse_raw_action("open | https://www.facebook.com/ |"); - assertEquals(Utils.SessAction.OPEN, s.action); + assertEquals(SessionOperation.SessAction.OPEN, s.action); assertEquals("https://www.facebook.com/", s.elem); assertEquals("open | https://www.facebook.com/ |", s.toString()); s = new SessionTrackAction("click | xpath=/html/body/div[1]/div[3]/div/div[5]/div[1]/span[1] |"); - assertEquals(Utils.SessAction.CLICK, s.action); + assertEquals(SessionOperation.SessAction.CLICK, s.action); assertEquals("xpath=/html/body/div[1]/div[3]/div/div[5]/div[1]/span[1]", s.elem); assertEquals("xpath", s.elem_type); assertEquals("/html/body/div[1]/div[3]/div/div[5]/div[1]/span[1]", s.elem_source); assertEquals("click | xpath=/html/body/div[1]/div[3]/div/div[5]/div[1]/span[1] |", s.toString()); s = new SessionTrackAction("type | id=login | folafo9046@eoscast.com"); - assertEquals(Utils.SessAction.TYPE, s.action); + assertEquals(SessionOperation.SessAction.TYPE, s.action); assertEquals("id=login", s.elem); assertEquals("id", s.elem_type); assertEquals("login", s.elem_source); @@ -38,10 +38,10 @@ void test_parseRawSessionAction(){ assertEquals("wait | 3000 |", s.toString()); s = new SessionTrackAction("equals | xpath=/html/body/div[1]/div[3]/div/div[5]/div[1]/span[1] |"); - assertEquals(Utils.SessAction.EQUALS, s.action); + assertEquals(SessionOperation.SessAction.EQUALS, s.action); assertEquals("equals | xpath=/html/body/div[1]/div[3]/div/div[5]/div[1]/span[1] |", s.toString()); - } catch (ParsingException e){ - assertEquals(1,0); + } catch (ParsingException e) { + assertEquals(1, 0); } } @@ -53,7 +53,7 @@ void testActionAsserts() throws ParsingException { in = "assert clickable | xpath=/html/body/div[1]/div[2]/div/div[2]/div/div/div/div[2]/div[2]/form/div[6]/div/div[3]/div/div[2]/div[1]/a |"; s.parse_raw_action(in); - assertEquals(s.action, Utils.SessAction.ASSERT_CLICKABLE); + assertEquals(s.action, SessionOperation.SessAction.ASSERT_CLICKABLE); assertEquals(s.toString(), in); s = new SessionTrackAction(); diff --git a/tool/src/test/java/Track_Test.java b/tool/src/test/java/Track_Test.java index 2a19d1f..9efb116 100644 --- a/tool/src/test/java/Track_Test.java +++ b/tool/src/test/java/Track_Test.java @@ -1,15 +1,15 @@ -import burp.Marker; -import burp.ParsingException; -import burp.SessionTrackAction; -import burp.Track; +import migt.Marker; +import migt.ParsingException; +import migt.SessionTrackAction; +import migt.Track; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import javax.management.DescriptorKey; import java.util.ArrayList; import java.util.List; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; public class Track_Test { @@ -17,8 +17,8 @@ public class Track_Test { @DisplayName("Test indexes") void testIndexes() throws ParsingException { Track t = new Track("open | https://www.google.com/ |\n" + - "open | https://www.youtube.com/ |\n" + - "wait | 3000"); + "open | https://www.youtube.com/ |\n" + + "wait | 3000"); int indx_0 = t.indexOfStaFromMarker("M0", false); int indx_L = t.indexOfStaFromMarker("ML", true); @@ -38,12 +38,12 @@ void testIndexes() throws ParsingException { @DisplayName("Test first and last indexes") void testFirstAndLAstIndexes() throws ParsingException { Track t = new Track("open | https://www.google.com/ |\n" + - "open | https://www.youtube.com/ |\n" + - "open | https://www.youtube.com/ |\n" + - "open | https://www.youtube.com/ |\n" + - "open | https://www.youtube.com/ |\n" + - "open | https://www.youtube.com/ |\n" + - "wait | 3000"); + "open | https://www.youtube.com/ |\n" + + "open | https://www.youtube.com/ |\n" + + "open | https://www.youtube.com/ |\n" + + "open | https://www.youtube.com/ |\n" + + "open | https://www.youtube.com/ |\n" + + "wait | 3000"); int indx_0 = t.indexOfStaFromMarker("M0", false); int indx_L = t.indexOfStaFromMarker("ML", true); @@ -141,16 +141,16 @@ void rangeTest() throws ParsingException { Track t = new Track("open | https://www.google.com/ |\n" + "open | https://www.youtube.com/ |\n" + "wait | 3000"); - List sta = t.getStasFromMarkers("M0", "ML", false, true); + List sta = t.getStasFromMarkers("M0", "ML", false, true); assertEquals(t.getTrack().get(1), sta.get(0)); assertEquals(t.getTrack().get(2), sta.get(1)); - sta = t.getStasFromMarkers("M0", "ML", true, false); + sta = t.getStasFromMarkers("M0", "ML", true, false); assertEquals(2, sta.size()); assertEquals(t.getTrack().get(0), sta.get(0)); assertEquals(t.getTrack().get(1), sta.get(1)); - sta = t.getStasFromMarkers("M0", "M0", true, true); + sta = t.getStasFromMarkers("M0", "M0", true, true); assertEquals(1, sta.size()); assertEquals(t.getTrack().get(0), sta.get(0)); } diff --git a/tool/src/test/java/Utils_Test.java b/tool/src/test/java/Utils_Test.java index 8bffad1..e896159 100644 --- a/tool/src/test/java/Utils_Test.java +++ b/tool/src/test/java/Utils_Test.java @@ -1,6 +1,5 @@ -import burp.*; +import migt.*; import org.json.JSONObject; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -31,7 +30,7 @@ void testBuildStringWithVars() throws ParsingException { String s = "test da aggiungere $questo$ e poi $qualcosaltro$"; String res = ""; try { - res = Utils.buildStringWithVars(vars, s); + res = Tools.buildStringWithVars(vars, s); } catch (ParsingException e) { e.printStackTrace(); } @@ -50,7 +49,7 @@ void testBuildStringWithVars() throws ParsingException { v4.value = "/link/a/caso"; vars.add(v4); - res = Utils.buildStringWithVars(vars, s); + res = Tools.buildStringWithVars(vars, s); assertEquals("open | https://www.youtube.com/link/a/caso |", res); } @@ -58,23 +57,23 @@ void testBuildStringWithVars() throws ParsingException { @DisplayName("Test find parent div") void testFindParentDiv() throws ParsingException { String in = "xpath=/html/body/div[3]/div[2]/div/div/div/div/div[3]/*[2]"; - String res = Utils.findParentDiv(in); + String res = Tools.findParentDiv(in); assertEquals("xpath=/html/body/div[3]/div[2]/div/div/div/div/div", res); assertThrows(ParsingException.class, () -> { - Utils.findParentDiv("https://www.facebook.com/"); + Tools.findParentDiv("https://www.facebook.com/"); }); assertThrows(ParsingException.class, () -> { - Utils.findParentDiv("https://www.ted.com/settings/account"); + Tools.findParentDiv("https://www.ted.com/settings/account"); }); in = "xpath=/html/body/div[2]/div/div[2]/form/div/span/span/button"; - res = Utils.findParentDiv(in); + res = Tools.findParentDiv(in); assertEquals("xpath=/html/body/div[2]/div/div[2]/form/div", res); in = "id=email"; - res = Utils.findParentDiv(in); + res = Tools.findParentDiv(in); assertEquals("id=email", res); } @@ -82,7 +81,7 @@ void testFindParentDiv() throws ParsingException { @Test @DisplayName("Test execute session ops") void testExecuteSessionOps() throws ParsingException { - burp.Test t = new burp.Test(); + migt.Test t = new migt.Test(); Operation op = new Operation(); List vars = new ArrayList<>(); @@ -107,8 +106,8 @@ void testExecuteSessionOps() throws ParsingException { @Test @DisplayName("Test execute session Ops") - void executeSessioOps_test() { - burp.Test t = new burp.Test(); + void executeSessioOps_test() throws ParsingException { + migt.Test t = new migt.Test(); Session s = new Session("s1"); try { @@ -135,7 +134,7 @@ void executeSessioOps_test() { "open | https://auth.fandom.com/auth/settings |\n" + "click | xpath=/html/body/div[1]/main/div/div[2]/form/section[2]/div[2]/button[1] |"); } catch (ParsingException e) { - assertEquals(1,0); + assertEquals(1, 0); } t.sessions.add(s); Operation op = new Operation(); @@ -154,14 +153,14 @@ void executeSessioOps_test() { try { op.session_operations = SessionOperation.parseFromJson(sop_json); } catch (ParsingException e) { - assertEquals(1,0); + assertEquals(1, 0); } List vars = new ArrayList<>(); try { - Utils.executeSessionOps(t, op, vars); + op.executeSessionOps(t, vars); } catch (ParsingException e) { - assertEquals(1,0); + assertEquals(1, 0); } Session right_output = new Session("s1"); @@ -185,13 +184,13 @@ void executeSessioOps_test() { "click | id=loginbutton |\n" + "click | xpath=/html/body/div[1]/div/div/div/div/div/div/div/div[1]/div/div/div[2]/div[2]/div[1]/div/div |\n"); } catch (ParsingException e) { - assertEquals(1,0); + assertEquals(1, 0); } try { assertEquals(t.getSession("s1").getTrack().toString(), right_output.getTrack().toString()); } catch (ParsingException e) { - assertEquals(1,0); + assertEquals(1, 0); } } } diff --git a/tool/testfile.pdf b/tool/testfile.pdf deleted file mode 100644 index f9ad525..0000000 Binary files a/tool/testfile.pdf and /dev/null differ