diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2597e76 --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ + +

+pesd +

+ + +# PESD Exporter - Burp Suite Extension + +Generate security-oriented sequence diagrams and fine-grained parsed traffic from Burp Suite Proxy history. + +This extension is based on Proxy Enriched Sequence Diagrams format. + +Discover the format [here](mds/Format.md) and read the introduction post in [doyensec blog](https://blog.doyensec.com/2023/02/14/pesd-extension-public-release.html). +

+extension +

+The exporter handles Burp Suite Proxy's traffic conversion to PESD format and offers the possibility to execute templates that will enrich the resulting exports. + +## Extension UI +

+extension +

+ +- Select and send specific traffic entries from Burp Suite Tabs to the Exporter tab + +- Specify a mode of operation for the export. Supported modes : + - ***Domains as Actors*** - Each domain involved in the traffic is represented as an actor in the diagram. Suitable for multi-domain flows analysis + - ***Endpoints as Actors*** - Each endpoint (path) involved in the traffic is represented as an actor in the diagram. Suitable for single-domain flows analysis + +- Configure the flags set that will be matched in the generation of the sequence diagram. Read about flags in the format definition page, section ["Base Diagram Syntaxes" ](mds/Format.md#flags) + +- Select the templates that will be executed on the resulting export. Read more about templates in the following section + +- Auto-Export. Checked by default, sending items to the extension will directly result in a export. + User is redirected to the Browser view and the items are cleaned after the export within the extension + +## Export Capabilities + + +

+extension +

+ +- **Expandable Metadata**. Underlined flags can be clicked to show the underlying metadata from the traffic in a scrollable popover + +- **Masked Randoms in URL Paths**. UUIDs and pseudorandom strings recognized inside path segments are mapped to variable names `` / ``. The re-renderization will reshape the diagram to improve flow readability. Every occurrency with the same value maintains the same name + +- **Notes**. Comments from Burp Suite are converted to notes in the resulting diagram. Use `
` in Burp Suite comments to obtain multi-line notes in PESD exports + +- **Save as** : + - Sequence Diagram in `SVG` format + - `Markdown` file (MermaidJS syntax), + - Traffic `metadata` in `JSON` format. Read about the metadata structure in the format definition page, ["exports section"](https://github.com/doyensec/PESD-Exporter-Extension/blob/main/mds/Format.md#exports) + +## Extending the diagram, syntax and metadata with Templates + +By default, a generic diagram follows the basic [PESD syntax ](mds/Format.md#base-diagram-syntaxes). +PESD Exporter supports syntax and metadata extension via templates execution. + +#### What is a Template? +Templates are iterations that occur on the basic PESD object in order to enrich its content by: +- Adding new Flags or modifying existing ones +- Framing sections of the resulting diagram. Read about [MermaidJS Alt Syntax](https://mermaid-js.github.io/mermaid/#/sequenceDiagram?id=alt). +- Enriching the metadata with new findings + +***Approach Idea :*** Users can leverage this extensibility to parse metadata and markdown in order to add new value in both of them by adding new logic. + +#### Currently Implemented Templates + +The Extension currently supports the following templates : +- **OAuth2 / OpenID Connect.** The template matches standard OAuth2/OpenID Connect flows and adds related flags + flow frame. + Oauth2 supported flows : Implicit Grant and Code Grant. OpenID supported flows : Code Grant, Implicit Grant and Hybrid flow. + Respectively based on [rfc6749](https://datatracker.ietf.org/doc/html/rfc6749) and [openid-connect-core-1_0](https://openid.net/specs/openid-connect-core-1_0.html) + +- **SAML SSO**. The template matches Single-Sign-On flows with SAML V2.0 and adds related flags + flow frame. + Based on [SAML V2.0 ](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0-cd-02.html#5.1.Web%20Browser%20SSO%20Profile|outline), supported flows: + - SP-initiated SSO using a Redirect Binding for the SP-to-IdP message and a POST Binding for the IdP-to-SP message + - SP-initiated SSO using a POST Binding for the message and an Artifact Binding for the message + - IDP-initiated SSO using a POST Binding for the IdP-to-SP message; no SP-to-IdP message is involved. + +Template matching example for *SAML SP-initiated SSO with redirect POST*: +

+ +samlex +

+ + +#### Development + +- Clone the repository +- Import it in Netbeans / your preferred IDE +- Run **gradle build fatjar** to compile the extension +- Import the compiled JAR in `build/libs/pesd-exporter-all.jar` + +#### How to write new templates + +Find the [template implementation guide](mds/WritingTemplates.md). + +## Credits + +*Author and Maintainer:* Francesco Lacerenza ([@lacerenza_fra](https://twitter.com/lacerenza_fra)) + +This project was made with love in the [Doyensec Research island](https://doyensec.com/research.html) during the [internship with 50% research time](https://blog.doyensec.com/2019/11/05/internship-at-doyensec.html). + + + +![alt text](https://doyensec.com/images/logo.svg "Doyensec Logo") diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..1d0a401 --- /dev/null +++ b/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'java' + +repositories { + mavenCentral() +} + +dependencies { + implementation 'net.portswigger.burp.extender:burp-extender-api:2.3' + implementation 'org.apache.commons:commons-lang3:3.5' + implementation group: 'org.apache.commons', name: 'commons-collections4', version: '4.1' + implementation group: 'org.json', name: 'json', version: '20210307' +} + +sourceSets { + main { + java { + srcDir 'src' + } + resources { + srcDir 'resources' + } + } +} + +task fatJar(type: Jar) { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + baseName = project.name + '-all' + from { configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) } } + with jar +} + +compileJava { + targetCompatibility '1.8' + sourceCompatibility '1.8' +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ffed3a2 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/images/burptopesd.gif b/images/burptopesd.gif new file mode 100644 index 0000000..6430871 Binary files /dev/null and b/images/burptopesd.gif differ diff --git a/images/example.png b/images/example.png new file mode 100644 index 0000000..cbd955f Binary files /dev/null and b/images/example.png differ diff --git a/images/export.gif b/images/export.gif new file mode 100644 index 0000000..2e70a48 Binary files /dev/null and b/images/export.gif differ diff --git a/images/extension.png b/images/extension.png new file mode 100644 index 0000000..2a360cf Binary files /dev/null and b/images/extension.png differ diff --git a/mds/Format.md b/mds/Format.md new file mode 100644 index 0000000..9ff4697 --- /dev/null +++ b/mds/Format.md @@ -0,0 +1,125 @@ +# PESD Format + +"Proxy Enriched Sequence Diagrams" is a simple format designed to laverage +[MermaidJS](https://github.com/mermaid-js/mermaid) technology and link it with fine-grained web traffic parsing to visualize and explore http traffic in a new way. + +## PESD Format +PESD is a diagramming format designed to visualize Web traffic and store parsed information usable for traffic analysis automation. It was created to reduce the efforts of IT / Security Professionals when dealing with the complexity of functional flows in modern web applications. + +Some key characteristics of the format : +- Enable `visual-analysis`, especially useful for complex Application Flows in multi-actor scenarios +- `Tester-specific syntax` in order to facilitate the analysis; +- Carry parsed metadata from the web traffic to `enable further automation of the analysis` +- Be `usable for reporting` purposes like documentation of current implementations or Proof Of Concepts + +### Exports +A PESD export is composed of three elements : +- ***pesd.md*** , a markdown file containing the sequence diagram representing the intercepted web traffic. The markdown is based on [MermaidJS](https: github.com/mermaid-js/mermaid) syntax, a Javascript based diagramming and charting tool that renders Markdown-inspired text definitions to create and modify diagrams dynamically. Here is an example : + +``` +sequenceDiagram +Burp->>example.com:[GET] /favicon.ico
+example.com->>Burp:[404] HTML +``` + +MermaidJS rendered markdown: +

+pesd +

+ +- ***pesd.json*** , a JSON file containing parsed and raw data extracted from the traffic. This file represents the metadata of the format that enable further analysis automation and detailed flows saving + +``` +{ + "item1":{ + "req":{ + "path":"/favicon.ico", + "headers":["[...] Array of Headers [...]"], + "protocol":"http", + "url_params":{}, + "port":80, + "destination":"example.com", + "raw":[...] BASE64ENCODED RAW REQUEST [...], + "body_params":{}, + "id":1, + "http-verb":"GET", + "body_json":{}, + "cookies":{} + }, + "templateMatches":{ ... }, + "res":{ + "Stated-MimeType":"HTML", + "headers":[...] Array of Headers [...], + "raw":"[...] BASE64ENCODED RAW RESPONSE [...]", + "Inferred-MimeType":"HTML", + "id":2, + "cookies":{}, + "statusCode":404, + "CSP": [...]CSP[...] + } + }, + . + . + . + "itemN":{ + . + . + . + } +} + +``` + +### Base Diagram Syntaxes + +The traffic conversion syntax was defined on top of MermaidJS markdown syntax. The definition was designed to represent HTTP traffic with Sequence Diagrams. + +Requests/responses arrows are filled with information that helps to visualize useful details of the traffic while maintaining the whole diagram readability. + +Two main modes of representation for Application Flows are supported: +- ***Domains as Actors*** - Each domain involved in the traffic is represented as an actor in the diagram. Suitable for multi-domain flows analysis +Current Syntax : +``` +request message syntax : [HTTP.METHOD] /path/of/the/request + $Flag1$ $Flag2$ ... $FlagN$ + +response message syntax : [Status.Code] Content-Type + $Flag1$ $Flag2$ ... $FlagN$ +``` + +- ***Endpoints as Actors*** - Each endpoint (path) involved in the traffic is represented as an actor in the diagram. Suitable for single-domain flows analysis +Current Syntax : +``` +request message syntax : [HTTP.METHOD] $Flag1$ $Flag2$ ... $FlagN$ + +response message syntax : [Status.Code] Content-Type $Flag1$ $Flag2$ ... $FlagN$ +``` + +###### Flags +Flags are basically Strings that represent the presence of something within req/res. +List of currently supported basic flags : +``` +In Requests : + - HasCookies + - HasUrlParams + - HasBodyParams + - HasJsonParam + - HasXMLParam + - HasMultiPartAttr + - HasBearerToken + - HasAuthz +In Responses : + - Content-Type + - CookiesSet + - HasCORS + - HasXFrameOption + - HasCSP + +``` + +### How to generate PESD exports? + +PESD can be generated directly from Burp Suite with the [PESD Exporter Extension](../README.md) + +Alternatively, it is possible to use the [PESD Wrapper Java library](PESDWrapper.md). +The library exposes an object with all the methods needed to fill and modify a PESD Object. diff --git a/mds/PESDWrapper.md b/mds/PESDWrapper.md new file mode 100644 index 0000000..3887758 --- /dev/null +++ b/mds/PESDWrapper.md @@ -0,0 +1,97 @@ + +# PESD Wrapper - Methods Detail +**Note**: If a method's signature contains the *Integer index* parameter, it is optional. If specified in the call, its value is used as index within the data array when performing the method's action. Otherwise the current iterator index value is used.

+ +| Return Type | Method's Signature and Description | +|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| - | **PESDWrapper(PrintWriter stdout)**
main constructor | +| - | **PESDWrapper(PrintWriter stdout, JSONArray data)**
Constructor usable to create copies of a PESD Object, simply use *getStucturedData()* to extract JSONArray data and pass it to the constructor | +| *String* | **getSeqDiagramdMD(Integer op_mode)**
This method serializes the JSONArray data to MermaidJS Markdown syntax. The *op_mode* parameter indicates the export mode: - 0 = Domains as Actors - 1 = Endpoints as Actors Returns a string containing a valid MermaidJS Markdown Sequence Diagram | +| *JSONObject* | **getMetadata()**
Generates the metadata export with PESD format | +| *JSONObject* | **getLine(*Integer index*)**
Returns line at *index*, if no parameter is passed, returns line according to internal iterator | +| *Integer* | **getIterator()**
Returns the current index value of the internal iterator. Return -1 if the end of the object is reached | +| *void* | **setIterator()**
Set index value of the internal iterator to 0 | +| *void* | **startIterator(Integer num)**
Set index value of the internal iterator to *num* parameter | +| *void* | **nextLine()**
Increase internal iterator index of 1 | +| *void* | **prevLine()**
Decrease internal iterator index of 1 | +| *void* | **addReq(String source, String destination, String method, String path, List flags, JSONObject metadata, *Integer index*)**
Method used to add a Request line to the obj that will have the format: Markdown : S ->> D : [METHOD] /path \n Flag1 Flag2 ... FlagN Metadata: JSONObject generated by PESDMetadata obj | +| *void* | **addRes(String source, String destination, String status_code, String mimeType,List flags, JSONObject metadata, *Integer index*)**
Method used to add a Response line to the obj that will have the format: Markdown : D ->> S : [STATUS_CODE] MIME_TYPE \n Flag1 Flag2 ... FlagN Metadata: JSONObject generated by PESDMetadata obj | +| *void* | **addAlt(String text, *Integer index*)**
Method used to add a Alt line (rectangle start in MermaidJS MD) to the obj that will have the format : Markdown : Alt NAME | +| *void* | **endAlt(*Integer index*)**
Method used to add a end line (rectangle close in MermaidJS MD) to the obj that will have the format : Markdown : end | +| *void* | **addNote(String note, *Integer index*)**
Method used to add a Note line to the obj that will have the format: Markdown : Note right of Browser : TEXT | +| *void* | **modifyFlags(List flagsArr, *Integer index*)**
Substituting flags in a transaction during an iteration. | +| *void* | **addFlag(String flag, *Integer index*)**
Adding a flag to a transaction while iterating. | +| *void* | **modifyMetadata(JSONObject metadataObj, *Integer index*)**
Replacing a metadata object in a line with new metadata while iterating. | +| *void* | **removeLine(*Integer index*)**
Remove line while iterating | +| *Boolean* | **altEndConsistencyNormalizer()**
This method is used to resolve local consistency problems within the resulting MermaidJS Markdown before the serialization. Currently it simply removes non-closed and non-opened Alt rectangles (Alt without end or end without Alt) Moreover, in case of 2 consecutive Alt, the first occurrence is removed | +| *void* | **addToPos(int pos, JSONObject jsonObj)**
Function needed to perform JSONArray.put(pos, json) without losing an entry due to replacement. Basically it performs right-shift of all the entries after the position needed for the put | +| *Integer* | **getSize()**
Return the number of lines within the Sequence Diagram | +| *JSONArray* | **getStucturedData()**
Return a copy by value of the data contained in the wrapper | + + + + +### Internal Data Structure +The internal data structure : +``` +[ + { + "type": "req", + "source": *String*, + "destination": *String*, + "maskedPath": *String*, + "method": *String*, + "path": *String*, + "flags": *List*, + "metadata": *JSONObject* + }, + { + "type": "res", + "source": String, + "destination": String, + "status_code": String, + "mime_type": String, + "flags": List, + "metadata": JSONObject + }, + { + "type": "alt", + "alt": String, + }, + { + "type": "end" + }, + { + "type": "Note", + "note": String, + }, +. +. +. + +] +``` + +### Usage Examples +``` +PESDWrapper pesdObj = new PESDWrapper(out_printWriter); +// Generic function that fills the object +fill_PESD(pesdObj); +// Setting the internal iterator to 0 +pesdObj.startIterator(); +// Iterating until we get end of PESD from *getIterator* (-1) +while (pesdObj.getIterator() != -1) { + // Getting current line according to internal iterator + JSONObject line = pesdObj.getLine(); + // Switching actions depending on the type of line + switch((String) line.get("type")){ + case "req": + // do stuff + case "res": + // do stuff + default: break + } + // Classic i++ + pesdObj.nextLine(); +} +``` \ No newline at end of file diff --git a/mds/WritingTemplates.md b/mds/WritingTemplates.md new file mode 100644 index 0000000..5569217 --- /dev/null +++ b/mds/WritingTemplates.md @@ -0,0 +1,111 @@ + +## How to write templates + +Required Steps : + +#### 1) Develop your template +In order to manipulate the pesd object you should read the [PESD Wrapper Java library](PESDWrapper.md) documentation and learn about all the exposed iteration / manipulation methods. + +Templates must inherit from **TemplateParent** and override the *Run* method. The engine will construct all the templates and save them in a TemplateParent var, then it will call the *Run* method to manipulate the *PESDWrapper* object. + +The following code is an Empty Template structure to start with: +``` +/* + * Empty Template Structure + */ + +public class EmptyTemplate extends TemplateParent{ + // As you can see, the creation will set pesdObj and stdout to be accessible directly within your code + public EmptyTemplate( PESDWrapper pesdObj, PrintWriter stdout) { + super(pesdObj, stdout); + } + + // put all your logic inside the run method + @Override + public PESDWrapper run(){ + + // your Logic + + // All the templates must return a PESDWrapper obj + return this.pesdObj; + } +} + +``` + +#### 2) Adding the new Template Class to the Burp extension +1. Modify PESDPanel.java by adding the a checkbox for your template + +2. Modify PESDPanel.java:45 and add your template name to the String array of templates + ``` + this.Templates = new String[]{"SAML_SSO", "OAuth2/OIDC","MY_NEW_TEMPLATE_NAME"}; + ``` + +3. Add the following lines of code to the PESDPanel constructor: +``` +YOUR_TEMPLATE_checkbox.setVisible(false); +YOUR_TEMPLATE_checkbox.doClick(); +``` + +4. In PESDPanel.java, modify *EditTemplates_ButtonActionPerformed* method by adding *your_template_checkbox.setVisible()* with false for the **if** and true for the **else** case. + +5. In PESDPanel.java, fill *YOUR_TEMPLATE_checkboxActionPerformed()* method as follows +``` +private void YOUR_TEMPLATE_checkboxActionPerformed(java.awt.event.ActionEvent evt) { + if (SAML_SSO_TEMPLATE_checkbox.isSelected()){ + this.Templates[$INDEX]= "MY_NEW_TEMPLATE_NAME"; + } else { + this.Templates[$INDEX]=""; + } + } +``` +Note that *$INDEX* is the position of your template inside *this.Templates* array (see step 2). + +6. Add the a case for the new template in TemplateRunner.java. Add it inside the switch-case in the constructor as follows: +``` +case "MY_NEW_TEMPLATE_NAME": + this.template = new NewTemplateClass(diagram, metadataObj, stdout); + break; +``` + +7. When you are done with coding, name and save your template inside the templates package directory. + +8. Re-build the Extension and you are ready to go. What a long journey! + + +## Example Template : Flow-wide Frame Add +This simple example shows how to iterate through a PESDWrapper object and add a frame (Mermaid Alt syntax) around the entire flow being exported. This is just a demonstration of the basic iteration. + +``` +/* + * Example Template that wraps the Entire flow in a Flow Frame (Alt/end named rectangle) + */ + +public class EmptyTemplate extends TemplateParent{ + + public EmptyTemplate( PESDWrapper pesdObj, PrintWriter stdout) { + super(pesdObj, stdout); + } + + // put all your logic inside the run method + @Override + public PESDWrapper run(){ + + pesdObj.startIterator(); + while (pesdObj.getIterator() != -1) { + JSONObject line = pesdObj.getLine(); + + // adding and Alt/end flow frame to wrap the entire flow + if(pesdObj.getIterator()==0){ + pesdObj.addFlag("HELLO PESD FLOW FRAME"); + } else if (pesdObj.getIterator() == pesdObj.getSize()-1) { + pesdObj.endAlt(pesdObj.getIterator()+1); + } + + pesdObj.nextLine(); + } + return this.pesdObj; + } +} + +``` diff --git a/resources/export.html b/resources/export.html new file mode 100644 index 0000000..162ce91 --- /dev/null +++ b/resources/export.html @@ -0,0 +1,500 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PESD Exporter + + + + + +
+
+
+ +
+

PESD Exports

+
+
+
+
+
+
+ +
+
+
+
+
+ + + + + +
+
+ +
+
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..45951de --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'pesd-exporter' diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000..c6412b0 Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/burp/BurpExtender.java b/src/burp/BurpExtender.java new file mode 100644 index 0000000..5429f9d --- /dev/null +++ b/src/burp/BurpExtender.java @@ -0,0 +1,80 @@ + +package burp; + +import java.util.List; +import pesd.PESDExportTab; + +import java.util.*; +import java.awt.event.*; +import javax.swing.JMenuItem; +import pesd.PESDPanel; + +public class BurpExtender implements IBurpExtender,IContextMenuFactory +{ + + private IBurpExtenderCallbacks callbacks; + private IExtensionHelpers helpers; + + private final static String DOMAIN_ACTORS = "Domains as Actors"; + private final static String ENDPOINTS_ACTORS = "Endpoints as Actors"; + private final static String CLEAN_FLOW = "Clean Flow"; + private PESDExportTab Tab; + + @Override + public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) + { + this.callbacks = callbacks; + helpers = callbacks.getHelpers(); + callbacks.setExtensionName("PESD Exporter"); + PESDExportTab tab = new PESDExportTab(callbacks, helpers); + this.Tab=tab; + callbacks.addSuiteTab(tab); + callbacks.registerContextMenuFactory(this); + + } + + @Override + public List createMenuItems(IContextMenuInvocation invocation) { + final IHttpRequestResponse[] messages = invocation.getSelectedMessages(); + if (messages == null || messages.length == 0) return null; + JMenuItem i1 = new JMenuItem(DOMAIN_ACTORS); + JMenuItem i2 = new JMenuItem(ENDPOINTS_ACTORS); + JMenuItem i3 = new JMenuItem(CLEAN_FLOW); + i1.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + copyMessages(messages,0); + } + }); + i2.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + copyMessages(messages,1); + } + }); + i3.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + cleanFlow(); + } + }); + return Arrays.asList(i1,i2,i3); + } + + private void copyMessages(IHttpRequestResponse[] messages, Integer opMode) { + PESDPanel panel = (PESDPanel) this.Tab.getPanel(); + panel.setItems(messages,opMode); + panel.setCountLabel(); + if (panel.getAutoExport()){ + panel.clickPESDExport_button(); + } + + } + + private void cleanFlow() { + PESDPanel panel = (PESDPanel) this.Tab.getPanel(); + IHttpRequestResponse iHttpRequestResponse[] = new IHttpRequestResponse[0]; + panel.items=iHttpRequestResponse; + panel.setCountLabel(); + } +} diff --git a/src/pesd/Oauth2_OpenID_Template.java b/src/pesd/Oauth2_OpenID_Template.java new file mode 100644 index 0000000..aa98541 --- /dev/null +++ b/src/pesd/Oauth2_OpenID_Template.java @@ -0,0 +1,104 @@ +/* + * This template is based on the following specification documents: + * https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1 + * https://openid.net/specs/openid-connect-core-1_0.html#Authentication + */ +package pesd; + +import java.io.PrintWriter; +import java.util.Base64; +import org.json.JSONArray; +import org.json.JSONObject; + +public class Oauth2_OpenID_Template extends TemplateParent{ + + public Oauth2_OpenID_Template( PESDWrapper pesdObj, PrintWriter stdout) { + super(pesdObj, stdout); + } + + @Override + public PESDWrapper run(){ + pesdObj.startIterator(); + while (pesdObj.getIterator() != -1) { + JSONObject line = pesdObj.getLine(); + JSONObject metadata; + JSONObject urlParams; + + switch((String) line.get("type")){ + case "req": + try { + metadata = (JSONObject) line.get("metadata"); + urlParams = (JSONObject) metadata.get("url_params"); + + if (urlParams.has("client_id") && urlParams.has("response_type")){ + // identifying first reqs of OpenID flow: code grant, token grant and hybrid + if (urlParams.has("scope") && urlParams.getString("scope").contains("openid")){ + + if (urlParams.getString("response_type").contains("code") && urlParams.getString("response_type").contains("token")){ + // hybrid flow + pesdObj.addFlag("OIDC_HybridGrant"); + } else if (urlParams.getString("response_type").contains("code")) { + // hybrid Code Grant flow + pesdObj.addFlag("OIDC_CodeGrant"); + } else if (urlParams.getString("response_type").contains("token")) { + // implicit grant flow + pesdObj.addFlag("OIDC_ImplicitGrant"); + } + pesdObj.addAlt("OIDC", pesdObj.getIterator()); + } else { + // identifying first reqs of OAuth2 flow: code grant and implicit grant + switch(urlParams.getString("response_type")){ + case "code": + pesdObj.addFlag("CodeGrant"); + break; + case "token": + pesdObj.addFlag("ImplicitGrant"); + break; + default: + break; + } + pesdObj.addAlt("OAuth2", pesdObj.getIterator()); + } + } + //identifying last request of Code Grant flow + if (urlParams.has("code")){ + pesdObj.addFlag("Code"); + pesdObj.endAlt(pesdObj.getIterator()+2); + } + } catch(Exception e) { + stdout.println(e); + } + break; + + case "res": + try { + //identifying last request of Implicit Grant flow + metadata = (JSONObject) line.get("metadata"); + JSONArray headers = (JSONArray) metadata.get("headers"); + for (int i = 0; i < headers.length(); i++) { + String header = headers.getString(i); + if (header.contains("Location") && header.contains("#") && header.contains("access_token") && header.contains("token_type")){ + pesdObj.addFlag("AccessToken"); + pesdObj.endAlt(pesdObj.getIterator()+1); + } + } + // looking for implicit grant tokens inside the response body + String raw_res_base64Encoded = (String) metadata.get("raw"); + byte[] decodedBytes = Base64.getDecoder().decode(raw_res_base64Encoded); + String decoded_raw_response = new String(decodedBytes); + if(decoded_raw_response.contains("access_token") && decoded_raw_response.contains("token_type")){ + pesdObj.addFlag("AccessToken"); + pesdObj.endAlt(pesdObj.getIterator()+1); + } + } catch(Exception e) { + stdout.println(e); + } + break; + + default: break; + } + pesdObj.nextLine(); + } + return this.pesdObj; + } +} diff --git a/src/pesd/PESDExportTab.java b/src/pesd/PESDExportTab.java new file mode 100644 index 0000000..31a8b7b --- /dev/null +++ b/src/pesd/PESDExportTab.java @@ -0,0 +1,41 @@ + +package pesd; +import burp.IBurpExtenderCallbacks; +import burp.IExtensionHelpers; +import burp.ITab; +import java.awt.Component; +import javax.swing.JPanel; +/** + * + * @author francesco lacerenza https://twitter.com/lacerenza_fra + */ + +public class PESDExportTab implements ITab { + + private final IBurpExtenderCallbacks callbacks; + private final IExtensionHelpers helpers; + private final JPanel panel; + + public PESDExportTab(IBurpExtenderCallbacks callbacks, IExtensionHelpers helpers) { + this.callbacks = callbacks; + this.helpers = helpers; + JPanel panelCreate = new PESDPanel(callbacks, helpers); + this.panel=panelCreate; + this.panel.setMaximumSize(this.panel.getMinimumSize()); + } + + @Override + public String getTabCaption() { + return "PESD Exporter"; + } + + @Override + public Component getUiComponent() { + callbacks.customizeUiComponent(this.panel); + return this.panel; + } + + public JPanel getPanel() { + return panel; + } +} diff --git a/src/pesd/PESDExporter.java b/src/pesd/PESDExporter.java new file mode 100644 index 0000000..fc2c277 --- /dev/null +++ b/src/pesd/PESDExporter.java @@ -0,0 +1,330 @@ +package pesd; + +import burp.IBurpExtenderCallbacks; +import burp.ICookie; +import burp.IExtensionHelpers; +import burp.IHttpRequestResponse; +import java.io.PrintWriter; +import burp.IHttpService; +import burp.IParameter; +import burp.IRequestInfo; +import burp.IResponseInfo; +import java.util.ArrayList; +import java.util.List; +import org.json.JSONObject; +import org.apache.commons.lang3.StringEscapeUtils; + +/** + * + * @author francesco + */ +public class PESDExporter { + + private IExtensionHelpers helpers; + private IHttpRequestResponse[] allReqRes; + private PrintWriter stdout; + private Integer operationMode; + private Boolean[] wantedBools; + private String metadata; + private JSONObject metadataObj; + private String[] templates; + + public PESDExporter(IBurpExtenderCallbacks callbacks, IExtensionHelpers helpers, IHttpRequestResponse[] allReqRes, PrintWriter stdout, PrintWriter stderr, Integer mode, Boolean[] syntaxBools, String[] templates) { + this.helpers = helpers; + this.allReqRes = allReqRes; + this.stdout = stdout; + // op. mode = 0 -> Domains as Actors , op. mode = 1 -> Endpoints as Actors + this.operationMode = mode; + // Selected template + this.templates = templates; + // wantedBools index: 0=HasUrlParams, 1=HasBodyParam, 2=HasJsonParam, 3=HasXmlParam, 4=HasMultipartAttr, 5=HasAuthzToken, 6=Content-type, 7=CookiesSet, 8=HasCORS, 9=HasXFrameOp, 10=HasCSP, 11=HasCookies + this.wantedBools = syntaxBools; + } + + public String[] generatePESD() { + JSONObject csp_res; + String CSP = ""; + PESDWrapper pesdObj = new PESDWrapper(this.stdout); + // PESDMetaData will handle data addition to the metadata + PESDMetaData metadataObj = new PESDMetaData(this.helpers); + //Looping over all req/res couples (items) and converting them to PESD markdown and metadata + int num = 1; + String browser = "Browser"; + for (int rc = 0; rc < this.allReqRes.length; rc++) { + try { + //needed items parsing vars + Boolean noRes = false; + IResponseInfo iResponseInfo = new IResponseInfo() { + @Override + public List getHeaders() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public int getBodyOffset() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public short getStatusCode() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public List getCookies() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public String getStatedMimeType() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public String getInferredMimeType() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + }; + + IHttpService httpService = allReqRes[rc].getHttpService(); + String actor = httpService.getHost(); + IRequestInfo iRequestInfo2 = helpers.analyzeRequest(allReqRes[rc]); + String path = iRequestInfo2.getUrl().getPath(); + IRequestInfo iRequestInfo = helpers.analyzeRequest(allReqRes[rc].getRequest()); + try { + iResponseInfo = helpers.analyzeResponse(allReqRes[rc].getResponse()); + } catch (Exception e) { + noRes = true; + } + + JSONObject params_res = this.getParamsSetFlag(iRequestInfo); + // getting params lists: url, body, json, cookies, csp + JSONObject url_params = params_res.getJSONObject("url_params"); + JSONObject body_params = params_res.getJSONObject("body_params"); + JSONObject body_json = params_res.getJSONObject("body_json"); + JSONObject req_cookies = params_res.getJSONObject("req_cookies"); + if (!noRes) { + csp_res = this.getHasCSPFlag(iResponseInfo); + csp_res.getBoolean("HasCSP"); + CSP = csp_res.getString("CSP"); + } + // adding metadata about the item + JSONObject metadata_item = metadataObj.addItemMetadata(num, path, iRequestInfo, iResponseInfo, allReqRes[rc], url_params, body_params, body_json, CSP, req_cookies, noRes); + num = num + 2; + List flags_req = aggregateReqFlags(iRequestInfo); + pesdObj.addReq(browser, actor, iRequestInfo.getMethod(), path, flags_req, (JSONObject) metadata_item.get("req")); + try { + if (!metadata_item.getJSONObject("req").get("comment").toString().isEmpty()) { + pesdObj.addNote(metadata_item.getJSONObject("req").get("comment").toString()); + } + } catch (Exception e) { + } + + //Handling items with response + if (!noRes) { + + List flags_res = aggregateResFlags(iResponseInfo); + String mime_type; + if (this.wantedBools[6]) { + mime_type = iResponseInfo.getStatedMimeType(); + } else { + mime_type = "NoNe"; + } + pesdObj.addRes(actor, browser, String.valueOf(iResponseInfo.getStatusCode()), mime_type, flags_res, (JSONObject) metadata_item.get("res")); + } else { + num = num - 1; + } + + } catch (Exception e) { + stdout.println("Error:" + e.toString()); + } + } + //template engine run + TemplateRunner engine = new TemplateRunner(this.templates, pesdObj, this.stdout); + this.metadata = engine.getMetadata().toString(); + this.metadataObj = engine.getMetadata(); + return engine.getDiagram(this.operationMode); + } + + public String getMetadata() { + return this.metadata; + } + + private Boolean getCookiesSetFlag(IResponseInfo iResponseInfo) { + // CookieSet Boolean + Boolean CookiesSet = false; + List iCookie = iResponseInfo.getCookies(); + if (iCookie.size() != 0 && this.wantedBools[7]) { + CookiesSet = true; + } + return CookiesSet; + } + + private Boolean getHasAuthzFlag(IRequestInfo iRequestInfo) { + Boolean HasAuthz = false; + if (this.wantedBools[5]) { + List headers = iRequestInfo.getHeaders(); + for (String header : headers) { + if (header.toLowerCase().replaceAll("\\s", "").startsWith("authorization")) { + HasAuthz = true; + break; + } + } + } + return HasAuthz; + } + + private JSONObject getParamsSetFlag(IRequestInfo iRequestInfo) { + JSONObject results = new JSONObject(); + Boolean PramBools[] = new Boolean[]{false, false, false, false, false, false}; + // Params Flags parsing and arranging for metadata + // Bools index: 0=HasUrlParams, 1=HasBodyParam, 2=HasJsonParam, 3=HasXmlParam, 4=HasMultipartAttr, 5=HasAuthzToken, 6=Content-type, 7=CookiesSet + // array of output bools ordered with true val if one of the types matches in one param, IParameter inteface types value : PARAM_URL=0, PARAM_BODY=1, PARAM_JSON=6, PARAM_XML=3, PARAM_MULTIPART_ATTR=5, PARAM_COOKIE=2 + List iParameter = iRequestInfo.getParameters(); + JSONObject url_params = new JSONObject(); + JSONObject body_params = new JSONObject(); + JSONObject body_json = new JSONObject(); + JSONObject req_cookies = new JSONObject(); + + for (int i = 0; i < iParameter.size(); i++) { + switch (iParameter.get(i).getType()) { + case 0: + if (this.wantedBools[0]) { + PramBools[0] = true; + //URL . use getname and getvalue then add it to a list + String name = iParameter.get(i).getName(); + String value = iParameter.get(i).getValue(); + url_params.put(StringEscapeUtils.escapeJava(name), StringEscapeUtils.escapeJava(value)); + } + break; + case 2: + if (this.wantedBools[11]) { + PramBools[5] = true; + //Cookie . use getname and getvalue then add it to a list + String name = iParameter.get(i).getName(); + String value = iParameter.get(i).getValue(); + req_cookies.put(StringEscapeUtils.escapeJava(name), StringEscapeUtils.escapeJava(value)); + } + break; + case 1: + if (this.wantedBools[1]) { + PramBools[1] = true; + //BodyParam . use getname and getvalue then add it to a list + String name = iParameter.get(i).getName(); + String value = iParameter.get(i).getValue(); + body_params.put(StringEscapeUtils.escapeJava(name), StringEscapeUtils.escapeJava(value)); + } + break; + case 6: + if (this.wantedBools[2]) { + PramBools[2] = true; + //bodyJSON . use getname and getvalue then add it to a list + String name = iParameter.get(i).getName(); + String value = iParameter.get(i).getValue(); + body_json.put(StringEscapeUtils.escapeJava(name), StringEscapeUtils.escapeJava(value)); + } + break; + case 3: + if (this.wantedBools[3]) { + PramBools[3] = true; + } + break; + case 5: + if (this.wantedBools[4]) { + PramBools[4] = true; + } + break; + default: + break; + } + } + results.put("ParamBools", PramBools); + results.put("url_params", url_params); + results.put("body_params", body_params); + results.put("body_json", body_json); + results.put("req_cookies", req_cookies); + return results; + } + + private Boolean getHasXFrameOpFlag(IResponseInfo iResponseInfo) { + Boolean HasXFrameOp = false; + List headers = iResponseInfo.getHeaders(); + if (this.wantedBools[9]) { + for (String header : headers) { + if (header.startsWith("X-Frame-Options")) { + HasXFrameOp = true; + break; + } + } + } + return HasXFrameOp; + } + + private JSONObject getHasCSPFlag(IResponseInfo iResponseInfo) { + JSONObject result = new JSONObject(); + String CSP_string = ""; + Boolean HasCSP = false; + List headers = iResponseInfo.getHeaders(); + if (this.wantedBools[10]) { + for (String header : headers) { + if (header.startsWith("Content-Security-Policy")) { + HasCSP = true; + CSP_string = header; + break; + } + } + } + result.put("HasCSP", HasCSP); + result.put("CSP", CSP_string); + return result; + } + + private Boolean getHasCORSFlag(IResponseInfo iResponseInfo) { + Boolean HasCORS = false; + List headers = iResponseInfo.getHeaders(); + if (this.wantedBools[8]) { + for (String header : headers) { + if (header.startsWith("Access-Control-Allow-Origin")) { + HasCORS = true; + break; + } + } + } + return HasCORS; + } + + private List aggregateReqFlags(IRequestInfo iRequestInfo) { + // ParamBools is an array of bools ordered with true val if one of the types matches in one param: PARAM_URL=0, PARAM_BODY=1, PARAM_JSON=6, PARAM_XML=3, PARAM_MULTIPART_ATTR=5 , PARAM_COOKIE=2 + String PramFlags[] = new String[]{"UrlParams", "BodyParams ", "JSONParam", "XMLParam", "Multipart", "Cookies"}; + List paramBools = new ArrayList(); + Boolean[] params = (Boolean[]) this.getParamsSetFlag(iRequestInfo).get("ParamBools"); + for (int i = 0; i < 6; i++) { + if (params[i] == true) { + paramBools.add(PramFlags[i]); + } + } + if (this.getHasAuthzFlag(iRequestInfo)) { + paramBools.add("HasAuthz"); + } + return paramBools; + } + + private List aggregateResFlags(IResponseInfo iResponseInfo) { + List resBools = new ArrayList(); + if (this.getCookiesSetFlag(iResponseInfo)) { + resBools.add("SetCookies"); + } + if (this.getHasCORSFlag(iResponseInfo)) { + resBools.add("CORS"); + } + if (this.getHasXFrameOpFlag(iResponseInfo)) { + resBools.add("XFrameOp"); + } + JSONObject csp_res = this.getHasCSPFlag(iResponseInfo); + if (csp_res.getBoolean("HasCSP")) { + resBools.add("CSP"); + } + return resBools; + } + +} diff --git a/src/pesd/PESDMetaData.java b/src/pesd/PESDMetaData.java new file mode 100644 index 0000000..0b48f4e --- /dev/null +++ b/src/pesd/PESDMetaData.java @@ -0,0 +1,87 @@ + +package pesd; + +import burp.ICookie; +import burp.IExtensionHelpers; +import burp.IHttpRequestResponse; +import burp.IRequestInfo; +import burp.IResponseInfo; +import java.util.ArrayList; +import java.util.List; +import org.json.JSONObject; +import org.apache.commons.lang3.StringEscapeUtils; + +/** + * + * @author francesco + */ +public class PESDMetaData { + + private final IExtensionHelpers helpers; + + public PESDMetaData(IExtensionHelpers helpers) { + this.helpers=helpers; + } + + public JSONObject addItemMetadata(Integer num, String path, IRequestInfo iRequestInfo, IResponseInfo iResponseInfo, IHttpRequestResponse reqResItem, JSONObject url_params, JSONObject body_params, JSONObject body_json, String CSP, JSONObject req_cookies, Boolean noRes) { + JSONObject item = new JSONObject(); + //adding req json key + JSONObject req = new JSONObject(); + req.put("id",num); + req.put("destination",StringEscapeUtils.escapeJava(reqResItem.getHttpService().getHost())); + req.put("protocol",StringEscapeUtils.escapeJava(reqResItem.getHttpService().getProtocol())); + req.put("port",reqResItem.getHttpService().getPort()); + req.put("path",StringEscapeUtils.escapeJava(path)); + req.put("raw", helpers.base64Encode(reqResItem.getRequest())); + req.put("http-verb",iRequestInfo.getMethod()); + List req_headers=iRequestInfo.getHeaders(); + List req_headers_escaped= new ArrayList(); + for (String header : req_headers){ + req_headers_escaped.add(StringEscapeUtils.escapeJava(header)); + } + req.put("headers",req_headers_escaped); + //Escaped in PESDExporter + req.put("url_params",url_params); + //Escaped in PESDExporter + req.put("body_params",body_params); + //Escaped in PESDExporter + req.put("body_json",body_json); + //Escaped in PESDExporter + req.put("cookies",req_cookies); + //add highlight, comment keys + req.put("highlight",reqResItem.getHighlight()); + req.put("comment", StringEscapeUtils.escapeJava(reqResItem.getComment())); + + item.put("req",req); + + if (!noRes){ + //adding res json key + JSONObject res = new JSONObject(); + + res.put("id",num+1); + res.put("statusCode",iResponseInfo.getStatusCode()); + res.put("raw",helpers.base64Encode(reqResItem.getResponse())); + res.put("Inferred-MimeType",StringEscapeUtils.escapeJava(iResponseInfo.getInferredMimeType())); + res.put("Stated-MimeType",StringEscapeUtils.escapeJava(iResponseInfo.getStatedMimeType())); + if (!CSP.isEmpty()){res.put("CSP",StringEscapeUtils.escapeJava(CSP));} + List res_headers=iResponseInfo.getHeaders(); + List res_headers_escaped= new ArrayList(); + for (String header : res_headers){ + res_headers_escaped.add(StringEscapeUtils.escapeJava(header)); + } + res.put("headers",res_headers_escaped); + + + JSONObject res_cookies_escaped = new JSONObject(); + List iCookie = iResponseInfo.getCookies(); + for (ICookie cookie : iCookie){ + res_cookies_escaped.put(StringEscapeUtils.escapeJava(cookie.getName()),StringEscapeUtils.escapeJava(cookie.getValue())); + } + res.put("cookies",res_cookies_escaped); + item.put("res",res); + } + + return item; + } + +} diff --git a/src/pesd/PESDPanel.form b/src/pesd/PESDPanel.form new file mode 100644 index 0000000..2636ab2 --- /dev/null +++ b/src/pesd/PESDPanel.form @@ -0,0 +1,418 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/pesd/PESDPanel.java b/src/pesd/PESDPanel.java new file mode 100644 index 0000000..4ce8c5b --- /dev/null +++ b/src/pesd/PESDPanel.java @@ -0,0 +1,806 @@ +package pesd; + +import burp.IExtensionHelpers; +import java.io.PrintWriter; +import burp.IBurpExtenderCallbacks; +import burp.IHttpRequestResponse; +import java.awt.Desktop; +import java.net.URI; +import java.util.Arrays; + +import java.io.File; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +/** + * + * @author francesco + */ +public class PESDPanel extends javax.swing.JPanel { + + private final IBurpExtenderCallbacks callbacks; + private final IExtensionHelpers helpers; + public IHttpRequestResponse[] items; + private int operationMode; + private PESDExporter pesdexporter; + private Boolean[] Bools; + private String[] Templates; + private String metadata; + private PrintWriter stdout; + private PrintWriter stderr; + private String[] diagrams; + + public PESDPanel(IBurpExtenderCallbacks callbacks, IExtensionHelpers helpers) { + initComponents(); + this.callbacks = callbacks; + this.helpers = helpers; + this.stdout = new PrintWriter(callbacks.getStdout(), true); + this.stderr = new PrintWriter(callbacks.getStderr(), true); + IHttpRequestResponse iHttpRequestResponse[] = new IHttpRequestResponse[0]; + this.items = iHttpRequestResponse; + this.operationMode = 0; + // Bools index: 0=HasUrlParams, 1=HasBodyParam, 2=HasJsonParam, 3=HasXmlParam, 4=HasMultipartAttr, 5=HasAuthz, 6=Content-type, 7=CookiesSet, 8=HasCORS, 9=HasXFrameOp, 10=HasCSP, 11=HasCookies + Boolean PramBools[] = new Boolean[]{false, false, false, false, false, false, false, false, false, false, false, false}; + this.Bools = PramBools; + this.Templates = new String[]{"SAML_SSO", "OAuth2/OIDC"}; + // hiding checkboxes + HasJsonParam_checkbox.setVisible(false); + HasJsonParam_checkbox.doClick(); + HasAuthz_checkbox.setVisible(false); + HasAuthz_checkbox.doClick(); + HasBodyParam_checkbox.setVisible(false); + HasBodyParam_checkbox.doClick(); + HasCORS_checkbox.setVisible(false); + HasCORS_checkbox.doClick(); + HasCSP_checkbox.setVisible(false); + HasCSP_checkbox.doClick(); + HasCookies_checkbox.setVisible(false); + HasCookies_checkbox.doClick(); + HasMultipartAttr_checkbox.setVisible(false); + HasMultipartAttr_checkbox.doClick(); + HasUrlParams_checkbox.setVisible(false); + HasUrlParams_checkbox.doClick(); + HasXFrameOp_checkbox.setVisible(false); + HasXFrameOp_checkbox.doClick(); + HasXMLParam_checkbox.setVisible(false); + HasXMLParam_checkbox.doClick(); + ContentType_checkbox.setVisible(false); + ContentType_checkbox.doClick(); + CookiesSet_checkbox.setVisible(false); + CookiesSet_checkbox.doClick(); + jSeparator1.setVisible(false); + InRequests_label.setVisible(false); + InResponses_label.setVisible(false); + OAuth2_OIDC_TEMPLATE_checkbox.setVisible(false); + OAuth2_OIDC_TEMPLATE_checkbox.doClick(); + SAML_SSO_TEMPLATE_checkbox.setVisible(false); + SAML_SSO_TEMPLATE_checkbox.doClick(); + autoExport_check.doClick(); + //setting automatically by default all booleans to true via Analysis preset + booleansPresets_ComboBox.setSelectedIndex(0); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + ModeOfOperation = new javax.swing.ButtonGroup(); + PESDExport_button = new javax.swing.JButton(); + jSeparator1 = new javax.swing.JSeparator(); + CleanFlow_button = new javax.swing.JButton(); + itemsCount_label = new javax.swing.JLabel(); + ContentType_checkbox = new javax.swing.JCheckBox(); + CookiesSet_checkbox = new javax.swing.JCheckBox(); + HasJsonParam_checkbox = new javax.swing.JCheckBox(); + HasUrlParams_checkbox = new javax.swing.JCheckBox(); + HasBodyParam_checkbox = new javax.swing.JCheckBox(); + HasXMLParam_checkbox = new javax.swing.JCheckBox(); + HasMultipartAttr_checkbox = new javax.swing.JCheckBox(); + InRequests_label = new javax.swing.JLabel(); + InResponses_label = new javax.swing.JLabel(); + HasAuthz_checkbox = new javax.swing.JCheckBox(); + BooleansPresets_label = new javax.swing.JLabel(); + booleansPresets_ComboBox = new javax.swing.JComboBox<>(); + HasCORS_checkbox = new javax.swing.JCheckBox(); + HasXFrameOp_checkbox = new javax.swing.JCheckBox(); + HasCSP_checkbox = new javax.swing.JCheckBox(); + HasCookies_checkbox = new javax.swing.JCheckBox(); + EditBools_Button = new javax.swing.JButton(); + modeOfOperation_ComboBox = new javax.swing.JComboBox<>(); + ModeOfOperation_label = new javax.swing.JLabel(); + template_label = new javax.swing.JLabel(); + jLabel2 = new javax.swing.JLabel(); + jSeparator2 = new javax.swing.JSeparator(); + autoExport_check = new javax.swing.JCheckBox(); + EditTemplates_Button = new javax.swing.JButton(); + OAuth2_OIDC_TEMPLATE_checkbox = new javax.swing.JCheckBox(); + SAML_SSO_TEMPLATE_checkbox = new javax.swing.JCheckBox(); + + PESDExport_button.setText("PESD Export"); + PESDExport_button.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + PESDExport_buttonActionPerformed(evt); + } + }); + + CleanFlow_button.setText("Clean Flow"); + CleanFlow_button.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + CleanFlow_buttonActionPerformed(evt); + } + }); + + itemsCount_label.setText("Items count : 0"); + + ContentType_checkbox.setText("Content-Type"); + ContentType_checkbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + ContentType_checkboxActionPerformed(evt); + } + }); + + CookiesSet_checkbox.setText("CookiesSet"); + CookiesSet_checkbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + CookiesSet_checkboxActionPerformed(evt); + } + }); + + HasJsonParam_checkbox.setText("HasJsonParam"); + HasJsonParam_checkbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + HasJsonParam_checkboxActionPerformed(evt); + } + }); + + HasUrlParams_checkbox.setText("HasUrlParams"); + HasUrlParams_checkbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + HasUrlParams_checkboxActionPerformed(evt); + } + }); + + HasBodyParam_checkbox.setText("HasBodyParam"); + HasBodyParam_checkbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + HasBodyParam_checkboxActionPerformed(evt); + } + }); + + HasXMLParam_checkbox.setText("HasXMLParam"); + HasXMLParam_checkbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + HasXMLParam_checkboxActionPerformed(evt); + } + }); + + HasMultipartAttr_checkbox.setText("HasMultipartAttr"); + HasMultipartAttr_checkbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + HasMultipartAttr_checkboxActionPerformed(evt); + } + }); + + InRequests_label.setText("In Requests :"); + + InResponses_label.setText("In Responses :"); + + HasAuthz_checkbox.setText("HasAuthz"); + HasAuthz_checkbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + HasAuthz_checkboxActionPerformed(evt); + } + }); + + BooleansPresets_label.setText("Flags Set : "); + + booleansPresets_ComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Analysis", "PoC" })); + booleansPresets_ComboBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + booleansPresets_ComboBoxActionPerformed(evt); + } + }); + + HasCORS_checkbox.setText("HasCORS"); + HasCORS_checkbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + HasCORS_checkboxActionPerformed(evt); + } + }); + + HasXFrameOp_checkbox.setText("HasXFrameOp"); + HasXFrameOp_checkbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + HasXFrameOp_checkboxActionPerformed(evt); + } + }); + + HasCSP_checkbox.setText("HasCSP"); + HasCSP_checkbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + HasCSP_checkboxActionPerformed(evt); + } + }); + + HasCookies_checkbox.setText("HasCookies"); + HasCookies_checkbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + HasCookies_checkboxActionPerformed(evt); + } + }); + + EditBools_Button.setText("Edit set"); + EditBools_Button.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + EditBools_ButtonActionPerformed(evt); + } + }); + + modeOfOperation_ComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Domains as Actors", "Endpoints as Actors" })); + modeOfOperation_ComboBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + modeOfOperation_ComboBoxActionPerformed(evt); + } + }); + + ModeOfOperation_label.setText("Mode of Operation : "); + + template_label.setText("Templates :"); + + jLabel2.setText("Proxy Enriched Sequence Diagrams Exporter"); + + autoExport_check.setText(" Auto-Export"); + autoExport_check.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + autoExport_checkActionPerformed(evt); + } + }); + + EditTemplates_Button.setText("Edit set"); + EditTemplates_Button.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + EditTemplates_ButtonActionPerformed(evt); + } + }); + + OAuth2_OIDC_TEMPLATE_checkbox.setText(" OAuth2 / OIDC"); + OAuth2_OIDC_TEMPLATE_checkbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + OAuth2_OIDC_TEMPLATE_checkboxActionPerformed(evt); + } + }); + + SAML_SSO_TEMPLATE_checkbox.setText(" SAML SSO"); + SAML_SSO_TEMPLATE_checkbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + SAML_SSO_TEMPLATE_checkboxActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(41, 41, 41) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(itemsCount_label) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(ModeOfOperation_label) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(modeOfOperation_ComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(layout.createSequentialGroup() + .addComponent(template_label) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(OAuth2_OIDC_TEMPLATE_checkbox) + .addGroup(layout.createSequentialGroup() + .addComponent(EditTemplates_Button) + .addGap(55, 55, 55) + .addComponent(BooleansPresets_label) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(booleansPresets_ComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(EditBools_Button)) + .addComponent(SAML_SSO_TEMPLATE_checkbox)))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(10, 10, 10) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(HasUrlParams_checkbox) + .addComponent(HasBodyParam_checkbox) + .addComponent(HasJsonParam_checkbox) + .addComponent(HasCookies_checkbox) + .addComponent(InRequests_label)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(HasXMLParam_checkbox) + .addComponent(HasMultipartAttr_checkbox) + .addComponent(HasAuthz_checkbox))) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(InResponses_label) + .addGap(29, 29, 29)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(CookiesSet_checkbox) + .addComponent(ContentType_checkbox) + .addComponent(HasCORS_checkbox)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(HasCSP_checkbox) + .addComponent(HasXFrameOp_checkbox))))) + .addGroup(layout.createSequentialGroup() + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 220, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addComponent(jLabel2) + .addComponent(jSeparator2, javax.swing.GroupLayout.PREFERRED_SIZE, 233, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createSequentialGroup() + .addComponent(PESDExport_button) + .addGap(18, 18, 18) + .addComponent(CleanFlow_button) + .addGap(18, 18, 18) + .addComponent(autoExport_check))) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(24, 24, 24) + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jSeparator2, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(itemsCount_label) + .addGap(9, 9, 9) + .addComponent(InRequests_label) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(HasXMLParam_checkbox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(HasMultipartAttr_checkbox, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(6, 6, 6) + .addComponent(HasAuthz_checkbox, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(77, 77, 77)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(HasCookies_checkbox, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(6, 6, 6) + .addComponent(HasUrlParams_checkbox, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(6, 6, 6) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(modeOfOperation_ComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(ModeOfOperation_label)) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(template_label) + .addComponent(EditTemplates_Button, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(BooleansPresets_label) + .addComponent(booleansPresets_ComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(EditBools_Button, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(6, 6, 6) + .addComponent(OAuth2_OIDC_TEMPLATE_checkbox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(SAML_SSO_TEMPLATE_checkbox)) + .addGroup(layout.createSequentialGroup() + .addComponent(HasBodyParam_checkbox) + .addGap(6, 6, 6) + .addComponent(HasJsonParam_checkbox, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(7, 7, 7) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 8, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(12, 12, 12) + .addComponent(InResponses_label, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(ContentType_checkbox) + .addComponent(HasXFrameOp_checkbox)))))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(HasCSP_checkbox) + .addComponent(CookiesSet_checkbox)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(HasCORS_checkbox) + .addGap(92, 92, 92) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(CleanFlow_button, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(PESDExport_button)) + .addComponent(autoExport_check)) + .addGap(153, 153, 153)) + ); + }// //GEN-END:initComponents + + private void PESDExport_buttonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_PESDExport_buttonActionPerformed + + this.pesdexporter = new PESDExporter(callbacks, helpers, this.items, stdout, stderr, this.operationMode, this.Bools, this.Templates); + this.diagrams = pesdexporter.generatePESD(); + this.metadata = pesdexporter.getMetadata(); + + try { + InputStream page = getClass().getResourceAsStream("/export.html"); + String response = new String(page.readAllBytes(), StandardCharsets.UTF_8); + String[] lines = this.diagrams[0].split("\r\n|\r|\n"); + if (lines.length > 100) { + response = response.replace("TEMPLATEINSERT1", "%% This file contains a Sequence Diagram defined with MermaidJS Markdown syntax (see https://mermaid-js.github.io/)%%\n" + + "%%{init: {'theme': 'base', 'themeVariables': {'actorBorder':'#808486','actorTextColor':'#34343b','primaryColor': '#fa9b35'}}}%%\n" + + "sequenceDiagram\n" + + "PESD->>User: Hey!\n" + + "PESD->>User: If it has more than 50 entries \n" + + "PESD->>User: It is not a flow\n" + + "PESD->>User: It is traffic history!\n" + + "Note right of Browser : Seriously\n" + + "PESD->>User: Try again with less than 50 items"); + response = response.replace("TEMPLATEINSERT2", "{}"); + response = response.replace("TEMPLATEINSERT3", "%% This file contains a Sequence Diagram defined with MermaidJS Markdown syntax (see https://mermaid-js.github.io/)%%\n" + + "%%{init: {'theme': 'base', 'themeVariables': {'actorBorder':'#808486','actorTextColor':'#34343b','primaryColor': '#fa9b35'}}}%%\n" + + "sequenceDiagram\n" + + "PESD->>User: Nothing to mask here!\n" + + "PESD->>User: Flows longer than 50 items are not supported for your own good\n" + + "PESD->>User: Nothing personal\n" + + "PESD->>User: That's it \n" + + "Note right of Browser : Seriously"); + } else { + response = response.replace("TEMPLATEINSERT1", this.diagrams[0].replace("`", "`")); + response = response.replace("TEMPLATEINSERT2", this.metadata.replace("`", "`")); + response = response.replace("TEMPLATEINSERT3", this.diagrams[1].replace("`", "`")); + } + + File temp = File.createTempFile("exports", ".html"); + String path = temp.getAbsolutePath(); + try { + Files.writeString(temp.toPath(), response, StandardCharsets.UTF_8); + // directing the user to the Browser Export Page File + Desktop.getDesktop().open(new File(path)); + } catch (Exception e) { + stdout.println(e); + } + } catch (Exception e) { + stdout.println(e); + } + + // delete current items sent to the extension and set counter to 0 in the UI + IHttpRequestResponse iHttpRequestResponse[] = new IHttpRequestResponse[0]; + this.items = iHttpRequestResponse; + setCountLabel(); + }//GEN-LAST:event_PESDExport_buttonActionPerformed + + private void CleanFlow_buttonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_CleanFlow_buttonActionPerformed + // delete current items sent to the extension and set counter to 0 in the UI + + IHttpRequestResponse iHttpRequestResponse[] = new IHttpRequestResponse[0]; + this.items = iHttpRequestResponse; + setCountLabel(); + }//GEN-LAST:event_CleanFlow_buttonActionPerformed + + private void HasUrlParams_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_HasUrlParams_checkboxActionPerformed + if (HasUrlParams_checkbox.isSelected()) { + this.Bools[0] = true; + } else { + this.Bools[0] = false; + } + }//GEN-LAST:event_HasUrlParams_checkboxActionPerformed + + private void HasBodyParam_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_HasBodyParam_checkboxActionPerformed + if (HasBodyParam_checkbox.isSelected()) { + this.Bools[1] = true; + } else { + this.Bools[1] = false; + } + }//GEN-LAST:event_HasBodyParam_checkboxActionPerformed + + private void HasJsonParam_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_HasJsonParam_checkboxActionPerformed + if (HasJsonParam_checkbox.isSelected()) { + this.Bools[2] = true; + } else { + this.Bools[2] = false; + } + }//GEN-LAST:event_HasJsonParam_checkboxActionPerformed + + private void HasXMLParam_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_HasXMLParam_checkboxActionPerformed + if (HasXMLParam_checkbox.isSelected()) { + this.Bools[3] = true; + } else { + this.Bools[3] = false; + } + }//GEN-LAST:event_HasXMLParam_checkboxActionPerformed + + private void HasMultipartAttr_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_HasMultipartAttr_checkboxActionPerformed + if (HasMultipartAttr_checkbox.isSelected()) { + this.Bools[4] = true; + } else { + this.Bools[4] = false; + } + }//GEN-LAST:event_HasMultipartAttr_checkboxActionPerformed + + private void HasAuthz_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_HasAuthz_checkboxActionPerformed + if (HasAuthz_checkbox.isSelected()) { + this.Bools[5] = true; + } else { + this.Bools[5] = false; + } + }//GEN-LAST:event_HasAuthz_checkboxActionPerformed + + private void ContentType_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_ContentType_checkboxActionPerformed + if (ContentType_checkbox.isSelected()) { + this.Bools[6] = true; + } else { + this.Bools[6] = false; + } + }//GEN-LAST:event_ContentType_checkboxActionPerformed + + private void CookiesSet_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_CookiesSet_checkboxActionPerformed + if (CookiesSet_checkbox.isSelected()) { + this.Bools[7] = true; + } else { + this.Bools[7] = false; + } + }//GEN-LAST:event_CookiesSet_checkboxActionPerformed + + + private void booleansPresets_ComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_booleansPresets_ComboBoxActionPerformed + // select all checkboxes for Booleans if Analysis mode and de-select all if PoC mode + + if (this.booleansPresets_ComboBox.getSelectedItem().toString() == "Analysis") { + if (!HasAuthz_checkbox.isSelected()) { + HasAuthz_checkbox.doClick(); + } + if (!HasMultipartAttr_checkbox.isSelected()) { + HasMultipartAttr_checkbox.doClick(); + } + if (!HasXMLParam_checkbox.isSelected()) { + HasXMLParam_checkbox.doClick(); + } + if (!HasBodyParam_checkbox.isSelected()) { + HasBodyParam_checkbox.doClick(); + } + if (!HasUrlParams_checkbox.isSelected()) { + HasUrlParams_checkbox.doClick(); + } + if (!HasJsonParam_checkbox.isSelected()) { + HasJsonParam_checkbox.doClick(); + } + if (!CookiesSet_checkbox.isSelected()) { + CookiesSet_checkbox.doClick(); + } + if (!ContentType_checkbox.isSelected()) { + ContentType_checkbox.doClick(); + } + if (!HasCORS_checkbox.isSelected()) { + HasCORS_checkbox.doClick(); + } + if (!HasXFrameOp_checkbox.isSelected()) { + HasXFrameOp_checkbox.doClick(); + } + if (!HasCSP_checkbox.isSelected()) { + HasCSP_checkbox.doClick(); + } + if (!HasCookies_checkbox.isSelected()) { + HasCookies_checkbox.doClick(); + } + } else { + if (HasAuthz_checkbox.isSelected()) { + HasAuthz_checkbox.doClick(); + } + if (HasMultipartAttr_checkbox.isSelected()) { + HasMultipartAttr_checkbox.doClick(); + } + if (HasXMLParam_checkbox.isSelected()) { + HasXMLParam_checkbox.doClick(); + } + if (HasBodyParam_checkbox.isSelected()) { + HasBodyParam_checkbox.doClick(); + } + if (HasUrlParams_checkbox.isSelected()) { + HasUrlParams_checkbox.doClick(); + } + if (HasJsonParam_checkbox.isSelected()) { + HasJsonParam_checkbox.doClick(); + } + if (CookiesSet_checkbox.isSelected()) { + CookiesSet_checkbox.doClick(); + } + if (ContentType_checkbox.isSelected()) { + ContentType_checkbox.doClick(); + } + if (HasCORS_checkbox.isSelected()) { + HasCORS_checkbox.doClick(); + } + if (HasXFrameOp_checkbox.isSelected()) { + HasXFrameOp_checkbox.doClick(); + } + if (HasCSP_checkbox.isSelected()) { + HasCSP_checkbox.doClick(); + } + if (HasCookies_checkbox.isSelected()) { + HasCookies_checkbox.doClick(); + } + } + }//GEN-LAST:event_booleansPresets_ComboBoxActionPerformed + + private void HasCORS_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_HasCORS_checkboxActionPerformed + if (HasCORS_checkbox.isSelected()) { + this.Bools[8] = true; + } else { + this.Bools[8] = false; + } + }//GEN-LAST:event_HasCORS_checkboxActionPerformed + + private void HasXFrameOp_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_HasXFrameOp_checkboxActionPerformed + if (HasXFrameOp_checkbox.isSelected()) { + this.Bools[9] = true; + } else { + this.Bools[9] = false; + } + }//GEN-LAST:event_HasXFrameOp_checkboxActionPerformed + + private void HasCSP_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_HasCSP_checkboxActionPerformed + if (HasCSP_checkbox.isSelected()) { + this.Bools[10] = true; + } else { + this.Bools[10] = false; + } + }//GEN-LAST:event_HasCSP_checkboxActionPerformed + + private void HasCookies_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_HasCookies_checkboxActionPerformed + if (HasCookies_checkbox.isSelected()) { + this.Bools[11] = true; + } else { + this.Bools[11] = false; + } + }//GEN-LAST:event_HasCookies_checkboxActionPerformed + + private void EditBools_ButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_EditBools_ButtonActionPerformed + //Hide/Show Booleans checkboxes + + if (HasJsonParam_checkbox.isVisible()) { + HasJsonParam_checkbox.setVisible(false); + HasAuthz_checkbox.setVisible(false); + HasBodyParam_checkbox.setVisible(false); + HasCORS_checkbox.setVisible(false); + HasCSP_checkbox.setVisible(false); + HasCookies_checkbox.setVisible(false); + HasMultipartAttr_checkbox.setVisible(false); + HasUrlParams_checkbox.setVisible(false); + HasXFrameOp_checkbox.setVisible(false); + HasXMLParam_checkbox.setVisible(false); + ContentType_checkbox.setVisible(false); + CookiesSet_checkbox.setVisible(false); + jSeparator1.setVisible(false); + InRequests_label.setVisible(false); + InResponses_label.setVisible(false); + } else { + HasJsonParam_checkbox.setVisible(true); + HasAuthz_checkbox.setVisible(true); + HasBodyParam_checkbox.setVisible(true); + HasCORS_checkbox.setVisible(true); + HasCSP_checkbox.setVisible(true); + HasCookies_checkbox.setVisible(true); + HasMultipartAttr_checkbox.setVisible(true); + HasUrlParams_checkbox.setVisible(true); + HasXFrameOp_checkbox.setVisible(true); + HasXMLParam_checkbox.setVisible(true); + ContentType_checkbox.setVisible(true); + CookiesSet_checkbox.setVisible(true); + jSeparator1.setVisible(true); + InRequests_label.setVisible(true); + InResponses_label.setVisible(true); + } + }//GEN-LAST:event_EditBools_ButtonActionPerformed + + private void modeOfOperation_ComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_modeOfOperation_ComboBoxActionPerformed + + if (this.modeOfOperation_ComboBox.getSelectedItem().toString() == "Domains as Actors") { + this.operationMode = 0; + } else { + this.operationMode = 1; + } + }//GEN-LAST:event_modeOfOperation_ComboBoxActionPerformed + + private void autoExport_checkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_autoExport_checkActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_autoExport_checkActionPerformed + + private void EditTemplates_ButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_EditTemplates_ButtonActionPerformed + //Hide/Show Booleans checkboxes + + if (SAML_SSO_TEMPLATE_checkbox.isVisible()) { + SAML_SSO_TEMPLATE_checkbox.setVisible(false); + OAuth2_OIDC_TEMPLATE_checkbox.setVisible(false); + } else { + SAML_SSO_TEMPLATE_checkbox.setVisible(true); + OAuth2_OIDC_TEMPLATE_checkbox.setVisible(true); + } + }//GEN-LAST:event_EditTemplates_ButtonActionPerformed + + private void SAML_SSO_TEMPLATE_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_SAML_SSO_TEMPLATE_checkboxActionPerformed + if (SAML_SSO_TEMPLATE_checkbox.isSelected()) { + this.Templates[0] = "SAML_SSO"; + } else { + this.Templates[0] = ""; + } + }//GEN-LAST:event_SAML_SSO_TEMPLATE_checkboxActionPerformed + + private void OAuth2_OIDC_TEMPLATE_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_OAuth2_OIDC_TEMPLATE_checkboxActionPerformed + if (OAuth2_OIDC_TEMPLATE_checkbox.isSelected()) { + this.Templates[1] = "OAuth2/OIDC"; + } else { + this.Templates[1] = ""; + } + }//GEN-LAST:event_OAuth2_OIDC_TEMPLATE_checkboxActionPerformed + + public int setItems(IHttpRequestResponse[] newItems, Integer opMode) { + // called from BurpExtender.java to set selected items and operation mode in the panel + try { + int aLen = this.items.length; + int bLen = newItems.length; + + // merging new selected items with previously selected items + IHttpRequestResponse[] c = Arrays.copyOf(this.items, aLen + bLen); + System.arraycopy(this.items, 0, c, 0, aLen); + System.arraycopy(newItems, 0, c, aLen, bLen); + this.items = c; + //updating Mode Of Operation combobox and op. mode attribute + this.modeOfOperation_ComboBox.setSelectedIndex(opMode); + } catch (Exception e) { + return 0; + } + return 1; + } + + public void setCountLabel() { + itemsCount_label.setText("Items count : " + this.items.length); + } + + public Boolean getAutoExport() { + return this.autoExport_check.isSelected(); + } + + public void clickPESDExport_button() { + this.PESDExport_button.doClick(); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel BooleansPresets_label; + private javax.swing.JButton CleanFlow_button; + private javax.swing.JCheckBox ContentType_checkbox; + private javax.swing.JCheckBox CookiesSet_checkbox; + private javax.swing.JButton EditBools_Button; + private javax.swing.JButton EditTemplates_Button; + private javax.swing.JCheckBox HasAuthz_checkbox; + private javax.swing.JCheckBox HasBodyParam_checkbox; + private javax.swing.JCheckBox HasCORS_checkbox; + private javax.swing.JCheckBox HasCSP_checkbox; + private javax.swing.JCheckBox HasCookies_checkbox; + private javax.swing.JCheckBox HasJsonParam_checkbox; + private javax.swing.JCheckBox HasMultipartAttr_checkbox; + private javax.swing.JCheckBox HasUrlParams_checkbox; + private javax.swing.JCheckBox HasXFrameOp_checkbox; + private javax.swing.JCheckBox HasXMLParam_checkbox; + private javax.swing.JLabel InRequests_label; + private javax.swing.JLabel InResponses_label; + private javax.swing.ButtonGroup ModeOfOperation; + private javax.swing.JLabel ModeOfOperation_label; + private javax.swing.JCheckBox OAuth2_OIDC_TEMPLATE_checkbox; + private javax.swing.JButton PESDExport_button; + private javax.swing.JCheckBox SAML_SSO_TEMPLATE_checkbox; + private javax.swing.JCheckBox autoExport_check; + private javax.swing.JComboBox booleansPresets_ComboBox; + private javax.swing.JLabel itemsCount_label; + private javax.swing.JLabel jLabel2; + private javax.swing.JSeparator jSeparator1; + private javax.swing.JSeparator jSeparator2; + private javax.swing.JComboBox modeOfOperation_ComboBox; + private javax.swing.JLabel template_label; + // End of variables declaration//GEN-END:variables +} diff --git a/src/pesd/PESDWrapper.java b/src/pesd/PESDWrapper.java new file mode 100644 index 0000000..82254e1 --- /dev/null +++ b/src/pesd/PESDWrapper.java @@ -0,0 +1,647 @@ +/* + * Proxy Enriched Sequence Diagrams Wrapper + * This Object exposes all the needed methods to create / access / manipulate PESD: MD + Metadata + * + * @author Francesco Lacerenza from Doyensec + */ +package pesd; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; +import org.json.JSONObject; +import org.json.JSONArray; + +public class PESDWrapper { + + private JSONArray data; + private int iterator; + private boolean iterating; + private HashMap mappings; + private final PrintWriter stdout; + + public PESDWrapper(PrintWriter stdout) { + // main constructor + this.stdout = stdout; + try { + this.data = new JSONArray(); + this.iterator = 0; + this.mappings = new HashMap<>(); + this.iterating = false; + } catch (Exception e) { + e.printStackTrace(); + } + } + + public PESDWrapper(PrintWriter stdout, JSONArray data) { + // Constructor usable to create copies of a PESD Object, + // simply use getStucturedData() to extract JSONArray data and pass it to the constructor to create a clone + this.data = data; + this.stdout = stdout; + this.iterator = 0; + this.mappings = new HashMap<>(); + this.iterating = false; + } + + public String[] getSeqDiagramdMD(Integer op_mode) { + // This method serializes the data JSONArray to MermaidJS Markdown syntax + + // the Wrapper automatically checks and fixes the consistency of alt/end markdown symbols before exporting the MD + // This is done to avoid garbage alt / end symbols from templates edge-cases that could break the MermaidJS Rendering of the MD + this.altEndConsistencyNormalizer(); + //Note about op_mode: + // 0 <- Domains as Actors + // 1 <- Endpoints as Actors + String seq_diagram_md = "%% This file contains a Sequence Diagram defined with MermaidJS Markdown syntax (see https://mermaid-js.github.io/)%%\n%%{init: {'theme': 'base', 'themeVariables': {'actorBorder':'#808486','actorTextColor':'#34343b','primaryColor': '#fa9b35'}}}%%\nsequenceDiagram\n"; + String seq_diagram_md_masked = "%% This file contains a Sequence Diagram defined with MermaidJS Markdown syntax (see https://mermaid-js.github.io/)%%\n%%{init: {'theme': 'base', 'themeVariables': {'actorBorder':'#808486','actorTextColor':'#34343b','primaryColor': '#fa9b35'}}}%%\nsequenceDiagram\n"; + this.startIterator(); + try { + while (this.iterator != -1) { + JSONObject line = this.getLine(); + String flags; + switch ((String) line.get("type")) { + case "req": + flags = this.flagsToStr((JSONArray) line.get("flags")); + if (op_mode == 0) { + seq_diagram_md += String.format("%s->>%s: [%s] %s
%s\n", line.get("source"), line.get("destination"), line.get("method"), escapeMermaid(line.get("path").toString()), flags); + seq_diagram_md_masked += String.format("%s->>%s: [%s] %s
%s\n", line.get("source"), line.get("destination"), line.get("method"), escapeMermaid(line.get("maskedPath").toString()), flags); + } else { + seq_diagram_md += String.format("%s->>%s: [%s] %s\n", line.get("source"), escapeMermaid(line.get("path").toString()), line.get("method"), flags); + seq_diagram_md_masked += String.format("%s->>%s: [%s] %s\n", line.get("source"), escapeMermaid(line.get("maskedPath").toString()).replaceAll("<", "_").replaceAll(">", "_"), line.get("method"), flags); + } + break; + case "res": + flags = this.flagsToStr((JSONArray) line.get("flags")); + if (line.get("mime_type") == "NoNe") { + if (op_mode == 0) { + seq_diagram_md += String.format("%s->>%s: [%s] %s\n", line.get("source"), line.get("destination"), line.get("status_code"), flags); + seq_diagram_md_masked += String.format("%s->>%s: [%s] %s\n", line.get("source"), line.get("destination"), line.get("status_code"), flags); + } else if (this.getLine(this.iterator - 1).get("type") != "req") { + if (this.getLine(this.iterator - 2).get("type") != "req"){ + seq_diagram_md += String.format("%s->>%s: [%s] %s\n", escapeMermaid(this.getLine(this.iterator - 3).get("path").toString()), line.get("destination"), line.get("status_code"), flags); + seq_diagram_md_masked += String.format("%s->>%s: [%s] %s\n", escapeMermaid(this.getLine(this.iterator - 3).get("maskedPath").toString()).replaceAll("<", "_").replaceAll(">", "_"), line.get("destination"), line.get("status_code"), flags); + } else { + seq_diagram_md += String.format("%s->>%s: [%s] %s\n", escapeMermaid(this.getLine(this.iterator - 2).get("path").toString()), line.get("destination"), line.get("status_code"), flags); + seq_diagram_md_masked += String.format("%s->>%s: [%s] %s\n", escapeMermaid(this.getLine(this.iterator - 2).get("maskedPath").toString()).replaceAll("<", "_").replaceAll(">", "_"), line.get("destination"), line.get("status_code"), flags); + } + } else { + seq_diagram_md += String.format("%s->>%s: [%s] %s\n", escapeMermaid(this.getLine(this.iterator - 1).get("path").toString()), line.get("destination"), line.get("status_code"), flags); + seq_diagram_md_masked += String.format("%s->>%s: [%s] %s\n", escapeMermaid(this.getLine(this.iterator - 1).get("maskedPath").toString()).replaceAll("<", "_").replaceAll(">", "_"), line.get("destination"), line.get("status_code"), flags); + } + } else { + if (op_mode == 0) { + seq_diagram_md += String.format("%s->>%s: [%s] %s
%s\n", line.get("source"), line.get("destination"), line.get("status_code"), line.get("mime_type"), flags); + seq_diagram_md_masked += String.format("%s->>%s: [%s] %s
%s\n", line.get("source"), line.get("destination"), line.get("status_code"), line.get("mime_type"), flags); + } else if (this.getLine(this.iterator - 1).get("type") != "req") { + if (this.getLine(this.iterator - 2).get("type") != "req"){ + seq_diagram_md += String.format("%s->>%s: [%s] %s
%s\n", escapeMermaid(this.getLine(this.iterator - 3).get("path").toString()), line.get("destination"), line.get("status_code"), line.get("mime_type"), flags); + seq_diagram_md_masked += String.format("%s->>%s: [%s] %s
%s\n", escapeMermaid(this.getLine(this.iterator - 3).get("maskedPath").toString()).replaceAll("<", "_").replaceAll(">", "_"), line.get("destination"), line.get("status_code"), line.get("mime_type"), flags); + } else { + seq_diagram_md += String.format("%s->>%s: [%s] %s
%s\n", escapeMermaid(this.getLine(this.iterator - 2).get("path").toString()), line.get("destination"), line.get("status_code"), line.get("mime_type"), flags); + seq_diagram_md_masked += String.format("%s->>%s: [%s] %s
%s\n", escapeMermaid(this.getLine(this.iterator - 2).get("maskedPath").toString()).replaceAll("<", "_").replaceAll(">", "_"), line.get("destination"), line.get("status_code"), line.get("mime_type"), flags); + } + } else { + seq_diagram_md += String.format("%s->>%s: [%s] %s
%s\n", escapeMermaid(this.getLine(this.iterator - 1).get("path").toString()), line.get("destination"), line.get("status_code"), line.get("mime_type"), flags); + seq_diagram_md_masked += String.format("%s->>%s: [%s] %s
%s\n", escapeMermaid(this.getLine(this.iterator - 1).get("maskedPath").toString()).replaceAll("<", "_").replaceAll(">", "_"), line.get("destination"), line.get("status_code"), line.get("mime_type"), flags); + } + + } + break; + case "alt": + seq_diagram_md += String.format("Alt %s\n", line.get("alt")); + seq_diagram_md_masked += String.format("Alt %s\n", line.get("alt")); + break; + case "end": + seq_diagram_md += "end\n"; + seq_diagram_md_masked += "end\n"; + break; + case "note": + seq_diagram_md += String.format("Note right of Browser : %s\n", line.get("note")); + seq_diagram_md_masked += String.format("Note right of Browser : %s\n", line.get("note")); + break; + default: + break; + } + this.nextLine(); + } + } catch (Exception e) { + this.stdout.println("error : "+e.toString()); + } + return new String[]{seq_diagram_md, seq_diagram_md_masked}; + } + + private String flagsToStr(JSONArray flagsArr) { + // serialize flags array to a string that will be added to the transaction in the MD + String flags = ""; + for (int i = 0; i < flagsArr.length(); i++) { + flags += String.format(" %s ", flagsArr.getString(i)); + } + return flags; + } + + public JSONObject getMetadata() { + // Creating metadata export with PESD format + + JSONObject metadata = new JSONObject(); + Integer item_num = 1; + JSONObject item; + this.startIterator(); + while (this.iterator != -1) { + try { + item = new JSONObject(); + if ((String) this.getLine().get("type") == "req") { + JSONObject line_metadata = (JSONObject) this.getLine().get("metadata"); + item.put("req", line_metadata); + if ((String) this.getLine(this.iterator + 1).get("type") == "res") { + item.put("res", (JSONObject) this.getLine(this.iterator + 1).get("metadata")); + this.setIterator(this.iterator + 2); + } else if ((String) this.getLine(this.iterator + 2).get("type") == "res" && (String) this.getLine(this.iterator + 1).get("type") != "req"){ + item.put("res", (JSONObject) this.getLine(this.iterator + 2).get("metadata")); + this.setIterator(this.iterator + 3); + } else if((String) this.getLine(this.iterator + 3).get("type") == "res" && (String) this.getLine(this.iterator + 2).get("type") != "req" && (String) this.getLine(this.iterator + 1).get("type") != "req"){ + item.put("res", (JSONObject) this.getLine(this.iterator + 3).get("metadata")); + this.setIterator(this.iterator + 4); + } else { + this.nextLine(); + } + metadata.put("item" + item_num, item); + item_num++; + } else { + this.nextLine(); + } + } catch (Exception e) { + this.nextLine(); + } + } + return metadata; + } + + public JSONObject getLine() { + return (JSONObject) this.data.get(this.iterator); + } + + public JSONObject getLine(Integer index) { + if (!(index > (this.data.length() - 1))) { + return (JSONObject) this.data.get(index); + } else { + JSONObject errorLine = new JSONObject(); + errorLine.put("type", "error"); + return errorLine; + } + } + + public Integer getIterator() { + return this.iterator; + } + + public void setIterator(Integer num) { + if (!(num > (this.data.length() - 1))) { + this.iterator = num; + } else { + this.iterator = -1; + } + } + + public void startIterator() { + this.iterator = 0; + this.iterating = true; + } + + public void startIterator(Integer num) { + this.iterator = num; + this.iterating = true; + } + + public void nextLine() { + if ((this.iterator + 1) > (this.data.length() - 1)) { + this.iterator = -1; + this.iterating = false; + } else { + this.iterator++; + } + } + + public void prevLine() { + if (this.iterator > 0) { + this.iterator--; + } + } + + public void addReq(String source, String destination, String method, String path, List flags, JSONObject metadata) { + // Method used to add a Request line to the obj that will have the format: + // Markdown : S ->> D : [METHOD] /path \n Flag1 Flag2 ... FlagN + // Metadata: JSONObject generated by PESDMetadata obj + + JSONObject line = new JSONObject(); + line.put("type", "req"); + line.put("source", source); + line.put("destination", destination); + line.put("method", method); + line.put("path", path); + line.put("flags", flags); + String maskedPath = pathMapper(path); + metadata.put("maskedPath", maskedPath); + line.put("maskedPath", maskedPath); + line.put("metadata", metadata); + this.data.put(line); + } + + public void addReq(String source, String destination, String method, String path, List flags, JSONObject metadata, Integer index) { + // Same as normal addReq, but to a specific index + + JSONObject line = new JSONObject(); + line.put("type", "req"); + line.put("source", source); + line.put("destination", destination); + line.put("method", method); + line.put("path", path); + line.put("flags", flags); + String maskedPath = pathMapper(path); + metadata.put("maskedPath", maskedPath); + line.put("metadata", metadata); + addToPos(index, line); + + if (this.iterating & index <= this.iterator) { + this.iterator++; + } + } + + public void addRes(String source, String destination, String status_code, String mimeType, List flags, JSONObject metadata) { + // Method used to add a Response line to the obj that will have the format: + // Markdown : D ->> S : [STATUS_CODE] MIME_TYPE \n Flag1 Flag2 ... FlagN + // Metadata: JSONObject generated by PESDMetadata obj + + JSONObject line = new JSONObject(); + line.put("type", "res"); + line.put("source", source); + line.put("destination", destination); + line.put("status_code", status_code); + line.put("mime_type", mimeType); + line.put("flags", flags); + line.put("metadata", metadata); + this.data.put(line); + } + + public void addRes(String source, String destination, String status_code, String mimeType, List flags, JSONObject metadata, Integer index) { + // Same as normal addRes, but to a specific index + + JSONObject line = new JSONObject(); + line.put("type", "res"); + line.put("source", source); + line.put("destination", destination); + line.put("status_code", status_code); + line.put("mime_type", mimeType); + line.put("flags", flags); + line.put("metadata", metadata); + addToPos(index, line); + + if (this.iterating & index <= this.iterator) { + this.iterator++; + } + } + + public void addAlt(String text) { + // Method used to add a Alt line (rectangle start in MermaidJS MD) to the obj that will have the format : + // Markdown : Alt NAME + + JSONObject line = new JSONObject(); + line.put("type", "alt"); + line.put("alt", text); + this.data.put(line); + } + + public void addAlt(String text, Integer index) { + // Same as normal addAlt, but to a specific index + + JSONObject line = new JSONObject(); + line.put("type", "alt"); + line.put("alt", text); + addToPos(index, line); + + if (this.iterating & index <= this.iterator) { + this.iterator++; + } + } + + public void endAlt() { + // Method used to add a end line (rectangle close in MermaidJS MD) to the obj that will have the format : + // Markdown : end + + JSONObject line = new JSONObject(); + line.put("type", "end"); + this.data.put(line); + } + + public void endAlt(Integer index) { + // Same as normal endAlt, but to a specific index + JSONObject line = new JSONObject(); + line.put("type", "end"); + addToPos(index, line); + + if (this.iterating & index <= this.iterator) { + this.iterator++; + } + } + + public void addNote(String note) { + // Method used to add a Note line to the obj that will have the format: + // Markdown : Note right of Browser : TEXT + + JSONObject line = new JSONObject(); + line.put("type", "note"); + line.put("note", note); + this.data.put(line); + } + + public void addNote(String note, Integer index) { + // Same as normal addNote, but to a specific index + + JSONObject line = new JSONObject(); + line.put("type", "note"); + line.put("note", note); + addToPos(index, line); + + if (this.iterating & index <= this.iterator) { + this.iterator++; + } + } + + public void modifyFlags(List flagsArr) { + // substituting flags in a transaction during an iteration + + if (this.iterating) { + JSONObject line = (JSONObject) this.data.get(this.iterator); + line.remove("flags"); + line.put("flags", flagsArr); + } + } + + public void modifyFlags(List flagsArr, Integer index) { + // Same as normal modifyFlags, but to a specific index + + JSONObject line = (JSONObject) this.data.get(index); + line.remove("flags"); + line.put("flags", flagsArr); + } + + public void addFlag(String flag) { + // adding a flag to a transaction while iterating + + JSONArray flags = (JSONArray) this.getLine().get("flags"); + List list = new ArrayList(); + for (int i = 0; i < flags.length(); i++) { + list.add(flags.getString(i)); + } + list.add(String.format(" %s ", flag)); + this.modifyFlags(list); + } + + public void addFlag(String flag, Integer index) { + // Same as normal addFlag, but to a specific index + + JSONArray flags = (JSONArray) this.getLine(index).get("flags"); + List list = new ArrayList(); + for (int i = 0; i < flags.length(); i++) { + list.add(flags.getString(i)); + } + list.add(String.format(" %s ", flag)); + this.modifyFlags(list); + } + + public void modifyMetadata(JSONObject metadataObj) { + // Replacing a metadata object in a line with new metadata while iterating + + if (this.iterating) { + JSONObject line = (JSONObject) this.data.get(this.iterator); + line.remove("metadata"); + line.put("metadata", metadataObj); + } + } + + public void modifyMetadata(JSONObject metadataObj, Integer index) { + // Same as normal modifyMetadata, but to a specific index + + JSONObject line = (JSONObject) this.data.get(index); + line.remove("metadata"); + line.put("metadata", metadataObj); + } + + public void removeLine() { + // remove line while iterating + + if (this.iterating) { + this.data.remove(this.iterator); + this.iterator--; + } + } + + public void removeLine(Integer index) { + //remove line on at a specific index + + JSONObject a = (JSONObject) this.data.remove(index); + if (this.iterating & index <= this.iterator) { + this.iterator--; + } + } + + public Boolean altEndConsistencyNormalizer() { + // This method is used to resolve local consistency problems within the resulting MermaidJS Markdown before the serialization + // Currently it simply remove non-closed and non-opened Alt rectangles (Alt without end or end without Alt) + // Moreover, in case of 2 consecutive Alt, the first occurrence is removed + + try { + this.startIterator(); + Integer lastAlt = -1; + while (this.getIterator() != -1) { + switch (this.getLine().getString("type")) { + case "alt": + if (lastAlt != -1) { + this.removeLine(lastAlt); + lastAlt = this.getIterator(); + } else { + lastAlt = this.getIterator(); + } + break; + case "end": + if (lastAlt != -1) { + lastAlt = -1; + } else { + this.removeLine(); + } + break; + default: + break; + } + this.nextLine(); + } + if (lastAlt != -1) { + this.removeLine(lastAlt); + } + } catch (Exception e) { + stdout.println("Error:" + e.toString()); + return false; + } + return true; + } + + public void addToPos(int pos, JSONObject jsonObj) { + // Function needed to perform JSONArray.put(pos, json) without losing an entry due to replacement. + // Basically it performs right-shift of all the entries after the position needed for the put + + for (int i = this.data.length(); i > pos; i--) { + this.data.put(i, this.data.get(i - 1)); + } + this.data.put(pos, jsonObj); + } + + public Integer getSize() { + return this.data.length(); + } + + public JSONArray getStucturedData() { + // returns a copy by value of the data contained in the wrapper + + return new JSONArray(this.data); + } + + public String escapeMermaid(String input) { + + String[] segments = input.split("/"); + if (segments.length == 0) { + return "/"; + } + for (int i = 0; i < segments.length; i++) { + if (!segments[i].matches("^") && !segments[i].matches("^")) { + segments[i] = java.net.URLEncoder.encode(segments[i]); + } + } + return String.join("/", segments); + } + + public String pathMapper(String path) { + + String[] segments = path.split("/"); + if (segments.length == 0) { + return "/"; + } + + for (int i = 0; i < segments.length; i++) { + if (uuidTest(segments[i]) || maskTest(segments[i])) { + if (!this.mappings.containsKey(segments[i])) { + this.mappings.put(segments[i], this.mappings.size() + 1); + segments[i] = substituteMask(segments[i]); + } else { + segments[i] = substituteMask(segments[i]); + } + } + + } + return String.join("/", segments); + } + + public String substituteMask(String segment) { + if (uuidTest(segment)) { + return String.format("", this.mappings.get(segment)); + } else { + return String.format("", this.mappings.get(segment)); + } + } + + public Boolean uuidTest(String input) { + try { + UUID uuid = UUID.fromString(input); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + public Boolean maskTest(String input) { + // A path segment is a part in a path separated by / + // So it’s basically A–Z, a–z, 0–9, -, ., _, ~, !, $, &, ', (, ), *, +, ,, ;, =, :, @, as well as % that must be followed by two hexadecimal digits. Any other character/byte needs to be encoded using the percent-encoding. + //Although these are 79 characters in total that can be used in a path segment literally, some user agents do encode some of these characters as well (e.g. %7E instead of ~). + // That’s why many use just the 62 alphanumeric characters (i.e. A–Z, a–z, 0–9) or the Base 64 Encoding with URL and Filename Safe Alphabet (i.e. A–Z, a–z, 0–9, -, _). + int numChars = 0; + int upper = 0, lower = 0, number = 0, special = 0; + + for (int i = 0; i < input.length(); i++) { + char ch = input.charAt(i); + if (ch >= 'A' && ch <= 'Z') { + upper++; + } else if (ch >= 'a' && ch <= 'z') { + lower++; + } else if (ch >= '0' && ch <= '9') { + number++; + } else { + special++; + } + } + if (upper > 0) { + numChars = numChars + 26; + } + if (lower > 0) { + numChars = numChars + 26; + } + if (special > 0) { + numChars = numChars + 5; + } + // Calculate the Shannon entropy of the input string + + HashMap frequencyMap = new HashMap<>(); + + // Iterate through each character in the input string + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + if (!frequencyMap.containsKey(c)) { + // If the character is not in the map, add it with a frequency of 1 + frequencyMap.put(c, 1); + } else { + // If the character is already in the map, increment its frequency + frequencyMap.put(c, frequencyMap.get(c) + 1); + } + } + + // Initialize the entropy value to 0 + double entropy = 0; + + // Iterate through each character in the frequency map + for (char c : frequencyMap.keySet()) { + // Calculate the probability of the character in the input string + double probability = (double) frequencyMap.get(c) / input.length(); + + // Add the contribution of the character to the entropy + entropy -= probability * (Math.log(probability) / Math.log(2)); + } + + // Calculate the maximum entropy for a string of the same length and character set + double maxEntropy = Math.log(numChars) / Math.log(2); + + // Compare the calculated entropy to the maximum entropy + double entropyRatio = entropy / maxEntropy; + + // If the entropy ratio is close to 1, the input string is considered random + if (entropyRatio >= 0.90) { + return true; + } + + if (input.length() > 18 && entropyRatio > 0.80) { + if (number > 0 && special > 0) { + return true; + } + } + + if (input.length() > 25 && number > 0 && (upper > 0 || special > 0) && entropyRatio > 0.70) { + return true; + } + + if (input.length() > 35) { + return true; + } + + return false; + + } + +} diff --git a/src/pesd/SAML_SSO_Template.java b/src/pesd/SAML_SSO_Template.java new file mode 100644 index 0000000..feaf9e5 --- /dev/null +++ b/src/pesd/SAML_SSO_Template.java @@ -0,0 +1,95 @@ +/* + * This template is based on the following specification documents: + * http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0-cd-02.html#5.1.Web%20Browser%20SSO%20Profile|outline + * iin particular on section "5.1 Web Browser SSO Profile" + * The section describes the typical flows likely to be used with the web browser SSO profile of SAML V2.0. + * message flows involved in web SSO exchanges for the following use case scenarios: + * - SP-initiated SSO using a Redirect Binding for the SP-to-IdP message and a POST Binding for the IdP-to-SP message + * - SP-initiated SSO using a POST Binding for the message and an Artifact Binding for the message + * - IDP-initiated SSO using a POST Binding for the IdP-to-SP message; no SP-to-IdP message is involved. + */ +package pesd; + +import java.io.PrintWriter; +import org.json.JSONObject; + +public class SAML_SSO_Template extends TemplateParent{ + + public SAML_SSO_Template( PESDWrapper pesdObj, PrintWriter stdout) { + super(pesdObj, stdout); + } + + @Override + public PESDWrapper run(){ + pesdObj.startIterator(); + // this boolean var will be set to false if a SAML request is found + // that because IdP initiated SAML SSO are based on unsolicited SAML response directly sent to the SP from the IdP via HTML form Post + Boolean is_IdP_Initiated = true; + + while (pesdObj.getIterator() != -1) { + JSONObject line = pesdObj.getLine(); + JSONObject metadata; + JSONObject urlParams; + + switch((String) line.get("type")){ + case "req": + try { + if (line.get("method") == "GET"){ + metadata = (JSONObject) line.get("metadata"); + urlParams = (JSONObject) metadata.get("url_params"); + + // SP-Initiated SSO with Redirect and POST Bindings + // // Identifying the SAML Request for SP-Initiated flow with redirect-Post bindings (flow's start) + if (urlParams.has("SAMLRequest")){ + is_IdP_Initiated = false; + pesdObj.addFlag("SAMLRequest"); + pesdObj.addAlt("SAML_SSO_SPI_Redirect_Post", pesdObj.getIterator()-2); + + } else if (urlParams.has("SAMLResponse")) { + // Identifying the SAML Response for SP-Initiated flow with Post-Artifact bindings (flow's end) + pesdObj.addFlag("SAMLResponse"); + pesdObj.endAlt(pesdObj.getIterator()+2); + } + + if (urlParams.has("RelayState")){ + pesdObj.addFlag("RelayState"); + } + } + } catch(Exception e) { + stdout.println(e); + } + + try { + if (line.get("method") == "POST"){ + metadata = (JSONObject) line.get("metadata"); + JSONObject bodyParams = (JSONObject) metadata.get("body_params"); + if (bodyParams.has("RelayState")){ + pesdObj.addFlag("RelayState"); + } + if (bodyParams.has("SAMLResponse")){ + if (is_IdP_Initiated) { + // if is_IdP_Initiated is true and we find a SAML Response, it means that we have an IdP initiated flow + pesdObj.addAlt("SAML_SSO_IdP_initiated", pesdObj.getIterator()-2); + } + // Identifying the SAML Response for SP-Initiated flow with redirect-Post and Post-Artifact bindings (flow's end) + pesdObj.addFlag("SAMLResponse"); + pesdObj.endAlt(pesdObj.getIterator()+2); + } else if (bodyParams.has("SAMLRequest")) { + // Identifying the SAML Request for SP-Initiated flow with POST-Artifact Binding (flow's start) + is_IdP_Initiated = false; + pesdObj.addFlag("SAMLRequest"); + pesdObj.addAlt("SAML_SSO_SPI__Post_Artifact", pesdObj.getIterator()-2); + } + } + } catch(Exception e) { + stdout.println(e); + } + break; + + default: break; + } + pesdObj.nextLine(); + } + return this.pesdObj; + } +} diff --git a/src/pesd/TemplateParent.java b/src/pesd/TemplateParent.java new file mode 100644 index 0000000..26d1dee --- /dev/null +++ b/src/pesd/TemplateParent.java @@ -0,0 +1,27 @@ +package pesd; + +import java.io.PrintWriter; + +// This is the PESD template parent class +// +// In order to write new templates to match your needs you have to create a template class which extends this one +// and overrides the run method. +// + + +public class TemplateParent { + public PrintWriter stdout; + public PESDWrapper pesdObj; + + public TemplateParent(PESDWrapper pesdObj, PrintWriter stdout) { + // PESD Template required vars for the constructor + this.stdout = stdout; + this.pesdObj = pesdObj; + } + + public PESDWrapper run(){ + //Override This + return this.pesdObj; + } + +} diff --git a/src/pesd/TemplateRunner.java b/src/pesd/TemplateRunner.java new file mode 100644 index 0000000..c942446 --- /dev/null +++ b/src/pesd/TemplateRunner.java @@ -0,0 +1,139 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package pesd; + +import java.io.PrintWriter; +import org.json.JSONObject; + +/** + * + * @author francesco lacerenza + */ +public class TemplateRunner { + + private PESDWrapper pesdObj; + private final PrintWriter stdout; + private TemplateParent template; + private PESDWrapper[] templates_results; + + public TemplateRunner(String[] templates, PESDWrapper pesdObj, PrintWriter stdout) { + this.stdout = stdout; + this.pesdObj = pesdObj; + //initializing copies (by value) of the exported pesdObj for the templates + this.templates_results = new PESDWrapper[templates.length]; + for (int i = 0; i < templates.length; i++) { + this.templates_results[i] = new PESDWrapper(this.stdout, pesdObj.getStucturedData()); + } + + Integer i = 0; + for (String template : templates) { + switch (template) { + case "OAuth2/OIDC": + this.template = new Oauth2_OpenID_Template(this.templates_results[i], stdout); + break; + case "SAML_SSO": + this.template = new SAML_SSO_Template(this.templates_results[i], stdout); + break; + default: + this.template = null; + break; + } + if (this.template != null) { + this.run(template, i); + i++; + } + } + // At this stage we have templates_result containing the normalized pesd objs for each template + // we need to merge them in a final obj + try { + if (this.templates_results.length > 0) { + for (PESDWrapper result : this.templates_results) { + this.pesdObj = mergeResults(this.pesdObj, result); + } + } + } catch (Exception e) { + stdout.println(e); + } + } + + public void run(String template, Integer i) { + try { + // executing the template i and saving its output + this.templates_results[i] = this.template.run(); + // Before exiting we need to run altEndConsistencyNormalizer to solve local inconsistencies in the template resulting pesd obj + // This is done to prevent MermaidJS rendering failure. Sometimes edge-cases within the templates execution may result + // in Alt symbols without end symbols, viceversa or even mixed inconsistencies like two alt in a row. + // This may happen due to non-standard flow implementation or other causes. + // altEndConsistencyNormalizer removes such possible inconsistencies in the local result of the template. + this.templates_results[i].altEndConsistencyNormalizer(); + } catch (Exception e) { + stdout.println(e); + } + + } + + public PESDWrapper mergeResults(PESDWrapper obj1, PESDWrapper obj2) { + // Ordered merge of two pesd obj that are template results of the same starting-obj. As each template result has local consistency (thanks to altEndConsistencyNormalizer) and + // it is originated from the same base obj, we can simply move Alt/end symbols from the shortest to the longest in an ordered way. + // By doing so we generate the final result that will contain the matches of all the templates + + // we equalize the results in terms of length, simply by adding the Alt/end between the results + PESDWrapper first; + PESDWrapper second; + if (obj1.getSize() < obj2.getSize()) { + first = obj2; + second = obj1; + } else { + first = obj1; + second = obj2; + } + equalizeAltEnd(first, second); + return first; + } + + public void equalizeAltEnd(PESDWrapper first, PESDWrapper second) { + + try { + first.startIterator(); + second.startIterator(); + + while (first.getIterator() != -1) { + JSONObject line1 = first.getLine(); + JSONObject line2 = null; + if (second.getIterator() != -1) { + line2 = second.getLine(); + Boolean diff_type = (String) line1.get("type") != (String) line2.get("type"); + Boolean second_is_alt = ((String) line2.get("type")).contains("alt"); + Boolean second_is_end = ((String) line2.get("type")).contains("end"); + Boolean first_is_alt = ((String) line1.get("type")).contains("alt"); + Boolean first_is_end = ((String) line1.get("type")).contains("end"); + // If types differ and second is alt or end, the line is inserted as first + if (diff_type) { + if (second_is_alt || second_is_end) { + first.addToPos(first.getIterator(), line2); + } else if (first_is_alt || first_is_end) { + second.addToPos(first.getIterator(), line1); + } + } + } else { + second.addToPos(first.getIterator(), line1); + } + + first.nextLine(); + second.nextLine(); + } + } catch (Exception e) { + stdout.println(e); + } + } + + public String[] getDiagram(Integer mode_op) { + return this.pesdObj.getSeqDiagramdMD(mode_op); + } + + public JSONObject getMetadata() { + return this.pesdObj.getMetadata(); + } +}