Skip to content

Commit

Permalink
## [1.0.1] - 2024-04-03
Browse files Browse the repository at this point in the history
### Changed

- Default Secret Keys now available at Wordlist View
- The com.nimbusds.jwt SignedJWT parser added to the finder logic. _Note_ RSA and ECDSA not supported by the extension yet

Signed-off-by: d4d <btlfry@gmail.com>
  • Loading branch information
d0ge committed Apr 3, 2024
1 parent 7a664a6 commit d55c88a
Show file tree
Hide file tree
Showing 15 changed files with 138 additions and 14 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [1.0.1] - 2024-04-03

### Changed

- Default Secret Keys now available at Wordlist View
- The com.nimbusds.jwt SignedJWT parser added to the finder logic. _Note_ RSA and ECDSA not supported by the extension yet

## [1.0.0] - 2024-03-27

### Changed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ found [here](https://github.com/blackberry/jwt-editor) and [here](https://github

* Ensure that Java JDK 17 or newer is installed
* From root of project, run the command `./gradlew jar`
* This should place the JAR file `sign-saboteur-1.0.0.jar` within the `build/libs` directory
* This should place the JAR file `sign-saboteur-1.0.1.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
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ plugins {
}

group = 'one.d4d'
version = '1.0.0'
version = '1.0.1'
description = 'sign-saboteur'

repositories {
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/burp/config/BurpKeysModelPersistence.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import burp.api.montoya.persistence.Preferences;
import com.google.gson.Gson;
import one.d4d.sessionless.keys.SecretKey;
import one.d4d.sessionless.utils.GsonHelper;
import one.d4d.sessionless.utils.Utils;

import java.io.File;
import java.util.List;
import java.util.Set;

public class BurpKeysModelPersistence {
Expand All @@ -23,6 +25,7 @@ public KeysModel loadOrCreateNew() {
KeysModel model = new KeysModel();
model.setSalts(loadDefaultSalts());
model.setSecrets(loadDefaultSecrets());
loadDefaultKeys().forEach(model::addKey);
return model;
}

Expand Down Expand Up @@ -63,4 +66,8 @@ private Set<String> loadDefaultSalts() {
return Utils.readResourceForClass("/salts", this.getClass());
}

private List<SecretKey> loadDefaultKeys() {
return Utils.readDefaultSecretKeys("/keys", this.getClass());
}

}
2 changes: 1 addition & 1 deletion src/main/java/burp/config/SignerConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class SignerConfig {
private Set<Signers> enabled;

public SignerConfig() {
EnumSet<Signers> disabled = EnumSet.of(Signers.OAUTH, Signers.UNKNOWN);
EnumSet<Signers> disabled = EnumSet.of(Signers.OAUTH, Signers.NIMBUSDS, Signers.UNKNOWN);
this.enabled = EnumSet.complementOf(disabled);
}

Expand Down
21 changes: 19 additions & 2 deletions src/main/java/one/d4d/sessionless/forms/SettingsView.form
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<grid id="27dc6" binding="mainPanel" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="10" left="10" bottom="10" right="10"/>
<constraints>
<xy x="20" y="20" width="577" height="418"/>
<xy x="20" y="20" width="577" height="442"/>
</constraints>
<properties/>
<border type="none"/>
Expand Down Expand Up @@ -94,7 +94,7 @@
<text resource-bundle="strings" key="signer_settings_label"/>
</properties>
</component>
<grid id="f7232" layout-manager="GridLayoutManager" row-count="7" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="f7232" layout-manager="GridLayoutManager" row-count="8" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="9" fill="0" indent="0" use-parent-layout="false"/>
Expand Down Expand Up @@ -214,6 +214,23 @@
<text resource-bundle="strings" key="jsonwebsignature_tab_label"/>
</properties>
</component>
<component id="440b3" class="javax.swing.JCheckBox" binding="checkBoxNIMBUSDS">
<constraints>
<grid row="7" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value=""/>
</properties>
</component>
<component id="1fd16" class="javax.swing.JLabel">
<constraints>
<grid row="7" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="strings" key="NIMBUSDS_label"/>
<toolTipText resource-bundle="strings" key="tooltip_NIMBUSDS"/>
</properties>
</component>
</children>
</grid>
<component id="8aec4" class="javax.swing.JSeparator">
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/one/d4d/sessionless/forms/SettingsView.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class SettingsView {
private JCheckBox checkBoxEnableTornadoSignedString;
private JCheckBox checkBoxEnableRubySignedString;
private JCheckBox checkBoxEnableJWT;
private JCheckBox checkBoxNIMBUSDS;

public SettingsView(Window parent, BurpConfig burpConfig, UserInterface userInterface) {
this.parent = parent;
Expand Down Expand Up @@ -75,6 +76,10 @@ public SettingsView(Window parent, BurpConfig burpConfig, UserInterface userInte
checkBoxEnableJWT.addActionListener(e ->
signerConfig.toggleEnabled(Signers.JWT, checkBoxEnableJWT.isSelected()));

checkBoxNIMBUSDS.setSelected(signerConfig.isEnabled(Signers.NIMBUSDS));
checkBoxNIMBUSDS.addActionListener(e ->
signerConfig.toggleEnabled(Signers.NIMBUSDS, checkBoxNIMBUSDS.isSelected()));

checkBoxEnableUnknownSignedString.setSelected(signerConfig.isEnabled(Signers.UNKNOWN));
checkBoxEnableUnknownSignedString.addActionListener(e ->
signerConfig.toggleEnabled(Signers.UNKNOWN, checkBoxEnableUnknownSignedString.isSelected()));
Expand Down
14 changes: 11 additions & 3 deletions src/main/java/one/d4d/sessionless/forms/WordlistView.form
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<grid id="27dc6" binding="mainPanel" layout-manager="GridLayoutManager" row-count="6" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="566"/>
<xy x="20" y="20" width="500" height="601"/>
</constraints>
<properties/>
<border type="none"/>
Expand Down Expand Up @@ -244,7 +244,7 @@
<text resource-bundle="strings" key="keys_label"/>
</properties>
</component>
<grid id="a7362" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="a7362" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
Expand All @@ -262,9 +262,17 @@
</component>
<vspacer id="ad32e">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="7ef43" class="javax.swing.JButton" binding="buttonLoadDefaults">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="strings" key="button_load_defaults"/>
</properties>
</component>
</children>
</grid>
<scrollpane id="768a6">
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/one/d4d/sessionless/forms/WordlistView.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class WordlistView {
private JTextArea textAreaSecrets;
private JButton secretsAddButton;
private JButton saltsAddButton;
private JButton buttonLoadDefaults;
private JMenuItem menuItemDelete;
private JMenuItem menuItemCopy;

Expand Down Expand Up @@ -94,6 +95,7 @@ public WordlistView(

// Attach event handlers for button clicks
newKeyButton.addActionListener(e -> presenter.onButtonNewSecretKeyClick());
buttonLoadDefaults.addActionListener(e -> presenter.onButtonLoadDefaultsClick());
}

public Component getUiComponent() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package one.d4d.sessionless.itsdangerous.crypto;

public enum Signers {
DANGEROUS, EXPRESS, OAUTH, TORNADO, RUBY, JWT, UNKNOWN
DANGEROUS, EXPRESS, OAUTH, TORNADO, RUBY, JWT, NIMBUSDS, UNKNOWN
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@
import burp.api.montoya.http.message.params.ParsedHttpParameter;
import burp.config.SignerConfig;
import com.google.common.base.CharMatcher;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jwt.SignedJWT;
import one.d4d.sessionless.itsdangerous.crypto.Signers;
import one.d4d.sessionless.utils.Utils;
import org.apache.commons.lang3.StringUtils;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class SignedTokenObjectFinder {
public static final char[] SEPARATORS = {'.', ':', '#', '|'};
public static final char[] ALL_SEPARATORS = {'.', ':', '!', '#', '$', '*', ';', '@', '|', '~'};
private static final int[] SIGNATURES_LENGTH = {20, 28, 32, 48, 64};
private static final Set<JWSAlgorithm> SUPPORTED_ALGORITHMS = Set.of(JWSAlgorithm.HS256, JWSAlgorithm.HS384, JWSAlgorithm.HS512);
private static final String SIGNED_PARAM = ".SIG";
// Regular expressions for JWS/JWE extraction

Expand Down Expand Up @@ -82,6 +83,14 @@ public static List<MutableSignedToken> extractSignedTokenObjects(SignerConfig si
signedTokensObjects.add(new MutableSignedToken(candidate.toString(), value)));
}
}
if (signerConfig.isEnabled(Signers.NIMBUSDS)) {
List<ByteArray> stringCandidates = Utils.searchByteArrayBase64URLSafe(text);
for (ByteArray candidate : stringCandidates) {
parseSignedJWT(candidate.toString(), false)
.ifPresent(value ->
signedTokensObjects.add(new MutableSignedToken(candidate.toString(), value)));
}
}
if (signerConfig.isEnabled(Signers.UNKNOWN)) {
List<ByteArray> stringCandidates = Utils.searchByteArrayBase64(text);
for (ByteArray candidate : stringCandidates) {
Expand Down Expand Up @@ -415,6 +424,26 @@ public static Optional<SignedToken> parseDjangoSignedToken(String text) {
return Optional.of(t);
}

public static Optional<SignedToken> parseSignedJWT(String text, boolean validateToken) {
try {
SignedJWT candidate = SignedJWT.parse(text);
JWSHeader header = candidate.getHeader();
JWSAlgorithm alg = header.getAlgorithm();
List<String> parts = Arrays.stream(candidate.getParsedParts()).map(com.nimbusds.jose.util.Base64::toString).toList();
if (parts.size() != 3) return Optional.empty();
if (!SUPPORTED_ALGORITHMS.contains(alg) && validateToken) {
return Optional.empty();
}
return Optional.of(new JSONWebSignature(
parts.get(0),
parts.get(1),
parts.get(2),
new byte[]{(byte) '.'}));
}catch (Exception e) {
return Optional.empty();
}
}

public static Optional<SignedToken> parseJSONWebSignature(String text) {
char separator = '.';
boolean compressed = false;
Expand Down Expand Up @@ -444,8 +473,8 @@ public static Optional<SignedToken> parseJSONWebSignature(String text) {
// Signature parser
String signature = parts[2];
try {
int length = Utils.base64Decompress(signature.getBytes()).length;
if (Arrays.stream(SIGNATURES_LENGTH).noneMatch(x -> x == length)) return Optional.empty();
byte[] sign = Utils.normalization(signature.getBytes());
if (sign == null || Arrays.stream(SIGNATURES_LENGTH).noneMatch(x -> x == sign.length)) return Optional.empty();
} catch (Exception e) {
return Optional.empty();
}
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/one/d4d/sessionless/presenter/KeyPresenter.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import burp.config.BurpKeysModelPersistence;
import burp.config.KeysModel;
import burp.config.KeysModelListener;
import com.google.gson.Gson;
import one.d4d.sessionless.forms.WordlistView;
import one.d4d.sessionless.forms.dialog.KeyDialog;
import one.d4d.sessionless.forms.dialog.NewKeyDialog;
Expand All @@ -12,7 +13,11 @@

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

Expand Down Expand Up @@ -129,6 +134,10 @@ public void onButtonNewSecretKeyClick() {
}
}

public void onButtonLoadDefaultsClick() {
Utils.readDefaultSecretKeys("/keys", this.getClass()).forEach(model::addKey);
}

public void onTableKeysDoubleClick() {
SecretKey key = model.getKey(view.getSelectedRow());

Expand Down
13 changes: 13 additions & 0 deletions src/main/java/one/d4d/sessionless/utils/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.google.gson.Gson;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import one.d4d.sessionless.keys.SecretKey;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Mac;
Expand Down Expand Up @@ -417,6 +418,18 @@ public static Set<String> readResourceForClass(final String fileName, Class claz
return result;
}

public static List<SecretKey> readDefaultSecretKeys(final String fileName, Class clazz) {
List<SecretKey> result = new ArrayList<>();
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, SecretKey.class)));
} catch (Exception e) {
return new ArrayList<>();
}
return result;
}

public static String compactJSON(String json) {

StringBuilder stringBuilder = new StringBuilder();
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/strings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,6 @@ audit_issue_name=Weak HMAC secret
audit_issue_details=Detected a signed string using a well-known HMAC secret key. The key used was [%s]
audit_issue_background=The cryptographic strength of the HMAC depends upon the size of the secret key that is used. If an attacker is able to create their own valid tokens with arbitrary values, they may be able to escalate their own privileges or impersonate other users, taking full control of their accounts.
audit_issue_remediation=How to prevent:<br><ul><li>Generate your secret key and salt with strong randomness</li><li>Make sure that you perform robust signature verification on any signed string that you receive. PBKDF2 key derivation function with a big number of iterations will reduce the risk of brute force attacks.</li><li>Use long and secure ID generator functions for user ID.</li></ul>
NIMBUSDS_label=NIMBUSDS
button_load_defaults=Load defaults
tooltip_NIMBUSDS=Use Nimbusds library to parse Json Web tokens
24 changes: 24 additions & 0 deletions src/test/java/JSONWebSignatureTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,30 @@ void JSONWebSignatureParserTest() {
Assertions.fail("Token not found.");
}
}
@Test
void JWTNimbus() {
final Set<String> secrets = new HashSet<>(List.of("your-256-bit-secret"));
final Set<String> salts = new HashSet<>(List.of("salt"));
final List<SecretKey> knownKeys = new ArrayList<>();
String value = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";

Optional<SignedToken> optionalToken = SignedTokenObjectFinder.parseSignedJWT(value, true);
if (optionalToken.isPresent()) {
JSONWebSignature token = (JSONWebSignature) optionalToken.get();
BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.FAST, token);
SecretKey sk = bf.parallel();
Assertions.assertNotNull(sk);
} else {
Assertions.fail("Token not found.");
}
}

@Test
void JSONWebTokenRS256Test() {
String value = "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.VUPWQZuClnkFbaEKCsPy7CZVMh5wxbCSpaAWFLpnTe9J0--PzHNeTFNXCrVHysAa3eFbuzD8_bLSsgTKC8SzHxRVSj5eN86vBPo_1fNfE7SHTYhWowjY4E_wuiC13yoj";
Optional<SignedToken> optionalToken = SignedTokenObjectFinder.parseSignedJWT(value, true);
Assertions.assertTrue(optionalToken.isEmpty());
}

@Test
void JSONWebSignatureClaimsTest() {
Expand Down

0 comments on commit d55c88a

Please sign in to comment.