Skip to content

Commit

Permalink
Merge pull request #8 from d0ge/ruby-encrypted-string
Browse files Browse the repository at this point in the history
## [1.0.6] - 2024-11-29
  • Loading branch information
d0ge authored Nov 29, 2024
2 parents c19dee3 + 1dc92db commit ef25950
Show file tree
Hide file tree
Showing 20 changed files with 704 additions and 43 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## [1.0.6] - 2024-11-29

### Added

- Load default secrets and salts
- Encrypted Ruby Rails tokens

### Changed

- Ruby on rails brute force logic

## [1.0.5] - 2024-05-22

### Changed
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# SignSaboteur

SignSaboteur is a Burp Suite extension for editing, signing, verifying, and attacking signed tokens.
It supports different types of 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), [Nimbus JOSE + JWT](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/)
It supports different types of 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), [Ruby Rails Encrypted cookies](https://api.rubyonrails.org/v5.2.3/classes/ActiveSupport/MessageEncryptor.html), [Nimbus JOSE + JWT](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/)
and Unknown signed string.

You can find more information about the extension on the Portswigger Research blog post page - [Introducing SignSaboteur: forge signed web tokens with ease](https://portswigger.net/research/introducing-signsaboteur-forge-signed-web-tokens-with-ease).
Expand All @@ -17,7 +17,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.5.jar` within the `build/libs` directory
* This should place the JAR file `sign-saboteur-1.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
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.5'
version = '1.0.6'
description = 'sign-saboteur'

repositories {
Expand Down
12 changes: 10 additions & 2 deletions src/main/java/one/d4d/signsaboteur/forms/EditorTab.form
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@
</component>
</children>
</scrollpane>
<grid id="690de" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="690de" 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="0" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
Expand All @@ -581,9 +581,17 @@
</component>
<vspacer id="db73a">
<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="50646" class="javax.swing.JButton" binding="decryptButton" default-binding="true">
<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_ruby_decrypt"/>
</properties>
</component>
</children>
</grid>
</children>
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/one/d4d/signsaboteur/forms/EditorTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public abstract class EditorTab implements ExtensionProvidedEditor {
private RSyntaxTextArea textAreaJSONWebSignaturePayload;
private JCheckBox checkBoxUnknownURLEncoded;
private JCheckBox checkBoxRubyURLEncoded;
private JButton decryptButton;
private CodeArea codeAreaDangerousSignature;
private CodeArea codeAreaDangerousSeparator;
private CodeArea codeAreaOAuthSignature;
Expand All @@ -108,6 +109,7 @@ public abstract class EditorTab implements ExtensionProvidedEditor {
this.signerConfig = signerConfig;
this.presenter = new EditorPresenter(
this,
rstaFactory,
collaboratorPayloadGenerator,
actionListenerFactory,
presenters,
Expand Down Expand Up @@ -168,6 +170,7 @@ public void changedUpdate(DocumentEvent e) {
buttonSign.addActionListener(e -> presenter.onSignClicked());
buttonAttack.addActionListener(e -> presenter.onAttackClicked());
buttonCopyExpressSignature.addActionListener(e -> presenter.copyExpressSignature());
decryptButton.addActionListener(e -> presenter.decryptRubyMessage());
}

public Window window() {
Expand Down Expand Up @@ -344,6 +347,9 @@ public void setRubyMessage(String text) {
public boolean getRubyIsURLEncoded() {
return checkBoxRubyURLEncoded.isSelected();
}
public boolean getRubyIsEncrypted() {
return decryptButton.isEnabled();
}

public void setRubyIsURLEncoded(boolean enabled) {
checkBoxRubyURLEncoded.setSelected(enabled);
Expand All @@ -365,6 +371,10 @@ public void setRubySeparator(byte[] separator) {
codeAreaRubySeparator.setData(new ByteArrayEditableData(separator));
}

public void setRubyDecryptButton(boolean enabled) {
decryptButton.setEnabled(enabled);
}

public String getJWTHeader() {
return textAreaJSONWebSignatureHeader.getText();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="one.d4d.signsaboteur.forms.dialog.EncryptionDialog">
<grid id="cbd77" binding="contentPane" 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="48" y="54" width="436" height="297"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<grid id="94766" layout-manager="GridLayoutManager" row-count="1" 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="1" vsize-policy="1" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<hspacer id="98af6">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<grid id="9538f" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="true" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="e7465" class="javax.swing.JButton" binding="buttonOK">
<constraints>
<grid row="0" 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_ruby_decrypt"/>
</properties>
</component>
<component id="5723f" class="javax.swing.JButton" binding="buttonCancel">
<constraints>
<grid row="0" column="1" 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 value="Cancel"/>
</properties>
</component>
</children>
</grid>
</children>
</grid>
<grid id="e3588" layout-manager="GridLayoutManager" row-count="2" 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="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<grid id="11b4e" layout-manager="GridLayoutManager" row-count="1" 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="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="ef293" class="javax.swing.JComboBox" binding="comboBoxEncryptionKeys">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
</children>
</grid>
<scrollpane id="ceeac" class="org.fife.ui.rtextarea.RTextScrollPane">
<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"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="8193e" class="org.fife.ui.rsyntaxtextarea.RSyntaxTextArea" binding="cypherText" custom-create="true">
<constraints/>
<properties>
<syntaxEditingStyle resource-bundle="strings" key="syntaxis"/>
</properties>
</component>
</children>
</scrollpane>
</children>
</grid>
</children>
</grid>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package one.d4d.signsaboteur.forms.dialog;

import one.d4d.signsaboteur.itsdangerous.crypto.*;
import one.d4d.signsaboteur.itsdangerous.model.*;
import one.d4d.signsaboteur.keys.SecretKey;
import one.d4d.signsaboteur.rsta.RstaFactory;
import one.d4d.signsaboteur.utils.ErrorLoggingActionListenerFactory;
import one.d4d.signsaboteur.utils.Utils;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import java.awt.*;
import java.awt.event.*;
import java.util.List;

import static java.awt.Color.RED;
import static javax.swing.JOptionPane.WARNING_MESSAGE;

public class EncryptionDialog extends AbstractDialog {
private JPanel contentPane;
private JButton buttonOK;
private JButton buttonCancel;
private JComboBox comboBoxEncryptionKeys;
private RSyntaxTextArea cypherText;
private SignedToken tokenObject;
private RstaFactory rstaFactory;

public EncryptionDialog(Window parent,
RstaFactory rstaFactory,
ErrorLoggingActionListenerFactory actionListenerFactory,
List<SecretKey> signingKeys,
SignedToken tokenObject) {
super(parent, "encryption_dialog_title");
this.rstaFactory = rstaFactory;
this.tokenObject = tokenObject;

setContentPane(contentPane);
getRootPane().setDefaultButton(buttonOK);

buttonOK.addActionListener(actionListenerFactory.from(e -> onOK()));
buttonCancel.addActionListener(e -> onCancel());

contentPane.registerKeyboardAction(
e -> onCancel(),
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
);

SecretKey[] signingKeysArray = new SecretKey[signingKeys.size()];
signingKeys.toArray(signingKeysArray);

comboBoxEncryptionKeys.setModel(new DefaultComboBoxModel<>(signingKeysArray));
comboBoxEncryptionKeys.setSelectedIndex(0);
}

private void onOK() {
SecretKey selectedKey = (SecretKey) comboBoxEncryptionKeys.getSelectedItem();

try {
assert selectedKey != null;
TokenSigner s;
if (tokenObject instanceof RubyEncryptedToken) {
s = new RubyEncryptionTokenSigner(selectedKey);
} else {
throw new Exception("Unknown");
}
tokenObject.setSigner(s);
String text = ((RubyEncryptedToken) tokenObject).getCypherText();
cypherText.setText(text);

Border serializedTextAreaBorder = text.equals("Error") ? new LineBorder(RED, 1) : null;
cypherText.setBorder(serializedTextAreaBorder);
} catch (Exception e) {
tokenObject = null;
JOptionPane.showMessageDialog(
this,
e.getMessage(),
Utils.getResourceString("error_title_unable_to_sign"),
WARNING_MESSAGE
);
}
}

private void createUIComponents() {
// TODO: place custom component creation code here
cypherText = rstaFactory.buildDefaultTextArea();
}
}
22 changes: 17 additions & 5 deletions src/main/java/one/d4d/signsaboteur/itsdangerous/BruteForce.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@

import com.google.common.collect.Lists;
import one.d4d.signsaboteur.itsdangerous.crypto.TokenSigner;
import one.d4d.signsaboteur.itsdangerous.model.RubyEncryptedToken;
import one.d4d.signsaboteur.itsdangerous.model.SignedToken;
import one.d4d.signsaboteur.itsdangerous.model.UnknownSignedToken;
import one.d4d.signsaboteur.keys.SecretKey;
import one.d4d.signsaboteur.presenter.Presenter;
import one.d4d.signsaboteur.presenter.PresenterStore;
import one.d4d.signsaboteur.utils.Utils;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.concurrent.*;

public class BruteForce extends Presenter {
Expand Down Expand Up @@ -52,10 +50,24 @@ public BruteForce (Set<String> secrets,
presenters.register(this);
}

public List<TokenSigner> prepareEncryption() {
List<TokenSigner> attacks = new ArrayList<>();
TokenSigner is = token.getSigner();
this.signingKeys.forEach(key -> {
TokenSigner ks = new TokenSigner(key);
attacks.add(ks);
});
secrets.forEach(secret ->
is.getKnownDerivations().forEach(d -> attacks.addAll(is.cloneWithSaltDerivation(secret, salts, d)))
);
return attacks;
}

public List<TokenSigner> prepareAdvanced() {
List<TokenSigner> attacks = new ArrayList<>();

List<Derivation> derivations = new ArrayList<>(List.of(Derivation.values()));
derivations.remove(Derivation.RUBY_ENCRYPTION);

Set<MessageDerivation> messages = new HashSet<>(List.of(MessageDerivation.NONE));

Expand Down Expand Up @@ -112,7 +124,7 @@ public SecretKey search(List<TokenSigner> attacks) {

public SecretKey parallel() {
int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
List<TokenSigner> attacks = prepareAdvanced();
List<TokenSigner> attacks = token instanceof RubyEncryptedToken ? prepareEncryption() : prepareAdvanced();
if (NUMBER_OF_CORES < 2) {
return search(attacks);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ public enum Derivation {
@Expose @SerializedName("7") RUBY("RUBY"),
@Expose @SerializedName("8") RUBY5("RUBY5"),
@Expose @SerializedName("9") RUBY5_TRUNCATED("RUBY5_TRUNCATED"),
@Expose @SerializedName("10") RUBY_KEY_GENERATOR("RUBY_KEY_GENERATOR");
@Expose @SerializedName("10") RUBY_KEY_GENERATOR("RUBY_KEY_GENERATOR"),
@Expose @SerializedName("11") RUBY_ENCRYPTION("RUBY_ENCRYPTION");
public final String name;

Derivation(String name) {
Expand Down
Loading

0 comments on commit ef25950

Please sign in to comment.