From 4e62397d6a8b7dd6c34f4732b4ee1f18bf2b2b98 Mon Sep 17 00:00:00 2001 From: Doge Date: Tue, 6 Feb 2024 19:51:42 +0100 Subject: [PATCH] ## [0.0.6] - 2024-02-06 ### Added - Ruby signed cookie Tab - Multithreading feature is available for brute force attack ### Changed - Brute force Deep mode supports Ruby, Ruby5 and Ruby truncated hashing key derivation --- CHANGELOG.md | 26 ++- README.md | 76 ++++++--- build.gradle | 14 +- .../burp/config/BurpKeysModelPersistence.java | 10 +- src/main/java/burp/config/KeysModel.java | 45 ++--- src/main/java/burp/config/SignerConfig.java | 65 +++----- src/main/java/burp/config/Signers.java | 5 + .../one/d4d/sessionless/forms/EditorTab.form | 75 +++++++++ .../one/d4d/sessionless/forms/EditorTab.java | 59 ++++++- .../sessionless/forms/ResponseEditorView.java | 18 +- .../d4d/sessionless/forms/SettingsView.form | 18 +- .../d4d/sessionless/forms/SettingsView.java | 44 ++--- .../d4d/sessionless/forms/WordlistView.form | 30 +++- .../d4d/sessionless/forms/WordlistView.java | 99 +++++------ .../forms/dialog/BruteForceAttackDialog.java | 34 ++-- .../forms/dialog/NewWordDialog.form | 82 +++++++++ .../forms/dialog/NewWordDialog.java | 42 +++++ .../sessionless/forms/dialog/SignDialog.java | 2 + .../sessionless/itsdangerous/BruteForce.java | 156 +++++++++++------- .../sessionless/itsdangerous/Derivation.java | 5 +- .../crypto/DangerousTokenSigner.java | 23 ++- .../crypto/DjangoTokenSigner.java | 21 +-- .../crypto/ExpressTokenSigner.java | 18 +- .../crypto/JSONWebSignatureTokenSigner.java | 7 +- .../crypto/OauthProxyTokenSigner.java | 60 ++++--- .../itsdangerous/crypto/RubyTokenSigner.java | 47 ++++++ .../itsdangerous/crypto/TokenSigner.java | 130 ++++++++++++--- .../crypto/TornadoTokenSigner.java | 63 ++++--- .../model/DangerousSignedToken.java | 61 +++---- .../itsdangerous/model/DjangoSignedToken.java | 8 +- .../itsdangerous/model/JSONWebSignature.java | 14 +- .../model/OauthProxySignedToken.java | 43 +++-- .../itsdangerous/model/RubySignedToken.java | 28 ++++ .../model/SignedTokenObjectFinder.java | 96 +++++++---- .../model/UnknownSignedToken.java | 20 ++- .../presenter/EditorPresenter.java | 45 ++++- .../sessionless/presenter/KeyPresenter.java | 35 +++- .../d4d/sessionless/utils/ClaimsUtils.java | 2 +- .../java/one/d4d/sessionless/utils/Utils.java | 45 +++-- src/main/resources/salts | 5 +- src/main/resources/secrets | 2 + src/main/resources/strings.properties | 9 +- src/test/java/BruteForceTest.java | 22 +-- src/test/java/CompressTest.java | 2 +- src/test/java/DjangoTest.java | 37 +++-- src/test/java/ExpressSignedCookieTest.java | 19 ++- src/test/java/FlaskDangerousTest.java | 33 +--- src/test/java/JSONWebSignatureTest.java | 19 +-- src/test/java/OAuth2Test.java | 6 +- src/test/java/RubySignedCookieTest.java | 86 ++++++++++ src/test/java/SignUnsignTest.java | 54 +++--- src/test/java/TornadoTest.java | 17 +- src/test/java/UnknownSignedTokenTest.java | 68 ++++---- src/test/resources/salts | 6 +- src/test/resources/secrets | 4 +- 55 files changed, 1446 insertions(+), 614 deletions(-) create mode 100644 src/main/java/burp/config/Signers.java create mode 100644 src/main/java/one/d4d/sessionless/forms/dialog/NewWordDialog.form create mode 100644 src/main/java/one/d4d/sessionless/forms/dialog/NewWordDialog.java create mode 100644 src/main/java/one/d4d/sessionless/itsdangerous/crypto/RubyTokenSigner.java create mode 100644 src/main/java/one/d4d/sessionless/itsdangerous/model/RubySignedToken.java create mode 100644 src/test/java/RubySignedCookieTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c3e031..9464065 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,40 @@ # Changelog +## [0.0.6] - 2024-02-06 + +### Added + +- Ruby signed cookie Tab +- Multithreading feature is available for brute force attack + +### Changed + +- Brute force Deep mode supports Ruby, Ruby5 and Ruby truncated hashing key derivation + +## [0.0.5] - 2024-01-07 + +### Added + +- Manual secret and salt item creation + +### Changed + +- Brute force uses all known keys for all attacks mode by default + ## [0.0.4] - 2024-01-07 ### Changed + - Github actions ## [0.0.2] - 2024-01-05 ### Added -- Unknown signed string tab. + +- Unknown signed string tab. - Enabled signers setting added to the main tab - _Known keys_ brute force technic added to the Attack mode ### Changed + - Upgrade dependencies: org.json:json \ No newline at end of file diff --git a/README.md b/README.md index b607b06..08d6f0d 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,46 @@ # Sessionless -Sessionless is a Burp Suite extension for editing, signing, verifying, attacking signed tokens: [Django TimestampSigner](https://docs.djangoproject.com/en/5.0/topics/signing/#verifying-timestamped-values), [ItsDangerous Signer](https://itsdangerous.palletsprojects.com/en/2.1.x/signer/), [Express cookie-session middleware](https://expressjs.com/en/resources/middleware/cookie-session.html), [OAuth2 Proxy](https://github.com/oauth2-proxy/oauth2-proxy), [Tornado’s signed cookies](https://www.tornadoweb.org/en/stable/guide/security.html) and Unknown signed string. +Sessionless is a Burp Suite extension for editing, signing, verifying, attacking signed +tokens: [Django TimestampSigner](https://docs.djangoproject.com/en/5.0/topics/signing/#verifying-timestamped-values), [ItsDangerous Signer](https://itsdangerous.palletsprojects.com/en/2.1.x/signer/), [Express cookie-session middleware](https://expressjs.com/en/resources/middleware/cookie-session.html), [OAuth2 Proxy](https://github.com/oauth2-proxy/oauth2-proxy), [Tornado’s signed cookies](https://www.tornadoweb.org/en/stable/guide/security.html), [Ruby Rails Signed cookies](https://api.rubyonrails.org/classes/ActiveSupport/MessageVerifier.html) +and Unknown signed string. -It provides automatic detection and in-line editing of token within HTTP requests/responses and WebSocket messages, signing of tokens and automation of brute force attacks against signed tokens implementations. +It provides automatic detection and in-line editing of token within HTTP requests/responses and WebSocket messages, +signing of tokens and automation of brute force attacks against signed tokens implementations. -It was inspired by Fraser Winterborn and Dolph Flynn JWT Token extension. The original source code can be found [here](https://github.com/blackberry/jwt-editor) and [here](https://github.com/DolphFlynn/jwt-editor). +It was inspired by Fraser Winterborn and Dolph Flynn JWT Token extension. The original source code can be +found [here](https://github.com/blackberry/jwt-editor) and [here](https://github.com/DolphFlynn/jwt-editor). ## Build Instructions + * Ensure that Java JDK 17 or newer is installed * From root of project, run the command `./gradlew jar` -* This should place the JAR file `token-library-0.0.4.jar` within the `build/libs` directory -* This can be loaded into Burp by navigating to the `Extensions` tab, `Installed` sub-tab, clicking `Add` and loading the JAR file -* This BApp is using the newer Montoya API so it's best to use the latest version of Burp (try the earlier adopter channel if there are issues with the latest stable release) +* This should place the JAR file `token-library-0.0.6.jar` within the `build/libs` directory +* This can be loaded into Burp by navigating to the `Extensions` tab, `Installed` sub-tab, clicking `Add` and loading + the JAR file +* This BApp is using the newer Montoya API so it's best to use the latest version of Burp (try the earlier adopter + channel if there are issues with the latest stable release) ## Wordlist View + -The `Wordlist View` allows to import secrets and salts list files. Extension has own prebuild dictionary lists. Most secrets are taken from [jwt-secrets](https://github.com/wallarm/jwt-secrets). As an option, [Flask-Unsign-Wordlist](https://github.com/Paradoxis/Flask-Unsign-Wordlist) can be used. Extension supports JSON strings format for special chars, to use it quot the secret string with `"`. +The `Wordlist View` allows to import secrets and salts list files. Extension has own prebuild dictionary lists. Most +secrets are taken from [jwt-secrets](https://github.com/wallarm/jwt-secrets). As an +option, [Flask-Unsign-Wordlist](https://github.com/Paradoxis/Flask-Unsign-Wordlist) can be used. Extension supports JSON +strings format for special chars, to use it quot the secret string with `"`. ## Editor View + -The `Editor View` supports a number of signed tokens: Django, Dangerous, Flask, Express, OAuth2 and Tornado. It allows modification of the signed tokens at Burp Suite's HTTP Request/Response view in the Proxy, History and Repeater tools. +The `Editor View` supports a number of signed tokens: Django, Dangerous, Flask, Express, OAuth2 and Tornado. It allows +modification of the signed tokens at Burp Suite's HTTP Request/Response view in the Proxy, History and Repeater tools. -The Dangerous tab can be used for both, `Flask` and `Django` tokens, which are selected depending on whether a Dangerous or Django token is detected. +The Dangerous tab can be used for both, `Flask` and `Django` tokens, which are selected depending on whether a Dangerous +or Django token is detected. -The Unknown tab can be used to brute force unknown signed strings. Guessing mode works only with _Balanced_ brute force attack. It supports different message derivation technics, including: +The Unknown tab can be used to brute force unknown signed strings. Guessing mode works only with _Balanced_ and _Deep_ +brute force attacks. It supports different message derivation technics, including: * _None_ message will be used as is * _CONCAT_ separator byte will be removed from the message and that new value will be used to calculate signature @@ -43,21 +58,25 @@ A JSON text editor is provided to edit each component that contain JSON content: A timestamp editor is provided to edit each component that contain it: -* Dangerous timestamp +* Dangerous timestamp * Django timestamp * OAuth2 Proxy timestamp * Tornado timestamp -A hex editor is provided to all signed tokens, except Express signatures. __NOTE__ Express Tab doesn't support signature auto update yet. Please copy it manually to corresponding signature cookie. +A hex editor is provided to all signed tokens, except Express signatures. __NOTE__ Express Tab doesn't support signature +auto update yet. Please copy it manually to corresponding signature cookie. ### Sign -`Sign` presents a signing dialog that can be used to update the Signature by signing the token using a key from the `Keys View` that has signing capabilities + +`Sign` presents a signing dialog that can be used to update the Signature by signing the token using a key from +the `Keys View` that has signing capabilities ### Brute force -`Brute force` will attempt to find secret key that was used for signature generation. If a secret key was found, a dialog will be presented. +`Brute force` will attempt to find secret key that was used for signature generation. If a secret key was found, a +dialog will be presented. @@ -65,8 +84,8 @@ The `Brute force` option implements three types of attacks against signed tokens * _Known keys_ will use previously found secret keys only * _Fast_ will use default hashing algorithm and key derivation -* _Balanced_ will use all known key derivation technics, except PBKDF2HMAC -* _Deep_ will use all key derivation technics, including PBKDF2HMAC +* _Balanced_ will use all known key derivation technics, except PBKDF2WithHmacSHA1, PBKDF2WithHmacSHA256 +* _Deep_ will use all key derivation technics, including different types supported by Ruby Rails framework ### Attack @@ -83,35 +102,41 @@ The `Attack` option implements eight well-known authorization attacks against si * Authenticated claims * User access_token -These are described in more detail below. +These are described in more detail below. # Attacks -All of these attacks can be used together. Please bear in mind that extension doesn't support token payload modification at Attack mod, so your payload will be replaced with new one. +All of these attacks can be used together. Please bear in mind that extension doesn't support token payload modification +at Attack mod, so your payload will be replaced with new one. ### User claims -OpenID connect ID token format usually used by signing libraries to store information about authenticated user. Extension will generate placeholder for `admin` user ID token. +OpenID connect ID token format usually used by signing libraries to store information about authenticated user. +Extension will generate placeholder for `admin` user ID token. ### Wrapped user claims -Same as `User claims` attack but will put it into `user` JSON attribute. +Same as `User claims` attack but will put it into `user` JSON attribute. ### Username and password claims -Another common way to store user details is `username` and `password` JSON attributes. Extension will generate placeholder for `admin` user. +Another common way to store user details is `username` and `password` JSON attributes. Extension will generate +placeholder for `admin` user. ### Flask claims -Flask authenticated user session information if stored at client side should include `id` and `_id` and `user_id` or `_user_id` JSON attribute. Extension will generate session for the first user, usually admin. +Flask authenticated user session information if stored at client side should include `id` and `_id` and `user_id` +or `_user_id` JSON attribute. Extension will generate session for the first user, usually admin. ### Express claims -Express framework uses `passport` JSON attribute to store user details. Extension will generate placeholder for `admin` user. +Express framework uses `passport` JSON attribute to store user details. Extension will generate placeholder for `admin` +user. ### Account user claims -Some frameworks may use `account` wrapper to store information about authenticated user. For exploitation, it might be required to use `Authenticated claims` too. +Some frameworks may use `account` wrapper to store information about authenticated user. For exploitation, it might be +required to use `Authenticated claims` too. ### Authenticated claims @@ -119,5 +144,6 @@ The Authenticated claims implements 12 well-known authorization flags. ### User access_token -The `User access_token` option generated JWT OpenID connect ID token signed with the same key and same hashing algorithm without any key derivation preformed. +The `User access_token` option generated JWT OpenID connect ID token signed with the same key and same hashing algorithm +without any key derivation preformed. diff --git a/build.gradle b/build.gradle index b2fcc38..cde6cdf 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,9 @@ - plugins { id 'java-library' } group = 'one.d4d' -version = '0.0.4' +version = '0.0.6' description = 'token-signer' repositories { @@ -63,8 +62,17 @@ tasks.withType(JavaCompile).configureEach { } } +def integrationTest = tasks.register("integrationTest", Test) { + useJUnitPlatform { + includeTags "slow" + } + shouldRunAfter test +} + test { - useJUnitPlatform() + useJUnitPlatform { + excludeTags 'slow' + } } jar { diff --git a/src/main/java/burp/config/BurpKeysModelPersistence.java b/src/main/java/burp/config/BurpKeysModelPersistence.java index e0c6e98..6ddf033 100644 --- a/src/main/java/burp/config/BurpKeysModelPersistence.java +++ b/src/main/java/burp/config/BurpKeysModelPersistence.java @@ -6,7 +6,7 @@ import one.d4d.sessionless.utils.Utils; import java.io.File; -import java.util.List; +import java.util.Set; public class BurpKeysModelPersistence { static final String BURP_SETTINGS_NAME = "one.d4d.sessionless.keys"; @@ -29,7 +29,7 @@ public KeysModel loadOrCreateNew() { Gson gson = GsonHelper.customGson; KeysModel keysModel = gson.fromJson(json, KeysModel.class); if (keysModel.getSaltsFilePath() != null) { - List result = Utils.deserializeFile(new File(keysModel.getSaltsFilePath())); + Set result = Utils.deserializeFile(new File(keysModel.getSaltsFilePath())); if (result.isEmpty()) { keysModel.setSalts(loadDefaultSalts()); } else { @@ -37,7 +37,7 @@ public KeysModel loadOrCreateNew() { } } if (keysModel.getSecretsFilePath() != null) { - List result = Utils.deserializeFile(new File(keysModel.getSecretsFilePath())); + Set result = Utils.deserializeFile(new File(keysModel.getSecretsFilePath())); if (result.isEmpty()) { keysModel.setSecrets(loadDefaultSecrets()); } else { @@ -55,11 +55,11 @@ public void save(KeysModel model) { } - private List loadDefaultSecrets() { + private Set loadDefaultSecrets() { return Utils.readResourceForClass("/secrets", this.getClass()); } - private List loadDefaultSalts() { + private Set loadDefaultSalts() { return Utils.readResourceForClass("/salts", this.getClass()); } diff --git a/src/main/java/burp/config/KeysModel.java b/src/main/java/burp/config/KeysModel.java index 653fcbd..df1c80f 100644 --- a/src/main/java/burp/config/KeysModel.java +++ b/src/main/java/burp/config/KeysModel.java @@ -3,17 +3,15 @@ import com.google.gson.annotations.Expose; import one.d4d.sessionless.keys.SecretKey; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.IntStream; public class KeysModel { - private final Object lock; + private final Object lockKeys = new Object(); @Expose private final List keys = new ArrayList<>(); - private List secrets = new ArrayList<>(); - private List salts = new ArrayList<>(); + private Set secrets = new HashSet<>(); + private Set salts = new HashSet<>(); @Expose private String secretsFilePath; @Expose @@ -22,23 +20,22 @@ public class KeysModel { public KeysModel() { this.modelListener = new KeysModelListener.InertKeyModelListener(); - this.lock = new Object(); } - public List getSecrets() { + public Set getSecrets() { return secrets; } - public void setSecrets(List secrets) { - this.secrets = secrets; + public void setSecrets(Set secrets) { + this.secrets.addAll(secrets); } - public List getSalts() { + public Set getSalts() { return salts; } - public void setSalts(List salts) { - this.salts = salts; + public void setSalts(Set salts) { + this.salts.addAll(salts); } public String getSecretsFilePath() { @@ -69,29 +66,39 @@ public void removeSecret(String s) { secrets.remove(s); } + public void addSecret(String s) { + secrets.add(s); + } + public void removeSalt(String s) { salts.remove(s); } + public void addSalt(String s) { + salts.add(s); + } + public Optional getKey(String keyId) { - return keys.stream().filter(x -> keyId.equals(x.getID())).findFirst(); + synchronized (lockKeys) { + return keys.stream().filter(x -> keyId.equals(x.getID())).findFirst(); + } } public SecretKey getKey(int index) { - synchronized (lock) { + synchronized (lockKeys) { return keys.get(index); } } public void addKey(SecretKey key) { - synchronized (lock) { + synchronized (lockKeys) { keys.add(key); } modelListener.notifyKeyInserted(key); } public List getSigningKeys() { - synchronized (lock) { + synchronized (lockKeys) { return keys; } } @@ -103,7 +110,7 @@ public void addKeyModelListener(KeysModelListener modelListener) { public void deleteKey(SecretKey keyId) { int rowIndex; - synchronized (lock) { + synchronized (lockKeys) { rowIndex = keys.indexOf(keyId); keys.remove(keyId); } @@ -114,7 +121,7 @@ public void deleteKey(SecretKey keyId) { } public void deleteKeys(int[] indices) { - synchronized (lock) { + synchronized (lockKeys) { List idsToDelete = IntStream.of(indices).mapToObj(this::getKey).toList(); for (SecretKey id : idsToDelete) { diff --git a/src/main/java/burp/config/SignerConfig.java b/src/main/java/burp/config/SignerConfig.java index 671477e..3cfc35e 100644 --- a/src/main/java/burp/config/SignerConfig.java +++ b/src/main/java/burp/config/SignerConfig.java @@ -2,63 +2,36 @@ import com.google.gson.annotations.Expose; +import java.util.EnumSet; +import java.util.Set; + public class SignerConfig { + @Expose - private boolean enableDangerous; - @Expose - private boolean enableExpress; - @Expose - private boolean enableOAuth; - @Expose - private boolean enableTornado; - @Expose - private boolean enableUnknown; + private Set enabled; public SignerConfig() { - this.enableDangerous = true; - this.enableExpress = true; - this.enableOAuth = false; - this.enableTornado = true; - this.enableUnknown = false; - } - - public boolean isEnableDangerous() { - return enableDangerous; - } - - public void setEnableDangerous(boolean enableDangerous) { - this.enableDangerous = enableDangerous; - } - - public boolean isEnableExpress() { - return enableExpress; - } - - public void setEnableExpress(boolean enableExpress) { - this.enableExpress = enableExpress; - } - - public boolean isEnableOAuth() { - return enableOAuth; - } - - public void setEnableOAuth(boolean enableOAuth) { - this.enableOAuth = enableOAuth; + EnumSet disabled = EnumSet.of(Signers.OAUTH, Signers.UNKNOWN); + this.enabled = EnumSet.complementOf(disabled); } - public boolean isEnableTornado() { - return enableTornado; + public boolean isEnabled(Signers s) { + return this.enabled.contains(s); } - public void setEnableTornado(boolean enableTornado) { - this.enableTornado = enableTornado; + public void setEnabled(Signers s) { + this.enabled.add(s); } - public boolean isEnableUnknown() { - return enableUnknown; + public void removeEnabled(Signers s) { + this.enabled.remove(s); } - public void setEnableUnknown(boolean enableUnknown) { - this.enableUnknown = enableUnknown; + public void toggleEnabled(Signers s, boolean isEnabled) { + if (isEnabled) { + this.setEnabled(s); + } else { + this.removeEnabled(s); + } } } diff --git a/src/main/java/burp/config/Signers.java b/src/main/java/burp/config/Signers.java new file mode 100644 index 0000000..12d24ed --- /dev/null +++ b/src/main/java/burp/config/Signers.java @@ -0,0 +1,5 @@ +package burp.config; + +public enum Signers { + DANGEROUS, EXPRESS, OAUTH, TORNADO, RUBY, UNKNOWN +} diff --git a/src/main/java/one/d4d/sessionless/forms/EditorTab.form b/src/main/java/one/d4d/sessionless/forms/EditorTab.form index 4fce046..9039dd1 100644 --- a/src/main/java/one/d4d/sessionless/forms/EditorTab.form +++ b/src/main/java/one/d4d/sessionless/forms/EditorTab.form @@ -529,6 +529,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/one/d4d/sessionless/forms/EditorTab.java b/src/main/java/one/d4d/sessionless/forms/EditorTab.java index e578ab6..9c47c0f 100644 --- a/src/main/java/one/d4d/sessionless/forms/EditorTab.java +++ b/src/main/java/one/d4d/sessionless/forms/EditorTab.java @@ -33,7 +33,8 @@ public abstract class EditorTab implements ExtensionProvidedEditor { public static final int TAB_EXPRESS = 1; public static final int TAB_OAUTH = 2; public static final int TAB_TORNADO = 3; - public static final int TAB_UNKNOWN = 4; + public static final int TAB_RUBY = 4; + public static final int TAB_UNKNOWN = 5; private static final int MAX_JOSE_OBJECT_STRING_LENGTH = 68; final EditorPresenter presenter; private final RstaFactory rstaFactory; @@ -71,10 +72,14 @@ public abstract class EditorTab implements ExtensionProvidedEditor { private RSyntaxTextArea textAreaUnknownStringMessage; private JPanel panelUnknownStringSeparator; private RSyntaxTextArea textAreaUnknownStringSignature; + private JPanel panelRubySeparator; + private RSyntaxTextArea textAreaRubyMessage; + private RSyntaxTextArea textAreaRubySignature; private CodeArea codeAreaDangerousSignature; private CodeArea codeAreaDangerousSeparator; private CodeArea codeAreaOAuthSignature; private CodeArea codeAreaTornadoSignature; + private CodeArea codeAreaRubySeparator; private CodeArea codeAreaUnknownSeparator; EditorTab( @@ -126,6 +131,8 @@ public void changedUpdate(DocumentEvent e) { textAreaTornadoTimestamp.getDocument().addDocumentListener(documentListener); textAreaTornadoName.getDocument().addDocumentListener(documentListener); textAreaTornadoValue.getDocument().addDocumentListener(documentListener); + textAreaRubyMessage.getDocument().addDocumentListener(documentListener); + textAreaRubySignature.getDocument().addDocumentListener(documentListener); textAreaUnknownStringMessage.getDocument().addDocumentListener(documentListener); textAreaUnknownStringSignature.getDocument().addDocumentListener(documentListener); @@ -137,6 +144,7 @@ public void changedUpdate(DocumentEvent e) { codeAreaDangerousSeparator.addDataChangedListener(presenter::componentChanged); codeAreaOAuthSignature.addDataChangedListener(presenter::componentChanged); codeAreaTornadoSignature.addDataChangedListener(presenter::componentChanged); + codeAreaRubySeparator.addDataChangedListener(presenter::componentChanged); codeAreaUnknownSeparator.addDataChangedListener(presenter::componentChanged); comboBoxSignedToken.addActionListener(e -> presenter.onSelectionChanged()); @@ -252,6 +260,7 @@ public byte[] getTornadoSignature() { public void setTornadoSignature(byte[] signature) { codeAreaTornadoSignature.setData(new ByteArrayEditableData(signature)); } + public byte[] getDangerousSignature() { return Utils.getCodeAreaData(codeAreaDangerousSignature); } @@ -307,6 +316,31 @@ public boolean getDangerouseIsCompressed() { public void setDangerouseIsCompressed(boolean enabled) { checkBoxCompress.setSelected(enabled); } + + public String getRubyMessage() { + return textAreaRubyMessage.getText(); + } + + public void setRubyMessage(String text) { + textAreaRubyMessage.setText(text); + } + + public String getRubySignature() { + return textAreaRubySignature.getText(); + } + + public void setRubySignature(String signature) { + textAreaRubySignature.setText(signature); + } + + public byte[] getRubySeparator() { + return Utils.getCodeAreaData(codeAreaRubySeparator); + } + + public void setRubySeparator(byte[] separator) { + codeAreaRubySeparator.setData(new ByteArrayEditableData(separator)); + } + public String getUnknownMessage() { return textAreaUnknownStringMessage.getText(); } @@ -314,6 +348,7 @@ public String getUnknownMessage() { public void setUnknownMessage(String text) { textAreaUnknownStringMessage.setText(text); } + public String getUnknownSignature() { return textAreaUnknownStringSignature.getText(); } @@ -321,6 +356,7 @@ public String getUnknownSignature() { public void setUnknownSignature(String signature) { textAreaUnknownStringSignature.setText(signature); } + public byte[] getUnknownSeparator() { return Utils.getCodeAreaData(codeAreaUnknownSeparator); } @@ -355,6 +391,11 @@ private void createUIComponents() { codeAreaTornadoSignature = hexCodeAreaFactory.build(); panelTornadoSignature.add(codeAreaTornadoSignature); + + panelRubySeparator = new JPanel(new BorderLayout()); + codeAreaRubySeparator = hexCodeAreaFactory.build(); + panelRubySeparator.add(codeAreaRubySeparator); + panelUnknownStringSeparator = new JPanel(new BorderLayout()); codeAreaUnknownSeparator = hexCodeAreaFactory.build(); panelUnknownStringSeparator.add(codeAreaUnknownSeparator); @@ -398,6 +439,8 @@ private void createUIComponents() { textAreaTornadoTimestamp = rstaFactory.buildDefaultTextArea(); textAreaTornadoName = rstaFactory.buildDefaultTextArea(); textAreaTornadoValue = rstaFactory.buildDefaultTextArea(); + textAreaRubyMessage = rstaFactory.buildDefaultTextArea(); + textAreaRubySignature = rstaFactory.buildDefaultTextArea(); textAreaUnknownStringMessage = rstaFactory.buildDefaultTextArea(); textAreaUnknownStringSignature = rstaFactory.buildDefaultTextArea(); } @@ -479,6 +522,20 @@ public void setTornadoMode() { EditationAllowed editationAllowed = editable ? ALLOWED : READ_ONLY; codeAreaTornadoSignature.setEditationAllowed(editationAllowed); } + + public void setRubyMode() { + mode = TAB_RUBY; + enableTabAtIndex(TAB_RUBY); + buttonBruteForceAttack.setEnabled(editable); + buttonAttack.setEnabled(editable); + + textAreaRubyMessage.setEditable(editable); + textAreaRubySignature.setEditable(editable); + + EditationAllowed editationAllowed = editable ? ALLOWED : READ_ONLY; + codeAreaRubySeparator.setEditationAllowed(editationAllowed); + } + public void setUnknownMode() { mode = TAB_UNKNOWN; enableTabAtIndex(TAB_UNKNOWN); diff --git a/src/main/java/one/d4d/sessionless/forms/ResponseEditorView.java b/src/main/java/one/d4d/sessionless/forms/ResponseEditorView.java index 0ce6cd1..8cc4727 100644 --- a/src/main/java/one/d4d/sessionless/forms/ResponseEditorView.java +++ b/src/main/java/one/d4d/sessionless/forms/ResponseEditorView.java @@ -20,14 +20,14 @@ public class ResponseEditorView extends EditorTab implements ExtensionProvidedHttpResponseEditor { public ResponseEditorView( - PresenterStore presenters, - RstaFactory rstaFactory, - Logging logging, - UserInterface userInterface, - CollaboratorPayloadGenerator collaboratorPayloadGenerator, - SignerConfig signerConfig, - boolean editable, - boolean isProVersion) { + PresenterStore presenters, + RstaFactory rstaFactory, + Logging logging, + UserInterface userInterface, + CollaboratorPayloadGenerator collaboratorPayloadGenerator, + SignerConfig signerConfig, + boolean editable, + boolean isProVersion) { super( presenters, rstaFactory, @@ -52,7 +52,7 @@ public void setRequestResponse(HttpRequestResponse requestResponse) { } catch (Exception e) { targetURL = null; } - presenter.setMessage(httpResponse.toByteArray().toString(),targetURL,httpResponse.cookies(),null); + presenter.setMessage(httpResponse.toByteArray().toString(), targetURL, httpResponse.cookies(), null); } @Override diff --git a/src/main/java/one/d4d/sessionless/forms/SettingsView.form b/src/main/java/one/d4d/sessionless/forms/SettingsView.form index 5ace786..7415ba0 100644 --- a/src/main/java/one/d4d/sessionless/forms/SettingsView.form +++ b/src/main/java/one/d4d/sessionless/forms/SettingsView.form @@ -94,7 +94,7 @@ - + @@ -182,6 +182,22 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/java/one/d4d/sessionless/forms/SettingsView.java b/src/main/java/one/d4d/sessionless/forms/SettingsView.java index 4c5e754..74bc43f 100644 --- a/src/main/java/one/d4d/sessionless/forms/SettingsView.java +++ b/src/main/java/one/d4d/sessionless/forms/SettingsView.java @@ -2,9 +2,10 @@ import burp.api.montoya.ui.UserInterface; import burp.config.BurpConfig; +import burp.config.ProxyConfig; import burp.config.SignerConfig; +import burp.config.Signers; import burp.proxy.HighlightColor; -import burp.config.ProxyConfig; import javax.swing.*; import java.awt.*; @@ -27,6 +28,7 @@ public class SettingsView { private JCheckBox checkBoxEnableExpressSignedString; private JCheckBox checkBoxEnableOAuthSignedString; private JCheckBox checkBoxEnableTornadoSignedString; + private JCheckBox checkBoxEnableRubySignedString; public SettingsView(Window parent, BurpConfig burpConfig, UserInterface userInterface) { this.parent = parent; @@ -48,30 +50,30 @@ public SettingsView(Window parent, BurpConfig burpConfig, UserInterface userInte userInterface.applyThemeToComponent(mainPanel); comboBoxHighlightColor.setRenderer(new HighlightComboRenderer()); - checkBoxEnableDangerousSignedString.setSelected(signerConfig.isEnableDangerous()); - checkBoxEnableDangerousSignedString.addActionListener(e -> { - signerConfig.setEnableDangerous(checkBoxEnableDangerousSignedString.isSelected()); - }); + checkBoxEnableDangerousSignedString.setSelected(signerConfig.isEnabled(Signers.DANGEROUS)); + checkBoxEnableDangerousSignedString.addActionListener(e -> + signerConfig.toggleEnabled(Signers.DANGEROUS, checkBoxEnableDangerousSignedString.isSelected())); - checkBoxEnableExpressSignedString.setSelected(signerConfig.isEnableExpress()); - checkBoxEnableExpressSignedString.addActionListener(e -> { - signerConfig.setEnableExpress(checkBoxEnableExpressSignedString.isSelected()); - }); + checkBoxEnableExpressSignedString.setSelected(signerConfig.isEnabled(Signers.EXPRESS)); + checkBoxEnableExpressSignedString.addActionListener(e -> + signerConfig.toggleEnabled(Signers.EXPRESS, checkBoxEnableExpressSignedString.isSelected())); - checkBoxEnableOAuthSignedString.setSelected(signerConfig.isEnableOAuth()); - checkBoxEnableOAuthSignedString.addActionListener(e -> { - signerConfig.setEnableOAuth(checkBoxEnableOAuthSignedString.isSelected()); - }); + checkBoxEnableOAuthSignedString.setSelected(signerConfig.isEnabled(Signers.OAUTH)); + checkBoxEnableOAuthSignedString.addActionListener(e -> + signerConfig.toggleEnabled(Signers.OAUTH, checkBoxEnableOAuthSignedString.isSelected())); - checkBoxEnableTornadoSignedString.setSelected(signerConfig.isEnableTornado()); - checkBoxEnableTornadoSignedString.addActionListener(e -> { - signerConfig.setEnableTornado(checkBoxEnableTornadoSignedString.isSelected()); - }); + checkBoxEnableTornadoSignedString.setSelected(signerConfig.isEnabled(Signers.TORNADO)); + checkBoxEnableTornadoSignedString.addActionListener(e -> + signerConfig.toggleEnabled(Signers.TORNADO, checkBoxEnableTornadoSignedString.isSelected())); + + checkBoxEnableRubySignedString.setSelected(signerConfig.isEnabled(Signers.RUBY)); + checkBoxEnableRubySignedString.addActionListener(e -> + signerConfig.toggleEnabled(Signers.RUBY, checkBoxEnableRubySignedString.isSelected())); + + checkBoxEnableUnknownSignedString.setSelected(signerConfig.isEnabled(Signers.UNKNOWN)); + checkBoxEnableUnknownSignedString.addActionListener(e -> + signerConfig.toggleEnabled(Signers.UNKNOWN, checkBoxEnableUnknownSignedString.isSelected())); - checkBoxEnableUnknownSignedString.setSelected(signerConfig.isEnableUnknown()); - checkBoxEnableUnknownSignedString.addActionListener(e -> { - signerConfig.setEnableUnknown(checkBoxEnableUnknownSignedString.isSelected()); - }); } private static class HighlightComboRenderer implements ListCellRenderer { diff --git a/src/main/java/one/d4d/sessionless/forms/WordlistView.form b/src/main/java/one/d4d/sessionless/forms/WordlistView.form index fc8f4ea..053d604 100644 --- a/src/main/java/one/d4d/sessionless/forms/WordlistView.form +++ b/src/main/java/one/d4d/sessionless/forms/WordlistView.form @@ -3,7 +3,7 @@ - + @@ -16,7 +16,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -42,12 +42,20 @@ - + + + + + + + + + @@ -112,7 +120,7 @@ - + @@ -130,7 +138,7 @@ - + @@ -138,12 +146,20 @@ - + + + + + + + + + diff --git a/src/main/java/one/d4d/sessionless/forms/WordlistView.java b/src/main/java/one/d4d/sessionless/forms/WordlistView.java index 641e8af..9b8a8df 100644 --- a/src/main/java/one/d4d/sessionless/forms/WordlistView.java +++ b/src/main/java/one/d4d/sessionless/forms/WordlistView.java @@ -25,6 +25,7 @@ public class WordlistView { private final KeysTableModel keysTableModel; private final DefaultListModel modelSecrets = new DefaultListModel<>(); private final DefaultListModel modelSalts = new DefaultListModel<>(); + private final KeyPresenter presenter; private JPanel mainPanel; private JButton secretsLoadButton; private JButton secretsRemoveButton; @@ -38,7 +39,8 @@ public class WordlistView { private JTable tableKeys; private JTextArea textAreaSalts; private JTextArea textAreaSecrets; - private final KeyPresenter presenter; + private JButton secretsAddButton; + private JButton saltsAddButton; private JMenuItem menuItemDelete; private JMenuItem menuItemCopy; @@ -79,10 +81,12 @@ public WordlistView( modelSalts); secretsLoadButton.addActionListener(presenter::onButtonLoadSecretsClick); + secretsAddButton.addActionListener(presenter::onButtonAddSecretsClick); secretsRemoveButton.addActionListener(presenter::onButtonRemoveSecretsClick); secretsCleanButton.addActionListener(presenter::onButtonCleanSecretsClick); saltsLoadButton.addActionListener(presenter::onButtonLoadSaltsClick); + saltsAddButton.addActionListener(presenter::onButtonAddSaltsClick); saltsRemoveButton.addActionListener(presenter::onButtonRemoveSaltsClick); saltsCleanButton.addActionListener(presenter::onButtonCleanSaltsClick); @@ -91,52 +95,6 @@ public WordlistView( // Attach event handlers for button clicks newKeyButton.addActionListener(e -> presenter.onButtonNewSecretKeyClick()); } - /** - * Class for the right-click popup menu - */ - private class JTablePopup extends PercentageBasedColumnWidthTable { - private Integer popupRow; - - public JTablePopup() { - super(KeysTableColumns.columnWidthPercentages()); - } - - @Override - public JPopupMenu getComponentPopupMenu() { - // Get the row that has been right-clicked on - Point p = getMousePosition(); - - if (p == null || rowAtPoint(p) < 0) { - popupRow = null; - return null; - } - - popupRow = rowAtPoint(p); - - boolean copyEnabled = false; - - // No selection, set the selection - if (tableKeys.getSelectedRowCount() == 0) { - tableKeys.changeSelection(popupRow, 0, false, false); - } - // Selection equals right-clicked row - this will trigger on right-click release - else if (tableKeys.getSelectedRowCount() == 1 && tableKeys.getSelectedRow() == popupRow) { - copyEnabled = keysModel.getKey(popupRow) != null; - } - // Selection doesn't equal right-clicked row, change the selection - else if (tableKeys.getSelectedRowCount() == 1 && tableKeys.getSelectedRow() != popupRow) { - tableKeys.changeSelection(popupRow, 0, false, false); - } - - menuItemCopy.setEnabled(copyEnabled); - - return super.getComponentPopupMenu(); - } - - public Integer getPopupRow() { - return popupRow; - } - } public Component getUiComponent() { return mainPanel; @@ -220,4 +178,51 @@ public Window getParent() { return parent; } + /** + * Class for the right-click popup menu + */ + private class JTablePopup extends PercentageBasedColumnWidthTable { + private Integer popupRow; + + public JTablePopup() { + super(KeysTableColumns.columnWidthPercentages()); + } + + @Override + public JPopupMenu getComponentPopupMenu() { + // Get the row that has been right-clicked on + Point p = getMousePosition(); + + if (p == null || rowAtPoint(p) < 0) { + popupRow = null; + return null; + } + + popupRow = rowAtPoint(p); + + boolean copyEnabled = false; + + // No selection, set the selection + if (tableKeys.getSelectedRowCount() == 0) { + tableKeys.changeSelection(popupRow, 0, false, false); + } + // Selection equals right-clicked row - this will trigger on right-click release + else if (tableKeys.getSelectedRowCount() == 1 && tableKeys.getSelectedRow() == popupRow) { + copyEnabled = keysModel.getKey(popupRow) != null; + } + // Selection doesn't equal right-clicked row, change the selection + else if (tableKeys.getSelectedRowCount() == 1 && tableKeys.getSelectedRow() != popupRow) { + tableKeys.changeSelection(popupRow, 0, false, false); + } + + menuItemCopy.setEnabled(copyEnabled); + + return super.getComponentPopupMenu(); + } + + public Integer getPopupRow() { + return popupRow; + } + } + } diff --git a/src/main/java/one/d4d/sessionless/forms/dialog/BruteForceAttackDialog.java b/src/main/java/one/d4d/sessionless/forms/dialog/BruteForceAttackDialog.java index b912940..a50c1ef 100644 --- a/src/main/java/one/d4d/sessionless/forms/dialog/BruteForceAttackDialog.java +++ b/src/main/java/one/d4d/sessionless/forms/dialog/BruteForceAttackDialog.java @@ -11,6 +11,7 @@ import java.awt.*; import java.awt.event.*; import java.util.List; +import java.util.Set; public class BruteForceAttackDialog extends AbstractDialog { @@ -23,8 +24,8 @@ public class BruteForceAttackDialog extends AbstractDialog { public BruteForceAttackDialog( Window parent, ErrorLoggingActionListenerFactory actionListenerFactory, - List signingSecrets, - List signingSalts, + Set signingSecrets, + Set signingSalts, List signingKeys, Attack mode, SignedToken token @@ -35,12 +36,6 @@ public BruteForceAttackDialog( getRootPane().setDefaultButton(buttonCancel); - buttonCancel.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - onCancel(); - } - }); - // call onCancel() when cross is clicked setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @@ -65,10 +60,12 @@ public void actionPerformed(ActionEvent e) { ); lblStatus.setText(lblText); SwingWorker sw = new SwingWorker() { + private BruteForce bf; + @Override protected Void doInBackground() throws Exception { - BruteForce bf = new BruteForce(signingSecrets, signingSalts, signingKeys, mode, token); - SecretKey k = bf.search(); + bf = new BruteForce(signingSecrets, signingSalts, signingKeys, mode, token); + SecretKey k = bf.parallel(); if (k != null) { secretKey = k; } @@ -77,10 +74,26 @@ protected Void doInBackground() throws Exception { @Override protected void done() { + if (isCancelled()) { + if (bf != null) bf.shutdown(); + } dispose(); } }; sw.execute(); + buttonCancel.addActionListener(e -> { + sw.cancel(true); + onCancel(); + }); + this.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + sw.cancel(true); + } + + public void windowClosed(WindowEvent e) { + sw.cancel(true); + } + }); } @@ -93,4 +106,5 @@ private void createUIComponents() { public SecretKey getSecretKey() { return secretKey; } + } diff --git a/src/main/java/one/d4d/sessionless/forms/dialog/NewWordDialog.form b/src/main/java/one/d4d/sessionless/forms/dialog/NewWordDialog.form new file mode 100644 index 0000000..11b7752 --- /dev/null +++ b/src/main/java/one/d4d/sessionless/forms/dialog/NewWordDialog.form @@ -0,0 +1,82 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/main/java/one/d4d/sessionless/forms/dialog/NewWordDialog.java b/src/main/java/one/d4d/sessionless/forms/dialog/NewWordDialog.java new file mode 100644 index 0000000..413ee92 --- /dev/null +++ b/src/main/java/one/d4d/sessionless/forms/dialog/NewWordDialog.java @@ -0,0 +1,42 @@ +package one.d4d.sessionless.forms.dialog; + +import one.d4d.sessionless.utils.GsonHelper; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyEvent; + +public class NewWordDialog extends AbstractDialog { + private JPanel contentPane; + private JButton buttonOK; + private JButton buttonCancel; + private JTextField textFieldItem; + private JCheckBox checkBoxJSON; + private String item; + + public NewWordDialog(Window parent) { + super(parent, "new_word_dialog_title"); + setContentPane(contentPane); + getRootPane().setDefaultButton(buttonOK); + + buttonOK.addActionListener(e -> onOK()); + buttonCancel.addActionListener(e -> onCancel()); + + contentPane.registerKeyboardAction( + e -> onCancel(), + KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT + ); + textFieldItem.setText("\"\""); + checkBoxJSON.setSelected(true); + } + + private void onOK() { + item = checkBoxJSON.isSelected() ? GsonHelper.customGson.fromJson(textFieldItem.getText(), String.class) : textFieldItem.getText(); + dispose(); + } + + public String getItem() { + return item; + } +} diff --git a/src/main/java/one/d4d/sessionless/forms/dialog/SignDialog.java b/src/main/java/one/d4d/sessionless/forms/dialog/SignDialog.java index 9d23cf8..5b75f4c 100644 --- a/src/main/java/one/d4d/sessionless/forms/dialog/SignDialog.java +++ b/src/main/java/one/d4d/sessionless/forms/dialog/SignDialog.java @@ -60,6 +60,8 @@ private void onOK() { s = new OauthProxyTokenSigner(selectedKey); } else if (tokenObject instanceof TornadoSignedToken) { s = new TornadoTokenSigner(selectedKey); + } else if (tokenObject instanceof RubySignedToken) { + s = new RubyTokenSigner(selectedKey); } else if (tokenObject instanceof UnknownSignedToken) { s = new TokenSigner(selectedKey); } else { diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/BruteForce.java b/src/main/java/one/d4d/sessionless/itsdangerous/BruteForce.java index c977e5b..36473bd 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/BruteForce.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/BruteForce.java @@ -1,21 +1,31 @@ package one.d4d.sessionless.itsdangerous; +import com.google.common.collect.Lists; import one.d4d.sessionless.itsdangerous.crypto.TokenSigner; import one.d4d.sessionless.itsdangerous.model.SignedToken; import one.d4d.sessionless.itsdangerous.model.UnknownSignedToken; import one.d4d.sessionless.keys.SecretKey; +import one.d4d.sessionless.utils.Utils; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.concurrent.*; public class BruteForce { - private final List secrets; - private final List salts; + private final Set secrets; + private final Set salts; private final List signingKeys; private final Attack scanConfiguration; private final SignedToken token; + private ExecutorService executor; - public BruteForce(List secrets, List salts, List signingKeys, Attack scanConfiguration, SignedToken token) { + public BruteForce(Set secrets, + Set salts, + List signingKeys, + Attack scanConfiguration, + SignedToken token) { this.secrets = secrets; this.salts = salts; this.signingKeys = signingKeys; @@ -23,72 +33,52 @@ public BruteForce(List secrets, List salts, List sign this.token = token; } - public List prepare() { + public List prepareAdvanced() { List attacks = new ArrayList<>(); + + List derivations = new ArrayList<>(List.of(Derivation.values())); + + Set messages = new HashSet<>(List.of(MessageDerivation.NONE)); + + List digests = new ArrayList<>(List.of(MessageDigestAlgorithm.values())); + TokenSigner is = token.getSigner(); - if (scanConfiguration == Attack.KNOWN) { - this.signingKeys.forEach(key -> { - TokenSigner ks = new TokenSigner(key); - attacks.add(ks); - }); + this.signingKeys.forEach(key -> { + TokenSigner ks = new TokenSigner(key); + attacks.add(ks); + }); + + if (scanConfiguration == Attack.KNOWN) return attacks; + + if (scanConfiguration == Attack.FAST) { + secrets.forEach(secret -> + is.getKnownDerivations().forEach(d -> attacks.addAll(is.cloneWithSaltDerivation(secret, salts, d))) + ); return attacks; } - for (String secret : secrets) { - if (scanConfiguration == Attack.FAST) { - if (is.getKeyDerivation() == Derivation.NONE) { - TokenSigner s = is.clone(); - s.setSecretKey(secret.getBytes()); - attacks.add(s); - } else { - for (String salt : salts) { - TokenSigner s = is.clone(); - s.setSecretKey(secret.getBytes()); - s.setSalt(salt.getBytes()); - attacks.add(s); - } - } - } else { - for (MessageDerivation md : MessageDerivation.values()) { - if(md != MessageDerivation.NONE && !(token instanceof UnknownSignedToken)) continue; - for (Derivation d : Derivation.values()) { - if (d == Derivation.NONE) { - TokenSigner s = is.clone(); - s.setKeyDerivation(d); - s.setMessageDerivation(md); - s.setSecretKey(secret.getBytes()); - attacks.add(s); - } else if (d == Derivation.HASH) { - for (MessageDigestAlgorithm m : MessageDigestAlgorithm.values()) { - TokenSigner s = is.clone(); - s.setKeyDerivation(d); - s.setMessageDerivation(md); - s.setSecretKey(secret.getBytes()); - s.setMessageDigestAlgorithm(m); - attacks.add(s); - } - } else { - if (d == Derivation.PBKDF2HMAC && scanConfiguration != Attack.Deep) continue; - for (MessageDigestAlgorithm m : MessageDigestAlgorithm.values()) { - for (String salt : salts) { - TokenSigner s = is.clone(); - s.setKeyDerivation(d); - s.setMessageDerivation(md); - s.setSecretKey(secret.getBytes()); - s.setSalt(salt.getBytes()); - s.setMessageDigestAlgorithm(m); - attacks.add(s); - } - } - } + + if (token instanceof UnknownSignedToken) messages.addAll(List.of(MessageDerivation.values())); + + if (scanConfiguration == Attack.Balanced) derivations.removeIf( + d -> d == Derivation.PBKDF2HMAC || d == Derivation.RUBY5 || d == Derivation.RUBY5_TRUNCATED); + + secrets.forEach(secret -> { + messages.forEach(md -> { + derivations.forEach(d -> { + if (d == Derivation.CONCAT || d == Derivation.DJANGO || d == Derivation.HASH) { + digests.forEach(mda -> { + attacks.addAll(is.cloneWithSaltDerivation(secret, salts, d, md, mda)); + }); + } else { + attacks.addAll(is.cloneWithSaltDerivation(secret, salts, d, md)); } - } - } - } + }); + }); + }); return attacks; } - public SecretKey search() { - List attacks = prepare(); + public SecretKey search(List attacks) { byte[] message = token.getEncodedMessage().getBytes(); byte[] signature = token.getEncodedSignature().getBytes(); for (TokenSigner s : attacks) { @@ -101,4 +91,48 @@ public SecretKey search() { return null; } + public SecretKey parallel() { + int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors(); + List attacks = prepareAdvanced(); + if (NUMBER_OF_CORES < 2) { + return search(attacks); + } + this.executor = Executors.newFixedThreadPool(NUMBER_OF_CORES); + byte[] message = token.getEncodedMessage().getBytes(); + byte[] signature = token.getEncodedSignature().getBytes(); + List> tasks = new ArrayList<>(); + Lists.partition(attacks, Utils.BRUTE_FORCE_CHUNK_SIZE) + .forEach(partition -> { + tasks.add(() -> { + for (TokenSigner s : partition) { + try { + s.fast_unsign(message, signature); + return s.getKey(); + } catch (BadSignatureException ignored) { + } + } + throw new RuntimeException("Key not found"); + }); + }); + try { + return executor.invokeAny(tasks); + } catch (InterruptedException | ExecutionException ignored) { + return null; + } finally { + executor.shutdown(); + } + } + + public void shutdown() { + if (this.executor != null) { + executor.shutdown(); + try { + if (!executor.awaitTermination(500, TimeUnit.MILLISECONDS)) { + executor.shutdownNow(); + } + } catch (InterruptedException ignored) { + + } + } + } } diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/Derivation.java b/src/main/java/one/d4d/sessionless/itsdangerous/Derivation.java index e7c5446..fceee95 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/Derivation.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/Derivation.java @@ -9,7 +9,10 @@ public enum Derivation { @Expose @SerializedName("3") CONCAT("concat"), @Expose @SerializedName("4") DJANGO("django-concat"), @Expose @SerializedName("5") HMAC("hmac"), - @Expose @SerializedName("6") NONE("none"); + @Expose @SerializedName("6") NONE("none"), + @Expose @SerializedName("7") RUBY("RUBY"), + @Expose @SerializedName("8") RUBY5("RUBY5"), + @Expose @SerializedName("9") RUBY5_TRUNCATED("RUBY5_TRUNCATED"); public final String name; Derivation(String name) { diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/crypto/DangerousTokenSigner.java b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/DangerousTokenSigner.java index febc3ea..156f96e 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/crypto/DangerousTokenSigner.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/DangerousTokenSigner.java @@ -6,23 +6,22 @@ import one.d4d.sessionless.utils.Utils; import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; public class DangerousTokenSigner extends TokenSigner { public DangerousTokenSigner(SecretKey key) { super(key); + this.knownDerivations = EnumSet.of(Derivation.HMAC); } - public DangerousTokenSigner(byte sep) { - super(Algorithms.SHA1, Derivation.HMAC, new byte[]{}, new byte[]{}, sep); + public DangerousTokenSigner(byte[] sep) { + this(Algorithms.SHA1, Derivation.HMAC, MessageDerivation.NONE, MessageDigestAlgorithm.SHA256, new byte[]{}, new byte[]{}, sep); } - public DangerousTokenSigner(byte[] secret_key, byte sep) { - super(secret_key, sep); - } - - public DangerousTokenSigner(Algorithms digestMethod, Derivation keyDerivation, byte[] secret_key, byte[] salt, byte sep) { - super(digestMethod, keyDerivation, secret_key, salt, sep); + public DangerousTokenSigner(byte[] secret_key, byte[] salt, byte[] sep) { + this(Algorithms.SHA1, Derivation.HMAC, MessageDerivation.NONE, MessageDigestAlgorithm.SHA256, secret_key, salt, sep); } public DangerousTokenSigner( @@ -32,16 +31,14 @@ public DangerousTokenSigner( MessageDigestAlgorithm digest, byte[] secret_key, byte[] salt, - byte sep) { + byte[] sep) { super(algorithm, keyDerivation, messageDerivation, digest, secret_key, salt, sep); + this.knownDerivations = EnumSet.of(Derivation.CONCAT, Derivation.DJANGO, Derivation.HMAC, Derivation.NONE); } - public DangerousTokenSigner(String digestMethod, String keyDerivation, byte[] secret_key, byte[] salt, byte sep) { - super(Algorithms.valueOf(digestMethod), Derivation.valueOf(keyDerivation), secret_key, salt, sep); - } public byte[] unsign(byte[] value) throws BadSignatureException { - int i = Bytes.lastIndexOf(value, sep); + int i = Collections.lastIndexOfSubList(Bytes.asList(value), Bytes.asList(sep)); byte[] message = Arrays.copyOfRange(value, 0, i); byte[] signature = Arrays.copyOfRange(value, i + 1, value.length); return fast_unsign(message, signature); diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/crypto/DjangoTokenSigner.java b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/DjangoTokenSigner.java index e520892..61ab8fd 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/crypto/DjangoTokenSigner.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/DjangoTokenSigner.java @@ -6,27 +6,19 @@ import one.d4d.sessionless.itsdangerous.MessageDigestAlgorithm; import one.d4d.sessionless.keys.SecretKey; +import java.util.EnumSet; + public class DjangoTokenSigner extends DangerousTokenSigner { public DjangoTokenSigner(SecretKey key) { super(key); + this.knownDerivations = EnumSet.of(Derivation.DJANGO); } - public DjangoTokenSigner(byte sep) { - super(sep); - } - - public DjangoTokenSigner(byte[] secret_key, byte sep) { - super(secret_key, sep); + public DjangoTokenSigner(byte[] secret_key, byte[] salt, byte[] sep) { + this(Algorithms.SHA1, Derivation.DJANGO, MessageDerivation.NONE, MessageDigestAlgorithm.SHA1, secret_key, salt, sep); } - public DjangoTokenSigner(Algorithms digestMethod, Derivation keyDerivation, byte[] secret_key, byte[] salt, byte sep) { - super(digestMethod, keyDerivation, secret_key, salt, sep); - } - - public DjangoTokenSigner(String digestMethod, String keyDerivation, byte[] secret_key, byte[] salt, byte sep) { - super(digestMethod, keyDerivation, secret_key, salt, sep); - } public DjangoTokenSigner( Algorithms algorithm, Derivation keyDerivation, @@ -34,7 +26,8 @@ public DjangoTokenSigner( MessageDigestAlgorithm digest, byte[] secret_key, byte[] salt, - byte sep) { + byte[] sep) { super(algorithm, keyDerivation, messageDerivation, digest, secret_key, salt, sep); + this.knownDerivations = EnumSet.of(Derivation.DJANGO); } } diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/crypto/ExpressTokenSigner.java b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/ExpressTokenSigner.java index 9856308..cd1b147 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/crypto/ExpressTokenSigner.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/ExpressTokenSigner.java @@ -6,21 +6,31 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; +import java.util.EnumSet; public class ExpressTokenSigner extends TokenSigner { public ExpressTokenSigner(SecretKey key) { super(key); + this.knownDerivations = EnumSet.of(Derivation.NONE); } public ExpressTokenSigner() { - super(Algorithms.SHA1, Derivation.NONE, MessageDerivation.NONE, MessageDigestAlgorithm.NONE, new byte[]{}, new byte[]{}, (byte) 0); + this(Algorithms.SHA1, Derivation.NONE, MessageDerivation.NONE, MessageDigestAlgorithm.NONE, new byte[]{}, new byte[]{}, new byte[]{}); } - public ExpressTokenSigner(byte sep) { - super(new byte[]{}, sep); + public ExpressTokenSigner( + Algorithms digestMethod, + Derivation keyDerivation, + MessageDerivation messageDerivation, + MessageDigestAlgorithm digest, + byte[] secret_key, + byte[] salt, + byte[] sep) { + super(digestMethod, keyDerivation, messageDerivation, digest, secret_key, salt, sep); + this.knownDerivations = EnumSet.of(Derivation.NONE); } - public ExpressTokenSigner(byte[] secret_key, byte sep) { + public ExpressTokenSigner(byte[] secret_key, byte[] sep) { super(secret_key, sep); } diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/crypto/JSONWebSignatureTokenSigner.java b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/JSONWebSignatureTokenSigner.java index c0c907f..3f9815f 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/crypto/JSONWebSignatureTokenSigner.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/JSONWebSignatureTokenSigner.java @@ -6,14 +6,19 @@ import one.d4d.sessionless.itsdangerous.MessageDigestAlgorithm; import one.d4d.sessionless.keys.SecretKey; +import java.util.EnumSet; + public class JSONWebSignatureTokenSigner extends TokenSigner { + public JSONWebSignatureTokenSigner(SecretKey key) { super(key); this.keyDerivation = Derivation.NONE; this.messageDigestAlgorithm = MessageDigestAlgorithm.NONE; + this.knownDerivations = EnumSet.of(Derivation.NONE); } - public JSONWebSignatureTokenSigner(byte sep) { + public JSONWebSignatureTokenSigner(byte[] sep) { super(Algorithms.SHA256, Derivation.NONE, MessageDerivation.NONE, MessageDigestAlgorithm.NONE, new byte[]{}, new byte[]{}, sep); + this.knownDerivations = EnumSet.of(Derivation.NONE); } } diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/crypto/OauthProxyTokenSigner.java b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/OauthProxyTokenSigner.java index fa53815..7c6fc71 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/crypto/OauthProxyTokenSigner.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/OauthProxyTokenSigner.java @@ -1,26 +1,59 @@ package one.d4d.sessionless.itsdangerous.crypto; import one.d4d.sessionless.itsdangerous.*; -import one.d4d.sessionless.utils.Utils; import one.d4d.sessionless.keys.SecretKey; +import one.d4d.sessionless.utils.Utils; + import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; +import java.util.EnumSet; -public class OauthProxyTokenSigner extends TokenSigner{ +public class OauthProxyTokenSigner extends TokenSigner { public OauthProxyTokenSigner(SecretKey key) { super(key); + this.knownDerivations = EnumSet.of(Derivation.NONE); } + public OauthProxyTokenSigner() { - super(Algorithms.SHA256, Derivation.NONE, MessageDerivation.NONE, MessageDigestAlgorithm.NONE, new byte[] {}, new byte[] {}, (byte)'|'); + this(new byte[]{}, new byte[]{'|'}); } - public OauthProxyTokenSigner(Algorithms digestMethod, byte[] secret_key, byte sep) { - super(digestMethod, Derivation.NONE, MessageDerivation.NONE, MessageDigestAlgorithm.NONE, secret_key, new byte[] {}, sep); + + public OauthProxyTokenSigner(byte[] secret_key, byte[] sep) { + this(Algorithms.SHA256, Derivation.NONE, MessageDerivation.NONE, MessageDigestAlgorithm.NONE, secret_key, new byte[]{}, sep); } + + public OauthProxyTokenSigner( + Algorithms digestMethod, + Derivation keyDerivation, + MessageDerivation messageDerivation, + MessageDigestAlgorithm digest, + byte[] secret_key, + byte[] salt, + byte[] sep) { + super(digestMethod, keyDerivation, messageDerivation, digest, secret_key, salt, sep); + this.knownDerivations = EnumSet.of(Derivation.NONE); + } + @Override public byte[] derive_key() throws DerivationException { return secret_key; } + + @Override + public byte[] get_signature_unsafe(byte[] value) throws Exception { + byte[][] data = Utils.split(value, sep); + byte[] key = derive_key(); + SecretKeySpec signingKey = new SecretKeySpec(key, digestMethod.name); + Mac mac = Mac.getInstance(digestMethod.name); + mac.init(signingKey); + for (byte[] d : data) { + mac.update(d); + } + byte[] sig = mac.doFinal(); + return Base64.getUrlEncoder().withoutPadding().encode(sig); + } + @Override public byte[] get_signature_bytes(byte[] value) { try { @@ -29,25 +62,12 @@ public byte[] get_signature_bytes(byte[] value) { SecretKeySpec signingKey = new SecretKeySpec(key, digestMethod.name); Mac mac = Mac.getInstance(digestMethod.name); mac.init(signingKey); - for(byte[] d: data) { + for (byte[] d : data) { mac.update(d); } return mac.doFinal(); } catch (Exception e) { - return new byte[] {}; + return new byte[]{}; } } - @Override - public byte[] get_signature_unsafe(byte[] value) throws Exception { - byte[][] data = Utils.split(value, sep); - byte[] key = derive_key(); - SecretKeySpec signingKey = new SecretKeySpec(key, digestMethod.name); - Mac mac = Mac.getInstance(digestMethod.name); - mac.init(signingKey); - for(byte[] d: data) { - mac.update(d); - } - byte[] sig = mac.doFinal(); - return Base64.getUrlEncoder().withoutPadding().encode(sig); - } } diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/crypto/RubyTokenSigner.java b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/RubyTokenSigner.java new file mode 100644 index 0000000..3e79b7e --- /dev/null +++ b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/RubyTokenSigner.java @@ -0,0 +1,47 @@ +package one.d4d.sessionless.itsdangerous.crypto; + +import one.d4d.sessionless.itsdangerous.Algorithms; +import one.d4d.sessionless.itsdangerous.Derivation; +import one.d4d.sessionless.itsdangerous.MessageDerivation; +import one.d4d.sessionless.itsdangerous.MessageDigestAlgorithm; +import one.d4d.sessionless.keys.SecretKey; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.util.EnumSet; + +public class RubyTokenSigner extends TokenSigner { + public RubyTokenSigner(SecretKey key) { + super(key); + this.knownDerivations = EnumSet.of(Derivation.RUBY); + } + + public RubyTokenSigner(byte[] sep) { + this(new byte[]{}, sep); + } + + public RubyTokenSigner(byte[] secret_key, byte[] sep) { + this(Algorithms.SHA1, Derivation.RUBY, MessageDerivation.NONE, MessageDigestAlgorithm.NONE, secret_key, new byte[]{}, sep); + } + + public RubyTokenSigner( + Algorithms digestMethod, + Derivation keyDerivation, + MessageDerivation messageDerivation, + MessageDigestAlgorithm digest, + byte[] secret_key, + byte[] salt, + byte[] sep) { + super(digestMethod, keyDerivation, messageDerivation, digest, secret_key, salt, sep); + this.knownDerivations = EnumSet.of(Derivation.RUBY); + } + + @Override + public byte[] get_signature_unsafe(byte[] value) throws Exception { + byte[] key = derive_key(); + SecretKeySpec signingKey = new SecretKeySpec(key, digestMethod.name); + Mac mac = Mac.getInstance(digestMethod.name); + mac.init(signingKey); + return mac.doFinal(value); + } +} diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/crypto/TokenSigner.java b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/TokenSigner.java index 8d9d127..50514dd 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/crypto/TokenSigner.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/TokenSigner.java @@ -14,26 +14,25 @@ import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; -import java.util.Arrays; -import java.util.Base64; -import java.util.UUID; +import java.util.*; public class TokenSigner implements Cloneable { public Algorithms digestMethod = Algorithms.SHA1; public Derivation keyDerivation = Derivation.HMAC; public MessageDerivation messageDerivation = MessageDerivation.NONE; public MessageDigestAlgorithm messageDigestAlgorithm = MessageDigestAlgorithm.SHA1; + public Set knownDerivations = EnumSet.allOf(Derivation.class); public byte[] secret_key; public byte[] salt = "itsdangerous.Signer".getBytes(); - public byte sep; + public byte[] sep; - public TokenSigner(Algorithms digestMethod, byte[] secret_key, byte sep) { + public TokenSigner(Algorithms digestMethod, byte[] secret_key, byte[] sep) { this.digestMethod = digestMethod; this.secret_key = secret_key; this.sep = sep; } - public TokenSigner(byte[] secret_key, byte sep) { + public TokenSigner(byte[] secret_key, byte[] sep) { this.secret_key = secret_key; this.sep = sep; } @@ -44,11 +43,11 @@ public TokenSigner(SecretKey key) { this.messageDerivation = key.getMessageDerivation(); this.secret_key = key.getSecret().getBytes(); this.salt = key.getSalt().getBytes(); - this.sep = key.getSeparator().getBytes().length > 0 ? key.getSeparator().getBytes()[0] : 46; + this.sep = key.getSeparator().getBytes().length > 0 ? key.getSeparator().getBytes() : new byte[]{46}; this.messageDigestAlgorithm = key.getMessageDigestAlgorythm(); } - public TokenSigner(Algorithms digestMethod, Derivation keyDerivation, byte[] secret_key, byte[] salt, byte sep) { + public TokenSigner(Algorithms digestMethod, Derivation keyDerivation, byte[] secret_key, byte[] salt, byte[] sep) { this.digestMethod = digestMethod; this.keyDerivation = keyDerivation; this.secret_key = secret_key; @@ -56,7 +55,7 @@ public TokenSigner(Algorithms digestMethod, Derivation keyDerivation, byte[] sec this.sep = sep; } - public TokenSigner(Algorithms digestMethod, Derivation keyDerivation, MessageDerivation messageDerivation, MessageDigestAlgorithm digest, byte[] secret_key, byte[] salt, byte sep) { + public TokenSigner(Algorithms digestMethod, Derivation keyDerivation, MessageDerivation messageDerivation, MessageDigestAlgorithm digest, byte[] secret_key, byte[] salt, byte[] sep) { this.digestMethod = digestMethod; this.keyDerivation = keyDerivation; this.messageDerivation = messageDerivation; @@ -82,24 +81,24 @@ public void setKeyDerivation(Derivation keyDerivation) { this.keyDerivation = keyDerivation; } - public void setMessageDerivation(MessageDerivation messageDerivation) { - this.messageDerivation = messageDerivation; - } - public MessageDerivation getMessageDerivation() { return messageDerivation; } + public void setMessageDerivation(MessageDerivation messageDerivation) { + this.messageDerivation = messageDerivation; + } + public MessageDigestAlgorithm getMessageDigestAlgorythm() { return messageDigestAlgorithm; } - public byte getSep() { + public byte[] getSep() { return sep; } - public void setSep(byte sep) { + public void setSep(byte[] sep) { this.sep = sep; } @@ -124,9 +123,9 @@ public void setMessageDigestAlgorithm(MessageDigestAlgorithm messageDigestAlgori } public byte[] derive_message(byte[] value) { - switch (messageDerivation){ + switch (messageDerivation) { case TORNADO -> { - return Bytes.concat(value, new byte[] {sep}); + return Bytes.concat(value, sep); } case CONCAT -> { return Bytes.concat(Utils.split(value, sep)); @@ -136,10 +135,9 @@ public byte[] derive_message(byte[] value) { } } } + public byte[] derive_key() throws DerivationException { try { - if (messageDigestAlgorithm == MessageDigestAlgorithm.NONE) return secret_key; - MessageDigest msdDigest = MessageDigest.getInstance(messageDigestAlgorithm.name); switch (keyDerivation) { case PBKDF2HMAC -> { KeySpec spec = new PBEKeySpec( @@ -152,14 +150,20 @@ public byte[] derive_key() throws DerivationException { return f.generateSecret(spec).getEncoded(); } case HASH -> { + if (messageDigestAlgorithm == MessageDigestAlgorithm.NONE) return secret_key; + MessageDigest msdDigest = MessageDigest.getInstance(messageDigestAlgorithm.name); msdDigest.update(secret_key); return msdDigest.digest(); } case CONCAT -> { + if (messageDigestAlgorithm == MessageDigestAlgorithm.NONE) return secret_key; + MessageDigest msdDigest = MessageDigest.getInstance(messageDigestAlgorithm.name); msdDigest.update(Bytes.concat(salt, secret_key)); return msdDigest.digest(); } case DJANGO -> { + if (messageDigestAlgorithm == MessageDigestAlgorithm.NONE) return secret_key; + MessageDigest msdDigest = MessageDigest.getInstance(messageDigestAlgorithm.name); msdDigest.update(Bytes.concat(salt, "signer".getBytes(), secret_key)); return msdDigest.digest(); } @@ -172,6 +176,36 @@ public byte[] derive_key() throws DerivationException { case NONE -> { return secret_key; } + case RUBY -> { + KeySpec spec = new PBEKeySpec( + (new String(secret_key)).toCharArray(), + salt, + 1000, + 64 * 8 + ); + SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + return f.generateSecret(spec).getEncoded(); + } + case RUBY5 -> { + KeySpec spec = new PBEKeySpec( + (new String(secret_key)).toCharArray(), + salt, + 65536, + 64 * 8 + ); + SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + return f.generateSecret(spec).getEncoded(); + } + case RUBY5_TRUNCATED -> { + KeySpec spec = new PBEKeySpec( + (new String(secret_key)).toCharArray(), + salt, + 65536, + 32 * 8 + ); + SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + return f.generateSecret(spec).getEncoded(); + } default -> throw new DerivationException("Unknown key derivation method"); } } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { @@ -217,7 +251,7 @@ public byte[] get_signature_bytes(byte[] value) { } public byte[] sign(byte[] value) { - return Bytes.concat(value, new byte[]{sep}, get_signature(value)); + return Bytes.concat(value, sep, get_signature(value)); } public boolean verify_signature(byte[] value, byte[] sign) { @@ -240,7 +274,7 @@ public boolean verify_signature_bytes(byte[] value, byte[] sign) { } public byte[] unsign(byte[] value) throws BadSignatureException { - int i = Bytes.lastIndexOf(value, sep); + int i = Collections.lastIndexOfSubList(Bytes.asList(value), Bytes.asList(sep)); byte[] message = Arrays.copyOfRange(value, 0, i); byte[] signature = Arrays.copyOfRange(value, i + 1, value.length); return fast_unsign(message, signature); @@ -266,7 +300,7 @@ public SecretKey getKey() { UUID.randomUUID().toString(), new String(secret_key), new String(salt), - String.valueOf((char) sep), + new String(sep), digestMethod, keyDerivation, messageDerivation, messageDigestAlgorithm); @@ -288,4 +322,56 @@ public TokenSigner clone() { throw new AssertionError(); } } + + public List cloneWithSaltDerivation(String secret, Set salts) { + List copies = new ArrayList<>(); + if (keyDerivation == Derivation.NONE || keyDerivation == Derivation.HASH) { + TokenSigner s = this.clone(); + s.setSecretKey(secret.getBytes()); + copies.add(s); + } else { + salts.forEach(salt -> { + TokenSigner s = this.clone(); + s.setSecretKey(secret.getBytes()); + s.setSalt(salt.getBytes()); + copies.add(s); + }); + } + return copies; + } + + public List cloneWithSaltDerivation( + String secret, + Set salts, + Derivation keyDerivation) { + this.keyDerivation = keyDerivation; + return this.cloneWithSaltDerivation(secret, salts); + } + + public List cloneWithSaltDerivation( + String secret, + Set salts, + Derivation keyDerivation, + MessageDerivation messageDerivation, + MessageDigestAlgorithm messageDigestAlgorithm) { + this.keyDerivation = keyDerivation; + this.messageDerivation = messageDerivation; + this.messageDigestAlgorithm = messageDigestAlgorithm; + return this.cloneWithSaltDerivation(secret, salts); + } + + public List cloneWithSaltDerivation( + String secret, + Set salts, + Derivation keyDerivation, + MessageDerivation messageDerivation) { + this.keyDerivation = keyDerivation; + this.messageDerivation = messageDerivation; + return this.cloneWithSaltDerivation(secret, salts); + } + + public Set getKnownDerivations() { + this.knownDerivations.add(keyDerivation); + return knownDerivations; + } } diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/crypto/TornadoTokenSigner.java b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/TornadoTokenSigner.java index 2a0987b..035cc96 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/crypto/TornadoTokenSigner.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/TornadoTokenSigner.java @@ -8,34 +8,53 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; -public class TornadoTokenSigner extends TokenSigner{ +public class TornadoTokenSigner extends TokenSigner { public TornadoTokenSigner(SecretKey key) { super(key); + this.knownDerivations = EnumSet.of(Derivation.NONE); } + public TornadoTokenSigner() { - super(Algorithms.SHA1, Derivation.NONE, MessageDerivation.NONE, MessageDigestAlgorithm.NONE,new byte[] {}, new byte[] {}, (byte) '|'); + this(new byte[]{}, new byte[]{'|'}); } - public TornadoTokenSigner( byte sep) { - super(new byte[] {}, sep); + + public TornadoTokenSigner(byte[] secret_key, byte[] sep) { + this(Algorithms.SHA1, Derivation.NONE, MessageDerivation.NONE, MessageDigestAlgorithm.NONE, secret_key, new byte[]{}, sep); } - public TornadoTokenSigner( byte[] secret_key, byte sep) { - super(secret_key, sep); + + public TornadoTokenSigner( + Algorithms digestMethod, + Derivation keyDerivation, + MessageDerivation messageDerivation, + MessageDigestAlgorithm digest, + byte[] secret_key, + byte[] salt, + byte[] sep) { + super(digestMethod, keyDerivation, messageDerivation, digest, secret_key, salt, sep); + this.knownDerivations = EnumSet.of(Derivation.NONE); } @Override public byte[] derive_key() throws DerivationException { return secret_key; } + @Override - public boolean verify_signature(byte[] value, byte[] signature) { + public byte[] get_signature(byte[] value) { try { - byte[] expected = get_signature_bytes(value); - return Arrays.equals(expected, signature); - }catch (Exception e){ - return false; + byte[] key = derive_key(); + SecretKeySpec signingKey = new SecretKeySpec(key, digestMethod.name); + Mac mac = Mac.getInstance(digestMethod.name); + mac.init(signingKey); + return mac.doFinal(value); + } catch (Exception e) { + return new byte[]{}; } } + @Override public byte[] get_signature_unsafe(byte[] value) throws Exception { byte[] key = derive_key(); @@ -44,8 +63,9 @@ public byte[] get_signature_unsafe(byte[] value) throws Exception { mac.init(signingKey); return mac.doFinal(value); } + @Override - public byte[] get_signature(byte[] value) { + public byte[] get_signature_bytes(byte[] value) { try { byte[] key = derive_key(); SecretKeySpec signingKey = new SecretKeySpec(key, digestMethod.name); @@ -53,29 +73,26 @@ public byte[] get_signature(byte[] value) { mac.init(signingKey); return mac.doFinal(value); } catch (Exception e) { - return new byte[] {}; + return new byte[]{}; } } @Override - public byte[] get_signature_bytes(byte[] value) { + public boolean verify_signature(byte[] value, byte[] signature) { try { - byte[] key = derive_key(); - SecretKeySpec signingKey = new SecretKeySpec(key, digestMethod.name); - Mac mac = Mac.getInstance(digestMethod.name); - mac.init(signingKey); - return mac.doFinal(value); + byte[] expected = get_signature_bytes(value); + return Arrays.equals(expected, signature); } catch (Exception e) { - return new byte[] {}; + return false; } } @Override public byte[] unsign(byte[] value) throws BadSignatureException { - int i = Bytes.lastIndexOf(value, sep); + int i = Collections.lastIndexOfSubList(Bytes.asList(value), Bytes.asList(sep)); // Note! Tornado uses last delimiter for signature calculation. - byte[] message = Arrays.copyOfRange(value, 0, i+1); - byte[] signature = Arrays.copyOfRange(value, i+1, value.length); + byte[] message = Arrays.copyOfRange(value, 0, i + 1); + byte[] signature = Arrays.copyOfRange(value, i + 1, value.length); byte[] sign = Utils.normalization(signature); switch (sign.length) { case 28 -> digestMethod = Algorithms.SHA224; diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/model/DangerousSignedToken.java b/src/main/java/one/d4d/sessionless/itsdangerous/model/DangerousSignedToken.java index ca8d334..9b15f99 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/model/DangerousSignedToken.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/model/DangerousSignedToken.java @@ -1,7 +1,10 @@ package one.d4d.sessionless.itsdangerous.model; import com.nimbusds.jwt.JWTClaimsSet; -import one.d4d.sessionless.itsdangerous.*; +import one.d4d.sessionless.itsdangerous.Algorithms; +import one.d4d.sessionless.itsdangerous.Derivation; +import one.d4d.sessionless.itsdangerous.MessageDerivation; +import one.d4d.sessionless.itsdangerous.MessageDigestAlgorithm; import one.d4d.sessionless.itsdangerous.crypto.DangerousTokenSigner; import one.d4d.sessionless.utils.Utils; @@ -10,10 +13,10 @@ public class DangerousSignedToken extends SignedToken { public final String timestamp; public String payload; - public byte separator; + public byte[] separator; - public DangerousSignedToken(byte separator, String payload, String timestamp, String signature) { - super(String.format("%s%c%s", payload, separator, timestamp)); + public DangerousSignedToken(byte[] separator, String payload, String timestamp, String signature) { + super(String.format("%s%s%s", payload, new String(separator), timestamp)); this.separator = separator; this.payload = payload; this.timestamp = timestamp; @@ -22,7 +25,7 @@ public DangerousSignedToken(byte separator, String payload, String timestamp, St } public DangerousSignedToken( - byte separator, + byte[] separator, String payload, String timestamp, String signature, @@ -30,7 +33,7 @@ public DangerousSignedToken( Derivation derivation, MessageDerivation messageDerivation, MessageDigestAlgorithm digest) { - super(String.format("%s%c%s", payload, separator, timestamp)); + super(String.format("%s%s%s", payload, new String(separator), timestamp)); this.separator = separator; this.payload = payload; this.timestamp = timestamp; @@ -52,10 +55,27 @@ public void setSigner(DangerousTokenSigner signer) { public String dumps() { byte[] header = Base64.getUrlEncoder().withoutPadding().encode(payload.getBytes()); - String message = String.format("%s%c%s", new String(header), (char) this.separator, this.timestamp); + String message = String.format("%s%s%s", new String(header), new String(this.separator), this.timestamp); return new String(signer.sign(message.getBytes())); } + public String toString() { + try { + StringBuilder sb = new StringBuilder(); + byte[] json = Utils.base64Decompress(this.payload.getBytes()); + sb.append(new String(json)).append(new String(this.separator)); + sb.append(Utils.base64timestamp(this.timestamp.getBytes())).append(new String(this.separator)); + sb.append(this.signature); + return sb.toString(); + } catch (Exception e) { + return String.format("%s%s%s%s%s", payload, new String(separator), timestamp, new String(separator), signature); + } + } + + public String serialize() { + return String.format("%s%s%s%s%s", payload, new String(separator), timestamp, new String(separator), signature); + } + public void resign() throws Exception { this.signature = new String(signer.get_signature_unsafe(message.getBytes())); } @@ -67,28 +87,13 @@ public void setClaims(JWTClaimsSet claims) { } else { this.payload = new String(Base64.getUrlEncoder().withoutPadding().encode(claims.toString().getBytes())); } - this.message = String.format("%s%c%s", payload, separator, timestamp); - } - - - public String toString() { - try { - StringBuilder sb = new StringBuilder(); - byte[] json = Utils.base64Decompress(this.payload.getBytes()); - sb.append(new String(json)).append(this.separator); - sb.append(Utils.base64timestamp(this.timestamp.getBytes())).append(this.separator); - sb.append(this.signature); - return sb.toString(); - } catch (Exception e) { - return String.format("%s%c%s%c%s", payload, (char) separator, timestamp, (char) separator, signature); - } + this.message = String.format("%s%s%s", payload, new String(separator), timestamp); } - public String serialize() { - return String.format("%s%c%s%c%s", payload, (char) separator, timestamp, (char) separator, signature); + public byte[] getSignature() { + return Base64.getUrlDecoder().decode(signature); } - public String getPayload() { return payload; } @@ -105,11 +110,7 @@ public String getTimestamp() { } } - public byte[] getSignature() { - return Base64.getUrlDecoder().decode(signature); - } - public byte[] getSeparator() { - return new byte[]{separator}; + return separator; } } \ No newline at end of file diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/model/DjangoSignedToken.java b/src/main/java/one/d4d/sessionless/itsdangerous/model/DjangoSignedToken.java index 0380398..e683fe5 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/model/DjangoSignedToken.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/model/DjangoSignedToken.java @@ -8,7 +8,7 @@ public class DjangoSignedToken extends DangerousSignedToken { - public DjangoSignedToken(byte separator, String payload, String timestamp, String signature) { + public DjangoSignedToken(byte[] separator, String payload, String timestamp, String signature) { super(separator, payload, timestamp, signature, Algorithms.SHA1, Derivation.DJANGO, MessageDerivation.NONE, MessageDigestAlgorithm.SHA1); } @@ -17,12 +17,12 @@ public String toString() { try { StringBuilder sb = new StringBuilder(); byte[] json = Utils.base64Decompress(this.payload.getBytes()); - sb.append(new String(json)).append(this.separator); - sb.append(Utils.base62timestamp(this.timestamp.getBytes())).append(this.separator); + sb.append(new String(json)).append(new String(this.separator)); + sb.append(Utils.base62timestamp(this.timestamp.getBytes())).append(new String(this.separator)); sb.append(this.signature); return sb.toString(); } catch (Exception e) { - return String.format("%s%c%s%c%s", payload, (char) separator, timestamp, (char) separator, signature); + return String.format("%s%s%s%s%s", payload, new String(separator), timestamp, new String(separator), signature); } } diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/model/JSONWebSignature.java b/src/main/java/one/d4d/sessionless/itsdangerous/model/JSONWebSignature.java index f06b531..723246a 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/model/JSONWebSignature.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/model/JSONWebSignature.java @@ -8,10 +8,10 @@ public class JSONWebSignature extends SignedToken { public String header; public String payload; - public byte separator; + public byte[] separator; - public JSONWebSignature(String header, String payload, String signature, byte separator) { - super(String.format("%s%c%s", header, (char) separator, payload)); + public JSONWebSignature(String header, String payload, String signature, byte[] separator) { + super(String.format("%s%s%s", header, new String(separator), payload)); this.header = header; this.payload = payload; this.signature = signature; @@ -44,16 +44,16 @@ public void setPayload(String payload) { } public byte[] getSeparator() { - return new byte[]{separator}; + return separator; } - public void setSeparator(byte separator) { + public void setSeparator(byte[] separator) { this.separator = separator; } @Override public String serialize() { - return String.format("%s%c%s%c%s", header, (char) separator, payload, (char) separator, signature); + return String.format("%s%s%s%s%s", header, new String(separator), payload, new String(separator), signature); } @@ -64,7 +64,7 @@ public void resign() throws Exception { @Override public void setClaims(JWTClaimsSet claims) { this.payload = new String(Base64.getUrlEncoder().withoutPadding().encode(claims.toString().getBytes())); - this.message = String.format("%s%c%s", header, (char) separator, payload); + this.message = String.format("%s%s%s", header, new String(separator), payload); } @Override diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/model/OauthProxySignedToken.java b/src/main/java/one/d4d/sessionless/itsdangerous/model/OauthProxySignedToken.java index 0a88e93..17374b7 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/model/OauthProxySignedToken.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/model/OauthProxySignedToken.java @@ -8,15 +8,14 @@ import java.util.Base64; public class OauthProxySignedToken extends SignedToken { - - public static byte separator = '|'; + public static byte[] separator = {'|'}; public String parameter; public String payload; public String timestamp; public OauthProxySignedToken(String parameter, String payload, String timestamp, String signature) { - super(String.format("%s%c%s%c%s", parameter, separator, payload, separator, timestamp)); + super(String.format("%s%s%s%s%s", parameter, new String(separator), payload, new String(separator), timestamp)); this.payload = payload; this.timestamp = timestamp; this.signature = signature; @@ -36,26 +35,10 @@ public String getTimestamp() { return Utils.timestampSeconds(timestamp); } - public byte[] getSignature() { - return Base64.getUrlDecoder().decode(signature); - } - public void setSigner(OauthProxyTokenSigner signer) { this.signer = signer; } - @Override - public void resign() throws Exception { - byte[] value = message.getBytes(); - this.signature = new String(signer.get_signature_unsafe(value)); - } - - @Override - public void setClaims(JWTClaimsSet claims) { - this.payload = new String(Base64.getUrlEncoder().encode(claims.toString().getBytes())); - this.message = String.format("%s%c%s%c%s", parameter, separator, payload, separator, timestamp); - } - public byte[] dumps(String payload) { try { return signer.sign(payload.getBytes()); @@ -64,19 +47,33 @@ public byte[] dumps(String payload) { } } - public void unsign() throws BadSignatureException { signer.fast_unsign(this.message.getBytes(), this.signature.getBytes()); } - - public byte getSeparator() { + public byte[] getSeparator() { return separator; } @Override public String serialize() { - return String.format("%s%c%s%c%s", payload, separator, timestamp, separator, signature); + return String.format("%s%s%s%s%s", payload, new String(separator), timestamp, new String(separator), signature); + } + + @Override + public void resign() throws Exception { + byte[] value = message.getBytes(); + this.signature = new String(signer.get_signature_unsafe(value)); + } + + @Override + public void setClaims(JWTClaimsSet claims) { + this.payload = new String(Base64.getUrlEncoder().encode(claims.toString().getBytes())); + this.message = String.format("%s%s%s%s%s", parameter, new String(separator), payload, new String(separator), timestamp); + } + + public byte[] getSignature() { + return Base64.getUrlDecoder().decode(signature); } } diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/model/RubySignedToken.java b/src/main/java/one/d4d/sessionless/itsdangerous/model/RubySignedToken.java new file mode 100644 index 0000000..f1d0eeb --- /dev/null +++ b/src/main/java/one/d4d/sessionless/itsdangerous/model/RubySignedToken.java @@ -0,0 +1,28 @@ +package one.d4d.sessionless.itsdangerous.model; + +import burp.config.Signers; +import one.d4d.sessionless.itsdangerous.crypto.RubyTokenSigner; +import one.d4d.sessionless.utils.HexUtils; + +public class RubySignedToken extends UnknownSignedToken { + + public RubySignedToken(String message, String signature) { + this(message, signature, "--".getBytes()); + } + + public RubySignedToken(String message, String signature, byte[] separator) { + super(message, signature, separator); + this.signer = new RubyTokenSigner(separator); + } + + @Override + public void resign() throws Exception { + this.signature = HexUtils.encodeHex(signer.get_signature_unsafe(message.getBytes())); + } + + @Override + public String getSignersName() { + return Signers.RUBY.name(); + } + +} diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/model/SignedTokenObjectFinder.java b/src/main/java/one/d4d/sessionless/itsdangerous/model/SignedTokenObjectFinder.java index 6c333ef..755dd83 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/model/SignedTokenObjectFinder.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/model/SignedTokenObjectFinder.java @@ -3,6 +3,7 @@ import burp.api.montoya.http.message.Cookie; import burp.api.montoya.http.message.params.ParsedHttpParameter; import burp.config.SignerConfig; +import burp.config.Signers; import com.google.common.base.CharMatcher; import one.d4d.sessionless.utils.Utils; import org.apache.commons.lang3.StringUtils; @@ -22,7 +23,7 @@ public class SignedTokenObjectFinder { private static final String SEPARATOR_REGEX = "[.:!#$*;@|~]"; private static final String SIGNER_REGEX = String.format("\\.?%s*%s+%s*%s+%s+", BASE64_REGEX, SEPARATOR_REGEX, BASE64_REGEX, SEPARATOR_REGEX, BASE64_REGEX); private static final Pattern SIGNER_OBJECT_PATTERN = Pattern.compile(String.format("(%s)", SIGNER_REGEX)); - private static final String UNKNOWN_SIGNED_STRING_REGEXP = String.format("(%s*%s%s{26,86})",BASE64_REGEX, SEPARATOR_REGEX, BASE64_REGEX); + private static final String UNKNOWN_SIGNED_STRING_REGEXP = String.format("(%s*%s%s{26,86})", BASE64_REGEX, SEPARATOR_REGEX, BASE64_REGEX); private static final Pattern UNKNOWN_SIGNED_STRING_PATTERN = Pattern.compile(UNKNOWN_SIGNED_STRING_REGEXP); public static boolean containsSignedTokenObjects(SignerConfig signerConfig, String text, List cookies, List params) { @@ -32,9 +33,9 @@ public static boolean containsSignedTokenObjects(SignerConfig signerConfig, Stri public static List extractSignedTokenObjects(SignerConfig signerConfig, String text, List cookies, List params) { List signedTokensObjects = new ArrayList<>(); - Map cookiesToHashMap = convertCookiesToHashMap(cookies); - Map paramsToHashMap = convertParamsToHashMap(params); - if (signerConfig.isEnableDangerous()) { + Map cookiesToHashMap = convertCookiesToHashMap(cookies); + Map paramsToHashMap = convertParamsToHashMap(params); + if (signerConfig.isEnabled(Signers.DANGEROUS)) { Set tokenCandidates = findCandidateSignedTokenObjectsWithin(text); for (String candidate : tokenCandidates) { parseToken(candidate) @@ -42,27 +43,35 @@ public static List extractSignedTokenObjects(SignerConfig si signedTokensObjects.add(new MutableSignedToken(candidate, value))); } } - if (signerConfig.isEnableExpress()) { + if (signerConfig.isEnabled(Signers.EXPRESS)) { signedTokensObjects.addAll(parseExpressSignedParams(cookiesToHashMap)); signedTokensObjects.addAll(parseExpressSignedParams(paramsToHashMap)); } - if(signerConfig.isEnableOAuth()) { - cookiesToHashMap.forEach((name,value) -> { + if (signerConfig.isEnabled(Signers.OAUTH)) { + cookiesToHashMap.forEach((name, value) -> { parseOauthProxySignedToken(name, value).ifPresent(v -> signedTokensObjects.add(new MutableSignedToken(value, v))); }); - paramsToHashMap.forEach((name,value) -> { + paramsToHashMap.forEach((name, value) -> { parseOauthProxySignedToken(name, value).ifPresent(v -> signedTokensObjects.add(new MutableSignedToken(value, v))); }); } - if(signerConfig.isEnableTornado()) { - cookiesToHashMap.forEach((name,value) -> { + if (signerConfig.isEnabled(Signers.TORNADO)) { + cookiesToHashMap.forEach((name, value) -> { parseTornadoSignedToken(name, value).ifPresent(v -> signedTokensObjects.add(new MutableSignedToken(value, v))); }); - paramsToHashMap.forEach((name,value) -> { + paramsToHashMap.forEach((name, value) -> { parseTornadoSignedToken(name, value).ifPresent(v -> signedTokensObjects.add(new MutableSignedToken(value, v))); }); } - if(signerConfig.isEnableUnknown()) { + if (signerConfig.isEnabled(Signers.RUBY)) { + cookiesToHashMap.forEach((name, value) -> { + parseRubySignedToken(name, value).ifPresent(v -> signedTokensObjects.add(new MutableSignedToken(value, v))); + }); + paramsToHashMap.forEach((name, value) -> { + parseRubySignedToken(name, value).ifPresent(v -> signedTokensObjects.add(new MutableSignedToken(value, v))); + }); + } + if (signerConfig.isEnabled(Signers.UNKNOWN)) { Set stringCandidates = findCandidateUnknownSignedStringWithin(text); for (String candidate : stringCandidates) { parseUnknownSignedString(candidate) @@ -97,6 +106,7 @@ public static Set findCandidateUnknownSignedStringWithin(String text) { return strings; } + public static Optional parseToken(String candidate) { Optional dst = parseDjangoSignedToken(candidate); return dst.isPresent() ? dst : parseDangerousSignedToken(candidate); @@ -110,7 +120,7 @@ private static List parseCookies(List params) { return parseSignedTokenWithinCookies(params); } - private static Map convertParamsToHashMap(List params) { + private static Map convertParamsToHashMap(List params) { if (params == null) return new HashMap<>(); return params.stream() .collect(Collectors.toMap( @@ -118,7 +128,8 @@ private static Map convertParamsToHashMap(List convertCookiesToHashMap(List cookies) { + + private static Map convertCookiesToHashMap(List cookies) { if (cookies == null) return new HashMap<>(); return cookies.stream() .collect(Collectors.toMap( @@ -126,7 +137,8 @@ private static Map convertCookiesToHashMap(List cookies) Cookie::value) ); } - public static List parseExpressSignedParams(Map params) { + + public static List parseExpressSignedParams(Map params) { List signedTokensObjects = new ArrayList<>(); if (params != null) { List signatures = params @@ -137,7 +149,7 @@ public static List parseExpressSignedParams(Map parseExpressSignedParams(Map parseSignedTokenWithinHashMap(Map params) { + + public static List parseSignedTokenWithinHashMap(Map params) { List signedTokensObjects = new ArrayList<>(); if (params != null) { List signatures = params.keySet().stream().filter(value -> value.toUpperCase().contains(SIGNED_PARAM)) @@ -160,7 +173,7 @@ public static List parseSignedTokenWithinHashMap(Map parseSignedTokenWithinHashMap(Map { + params.forEach((name, value) -> { Optional candidate = parseOauthProxySignedToken(name, value); candidate = candidate.isPresent() ? candidate : parseTornadoSignedToken(name, value); candidate.ifPresent(v -> signedTokensObjects.add(new MutableSignedToken(value, v))); @@ -180,6 +193,7 @@ public static List parseSignedTokenWithinHashMap(Map parseSignedTokenWithinParams(List params) { List signedTokensObjects = new ArrayList<>(); if (params != null) { @@ -284,6 +298,7 @@ private static String extractFormattedField(String field) { private static String unquoteCookie(String cookie) { return cookie.replaceAll("^\"|\"$", ""); } + public static Optional parseTornadoSignedToken(String key, String value) { char sep = '|'; String[] parts = StringUtils.split(unquoteCookie(value), sep); @@ -353,12 +368,12 @@ private static Optional parseDangerousSignedToken(String text) { String signature = parts[2]; try { int length = Utils.normalization(signature.getBytes()).length; - if(Arrays.stream(SIGNATURES_LENGTH).noneMatch(x -> x == length)) return Optional.empty(); + if (Arrays.stream(SIGNATURES_LENGTH).noneMatch(x -> x == length)) return Optional.empty(); } catch (Exception e) { return Optional.empty(); } - DangerousSignedToken t = new DangerousSignedToken((byte) separator, header, timestamp, signature); + DangerousSignedToken t = new DangerousSignedToken(new byte[]{(byte) separator}, header, timestamp, signature); return Optional.of(t); } @@ -392,18 +407,19 @@ public static Optional parseDjangoSignedToken(String text) { String signature = parts[2]; try { int length = Utils.normalization(signature.getBytes()).length; - if(Arrays.stream(SIGNATURES_LENGTH).noneMatch(x -> x == length)) return Optional.empty(); + if (Arrays.stream(SIGNATURES_LENGTH).noneMatch(x -> x == length)) return Optional.empty(); } catch (Exception e) { return Optional.empty(); } DjangoSignedToken t = new DjangoSignedToken( - (byte) separator, + new byte[]{(byte) separator}, header, timestamp, signature); return Optional.of(t); } + public static Optional parseJSONWebSignature(String text) { char separator = '.'; boolean compressed = false; @@ -419,14 +435,14 @@ public static Optional parseJSONWebSignature(String text) { // Header parser String header = compressed ? String.format(".%s", parts[0]) : parts[0]; try { - if(!Utils.isValidJSON(Utils.base64Decompress(header.getBytes()))) return Optional.empty(); + if (!Utils.isValidJSON(Utils.base64Decompress(header.getBytes()))) return Optional.empty(); } catch (Exception e) { return Optional.empty(); } // Body parser String body = parts[1]; try { - if(!Utils.isValidJSON(Utils.base64Decompress(body.getBytes()))) return Optional.empty(); + if (!Utils.isValidJSON(Utils.base64Decompress(body.getBytes()))) return Optional.empty(); } catch (Exception e) { return Optional.empty(); } @@ -434,11 +450,11 @@ public static Optional parseJSONWebSignature(String text) { String signature = parts[2]; try { int length = Utils.base64Decompress(signature.getBytes()).length; - if(Arrays.stream(SIGNATURES_LENGTH).noneMatch(x -> x == length)) return Optional.empty(); + if (Arrays.stream(SIGNATURES_LENGTH).noneMatch(x -> x == length)) return Optional.empty(); } catch (Exception e) { return Optional.empty(); } - SignedToken t = new JSONWebSignature(header, body, signature, (byte) separator); + SignedToken t = new JSONWebSignature(header, body, signature, new byte[]{(byte) separator}); return Optional.of(t); } @@ -452,16 +468,36 @@ public static Optional parseUnknownSignedString(String text) { } if (separator == 0) return Optional.empty(); int index = text.lastIndexOf(separator); - String message = text.substring(0,index); + String message = text.substring(0, index); String signature = text.substring(index + 1); try { int length = Utils.normalization(signature.getBytes()).length; - if(Arrays.stream(SIGNATURES_LENGTH).noneMatch(x -> x == length)) return Optional.empty(); + if (Arrays.stream(SIGNATURES_LENGTH).noneMatch(x -> x == length)) return Optional.empty(); } catch (Exception e) { return Optional.empty(); } - UnknownSignedToken t = new UnknownSignedToken(message, signature, (byte) separator); + UnknownSignedToken t = new UnknownSignedToken(message, signature, new byte[]{(byte) separator}); return Optional.of(t); } + + public static Optional parseRubySignedToken(String key, String value) { + String sep = "--"; + String[] parts = StringUtils.split(value, sep); + if (parts.length == 2) { + String payload = parts[0]; + String signature = parts[1]; + try { + Base64.getUrlDecoder().decode(payload); + int length = Utils.normalization(signature.getBytes()).length; + if (Arrays.stream(SIGNATURES_LENGTH).noneMatch(x -> x == length)) return Optional.empty(); + } catch (Exception e) { + return Optional.empty(); + } + RubySignedToken t = new RubySignedToken(payload, signature); + return Optional.of(t); + } + + return Optional.empty(); + } } diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/model/UnknownSignedToken.java b/src/main/java/one/d4d/sessionless/itsdangerous/model/UnknownSignedToken.java index d8a08a2..1ad981a 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/model/UnknownSignedToken.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/model/UnknownSignedToken.java @@ -1,5 +1,6 @@ package one.d4d.sessionless.itsdangerous.model; +import burp.config.Signers; import com.nimbusds.jwt.JWTClaimsSet; import one.d4d.sessionless.itsdangerous.Algorithms; import one.d4d.sessionless.itsdangerous.Derivation; @@ -8,9 +9,9 @@ import one.d4d.sessionless.itsdangerous.crypto.TokenSigner; public class UnknownSignedToken extends SignedToken { - public byte separator; + public byte[] separator; - public UnknownSignedToken(String message, String signature, byte separator) { + public UnknownSignedToken(String message, String signature, byte[] separator) { super(message); this.signature = signature; this.separator = separator; @@ -19,15 +20,15 @@ public UnknownSignedToken(String message, String signature, byte separator) { Derivation.NONE, MessageDerivation.NONE, MessageDigestAlgorithm.NONE, - new byte[] {}, - new byte[] {}, + new byte[]{}, + new byte[]{}, separator); - } + } @Override public String serialize() { - return String.format("%s%c%s", message, separator, signature); + return String.format("%s%s%s", message, new String(separator), signature); } @Override @@ -39,7 +40,12 @@ public void resign() throws Exception { public void setClaims(JWTClaimsSet claims) { } + + public String getSignersName() { + return Signers.UNKNOWN.name(); + } + public byte[] getSeparator() { - return new byte[]{separator}; + return separator; } } diff --git a/src/main/java/one/d4d/sessionless/presenter/EditorPresenter.java b/src/main/java/one/d4d/sessionless/presenter/EditorPresenter.java index 5374058..1edddbf 100644 --- a/src/main/java/one/d4d/sessionless/presenter/EditorPresenter.java +++ b/src/main/java/one/d4d/sessionless/presenter/EditorPresenter.java @@ -17,6 +17,7 @@ import java.net.URL; import java.util.Base64; import java.util.List; +import java.util.Set; import static one.d4d.sessionless.itsdangerous.model.SignedTokenObjectFinder.containsSignedTokenObjects; @@ -71,14 +72,14 @@ private DangerousSignedToken getDangerous() { if (view.getDangerouseIsDjangoFormatting()) { timestamp = Utils.encodeBase62TimestampFromDate(view.getDangerousTimestamp()); return new DjangoSignedToken( - separator[0], + separator, payload, timestamp, signature); } else { timestamp = Utils.encodeBase64TimestampFromDate(view.getDangerousTimestamp()); return new DangerousSignedToken( - separator[0], + separator, payload, timestamp, signature); @@ -161,13 +162,27 @@ private void setTornado(TornadoSignedToken token) { view.setTornadoValue(token.getValue()); view.setTornadoSignature(token.getSignature()); } - private UnknownSignedToken getUnknown() { + private RubySignedToken getRuby() { + String message = view.getRubyMessage(); + String signature = view.getRubySignature(); + byte[] separator = view.getRubySeparator().length == 0 ? new byte[]{46} : view.getRubySeparator(); + + return new RubySignedToken(message, signature, separator); + } + + private void setRuby(RubySignedToken token) { + view.setRubyMessage(token.getEncodedMessage()); + view.setRubySignature(token.getEncodedSignature()); + view.setRubySeparator(token.getSeparator()); + } + + private UnknownSignedToken getUnknown() { String message = view.getUnknownMessage(); String signature = view.getUnknownSignature(); byte[] separator = view.getUnknownSeparator().length == 0 ? new byte[]{46} : view.getUnknownSeparator(); - return new UnknownSignedToken(message,signature,separator[0]); + return new UnknownSignedToken(message, signature, separator); } private void setUnknown(UnknownSignedToken token) { @@ -186,6 +201,7 @@ public void componentChanged() { case EditorTab.TAB_EXPRESS -> tokenObject = getExpress(); case EditorTab.TAB_OAUTH -> tokenObject = getOAuth(); case EditorTab.TAB_TORNADO -> tokenObject = getTornado(); + case EditorTab.TAB_RUBY -> tokenObject = getRuby(); default -> tokenObject = getUnknown(); } mutableSignedTokenObject.setModified(tokenObject); @@ -210,6 +226,9 @@ public void onSelectionChanged() { } else if (tokenObject instanceof TornadoSignedToken) { view.setTornadoMode(); setTornado((TornadoSignedToken) tokenObject); + } else if (tokenObject instanceof RubySignedToken) { + view.setRubyMode(); + setRuby((RubySignedToken) tokenObject); } else if (tokenObject instanceof UnknownSignedToken) { view.setUnknownMode(); setUnknown((UnknownSignedToken) tokenObject); @@ -224,6 +243,7 @@ public void copyExpressSignature() { public void onSignClicked() { signingDialog(); } + public void onAttackClicked() { attackDialog(); } @@ -262,12 +282,16 @@ private void attackDialog() { } else if (signed instanceof TornadoSignedToken) { view.setTornadoMode(); setTornado((TornadoSignedToken) signed); + } else if (signed instanceof RubySignedToken) { + view.setRubyMode(); + setRuby((RubySignedToken) signed); } else if (signed instanceof UnknownSignedToken) { view.setUnknownMode(); setUnknown((UnknownSignedToken) signed); } } } + private void signingDialog() { KeyPresenter keysPresenter = (KeyPresenter) presenters.get(KeyPresenter.class); @@ -301,6 +325,9 @@ private void signingDialog() { } else if (signed instanceof TornadoSignedToken) { view.setTornadoMode(); setTornado((TornadoSignedToken) signed); + } else if (signed instanceof RubySignedToken) { + view.setRubyMode(); + setRuby((RubySignedToken) signed); } else if (signed instanceof UnknownSignedToken) { view.setUnknownMode(); setUnknown((UnknownSignedToken) signed); @@ -314,8 +341,8 @@ public void onAttackClicked(Attack mode) { MutableSignedToken mutableJoseObject = model.getSignedTokenObject(view.getSelectedSignedTokenObjectIndex()); SignedToken tokenObject = mutableJoseObject.getModified(); - List attackKeys = keysPresenter.getSecrets(); - List attackSalts = keysPresenter.getSalts(); + Set attackKeys = keysPresenter.getSecrets(); + Set attackSalts = keysPresenter.getSalts(); if (attackKeys.size() == 0) { messageDialogFactory.showWarningDialog("error_title_no_secrets", "error_no_secrets"); @@ -327,6 +354,11 @@ public void onAttackClicked(Attack mode) { return; } + if (keysPresenter.getSigningKeys().size() == 0 && mode == Attack.KNOWN) { + messageDialogFactory.showWarningDialog("error_title_no_signing_keys", "error_no_signing_keys"); + return; + } + BruteForceAttackDialog bruteForceDialog = new BruteForceAttackDialog( view.window(), actionListenerFactory, @@ -354,6 +386,7 @@ public void onAttackClicked(Attack mode) { public void onAttackKnownKeysClicked() { onAttackClicked(Attack.KNOWN); } + public void onAttackFastClicked() { onAttackClicked(Attack.FAST); } diff --git a/src/main/java/one/d4d/sessionless/presenter/KeyPresenter.java b/src/main/java/one/d4d/sessionless/presenter/KeyPresenter.java index 4ba80ff..5fda56d 100644 --- a/src/main/java/one/d4d/sessionless/presenter/KeyPresenter.java +++ b/src/main/java/one/d4d/sessionless/presenter/KeyPresenter.java @@ -6,6 +6,7 @@ import one.d4d.sessionless.forms.WordlistView; import one.d4d.sessionless.forms.dialog.KeyDialog; import one.d4d.sessionless.forms.dialog.NewKeyDialog; +import one.d4d.sessionless.forms.dialog.NewWordDialog; import one.d4d.sessionless.keys.SecretKey; import one.d4d.sessionless.utils.Utils; @@ -13,6 +14,7 @@ import java.awt.event.ActionEvent; import java.io.File; import java.util.List; +import java.util.Set; import static one.d4d.sessionless.utils.Utils.prettyPrintJSON; @@ -52,6 +54,18 @@ public void onButtonLoadSecretsClick(ActionEvent e) { readSecretsFromFile(e); } + public void onButtonAddSecretsClick(ActionEvent e) { + NewWordDialog d = new NewWordDialog(view.getParent()); + d.display(); + + // If the dialog returned an item, add it to the model + String item = d.getItem(); + if (item != null) { + model.addSecret(item); + modelSecrets.addElement(item); + } + } + public void onButtonRemoveSecretsClick(ActionEvent e) { JList listSecrets = view.getSecretsList(); ListSelectionModel selmodel = listSecrets.getSelectionModel(); @@ -74,6 +88,19 @@ public void onButtonLoadSaltsClick(ActionEvent e) { readSaltsFromFile(e); } + public void onButtonAddSaltsClick(ActionEvent e) { + + NewWordDialog d = new NewWordDialog(view.getParent()); + d.display(); + + // If the dialog returned an item, add it to the model + String item = d.getItem(); + if (item != null) { + model.addSalt(item); + modelSalts.addElement(item); + } + } + public void onButtonRemoveSaltsClick(ActionEvent e) { JList listSalts = view.getSaltsList(); ListSelectionModel selmodel = listSalts.getSelectionModel(); @@ -149,7 +176,7 @@ private void readSecretsFromFile(ActionEvent e) { int returnVal = fc.showOpenDialog(view.getUiComponent()); if (returnVal == JFileChooser.APPROVE_OPTION) { File selectedFile = fc.getSelectedFile(); - List s = Utils.deserializeFile(selectedFile); + Set s = Utils.deserializeFile(selectedFile); if (!s.isEmpty()) { model.setSecrets(s); model.setSecretsFilePath(selectedFile.getAbsolutePath()); @@ -166,7 +193,7 @@ private void readSaltsFromFile(ActionEvent e) { int returnVal = fc.showOpenDialog(view.getUiComponent()); if (returnVal == JFileChooser.APPROVE_OPTION) { File selectedFile = fc.getSelectedFile(); - List s = Utils.deserializeFile(selectedFile); + Set s = Utils.deserializeFile(selectedFile); if (!s.isEmpty()) { model.setSalts(s); model.setSaltsFilePath(selectedFile.getAbsolutePath()); @@ -178,11 +205,11 @@ private void readSaltsFromFile(ActionEvent e) { } } - public List getSecrets() { + public Set getSecrets() { return model.getSecrets(); } - public List getSalts() { + public Set getSalts() { return model.getSalts(); } diff --git a/src/main/java/one/d4d/sessionless/utils/ClaimsUtils.java b/src/main/java/one/d4d/sessionless/utils/ClaimsUtils.java index 8ac19c1..11ab162 100644 --- a/src/main/java/one/d4d/sessionless/utils/ClaimsUtils.java +++ b/src/main/java/one/d4d/sessionless/utils/ClaimsUtils.java @@ -161,7 +161,7 @@ public static String generateJSONWebToken(URL target, String username, SecretKey .customParam("iat", currentTimeMillis) .customParam("exp", expirationTimeMillis) .build(); - JSONWebSignature token = new JSONWebSignature(header.toBase64URL().toString(), payload.toBase64URL().toString(), "", (byte) '.'); + JSONWebSignature token = new JSONWebSignature(header.toBase64URL().toString(), payload.toBase64URL().toString(), "", new byte[]{'.'}); JSONWebSignatureTokenSigner signer = new JSONWebSignatureTokenSigner(key); token.setSigner(signer); token.resign(); diff --git a/src/main/java/one/d4d/sessionless/utils/Utils.java b/src/main/java/one/d4d/sessionless/utils/Utils.java index 7e647c8..1e33906 100644 --- a/src/main/java/one/d4d/sessionless/utils/Utils.java +++ b/src/main/java/one/d4d/sessionless/utils/Utils.java @@ -20,13 +20,13 @@ import java.util.List; import java.util.*; import java.util.regex.Pattern; -import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.Inflater; public class Utils { + public static final int BRUTE_FORCE_CHUNK_SIZE = 4096; public static final int WORDLIST_ONE_CHAR = 256; public static final int WORDLIST_TWO_CHAR = 65536; public static final int WORDLIST_THREE_CHAR = 16_777_216; @@ -107,6 +107,30 @@ public static byte[][] split(byte[] data, byte sep) { return ret; } + public static byte[][] split(byte[] data, byte[] sep) { + ArrayList offsets = new ArrayList<>(); + + for (int i = 0; i < (data.length - sep.length); i++) { + byte[] candidate = Arrays.copyOfRange(data, i, i + sep.length); + if (Arrays.equals(candidate, sep)) { + offsets.add(i); + } + } + + offsets.add(data.length); + + byte[][] ret = new byte[offsets.size()][]; + + int index = 0; + for (int i = 0; i < offsets.size(); i++) { + ret[i] = new byte[offsets.get(i) - index]; + System.arraycopy(data, index, ret[i], 0, ret[i].length); + index = offsets.get(i) + 1; + } + + return ret; + } + public static byte[] normalizationWithDecompression(byte[] message) throws DataFormatException { try { return hexdigest2byte(new String(message)); @@ -209,6 +233,7 @@ public static boolean isValidJSON(String json) { } return true; } + public static boolean isValidJSON(byte[] json) { try { JsonParser.parseString(new String(json)); @@ -324,14 +349,14 @@ public static String getResourceString(String id) { return ResourceBundle.getBundle(RESOURCE_BUNDLE).getString(id); } - public static List readResourceForClass(final String fileName, Class clazz) { - List result = new ArrayList<>(); + public static Set readResourceForClass(final String fileName, Class clazz) { + Set result = new HashSet<>(); try (InputStream inputStream = clazz.getResourceAsStream(fileName); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { Gson gson = new Gson(); reader.lines().forEach(x -> result.add(gson.fromJson(x, String.class))); } catch (Exception e) { - return new ArrayList<>(); + return new HashSet<>(); } return result; } @@ -364,8 +389,8 @@ public static byte[] getCodeAreaData(CodeArea codeArea) { return data; } - public static List deserializeFile(File f) { - List result = new ArrayList<>(); + public static Set deserializeFile(File f) { + Set result = new HashSet<>(); Gson gson = new Gson(); try (Stream lines = Files.lines(f.toPath())) { lines.forEach(s -> { @@ -378,12 +403,10 @@ public static List deserializeFile(File f) { } catch (IOException ex) { return result; } - return result.stream() - .distinct() - .collect(Collectors.toList()); + return result; } - public static List generateWordlist(long l) { + public static Set generateWordlist(long l) { List list = new ArrayList<>(); for (; l < WORDLIST_ONE_CHAR; l++) { byte[] secret_key = new byte[]{(byte) l}; @@ -402,7 +425,7 @@ public static List generateWordlist(long l) { (byte) l}; list.add(new String(secret_key)); } - return list; + return new HashSet<>(list); } } diff --git a/src/main/resources/salts b/src/main/resources/salts index 11c832a..1f88f44 100644 --- a/src/main/resources/salts +++ b/src/main/resources/salts @@ -9,4 +9,7 @@ "django.core.signing.get_cookie_signer" "django.contrib.sessions.backends.signed_cookies" "flask-oidc-cookie" -"flask-oidc-extra-data" \ No newline at end of file +"flask-oidc-extra-data" +"signed cookie" +"encrypted cookie" +"signed encrypted cookie" \ No newline at end of file diff --git a/src/main/resources/secrets b/src/main/resources/secrets index 6218b31..58469bd 100644 --- a/src/main/resources/secrets +++ b/src/main/resources/secrets @@ -11,6 +11,8 @@ "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__" "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2X6TP1o/Vo=" "cOOk!e-$ecR@T" +"593146e0067b8b14fcdcd5ebfc0b4b0e987a889f" +"aeb977de013ade650b97e0aa5246813591104017871a7753fe186e9634c9129b367306606878985c759ca4fddd17d955207011bb855ef01ed414398b4ac8317b" "bZBc2sEbQLKqv7GkJD/VB8YuTC3eC0R0kRvJ5/xX37P=" "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=" "bZJc2sWbQLKoscdGkHn/VytuyfgXwQt8S0R0kRvJ5/xJ89E=" diff --git a/src/main/resources/strings.properties b/src/main/resources/strings.properties index dfe5386..132bb4b 100644 --- a/src/main/resources/strings.properties +++ b/src/main/resources/strings.properties @@ -94,4 +94,11 @@ signer_settings_label=Enabled signers: key_dialog_digest=Digest key_dialog_message_derivation=Message derivation key_dialog_key_derivation=Key derivation -key_dialog_algorythm=Algorythm \ No newline at end of file +key_dialog_algorythm=Algorythm +button_add_label=Add +new_word_dialog_title=New item +new_word_item_label=New item +ruby_tab_label=Ruby +ruby_message_label=Message +ruby_signature_label=Signature +ruby_separator_label=Separator \ No newline at end of file diff --git a/src/test/java/BruteForceTest.java b/src/test/java/BruteForceTest.java index d216b4a..427c4ff 100644 --- a/src/test/java/BruteForceTest.java +++ b/src/test/java/BruteForceTest.java @@ -7,9 +7,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; public class BruteForceTest { @@ -19,14 +17,15 @@ void BruteForceAttack() { Optional optionalSignedToken = SignedTokenObjectFinder.parseToken("e30.Zm17Ig.Ajtll0l5CXAy9Yqgy-vvhF05G28"); if (optionalSignedToken.isPresent()) { SignedToken token = optionalSignedToken.get(); - DangerousTokenSigner s = new DangerousTokenSigner((byte) '.'); + byte[] sep = new byte[]{'.'}; + DangerousTokenSigner s = new DangerousTokenSigner(sep); token.setSigner(s); - final List secrets = List.of("secret"); - final List salts = List.of("salt"); + final Set secrets = new HashSet<>(List.of("secret")); + final Set salts = new HashSet<>(List.of("salt")); final List knownKeys = new ArrayList<>(); BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.FAST, token); - SecretKey sk = bf.search(); + SecretKey sk = bf.parallel(); Assertions.assertNotNull(sk); } else { Assertions.fail("Token not found."); @@ -40,14 +39,15 @@ void BruteForceMultiThreatAttack() { Optional optionalSignedToken = SignedTokenObjectFinder.parseToken("e30.Zm17Ig.Ajtll0l5CXAy9Yqgy-vvhF05G28"); if (optionalSignedToken.isPresent()) { SignedToken token = optionalSignedToken.get(); - DangerousTokenSigner s = new DangerousTokenSigner((byte) '.'); + byte[] sep = new byte[]{'.'}; + DangerousTokenSigner s = new DangerousTokenSigner(sep); token.setSigner(s); - final List secrets = List.of("secret"); - final List salts = List.of("salt"); + final Set secrets = new HashSet<>(List.of("secret")); + final Set salts = new HashSet<>(List.of("salt")); final List knownKeys = new ArrayList<>(); BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.FAST, token); - SecretKey sk = bf.search(); + SecretKey sk = bf.parallel(); Assertions.assertNotNull(sk); } else { Assertions.fail("Token not found."); diff --git a/src/test/java/CompressTest.java b/src/test/java/CompressTest.java index 55e89e7..e2fcd4e 100644 --- a/src/test/java/CompressTest.java +++ b/src/test/java/CompressTest.java @@ -12,7 +12,7 @@ void Base64EncodedTimestampTest() { byte[] decArray = Utils.base64Decompress(expectedValue.getBytes()); String realValue = Utils.compressBase64(decArray); assertArrayEquals(expectedValue.toCharArray(), realValue.toCharArray()); - }catch (Exception e) { + } catch (Exception e) { fail(e.getMessage()); } } diff --git a/src/test/java/DjangoTest.java b/src/test/java/DjangoTest.java index f955fe9..0d0f24e 100644 --- a/src/test/java/DjangoTest.java +++ b/src/test/java/DjangoTest.java @@ -1,7 +1,5 @@ -import one.d4d.sessionless.itsdangerous.Algorithms; import one.d4d.sessionless.itsdangerous.Attack; import one.d4d.sessionless.itsdangerous.BruteForce; -import one.d4d.sessionless.itsdangerous.Derivation; import one.d4d.sessionless.itsdangerous.crypto.DjangoTokenSigner; import one.d4d.sessionless.itsdangerous.model.DangerousSignedToken; import one.d4d.sessionless.itsdangerous.model.DjangoSignedToken; @@ -11,16 +9,14 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; public class DjangoTest { @Test void DjangoBruteForceTest() { - List signingSecrets = List.of("secret"); - List signingSalts = List.of("django.contrib.sessions.backends.signed_cookies"); + final Set signingSecrets = new HashSet<>(List.of("secret")); + final Set signingSalts = new HashSet<>(List.of("django.contrib.sessions.backends.signed_cookies")); List knownKeys = new ArrayList<>(); Attack mode = Attack.Deep; String value = "gAWVMwAAAAAAAAB9lIwKdGVzdGNvb2tpZZSMBXBvc2l4lIwGc3lzdGVtlJOUjAhzbGVlcCAzMJSFlFKUcy4:1rBDnz:6RroyItcbm4P82lx2kEAuV2ykxs"; @@ -28,7 +24,7 @@ void DjangoBruteForceTest() { if (optionalToken.isPresent()) { DangerousSignedToken token = (DangerousSignedToken) optionalToken.get(); BruteForce bf = new BruteForce(signingSecrets, signingSalts, knownKeys, mode, token); - SecretKey k = bf.search(); + SecretKey k = bf.parallel(); Assertions.assertNotNull(k); } else { Assertions.fail("Token not found."); @@ -40,17 +36,38 @@ void DjangoBruteForceTest() { void DjangoParserTest() { byte[] secret = "secret".getBytes(); byte[] salt = "django.contrib.sessions.backends.signed_cookies".getBytes(); + byte[] sep = new byte[]{(byte) ':'}; String value = ".eJxTKkstqlSgIpGTn5eukJyfV5KaV6IEAJM1I3A:1rBGj6:xBAP3gQxgLfArMsY2j3SWmpxlqY"; Optional optionalToken = SignedTokenObjectFinder.parseToken(value); if (optionalToken.isPresent()) { DjangoSignedToken token = (DjangoSignedToken) optionalToken.get(); - DjangoTokenSigner s = new DjangoTokenSigner(Algorithms.SHA1, Derivation.DJANGO, secret, salt, (byte) ':'); + DjangoTokenSigner s = new DjangoTokenSigner(secret, salt, sep); token.setSigner(s); - Assertions.assertDoesNotThrow( ()-> { + Assertions.assertDoesNotThrow(() -> { s.unsign(value.getBytes()); }); } else { Assertions.fail("Token not found."); } } + + @Test + void DjangoSignerTest() { + byte[] secret = "secret".getBytes(); + byte[] salt = "django.contrib.sessions.backends.signed_cookies".getBytes(); + byte[] sep = new byte[]{(byte) ':'}; + String value = "gAWVMwAAAAAAAAB9lIwKdGVzdGNvb2tpZZSMBXBvc2l4lIwGc3lzdGVtlJOUjAhzbGVlcCAzMJSFlFKUcy4:1rBDnz:6RroyItcbm4P82lx2kEAuV2ykxs"; + Optional optionalToken = SignedTokenObjectFinder.parseToken(value); + if (optionalToken.isPresent()) { + DjangoSignedToken token = (DjangoSignedToken) optionalToken.get(); + DjangoTokenSigner s = new DjangoTokenSigner(secret, salt, sep); + token.setSigner(s); + Assertions.assertDoesNotThrow(() -> { + s.unsign(value.getBytes()); + }); + } else { + Assertions.fail("Token not found."); + } + + } } diff --git a/src/test/java/ExpressSignedCookieTest.java b/src/test/java/ExpressSignedCookieTest.java index 9ab535d..4c3c924 100644 --- a/src/test/java/ExpressSignedCookieTest.java +++ b/src/test/java/ExpressSignedCookieTest.java @@ -14,8 +14,9 @@ public class ExpressSignedCookieTest { @Test void OauthProxyParserTest() { - byte[] secret ="key1".getBytes(); - ExpressTokenSigner s = new ExpressTokenSigner(secret, (byte) '.'); + byte[] secret = "key1".getBytes(); + byte[] sep = new byte[]{(byte) '.'}; + ExpressTokenSigner s = new ExpressTokenSigner(secret, sep); String payload = "eyJwYXNzcG9ydCI6eyJ1c2VyIjoiYWRtaW4ifSwiZmxhc2giOnt9fQ=="; String signature = "zNzk1rU-uVc2rF2sGxxkt1t_4ewHRQtuE5OTD8b2FQnMZZV-c1A1eUwV6j4_s2hL"; @@ -26,15 +27,15 @@ void OauthProxyParserTest() { cookies.add(signatureCookie); Assertions.assertDoesNotThrow(() -> { Optional token = - SignedTokenObjectFinder.parseSignedTokenWithinCookies(cookies) - .stream() - .map(MutableSignedToken::getModified) - .map(ExpressSignedToken.class::cast) - .findFirst(); - if (token.isPresent()){ + SignedTokenObjectFinder.parseSignedTokenWithinCookies(cookies) + .stream() + .map(MutableSignedToken::getModified) + .map(ExpressSignedToken.class::cast) + .findFirst(); + if (token.isPresent()) { token.get().setSigner(s); token.get().unsign(); - }else throw new Exception("Missed cookie"); + } else throw new Exception("Missed cookie"); }); } } diff --git a/src/test/java/FlaskDangerousTest.java b/src/test/java/FlaskDangerousTest.java index ca82545..9cfef9e 100644 --- a/src/test/java/FlaskDangerousTest.java +++ b/src/test/java/FlaskDangerousTest.java @@ -1,45 +1,20 @@ -import one.d4d.sessionless.itsdangerous.Algorithms; -import one.d4d.sessionless.itsdangerous.Derivation; import one.d4d.sessionless.itsdangerous.crypto.DangerousTokenSigner; import one.d4d.sessionless.itsdangerous.model.DangerousSignedToken; -import one.d4d.sessionless.itsdangerous.model.SignedToken; -import one.d4d.sessionless.itsdangerous.model.SignedTokenObjectFinder; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.util.Optional; - import static org.junit.jupiter.api.Assertions.assertArrayEquals; public class FlaskDangerousTest { - - @Test - void DangerousParserTest() { - byte[] secret = "secret".getBytes(); - byte[] salt = "django.contrib.sessions.backends.signed_cookies".getBytes(); - String value = "gAWVMwAAAAAAAAB9lIwKdGVzdGNvb2tpZZSMBXBvc2l4lIwGc3lzdGVtlJOUjAhzbGVlcCAzMJSFlFKUcy4:1rBDnz:6RroyItcbm4P82lx2kEAuV2ykxs"; - Optional optionalToken = SignedTokenObjectFinder.parseToken(value); - if (optionalToken.isPresent()) { - DangerousSignedToken token = (DangerousSignedToken) optionalToken.get(); - DangerousTokenSigner s = new DangerousTokenSigner(Algorithms.SHA1, Derivation.DJANGO, secret, salt, (byte) ':'); - token.setSigner(s); - Assertions.assertDoesNotThrow( ()-> { - s.unsign(value.getBytes()); - }); - } else { - Assertions.fail("Token not found."); - } - - } @Test void DefaultFlaskSignedTokenTest() { byte[] secret = "secret".getBytes(); byte[] salt = "cookie-session".getBytes(); - DangerousSignedToken newToken = new DangerousSignedToken((byte)'.',"{}","Zzx63w",""); - DangerousTokenSigner s = new DangerousTokenSigner(Algorithms.SHA1, Derivation.HMAC,secret,salt,(byte)'.'); + byte[] sep = new byte[]{(byte) '.'}; + DangerousSignedToken newToken = new DangerousSignedToken(sep, "{}", "Zzx63w", ""); + DangerousTokenSigner s = new DangerousTokenSigner(secret, salt, sep); newToken.setSigner(s); char[] signedToken = newToken.dumps().toCharArray(); char[] testValue = "e30.Zzx63w.2BFIyJyE4fVqPm2hhw3edr8QTwo".toCharArray(); - assertArrayEquals(signedToken,testValue); + assertArrayEquals(signedToken, testValue); } } diff --git a/src/test/java/JSONWebSignatureTest.java b/src/test/java/JSONWebSignatureTest.java index f9717f7..5ffda51 100644 --- a/src/test/java/JSONWebSignatureTest.java +++ b/src/test/java/JSONWebSignatureTest.java @@ -9,41 +9,40 @@ import java.net.MalformedURLException; import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; public class JSONWebSignatureTest { @Test void JSONWebSignatureParserTest() { - final List secrets = List.of("your-256-bit-secret"); - final List salts = List.of("salt"); + final Set secrets = new HashSet<>(List.of("your-256-bit-secret")); + final Set salts = new HashSet<>(List.of("salt")); final List knownKeys = new ArrayList<>(); String value = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; Optional optionalToken = SignedTokenObjectFinder.parseJSONWebSignature(value); if (optionalToken.isPresent()) { JSONWebSignature token = (JSONWebSignature) optionalToken.get(); BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.FAST, token); - SecretKey sk = bf.search(); + SecretKey sk = bf.parallel(); Assertions.assertNotNull(sk); } else { Assertions.fail("Token not found."); } } + @Test void JSONWebSignatureClaimsTest() { try { - final List secrets = List.of("secret"); - final List salts = List.of("salt"); + final Set secrets = new HashSet<>(List.of("secret")); + final Set salts = new HashSet<>(List.of("salt")); final List knownKeys = new ArrayList<>(); URL target = new URL("https://d4d.one/"); - SecretKey key = new SecretKey("1", "secret", "",".", Algorithms.SHA256, Derivation.NONE, MessageDerivation.NONE, MessageDigestAlgorithm.NONE); + SecretKey key = new SecretKey("1", "secret", "", ".", Algorithms.SHA256, Derivation.NONE, MessageDerivation.NONE, MessageDigestAlgorithm.NONE); String value = ClaimsUtils.generateJSONWebToken(target, ClaimsUtils.DEFAULT_USERNAME, key); Optional optionalToken = SignedTokenObjectFinder.parseJSONWebSignature(value); if (optionalToken.isPresent()) { JSONWebSignature token = (JSONWebSignature) optionalToken.get(); BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.FAST, token); - SecretKey sk = bf.search(); + SecretKey sk = bf.parallel(); Assertions.assertNotNull(sk); } else { Assertions.fail("Token not found."); diff --git a/src/test/java/OAuth2Test.java b/src/test/java/OAuth2Test.java index 1acf696..9ee1f64 100644 --- a/src/test/java/OAuth2Test.java +++ b/src/test/java/OAuth2Test.java @@ -1,4 +1,3 @@ -import one.d4d.sessionless.itsdangerous.Algorithms; import one.d4d.sessionless.itsdangerous.crypto.OauthProxyTokenSigner; import one.d4d.sessionless.itsdangerous.model.OauthProxySignedToken; import one.d4d.sessionless.itsdangerous.model.SignedToken; @@ -12,11 +11,12 @@ public class OAuth2Test { @Test void OauthProxyParserTest() { byte[] secret = "j76h5PEMx3FIGr3caArJ5g==".getBytes(); - OauthProxyTokenSigner s = new OauthProxyTokenSigner(Algorithms.SHA256, secret, (byte) '|'); + byte[] sep = new byte[]{(byte) '|'}; + OauthProxyTokenSigner s = new OauthProxyTokenSigner(secret, sep); String key = "_oauth2_proxy_csrf"; String value = "hVV2htpqQw4UXgsLYtKdAWct1VAg_yPMxjq2xrGaaCfZStG0p6sGjlAGim1a686QrbBgDGNnpr6LrKH88uTQpTMHLiknn-YbVnXsbFtRyciE5QJIk3q8t24=|1688047283|MFrbdc2q8uQSZd9bpfaWWAmfkHY3U4mijmQo-vqMRKw="; Optional optionalSignedToken = SignedTokenObjectFinder.parseOauthProxySignedToken(key, value); - if (optionalSignedToken.isPresent()){ + if (optionalSignedToken.isPresent()) { OauthProxySignedToken token = (OauthProxySignedToken) optionalSignedToken.get(); token.setSigner(s); Assertions.assertDoesNotThrow(() -> { diff --git a/src/test/java/RubySignedCookieTest.java b/src/test/java/RubySignedCookieTest.java new file mode 100644 index 0000000..3e3cabe --- /dev/null +++ b/src/test/java/RubySignedCookieTest.java @@ -0,0 +1,86 @@ +import one.d4d.sessionless.itsdangerous.Attack; +import one.d4d.sessionless.itsdangerous.BruteForce; +import one.d4d.sessionless.itsdangerous.model.RubySignedToken; +import one.d4d.sessionless.keys.SecretKey; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class RubySignedCookieTest { + + @Test + void UnknownSignedRubySessionCookie() { + try { + String secret = "aeb977de013ade650b97e0aa5246813591104017871a7753fe186e9634c9129b367306606878985c759ca4fddd17d955207011bb855ef01ed414398b4ac8317b"; + String message = "WVFQVTFtbmNxWWJPODZNb3NUMVZzZGtDVjZQNXpMYStFMWdiZlJPMkdjRFRBOGZ5T3pOTzBPKzk3NWxvQUJvTlRRU2t4MXZmdG8rT0I0R2M3Ulh0YXpxRVhNMll5UW1xUHhvVXBLbXozZ3ZyNjB4VDU4dWRIUkxBWjBXbDJhci93YkYrZWswUHdFL0hUNDJaUHo2cEpxbXFvdlFZMjJWVU9KTWhHb3NyalFwTkphd0pUQVZSTXRHbkVqRlFnSGpNVTNFQlVxYlRmT3pWbXNjK0JuQ3FydzQvODRhbmtuU29haGNRbXQ4T3o1ZjhqMk53WTRMa0pVd1hPb2NHTVFQY3dvanE2ZElqUk1Mc21HS0k2SHVuZEZ3OWhjdzZPQnRSMEdVVkQwL2IxSVh5QzNSWVlJZms5c1JJV0lzUE1Zb1NHbEtqYm5nTGRKd1ZSdGpOQ1RZZWthR1A2anRFMEluaTcyWTNaNHJBR1N0dklzMkg1RjVmVmY4azEzV3o0N2Z2LS1wQlowRUZ6cjI3SVFQU0F5bGlYSDNnPT0="; + String signature = "19650cc5c3e2599fb43b7235ab4de5a1ce8a46ac"; + RubySignedToken token = new RubySignedToken(message, signature); + final Set secrets = new HashSet<>(List.of(secret)); + final Set salts = new HashSet<>(List.of("signed encrypted cookie")); + final List knownKeys = new ArrayList<>(); + BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.FAST, token); + SecretKey sk = bf.parallel(); + Assertions.assertNotNull(sk); + } catch (Exception e) { + Assertions.fail("Token not found."); + } + } + + @Test + void UnknownSignedDefaultRubySessionCookie() { + try { + String secret = "aeb977de013ade650b97e0aa5246813591104017871a7753fe186e9634c9129b367306606878985c759ca4fddd17d955207011bb855ef01ed414398b4ac8317b"; + String message = "cE5HNFl3QlUxVUxsMkdmNjVKaGJ3YkkvNEVxQ0xmenZ5dENzejdPYWpJTT0tLVcxZXlSSWxLS1hqcE4rMmhuNU5nVUE9PQ=="; + String signature = "7e6628b1f383f447b6feac19c2363083a9998d9e"; + RubySignedToken token = new RubySignedToken(message, signature); + final Set secrets = new HashSet<>(List.of(secret)); + final Set salts = new HashSet<>(List.of("signed encrypted cookie")); + final List knownKeys = new ArrayList<>(); + BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.Deep, token); + SecretKey sk = bf.parallel(); + Assertions.assertNotNull(sk); + } catch (Exception e) { + Assertions.fail("Token not found."); + } + } + + @Test + void UnknownSignedDefaultRubyMessageVerifier() { + try { + String secret = "aeb977de013ade650b97e0aa5246813591104017871a7753fe186e9634c9129b367306606878985c759ca4fddd17d955207011bb855ef01ed414398b4ac8317b"; + String message = "BAhJIhNteSBzZWNyZXQgZGF0YQY6BkVU"; + String signature = "578b70deb080dbe07538ab86198ab19819d6a310"; + RubySignedToken token = new RubySignedToken(message, signature); + final Set secrets = new HashSet<>(List.of(secret)); + final Set salts = new HashSet<>(List.of("signed encrypted cookie")); + final List knownKeys = new ArrayList<>(); + BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.Deep, token); + SecretKey sk = bf.parallel(); + Assertions.assertNotNull(sk); + } catch (Exception e) { + Assertions.fail("Token not found."); + } + } + + @Test + void UnknownSignedDefaultRubySessionCookie32() { + try { + String secret = "aeb977de013ade650b97e0aa5246813591104017871a7753fe186e9634c9129b367306606878985c759ca4fddd17d955207011bb855ef01ed414398b4ac8317b"; + String message = "NWU3YXRTS3RsSXRZcHBoMlJrNW9DOWlxSGpaWEFzbnM5UzBCUExMNDkwbz0tLXRDOWpQMHVCK055cmNXcDJGRXppZmc9PQ=="; + String signature = "5112fc6a45589e07bda0916670a67053366d09d3"; + RubySignedToken token = new RubySignedToken(message, signature); + final Set secrets = new HashSet<>(List.of(secret)); + final Set salts = new HashSet<>(List.of("signed encrypted cookie")); + final List knownKeys = new ArrayList<>(); + BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.Deep, token); + SecretKey sk = bf.parallel(); + Assertions.assertNotNull(sk); + } catch (Exception e) { + Assertions.fail("Token not found."); + } + } +} diff --git a/src/test/java/SignUnsignTest.java b/src/test/java/SignUnsignTest.java index 1c4dcc0..300e75d 100644 --- a/src/test/java/SignUnsignTest.java +++ b/src/test/java/SignUnsignTest.java @@ -6,40 +6,48 @@ import one.d4d.sessionless.keys.SecretKey; import one.d4d.sessionless.utils.Utils; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; public class SignUnsignTest { @Test + @Tag("slow") void KeyDerivationTest() { Assertions.assertDoesNotThrow(() -> { - for(Algorithms a: Algorithms.values()){ - for(Derivation d: Derivation.values()){ - byte[] secret = "secret".getBytes(); - byte[] salt = "cookie-session".getBytes(); - String ts = new String(Utils.timestampInFuture()); - DangerousSignedToken newToken = new DangerousSignedToken((byte)'.',"{}",ts,""); - DangerousTokenSigner s = new DangerousTokenSigner(a,d,secret,salt,(byte)'.'); - newToken.setSigner(s); - String signedToken = newToken.dumps(); - Optional optionalToken = SignedTokenObjectFinder.parseToken(signedToken); - if (optionalToken.isPresent()) { - SignedToken token = optionalToken.get(); - token.setSigner(s); - final List secrets = List.of("secret"); - final List salts = List.of("cookie-session"); - final List knownKeys = new ArrayList<>(); + long start = System.currentTimeMillis(); + for (Algorithms a : Algorithms.values()) { + for (Derivation d : Derivation.values()) { + for (MessageDerivation md : MessageDerivation.values()) { + for (MessageDigestAlgorithm mda : MessageDigestAlgorithm.values()) { + byte[] secret = "secret".getBytes(); + byte[] salt = "cookie-session".getBytes(); + byte[] sep = new byte[]{(byte) '.'}; + String ts = new String(Utils.timestampInFuture()); + DangerousSignedToken newToken = new DangerousSignedToken(sep, "{}", ts, ""); + DangerousTokenSigner s = new DangerousTokenSigner(a, d, md, mda, secret, salt, sep); + newToken.setSigner(s); + String signedToken = newToken.dumps(); + Optional optionalToken = SignedTokenObjectFinder.parseToken(signedToken); + if (optionalToken.isPresent()) { + SignedToken token = optionalToken.get(); + token.setSigner(s); + final Set secrets = new HashSet<>(List.of("secret")); + final Set salts = new HashSet<>(List.of("cookie-session")); + final List knownKeys = new ArrayList<>(); - BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.Deep, token); - SecretKey sk = bf.search(); - Assertions.assertNotNull(sk); + BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.Deep, token); + SecretKey sk = bf.parallel(); + System.out.println(sk.toJSONString()); + Assertions.assertNotNull(sk); + } + } } - } } + long end = System.currentTimeMillis() - start; + System.out.printf("Task finished in %.0f seconds", end / 1000.0); }); } } diff --git a/src/test/java/TornadoTest.java b/src/test/java/TornadoTest.java index 2288fcc..4128b83 100644 --- a/src/test/java/TornadoTest.java +++ b/src/test/java/TornadoTest.java @@ -12,25 +12,28 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.Set; public class TornadoTest { @Test void TornadoParserTest() { - byte[] secret ="secret".getBytes(); + byte[] secret = "secret".getBytes(); + byte[] sep = new byte[]{(byte) '|'}; String value = "2|1:0|10:1686150202|7:session|4:e30=|5e05eeef41715bc4b109138f00a37bbc580ca7e94ba9a21d5ec062b7aebff557"; Optional optionalToken = SignedTokenObjectFinder.parseTornadoSignedToken("test", value); if (optionalToken.isPresent()) { TornadoSignedToken token = (TornadoSignedToken) optionalToken.get(); - TornadoTokenSigner s = new TornadoTokenSigner(secret, (byte)'|'); + TornadoTokenSigner s = new TornadoTokenSigner(secret, sep); token.setSigner(s); - Assertions.assertDoesNotThrow( ()-> { + Assertions.assertDoesNotThrow(() -> { s.unsign(value.getBytes()); }); - }else { + } else { Assertions.fail("Token not found."); } } + @Test void BruteForceMultiThreatTornado() { String value = "2|1:0|10:1686150202|7:session|4:e30=|5e05eeef41715bc4b109138f00a37bbc580ca7e94ba9a21d5ec062b7aebff557"; @@ -39,12 +42,12 @@ void BruteForceMultiThreatTornado() { if (optionalToken.isPresent()) { TornadoTokenSigner s = new TornadoTokenSigner(); optionalToken.get().setSigner(s); - final List secrets = Utils.readResourceForClass("/secrets", this.getClass()); - final List salts = Utils.readResourceForClass("/salts", this.getClass()); + final Set secrets = Utils.readResourceForClass("/secrets", this.getClass()); + final Set salts = Utils.readResourceForClass("/salts", this.getClass()); final List knownKeys = new ArrayList<>(); BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.FAST, optionalToken.get()); - SecretKey sk = bf.search(); + SecretKey sk = bf.parallel(); Assertions.assertNotNull(sk); } diff --git a/src/test/java/UnknownSignedTokenTest.java b/src/test/java/UnknownSignedTokenTest.java index 4c033f8..f68b1f4 100644 --- a/src/test/java/UnknownSignedTokenTest.java +++ b/src/test/java/UnknownSignedTokenTest.java @@ -1,4 +1,3 @@ -import com.google.common.collect.Lists; import one.d4d.sessionless.itsdangerous.*; import one.d4d.sessionless.itsdangerous.crypto.DjangoTokenSigner; import one.d4d.sessionless.itsdangerous.model.SignedToken; @@ -9,23 +8,22 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; public class UnknownSignedTokenTest { @Test void DjangoSignerTest() { byte[] secret = "secret".getBytes(); byte[] salt = "django.core.signing.Signer".getBytes(); + byte[] sep = new byte[]{(byte) ':'}; String value = "eyJtZXNzYWdlIjoiSGVsbG8hIn0:V1O2qShdoisLMx2d0JTmVQecu8zsLPeXmTM5Id3ll-0"; UnknownSignedToken token = new UnknownSignedToken( "eyJtZXNzYWdlIjoiSGVsbG8hIn0", "V1O2qShdoisLMx2d0JTmVQecu8zsLPeXmTM5Id3ll", - (byte)':'); - DjangoTokenSigner s = new DjangoTokenSigner(Algorithms.SHA256, Derivation.DJANGO, MessageDerivation.NONE, MessageDigestAlgorithm.SHA256,secret, salt, (byte) ':'); + sep); + DjangoTokenSigner s = new DjangoTokenSigner(Algorithms.SHA256, Derivation.DJANGO, MessageDerivation.NONE, MessageDigestAlgorithm.SHA256, secret, salt, sep); token.setSigner(s); - Assertions.assertDoesNotThrow( ()-> { + Assertions.assertDoesNotThrow(() -> { s.unsign(value.getBytes()); }); } @@ -33,83 +31,93 @@ void DjangoSignerTest() { @Test void DjangoSignedMessageSaltDictionaryTest() { String secret = "ybgcsl1^swnd1*shae^5mibuc3j4^sq(l(+qb&qj1k8aydfw)("; + byte[] sep = new byte[]{(byte) ':'}; UnknownSignedToken token = new UnknownSignedToken( "My string", "prKofmME1ctlqPuuojYNv3CbncoQuwocwMTrG9_Viuw", - (byte)':'); - final List secrets = Utils.readResourceForClass("/secrets", this.getClass()); - final List salts = Utils.readResourceForClass("/salts", this.getClass()); + sep); + final Set secrets = Utils.readResourceForClass("/secrets", this.getClass()); + final Set salts = Utils.readResourceForClass("/salts", this.getClass()); final List knownKeys = new ArrayList<>(); secrets.add(secret); BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.Balanced, token); - SecretKey sk = bf.search(); + SecretKey sk = bf.parallel(); Assertions.assertNotNull(sk); } + @Test void DjangoSignedMessageBruteForceTest() { String secret = "ybgcsl1^swnd1*shae^5mibuc3j4^sq(l(+qb&qj1k8aydfw)("; + byte[] sep = new byte[]{(byte) ':'}; UnknownSignedToken token = new UnknownSignedToken( "hello:1rFgFX", "rpptBM3tbFJOZuNpSl_3wZwqHUGFsWlyY5ygIlsJOPA", - (byte)':'); - final List secrets = Utils.readResourceForClass("/secrets", this.getClass()); - final List salts = Utils.readResourceForClass("/salts", this.getClass()); + sep); + final Set secrets = Utils.readResourceForClass("/secrets", this.getClass()); + final Set salts = Utils.readResourceForClass("/salts", this.getClass()); final List knownKeys = new ArrayList<>(); secrets.add(secret); BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.Balanced, token); - SecretKey sk = bf.search(); + SecretKey sk = bf.parallel(); Assertions.assertNotNull(sk); } + @Test void URLSafeSerializerTest() { + byte[] sep = new byte[]{(byte) '.'}; UnknownSignedToken token = new UnknownSignedToken( "eyJpZCI6NSwibmFtZSI6Iml0c2Rhbmdlcm91cyJ9", "6YP6T0BaO67XP--9UzTrmurXSmg", - (byte)'.'); - final List secrets = Lists.newArrayList("secret key"); - final List salts = Lists.newArrayList("auth"); + sep); + final Set secrets = new HashSet<>(List.of("secret key")); + final Set salts = new HashSet<>(List.of("auth")); final List knownKeys = new ArrayList<>(); BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.Balanced, token); - SecretKey sk = bf.search(); + SecretKey sk = bf.parallel(); Assertions.assertNotNull(sk); } + @Test void ItsDangerousSignerTest() { + byte[] sep = new byte[]{(byte) '.'}; UnknownSignedToken token = new UnknownSignedToken( "my string", "wh6tMHxLgJqB6oY1uT73iMlyrOA", - (byte)'.'); - final List secrets = Lists.newArrayList("secret-key"); - final List salts = Lists.newArrayList("itsdangerous.Signer"); + sep); + final Set secrets = new HashSet<>(List.of("secret-key")); + final Set salts = new HashSet<>(List.of("itsdangerous.Signer")); final List knownKeys = new ArrayList<>(); BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.Balanced, token); - SecretKey sk = bf.search(); + SecretKey sk = bf.parallel(); Assertions.assertNotNull(sk); } + @Test void RedashURLSafeTimedSerializerTest() { + byte[] sep = new byte[]{(byte) '.'}; UnknownSignedToken token = new UnknownSignedToken( "IjEi.YhAmmQ", "cdQp7CnnVq02aQ05y8tSBddl-qs", - (byte)'.'); - final List secrets = Lists.newArrayList("c292a0a3aa32397cdb050e233733900f"); - final List salts = Lists.newArrayList("itsdangerous"); + sep); + final Set secrets = new HashSet<>(List.of("c292a0a3aa32397cdb050e233733900f")); + final Set salts = new HashSet<>(List.of("itsdangerous")); final List knownKeys = new ArrayList<>(); BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.Balanced, token); - SecretKey sk = bf.search(); + SecretKey sk = bf.parallel(); Assertions.assertNotNull(sk); } + @Test void UnknownSignedStringParserTest() { - final List secrets = List.of("c292a0a3aa32397cdb050e233733900f"); - final List salts = List.of("itsdangerous"); + final Set secrets = new HashSet<>(List.of("c292a0a3aa32397cdb050e233733900f")); + final Set salts = new HashSet<>(List.of("itsdangerous")); final List knownKeys = new ArrayList<>(); String value = "IjEi.YhAmmQ.cdQp7CnnVq02aQ05y8tSBddl-qs"; Optional optionalToken = SignedTokenObjectFinder.parseUnknownSignedString(value); if (optionalToken.isPresent()) { UnknownSignedToken token = (UnknownSignedToken) optionalToken.get(); BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.Balanced, token); - SecretKey sk = bf.search(); + SecretKey sk = bf.parallel(); Assertions.assertNotNull(sk); } else { Assertions.fail("Token not found."); diff --git a/src/test/resources/salts b/src/test/resources/salts index 11c832a..e64bfd8 100644 --- a/src/test/resources/salts +++ b/src/test/resources/salts @@ -9,4 +9,8 @@ "django.core.signing.get_cookie_signer" "django.contrib.sessions.backends.signed_cookies" "flask-oidc-cookie" -"flask-oidc-extra-data" \ No newline at end of file +"flask-oidc-extra-data" +"signed cookie" +"encrypted cookie" +"signed encrypted cookie" +"a4fb52b0ccb302eaef92bda18fedf5c3" \ No newline at end of file diff --git a/src/test/resources/secrets b/src/test/resources/secrets index 21a68e5..bec2e60 100644 --- a/src/test/resources/secrets +++ b/src/test/resources/secrets @@ -11,4 +11,6 @@ "dev" "user" "j76h5PEMx3FIGr3caArJ5g==" -"\u0002\u0001thisismyscretkey\u0001\u0002\\e\\y\\y\\h" \ No newline at end of file +"\u0002\u0001thisismyscretkey\u0001\u0002\\e\\y\\y\\h" +"b2efbaccbdb9548217eebc73a896db73" +"aeb977de013ade650b97e0aa5246813591104017871a7753fe186e9634c9129b367306606878985c759ca4fddd17d955207011bb855ef01ed414398b4ac8317b" \ No newline at end of file