├── gitimg ├── key_dialog.png ├── unknown_tab.png ├── attack_dialog.png ├── editor_flask.png ├── settings_view.png ├── wordlist_view.png └── brute_force_attack.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── main │ ├── java │ │ ├── one │ │ │ └── d4d │ │ │ │ └── signsaboteur │ │ │ │ ├── keys │ │ │ │ ├── Key.java │ │ │ │ └── SecretKey.java │ │ │ │ ├── presenter │ │ │ │ ├── Presenter.java │ │ │ │ ├── PresenterStore.java │ │ │ │ └── EditorModel.java │ │ │ │ ├── itsdangerous │ │ │ │ ├── crypto │ │ │ │ │ ├── Signers.java │ │ │ │ │ ├── JSONWebSignatureTokenSigner.java │ │ │ │ │ ├── DjangoTokenSigner.java │ │ │ │ │ ├── RubyTokenSigner.java │ │ │ │ │ ├── ExpressTokenSigner.java │ │ │ │ │ ├── DangerousTokenSigner.java │ │ │ │ │ ├── OauthProxyTokenSigner.java │ │ │ │ │ └── TornadoTokenSigner.java │ │ │ │ ├── BadPayloadException.java │ │ │ │ ├── DerivationException.java │ │ │ │ ├── BadSignatureException.java │ │ │ │ ├── MessageDerivation.java │ │ │ │ ├── Attack.java │ │ │ │ ├── Algorithms.java │ │ │ │ ├── MessageDigestAlgorithm.java │ │ │ │ ├── model │ │ │ │ │ ├── MutableSignedToken.java │ │ │ │ │ ├── SignedToken.java │ │ │ │ │ ├── DjangoSignedToken.java │ │ │ │ │ ├── RubySignedToken.java │ │ │ │ │ ├── UnknownSignedToken.java │ │ │ │ │ ├── ExpressSignedToken.java │ │ │ │ │ ├── JSONWebSignature.java │ │ │ │ │ ├── OauthProxySignedToken.java │ │ │ │ │ ├── TornadoSignedToken.java │ │ │ │ │ ├── RubyEncryptedToken.java │ │ │ │ │ └── DangerousSignedToken.java │ │ │ │ └── Derivation.java │ │ │ │ ├── rsta │ │ │ │ ├── token │ │ │ │ │ ├── SignedTokenizerConstants.java │ │ │ │ │ └── SignedTokenMaker.java │ │ │ │ ├── DarkModeDetector.java │ │ │ │ ├── CustomTokenColors.java │ │ │ │ ├── CustomizedRSyntaxTextArea.java │ │ │ │ └── RstaFactory.java │ │ │ │ ├── utils │ │ │ │ ├── FontProvider.java │ │ │ │ ├── ErrorLoggingActionListenerFactory.java │ │ │ │ ├── MaxLengthStringComboBoxModel.java │ │ │ │ ├── DocumentAdapter.java │ │ │ │ ├── ErrorLoggingActionListener.java │ │ │ │ ├── TestCookie.java │ │ │ │ └── GsonHelper.java │ │ │ │ ├── forms │ │ │ │ ├── utils │ │ │ │ │ └── FormUtils.java │ │ │ │ ├── AlternateRowBackgroundDecoratingTableCellRenderer.java │ │ │ │ ├── dialog │ │ │ │ │ ├── AbstractDialog.java │ │ │ │ │ ├── NewWordDialog.java │ │ │ │ │ ├── KeyDialog.java │ │ │ │ │ ├── SignDialog.java │ │ │ │ │ ├── EncryptionDialog.java │ │ │ │ │ ├── BruteForceAttackDialog.java │ │ │ │ │ ├── NewWordDialog.form │ │ │ │ │ ├── SignDialog.form │ │ │ │ │ ├── EncryptionDialog.form │ │ │ │ │ └── BruteForceAttackDialog.form │ │ │ │ ├── MessageDialogFactory.java │ │ │ │ ├── KeysTableColumns.java │ │ │ │ ├── ExtensionTab.java │ │ │ │ ├── KeysTableModel.java │ │ │ │ ├── RequestEditorView.java │ │ │ │ ├── ExtensionTab.form │ │ │ │ └── ResponseEditorView.java │ │ │ │ ├── RowHeightDecoratingTableCellRenderer.java │ │ │ │ ├── hexcodearea │ │ │ │ ├── HexCodeAreaFactory.java │ │ │ │ ├── FontMetricsClearingCodeArea.java │ │ │ │ └── HexCodeAreaCommandHandler.java │ │ │ │ └── PercentageBasedColumnWidthTable.java │ │ └── burp │ │ │ ├── config │ │ │ ├── BurpConfig.java │ │ │ ├── KeysModelListener.java │ │ │ ├── SignerConfig.java │ │ │ ├── BurpConfigPersistence.java │ │ │ ├── ProxyConfig.java │ │ │ ├── BurpKeysModelPersistence.java │ │ │ └── KeysModel.java │ │ │ ├── scanner │ │ │ ├── ScannerPresenter.java │ │ │ └── BrokenSecretKeyIssue.java │ │ │ ├── proxy │ │ │ ├── HighlightColor.java │ │ │ ├── ProxyHttpMessageHandler.java │ │ │ ├── ProxyWsMessageHandler.java │ │ │ └── AnnotationsModifier.java │ │ │ └── SignSaboteurExtension.java │ └── resources │ │ ├── salts │ │ ├── keys │ │ └── strings.properties └── test │ ├── resources │ ├── secrets │ └── salts │ └── java │ ├── CompressTest.java │ ├── FlaskDangerousTest.java │ ├── TimestampTest.java │ ├── KeyPersistenceStoreTest.java │ ├── OAuth2Test.java │ ├── ExpressSignedCookieTest.java │ ├── TornadoTest.java │ ├── SignUnsignTest.java │ ├── ClaimsTest.java │ ├── DjangoTest.java │ ├── JSONWebSignatureTest.java │ └── BruteForceTest.java ├── settings.gradle ├── .gitignore ├── .github └── workflows │ └── gradle.yml ├── CHANGELOG.md └── gradlew.bat /gitimg/key_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ge/sign-saboteur/HEAD/gitimg/key_dialog.png -------------------------------------------------------------------------------- /gitimg/unknown_tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ge/sign-saboteur/HEAD/gitimg/unknown_tab.png -------------------------------------------------------------------------------- /gitimg/attack_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ge/sign-saboteur/HEAD/gitimg/attack_dialog.png -------------------------------------------------------------------------------- /gitimg/editor_flask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ge/sign-saboteur/HEAD/gitimg/editor_flask.png -------------------------------------------------------------------------------- /gitimg/settings_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ge/sign-saboteur/HEAD/gitimg/settings_view.png -------------------------------------------------------------------------------- /gitimg/wordlist_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ge/sign-saboteur/HEAD/gitimg/wordlist_view.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | bouncycastle_version=1.73 2 | gui_designer_version=231.9392.1 3 | extender_version=2023.5 -------------------------------------------------------------------------------- /gitimg/brute_force_attack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ge/sign-saboteur/HEAD/gitimg/brute_force_attack.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ge/sign-saboteur/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/keys/Key.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.keys; 2 | 3 | public interface Key { 4 | String getID(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/presenter/Presenter.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.presenter; 2 | 3 | public abstract class Presenter { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/crypto/Signers.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.crypto; 2 | 3 | public enum Signers { 4 | DANGEROUS, EXPRESS, OAUTH, TORNADO, RUBY, JWT, NIMBUSDS, UNKNOWN, ENCRYPTION 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/BadPayloadException.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous; 2 | 3 | public class BadPayloadException extends Exception{ 4 | public BadPayloadException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/DerivationException.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous; 2 | 3 | public class DerivationException extends Exception{ 4 | public DerivationException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/BadSignatureException.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous; 2 | 3 | public class BadSignatureException extends Exception{ 4 | public BadSignatureException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/rsta/token/SignedTokenizerConstants.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.rsta.token; 2 | 3 | public interface SignedTokenizerConstants { 4 | String MAPPING = "text/jwt"; 5 | String TOKEN_MAKER_FQCN = SignedTokenMaker.class.getName(); 6 | } 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.4/userguide/building_swift_projects.html in the Gradle documentation. 6 | */ 7 | 8 | rootProject.name = 'sign-saboteur' 9 | -------------------------------------------------------------------------------- /src/test/resources/secrets: -------------------------------------------------------------------------------- 1 | "secret" 2 | "secret key" 3 | "magic" 4 | "1" 5 | "*" 6 | "Secret" 7 | "secret1" 8 | "key2" 9 | "key1" 10 | "super secret key" 11 | "dev" 12 | "user" 13 | "j76h5PEMx3FIGr3caArJ5g==" 14 | "\u0002\u0001thisismyscretkey\u0001\u0002\\e\\y\\y\\h" 15 | "b2efbaccbdb9548217eebc73a896db73" 16 | "aeb977de013ade650b97e0aa5246813591104017871a7753fe186e9634c9129b367306606878985c759ca4fddd17d955207011bb855ef01ed414398b4ac8317b" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | .idea 25 | .gradle 26 | .DS_Store 27 | build/ 28 | out/ 29 | dist/ -------------------------------------------------------------------------------- /src/main/resources/salts: -------------------------------------------------------------------------------- 1 | "salt" 2 | "auth" 3 | "cookie-session" 4 | "itsdangerous" 5 | "itsdangerous.Signer" 6 | "django.core.signing" 7 | "django.core.signing.Signer" 8 | "django.core.signing.TimestampSigner" 9 | "django.core.signing.get_cookie_signer" 10 | "django.contrib.sessions.backends.signed_cookies" 11 | "flask-oidc-cookie" 12 | "flask-oidc-extra-data" 13 | "signed cookie" 14 | "encrypted cookie" 15 | "signed encrypted cookie" 16 | "ActiveStorage" 17 | "authenticated encrypted cookie" -------------------------------------------------------------------------------- /src/main/java/burp/config/BurpConfig.java: -------------------------------------------------------------------------------- 1 | package burp.config; 2 | 3 | import com.google.gson.annotations.Expose; 4 | 5 | public class BurpConfig { 6 | private final @Expose ProxyConfig proxyConfig = new ProxyConfig(); 7 | private final @Expose SignerConfig signerConfig = new SignerConfig(); 8 | 9 | public ProxyConfig proxyConfig() { 10 | return proxyConfig; 11 | } 12 | 13 | public SignerConfig signerConfig() { 14 | return signerConfig; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/utils/FontProvider.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.utils; 2 | 3 | import burp.api.montoya.ui.UserInterface; 4 | 5 | import java.awt.*; 6 | 7 | public class FontProvider { 8 | private final UserInterface userInterface; 9 | 10 | public FontProvider(UserInterface userInterface) { 11 | this.userInterface = userInterface; 12 | } 13 | 14 | public Font editorFont() { 15 | return userInterface.currentEditorFont(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/rsta/DarkModeDetector.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.rsta; 2 | 3 | import burp.api.montoya.ui.Theme; 4 | import burp.api.montoya.ui.UserInterface; 5 | 6 | class DarkModeDetector { 7 | private final UserInterface userInterface; 8 | 9 | DarkModeDetector(UserInterface userInterface) { 10 | this.userInterface = userInterface; 11 | } 12 | 13 | boolean isDarkMode() { 14 | return userInterface.currentTheme() == Theme.DARK; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/resources/salts: -------------------------------------------------------------------------------- 1 | "salt" 2 | "auth" 3 | "cookie-session" 4 | "itsdangerous" 5 | "itsdangerous.Signer" 6 | "django.core.signing" 7 | "django.core.signing.Signer" 8 | "django.core.signing.TimestampSigner" 9 | "django.core.signing.get_cookie_signer" 10 | "django.contrib.sessions.backends.signed_cookies" 11 | "flask-oidc-cookie" 12 | "flask-oidc-extra-data" 13 | "signed cookie" 14 | "encrypted cookie" 15 | "signed encrypted cookie" 16 | "authenticated encrypted cookie" 17 | "a4fb52b0ccb302eaef92bda18fedf5c3" -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/presenter/PresenterStore.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.presenter; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class PresenterStore { 7 | 8 | private final Map presenters = new HashMap<>(); 9 | 10 | public Presenter get(Class cls) { 11 | return presenters.get(cls); 12 | } 13 | 14 | public void register(Presenter presenter) { 15 | presenters.put(presenter.getClass(), presenter); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/MessageDerivation.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | public enum MessageDerivation { 7 | @Expose @SerializedName("0") NONE("none"), 8 | @Expose @SerializedName("1") CONCAT("concat"), 9 | @Expose @SerializedName("2") TORNADO("tornado"); 10 | public final String name; 11 | MessageDerivation(String name) { 12 | this.name = name; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/burp/config/KeysModelListener.java: -------------------------------------------------------------------------------- 1 | package burp.config; 2 | 3 | import one.d4d.signsaboteur.keys.SecretKey; 4 | 5 | public interface KeysModelListener { 6 | void notifyKeyInserted(SecretKey key); 7 | 8 | void notifyKeyDeleted(int rowIndex); 9 | 10 | class InertKeyModelListener implements KeysModelListener { 11 | @Override 12 | public void notifyKeyInserted(SecretKey key) { 13 | } 14 | 15 | @Override 16 | public void notifyKeyDeleted(int rowIndex) { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/utils/FormUtils.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.forms.utils; 2 | 3 | import org.exbin.deltahex.swing.CodeArea; 4 | import org.exbin.utils.binary_data.BinaryData; 5 | 6 | public class FormUtils { 7 | public static byte[] getCodeAreaData(CodeArea codeArea) { 8 | BinaryData binaryData = codeArea.getData(); 9 | int size = (int) binaryData.getDataSize(); 10 | byte[] data = new byte[size]; 11 | binaryData.copyToArray(0L, data, 0, size); 12 | return data; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/utils/ErrorLoggingActionListenerFactory.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.utils; 2 | 3 | import burp.api.montoya.logging.Logging; 4 | 5 | import java.awt.event.ActionListener; 6 | 7 | public class ErrorLoggingActionListenerFactory { 8 | private final Logging logging; 9 | 10 | public ErrorLoggingActionListenerFactory(Logging logging) { 11 | this.logging = logging; 12 | } 13 | 14 | public ErrorLoggingActionListener from(ActionListener actionListener) { 15 | return new ErrorLoggingActionListener(logging, actionListener); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/Attack.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | public enum Attack { 6 | @SerializedName("Known") 7 | KNOWN("Known"), 8 | @SerializedName("Fast") 9 | FAST("Fast"), 10 | @SerializedName("Balanced") 11 | Balanced("Balanced"), 12 | @SerializedName("Deep") 13 | Deep("Deep"); 14 | 15 | public final String name; 16 | 17 | Attack(String name) { 18 | this.name = name; 19 | } 20 | 21 | public String getName() { 22 | return name; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/Algorithms.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | public enum Algorithms { 7 | @Expose @SerializedName("1") SHA1("HmacSHA1"), 8 | @Expose @SerializedName("2") SHA224("HmacSHA224"), 9 | @Expose @SerializedName("3") SHA256("HmacSHA256"), 10 | @Expose @SerializedName("4") SHA384("HmacSHA384"), 11 | @Expose @SerializedName("5") SHA512("HmacSHA512"); 12 | public final String name; 13 | 14 | Algorithms(String name) { 15 | this.name = name; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/CompressTest.java: -------------------------------------------------------------------------------- 1 | import one.d4d.signsaboteur.utils.Utils; 2 | import org.junit.jupiter.api.Test; 3 | 4 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 5 | import static org.junit.jupiter.api.Assertions.fail; 6 | 7 | public class CompressTest { 8 | @Test 9 | void Base64EncodedTimestampTest() { 10 | try { 11 | String expectedValue = ".eJxTKkstqlSgIpGTn5eukJyfV5KaV6IEAJM1I3A"; 12 | byte[] decArray = Utils.base64Decompress(expectedValue.getBytes()); 13 | String realValue = Utils.compressBase64(decArray); 14 | assertArrayEquals(expectedValue.toCharArray(), realValue.toCharArray()); 15 | } catch (Exception e) { 16 | fail(e.getMessage()); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/utils/MaxLengthStringComboBoxModel.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.utils; 2 | 3 | import javax.swing.*; 4 | import java.util.List; 5 | 6 | public class MaxLengthStringComboBoxModel extends DefaultComboBoxModel { 7 | private static final String FORMAT_STRING = "%s ..."; 8 | 9 | public MaxLengthStringComboBoxModel(int maxLength, List items) { 10 | super(items.stream().map(item -> truncateIfRequired(maxLength, item)).toArray(String[]::new)); 11 | } 12 | 13 | private static String truncateIfRequired(int maxLength, String item) { 14 | return item != null && item.length() > maxLength 15 | ? FORMAT_STRING.formatted(item.substring(0, maxLength)) 16 | : item; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/MessageDigestAlgorithm.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | public enum MessageDigestAlgorithm { 7 | @Expose @SerializedName("1") MD5("MD5"), 8 | @Expose @SerializedName("2") SHA1("SHA-1"), 9 | @Expose @SerializedName("3") SHA224("SHA-224"), 10 | @Expose @SerializedName("4") SHA256("SHA-256"), 11 | @Expose @SerializedName("5") SHA384("SHA-384"), 12 | @Expose @SerializedName("6") SHA512("SHA-512"), 13 | @Expose @SerializedName("7") NONE("NONE"); 14 | 15 | public final String name; 16 | 17 | MessageDigestAlgorithm(String name) { 18 | this.name = name; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/model/MutableSignedToken.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.model; 2 | 3 | public class MutableSignedToken { 4 | private final String original; 5 | private SignedToken modified; 6 | 7 | public MutableSignedToken(String original, SignedToken modified) { 8 | this.original = original; 9 | this.modified = modified; 10 | } 11 | 12 | public boolean cracked() { 13 | return modified.getKey() != null; 14 | } 15 | 16 | public boolean changed() { 17 | return !original.equals(modified.serialize()); 18 | } 19 | 20 | public void setModified(SignedToken o) { 21 | modified = o; 22 | } 23 | 24 | public SignedToken getModified() { 25 | return modified; 26 | } 27 | 28 | public String getOriginal() { 29 | return original; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/utils/DocumentAdapter.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.utils; 2 | 3 | import javax.swing.event.DocumentEvent; 4 | import javax.swing.event.DocumentListener; 5 | 6 | public class DocumentAdapter implements DocumentListener { 7 | 8 | @FunctionalInterface 9 | public interface DocumentAction { 10 | void documentUpdated(DocumentEvent e); 11 | } 12 | 13 | private final DocumentAction action; 14 | 15 | public DocumentAdapter(DocumentAction action) { 16 | this.action = action; 17 | } 18 | 19 | @Override 20 | public void insertUpdate(DocumentEvent e) { 21 | action.documentUpdated(e); 22 | } 23 | 24 | @Override 25 | public void removeUpdate(DocumentEvent e) { 26 | action.documentUpdated(e); 27 | } 28 | 29 | @Override 30 | public void changedUpdate(DocumentEvent e) { 31 | action.documentUpdated(e); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/FlaskDangerousTest.java: -------------------------------------------------------------------------------- 1 | import one.d4d.signsaboteur.itsdangerous.crypto.DangerousTokenSigner; 2 | import one.d4d.signsaboteur.itsdangerous.model.DangerousSignedToken; 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 6 | 7 | public class FlaskDangerousTest { 8 | @Test 9 | void DefaultFlaskSignedTokenTest() { 10 | byte[] secret = "secret".getBytes(); 11 | byte[] salt = "cookie-session".getBytes(); 12 | byte[] sep = new byte[]{(byte) '.'}; 13 | DangerousSignedToken newToken = new DangerousSignedToken(sep, "{}", "Zzx63w", ""); 14 | DangerousTokenSigner s = new DangerousTokenSigner(secret, salt, sep); 15 | newToken.setSigner(s); 16 | char[] signedToken = newToken.dumps().toCharArray(); 17 | char[] testValue = "e30.Zzx63w.2BFIyJyE4fVqPm2hhw3edr8QTwo".toCharArray(); 18 | assertArrayEquals(signedToken, testValue); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/RowHeightDecoratingTableCellRenderer.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur; 2 | 3 | import javax.swing.*; 4 | import javax.swing.table.TableCellRenderer; 5 | import java.awt.*; 6 | 7 | public record RowHeightDecoratingTableCellRenderer(TableCellRenderer tableCellRenderer) implements TableCellRenderer { 8 | private static final int ADDITIONAL_HEIGHT_PIXELS = 5; 9 | 10 | @Override 11 | public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 12 | Component component = tableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 13 | int componentHeight = component.getPreferredSize().height; 14 | 15 | if (table.getRowHeight() != componentHeight + ADDITIONAL_HEIGHT_PIXELS) { 16 | table.setRowHeight(componentHeight + ADDITIONAL_HEIGHT_PIXELS); 17 | } 18 | 19 | return component; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/AlternateRowBackgroundDecoratingTableCellRenderer.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.forms; 2 | 3 | import javax.swing.*; 4 | import javax.swing.table.TableCellRenderer; 5 | import java.awt.*; 6 | 7 | public record AlternateRowBackgroundDecoratingTableCellRenderer(TableCellRenderer tableCellRenderer) implements TableCellRenderer { 8 | 9 | @Override 10 | public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 11 | Component component = tableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 12 | 13 | if (!isSelected && !hasFocus) { 14 | Color alternateRowColor = UIManager.getColor("Table.alternateRowColor"); 15 | 16 | if (alternateRowColor != null && row % 2 != 0) { 17 | component.setBackground(alternateRowColor); 18 | } 19 | } 20 | 21 | return component; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/Derivation.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | public enum Derivation { 7 | @Expose @SerializedName("1") PBKDF2HMAC("PBKDF2HMAC"), 8 | @Expose @SerializedName("2") HASH("hash"), 9 | @Expose @SerializedName("3") CONCAT("concat"), 10 | @Expose @SerializedName("4") DJANGO("django-concat"), 11 | @Expose @SerializedName("5") HMAC("hmac"), 12 | @Expose @SerializedName("6") NONE("none"), 13 | @Expose @SerializedName("7") RUBY("RUBY"), 14 | @Expose @SerializedName("8") RUBY5("RUBY5"), 15 | @Expose @SerializedName("9") RUBY5_TRUNCATED("RUBY5_TRUNCATED"), 16 | @Expose @SerializedName("10") RUBY_KEY_GENERATOR("RUBY_KEY_GENERATOR"), 17 | @Expose @SerializedName("11") RUBY_ENCRYPTION("RUBY_ENCRYPTION"); 18 | public final String name; 19 | 20 | Derivation(String name) { 21 | this.name = name; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/utils/ErrorLoggingActionListener.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.utils; 2 | 3 | import burp.api.montoya.logging.Logging; 4 | 5 | import java.awt.event.ActionEvent; 6 | import java.awt.event.ActionListener; 7 | import java.io.PrintWriter; 8 | import java.io.StringWriter; 9 | 10 | class ErrorLoggingActionListener implements ActionListener { 11 | private final Logging logging; 12 | private final ActionListener actionListener; 13 | 14 | ErrorLoggingActionListener(Logging logging, ActionListener actionListener) { 15 | this.logging = logging; 16 | this.actionListener = actionListener; 17 | } 18 | 19 | @Override 20 | public void actionPerformed(ActionEvent e) { 21 | try { 22 | actionListener.actionPerformed(e); 23 | } catch (RuntimeException ex) { 24 | StringWriter stackTrace = new StringWriter(); 25 | ex.printStackTrace(new PrintWriter(stackTrace)); 26 | logging.logToError(stackTrace.toString()); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/burp/config/SignerConfig.java: -------------------------------------------------------------------------------- 1 | package burp.config; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import one.d4d.signsaboteur.itsdangerous.crypto.Signers; 5 | 6 | import java.util.EnumSet; 7 | import java.util.Set; 8 | 9 | public class SignerConfig { 10 | 11 | @Expose 12 | private Set enabled; 13 | 14 | public SignerConfig() { 15 | EnumSet disabled = EnumSet.of(Signers.OAUTH, Signers.NIMBUSDS, Signers.UNKNOWN); 16 | this.enabled = EnumSet.complementOf(disabled); 17 | } 18 | 19 | public boolean isEnabled(Signers s) { 20 | return this.enabled.contains(s); 21 | } 22 | 23 | public void setEnabled(Signers s) { 24 | this.enabled.add(s); 25 | } 26 | 27 | public void removeEnabled(Signers s) { 28 | this.enabled.remove(s); 29 | } 30 | 31 | public void toggleEnabled(Signers s, boolean isEnabled) { 32 | if (isEnabled) { 33 | this.setEnabled(s); 34 | } else { 35 | this.removeEnabled(s); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/dialog/AbstractDialog.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.forms.dialog; 2 | 3 | import one.d4d.signsaboteur.utils.Utils; 4 | 5 | import javax.swing.*; 6 | import java.awt.*; 7 | import java.awt.event.WindowAdapter; 8 | import java.awt.event.WindowEvent; 9 | 10 | import static java.awt.Dialog.ModalityType.APPLICATION_MODAL; 11 | 12 | public abstract class AbstractDialog extends JDialog { 13 | 14 | protected AbstractDialog(Window parent, String titleResourceId) { 15 | super(parent, Utils.getResourceString(titleResourceId), APPLICATION_MODAL); 16 | 17 | setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); 18 | 19 | addWindowListener(new WindowAdapter() { 20 | @Override 21 | public void windowClosing(WindowEvent e) { 22 | onCancel(); 23 | } 24 | }); 25 | } 26 | 27 | public void display() { 28 | pack(); 29 | setLocationRelativeTo(getOwner()); 30 | setVisible(true); 31 | } 32 | 33 | protected void onCancel() { 34 | dispose(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/crypto/JSONWebSignatureTokenSigner.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.crypto; 2 | 3 | import one.d4d.signsaboteur.itsdangerous.Algorithms; 4 | import one.d4d.signsaboteur.itsdangerous.Derivation; 5 | import one.d4d.signsaboteur.itsdangerous.MessageDerivation; 6 | import one.d4d.signsaboteur.itsdangerous.MessageDigestAlgorithm; 7 | import one.d4d.signsaboteur.keys.SecretKey; 8 | 9 | import java.util.EnumSet; 10 | 11 | public class JSONWebSignatureTokenSigner extends TokenSigner { 12 | 13 | public JSONWebSignatureTokenSigner(SecretKey key) { 14 | super(key); 15 | this.keyDerivation = Derivation.NONE; 16 | this.messageDigestAlgorithm = MessageDigestAlgorithm.NONE; 17 | this.knownDerivations = EnumSet.of(Derivation.NONE); 18 | } 19 | 20 | public JSONWebSignatureTokenSigner(byte[] sep) { 21 | super(Algorithms.SHA256, Derivation.NONE, MessageDerivation.NONE, MessageDigestAlgorithm.NONE, new byte[]{}, new byte[]{}, sep); 22 | this.knownDerivations = EnumSet.of(Derivation.NONE); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/TimestampTest.java: -------------------------------------------------------------------------------- 1 | import one.d4d.signsaboteur.utils.Utils; 2 | import org.junit.jupiter.api.Test; 3 | 4 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 5 | import static org.junit.jupiter.api.Assertions.fail; 6 | 7 | public class TimestampTest { 8 | @Test 9 | void Base64EncodedTimestampTest() { 10 | String expectedValue = "Zm17Ig"; 11 | String prob = Utils.base64timestamp(expectedValue.getBytes()); 12 | String realValue = Utils.encodeBase64TimestampFromDate(prob); 13 | assertArrayEquals(expectedValue.toCharArray(),realValue.toCharArray()); 14 | 15 | } 16 | @Test 17 | void Base62EncodedTimestampTest() { 18 | try { 19 | String expectedValue = "1rBDnz"; 20 | String prob = Utils.base62timestamp(expectedValue.getBytes()); 21 | String realValue = Utils.encodeBase62TimestampFromDate(prob); 22 | assertArrayEquals(expectedValue.toCharArray(), realValue.toCharArray()); 23 | }catch (Exception e) { 24 | fail(e.getMessage()); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/utils/TestCookie.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.utils; 2 | 3 | import java.time.ZonedDateTime; 4 | import java.util.Optional; 5 | 6 | public class TestCookie implements burp.api.montoya.http.message.Cookie { 7 | 8 | private final String name; 9 | private final String value; 10 | private final String domain; 11 | private final String path; 12 | 13 | public TestCookie(String name, String value, String domain, String path) { 14 | this.name = name; 15 | this.value = value; 16 | this.domain = domain; 17 | this.path = path; 18 | } 19 | 20 | @Override 21 | public String name() { 22 | return name; 23 | } 24 | 25 | @Override 26 | public String value() { 27 | return value; 28 | } 29 | 30 | @Override 31 | public String domain() { 32 | return domain; 33 | } 34 | 35 | @Override 36 | public String path() { 37 | return path; 38 | } 39 | 40 | @Override 41 | public Optional expiration() { 42 | return Optional.empty(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/burp/scanner/ScannerPresenter.java: -------------------------------------------------------------------------------- 1 | package burp.scanner; 2 | 3 | import one.d4d.signsaboteur.keys.SecretKey; 4 | import one.d4d.signsaboteur.presenter.KeyPresenter; 5 | import one.d4d.signsaboteur.presenter.Presenter; 6 | import one.d4d.signsaboteur.presenter.PresenterStore; 7 | 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | public class ScannerPresenter extends Presenter { 12 | private final PresenterStore presenters; 13 | 14 | public ScannerPresenter(PresenterStore presenters) { 15 | this.presenters = presenters; 16 | presenters.register(this); 17 | } 18 | 19 | public List getSigningKeys() { 20 | KeyPresenter keysPresenter = (KeyPresenter) presenters.get(KeyPresenter.class); 21 | return keysPresenter.getSigningKeys(); 22 | } 23 | public Set getSigningSecrets() { 24 | KeyPresenter keysPresenter = (KeyPresenter) presenters.get(KeyPresenter.class); 25 | return keysPresenter.getSecrets(); 26 | } 27 | public Set getSigningSalts() { 28 | KeyPresenter keysPresenter = (KeyPresenter) presenters.get(KeyPresenter.class); 29 | return keysPresenter.getSalts(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/MessageDialogFactory.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.forms; 2 | 3 | import one.d4d.signsaboteur.utils.Utils; 4 | 5 | import javax.swing.*; 6 | import java.awt.*; 7 | 8 | import static javax.swing.JOptionPane.ERROR_MESSAGE; 9 | import static javax.swing.JOptionPane.WARNING_MESSAGE; 10 | 11 | public class MessageDialogFactory { 12 | private final Component parent; 13 | 14 | public MessageDialogFactory(Component parent) { 15 | this.parent = parent; 16 | } 17 | public void showErrorDialog(String titleKey, String messageKey, Object... args) { 18 | showDialog(ERROR_MESSAGE, titleKey, messageKey, args); 19 | } 20 | 21 | public void showWarningDialog(String titleKey, String messageKey, Object... args) { 22 | showDialog(WARNING_MESSAGE, titleKey, messageKey, args); 23 | } 24 | 25 | private void showDialog(int messageType, String titleKey, String messageKey, Object... args) { 26 | JOptionPane.showMessageDialog( 27 | parent, 28 | Utils.getResourceString(messageKey).formatted(args), 29 | Utils.getResourceString(titleKey), 30 | messageType 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/rsta/CustomTokenColors.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.rsta; 2 | 3 | import java.awt.*; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.Optional; 7 | 8 | class CustomTokenColors { 9 | private final Map foregroundColors; 10 | 11 | private CustomTokenColors(Map foregroundColors) { 12 | this.foregroundColors = foregroundColors; 13 | } 14 | 15 | Optional foregroundForTokenType(int type) { 16 | return Optional.ofNullable(foregroundColors.get(type)); 17 | } 18 | 19 | static CustomTokenColorsBuilder customTokenColors() { 20 | return new CustomTokenColorsBuilder(); 21 | } 22 | 23 | static class CustomTokenColorsBuilder { 24 | private final Map foregroundColors = new HashMap<>(); 25 | 26 | private CustomTokenColorsBuilder() { 27 | } 28 | 29 | CustomTokenColorsBuilder withForeground(int type, Color color) { 30 | foregroundColors.put(type, color); 31 | return this; 32 | } 33 | 34 | CustomTokenColors build() { 35 | return new CustomTokenColors(foregroundColors); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/hexcodearea/HexCodeAreaFactory.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.hexcodearea; 2 | 3 | import burp.api.montoya.logging.Logging; 4 | import burp.api.montoya.ui.UserInterface; 5 | import one.d4d.signsaboteur.utils.FontProvider; 6 | import org.exbin.deltahex.swing.CodeArea; 7 | import org.exbin.utils.binary_data.ByteArrayEditableData; 8 | 9 | import static org.exbin.deltahex.ViewMode.CODE_MATRIX; 10 | 11 | public class HexCodeAreaFactory { 12 | private final Logging logging; 13 | private final FontProvider fontProvider; 14 | 15 | public HexCodeAreaFactory(Logging logging, UserInterface userInterface) { 16 | this.logging = logging; 17 | this.fontProvider = new FontProvider(userInterface); 18 | } 19 | 20 | public CodeArea build() { 21 | CodeArea codeArea = new FontMetricsClearingCodeArea(logging); 22 | 23 | codeArea.setCommandHandler(new HexCodeAreaCommandHandler(codeArea)); 24 | codeArea.setShowHeader(false); 25 | codeArea.setShowLineNumbers(false); 26 | codeArea.setViewMode(CODE_MATRIX); 27 | codeArea.setData(new ByteArrayEditableData()); 28 | codeArea.setFont(fontProvider.editorFont()); 29 | 30 | return codeArea; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/KeyPersistenceStoreTest.java: -------------------------------------------------------------------------------- 1 | import burp.config.KeysModel; 2 | import com.google.gson.Gson; 3 | import one.d4d.signsaboteur.itsdangerous.Algorithms; 4 | import one.d4d.signsaboteur.itsdangerous.Derivation; 5 | import one.d4d.signsaboteur.itsdangerous.MessageDerivation; 6 | import one.d4d.signsaboteur.itsdangerous.MessageDigestAlgorithm; 7 | import one.d4d.signsaboteur.keys.SecretKey; 8 | import one.d4d.signsaboteur.utils.GsonHelper; 9 | import one.d4d.signsaboteur.utils.Utils; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import static org.junit.jupiter.api.Assertions.*; 13 | 14 | public class KeyPersistenceStoreTest { 15 | @Test 16 | void KeyDerivationTest() { 17 | KeysModel model = new KeysModel(); 18 | model.setSalts(Utils.readResourceForClass("/salts", this.getClass())); 19 | model.setSecrets(Utils.readResourceForClass("/secrets", this.getClass())); 20 | model.addKey(new SecretKey("test", "secret","salt",".", Algorithms.SHA1, Derivation.HMAC, MessageDerivation.NONE, MessageDigestAlgorithm.SHA1)); 21 | 22 | Gson gson = GsonHelper.customGson; 23 | String serial = gson.toJson(model); 24 | KeysModel restoredModel = gson.fromJson(serial, KeysModel.class); 25 | assertEquals(model.getSigningKeys().size(), restoredModel.getSigningKeys().size()); 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/model/SignedToken.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.model; 2 | 3 | import com.nimbusds.jwt.JWTClaimsSet; 4 | import one.d4d.signsaboteur.itsdangerous.crypto.TokenSigner; 5 | import one.d4d.signsaboteur.keys.SecretKey; 6 | 7 | public abstract class SignedToken { 8 | public String message; 9 | public String signature; 10 | public TokenSigner signer; 11 | private SecretKey key; 12 | 13 | public SignedToken(String message) { 14 | this.message = message; 15 | } 16 | 17 | public TokenSigner getSigner() { 18 | return signer; 19 | } 20 | 21 | public void setSigner(TokenSigner signer) { 22 | this.signer = signer; 23 | } 24 | 25 | public SecretKey getKey() { 26 | return key; 27 | } 28 | 29 | public void setKey(SecretKey key) { 30 | this.key = key; 31 | } 32 | 33 | public String getEncodedMessage() { 34 | return message; 35 | } 36 | 37 | public abstract String serialize(); 38 | 39 | public abstract void resign() throws Exception; 40 | public abstract void setClaims(JWTClaimsSet claims); 41 | 42 | public String getEncodedSignature() { 43 | return signature; 44 | } 45 | 46 | public byte[] getSignature() { 47 | return signature.getBytes(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/KeysTableColumns.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.forms; 2 | 3 | import one.d4d.signsaboteur.utils.Utils; 4 | 5 | import static java.util.Arrays.stream; 6 | 7 | enum KeysTableColumns { 8 | ID("table_id", 30, String.class), 9 | SECRET("table_secret", 30, String.class), 10 | ALGORITHM("table_algorithm", 10, String.class), 11 | DERIVATION("table_derivation",10, String.class), 12 | MESSAGE_DERIVATION("table_message_derivation", 10, String.class), 13 | DIGEST("table_digest",10, String.class); 14 | 15 | private final String label; 16 | private final int widthPercentage; 17 | private final Class type; 18 | 19 | KeysTableColumns(String labelResourceId, int widthPercentage, Class type) { 20 | this.label = Utils.getResourceString(labelResourceId); 21 | this.widthPercentage = widthPercentage; 22 | this.type = type; 23 | } 24 | 25 | static int[] columnWidthPercentages() { 26 | return stream(values()).mapToInt(c -> c.widthPercentage).toArray(); 27 | } 28 | 29 | static KeysTableColumns fromIndex(int index) { 30 | return values()[index]; 31 | } 32 | 33 | static String labelWithIndex(int index) { 34 | return values()[index].label; 35 | } 36 | 37 | static Class typeForIndex(int index) { 38 | return values()[index].type; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/crypto/DjangoTokenSigner.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.crypto; 2 | 3 | import one.d4d.signsaboteur.itsdangerous.Algorithms; 4 | import one.d4d.signsaboteur.itsdangerous.Derivation; 5 | import one.d4d.signsaboteur.itsdangerous.MessageDerivation; 6 | import one.d4d.signsaboteur.itsdangerous.MessageDigestAlgorithm; 7 | import one.d4d.signsaboteur.keys.SecretKey; 8 | 9 | import java.util.EnumSet; 10 | 11 | public class DjangoTokenSigner extends DangerousTokenSigner { 12 | 13 | public DjangoTokenSigner(SecretKey key) { 14 | super(key); 15 | this.knownDerivations = EnumSet.of(Derivation.DJANGO); 16 | } 17 | 18 | public DjangoTokenSigner(byte[] secret_key, byte[] salt, byte[] sep) { 19 | this(Algorithms.SHA1, Derivation.DJANGO, MessageDerivation.NONE, MessageDigestAlgorithm.SHA1, secret_key, salt, sep); 20 | } 21 | 22 | public DjangoTokenSigner( 23 | Algorithms algorithm, 24 | Derivation keyDerivation, 25 | MessageDerivation messageDerivation, 26 | MessageDigestAlgorithm digest, 27 | byte[] secret_key, 28 | byte[] salt, 29 | byte[] sep) { 30 | super(algorithm, keyDerivation, messageDerivation, digest, secret_key, salt, sep); 31 | this.knownDerivations = EnumSet.of(Derivation.DJANGO); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/utils/GsonHelper.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.utils; 2 | 3 | import java.lang.reflect.Type; 4 | import java.util.Base64; 5 | 6 | import com.google.gson.Gson; 7 | import com.google.gson.GsonBuilder; 8 | import com.google.gson.JsonDeserializationContext; 9 | import com.google.gson.JsonDeserializer; 10 | import com.google.gson.JsonElement; 11 | import com.google.gson.JsonParseException; 12 | import com.google.gson.JsonPrimitive; 13 | import com.google.gson.JsonSerializationContext; 14 | import com.google.gson.JsonSerializer; 15 | 16 | public class GsonHelper { 17 | public static final Gson customGson = new GsonBuilder() 18 | .excludeFieldsWithoutExposeAnnotation() 19 | .registerTypeHierarchyAdapter(byte[].class, new ByteArrayToBase64TypeAdapter()) 20 | .create(); 21 | 22 | private static class ByteArrayToBase64TypeAdapter implements JsonSerializer, JsonDeserializer { 23 | public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 24 | return Base64.getUrlDecoder().decode(json.getAsString()); 25 | } 26 | 27 | public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) { 28 | return new JsonPrimitive(Base64.getUrlEncoder().encodeToString(src)); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/dialog/NewWordDialog.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.forms.dialog; 2 | 3 | import one.d4d.signsaboteur.utils.GsonHelper; 4 | 5 | import javax.swing.*; 6 | import java.awt.*; 7 | import java.awt.event.KeyEvent; 8 | 9 | public class NewWordDialog extends AbstractDialog { 10 | private JPanel contentPane; 11 | private JButton buttonOK; 12 | private JButton buttonCancel; 13 | private JTextField textFieldItem; 14 | private JCheckBox checkBoxJSON; 15 | private String item; 16 | 17 | public NewWordDialog(Window parent) { 18 | super(parent, "new_word_dialog_title"); 19 | setContentPane(contentPane); 20 | getRootPane().setDefaultButton(buttonOK); 21 | 22 | buttonOK.addActionListener(e -> onOK()); 23 | buttonCancel.addActionListener(e -> onCancel()); 24 | 25 | contentPane.registerKeyboardAction( 26 | e -> onCancel(), 27 | KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), 28 | JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT 29 | ); 30 | textFieldItem.setText("\"\""); 31 | checkBoxJSON.setSelected(true); 32 | } 33 | 34 | private void onOK() { 35 | item = checkBoxJSON.isSelected() ? GsonHelper.customGson.fromJson(textFieldItem.getText(), String.class) : textFieldItem.getText(); 36 | dispose(); 37 | } 38 | 39 | public String getItem() { 40 | return item; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/OAuth2Test.java: -------------------------------------------------------------------------------- 1 | import one.d4d.signsaboteur.itsdangerous.crypto.OauthProxyTokenSigner; 2 | import one.d4d.signsaboteur.itsdangerous.model.OauthProxySignedToken; 3 | import one.d4d.signsaboteur.itsdangerous.model.SignedToken; 4 | import one.d4d.signsaboteur.itsdangerous.model.SignedTokenObjectFinder; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.Optional; 9 | 10 | public class OAuth2Test { 11 | @Test 12 | void OauthProxyParserTest() { 13 | byte[] secret = "j76h5PEMx3FIGr3caArJ5g==".getBytes(); 14 | byte[] sep = new byte[]{(byte) '|'}; 15 | OauthProxyTokenSigner s = new OauthProxyTokenSigner(secret, sep); 16 | String key = "_oauth2_proxy_csrf"; 17 | String value = "hVV2htpqQw4UXgsLYtKdAWct1VAg_yPMxjq2xrGaaCfZStG0p6sGjlAGim1a686QrbBgDGNnpr6LrKH88uTQpTMHLiknn-YbVnXsbFtRyciE5QJIk3q8t24=|1688047283|MFrbdc2q8uQSZd9bpfaWWAmfkHY3U4mijmQo-vqMRKw="; 18 | Optional optionalSignedToken = SignedTokenObjectFinder.parseOauthProxySignedToken(key, value); 19 | if (optionalSignedToken.isPresent()) { 20 | OauthProxySignedToken token = (OauthProxySignedToken) optionalSignedToken.get(); 21 | token.setSigner(s); 22 | Assertions.assertDoesNotThrow(() -> { 23 | s.unsign(String.format("%s|%s", key, value).getBytes()); 24 | }); 25 | } else { 26 | Assertions.fail("Token not found."); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/burp/config/BurpConfigPersistence.java: -------------------------------------------------------------------------------- 1 | package burp.config; 2 | 3 | import burp.api.montoya.persistence.Preferences; 4 | import com.google.gson.Gson; 5 | import com.google.gson.GsonBuilder; 6 | import one.d4d.signsaboteur.itsdangerous.BruteForce; 7 | import one.d4d.signsaboteur.presenter.PresenterStore; 8 | 9 | public class BurpConfigPersistence { 10 | static final String BURP_SETTINGS_NAME = "one.d4d.signsaboteur.settings"; 11 | private final Preferences preferences; 12 | private final PresenterStore presenters; 13 | 14 | public BurpConfigPersistence(Preferences preferences, PresenterStore presenters) { 15 | this.preferences = preferences; 16 | this.presenters = presenters; 17 | } 18 | 19 | public BurpConfig loadOrCreateNew() { 20 | String json = preferences.getString(BURP_SETTINGS_NAME); 21 | 22 | if (json == null) { 23 | return new BurpConfig(); 24 | } 25 | 26 | Gson gson = new Gson(); 27 | return gson.fromJson(json, BurpConfig.class); 28 | } 29 | 30 | public void unload(BurpConfig model) { 31 | Gson gson = new GsonBuilder() 32 | .excludeFieldsWithoutExposeAnnotation() 33 | .create(); 34 | String burpConfigJson = gson.toJson(model); 35 | 36 | preferences.setString(BURP_SETTINGS_NAME, burpConfigJson); 37 | try { 38 | BruteForce presenter = (BruteForce) presenters.get(BruteForce.class); 39 | presenter.shutdown(); 40 | } catch (Exception ignored){} 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/burp/proxy/HighlightColor.java: -------------------------------------------------------------------------------- 1 | package burp.proxy; 2 | 3 | import java.awt.*; 4 | 5 | import static java.util.Arrays.stream; 6 | 7 | public enum HighlightColor { 8 | RED("Red", burp.api.montoya.core.HighlightColor.RED, Color.RED), 9 | ORANGE("Orange", burp.api.montoya.core.HighlightColor.ORANGE, Color.ORANGE), 10 | YELLOW("Yellow", burp.api.montoya.core.HighlightColor.YELLOW, Color.YELLOW), 11 | GREEN("Green", burp.api.montoya.core.HighlightColor.GREEN, Color.GREEN), 12 | CYAN("Cyan", burp.api.montoya.core.HighlightColor.CYAN, Color.CYAN), 13 | BLUE("Blue", burp.api.montoya.core.HighlightColor.BLUE, Color.BLUE), 14 | PINK("Pink", burp.api.montoya.core.HighlightColor.PINK, Color.PINK), 15 | MAGENTA("Magenta", burp.api.montoya.core.HighlightColor.MAGENTA, Color.MAGENTA), 16 | GRAY("Gray", burp.api.montoya.core.HighlightColor.GRAY, Color.GRAY); 17 | 18 | public final burp.api.montoya.core.HighlightColor burpColor; 19 | public final Color color; 20 | 21 | private final String displayName; 22 | 23 | HighlightColor(String displayName, burp.api.montoya.core.HighlightColor burpColor, Color color) { 24 | this.displayName = displayName; 25 | this.burpColor = burpColor; 26 | this.color = color; 27 | } 28 | 29 | public static HighlightColor from(String displayName) { 30 | return stream(values()).filter(highlightColor -> highlightColor.displayName.equalsIgnoreCase(displayName)).findFirst().orElse(null); 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return displayName; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time 6 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle 7 | 8 | name: Java CI with Gradle 9 | 10 | on: 11 | push: 12 | tags: 13 | - '*' 14 | 15 | permissions: 16 | contents: write 17 | 18 | jobs: 19 | build: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Set up JDK 17 24 | uses: actions/setup-java@v3 25 | with: 26 | java-version: '17' 27 | distribution: 'temurin' 28 | - name: Build with Gradle 29 | uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0 30 | with: 31 | arguments: build 32 | - name: Creating the jar file 33 | uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0 34 | with: 35 | arguments: jar 36 | - name: Upload a Build Artifact 37 | uses: actions/upload-artifact@v4.0.0 38 | with: 39 | path: ./build/libs/*.jar 40 | name: Downloadable Extension File 41 | - name: Create Release 42 | uses: ncipollo/release-action@v1.13.0 43 | with: 44 | name: ${{github.ref_name}} 45 | allowUpdates: true 46 | artifacts: "./build/libs/*.jar" 47 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/model/DjangoSignedToken.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.model; 2 | 3 | import one.d4d.signsaboteur.itsdangerous.Algorithms; 4 | import one.d4d.signsaboteur.itsdangerous.Derivation; 5 | import one.d4d.signsaboteur.itsdangerous.MessageDerivation; 6 | import one.d4d.signsaboteur.itsdangerous.MessageDigestAlgorithm; 7 | import one.d4d.signsaboteur.utils.Utils; 8 | 9 | public class DjangoSignedToken extends DangerousSignedToken { 10 | 11 | public DjangoSignedToken(byte[] separator, String payload, String timestamp, String signature) { 12 | super(separator, payload, timestamp, signature, Algorithms.SHA1, Derivation.DJANGO, MessageDerivation.NONE, MessageDigestAlgorithm.SHA1); 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | try { 18 | StringBuilder sb = new StringBuilder(); 19 | byte[] json = Utils.base64Decompress(this.payload.getBytes()); 20 | sb.append(new String(json)).append(new String(this.separator)); 21 | sb.append(Utils.base62timestamp(this.timestamp.getBytes())).append(new String(this.separator)); 22 | sb.append(this.signature); 23 | return sb.toString(); 24 | } catch (Exception e) { 25 | return String.format("%s%s%s%s%s", payload, new String(separator), timestamp, new String(separator), signature); 26 | } 27 | } 28 | 29 | @Override 30 | public String getTimestamp() { 31 | try { 32 | return Utils.base62timestamp(timestamp.getBytes()); 33 | } catch (Exception e) { 34 | return Utils.timestamp(timestamp.getBytes()); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/model/RubySignedToken.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.model; 2 | 3 | import one.d4d.signsaboteur.itsdangerous.crypto.RubyTokenSigner; 4 | import one.d4d.signsaboteur.itsdangerous.crypto.Signers; 5 | 6 | import java.net.URLEncoder; 7 | import java.nio.charset.StandardCharsets; 8 | import java.util.HexFormat; 9 | 10 | public class RubySignedToken extends UnknownSignedToken { 11 | 12 | public RubySignedToken(String message, String signature) { 13 | this(message, signature, "--".getBytes(), false); 14 | } 15 | public RubySignedToken(String message, String signature, boolean isURLEncoded) { 16 | this(message, signature, "--".getBytes(), isURLEncoded); 17 | } 18 | 19 | public RubySignedToken(String message, String signature, byte[] separator, boolean isURLEncoded) { 20 | super(message, signature, separator, isURLEncoded); 21 | this.signer = new RubyTokenSigner(separator); 22 | } 23 | 24 | @Override 25 | public void resign() throws Exception { 26 | HexFormat hexFormat = HexFormat.of(); 27 | this.signature = hexFormat.formatHex(signer.get_signature_unsafe(message.getBytes())); 28 | } 29 | @Override 30 | public String serialize() { 31 | String raw = isURLEncoded ? URLEncoder.encode(message, StandardCharsets.UTF_8) : message; 32 | return String.format("%s%s%s", raw, new String(separator), signature); 33 | } 34 | 35 | 36 | @Override 37 | public String getEncodedSignature() { 38 | return signature.toLowerCase(); 39 | } 40 | 41 | @Override 42 | public String getSignersName() { 43 | return Signers.RUBY.name(); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/burp/config/ProxyConfig.java: -------------------------------------------------------------------------------- 1 | package burp.config; 2 | 3 | import burp.proxy.HighlightColor; 4 | import com.google.gson.annotations.Expose; 5 | import one.d4d.signsaboteur.utils.Utils; 6 | 7 | import static burp.proxy.HighlightColor.GREEN; 8 | 9 | public class ProxyConfig { 10 | public static final HighlightColor DEFAULT_HIGHLIGHT_COLOR = GREEN; 11 | 12 | private static final String BURP_PROXY_COMMENT_TEMPLATE = Utils.getResourceString("burp_proxy_comment"); 13 | 14 | @Expose 15 | private boolean highlightToken; 16 | @Expose 17 | private boolean enablePassiveScan; 18 | @Expose 19 | private HighlightColor highlightColor; 20 | 21 | public ProxyConfig() { 22 | this.highlightToken = true; 23 | this.enablePassiveScan = false; 24 | this.highlightColor = DEFAULT_HIGHLIGHT_COLOR; 25 | } 26 | 27 | public boolean enablePassiveScan() { 28 | return enablePassiveScan; 29 | } 30 | 31 | public void disablePassiveScan(boolean enablePassiveScan) { 32 | this.enablePassiveScan = enablePassiveScan; 33 | } 34 | 35 | public boolean highlightToken() { 36 | return highlightToken; 37 | } 38 | 39 | public void setHighlightToken(boolean highlightToken) { 40 | this.highlightToken = highlightToken; 41 | } 42 | 43 | public HighlightColor highlightColor() { 44 | return highlightColor; 45 | } 46 | 47 | public void setHighlightColor(HighlightColor highlightColor) { 48 | this.highlightColor = highlightColor == null ? DEFAULT_HIGHLIGHT_COLOR : highlightColor; 49 | } 50 | 51 | public String comment(int count) { 52 | return String.format(BURP_PROXY_COMMENT_TEMPLATE, count); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/PercentageBasedColumnWidthTable.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur; 2 | 3 | import javax.swing.*; 4 | import javax.swing.table.TableColumnModel; 5 | import java.awt.event.HierarchyEvent; 6 | import java.awt.event.HierarchyListener; 7 | 8 | import static java.awt.event.HierarchyEvent.SHOWING_CHANGED; 9 | 10 | public class PercentageBasedColumnWidthTable extends JTable { 11 | private final int[] columnWidthPercentages; 12 | 13 | public PercentageBasedColumnWidthTable(int[] columnWidthPercentages) { 14 | this.columnWidthPercentages = columnWidthPercentages; 15 | addHierarchyListener(new ResizeColumnsOnFirstRenderHierarchyListener()); 16 | 17 | tableHeader.setReorderingAllowed(false); 18 | } 19 | 20 | private void resizeColumns() { 21 | TableColumnModel columnModel = this.getColumnModel(); 22 | 23 | if (columnWidthPercentages == null || columnModel.getColumnCount() != columnWidthPercentages.length) { 24 | return; 25 | } 26 | 27 | int tableWidth = getWidth(); 28 | 29 | for (int i = 0; i < columnWidthPercentages.length; i++) { 30 | int preferredWidth = (int) (columnWidthPercentages[i] * 0.01 * tableWidth); 31 | columnModel.getColumn(i).setPreferredWidth(preferredWidth); 32 | } 33 | } 34 | 35 | private class ResizeColumnsOnFirstRenderHierarchyListener implements HierarchyListener { 36 | @Override 37 | public void hierarchyChanged(HierarchyEvent e) { 38 | if (e.getChangeFlags() != SHOWING_CHANGED || !e.getComponent().isShowing()) { 39 | return; 40 | } 41 | 42 | resizeColumns(); 43 | removeHierarchyListener(this); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/ExtensionTab.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.forms; 2 | 3 | import burp.api.montoya.ui.UserInterface; 4 | import burp.config.BurpConfig; 5 | import burp.config.BurpKeysModelPersistence; 6 | import burp.config.KeysModel; 7 | import one.d4d.signsaboteur.presenter.PresenterStore; 8 | import one.d4d.signsaboteur.utils.Utils; 9 | 10 | import javax.swing.*; 11 | import java.awt.*; 12 | 13 | public class ExtensionTab { 14 | private final Window parent; 15 | private final PresenterStore presenters; 16 | private final UserInterface userInterface; 17 | private final BurpConfig burpConfig; 18 | private final KeysModel keysModel; 19 | private final BurpKeysModelPersistence keysModelPersistence; 20 | private JPanel rootPanel; 21 | private WordlistView wordlistView; 22 | private SettingsView settingsView; 23 | 24 | public ExtensionTab( 25 | Window parent, 26 | PresenterStore presenters, 27 | KeysModel keysModel, 28 | BurpKeysModelPersistence keysModelPersistence, 29 | BurpConfig burpConfig, 30 | UserInterface userInterface) { 31 | this.parent = parent; 32 | this.keysModel = keysModel; 33 | this.keysModelPersistence = keysModelPersistence; 34 | this.burpConfig = burpConfig; 35 | this.userInterface = userInterface; 36 | this.presenters = presenters; 37 | } 38 | 39 | public String getTabCaption() { 40 | return Utils.getResourceString("tool_name"); 41 | } 42 | 43 | public Component getUiComponent() { 44 | return rootPanel; 45 | } 46 | 47 | private void createUIComponents() { 48 | wordlistView = new WordlistView(parent, keysModel, presenters, keysModelPersistence, userInterface); 49 | settingsView = new SettingsView(parent, burpConfig, userInterface); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/crypto/RubyTokenSigner.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.crypto; 2 | 3 | import one.d4d.signsaboteur.itsdangerous.Algorithms; 4 | import one.d4d.signsaboteur.itsdangerous.Derivation; 5 | import one.d4d.signsaboteur.itsdangerous.MessageDerivation; 6 | import one.d4d.signsaboteur.itsdangerous.MessageDigestAlgorithm; 7 | import one.d4d.signsaboteur.keys.SecretKey; 8 | 9 | import javax.crypto.Mac; 10 | import javax.crypto.spec.SecretKeySpec; 11 | import java.util.EnumSet; 12 | 13 | public class RubyTokenSigner extends TokenSigner { 14 | public RubyTokenSigner(SecretKey key) { 15 | super(key); 16 | this.knownDerivations = EnumSet.of(Derivation.RUBY_KEY_GENERATOR, Derivation.RUBY); 17 | } 18 | 19 | public RubyTokenSigner(byte[] sep) { 20 | this(new byte[]{}, sep); 21 | } 22 | 23 | public RubyTokenSigner(byte[] secret_key, byte[] sep) { 24 | this(Algorithms.SHA1, Derivation.RUBY_KEY_GENERATOR, MessageDerivation.NONE, MessageDigestAlgorithm.SHA256, secret_key, new byte[]{}, sep); 25 | } 26 | 27 | public RubyTokenSigner( 28 | Algorithms digestMethod, 29 | Derivation keyDerivation, 30 | MessageDerivation messageDerivation, 31 | MessageDigestAlgorithm digest, 32 | byte[] secret_key, 33 | byte[] salt, 34 | byte[] sep) { 35 | super(digestMethod, keyDerivation, messageDerivation, digest, secret_key, salt, sep); 36 | this.knownDerivations = EnumSet.of(Derivation.RUBY_KEY_GENERATOR, Derivation.RUBY); 37 | } 38 | 39 | @Override 40 | public byte[] get_signature_unsafe(byte[] value) throws Exception { 41 | byte[] key = derive_key(); 42 | SecretKeySpec signingKey = new SecretKeySpec(key, digestMethod.name); 43 | Mac mac = Mac.getInstance(digestMethod.name); 44 | mac.init(signingKey); 45 | return mac.doFinal(value); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/hexcodearea/FontMetricsClearingCodeArea.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.hexcodearea; 2 | 3 | import burp.api.montoya.logging.Logging; 4 | import org.exbin.deltahex.swing.CodeArea; 5 | 6 | import java.io.PrintWriter; 7 | import java.io.StringWriter; 8 | import java.lang.reflect.Field; 9 | 10 | class FontMetricsClearingCodeArea extends CodeArea { 11 | private final Logging logging; 12 | 13 | FontMetricsClearingCodeArea(Logging logging) { 14 | this.logging = logging; 15 | } 16 | 17 | @Override 18 | public void updateUI() { 19 | super.updateUI(); 20 | 21 | if (logging != null) { 22 | // Reset fontMetrics in case Burp's font size has changed 23 | try { 24 | Field paintDataCacheField = FontMetricsClearingCodeArea.class.getSuperclass().getDeclaredField("paintDataCache"); 25 | paintDataCacheField.setAccessible(true); 26 | 27 | Object paintDataCacheRef = paintDataCacheField.get(this); 28 | Field fontMetricsField = paintDataCacheRef.getClass().getDeclaredField("fontMetrics"); 29 | fontMetricsField.setAccessible(true); 30 | 31 | fontMetricsField.set(paintDataCacheRef, null); 32 | } catch (NoSuchFieldException | IllegalAccessException e) { 33 | StringWriter stringWriter = new StringWriter(); 34 | PrintWriter printWriter = new PrintWriter(stringWriter); 35 | e.printStackTrace(printWriter); 36 | logging.logToError(stringWriter.toString()); 37 | } 38 | 39 | // Reset colors in case Burp's theme has changed 40 | CodeArea codeArea = new CodeArea(); 41 | setMainColors(codeArea.getMainColors()); 42 | setAlternateColors(codeArea.getAlternateColors()); 43 | setSelectionColors(codeArea.getSelectionColors()); 44 | setMirrorSelectionColors(codeArea.getMirrorSelectionColors()); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/burp/proxy/ProxyHttpMessageHandler.java: -------------------------------------------------------------------------------- 1 | package burp.proxy; 2 | 3 | import burp.api.montoya.proxy.http.*; 4 | import burp.api.montoya.utilities.ByteUtils; 5 | import burp.config.ProxyConfig; 6 | import burp.config.SignerConfig; 7 | 8 | public class ProxyHttpMessageHandler implements ProxyRequestHandler, ProxyResponseHandler { 9 | private final AnnotationsModifier annotationsModifier; 10 | 11 | public ProxyHttpMessageHandler(ProxyConfig proxyConfig, SignerConfig signerConfig, ByteUtils byteUtils) { 12 | this.annotationsModifier = new AnnotationsModifier(proxyConfig, signerConfig, byteUtils); 13 | } 14 | 15 | @Override 16 | public ProxyRequestReceivedAction handleRequestReceived(InterceptedRequest interceptedRequest) { 17 | annotationsModifier.updateAnnotationsIfApplicable( 18 | interceptedRequest.annotations(), 19 | interceptedRequest.toByteArray(), 20 | null, 21 | interceptedRequest.parameters()); 22 | 23 | return ProxyRequestReceivedAction.continueWith(interceptedRequest); 24 | } 25 | 26 | @Override 27 | public ProxyRequestToBeSentAction handleRequestToBeSent(InterceptedRequest interceptedRequest) { 28 | return ProxyRequestToBeSentAction.continueWith(interceptedRequest); 29 | } 30 | 31 | @Override 32 | public ProxyResponseReceivedAction handleResponseReceived(InterceptedResponse interceptedResponse) { 33 | annotationsModifier.updateAnnotationsIfApplicable( 34 | interceptedResponse.annotations(), 35 | interceptedResponse.toByteArray().subArray(0, interceptedResponse.bodyOffset()), 36 | interceptedResponse.cookies(), 37 | null); 38 | return ProxyResponseReceivedAction.continueWith(interceptedResponse); 39 | } 40 | 41 | @Override 42 | public ProxyResponseToBeSentAction handleResponseToBeSent(InterceptedResponse interceptedResponse) { 43 | return ProxyResponseToBeSentAction.continueWith(interceptedResponse); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/burp/proxy/ProxyWsMessageHandler.java: -------------------------------------------------------------------------------- 1 | package burp.proxy; 2 | 3 | import burp.api.montoya.core.ByteArray; 4 | import burp.api.montoya.proxy.websocket.*; 5 | import burp.api.montoya.utilities.ByteUtils; 6 | import burp.config.ProxyConfig; 7 | import burp.config.SignerConfig; 8 | 9 | public class ProxyWsMessageHandler implements ProxyMessageHandler { 10 | private final AnnotationsModifier annotationsModifier; 11 | 12 | public ProxyWsMessageHandler(ProxyConfig proxyConfig, SignerConfig signerConfig, ByteUtils byteUtils) { 13 | this.annotationsModifier = new AnnotationsModifier(proxyConfig, signerConfig, byteUtils); 14 | } 15 | 16 | @Override 17 | public TextMessageReceivedAction handleTextMessageReceived(InterceptedTextMessage interceptedTextMessage) { 18 | annotationsModifier.updateAnnotationsIfApplicable( 19 | interceptedTextMessage.annotations(), 20 | ByteArray.byteArray(interceptedTextMessage.payload()), 21 | null, 22 | null); 23 | 24 | return TextMessageReceivedAction.continueWith(interceptedTextMessage); 25 | } 26 | 27 | @Override 28 | public TextMessageToBeSentAction handleTextMessageToBeSent(InterceptedTextMessage interceptedTextMessage) { 29 | return TextMessageToBeSentAction.continueWith(interceptedTextMessage); 30 | } 31 | 32 | @Override 33 | public BinaryMessageReceivedAction handleBinaryMessageReceived(InterceptedBinaryMessage interceptedBinaryMessage) { 34 | annotationsModifier.updateAnnotationsIfApplicable( 35 | interceptedBinaryMessage.annotations(), 36 | interceptedBinaryMessage.payload(), 37 | null, 38 | null); 39 | 40 | return BinaryMessageReceivedAction.continueWith(interceptedBinaryMessage); 41 | } 42 | 43 | @Override 44 | public BinaryMessageToBeSentAction handleBinaryMessageToBeSent(InterceptedBinaryMessage interceptedBinaryMessage) { 45 | return BinaryMessageToBeSentAction.continueWith(interceptedBinaryMessage); 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/KeysTableModel.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.forms; 2 | 3 | import one.d4d.signsaboteur.keys.SecretKey; 4 | 5 | import javax.swing.table.AbstractTableModel; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | class KeysTableModel extends AbstractTableModel { 10 | private final List data; 11 | 12 | KeysTableModel(Iterable keys) { 13 | this.data = new ArrayList<>(); 14 | keys.forEach(data::add); 15 | } 16 | 17 | void addKey(SecretKey key) { 18 | int nextRowIndex = data.size(); 19 | data.add(key); 20 | fireTableRowsInserted(nextRowIndex, nextRowIndex); 21 | } 22 | 23 | void deleteRow(int rowIndex) { 24 | data.remove(rowIndex); 25 | fireTableRowsDeleted(rowIndex, rowIndex); 26 | } 27 | 28 | @Override 29 | public int getRowCount() { 30 | return data.size(); 31 | } 32 | 33 | @Override 34 | public int getColumnCount() { 35 | return KeysTableColumns.values().length; 36 | } 37 | 38 | @Override 39 | public Object getValueAt(int rowIndex, int columnIndex) { 40 | if (rowIndex < 0 || rowIndex >= data.size()) { 41 | return null; 42 | } 43 | 44 | SecretKey key = data.get(rowIndex); 45 | KeysTableColumns column = KeysTableColumns.fromIndex(columnIndex); 46 | 47 | return switch (column) { 48 | case ID -> key.getID(); 49 | case SECRET -> key.getSecret(); 50 | case ALGORITHM -> key.getDigestMethod(); 51 | case DERIVATION -> key.getKeyDerivation(); 52 | case MESSAGE_DERIVATION -> key.getMessageDerivation(); 53 | case DIGEST -> key.getMessageDigestAlgorythm(); 54 | }; 55 | } 56 | 57 | @Override 58 | public String getColumnName(int column) { 59 | return KeysTableColumns.labelWithIndex(column); 60 | } 61 | 62 | @Override 63 | public Class getColumnClass(int columnIndex) { 64 | return KeysTableColumns.typeForIndex(columnIndex); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/model/UnknownSignedToken.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.model; 2 | 3 | import com.nimbusds.jwt.JWTClaimsSet; 4 | import one.d4d.signsaboteur.itsdangerous.Algorithms; 5 | import one.d4d.signsaboteur.itsdangerous.Derivation; 6 | import one.d4d.signsaboteur.itsdangerous.MessageDerivation; 7 | import one.d4d.signsaboteur.itsdangerous.MessageDigestAlgorithm; 8 | import one.d4d.signsaboteur.itsdangerous.crypto.Signers; 9 | import one.d4d.signsaboteur.itsdangerous.crypto.TokenSigner; 10 | 11 | public class UnknownSignedToken extends SignedToken { 12 | public byte[] separator; 13 | public boolean isURLEncoded = false; 14 | 15 | public UnknownSignedToken(String message, String signature, byte[] separator){ 16 | this(message, signature, separator, false); 17 | } 18 | 19 | public UnknownSignedToken(String message, String signature, byte[] separator, boolean isURLEncoded) { 20 | super(message); 21 | this.signature = signature; 22 | this.separator = separator; 23 | this.isURLEncoded = isURLEncoded; 24 | this.signer = new TokenSigner( 25 | Algorithms.SHA256, 26 | Derivation.NONE, 27 | MessageDerivation.NONE, 28 | MessageDigestAlgorithm.NONE, 29 | new byte[]{}, 30 | new byte[]{}, 31 | separator); 32 | } 33 | 34 | 35 | @Override 36 | public String serialize() { 37 | return String.format("%s%s%s", message, new String(separator), signature); 38 | } 39 | 40 | @Override 41 | public void resign() throws Exception { 42 | this.signature = new String(signer.get_signature_unsafe(message.getBytes())); 43 | } 44 | 45 | @Override 46 | public void setClaims(JWTClaimsSet claims) { 47 | 48 | } 49 | 50 | public boolean isURLEncoded() { return isURLEncoded;} 51 | public String getSignersName() { 52 | return Signers.UNKNOWN.name(); 53 | } 54 | 55 | public byte[] getSeparator() { 56 | return separator; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/model/ExpressSignedToken.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.model; 2 | 3 | import com.nimbusds.jwt.JWTClaimsSet; 4 | import one.d4d.signsaboteur.itsdangerous.BadSignatureException; 5 | import one.d4d.signsaboteur.itsdangerous.crypto.ExpressTokenSigner; 6 | import one.d4d.signsaboteur.utils.Utils; 7 | 8 | import java.util.Base64; 9 | 10 | public class ExpressSignedToken extends SignedToken { 11 | public byte separator = 0; 12 | public String parameter; 13 | public String payload; 14 | 15 | public ExpressSignedToken(String parameter, String payload, String signature) { 16 | super(String.format("%s=%s", parameter, payload)); 17 | this.parameter = parameter; 18 | this.payload = payload; 19 | this.signature = signature; 20 | this.signer = new ExpressTokenSigner(); 21 | } 22 | 23 | 24 | public void setSigner(ExpressTokenSigner signer) { 25 | this.signer = signer; 26 | } 27 | 28 | 29 | public void unsign() throws BadSignatureException { 30 | signer.fast_unsign(getEncodedMessage().getBytes(), this.signature.getBytes()); 31 | } 32 | 33 | public String getParameter() { 34 | return parameter; 35 | } 36 | 37 | public String getPayload() { 38 | try { 39 | byte[] json = Utils.base64Decompress(this.payload.getBytes()); 40 | return new String(json); 41 | } catch (Exception e) { 42 | return payload; 43 | } 44 | } 45 | 46 | public byte[] getSeparator() { 47 | return new byte[]{separator}; 48 | } 49 | 50 | @Override 51 | public String serialize() { 52 | return String.format("%s", payload); 53 | } 54 | 55 | @Override 56 | public void resign() throws Exception { 57 | byte[] value = message.getBytes(); 58 | this.signature = new String(signer.get_signature_unsafe(value)); 59 | } 60 | 61 | @Override 62 | public void setClaims(JWTClaimsSet claims) { 63 | this.payload = new String(Base64.getUrlEncoder().encode(claims.toString().getBytes())); 64 | this.message = String.format("%s=%s", parameter, payload); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/burp/proxy/AnnotationsModifier.java: -------------------------------------------------------------------------------- 1 | package burp.proxy; 2 | 3 | import burp.api.montoya.core.Annotations; 4 | import burp.api.montoya.core.ByteArray; 5 | import burp.api.montoya.http.message.Cookie; 6 | import burp.api.montoya.http.message.params.ParsedHttpParameter; 7 | import burp.api.montoya.utilities.ByteUtils; 8 | import burp.config.ProxyConfig; 9 | import burp.config.SignerConfig; 10 | import one.d4d.signsaboteur.itsdangerous.model.SignedTokenObjectFinder; 11 | 12 | import java.util.List; 13 | 14 | class AnnotationsModifier { 15 | private final ByteUtils byteUtils; 16 | private final ProxyConfig proxyConfig; 17 | private final SignerConfig signerConfig; 18 | 19 | AnnotationsModifier(ProxyConfig proxyConfig, SignerConfig signerConfig, ByteUtils byteUtils) { 20 | this.byteUtils = byteUtils; 21 | this.proxyConfig = proxyConfig; 22 | this.signerConfig = signerConfig; 23 | } 24 | 25 | void updateAnnotationsIfApplicable(Annotations annotations, ByteArray message, List cookies, List params) { 26 | updateAnnotations(annotations, message, cookies, params); 27 | } 28 | 29 | private void updateAnnotations(Annotations annotations, ByteArray messageString, List cookies, List params) { 30 | Counts counts = countExtractedSignedTokenObjects(messageString, cookies, params); 31 | 32 | if (!counts.isZero()) { 33 | annotations.setHighlightColor(proxyConfig.highlightColor().burpColor); 34 | annotations.setNotes(counts.comment()); 35 | } 36 | } 37 | 38 | private Counts countExtractedSignedTokenObjects(ByteArray messageString, List cookies, List params) { 39 | int count = SignedTokenObjectFinder.extractSignedTokenObjects(signerConfig, messageString, cookies, params).size(); 40 | 41 | return new Counts(proxyConfig, count); 42 | } 43 | 44 | private record Counts(ProxyConfig proxyConfig, int count) { 45 | boolean isZero() { 46 | return count == 0; 47 | } 48 | 49 | String comment() { 50 | return proxyConfig.comment(count); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/dialog/KeyDialog.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.forms.dialog; 2 | 3 | import one.d4d.signsaboteur.keys.Key; 4 | import one.d4d.signsaboteur.presenter.KeyPresenter; 5 | import one.d4d.signsaboteur.presenter.PresenterStore; 6 | import one.d4d.signsaboteur.utils.Utils; 7 | 8 | import javax.swing.*; 9 | import java.awt.*; 10 | import java.awt.event.WindowAdapter; 11 | import java.awt.event.WindowEvent; 12 | 13 | import static javax.swing.JOptionPane.*; 14 | 15 | public abstract class KeyDialog extends JDialog { 16 | 17 | protected PresenterStore presenters; 18 | protected String originalId; 19 | 20 | public KeyDialog(Window parent, String titleResourceId) { 21 | super(parent); 22 | 23 | setModal(true); 24 | setTitle(Utils.getResourceString(titleResourceId)); 25 | 26 | // call onCancel() when cross is clicked 27 | setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); 28 | addWindowListener(new WindowAdapter() { 29 | public void windowClosing(WindowEvent e) { 30 | onCancel(); 31 | } 32 | }); 33 | } 34 | 35 | public abstract Key getKey(); 36 | 37 | abstract void onCancel(); 38 | 39 | public void display() { 40 | pack(); 41 | setLocationRelativeTo(getOwner()); 42 | setVisible(true); 43 | } 44 | 45 | void onOK() { 46 | KeyPresenter keyPresenter = (KeyPresenter) presenters.get(KeyPresenter.class); 47 | Key newKey = getKey(); 48 | 49 | // Handle overwrites if a key already exists with the same kid 50 | if (keyPresenter.keyExists(newKey.getID())) { 51 | // If the new and original key ids match, then this is an update 52 | if (originalId != null && !originalId.equals(newKey.getID())) { 53 | // Otherwise, saving the key could overwrite an existing kid, so show a dialog to confirm 54 | if (showConfirmDialog( 55 | this, 56 | Utils.getResourceString("keys_confirm_overwrite"), 57 | Utils.getResourceString("keys_confirm_overwrite_title"), 58 | OK_CANCEL_OPTION) != OK_OPTION) { 59 | return; 60 | } 61 | } 62 | } 63 | 64 | dispose(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/crypto/ExpressTokenSigner.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.crypto; 2 | 3 | import one.d4d.signsaboteur.itsdangerous.*; 4 | import one.d4d.signsaboteur.keys.SecretKey; 5 | 6 | import javax.crypto.Mac; 7 | import javax.crypto.spec.SecretKeySpec; 8 | import java.util.Base64; 9 | import java.util.EnumSet; 10 | 11 | public class ExpressTokenSigner extends TokenSigner { 12 | public ExpressTokenSigner(SecretKey key) { 13 | super(key); 14 | this.knownDerivations = EnumSet.of(Derivation.NONE); 15 | } 16 | 17 | public ExpressTokenSigner() { 18 | this(Algorithms.SHA1, Derivation.NONE, MessageDerivation.NONE, MessageDigestAlgorithm.NONE, new byte[]{}, new byte[]{}, new byte[]{}); 19 | } 20 | 21 | public ExpressTokenSigner( 22 | Algorithms digestMethod, 23 | Derivation keyDerivation, 24 | MessageDerivation messageDerivation, 25 | MessageDigestAlgorithm digest, 26 | byte[] secret_key, 27 | byte[] salt, 28 | byte[] sep) { 29 | super(digestMethod, keyDerivation, messageDerivation, digest, secret_key, salt, sep); 30 | this.knownDerivations = EnumSet.of(Derivation.NONE); 31 | } 32 | 33 | public ExpressTokenSigner(byte[] secret_key, byte[] sep) { 34 | super(secret_key, sep); 35 | } 36 | 37 | @Override 38 | public byte[] derive_key() throws DerivationException { 39 | return secret_key; 40 | } 41 | 42 | @Override 43 | public byte[] get_signature(byte[] value) { 44 | try { 45 | byte[] key = derive_key(); 46 | SecretKeySpec signingKey = new SecretKeySpec(key, digestMethod.name); 47 | Mac mac = Mac.getInstance(digestMethod.name); 48 | mac.init(signingKey); 49 | byte[] sig = mac.doFinal(value); 50 | return Base64.getUrlEncoder().withoutPadding().encode(sig); 51 | } catch (Exception e) { 52 | return new byte[]{}; 53 | } 54 | } 55 | 56 | @Override 57 | public byte[] get_signature_bytes(byte[] value) { 58 | try { 59 | byte[] key = derive_key(); 60 | SecretKeySpec signingKey = new SecretKeySpec(key, digestMethod.name); 61 | Mac mac = Mac.getInstance(digestMethod.name); 62 | mac.init(signingKey); 63 | return mac.doFinal(value); 64 | } catch (Exception e) { 65 | return new byte[]{}; 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/model/JSONWebSignature.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.model; 2 | 3 | import com.nimbusds.jwt.JWTClaimsSet; 4 | import one.d4d.signsaboteur.itsdangerous.crypto.JSONWebSignatureTokenSigner; 5 | 6 | import java.util.Base64; 7 | 8 | public class JSONWebSignature extends SignedToken { 9 | public String header; 10 | public String payload; 11 | public byte[] separator; 12 | 13 | public JSONWebSignature(String header, String payload, String signature, byte[] separator) { 14 | super(String.format("%s%s%s", header, new String(separator), payload)); 15 | this.header = header; 16 | this.payload = payload; 17 | this.signature = signature; 18 | this.separator = separator; 19 | this.signer = new JSONWebSignatureTokenSigner(separator); 20 | } 21 | 22 | public String getHeader() { 23 | try { 24 | return new String(Base64.getUrlDecoder().decode(header)); 25 | } catch (IllegalArgumentException e) { 26 | return ""; 27 | } 28 | } 29 | 30 | public void setHeader(String header) { 31 | this.header = header; 32 | } 33 | 34 | public String getPayload() { 35 | try { 36 | return new String(Base64.getUrlDecoder().decode(payload)); 37 | } catch (IllegalArgumentException e) { 38 | return ""; 39 | } 40 | } 41 | 42 | public void setPayload(String payload) { 43 | this.payload = payload; 44 | } 45 | 46 | public byte[] getSeparator() { 47 | return separator; 48 | } 49 | 50 | public void setSeparator(byte[] separator) { 51 | this.separator = separator; 52 | } 53 | 54 | @Override 55 | public String serialize() { 56 | return String.format("%s%s%s%s%s", header, new String(separator), payload, new String(separator), signature); 57 | } 58 | 59 | 60 | public void resign() throws Exception { 61 | this.signature = new String(signer.get_signature_unsafe(message.getBytes())); 62 | } 63 | 64 | @Override 65 | public void setClaims(JWTClaimsSet claims) { 66 | this.payload = new String(Base64.getUrlEncoder().withoutPadding().encode(claims.toString().getBytes())); 67 | this.message = String.format("%s%s%s", header, new String(separator), payload); 68 | } 69 | 70 | @Override 71 | public byte[] getSignature() { 72 | try { 73 | return Base64.getUrlDecoder().decode(signature); 74 | } catch (IllegalArgumentException e) { 75 | return new byte[]{}; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/ExpressSignedCookieTest.java: -------------------------------------------------------------------------------- 1 | import burp.api.montoya.http.message.Cookie; 2 | import one.d4d.signsaboteur.itsdangerous.Attack; 3 | import one.d4d.signsaboteur.itsdangerous.BruteForce; 4 | import one.d4d.signsaboteur.itsdangerous.crypto.ExpressTokenSigner; 5 | import one.d4d.signsaboteur.itsdangerous.model.ExpressSignedToken; 6 | import one.d4d.signsaboteur.itsdangerous.model.MutableSignedToken; 7 | import one.d4d.signsaboteur.itsdangerous.model.SignedTokenObjectFinder; 8 | import one.d4d.signsaboteur.keys.SecretKey; 9 | import one.d4d.signsaboteur.utils.TestCookie; 10 | import org.junit.jupiter.api.Assertions; 11 | import org.junit.jupiter.api.Test; 12 | 13 | import java.util.*; 14 | 15 | public class ExpressSignedCookieTest { 16 | @Test 17 | void OauthProxyParserTest() { 18 | byte[] secret = "key1".getBytes(); 19 | byte[] sep = new byte[]{(byte) '.'}; 20 | ExpressTokenSigner s = new ExpressTokenSigner(secret, sep); 21 | 22 | String payload = "eyJwYXNzcG9ydCI6eyJ1c2VyIjoiYWRtaW4ifSwiZmxhc2giOnt9fQ=="; 23 | String signature = "zNzk1rU-uVc2rF2sGxxkt1t_4ewHRQtuE5OTD8b2FQnMZZV-c1A1eUwV6j4_s2hL"; 24 | TestCookie payloadCookie = new TestCookie("session", payload, "evil.com", "/"); 25 | TestCookie signatureCookie = new TestCookie("session.sig", signature, "evil.com", "/"); 26 | List cookies = new ArrayList<>(); 27 | cookies.add(payloadCookie); 28 | cookies.add(signatureCookie); 29 | Assertions.assertDoesNotThrow(() -> { 30 | Optional optionalSignedToken = 31 | SignedTokenObjectFinder.parseSignedTokenWithinCookies(cookies) 32 | .stream() 33 | .map(MutableSignedToken::getModified) 34 | .map(ExpressSignedToken.class::cast) 35 | .findFirst(); 36 | if (optionalSignedToken.isPresent()) { 37 | ExpressSignedToken token = optionalSignedToken.get(); 38 | token.setSigner(s); 39 | token.unsign(); 40 | final Set secrets = new HashSet<>(List.of("key1")); 41 | final Set salts = new HashSet<>(List.of("salt")); 42 | final List knownKeys = new ArrayList<>(); 43 | 44 | BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.Balanced, token); 45 | SecretKey sk = bf.parallel(); 46 | Assertions.assertNotNull(sk); 47 | } else throw new Exception("Missed cookie"); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/TornadoTest.java: -------------------------------------------------------------------------------- 1 | import one.d4d.signsaboteur.itsdangerous.Attack; 2 | import one.d4d.signsaboteur.itsdangerous.BruteForce; 3 | import one.d4d.signsaboteur.itsdangerous.crypto.TornadoTokenSigner; 4 | import one.d4d.signsaboteur.itsdangerous.model.SignedToken; 5 | import one.d4d.signsaboteur.itsdangerous.model.SignedTokenObjectFinder; 6 | import one.d4d.signsaboteur.itsdangerous.model.TornadoSignedToken; 7 | import one.d4d.signsaboteur.keys.SecretKey; 8 | import one.d4d.signsaboteur.utils.Utils; 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Optional; 15 | import java.util.Set; 16 | 17 | public class TornadoTest { 18 | 19 | @Test 20 | void TornadoParserTest() { 21 | byte[] secret = "secret".getBytes(); 22 | byte[] sep = new byte[]{(byte) '|'}; 23 | String value = "2|1:0|10:1686150202|7:session|4:e30=|5e05eeef41715bc4b109138f00a37bbc580ca7e94ba9a21d5ec062b7aebff557"; 24 | Optional optionalToken = SignedTokenObjectFinder.parseTornadoSignedToken("test", value); 25 | if (optionalToken.isPresent()) { 26 | TornadoSignedToken token = (TornadoSignedToken) optionalToken.get(); 27 | TornadoTokenSigner s = new TornadoTokenSigner(secret, sep); 28 | token.setSigner(s); 29 | Assertions.assertDoesNotThrow(() -> { 30 | s.unsign(value.getBytes()); 31 | }); 32 | } else { 33 | Assertions.fail("Token not found."); 34 | } 35 | } 36 | 37 | @Test 38 | void BruteForceMultiThreatTornado() { 39 | String value = "2|1:0|10:1686150202|7:session|4:e30=|5e05eeef41715bc4b109138f00a37bbc580ca7e94ba9a21d5ec062b7aebff557"; 40 | Assertions.assertDoesNotThrow(() -> { 41 | Optional optionalToken = SignedTokenObjectFinder.parseTornadoSignedToken("test", value); 42 | if (optionalToken.isPresent()) { 43 | TornadoTokenSigner s = new TornadoTokenSigner(); 44 | optionalToken.get().setSigner(s); 45 | final Set secrets = Utils.readResourceForClass("/secrets", this.getClass()); 46 | final Set salts = Utils.readResourceForClass("/salts", this.getClass()); 47 | final List knownKeys = new ArrayList<>(); 48 | 49 | BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.FAST, optionalToken.get()); 50 | SecretKey sk = bf.parallel(); 51 | Assertions.assertNotNull(sk); 52 | } 53 | 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/crypto/DangerousTokenSigner.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.crypto; 2 | 3 | import com.google.common.primitives.Bytes; 4 | import one.d4d.signsaboteur.itsdangerous.*; 5 | import one.d4d.signsaboteur.keys.SecretKey; 6 | import one.d4d.signsaboteur.utils.Utils; 7 | 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.EnumSet; 11 | 12 | public class DangerousTokenSigner extends TokenSigner { 13 | 14 | public DangerousTokenSigner(SecretKey key) { 15 | super(key); 16 | this.knownDerivations = EnumSet.of(Derivation.HMAC); 17 | } 18 | 19 | public DangerousTokenSigner(byte[] sep) { 20 | this(Algorithms.SHA1, Derivation.HMAC, MessageDerivation.NONE, MessageDigestAlgorithm.SHA256, new byte[]{}, new byte[]{}, sep); 21 | } 22 | 23 | public DangerousTokenSigner(byte[] secret_key, byte[] salt, byte[] sep) { 24 | this(Algorithms.SHA1, Derivation.HMAC, MessageDerivation.NONE, MessageDigestAlgorithm.SHA256, secret_key, salt, sep); 25 | } 26 | 27 | public DangerousTokenSigner( 28 | Algorithms algorithm, 29 | Derivation keyDerivation, 30 | MessageDerivation messageDerivation, 31 | MessageDigestAlgorithm digest, 32 | byte[] secret_key, 33 | byte[] salt, 34 | byte[] sep) { 35 | super(algorithm, keyDerivation, messageDerivation, digest, secret_key, salt, sep); 36 | this.knownDerivations = EnumSet.of(Derivation.CONCAT, Derivation.DJANGO, Derivation.HMAC, Derivation.NONE); 37 | } 38 | 39 | 40 | public byte[] unsign(byte[] value) throws BadSignatureException { 41 | int i = Collections.lastIndexOfSubList(Bytes.asList(value), Bytes.asList(sep)); 42 | byte[] message = Arrays.copyOfRange(value, 0, i); 43 | byte[] signature = Arrays.copyOfRange(value, i + 1, value.length); 44 | return fast_unsign(message, signature); 45 | } 46 | 47 | public byte[] fast_unsign(byte[] message, byte[] signature) throws BadSignatureException { 48 | byte[] sign = Utils.normalization(signature); 49 | // Signature length in bytes 50 | switch (sign.length) { 51 | case 28 -> digestMethod = Algorithms.SHA224; 52 | case 32 -> digestMethod = Algorithms.SHA256; 53 | case 48 -> digestMethod = Algorithms.SHA384; 54 | case 64 -> digestMethod = Algorithms.SHA512; 55 | default -> digestMethod = Algorithms.SHA1; 56 | } 57 | if (verify_signature_bytes(message, sign)) 58 | return message; 59 | throw new BadSignatureException("Signature didn't match"); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.0.6] - 2024-11-29 4 | 5 | ### Added 6 | 7 | - Load default secrets and salts 8 | - Encrypted Ruby Rails tokens 9 | 10 | ### Changed 11 | 12 | - Ruby on rails brute force logic 13 | 14 | ## [1.0.5] - 2024-05-22 15 | 16 | ### Changed 17 | 18 | - Passive scan is disabled by default now 19 | 20 | ## [1.0.4] - 2024-05-02 21 | 22 | ### Changed 23 | 24 | - Code refactoring 25 | 26 | ## [1.0.3] - 2024-04-25 27 | 28 | ### Added 29 | 30 | - New key derivation: Ruby Key Generator 31 | - JSON editor for Ruby tokens 32 | 33 | ### Changed 34 | 35 | - Ruby on rails signer was updated to support different Key Generators 36 | 37 | ## [1.0.2] - 2024-04-04 38 | 39 | ### Changed 40 | 41 | - Code refactoring 42 | 43 | ## [1.0.1] - 2024-04-03 44 | 45 | ### Changed 46 | 47 | - Default Secret Keys now available at Wordlist View 48 | - The com.nimbusds.jwt SignedJWT parser added to the finder logic. _Note_ RSA and ECDSA not supported by the extension yet 49 | 50 | 51 | ## [1.0.0] - 2024-03-27 52 | 53 | ### Changed 54 | 55 | - Tool name changed to SignSaboteur 56 | - Unknown web signed tokens with empty body excluded from search algorithm to avoid duplicates 57 | - JWT finder separated from Flask/Django implementation 58 | 59 | ## [0.0.8] - 2024-03-20 60 | 61 | ### Added 62 | 63 | - Burp Suite Pro active scan supported now. All identified signed string are scanned for known secrets with Fast algorithm 64 | 65 | ### Changed 66 | 67 | - Regex token search method was removed due to poor performance. New search algorithm was introduced instead. 68 | 69 | ## [0.0.7] - 2024-03-12 70 | 71 | ### Added 72 | 73 | - JWT Tab was added for testing purposes 74 | - Default keys dictionary 75 | 76 | ### Changed 77 | 78 | - Response body was removed from token parser logic due to performance issues 79 | 80 | ## [0.0.6] - 2024-02-06 81 | 82 | ### Added 83 | 84 | - Ruby signed cookie Tab 85 | - Multithreading feature is available for brute force attack 86 | 87 | ### Changed 88 | 89 | - Brute force Deep mode supports Ruby, Ruby5 and Ruby truncated hashing key derivation 90 | 91 | ## [0.0.5] - 2024-01-07 92 | 93 | ### Added 94 | 95 | - Manual secret and salt item creation 96 | 97 | ### Changed 98 | 99 | - Brute force uses all known keys for all attacks mode by default 100 | 101 | ## [0.0.4] - 2024-01-07 102 | 103 | ### Changed 104 | 105 | - Github actions 106 | 107 | ## [0.0.2] - 2024-01-05 108 | 109 | ### Added 110 | 111 | - Unknown signed string tab. 112 | - Enabled signers setting added to the main tab 113 | - _Known keys_ brute force technic added to the Attack mode 114 | 115 | ### Changed 116 | 117 | - Upgrade dependencies: org.json:json -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/crypto/OauthProxyTokenSigner.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.crypto; 2 | 3 | import one.d4d.signsaboteur.itsdangerous.*; 4 | import one.d4d.signsaboteur.keys.SecretKey; 5 | import one.d4d.signsaboteur.utils.Utils; 6 | 7 | import javax.crypto.Mac; 8 | import javax.crypto.spec.SecretKeySpec; 9 | import java.util.Base64; 10 | import java.util.EnumSet; 11 | 12 | public class OauthProxyTokenSigner extends TokenSigner { 13 | public OauthProxyTokenSigner(SecretKey key) { 14 | super(key); 15 | this.knownDerivations = EnumSet.of(Derivation.NONE); 16 | } 17 | 18 | public OauthProxyTokenSigner() { 19 | this(new byte[]{}, new byte[]{'|'}); 20 | } 21 | 22 | public OauthProxyTokenSigner(byte[] secret_key, byte[] sep) { 23 | this(Algorithms.SHA256, Derivation.NONE, MessageDerivation.NONE, MessageDigestAlgorithm.NONE, secret_key, new byte[]{}, sep); 24 | } 25 | 26 | public OauthProxyTokenSigner( 27 | Algorithms digestMethod, 28 | Derivation keyDerivation, 29 | MessageDerivation messageDerivation, 30 | MessageDigestAlgorithm digest, 31 | byte[] secret_key, 32 | byte[] salt, 33 | byte[] sep) { 34 | super(digestMethod, keyDerivation, messageDerivation, digest, secret_key, salt, sep); 35 | this.knownDerivations = EnumSet.of(Derivation.NONE); 36 | } 37 | 38 | @Override 39 | public byte[] derive_key() throws DerivationException { 40 | return secret_key; 41 | } 42 | 43 | @Override 44 | public byte[] get_signature_unsafe(byte[] value) throws Exception { 45 | byte[][] data = Utils.split(value, sep); 46 | byte[] key = derive_key(); 47 | SecretKeySpec signingKey = new SecretKeySpec(key, digestMethod.name); 48 | Mac mac = Mac.getInstance(digestMethod.name); 49 | mac.init(signingKey); 50 | for (byte[] d : data) { 51 | mac.update(d); 52 | } 53 | byte[] sig = mac.doFinal(); 54 | return Base64.getUrlEncoder().withoutPadding().encode(sig); 55 | } 56 | 57 | @Override 58 | public byte[] get_signature_bytes(byte[] value) { 59 | try { 60 | byte[][] data = Utils.split(value, sep); 61 | byte[] key = derive_key(); 62 | SecretKeySpec signingKey = new SecretKeySpec(key, digestMethod.name); 63 | Mac mac = Mac.getInstance(digestMethod.name); 64 | mac.init(signingKey); 65 | for (byte[] d : data) { 66 | mac.update(d); 67 | } 68 | return mac.doFinal(); 69 | } catch (Exception e) { 70 | return new byte[]{}; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/model/OauthProxySignedToken.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.model; 2 | 3 | import com.nimbusds.jwt.JWTClaimsSet; 4 | import one.d4d.signsaboteur.itsdangerous.BadSignatureException; 5 | import one.d4d.signsaboteur.itsdangerous.crypto.OauthProxyTokenSigner; 6 | import one.d4d.signsaboteur.utils.Utils; 7 | 8 | import java.util.Base64; 9 | 10 | public class OauthProxySignedToken extends SignedToken { 11 | public static byte[] separator = {'|'}; 12 | public String parameter; 13 | public String payload; 14 | public String timestamp; 15 | 16 | 17 | public OauthProxySignedToken(String parameter, String payload, String timestamp, String signature) { 18 | super(String.format("%s%s%s%s%s", parameter, new String(separator), payload, new String(separator), timestamp)); 19 | this.payload = payload; 20 | this.timestamp = timestamp; 21 | this.signature = signature; 22 | this.parameter = parameter; 23 | this.signer = new OauthProxyTokenSigner(); 24 | } 25 | 26 | public String getParameter() { 27 | return parameter; 28 | } 29 | 30 | public String getPayload() { 31 | return payload; 32 | } 33 | 34 | public String getTimestamp() { 35 | return Utils.timestampSeconds(timestamp); 36 | } 37 | 38 | public void setSigner(OauthProxyTokenSigner signer) { 39 | this.signer = signer; 40 | } 41 | 42 | public byte[] dumps(String payload) { 43 | try { 44 | return signer.sign(payload.getBytes()); 45 | } catch (Exception e) { 46 | return new byte[]{}; 47 | } 48 | } 49 | 50 | public void unsign() throws BadSignatureException { 51 | signer.fast_unsign(this.message.getBytes(), this.signature.getBytes()); 52 | } 53 | 54 | public byte[] getSeparator() { 55 | return separator; 56 | } 57 | 58 | @Override 59 | public String serialize() { 60 | return String.format("%s%s%s%s%s", payload, new String(separator), timestamp, new String(separator), signature); 61 | } 62 | 63 | @Override 64 | public void resign() throws Exception { 65 | byte[] value = message.getBytes(); 66 | this.signature = new String(signer.get_signature_unsafe(value)); 67 | } 68 | 69 | @Override 70 | public void setClaims(JWTClaimsSet claims) { 71 | this.payload = new String(Base64.getUrlEncoder().encode(claims.toString().getBytes())); 72 | this.message = String.format("%s%s%s%s%s", parameter, new String(separator), payload, new String(separator), timestamp); 73 | } 74 | 75 | public byte[] getSignature() { 76 | return Base64.getUrlDecoder().decode(signature); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/RequestEditorView.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.forms; 2 | 3 | import burp.api.montoya.collaborator.CollaboratorPayloadGenerator; 4 | import burp.api.montoya.core.ByteArray; 5 | import burp.api.montoya.http.HttpService; 6 | import burp.api.montoya.http.message.HttpRequestResponse; 7 | import burp.api.montoya.http.message.requests.HttpRequest; 8 | import burp.api.montoya.logging.Logging; 9 | import burp.api.montoya.ui.UserInterface; 10 | import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpRequestEditor; 11 | import burp.config.SignerConfig; 12 | import one.d4d.signsaboteur.hexcodearea.HexCodeAreaFactory; 13 | import one.d4d.signsaboteur.presenter.PresenterStore; 14 | import one.d4d.signsaboteur.rsta.RstaFactory; 15 | import one.d4d.signsaboteur.utils.ErrorLoggingActionListenerFactory; 16 | 17 | import java.net.URL; 18 | 19 | import static burp.api.montoya.internal.ObjectFactoryLocator.FACTORY; 20 | 21 | public class RequestEditorView extends EditorTab implements ExtensionProvidedHttpRequestEditor { 22 | private volatile HttpService httpService; 23 | 24 | public RequestEditorView( 25 | PresenterStore presenters, 26 | RstaFactory rstaFactory, 27 | Logging logging, 28 | UserInterface userInterface, 29 | CollaboratorPayloadGenerator collaboratorPayloadGenerator, 30 | SignerConfig signerConfig, 31 | boolean editable, 32 | boolean isProVersion) { 33 | super( 34 | presenters, 35 | rstaFactory, 36 | new HexCodeAreaFactory(logging, userInterface), 37 | collaboratorPayloadGenerator, 38 | new ErrorLoggingActionListenerFactory(logging), 39 | signerConfig, 40 | editable, 41 | isProVersion 42 | ); 43 | } 44 | 45 | @Override 46 | public void setRequestResponse(HttpRequestResponse requestResponse) { 47 | HttpRequest httpRequest = requestResponse.request(); 48 | URL targetURL; 49 | try { 50 | URL raw = new URL(httpRequest.url()); 51 | targetURL = new URL(raw.getProtocol(), 52 | raw.getAuthority(), 53 | raw.getPath()); 54 | } catch (Exception e) { 55 | targetURL = null; 56 | } 57 | httpService = httpRequest.httpService(); 58 | presenter.setMessage(httpRequest.toByteArray(), targetURL, null, httpRequest.parameters()); 59 | } 60 | 61 | @Override 62 | public boolean isEnabledFor(HttpRequestResponse requestResponse) { 63 | ByteArray content = requestResponse.request().toByteArray(); 64 | return presenter.isEnabled(content, null, requestResponse.request().parameters()); 65 | } 66 | 67 | @Override 68 | public HttpRequest getRequest() { 69 | return FACTORY.httpRequest(httpService, presenter.getMessage()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/burp/config/BurpKeysModelPersistence.java: -------------------------------------------------------------------------------- 1 | package burp.config; 2 | 3 | import burp.api.montoya.persistence.Preferences; 4 | import com.google.gson.Gson; 5 | import one.d4d.signsaboteur.keys.SecretKey; 6 | import one.d4d.signsaboteur.utils.GsonHelper; 7 | import one.d4d.signsaboteur.utils.Utils; 8 | 9 | import java.io.File; 10 | import java.util.List; 11 | import java.util.Set; 12 | 13 | public class BurpKeysModelPersistence { 14 | static final String BURP_SETTINGS_NAME = "one.d4d.signsaboteur.keys"; 15 | private final Preferences preferences; 16 | 17 | public BurpKeysModelPersistence(Preferences preferences) { 18 | this.preferences = preferences; 19 | } 20 | 21 | public KeysModel loadOrCreateNew() { 22 | String json = preferences.getString(BURP_SETTINGS_NAME); 23 | 24 | if (json == null) { 25 | KeysModel model = new KeysModel(); 26 | model.setSalts(loadDefaultSalts()); 27 | model.setSecrets(loadDefaultSecrets()); 28 | loadDefaultKeys().forEach(model::addKey); 29 | return model; 30 | } 31 | 32 | Gson gson = GsonHelper.customGson; 33 | KeysModel keysModel = gson.fromJson(json, KeysModel.class); 34 | try { 35 | if (keysModel.getSaltsFilePath() != null) { 36 | Set result = Utils.deserializeFile(new File(keysModel.getSaltsFilePath())); 37 | if (result.isEmpty()) { 38 | keysModel.setSalts(loadDefaultSalts()); 39 | } else { 40 | keysModel.setSalts(result); 41 | } 42 | } 43 | } catch (Exception e) { 44 | keysModel.setSalts(loadDefaultSalts()); 45 | } 46 | try { 47 | if (keysModel.getSecretsFilePath() != null) { 48 | Set result = Utils.deserializeFile(new File(keysModel.getSecretsFilePath())); 49 | if (result.isEmpty()) { 50 | keysModel.setSecrets(loadDefaultSecrets()); 51 | } else { 52 | keysModel.setSecrets(result); 53 | } 54 | } 55 | } catch (Exception e) { 56 | keysModel.setSecrets(loadDefaultSecrets()); 57 | } 58 | return keysModel; 59 | } 60 | 61 | public void save(KeysModel model) { 62 | Gson gson = GsonHelper.customGson; 63 | String keysModeJson = gson.toJson(model); 64 | 65 | preferences.setString(BURP_SETTINGS_NAME, keysModeJson); 66 | } 67 | 68 | 69 | private Set loadDefaultSecrets() { 70 | return Utils.readResourceForClass("/secrets", this.getClass()); 71 | } 72 | 73 | private Set loadDefaultSalts() { 74 | return Utils.readResourceForClass("/salts", this.getClass()); 75 | } 76 | private List loadDefaultKeys() { 77 | return Utils.readDefaultSecretKeys("/keys", this.getClass()); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/ExtensionTab.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
55 | -------------------------------------------------------------------------------- /src/test/java/SignUnsignTest.java: -------------------------------------------------------------------------------- 1 | import one.d4d.signsaboteur.itsdangerous.*; 2 | import one.d4d.signsaboteur.itsdangerous.crypto.DangerousTokenSigner; 3 | import one.d4d.signsaboteur.itsdangerous.model.DangerousSignedToken; 4 | import one.d4d.signsaboteur.itsdangerous.model.SignedToken; 5 | import one.d4d.signsaboteur.itsdangerous.model.SignedTokenObjectFinder; 6 | import one.d4d.signsaboteur.keys.SecretKey; 7 | import one.d4d.signsaboteur.utils.Utils; 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.Tag; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import java.util.*; 13 | 14 | public class SignUnsignTest { 15 | @Test 16 | @Tag("slow") 17 | void KeyDerivationTest() { 18 | Assertions.assertDoesNotThrow(() -> { 19 | long start = System.currentTimeMillis(); 20 | for (Algorithms a : Algorithms.values()) { 21 | for (Derivation d : Derivation.values()) { 22 | if (d == Derivation.RUBY_ENCRYPTION) continue; 23 | for (MessageDerivation md : MessageDerivation.values()) { 24 | for (MessageDigestAlgorithm mda : MessageDigestAlgorithm.values()) { 25 | byte[] secret = "secret".getBytes(); 26 | byte[] salt = "cookie-session".getBytes(); 27 | byte[] sep = new byte[]{(byte) '.'}; 28 | String ts = new String(Utils.timestampInFuture()); 29 | DangerousSignedToken newToken = new DangerousSignedToken(sep, "{}", ts, ""); 30 | DangerousTokenSigner s = new DangerousTokenSigner(a, d, md, mda, secret, salt, sep); 31 | newToken.setSigner(s); 32 | String signedToken = newToken.dumps(); 33 | Optional optionalToken = SignedTokenObjectFinder.parseToken(signedToken); 34 | if (optionalToken.isPresent()) { 35 | SignedToken token = optionalToken.get(); 36 | token.setSigner(s); 37 | final Set secrets = new HashSet<>(List.of("secret")); 38 | final Set salts = new HashSet<>(List.of("cookie-session")); 39 | final List knownKeys = new ArrayList<>(); 40 | 41 | BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.Deep, token); 42 | SecretKey sk = bf.parallel(); 43 | System.out.println(sk.toJSONString()); 44 | Assertions.assertNotNull(sk); 45 | } 46 | } 47 | } 48 | } 49 | } 50 | long end = System.currentTimeMillis() - start; 51 | System.out.printf("Task finished in %.0f seconds", end / 1000.0); 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/burp/scanner/BrokenSecretKeyIssue.java: -------------------------------------------------------------------------------- 1 | package burp.scanner; 2 | 3 | import burp.api.montoya.collaborator.Interaction; 4 | import burp.api.montoya.http.HttpService; 5 | import burp.api.montoya.http.message.HttpRequestResponse; 6 | import burp.api.montoya.scanner.audit.issues.AuditIssue; 7 | import burp.api.montoya.scanner.audit.issues.AuditIssueConfidence; 8 | import burp.api.montoya.scanner.audit.issues.AuditIssueDefinition; 9 | import burp.api.montoya.scanner.audit.issues.AuditIssueSeverity; 10 | import one.d4d.signsaboteur.keys.SecretKey; 11 | import one.d4d.signsaboteur.utils.Utils; 12 | 13 | import java.util.Collections; 14 | import java.util.List; 15 | 16 | public class BrokenSecretKeyIssue implements AuditIssue { 17 | SecretKey issueSecretKey; 18 | HttpRequestResponse issueRequestResponse; 19 | AuditIssueConfidence issueConfidence; 20 | AuditIssueSeverity issueSeverity; 21 | 22 | 23 | public BrokenSecretKeyIssue(SecretKey issueSecretKey, 24 | HttpRequestResponse issueRequestResponse, 25 | AuditIssueConfidence issueConfidence, 26 | AuditIssueSeverity issueSeverity) { 27 | this.issueSecretKey = issueSecretKey; 28 | this.issueRequestResponse = issueRequestResponse; 29 | this.issueConfidence = issueConfidence; 30 | this.issueSeverity = issueSeverity; 31 | } 32 | 33 | @Override 34 | public String name() { 35 | return Utils.getResourceString("audit_issue_name"); 36 | } 37 | 38 | @Override 39 | public String detail() { 40 | return String.format( 41 | Utils.getResourceString("audit_issue_details"), 42 | this.issueSecretKey.toJSONString()); 43 | } 44 | 45 | @Override 46 | public String remediation() { 47 | return Utils.getResourceString("audit_issue_remediation"); 48 | } 49 | 50 | @Override 51 | public HttpService httpService() { 52 | return this.issueRequestResponse.httpService(); 53 | } 54 | 55 | @Override 56 | public String baseUrl() { 57 | return this.issueRequestResponse.url(); 58 | } 59 | 60 | @Override 61 | public AuditIssueSeverity severity() { 62 | return this.issueSeverity; 63 | } 64 | 65 | @Override 66 | public AuditIssueConfidence confidence() { 67 | return this.issueConfidence; 68 | } 69 | 70 | @Override 71 | public List requestResponses() { 72 | return Collections.singletonList(this.issueRequestResponse); 73 | } 74 | 75 | @Override 76 | public List collaboratorInteractions() { 77 | return null; 78 | } 79 | 80 | @Override 81 | public AuditIssueDefinition definition() { 82 | return AuditIssueDefinition.auditIssueDefinition( 83 | Utils.getResourceString("audit_issue_name"), 84 | Utils.getResourceString("audit_issue_background"), 85 | Utils.getResourceString("audit_issue_remediation"),AuditIssueSeverity.HIGH); 86 | 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/ClaimsTest.java: -------------------------------------------------------------------------------- 1 | import com.nimbusds.jwt.JWTClaimsSet; 2 | import one.d4d.signsaboteur.itsdangerous.Algorithms; 3 | import one.d4d.signsaboteur.itsdangerous.Derivation; 4 | import one.d4d.signsaboteur.itsdangerous.MessageDerivation; 5 | import one.d4d.signsaboteur.itsdangerous.MessageDigestAlgorithm; 6 | import one.d4d.signsaboteur.keys.SecretKey; 7 | import one.d4d.signsaboteur.utils.ClaimsUtils; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.net.MalformedURLException; 11 | import java.net.URL; 12 | import java.text.ParseException; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | public class ClaimsTest { 17 | @Test 18 | void UserClaimTest() { 19 | try { 20 | URL target = new URL("https://d4d.one/"); 21 | ClaimsUtils.generateUserClaim(target); 22 | } catch (MalformedURLException e) { 23 | throw new RuntimeException(e); 24 | } 25 | } 26 | @Test 27 | void BasicUserPayloadTest() { 28 | try { 29 | URL target = new URL("https://d4d.one/"); 30 | ClaimsUtils.generateUserPayload(target); 31 | } catch (MalformedURLException e) { 32 | throw new RuntimeException(e); 33 | } 34 | } 35 | @Test 36 | void FlaskUserPayloadTest() { 37 | try { 38 | URL target = new URL("https://d4d.one/"); 39 | ClaimsUtils.generateFlaskUserPayload(target, 1337); 40 | } catch (MalformedURLException e) { 41 | throw new RuntimeException(e); 42 | } 43 | } 44 | @Test 45 | void ExpressUserPayloadTest() { 46 | ClaimsUtils.generateExpressUserPayload(); 47 | } 48 | 49 | @Test 50 | void ClaimsJoinerTest() { 51 | try { 52 | URL target = new URL("https://d4d.one/"); 53 | List args = new ArrayList<>(); 54 | args.add(ClaimsUtils.generateUserClaim(target)); 55 | args.add(ClaimsUtils.generateAuthenticatedClaims()); 56 | ClaimsUtils.concatClaims(args); 57 | } catch (MalformedURLException|ParseException e) { 58 | throw new RuntimeException(e); 59 | } 60 | } 61 | @Test 62 | void AccountUserPayloadTest() { 63 | try { 64 | URL target = new URL("https://d4d.one/"); 65 | ClaimsUtils.generateAccountUserPayload(target); 66 | } catch (MalformedURLException e) { 67 | throw new RuntimeException(e); 68 | } 69 | } 70 | @Test 71 | void UserAccessTokenPayloadTest() { 72 | try { 73 | SecretKey key = new SecretKey( 74 | "1", 75 | "secret", 76 | "", 77 | ".", 78 | Algorithms.SHA256, 79 | Derivation.NONE, 80 | MessageDerivation.NONE, 81 | MessageDigestAlgorithm.NONE); 82 | URL target = new URL("https://d4d.one/"); 83 | ClaimsUtils.generateUserAccessTokenPayload(target, key); 84 | } catch (MalformedURLException e) { 85 | throw new RuntimeException(e); 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/ResponseEditorView.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.forms; 2 | 3 | import burp.api.montoya.collaborator.CollaboratorPayloadGenerator; 4 | import burp.api.montoya.core.ByteArray; 5 | import burp.api.montoya.http.message.HttpRequestResponse; 6 | import burp.api.montoya.http.message.MimeType; 7 | import burp.api.montoya.http.message.responses.HttpResponse; 8 | import burp.api.montoya.logging.Logging; 9 | import burp.api.montoya.ui.UserInterface; 10 | import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpResponseEditor; 11 | import burp.config.SignerConfig; 12 | import one.d4d.signsaboteur.hexcodearea.HexCodeAreaFactory; 13 | import one.d4d.signsaboteur.presenter.PresenterStore; 14 | import one.d4d.signsaboteur.rsta.RstaFactory; 15 | import one.d4d.signsaboteur.utils.ErrorLoggingActionListenerFactory; 16 | 17 | import java.net.URL; 18 | import java.util.Set; 19 | 20 | import static burp.api.montoya.internal.ObjectFactoryLocator.FACTORY; 21 | 22 | public class ResponseEditorView extends EditorTab implements ExtensionProvidedHttpResponseEditor { 23 | 24 | public ResponseEditorView( 25 | PresenterStore presenters, 26 | RstaFactory rstaFactory, 27 | Logging logging, 28 | UserInterface userInterface, 29 | CollaboratorPayloadGenerator collaboratorPayloadGenerator, 30 | SignerConfig signerConfig, 31 | boolean editable, 32 | boolean isProVersion) { 33 | super( 34 | presenters, 35 | rstaFactory, 36 | new HexCodeAreaFactory(logging, userInterface), 37 | collaboratorPayloadGenerator, 38 | new ErrorLoggingActionListenerFactory(logging), 39 | signerConfig, 40 | editable, 41 | isProVersion 42 | ); 43 | } 44 | 45 | @Override 46 | public void setRequestResponse(HttpRequestResponse requestResponse) { 47 | HttpResponse httpResponse = requestResponse.response(); 48 | URL targetURL; 49 | try { 50 | URL raw = new URL(requestResponse.url()); 51 | targetURL = new URL(raw.getProtocol(), 52 | raw.getAuthority(), 53 | raw.getPath()); 54 | } catch (Exception e) { 55 | targetURL = null; 56 | } 57 | presenter.setMessage(httpResponse.toByteArray(), targetURL, httpResponse.cookies(), null); 58 | } 59 | 60 | @Override 61 | public boolean isEnabledFor(HttpRequestResponse requestResponse) { 62 | Set disabled = Set.of( 63 | MimeType.IMAGE_UNKNOWN, 64 | MimeType.IMAGE_BMP, 65 | MimeType.IMAGE_GIF, 66 | MimeType.IMAGE_JPEG, 67 | MimeType.IMAGE_PNG, 68 | MimeType.IMAGE_SVG_XML, 69 | MimeType.IMAGE_TIFF, 70 | MimeType.SOUND, 71 | MimeType.VIDEO 72 | ); 73 | MimeType type = requestResponse.response().statedMimeType(); 74 | if (disabled.contains(type)) { 75 | return false; 76 | } 77 | return presenter.isEnabled(requestResponse.response().toByteArray(), requestResponse.response().cookies(), null); 78 | } 79 | 80 | @Override 81 | public HttpResponse getResponse() { 82 | return FACTORY.httpResponse(presenter.getMessage()); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/resources/keys: -------------------------------------------------------------------------------- 1 | {"keyId":"Flask","secret":"secret","salt":"salt","separator":".","digestMethod":"1","keyDerivation":"5","messageDerivation":"0","messageDigestAlgorythm":"4"} 2 | {"keyId":"Django","secret":"secret","salt":"django.contrib.sessions.backends.signed_cookies","separator":":","digestMethod":"1","keyDerivation":"4","messageDerivation":"0","messageDigestAlgorythm":"2"} 3 | {"keyId":"Express1","secret":"key1","salt":"salt","separator":".","digestMethod":"4","keyDerivation":"2","messageDerivation":"0","messageDigestAlgorythm":"1"} 4 | {"keyId":"Express2","secret":"key2","salt":"salt","separator":".","digestMethod":"4","keyDerivation":"2","messageDerivation":"0","messageDigestAlgorythm":"1"} 5 | {"keyId":"ExpressMagic","secret":"magic","salt":"salt","separator":".","digestMethod":"4","keyDerivation":"2","messageDerivation":"0","messageDigestAlgorythm":"1"} 6 | {"keyId":"OAuth2","secret":"j76h5PEMx3FIGr3caArJ5g\u003d\u003d","salt":"salt","separator":"|","digestMethod":"3","keyDerivation":"1","messageDerivation":"0","messageDigestAlgorythm":"7"} 7 | {"keyId":"Tornado","secret":"secret","salt":"","separator":"|","digestMethod":"3","keyDerivation":"6","messageDerivation":"0","messageDigestAlgorythm":"7"} 8 | {"keyId":"JSONWebSignature","secret":"secret","salt":"","separator":".","digestMethod":"3","keyDerivation":"6","messageDerivation":"0","messageDigestAlgorythm":"7"} 9 | {"keyId":"Ruby","secret":"aeb977de013ade650b97e0aa5246813591104017871a7753fe186e9634c9129b367306606878985c759ca4fddd17d955207011bb855ef01ed414398b4ac8317b","salt":"signed encrypted cookie","separator":"--","digestMethod":"1","keyDerivation":"7","messageDerivation":"0","messageDigestAlgorythm":"7"} 10 | {"keyId":"Ruby5","secret":"aeb977de013ade650b97e0aa5246813591104017871a7753fe186e9634c9129b367306606878985c759ca4fddd17d955207011bb855ef01ed414398b4ac8317b","salt":"signed encrypted cookie","separator":"--","digestMethod":"1","keyDerivation":"8","messageDerivation":"0","messageDigestAlgorythm":"7"} 11 | {"keyId":"RubyTruncated","secret":"aeb977de013ade650b97e0aa5246813591104017871a7753fe186e9634c9129b367306606878985c759ca4fddd17d955207011bb855ef01ed414398b4ac8317b","salt":"signed encrypted cookie","separator":"--","digestMethod":"1","keyDerivation":"9","messageDerivation":"0","messageDigestAlgorythm":"7"} 12 | {"keyId":"Superset","secret":"thisISaSECRET_1234","salt":"cookie-session","separator":".","digestMethod":"1","keyDerivation":"5","messageDerivation":"0","messageDigestAlgorythm":"4"} 13 | {"keyId":"Superset2","secret":"CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET","salt":"cookie-session","separator":".","digestMethod":"1","keyDerivation":"5","messageDerivation":"0","messageDigestAlgorythm":"4"} 14 | {"keyId":"Superset3","secret":"YOUR_OWN_RANDOM_GENERATED_SECRET_KEY","salt":"cookie-session","separator":".","digestMethod":"1","keyDerivation":"5","messageDerivation":"0","messageDigestAlgorythm":"4"} 15 | {"keyId":"Superset4","secret":"TEST_NON_DEV_SECRET","salt":"cookie-session","separator":".","digestMethod":"1","keyDerivation":"5","messageDerivation":"0","messageDigestAlgorythm":"4"} 16 | {"keyId":"Superset5","secret":"\u0002\u0001thisismyscretkey\u0001\u0002\\e\\y\\y\\h","salt":"cookie-session","separator":".","digestMethod":"1","keyDerivation":"5","messageDerivation":"0","messageDigestAlgorythm":"4"} 17 | {"keyId":"Redash","secret":"c292a0a3aa32397cdb050e233733900f","salt":"cookie-session","separator":".","digestMethod":"1","keyDerivation":"5","messageDerivation":"0","messageDigestAlgorythm":"4"} 18 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/keys/SecretKey.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.keys; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import com.google.gson.annotations.SerializedName; 5 | import one.d4d.signsaboteur.itsdangerous.Algorithms; 6 | import one.d4d.signsaboteur.itsdangerous.Derivation; 7 | import one.d4d.signsaboteur.itsdangerous.MessageDerivation; 8 | import one.d4d.signsaboteur.itsdangerous.MessageDigestAlgorithm; 9 | 10 | public class SecretKey implements Key { 11 | @Expose 12 | @SerializedName("keyId") 13 | private final String keyId; 14 | @Expose 15 | @SerializedName("secret") 16 | private final String secret; 17 | @Expose 18 | @SerializedName("salt") 19 | private final String salt; 20 | @Expose 21 | @SerializedName("separator") 22 | private final String separator; 23 | @Expose 24 | @SerializedName("digestMethod") 25 | private final Algorithms digestMethod; 26 | @Expose 27 | @SerializedName("keyDerivation") 28 | private final Derivation keyDerivation; 29 | @Expose 30 | @SerializedName("messageDerivation") 31 | private final MessageDerivation messageDerivation; 32 | @Expose 33 | @SerializedName("messageDigestAlgorythm") 34 | private final MessageDigestAlgorithm messageDigestAlgorithm; 35 | 36 | public SecretKey( 37 | String keyId, 38 | String secret, 39 | String salt, 40 | String separator, 41 | Algorithms digestMethod, 42 | Derivation keyDerivation, 43 | MessageDerivation messageDerivation, 44 | MessageDigestAlgorithm messageDigestAlgorithm) { 45 | this.keyId = keyId; 46 | this.secret = secret; 47 | this.salt = salt; 48 | this.separator = separator; 49 | this.digestMethod = digestMethod; 50 | this.keyDerivation = keyDerivation; 51 | this.messageDerivation = messageDerivation; 52 | this.messageDigestAlgorithm = messageDigestAlgorithm; 53 | } 54 | 55 | public String getSecret() { 56 | return secret; 57 | } 58 | 59 | public String getSalt() { 60 | return salt; 61 | } 62 | 63 | public String getSeparator() { 64 | return separator; 65 | } 66 | 67 | public Algorithms getDigestMethod() { 68 | return digestMethod; 69 | } 70 | 71 | public Derivation getKeyDerivation() { 72 | return keyDerivation; 73 | } 74 | 75 | public MessageDerivation getMessageDerivation() { 76 | return messageDerivation; 77 | } 78 | 79 | public MessageDigestAlgorithm getMessageDigestAlgorythm() { 80 | return messageDigestAlgorithm; 81 | } 82 | 83 | @Override 84 | public String getID() { 85 | return keyId; 86 | } 87 | 88 | @Override 89 | public String toString() { 90 | return keyId; 91 | } 92 | 93 | public String toJSONString() { 94 | return "{" + 95 | "keyId='" + keyId + '\'' + 96 | ", secret='" + secret + '\'' + 97 | ", salt='" + salt + '\'' + 98 | ", separator='" + separator + '\'' + 99 | ", digestMethod=" + digestMethod + 100 | ", keyDerivation=" + keyDerivation + 101 | ", messageDigestAlgorythm=" + messageDigestAlgorithm + 102 | '}'; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/test/java/DjangoTest.java: -------------------------------------------------------------------------------- 1 | import one.d4d.signsaboteur.itsdangerous.Attack; 2 | import one.d4d.signsaboteur.itsdangerous.BruteForce; 3 | import one.d4d.signsaboteur.itsdangerous.crypto.DjangoTokenSigner; 4 | import one.d4d.signsaboteur.itsdangerous.model.DangerousSignedToken; 5 | import one.d4d.signsaboteur.itsdangerous.model.DjangoSignedToken; 6 | import one.d4d.signsaboteur.itsdangerous.model.SignedToken; 7 | import one.d4d.signsaboteur.itsdangerous.model.SignedTokenObjectFinder; 8 | import one.d4d.signsaboteur.keys.SecretKey; 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import java.util.*; 13 | 14 | public class DjangoTest { 15 | 16 | @Test 17 | void DjangoBruteForceTest() { 18 | final Set signingSecrets = new HashSet<>(List.of("secret")); 19 | final Set signingSalts = new HashSet<>(List.of("django.contrib.sessions.backends.signed_cookies")); 20 | List knownKeys = new ArrayList<>(); 21 | Attack mode = Attack.Deep; 22 | String value = "gAWVMwAAAAAAAAB9lIwKdGVzdGNvb2tpZZSMBXBvc2l4lIwGc3lzdGVtlJOUjAhzbGVlcCAzMJSFlFKUcy4:1rBDnz:6RroyItcbm4P82lx2kEAuV2ykxs"; 23 | Optional optionalToken = SignedTokenObjectFinder.parseToken(value); 24 | if (optionalToken.isPresent()) { 25 | DangerousSignedToken token = (DangerousSignedToken) optionalToken.get(); 26 | BruteForce bf = new BruteForce(signingSecrets, signingSalts, knownKeys, mode, token); 27 | SecretKey k = bf.parallel(); 28 | Assertions.assertNotNull(k); 29 | } else { 30 | Assertions.fail("Token not found."); 31 | } 32 | 33 | } 34 | 35 | @Test 36 | void DjangoParserTest() { 37 | byte[] secret = "secret".getBytes(); 38 | byte[] salt = "django.contrib.sessions.backends.signed_cookies".getBytes(); 39 | byte[] sep = new byte[]{(byte) ':'}; 40 | String value = ".eJxTKkstqlSgIpGTn5eukJyfV5KaV6IEAJM1I3A:1rBGj6:xBAP3gQxgLfArMsY2j3SWmpxlqY"; 41 | Optional optionalToken = SignedTokenObjectFinder.parseToken(value); 42 | if (optionalToken.isPresent()) { 43 | DjangoSignedToken token = (DjangoSignedToken) optionalToken.get(); 44 | DjangoTokenSigner s = new DjangoTokenSigner(secret, salt, sep); 45 | token.setSigner(s); 46 | Assertions.assertDoesNotThrow(() -> { 47 | s.unsign(value.getBytes()); 48 | }); 49 | } else { 50 | Assertions.fail("Token not found."); 51 | } 52 | } 53 | 54 | @Test 55 | void DjangoSignerTest() { 56 | byte[] secret = "secret".getBytes(); 57 | byte[] salt = "django.contrib.sessions.backends.signed_cookies".getBytes(); 58 | byte[] sep = new byte[]{(byte) ':'}; 59 | String value = "gAWVMwAAAAAAAAB9lIwKdGVzdGNvb2tpZZSMBXBvc2l4lIwGc3lzdGVtlJOUjAhzbGVlcCAzMJSFlFKUcy4:1rBDnz:6RroyItcbm4P82lx2kEAuV2ykxs"; 60 | Optional optionalToken = SignedTokenObjectFinder.parseToken(value); 61 | if (optionalToken.isPresent()) { 62 | DjangoSignedToken token = (DjangoSignedToken) optionalToken.get(); 63 | DjangoTokenSigner s = new DjangoTokenSigner(secret, salt, sep); 64 | token.setSigner(s); 65 | Assertions.assertDoesNotThrow(() -> { 66 | s.unsign(value.getBytes()); 67 | }); 68 | } else { 69 | Assertions.fail("Token not found."); 70 | } 71 | 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/model/TornadoSignedToken.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.model; 2 | 3 | import com.nimbusds.jwt.JWTClaimsSet; 4 | import one.d4d.signsaboteur.itsdangerous.BadSignatureException; 5 | import one.d4d.signsaboteur.itsdangerous.crypto.ExpressTokenSigner; 6 | import one.d4d.signsaboteur.itsdangerous.crypto.TornadoTokenSigner; 7 | import one.d4d.signsaboteur.utils.Utils; 8 | 9 | import java.util.Base64; 10 | import java.util.HexFormat; 11 | 12 | public class TornadoSignedToken extends SignedToken { 13 | public static String formatVersion = "2"; 14 | public static String keyVersion = "0"; 15 | public byte separator = (byte) '|'; 16 | public String timestamp; 17 | public String name; 18 | public String value; 19 | 20 | public TornadoSignedToken(String timestamp, String name, String value, String signature) { 21 | super(String.join( 22 | "|", 23 | formatVersion, 24 | formatField(keyVersion), 25 | formatField(timestamp), 26 | formatField(name), 27 | formatField(value), 28 | "")); 29 | this.timestamp = timestamp; 30 | this.name = name; 31 | this.value = value; 32 | this.signature = signature; 33 | this.signer = new TornadoTokenSigner(); 34 | } 35 | 36 | private static String formatField(String field) { 37 | return String.format("%d:%s", field.length(), field); 38 | } 39 | 40 | public void setSigner(ExpressTokenSigner signer) { 41 | this.signer = signer; 42 | } 43 | 44 | 45 | public void unsign() throws BadSignatureException { 46 | signer.fast_unsign(getEncodedMessage().getBytes(), this.signature.getBytes()); 47 | } 48 | 49 | public String getName() { 50 | return name; 51 | } 52 | 53 | public String getValue() { 54 | try { 55 | byte[] json = Utils.base64Decompress(this.value.getBytes()); 56 | return new String(json); 57 | } catch (Exception e) { 58 | return value; 59 | } 60 | } 61 | 62 | public String getTimestamp() { 63 | return Utils.timestampSeconds(timestamp); 64 | } 65 | 66 | public byte[] getSeparator() { 67 | return new byte[]{separator}; 68 | } 69 | 70 | @Override 71 | public String serialize() { 72 | return String.join( 73 | "|", 74 | formatVersion, 75 | formatField(keyVersion), 76 | formatField(timestamp), 77 | formatField(name), 78 | formatField(value), 79 | signature); 80 | } 81 | 82 | @Override 83 | public void resign() throws Exception { 84 | byte[] value = message.getBytes(); 85 | HexFormat hexFormat = HexFormat.of(); 86 | this.signature = hexFormat.formatHex(signer.get_signature_unsafe(value)); 87 | } 88 | 89 | @Override 90 | public void setClaims(JWTClaimsSet claims) { 91 | this.value = new String(Base64.getUrlEncoder().encode(claims.toString().getBytes())); 92 | this.message = String.join( 93 | "|", 94 | formatVersion, 95 | formatField(keyVersion), 96 | formatField(timestamp), 97 | formatField(name), 98 | formatField(value), 99 | ""); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/model/RubyEncryptedToken.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.model; 2 | 3 | import com.nimbusds.jwt.JWTClaimsSet; 4 | import one.d4d.signsaboteur.itsdangerous.*; 5 | import one.d4d.signsaboteur.itsdangerous.crypto.RubyEncryptionTokenSigner; 6 | import one.d4d.signsaboteur.itsdangerous.crypto.RubyTokenSigner; 7 | import one.d4d.signsaboteur.itsdangerous.crypto.Signers; 8 | import one.d4d.signsaboteur.itsdangerous.crypto.TokenSigner; 9 | 10 | import java.net.URLEncoder; 11 | import java.nio.charset.StandardCharsets; 12 | import java.util.HexFormat; 13 | 14 | public class RubyEncryptedToken extends SignedToken { 15 | public byte[] separator; 16 | public boolean isURLEncoded; 17 | 18 | public RubyEncryptedToken(String message, String signature) { 19 | this(message, signature, "--".getBytes(), false); 20 | } 21 | public RubyEncryptedToken(String message, String signature, boolean isURLEncoded) { 22 | this(message, signature, "--".getBytes(), isURLEncoded); 23 | } 24 | 25 | public RubyEncryptedToken(String message, String signature, byte[] separator, boolean isURLEncoded) { 26 | super(message); 27 | this.signature = signature; 28 | this.separator = separator; 29 | this.isURLEncoded = isURLEncoded; 30 | this.signer = new RubyEncryptionTokenSigner( 31 | Algorithms.SHA256, 32 | Derivation.RUBY_ENCRYPTION, 33 | MessageDerivation.NONE, 34 | MessageDigestAlgorithm.NONE, 35 | new byte[]{}, 36 | new byte[]{}, 37 | separator); 38 | } 39 | 40 | @Override 41 | public String serialize() { 42 | String raw = String.format("%s%s%s", message, new String(separator), signature); 43 | return isURLEncoded ? URLEncoder.encode(raw, StandardCharsets.UTF_8) : raw; 44 | } 45 | 46 | @Override 47 | public void resign() throws Exception { 48 | try { 49 | byte[] decrypted = this.signer.fast_unsign(message.getBytes(StandardCharsets.UTF_8), signature.getBytes(StandardCharsets.UTF_8)); 50 | String encrypted = new String(this.signer.sign(decrypted)); 51 | this.message = encrypted.substring(0, encrypted.lastIndexOf("--")); 52 | this.signature = encrypted.substring(encrypted.lastIndexOf("--") + 2); 53 | }catch (BadSignatureException ignored) { 54 | } 55 | } 56 | 57 | @Override 58 | public void setClaims(JWTClaimsSet claims) { 59 | 60 | } 61 | 62 | public String getCypherText() { 63 | try { 64 | return new String(this.signer.fast_unsign(message.getBytes(StandardCharsets.UTF_8), signature.getBytes(StandardCharsets.UTF_8))); 65 | } catch (BadSignatureException e) { 66 | return "Error"; 67 | } 68 | } 69 | 70 | public void setCypherText(String text) { 71 | String encrypted = new String(this.signer.sign(text.getBytes(StandardCharsets.UTF_8))); 72 | this.message = encrypted.substring(0, encrypted.lastIndexOf("--")); 73 | this.signature = encrypted.substring(encrypted.lastIndexOf("--") + 2); 74 | } 75 | 76 | public boolean isURLEncoded() { return isURLEncoded;} 77 | 78 | public String getSignersName() { 79 | return Signers.ENCRYPTION.name(); 80 | } 81 | 82 | public byte[] getSeparator() { 83 | return separator; 84 | } 85 | 86 | public void setSeparator(byte[] separator) { 87 | this.separator = separator; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/presenter/EditorModel.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.presenter; 2 | 3 | import burp.api.montoya.core.ByteArray; 4 | import burp.api.montoya.http.message.Cookie; 5 | import burp.api.montoya.http.message.params.ParsedHttpParameter; 6 | import burp.config.SignerConfig; 7 | import one.d4d.signsaboteur.itsdangerous.model.MutableSignedToken; 8 | import one.d4d.signsaboteur.itsdangerous.model.SignedTokenObjectFinder; 9 | import org.apache.commons.lang3.StringUtils; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.concurrent.atomic.AtomicInteger; 14 | 15 | class EditorModel { 16 | private static final String SERIALIZED_OBJECT_FORMAT_STRING = "%d - %s"; 17 | private final SignerConfig signerConfig; 18 | private final List mutableSerializedObjects = new ArrayList<>(); 19 | private final Object lock = new Object(); 20 | 21 | private String message; 22 | 23 | public EditorModel(SignerConfig signerConfig) { 24 | this.signerConfig = signerConfig; 25 | } 26 | 27 | void setMessage(ByteArray content, List cookies, List params) { 28 | synchronized (lock) { 29 | message = content.toString(); 30 | mutableSerializedObjects.clear(); 31 | mutableSerializedObjects.addAll(SignedTokenObjectFinder.extractSignedTokenObjects(signerConfig, content, cookies, params)); 32 | } 33 | } 34 | 35 | List getSerializedObjectStrings() { 36 | synchronized (lock) { 37 | AtomicInteger counter = new AtomicInteger(); 38 | 39 | return mutableSerializedObjects.stream() 40 | .map(MutableSignedToken::getOriginal) 41 | .map(serializedToken -> SERIALIZED_OBJECT_FORMAT_STRING.formatted(counter.incrementAndGet(), serializedToken)) 42 | .toList(); 43 | } 44 | } 45 | 46 | String getMessage() { 47 | synchronized (lock) { 48 | // Create two lists, one containing the original, the other containing the modified version at the same index 49 | List searchList = new ArrayList<>(); 50 | List replacementList = new ArrayList<>(); 51 | 52 | // Add a replacement pair to the lists if the JOSEObjectPair has changed 53 | for (MutableSignedToken mutableSignedTokenObject : mutableSerializedObjects) { 54 | if (mutableSignedTokenObject.changed()) { 55 | searchList.add(mutableSignedTokenObject.getOriginal()); 56 | replacementList.add(mutableSignedTokenObject.getModified().serialize()); 57 | } 58 | } 59 | 60 | // Convert the lists to arrays 61 | String[] search = new String[searchList.size()]; 62 | searchList.toArray(search); 63 | String[] replacement = new String[replacementList.size()]; 64 | replacementList.toArray(replacement); 65 | 66 | // Use the Apache Commons StringUtils library to do in-place replacement 67 | return StringUtils.replaceEach(message, search, replacement); 68 | } 69 | } 70 | 71 | boolean isModified() { 72 | synchronized (lock) { 73 | return mutableSerializedObjects.stream().anyMatch(MutableSignedToken::changed); 74 | } 75 | } 76 | 77 | MutableSignedToken getSignedTokenObject(int index) { 78 | synchronized (lock) { 79 | return mutableSerializedObjects.get(index); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/dialog/SignDialog.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.forms.dialog; 2 | 3 | import one.d4d.signsaboteur.itsdangerous.crypto.*; 4 | import one.d4d.signsaboteur.itsdangerous.model.*; 5 | import one.d4d.signsaboteur.keys.SecretKey; 6 | import one.d4d.signsaboteur.utils.ErrorLoggingActionListenerFactory; 7 | import one.d4d.signsaboteur.utils.Utils; 8 | 9 | import javax.swing.*; 10 | import java.awt.*; 11 | import java.awt.event.KeyEvent; 12 | import java.util.List; 13 | 14 | import static javax.swing.JOptionPane.WARNING_MESSAGE; 15 | 16 | public class SignDialog extends AbstractDialog { 17 | private JPanel contentPane; 18 | private JButton buttonOK; 19 | private JButton buttonCancel; 20 | private JComboBox comboBoxSigningKey; 21 | private SignedToken tokenObject; 22 | 23 | public SignDialog(Window parent, 24 | ErrorLoggingActionListenerFactory actionListenerFactory, 25 | List signingKeys, 26 | SignedToken tokenObject) { 27 | super(parent, "sign_dialog_title"); 28 | this.tokenObject = tokenObject; 29 | 30 | setContentPane(contentPane); 31 | getRootPane().setDefaultButton(buttonOK); 32 | 33 | buttonOK.addActionListener(actionListenerFactory.from(e -> onOK())); 34 | buttonCancel.addActionListener(e -> onCancel()); 35 | 36 | contentPane.registerKeyboardAction( 37 | e -> onCancel(), 38 | KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), 39 | JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT 40 | ); 41 | 42 | SecretKey[] signingKeysArray = new SecretKey[signingKeys.size()]; 43 | signingKeys.toArray(signingKeysArray); 44 | 45 | comboBoxSigningKey.setModel(new DefaultComboBoxModel<>(signingKeysArray)); 46 | comboBoxSigningKey.setSelectedIndex(0); 47 | } 48 | 49 | private void onOK() { 50 | SecretKey selectedKey = (SecretKey) comboBoxSigningKey.getSelectedItem(); 51 | 52 | try { 53 | assert selectedKey != null; 54 | TokenSigner s; 55 | if (tokenObject instanceof DangerousSignedToken) { 56 | s = new DangerousTokenSigner(selectedKey); 57 | } else if (tokenObject instanceof ExpressSignedToken) { 58 | s = new ExpressTokenSigner(selectedKey); 59 | } else if (tokenObject instanceof OauthProxySignedToken) { 60 | s = new OauthProxyTokenSigner(selectedKey); 61 | } else if (tokenObject instanceof TornadoSignedToken) { 62 | s = new TornadoTokenSigner(selectedKey); 63 | } else if (tokenObject instanceof RubySignedToken) { 64 | s = new RubyTokenSigner(selectedKey); 65 | } else if (tokenObject instanceof UnknownSignedToken) { 66 | s = new TokenSigner(selectedKey); 67 | } else { 68 | throw new Exception("Unknown"); 69 | } 70 | tokenObject.setSigner(s); 71 | tokenObject.resign(); 72 | } catch (Exception e) { 73 | tokenObject = null; 74 | JOptionPane.showMessageDialog( 75 | this, 76 | e.getMessage(), 77 | Utils.getResourceString("error_title_unable_to_sign"), 78 | WARNING_MESSAGE 79 | ); 80 | } finally { 81 | dispose(); 82 | } 83 | } 84 | 85 | public SignedToken getToken() { 86 | return tokenObject; 87 | } 88 | 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/dialog/EncryptionDialog.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.forms.dialog; 2 | 3 | import one.d4d.signsaboteur.itsdangerous.crypto.*; 4 | import one.d4d.signsaboteur.itsdangerous.model.*; 5 | import one.d4d.signsaboteur.keys.SecretKey; 6 | import one.d4d.signsaboteur.rsta.RstaFactory; 7 | import one.d4d.signsaboteur.utils.ErrorLoggingActionListenerFactory; 8 | import one.d4d.signsaboteur.utils.Utils; 9 | import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; 10 | 11 | import javax.swing.*; 12 | import javax.swing.border.Border; 13 | import javax.swing.border.LineBorder; 14 | import java.awt.*; 15 | import java.awt.event.*; 16 | import java.util.List; 17 | 18 | import static java.awt.Color.RED; 19 | import static javax.swing.JOptionPane.WARNING_MESSAGE; 20 | 21 | public class EncryptionDialog extends AbstractDialog { 22 | private JPanel contentPane; 23 | private JButton buttonOK; 24 | private JButton buttonCancel; 25 | private JComboBox comboBoxEncryptionKeys; 26 | private RSyntaxTextArea cypherText; 27 | private SignedToken tokenObject; 28 | private RstaFactory rstaFactory; 29 | 30 | public EncryptionDialog(Window parent, 31 | RstaFactory rstaFactory, 32 | ErrorLoggingActionListenerFactory actionListenerFactory, 33 | List signingKeys, 34 | SignedToken tokenObject) { 35 | super(parent, "encryption_dialog_title"); 36 | this.rstaFactory = rstaFactory; 37 | this.tokenObject = tokenObject; 38 | 39 | setContentPane(contentPane); 40 | getRootPane().setDefaultButton(buttonOK); 41 | 42 | buttonOK.addActionListener(actionListenerFactory.from(e -> onOK())); 43 | buttonCancel.addActionListener(e -> onCancel()); 44 | 45 | contentPane.registerKeyboardAction( 46 | e -> onCancel(), 47 | KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), 48 | JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT 49 | ); 50 | 51 | SecretKey[] signingKeysArray = new SecretKey[signingKeys.size()]; 52 | signingKeys.toArray(signingKeysArray); 53 | 54 | comboBoxEncryptionKeys.setModel(new DefaultComboBoxModel<>(signingKeysArray)); 55 | comboBoxEncryptionKeys.setSelectedIndex(0); 56 | } 57 | 58 | private void onOK() { 59 | SecretKey selectedKey = (SecretKey) comboBoxEncryptionKeys.getSelectedItem(); 60 | 61 | try { 62 | assert selectedKey != null; 63 | TokenSigner s; 64 | if (tokenObject instanceof RubyEncryptedToken) { 65 | s = new RubyEncryptionTokenSigner(selectedKey); 66 | } else { 67 | throw new Exception("Unknown"); 68 | } 69 | tokenObject.setSigner(s); 70 | String text = ((RubyEncryptedToken) tokenObject).getCypherText(); 71 | cypherText.setText(text); 72 | 73 | Border serializedTextAreaBorder = text.equals("Error") ? new LineBorder(RED, 1) : null; 74 | cypherText.setBorder(serializedTextAreaBorder); 75 | } catch (Exception e) { 76 | tokenObject = null; 77 | JOptionPane.showMessageDialog( 78 | this, 79 | e.getMessage(), 80 | Utils.getResourceString("error_title_unable_to_sign"), 81 | WARNING_MESSAGE 82 | ); 83 | } 84 | } 85 | 86 | private void createUIComponents() { 87 | // TODO: place custom component creation code here 88 | cypherText = rstaFactory.buildDefaultTextArea(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/burp/config/KeysModel.java: -------------------------------------------------------------------------------- 1 | package burp.config; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import one.d4d.signsaboteur.keys.SecretKey; 5 | 6 | import java.util.*; 7 | import java.util.stream.IntStream; 8 | 9 | public class KeysModel { 10 | private final Object lockKeys = new Object(); 11 | @Expose 12 | private final List keys = new ArrayList<>(); 13 | private Set secrets = new HashSet<>(); 14 | private Set salts = new HashSet<>(); 15 | @Expose 16 | private String secretsFilePath; 17 | @Expose 18 | private String saltsFilePath; 19 | private KeysModelListener modelListener; 20 | 21 | public KeysModel() { 22 | this.modelListener = new KeysModelListener.InertKeyModelListener(); 23 | } 24 | 25 | public Set getSecrets() { 26 | return secrets; 27 | } 28 | 29 | public void setSecrets(Set secrets) { 30 | this.secrets.addAll(secrets); 31 | } 32 | 33 | public Set getSalts() { 34 | return salts; 35 | } 36 | 37 | public void setSalts(Set salts) { 38 | this.salts.addAll(salts); 39 | } 40 | 41 | public String getSecretsFilePath() { 42 | return secretsFilePath; 43 | } 44 | 45 | public void setSecretsFilePath(String secretsFilePath) { 46 | this.secretsFilePath = secretsFilePath; 47 | } 48 | 49 | public String getSaltsFilePath() { 50 | return saltsFilePath; 51 | } 52 | 53 | public void setSaltsFilePath(String saltsFilePath) { 54 | this.saltsFilePath = saltsFilePath; 55 | } 56 | 57 | public void clearSecrets() { 58 | secrets.clear(); 59 | } 60 | 61 | public void clearSalts() { 62 | salts.clear(); 63 | } 64 | 65 | public void removeSecret(String s) { 66 | secrets.remove(s); 67 | } 68 | 69 | public void addSecret(String s) { 70 | secrets.add(s); 71 | } 72 | 73 | public void removeSalt(String s) { 74 | salts.remove(s); 75 | } 76 | 77 | public void addSalt(String s) { 78 | salts.add(s); 79 | } 80 | 81 | public Optional getKey(String keyId) { 82 | synchronized (lockKeys) { 83 | return keys.stream().filter(x -> keyId.equals(x.getID())).findFirst(); 84 | } 85 | } 86 | 87 | public SecretKey getKey(int index) { 88 | synchronized (lockKeys) { 89 | return keys.get(index); 90 | } 91 | } 92 | 93 | public void addKey(SecretKey key) { 94 | synchronized (lockKeys) { 95 | keys.add(key); 96 | } 97 | modelListener.notifyKeyInserted(key); 98 | } 99 | 100 | public List getSigningKeys() { 101 | synchronized (lockKeys) { 102 | return keys; 103 | } 104 | } 105 | 106 | public void addKeyModelListener(KeysModelListener modelListener) { 107 | this.modelListener = modelListener; 108 | } 109 | 110 | public void deleteKey(SecretKey keyId) { 111 | int rowIndex; 112 | 113 | synchronized (lockKeys) { 114 | rowIndex = keys.indexOf(keyId); 115 | keys.remove(keyId); 116 | } 117 | 118 | if (rowIndex >= 0) { 119 | modelListener.notifyKeyDeleted(rowIndex); 120 | } 121 | } 122 | 123 | public void deleteKeys(int[] indices) { 124 | synchronized (lockKeys) { 125 | List idsToDelete = IntStream.of(indices).mapToObj(this::getKey).toList(); 126 | 127 | for (SecretKey id : idsToDelete) { 128 | deleteKey(id); 129 | } 130 | } 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/rsta/CustomizedRSyntaxTextArea.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.rsta; 2 | 3 | import one.d4d.signsaboteur.utils.FontProvider; 4 | import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; 5 | import org.fife.ui.rsyntaxtextarea.Theme; 6 | 7 | import java.awt.*; 8 | import java.awt.event.MouseEvent; 9 | import java.io.IOException; 10 | import java.util.function.Consumer; 11 | 12 | import static one.d4d.signsaboteur.rsta.CustomTokenColors.customTokenColors; 13 | import static java.awt.event.HierarchyEvent.SHOWING_CHANGED; 14 | import static org.fife.ui.rsyntaxtextarea.Theme.load; 15 | 16 | class CustomizedRSyntaxTextArea extends RSyntaxTextArea { 17 | private static final String DARK_THEME = "/org/fife/ui/rsyntaxtextarea/themes/dark.xml"; 18 | private static final String LIGHT_THEME = "/org/fife/ui/rsyntaxtextarea/themes/default.xml"; 19 | private static final double LINE_HEIGHT_SCALING_FACTOR = 1.15; 20 | 21 | private final DarkModeDetector darkModeDetector; 22 | private final FontProvider fontProvider; 23 | private final Consumer errorLogger; 24 | private final CustomTokenColors customTokenColors; 25 | 26 | CustomizedRSyntaxTextArea( 27 | DarkModeDetector darkModeDetector, 28 | FontProvider fontProvider, 29 | Consumer errorLogger) { 30 | this(darkModeDetector, fontProvider, errorLogger, customTokenColors().build()); 31 | } 32 | 33 | CustomizedRSyntaxTextArea( 34 | DarkModeDetector darkModeDetector, 35 | FontProvider fontProvider, 36 | Consumer errorLogger, 37 | CustomTokenColors customTokenColors) { 38 | this.darkModeDetector = darkModeDetector; 39 | this.fontProvider = fontProvider; 40 | this.errorLogger = errorLogger; 41 | this.customTokenColors = customTokenColors; 42 | 43 | this.addHierarchyListener(e -> { 44 | if (e.getChangeFlags() == SHOWING_CHANGED && e.getComponent().isShowing()) { 45 | applyThemeAndFont(); 46 | } 47 | }); 48 | 49 | setUseFocusableTips(false); 50 | setBracketMatchingEnabled(false); 51 | setShowMatchedBracketPopup(false); 52 | } 53 | 54 | @Override 55 | public String getToolTipText(MouseEvent e) { 56 | return null; 57 | } 58 | 59 | @Override 60 | protected String getToolTipTextImpl(MouseEvent e) { 61 | return null; 62 | } 63 | 64 | @Override 65 | public void setSyntaxEditingStyle(String styleKey) { 66 | super.setSyntaxEditingStyle(styleKey); 67 | applyThemeAndFont(); 68 | } 69 | 70 | @Override 71 | public void updateUI() { 72 | super.updateUI(); 73 | applyThemeAndFont(); 74 | } 75 | 76 | @Override 77 | public Color getForegroundForTokenType(int type) { 78 | return customTokenColors 79 | .foregroundForTokenType(type) 80 | .orElse(super.getForegroundForTokenType(type)); 81 | } 82 | 83 | @Override 84 | public int getLineHeight() { 85 | return (int) (super.getLineHeight() * LINE_HEIGHT_SCALING_FACTOR); 86 | } 87 | 88 | private void applyThemeAndFont() { 89 | if (errorLogger == null || fontProvider == null) { 90 | return; 91 | } 92 | 93 | String themeResource = darkModeDetector.isDarkMode() ? DARK_THEME : LIGHT_THEME; 94 | 95 | try { 96 | Theme theme = load(getClass().getResourceAsStream(themeResource)); 97 | theme.apply(this); 98 | 99 | Font font = fontProvider.editorFont(); 100 | setFont(font); 101 | } catch (IOException e) { 102 | errorLogger.accept(e.getMessage()); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/rsta/RstaFactory.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.rsta; 2 | 3 | import burp.api.montoya.logging.Logging; 4 | import burp.api.montoya.ui.UserInterface; 5 | import one.d4d.signsaboteur.rsta.token.SignedTokenMaker; 6 | import one.d4d.signsaboteur.rsta.token.SignedTokenizerConstants; 7 | import one.d4d.signsaboteur.utils.FontProvider; 8 | import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory; 9 | import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; 10 | import org.fife.ui.rsyntaxtextarea.TokenMakerFactory; 11 | 12 | import javax.swing.*; 13 | import javax.swing.text.JTextComponent; 14 | import java.awt.*; 15 | import java.util.function.Supplier; 16 | 17 | import static one.d4d.signsaboteur.rsta.CustomTokenColors.customTokenColors; 18 | import static one.d4d.signsaboteur.rsta.token.SignedTokenMaker.*; 19 | import static one.d4d.signsaboteur.rsta.token.SignedTokenizerConstants.MAPPING; 20 | import static one.d4d.signsaboteur.rsta.token.SignedTokenizerConstants.TOKEN_MAKER_FQCN; 21 | 22 | public class RstaFactory { 23 | private final DarkModeDetector darkModeDetector; 24 | private final FontProvider fontProvider; 25 | private final Logging logging; 26 | 27 | public RstaFactory(UserInterface userInterface, Logging logging) { 28 | this.darkModeDetector = new DarkModeDetector(userInterface); 29 | this.fontProvider = new FontProvider(userInterface); 30 | this.logging = logging; 31 | 32 | AbstractTokenMakerFactory tokenMakerFactory = (AbstractTokenMakerFactory) TokenMakerFactory.getDefaultInstance(); 33 | tokenMakerFactory.putMapping(MAPPING, TOKEN_MAKER_FQCN); 34 | SignedTokenMaker.errorLogger = logging::logToError; 35 | } 36 | 37 | public RSyntaxTextArea buildDefaultTextArea() { 38 | return fixKeyEventCapture( 39 | () -> new CustomizedRSyntaxTextArea(darkModeDetector, fontProvider, logging::logToError) 40 | ); 41 | } 42 | 43 | public RSyntaxTextArea buildSerializedJWTTextArea() { 44 | CustomTokenColors customTokenColors = customTokenColors() 45 | .withForeground(JWT_PART1, Color.decode("#FB015B")) 46 | .withForeground(JWT_PART2, Color.decode("#D63AFF")) 47 | .withForeground(JWT_PART3, Color.decode("#00B9F1")) 48 | .withForeground(JWT_PART4, Color.decode("#EA7600")) 49 | .withForeground(JWT_PART5, Color.decode("#EDB219")) 50 | .withForeground(JWT_SEPARATOR1, Color.decode("#A6A282")) 51 | .withForeground(JWT_SEPARATOR2, Color.decode("#A6A282")) 52 | .withForeground(JWT_SEPARATOR3, Color.decode("#A6A282")) 53 | .withForeground(JWT_SEPARATOR4, Color.decode("#A6A282")) 54 | .build(); 55 | 56 | RSyntaxTextArea textArea = fixKeyEventCapture( 57 | () -> new CustomizedRSyntaxTextArea( 58 | darkModeDetector, 59 | fontProvider, 60 | logging::logToError, 61 | customTokenColors 62 | ) 63 | ); 64 | 65 | textArea.setSyntaxEditingStyle(SignedTokenizerConstants.MAPPING); 66 | 67 | return textArea; 68 | } 69 | 70 | // Ensure Burp key events not captured - https://github.com/bobbylight/RSyntaxTextArea/issues/269#issuecomment-776329702 71 | private RSyntaxTextArea fixKeyEventCapture(Supplier rSyntaxTextAreaSupplier) { 72 | JTextComponent.removeKeymap("RTextAreaKeymap"); 73 | 74 | RSyntaxTextArea textArea = rSyntaxTextAreaSupplier.get(); 75 | 76 | UIManager.put("RSyntaxTextAreaUI.actionMap", null); 77 | UIManager.put("RSyntaxTextAreaUI.inputMap", null); 78 | UIManager.put("RTextAreaUI.actionMap", null); 79 | UIManager.put("RTextAreaUI.inputMap", null); 80 | 81 | return textArea; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/rsta/token/SignedTokenMaker.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.rsta.token; 2 | 3 | import org.fife.ui.rsyntaxtextarea.AbstractTokenMaker; 4 | import org.fife.ui.rsyntaxtextarea.Token; 5 | import org.fife.ui.rsyntaxtextarea.TokenMap; 6 | import org.fife.ui.rsyntaxtextarea.TokenTypes; 7 | 8 | import javax.swing.text.Segment; 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.PrintStream; 11 | import java.util.function.Consumer; 12 | 13 | import static org.fife.ui.rsyntaxtextarea.TokenTypes.NULL; 14 | 15 | public class SignedTokenMaker extends AbstractTokenMaker { 16 | public static final int JWT_PART1 = TokenTypes.COMMENT_EOL; 17 | public static final int JWT_SEPARATOR1 = TokenTypes.COMMENT_MULTILINE; 18 | public static final int JWT_PART2 = TokenTypes.COMMENT_DOCUMENTATION; 19 | public static final int JWT_SEPARATOR2 = TokenTypes.COMMENT_KEYWORD; 20 | public static final int JWT_PART3 = TokenTypes.COMMENT_MARKUP; 21 | public static final int JWT_SEPARATOR3 = TokenTypes.RESERVED_WORD; 22 | public static final int JWT_PART4 = TokenTypes.RESERVED_WORD_2; 23 | public static final int JWT_SEPARATOR4 = TokenTypes.FUNCTION; 24 | public static final int JWT_PART5 = TokenTypes.LITERAL_BOOLEAN; 25 | 26 | public static Consumer errorLogger = s -> {}; 27 | 28 | @Override 29 | public TokenMap getWordsToHighlight() { 30 | return new TokenMap(); 31 | } 32 | 33 | @Override 34 | public Token getTokenList(Segment text, int initialTokenType, int startOffset) { 35 | try { 36 | resetTokenList(); 37 | 38 | char[] array = text.array; 39 | int offset = text.offset; 40 | int count = text.count; 41 | int end = offset + count; 42 | 43 | // Token starting offsets are always of the form: 44 | // 'startOffset + (currentTokenStart-offset)', but since startOffset and 45 | // offset are constant, tokens' starting positions become: 46 | // 'newStartOffset+currentTokenStart'. 47 | int newStartOffset = startOffset - offset; 48 | 49 | int currentTokenStart = offset; 50 | int currentTokenType = initialTokenType; 51 | int previousTokenType = initialTokenType; 52 | 53 | for (int i = offset; i < end; i++) { 54 | char c = array[i]; 55 | 56 | currentTokenType = switch (currentTokenType) { 57 | case NULL, JWT_PART1 -> c == '.' ? JWT_SEPARATOR1 : JWT_PART1; 58 | case JWT_SEPARATOR1, JWT_PART2 -> c == '.' ? JWT_SEPARATOR2 : JWT_PART2; 59 | case JWT_SEPARATOR2, JWT_PART3 -> c == '.' ? JWT_SEPARATOR3 : JWT_PART3; 60 | case JWT_SEPARATOR3, JWT_PART4 -> c == '.' ? JWT_SEPARATOR4 : JWT_PART4; 61 | case JWT_SEPARATOR4, JWT_PART5 -> JWT_PART5; 62 | default -> throw new IllegalStateException("State: %d Char: %c".formatted(currentTokenType, c)); 63 | }; 64 | 65 | if (previousTokenType != NULL && previousTokenType != currentTokenType) { 66 | addToken(text, currentTokenStart, i - 1, previousTokenType, newStartOffset + currentTokenStart); 67 | currentTokenStart = i; 68 | } 69 | 70 | previousTokenType = currentTokenType; 71 | } 72 | 73 | if (currentTokenType != NULL) { 74 | addToken(text, currentTokenStart, end - 1, currentTokenType, newStartOffset + currentTokenStart); 75 | } 76 | 77 | addNullToken(); 78 | 79 | return firstToken; 80 | } catch (Throwable t) { 81 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 82 | t.printStackTrace(new PrintStream(outputStream)); 83 | 84 | errorLogger.accept(outputStream.toString()); 85 | 86 | return null; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/dialog/BruteForceAttackDialog.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.forms.dialog; 2 | 3 | import one.d4d.signsaboteur.itsdangerous.Attack; 4 | import one.d4d.signsaboteur.itsdangerous.BruteForce; 5 | import one.d4d.signsaboteur.itsdangerous.model.SignedToken; 6 | import one.d4d.signsaboteur.keys.SecretKey; 7 | import one.d4d.signsaboteur.presenter.PresenterStore; 8 | import one.d4d.signsaboteur.utils.ErrorLoggingActionListenerFactory; 9 | import one.d4d.signsaboteur.utils.Utils; 10 | 11 | import javax.swing.*; 12 | import java.awt.*; 13 | import java.awt.event.*; 14 | import java.util.List; 15 | import java.util.Set; 16 | 17 | 18 | public class BruteForceAttackDialog extends AbstractDialog { 19 | private JPanel contentPane; 20 | private JButton buttonCancel; 21 | private JProgressBar progressBarBruteForce; 22 | private JLabel lblStatus; 23 | private SecretKey secretKey; 24 | 25 | public BruteForceAttackDialog( 26 | Window parent, 27 | ErrorLoggingActionListenerFactory actionListenerFactory, 28 | Set signingSecrets, 29 | Set signingSalts, 30 | List signingKeys, 31 | Attack mode, 32 | SignedToken token, 33 | PresenterStore presenters 34 | ) { 35 | super(parent, "attack_dialog_title"); 36 | 37 | setContentPane(contentPane); 38 | getRootPane().setDefaultButton(buttonCancel); 39 | 40 | 41 | // call onCancel() when cross is clicked 42 | setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); 43 | addWindowListener(new WindowAdapter() { 44 | public void windowClosing(WindowEvent e) { 45 | onCancel(); 46 | } 47 | }); 48 | 49 | // call onCancel() on ESCAPE 50 | contentPane.registerKeyboardAction(new ActionListener() { 51 | public void actionPerformed(ActionEvent e) { 52 | onCancel(); 53 | } 54 | }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 55 | 56 | // Logic 57 | String lblText = String.format( 58 | Utils.getResourceString("attack_dialog_progress_bar_status"), 59 | mode == Attack.KNOWN ? signingKeys.size() : signingSecrets.size(), 60 | mode == Attack.KNOWN ? signingKeys.size() : signingSalts.size(), 61 | mode.getName() 62 | ); 63 | lblStatus.setText(lblText); 64 | SwingWorker sw = new SwingWorker() { 65 | private BruteForce bf; 66 | 67 | @Override 68 | protected Void doInBackground() throws Exception { 69 | bf = new BruteForce(signingSecrets, signingSalts, signingKeys, mode, token, presenters); 70 | SecretKey k = bf.parallel(); 71 | if (k != null) { 72 | secretKey = k; 73 | } 74 | return null; 75 | } 76 | 77 | @Override 78 | protected void done() { 79 | if (isCancelled()) { 80 | if (bf != null) bf.shutdown(); 81 | } 82 | dispose(); 83 | } 84 | }; 85 | sw.execute(); 86 | buttonCancel.addActionListener(e -> { 87 | sw.cancel(true); 88 | onCancel(); 89 | }); 90 | this.addWindowListener(new WindowAdapter() { 91 | public void windowClosing(WindowEvent e) { 92 | sw.cancel(true); 93 | } 94 | 95 | public void windowClosed(WindowEvent e) { 96 | sw.cancel(true); 97 | } 98 | }); 99 | } 100 | 101 | 102 | private void createUIComponents() { 103 | progressBarBruteForce = new JProgressBar(0, 100); 104 | progressBarBruteForce.setIndeterminate(true); 105 | } 106 | 107 | 108 | public SecretKey getSecretKey() { 109 | return secretKey; 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/crypto/TornadoTokenSigner.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.crypto; 2 | 3 | import com.google.common.primitives.Bytes; 4 | import one.d4d.signsaboteur.itsdangerous.*; 5 | import one.d4d.signsaboteur.keys.SecretKey; 6 | import one.d4d.signsaboteur.utils.Utils; 7 | 8 | import javax.crypto.Mac; 9 | import javax.crypto.spec.SecretKeySpec; 10 | import java.util.Arrays; 11 | import java.util.Collections; 12 | import java.util.EnumSet; 13 | 14 | public class TornadoTokenSigner extends TokenSigner { 15 | public TornadoTokenSigner(SecretKey key) { 16 | super(key); 17 | this.knownDerivations = EnumSet.of(Derivation.NONE); 18 | } 19 | 20 | public TornadoTokenSigner() { 21 | this(new byte[]{}, new byte[]{'|'}); 22 | } 23 | 24 | public TornadoTokenSigner(byte[] secret_key, byte[] sep) { 25 | this(Algorithms.SHA1, Derivation.NONE, MessageDerivation.NONE, MessageDigestAlgorithm.NONE, secret_key, new byte[]{}, sep); 26 | } 27 | 28 | public TornadoTokenSigner( 29 | Algorithms digestMethod, 30 | Derivation keyDerivation, 31 | MessageDerivation messageDerivation, 32 | MessageDigestAlgorithm digest, 33 | byte[] secret_key, 34 | byte[] salt, 35 | byte[] sep) { 36 | super(digestMethod, keyDerivation, messageDerivation, digest, secret_key, salt, sep); 37 | this.knownDerivations = EnumSet.of(Derivation.NONE); 38 | } 39 | 40 | @Override 41 | public byte[] derive_key() throws DerivationException { 42 | return secret_key; 43 | } 44 | 45 | @Override 46 | public byte[] get_signature(byte[] value) { 47 | try { 48 | byte[] key = derive_key(); 49 | SecretKeySpec signingKey = new SecretKeySpec(key, digestMethod.name); 50 | Mac mac = Mac.getInstance(digestMethod.name); 51 | mac.init(signingKey); 52 | return mac.doFinal(value); 53 | } catch (Exception e) { 54 | return new byte[]{}; 55 | } 56 | } 57 | 58 | @Override 59 | public byte[] get_signature_unsafe(byte[] value) throws Exception { 60 | byte[] key = derive_key(); 61 | SecretKeySpec signingKey = new SecretKeySpec(key, digestMethod.name); 62 | Mac mac = Mac.getInstance(digestMethod.name); 63 | mac.init(signingKey); 64 | return mac.doFinal(value); 65 | } 66 | 67 | @Override 68 | public byte[] get_signature_bytes(byte[] value) { 69 | try { 70 | byte[] key = derive_key(); 71 | SecretKeySpec signingKey = new SecretKeySpec(key, digestMethod.name); 72 | Mac mac = Mac.getInstance(digestMethod.name); 73 | mac.init(signingKey); 74 | return mac.doFinal(value); 75 | } catch (Exception e) { 76 | return new byte[]{}; 77 | } 78 | } 79 | 80 | @Override 81 | public boolean verify_signature(byte[] value, byte[] signature) { 82 | try { 83 | byte[] expected = get_signature_bytes(value); 84 | return Arrays.equals(expected, signature); 85 | } catch (Exception e) { 86 | return false; 87 | } 88 | } 89 | 90 | @Override 91 | public byte[] unsign(byte[] value) throws BadSignatureException { 92 | int i = Collections.lastIndexOfSubList(Bytes.asList(value), Bytes.asList(sep)); 93 | // Note! Tornado uses last delimiter for signature calculation. 94 | byte[] message = Arrays.copyOfRange(value, 0, i + 1); 95 | byte[] signature = Arrays.copyOfRange(value, i + 1, value.length); 96 | byte[] sign = Utils.normalization(signature); 97 | switch (sign.length) { 98 | case 28 -> digestMethod = Algorithms.SHA224; 99 | case 32 -> digestMethod = Algorithms.SHA256; 100 | case 48 -> digestMethod = Algorithms.SHA384; 101 | case 64 -> digestMethod = Algorithms.SHA512; 102 | default -> digestMethod = Algorithms.SHA1; 103 | } 104 | if (verify_signature(message, sign)) 105 | return message; 106 | throw new BadSignatureException("Signature didn't match"); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/test/java/JSONWebSignatureTest.java: -------------------------------------------------------------------------------- 1 | import one.d4d.signsaboteur.itsdangerous.*; 2 | import one.d4d.signsaboteur.itsdangerous.model.JSONWebSignature; 3 | import one.d4d.signsaboteur.itsdangerous.model.SignedToken; 4 | import one.d4d.signsaboteur.itsdangerous.model.SignedTokenObjectFinder; 5 | import one.d4d.signsaboteur.keys.SecretKey; 6 | import one.d4d.signsaboteur.utils.ClaimsUtils; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.net.MalformedURLException; 11 | import java.net.URL; 12 | import java.util.*; 13 | 14 | public class JSONWebSignatureTest { 15 | @Test 16 | void JSONWebSignatureParserTest() { 17 | final Set secrets = new HashSet<>(List.of("your-256-bit-secret")); 18 | final Set salts = new HashSet<>(List.of("salt")); 19 | final List knownKeys = new ArrayList<>(); 20 | String value = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; 21 | Optional optionalToken = SignedTokenObjectFinder.parseJSONWebSignature(value); 22 | if (optionalToken.isPresent()) { 23 | JSONWebSignature token = (JSONWebSignature) optionalToken.get(); 24 | BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.FAST, token); 25 | SecretKey sk = bf.parallel(); 26 | Assertions.assertNotNull(sk); 27 | } else { 28 | Assertions.fail("Token not found."); 29 | } 30 | } 31 | @Test 32 | void JWTNimbus() { 33 | final Set secrets = new HashSet<>(List.of("your-256-bit-secret")); 34 | final Set salts = new HashSet<>(List.of("salt")); 35 | final List knownKeys = new ArrayList<>(); 36 | String value = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; 37 | 38 | Optional optionalToken = SignedTokenObjectFinder.parseSignedJWT(value, true); 39 | if (optionalToken.isPresent()) { 40 | JSONWebSignature token = (JSONWebSignature) optionalToken.get(); 41 | BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.FAST, token); 42 | SecretKey sk = bf.parallel(); 43 | Assertions.assertNotNull(sk); 44 | } else { 45 | Assertions.fail("Token not found."); 46 | } 47 | } 48 | 49 | @Test 50 | void JSONWebTokenRS256Test() { 51 | String value = "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.VUPWQZuClnkFbaEKCsPy7CZVMh5wxbCSpaAWFLpnTe9J0--PzHNeTFNXCrVHysAa3eFbuzD8_bLSsgTKC8SzHxRVSj5eN86vBPo_1fNfE7SHTYhWowjY4E_wuiC13yoj"; 52 | Optional optionalToken = SignedTokenObjectFinder.parseSignedJWT(value, true); 53 | Assertions.assertTrue(optionalToken.isEmpty()); 54 | } 55 | 56 | @Test 57 | void JSONWebSignatureClaimsTest() { 58 | try { 59 | final Set secrets = new HashSet<>(List.of("secret")); 60 | final Set salts = new HashSet<>(List.of("salt")); 61 | final List knownKeys = new ArrayList<>(); 62 | URL target = new URL("https://d4d.one/"); 63 | SecretKey key = new SecretKey("1", "secret", "", ".", Algorithms.SHA256, Derivation.NONE, MessageDerivation.NONE, MessageDigestAlgorithm.NONE); 64 | String value = ClaimsUtils.generateJSONWebToken(target, ClaimsUtils.DEFAULT_USERNAME, key); 65 | Optional optionalToken = SignedTokenObjectFinder.parseJSONWebSignature(value); 66 | if (optionalToken.isPresent()) { 67 | JSONWebSignature token = (JSONWebSignature) optionalToken.get(); 68 | BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.FAST, token); 69 | SecretKey sk = bf.parallel(); 70 | Assertions.assertNotNull(sk); 71 | } else { 72 | Assertions.fail("Token not found."); 73 | } 74 | } catch (MalformedURLException | BadPayloadException e) { 75 | throw new RuntimeException(e); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/itsdangerous/model/DangerousSignedToken.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.itsdangerous.model; 2 | 3 | import com.nimbusds.jwt.JWTClaimsSet; 4 | import one.d4d.signsaboteur.itsdangerous.Algorithms; 5 | import one.d4d.signsaboteur.itsdangerous.Derivation; 6 | import one.d4d.signsaboteur.itsdangerous.MessageDerivation; 7 | import one.d4d.signsaboteur.itsdangerous.MessageDigestAlgorithm; 8 | import one.d4d.signsaboteur.itsdangerous.crypto.DangerousTokenSigner; 9 | import one.d4d.signsaboteur.utils.Utils; 10 | 11 | import java.util.Base64; 12 | 13 | public class DangerousSignedToken extends SignedToken { 14 | public final String timestamp; 15 | public String payload; 16 | public byte[] separator; 17 | 18 | public DangerousSignedToken(byte[] separator, String payload, String timestamp, String signature) { 19 | super(String.format("%s%s%s", payload, new String(separator), timestamp)); 20 | this.separator = separator; 21 | this.payload = payload; 22 | this.timestamp = timestamp; 23 | this.signature = signature; 24 | this.signer = new DangerousTokenSigner(separator); 25 | } 26 | 27 | public DangerousSignedToken( 28 | byte[] separator, 29 | String payload, 30 | String timestamp, 31 | String signature, 32 | Algorithms algorithm, 33 | Derivation derivation, 34 | MessageDerivation messageDerivation, 35 | MessageDigestAlgorithm digest) { 36 | super(String.format("%s%s%s", payload, new String(separator), timestamp)); 37 | this.separator = separator; 38 | this.payload = payload; 39 | this.timestamp = timestamp; 40 | this.signature = signature; 41 | this.signer = new DangerousTokenSigner( 42 | algorithm, 43 | derivation, 44 | messageDerivation, 45 | digest, 46 | new byte[]{}, 47 | new byte[]{}, 48 | separator 49 | ); 50 | } 51 | 52 | public void setSigner(DangerousTokenSigner signer) { 53 | this.signer = signer; 54 | } 55 | 56 | public String dumps() { 57 | byte[] header = Base64.getUrlEncoder().withoutPadding().encode(payload.getBytes()); 58 | String message = String.format("%s%s%s", new String(header), new String(this.separator), this.timestamp); 59 | return new String(signer.sign(message.getBytes())); 60 | } 61 | 62 | public String toString() { 63 | try { 64 | StringBuilder sb = new StringBuilder(); 65 | byte[] json = Utils.base64Decompress(this.payload.getBytes()); 66 | sb.append(new String(json)).append(new String(this.separator)); 67 | sb.append(Utils.base64timestamp(this.timestamp.getBytes())).append(new String(this.separator)); 68 | sb.append(this.signature); 69 | return sb.toString(); 70 | } catch (Exception e) { 71 | return String.format("%s%s%s%s%s", payload, new String(separator), timestamp, new String(separator), signature); 72 | } 73 | } 74 | 75 | public String serialize() { 76 | return String.format("%s%s%s%s%s", payload, new String(separator), timestamp, new String(separator), signature); 77 | } 78 | 79 | public void resign() throws Exception { 80 | this.signature = new String(signer.get_signature_unsafe(message.getBytes())); 81 | } 82 | 83 | @Override 84 | public void setClaims(JWTClaimsSet claims) { 85 | if (isCompressed()) { 86 | this.payload = Utils.compressBase64(claims.toString().getBytes()); 87 | } else { 88 | this.payload = new String(Base64.getUrlEncoder().withoutPadding().encode(claims.toString().getBytes())); 89 | } 90 | this.message = String.format("%s%s%s", payload, new String(separator), timestamp); 91 | } 92 | 93 | public byte[] getSignature() { 94 | return Base64.getUrlDecoder().decode(signature); 95 | } 96 | 97 | public String getPayload() { 98 | return payload; 99 | } 100 | 101 | public boolean isCompressed() { 102 | return payload.startsWith("."); 103 | } 104 | 105 | public String getTimestamp() { 106 | try { 107 | return Utils.base64timestamp(timestamp.getBytes()); 108 | } catch (Exception e) { 109 | return Utils.timestamp(timestamp.getBytes()); 110 | } 111 | } 112 | 113 | public byte[] getSeparator() { 114 | return separator; 115 | } 116 | } -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/dialog/NewWordDialog.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 |
83 | -------------------------------------------------------------------------------- /src/test/java/BruteForceTest.java: -------------------------------------------------------------------------------- 1 | import one.d4d.signsaboteur.itsdangerous.Attack; 2 | import one.d4d.signsaboteur.itsdangerous.BruteForce; 3 | import one.d4d.signsaboteur.itsdangerous.crypto.DangerousTokenSigner; 4 | import one.d4d.signsaboteur.itsdangerous.crypto.RubyEncryptionTokenSigner; 5 | import one.d4d.signsaboteur.itsdangerous.model.SignedToken; 6 | import one.d4d.signsaboteur.itsdangerous.model.SignedTokenObjectFinder; 7 | import one.d4d.signsaboteur.keys.SecretKey; 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.util.*; 12 | 13 | public class BruteForceTest { 14 | 15 | @Test 16 | void BruteForceAttack() { 17 | Assertions.assertDoesNotThrow(() -> { 18 | Optional optionalSignedToken = SignedTokenObjectFinder.parseToken("e30.Zm17Ig.Ajtll0l5CXAy9Yqgy-vvhF05G28"); 19 | if (optionalSignedToken.isPresent()) { 20 | SignedToken token = optionalSignedToken.get(); 21 | byte[] sep = new byte[]{'.'}; 22 | DangerousTokenSigner s = new DangerousTokenSigner(sep); 23 | token.setSigner(s); 24 | final Set secrets = new HashSet<>(List.of("secret")); 25 | final Set salts = new HashSet<>(List.of("salt")); 26 | final List knownKeys = new ArrayList<>(); 27 | 28 | BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.FAST, token); 29 | SecretKey sk = bf.parallel(); 30 | Assertions.assertNotNull(sk); 31 | } else { 32 | Assertions.fail("Token not found."); 33 | } 34 | }); 35 | } 36 | 37 | @Test 38 | void BruteForceMultiThreatAttack() { 39 | Assertions.assertDoesNotThrow(() -> { 40 | Optional optionalSignedToken = SignedTokenObjectFinder.parseToken("e30.Zm17Ig.Ajtll0l5CXAy9Yqgy-vvhF05G28"); 41 | if (optionalSignedToken.isPresent()) { 42 | SignedToken token = optionalSignedToken.get(); 43 | byte[] sep = new byte[]{'.'}; 44 | DangerousTokenSigner s = new DangerousTokenSigner(sep); 45 | token.setSigner(s); 46 | final Set secrets = new HashSet<>(List.of("secret")); 47 | final Set salts = new HashSet<>(List.of("salt")); 48 | final List knownKeys = new ArrayList<>(); 49 | 50 | BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.FAST, token); 51 | SecretKey sk = bf.parallel(); 52 | Assertions.assertNotNull(sk); 53 | } else { 54 | Assertions.fail("Token not found."); 55 | } 56 | }); 57 | } 58 | 59 | @Test 60 | void encryptionTest() { 61 | String secret = "aeb977de013ade650b97e0aa5246813591104017871a7753fe186e9634c9129b367306606878985c759ca4fddd17d955207011bb855ef01ed414398b4ac8317b"; 62 | String salt = "authenticated encrypted cookie"; 63 | String app_session = "isteTiyNSFdbUoabLodAVDd4jQuj%2F5t%2FRTE6BqyklssH0ye%2F2RnMJ3fIBkFfr9tei5yh5agfgX%2F9Mi8gQIA4zAOXwGyCuJBhauvszTYDCW7Q%2FVwDXIc4lAtiO%2FmBf5txRBoAulkc4ZTAaT1FMM%2F6ky7p8oul0hbi4xZf1%2ByURhPci4f%2FEGNYsJ2eLx9BALX7sVOB3dYpN6eQb%2B7LTXRxy2bnObmiHQaNaTx6jhdWwRcdEgGph7le6dN49gi%2FiLp%2B0yecWNyEzQbZ%2FRHKniIf%2FmCFTVw%3D--e7EBPhAdylQsT6It--wIte3m%2F2WUhtfKQewysoSQ%3D%3D"; 64 | 65 | Assertions.assertDoesNotThrow(() -> { 66 | Optional optionalSignedToken = SignedTokenObjectFinder.parseRubyEncryptedToken("",app_session); 67 | if (optionalSignedToken.isPresent()) { 68 | SignedToken token = optionalSignedToken.get(); 69 | byte[] sep = new byte[]{'-','-'}; 70 | RubyEncryptionTokenSigner s = new RubyEncryptionTokenSigner(sep); 71 | token.setSigner(s); 72 | final Set secrets = new HashSet<>(List.of(secret)); 73 | final Set salts = new HashSet<>(List.of(salt)); 74 | final List knownKeys = new ArrayList<>(); 75 | 76 | BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.FAST, token); 77 | SecretKey sk = bf.parallel(); 78 | Assertions.assertNotNull(sk); 79 | RubyEncryptionTokenSigner ns = new RubyEncryptionTokenSigner(sk); 80 | System.out.println(sk.toJSONString()); 81 | token.setSigner(ns); 82 | token.resign(); 83 | System.out.println(token); 84 | } else { 85 | Assertions.fail("Token not found."); 86 | } 87 | }); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/burp/SignSaboteurExtension.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import burp.api.montoya.BurpExtension; 4 | import burp.api.montoya.MontoyaApi; 5 | import burp.api.montoya.persistence.Preferences; 6 | import burp.api.montoya.proxy.Proxy; 7 | import burp.api.montoya.scanner.Scanner; 8 | import burp.api.montoya.ui.UserInterface; 9 | import burp.api.montoya.utilities.ByteUtils; 10 | import burp.config.*; 11 | import burp.proxy.ProxyHttpMessageHandler; 12 | import burp.proxy.ProxyWsMessageHandler; 13 | import burp.scanner.ScannerHandler; 14 | import one.d4d.signsaboteur.forms.ExtensionTab; 15 | import one.d4d.signsaboteur.forms.RequestEditorView; 16 | import one.d4d.signsaboteur.forms.ResponseEditorView; 17 | import one.d4d.signsaboteur.presenter.PresenterStore; 18 | import one.d4d.signsaboteur.rsta.RstaFactory; 19 | import one.d4d.signsaboteur.utils.Utils; 20 | 21 | import java.awt.*; 22 | 23 | import static burp.api.montoya.core.BurpSuiteEdition.PROFESSIONAL; 24 | import static burp.api.montoya.ui.editor.extension.EditorMode.READ_ONLY; 25 | 26 | public class SignSaboteurExtension implements BurpExtension { 27 | 28 | @Override 29 | public void initialize(MontoyaApi api) { 30 | api.extension().setName(Utils.getResourceString("tool_name")); 31 | PresenterStore presenters = new PresenterStore(); 32 | 33 | Preferences preferences = api.persistence().preferences(); 34 | BurpKeysModelPersistence keysModelPersistence = new BurpKeysModelPersistence(preferences); 35 | KeysModel keysModel = keysModelPersistence.loadOrCreateNew(); 36 | 37 | BurpConfigPersistence burpConfigPersistence = new BurpConfigPersistence(preferences, presenters); 38 | BurpConfig burpConfig = burpConfigPersistence.loadOrCreateNew(); 39 | api.extension().registerUnloadingHandler(() -> burpConfigPersistence.unload(burpConfig)); 40 | 41 | UserInterface userInterface = api.userInterface(); 42 | Window suiteWindow = userInterface.swingUtils().suiteFrame(); 43 | 44 | Proxy proxy = api.proxy(); 45 | Scanner scanner = api.scanner(); 46 | ProxyConfig proxyConfig = burpConfig.proxyConfig(); 47 | SignerConfig signerConfig = burpConfig.signerConfig(); 48 | ByteUtils byteUtils = api.utilities().byteUtils(); 49 | 50 | boolean isProVersion = api.burpSuite().version().edition() == PROFESSIONAL; 51 | RstaFactory rstaFactory = new RstaFactory(userInterface, api.logging()); 52 | 53 | ExtensionTab suiteView = new ExtensionTab( 54 | suiteWindow, 55 | presenters, 56 | keysModel, 57 | keysModelPersistence, 58 | burpConfig, 59 | userInterface 60 | ); 61 | 62 | api.userInterface().registerSuiteTab(suiteView.getTabCaption(), suiteView.getUiComponent()); 63 | 64 | userInterface.registerHttpRequestEditorProvider(editorCreationContext -> 65 | new RequestEditorView( 66 | presenters, 67 | rstaFactory, 68 | api.logging(), 69 | api.userInterface(), 70 | api.collaborator().defaultPayloadGenerator(), 71 | signerConfig, 72 | editorCreationContext.editorMode() != READ_ONLY, 73 | isProVersion 74 | ) 75 | ); 76 | 77 | userInterface.registerHttpResponseEditorProvider(editorCreationContext -> 78 | new ResponseEditorView( 79 | presenters, 80 | rstaFactory, 81 | api.logging(), 82 | api.userInterface(), 83 | api.collaborator().defaultPayloadGenerator(), 84 | signerConfig, 85 | editorCreationContext.editorMode() != READ_ONLY, 86 | isProVersion 87 | ) 88 | ); 89 | 90 | ProxyHttpMessageHandler proxyHttpMessageHandler = new ProxyHttpMessageHandler(proxyConfig, signerConfig, byteUtils); 91 | proxy.registerRequestHandler(proxyHttpMessageHandler); 92 | proxy.registerResponseHandler(proxyHttpMessageHandler); 93 | 94 | ProxyWsMessageHandler proxyWsMessageHandler = new ProxyWsMessageHandler(proxyConfig, signerConfig, byteUtils); 95 | proxy.registerWebSocketCreationHandler(proxyWebSocketCreation -> 96 | proxyWebSocketCreation.proxyWebSocket().registerProxyMessageHandler(proxyWsMessageHandler) 97 | ); 98 | 99 | if (isProVersion && proxyConfig.enablePassiveScan()) { 100 | ScannerHandler scannerHandler = new ScannerHandler(presenters, signerConfig); 101 | scanner.registerScanCheck(scannerHandler); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/hexcodearea/HexCodeAreaCommandHandler.java: -------------------------------------------------------------------------------- 1 | package one.d4d.signsaboteur.hexcodearea; 2 | 3 | import com.nimbusds.jose.util.Base64URL; 4 | import one.d4d.signsaboteur.forms.utils.FormUtils; 5 | import one.d4d.signsaboteur.utils.Utils; 6 | import org.exbin.deltahex.EditationMode; 7 | import org.exbin.deltahex.swing.CodeArea; 8 | import org.exbin.deltahex.swing.CodeAreaCaret; 9 | import org.exbin.deltahex.swing.DefaultCodeAreaCommandHandler; 10 | import org.exbin.utils.binary_data.EditableBinaryData; 11 | 12 | import java.awt.*; 13 | import java.awt.datatransfer.Clipboard; 14 | import java.awt.datatransfer.DataFlavor; 15 | import java.awt.datatransfer.UnsupportedFlavorException; 16 | import java.io.IOException; 17 | import java.util.HexFormat; 18 | 19 | /** 20 | * Class to handle copy and paste from a CodeArea to/from hexadecimal strings 21 | *

22 | * Modified from https://github.com/exbin/bined-lib-java/blob/5abc397f3091cf2471057e9c7a9943bb19deeb32/modules/bined-swt/src/main/java/org/exbin/bined/swt/basic/DefaultCodeAreaCommandHandler.java 23 | *

24 | * Copyright (C) ExBin Project 25 | *

26 | * Licensed under the Apache License, Version 2.0 (the "License"); 27 | * you may not use this file except in compliance with the License. 28 | * You may obtain a copy of the License at 29 | *

30 | * http://www.apache.org/licenses/LICENSE-2.0 31 | *

32 | * Unless required by applicable law or agreed to in writing, software 33 | * distributed under the License is distributed on an "AS IS" BASIS, 34 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 35 | * See the License for the specific language governing permissions and 36 | * limitations under the License. 37 | */ 38 | class HexCodeAreaCommandHandler extends DefaultCodeAreaCommandHandler { 39 | 40 | private final CodeArea codeArea; 41 | 42 | HexCodeAreaCommandHandler(CodeArea codeArea) { 43 | super(codeArea); 44 | this.codeArea = codeArea; 45 | } 46 | 47 | /** 48 | * Copy the contents of the CodeArea to the clipboard as a hexadecimal string 49 | */ 50 | @Override 51 | public void copy() { 52 | byte[] data = FormUtils.getCodeAreaData(codeArea); 53 | HexFormat hexFormat = HexFormat.of(); 54 | Utils.copyToClipboard(hexFormat.formatHex(data)); 55 | } 56 | 57 | /** 58 | * Cut the contents of the CodeArea to the clipboard as a hexadecimal string 59 | */ 60 | @Override 61 | public void cut() { 62 | byte[] data = FormUtils.getCodeAreaData(codeArea); 63 | super.cut(); 64 | HexFormat hexFormat = HexFormat.of(); 65 | Utils.copyToClipboard(hexFormat.formatHex(data)); 66 | } 67 | 68 | /** 69 | * Paste to the contents of the CodeArea from the clipboard, which can be binary, base64 or hexadecimal 70 | */ 71 | @Override 72 | public void paste() { 73 | Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); 74 | try { 75 | if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) { 76 | String clipboardData = (String) clipboard.getData(DataFlavor.stringFlavor); 77 | 78 | if (Utils.isHex(clipboardData)) { 79 | HexFormat hexFormat = HexFormat.of(); 80 | pasteByteArray(hexFormat.parseHex(clipboardData)); 81 | } else if (Utils.isBase64URL(clipboardData)) { 82 | pasteByteArray(Base64URL.from(clipboardData).decode()); 83 | } else { 84 | super.paste(); 85 | } 86 | } 87 | } catch (UnsupportedFlavorException | IOException e) { 88 | super.paste(); 89 | } 90 | } 91 | 92 | /** 93 | * Paste an array of bytes into the CodeArea 94 | * 95 | * @param bytes bytes to paste 96 | */ 97 | private void pasteByteArray(byte[] bytes) { 98 | CodeAreaCaret caret = codeArea.getCaret(); 99 | long dataPosition = caret.getDataPosition(); 100 | int length = bytes.length; 101 | if (this.codeArea.getEditationMode() == EditationMode.OVERWRITE) { 102 | long toRemove = length; 103 | if (dataPosition + toRemove > this.codeArea.getDataSize()) { 104 | toRemove = this.codeArea.getDataSize() - dataPosition; 105 | } 106 | 107 | ((EditableBinaryData) this.codeArea.getData()).remove(dataPosition, toRemove); 108 | } 109 | 110 | ((EditableBinaryData) this.codeArea.getData()).insert(this.codeArea.getDataPosition(), bytes); 111 | this.codeArea.notifyDataChanged(); 112 | caret.setCaretPosition(caret.getDataPosition() + (long) length); 113 | caret.setCodeOffset(0); 114 | this.codeArea.updateScrollBars(); 115 | this.codeArea.notifyCaretMoved(); 116 | this.codeArea.revealCursor(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/dialog/SignDialog.form: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |
92 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/dialog/EncryptionDialog.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
96 | -------------------------------------------------------------------------------- /src/main/resources/strings.properties: -------------------------------------------------------------------------------- 1 | # GUI 2 | button_clean=Clean 3 | button_load=Load 4 | button_remove=Remove 5 | # Burp Suite 6 | tool_name=SignSaboteur 7 | burp_proxy_comment=%d Tokens 8 | wordlist_view=Wordlist 9 | proxy_label=Proxy 10 | label_highlight_color=Highlight color: 11 | label_highlight=Highlight tokens within HTTP and WebSocket messages 12 | settings_view=Settings 13 | secrets_label=Secrets 14 | salts_label=Salts 15 | file_label=File: 16 | token_label=Token 17 | signed_token_label=Signed Token 18 | dangerous_tab_label=Dangerous 19 | dangerous_payload_lable=Payload 20 | dangerous_timestamp_label=Timestamp 21 | dangerous_signature_label=Signature 22 | express_tab_label=Express 23 | express_payload_label=Payload 24 | express_signature_label=Signature 25 | button_attack=Attack 26 | compact_json_label=JSON 27 | panelSeparatorLable=Separator 28 | attack_dialog_title=Brute force attack 29 | attack_dialog_options_label=Attack options 30 | attack_dialog_progress_bar_working=Working... 31 | attack_dialog_progress_bar_status=Total number of secrets: %d. Total number of salts %d. Attack mode %s 32 | error_title_no_secrets=Secrets not found! 33 | error_no_secrets=Please add secrets at settings tab 34 | error_title_no_salts=Salts not found! 35 | error_no_salts=Please add salts at settings tab 36 | editor_view_button_attack_known_keys=Known keys 37 | editor_view_button_attack_fast=Fast 38 | editor_view_button_attack_balanced=Balanced 39 | editor_view_button_attack_deep=Deep 40 | burp_editor_tab=SignSaboteur 41 | new_key_dialog_title=New signing key 42 | keys_confirm_overwrite=Confirm 43 | keys_confirm_overwrite_title=Overwrite key 44 | error_title_no_signing_keys=No signing keys found 45 | error_no_signing_keys=Try to brute force first or add keys manually 46 | sign_dialog_title=Sign Token 47 | encryption_dialog_title=Decrypt Token 48 | error_title_unable_to_sign=Unable to sign the token 49 | oauth_tab_label=OAuth 50 | oauth_payload_label=Payload 51 | oauth_timestamp_label=Timestamp 52 | oauth_parameter_label=Signed Parameter 53 | oauth_signature_label=Signature 54 | keys_label=Keys 55 | new_key_label=New key 56 | table_id=ID 57 | table_secret=Secret 58 | table_algorithm=Algorithm 59 | table_derivation=Derivation 60 | table_message_derivation=Message Derivation 61 | table_digest=Digest 62 | table_menu_delete=Delete 63 | table_menu_copy=Copy 64 | keys_confirm_delete_multiple=Are you sure you want to delete the selected keys? 65 | keys_confirm_delete_single=Are you sure you want to delete the selected key? 66 | keys_confirm_delete_title=Confirm 67 | express_parameter_label=Signed parameter 68 | express_copy_signature=Copy signature 69 | Tornado_tab_label=Tornado 70 | tornado_timestamp_label=Timestamp 71 | tornado_name_label=Name 72 | tornado_value_label=Value 73 | tornado_signature_label=Signature 74 | dangerous_payload_django=Django format 75 | dangerous_compress_payload=Compress 76 | button_brute_force=Brute force 77 | injection_attack_label=Select claims to inject into the payload 78 | checkbox_user_claims=User claims 79 | checkbox_user_wrapped_claims=Wrapped user claims 80 | checkbox_username_password_label=Username and password claims 81 | checkbox_flask_claims_label=Flask claims 82 | checkbox_express_claims=Express claims 83 | checkbox_account_user_claims=Account user claims 84 | checkbox_authenticated_claims_lable=Authenticated claims 85 | checkbox_user_access_token=User access_token 86 | panel_signing_keys_label=Signing keys 87 | checkbox_json_label=JSON 88 | button_sign_label=Sign 89 | text_area_syntax_label=text/json 90 | unknown_tab_lable=Unknown 91 | unknown_message_label=Message 92 | unknown_signature_label=Signature 93 | unknown_separator_label=Separator 94 | signer_settings_label=Enabled signers: 95 | key_dialog_digest=Digest 96 | key_dialog_message_derivation=Message derivation 97 | key_dialog_key_derivation=Key derivation 98 | key_dialog_algorythm=Algorithm 99 | button_add_label=Add 100 | new_word_dialog_title=New item 101 | new_word_item_label=New item 102 | ruby_tab_label=Ruby 103 | ruby_message_label=Message 104 | ruby_signature_label=Signature 105 | ruby_separator_label=Separator 106 | jsonwebsignature_tab_label=JWT 107 | jsonwebsignature_header_label=Header 108 | jsonwebsignature_payload_label=Payload 109 | jsonwebsignature_label=Signature 110 | jsonwebsignature_separator_label=Separator 111 | audit_issue_name=Weak HMAC secret 112 | audit_issue_details=Detected a signed string using a well-known HMAC secret key. The key used was [%s] 113 | 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. 114 | audit_issue_remediation=How to prevent:
  • Generate your secret key and salt with strong randomness
  • 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.
  • Use long and secure ID generator functions for user ID.
115 | NIMBUSDS_label=NIMBUSDS 116 | button_load_defaults=Load defaults 117 | tooltip_NIMBUSDS=Use Nimbusds library to parse Json Web tokens 118 | urlencoded_checkbox=URL Encode 119 | proxy_settings_enable_passwive_scan=Enable Passive scan 120 | button_ruby_decrypt=Decrypt 121 | syntaxis=text/json 122 | -------------------------------------------------------------------------------- /src/main/java/one/d4d/signsaboteur/forms/dialog/BruteForceAttackDialog.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
98 | --------------------------------------------------------------------------------