diff --git a/CHANGELOG.md b/CHANGELOG.md index 9464065..9954df7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [0.0.7] - 2024-03-12 + +### Added + +- JWT Tab was added for testing purposes +- Default keys dictionary + +### Changed + +- Response body was removed from token parser logic due to performance issues + ## [0.0.6] - 2024-02-06 ### Added diff --git a/README.md b/README.md index 08d6f0d..93ca9ad 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ found [here](https://github.com/blackberry/jwt-editor) and [here](https://github * Ensure that Java JDK 17 or newer is installed * From root of project, run the command `./gradlew jar` -* This should place the JAR file `token-library-0.0.6.jar` within the `build/libs` directory +* This should place the JAR file `token-library-0.0.7.jar` within the `build/libs` directory * This can be loaded into Burp by navigating to the `Extensions` tab, `Installed` sub-tab, clicking `Add` and loading the JAR file * This BApp is using the newer Montoya API so it's best to use the latest version of Burp (try the earlier adopter diff --git a/build.gradle b/build.gradle index cde6cdf..4e4a92b 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { } group = 'one.d4d' -version = '0.0.6' +version = '0.0.7' description = 'token-signer' repositories { diff --git a/src/main/java/burp/config/SignerConfig.java b/src/main/java/burp/config/SignerConfig.java index 3cfc35e..e24d30d 100644 --- a/src/main/java/burp/config/SignerConfig.java +++ b/src/main/java/burp/config/SignerConfig.java @@ -1,6 +1,7 @@ package burp.config; import com.google.gson.annotations.Expose; +import one.d4d.sessionless.itsdangerous.crypto.Signers; import java.util.EnumSet; import java.util.Set; diff --git a/src/main/java/burp/config/Signers.java b/src/main/java/burp/config/Signers.java deleted file mode 100644 index 12d24ed..0000000 --- a/src/main/java/burp/config/Signers.java +++ /dev/null @@ -1,5 +0,0 @@ -package burp.config; - -public enum Signers { - DANGEROUS, EXPRESS, OAUTH, TORNADO, RUBY, UNKNOWN -} diff --git a/src/main/java/burp/proxy/ProxyHttpMessageHandler.java b/src/main/java/burp/proxy/ProxyHttpMessageHandler.java index 1c369ec..7b3ffd0 100644 --- a/src/main/java/burp/proxy/ProxyHttpMessageHandler.java +++ b/src/main/java/burp/proxy/ProxyHttpMessageHandler.java @@ -32,7 +32,7 @@ public ProxyRequestToBeSentAction handleRequestToBeSent(InterceptedRequest inter public ProxyResponseReceivedAction handleResponseReceived(InterceptedResponse interceptedResponse) { annotationsModifier.updateAnnotationsIfApplicable( interceptedResponse.annotations(), - interceptedResponse.toByteArray(), + interceptedResponse.toByteArray().subArray(0, interceptedResponse.bodyOffset()), interceptedResponse.cookies(), null); return ProxyResponseReceivedAction.continueWith(interceptedResponse); diff --git a/src/main/java/one/d4d/sessionless/forms/EditorTab.form b/src/main/java/one/d4d/sessionless/forms/EditorTab.form index 9039dd1..d6cc2a3 100644 --- a/src/main/java/one/d4d/sessionless/forms/EditorTab.form +++ b/src/main/java/one/d4d/sessionless/forms/EditorTab.form @@ -3,7 +3,7 @@ - + @@ -604,6 +604,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/one/d4d/sessionless/forms/EditorTab.java b/src/main/java/one/d4d/sessionless/forms/EditorTab.java index 9c47c0f..77de23d 100644 --- a/src/main/java/one/d4d/sessionless/forms/EditorTab.java +++ b/src/main/java/one/d4d/sessionless/forms/EditorTab.java @@ -4,6 +4,7 @@ import burp.api.montoya.ui.Selection; import burp.api.montoya.ui.editor.extension.ExtensionProvidedEditor; import burp.config.SignerConfig; +import one.d4d.sessionless.forms.utils.FormUtils; import one.d4d.sessionless.hexcodearea.HexCodeAreaFactory; import one.d4d.sessionless.presenter.EditorPresenter; import one.d4d.sessionless.presenter.PresenterStore; @@ -29,12 +30,13 @@ import static org.exbin.deltahex.EditationAllowed.READ_ONLY; public abstract class EditorTab implements ExtensionProvidedEditor { - public static final int TAB_DANGEROUSE = 0; + public static final int TAB_DANGEROUS = 0; public static final int TAB_EXPRESS = 1; public static final int TAB_OAUTH = 2; public static final int TAB_TORNADO = 3; public static final int TAB_RUBY = 4; - public static final int TAB_UNKNOWN = 5; + public static final int TAB_JWT = 5; + public static final int TAB_UNKNOWN = 6; private static final int MAX_JOSE_OBJECT_STRING_LENGTH = 68; final EditorPresenter presenter; private final RstaFactory rstaFactory; @@ -75,11 +77,17 @@ public abstract class EditorTab implements ExtensionProvidedEditor { private JPanel panelRubySeparator; private RSyntaxTextArea textAreaRubyMessage; private RSyntaxTextArea textAreaRubySignature; + private JPanel panelJSONWebSignature; + private JPanel panelJSONWebSeparator; + private RSyntaxTextArea textAreaJSONWebSignatureHeader; + private RSyntaxTextArea textAreaJSONWebSignaturePayload; private CodeArea codeAreaDangerousSignature; private CodeArea codeAreaDangerousSeparator; private CodeArea codeAreaOAuthSignature; private CodeArea codeAreaTornadoSignature; private CodeArea codeAreaRubySeparator; + private CodeArea codeAreaJWTSignature; + private CodeArea codeAreaJWTSeparator; private CodeArea codeAreaUnknownSeparator; EditorTab( @@ -133,6 +141,8 @@ public void changedUpdate(DocumentEvent e) { textAreaTornadoValue.getDocument().addDocumentListener(documentListener); textAreaRubyMessage.getDocument().addDocumentListener(documentListener); textAreaRubySignature.getDocument().addDocumentListener(documentListener); + textAreaJSONWebSignatureHeader.getDocument().addDocumentListener(documentListener); + textAreaJSONWebSignaturePayload.getDocument().addDocumentListener(documentListener); textAreaUnknownStringMessage.getDocument().addDocumentListener(documentListener); textAreaUnknownStringSignature.getDocument().addDocumentListener(documentListener); @@ -145,6 +155,8 @@ public void changedUpdate(DocumentEvent e) { codeAreaOAuthSignature.addDataChangedListener(presenter::componentChanged); codeAreaTornadoSignature.addDataChangedListener(presenter::componentChanged); codeAreaRubySeparator.addDataChangedListener(presenter::componentChanged); + codeAreaJWTSignature.addDataChangedListener(presenter::componentChanged); + codeAreaJWTSeparator.addDataChangedListener(presenter::componentChanged); codeAreaUnknownSeparator.addDataChangedListener(presenter::componentChanged); comboBoxSignedToken.addActionListener(e -> presenter.onSelectionChanged()); @@ -222,7 +234,7 @@ public void setExpressSignature(String text) { } public byte[] getOAuthSignature() { - return Utils.getCodeAreaData(codeAreaOAuthSignature); + return FormUtils.getCodeAreaData(codeAreaOAuthSignature); } public void setOAuthSignature(byte[] signature) { @@ -254,7 +266,7 @@ public void setTornadoTimestamp(String parameter) { } public byte[] getTornadoSignature() { - return Utils.getCodeAreaData(codeAreaTornadoSignature); + return FormUtils.getCodeAreaData(codeAreaTornadoSignature); } public void setTornadoSignature(byte[] signature) { @@ -262,7 +274,7 @@ public void setTornadoSignature(byte[] signature) { } public byte[] getDangerousSignature() { - return Utils.getCodeAreaData(codeAreaDangerousSignature); + return FormUtils.getCodeAreaData(codeAreaDangerousSignature); } public void setDangerousSignature(byte[] signature) { @@ -286,7 +298,7 @@ public void setOAuthTimestamp(String timestamp) { } public byte[] getDangerousSeparator() { - return Utils.getCodeAreaData(codeAreaDangerousSeparator); + return FormUtils.getCodeAreaData(codeAreaDangerousSeparator); } public void setDangerousSeparator(byte[] separator) { @@ -334,13 +346,45 @@ public void setRubySignature(String signature) { } public byte[] getRubySeparator() { - return Utils.getCodeAreaData(codeAreaRubySeparator); + return FormUtils.getCodeAreaData(codeAreaRubySeparator); } public void setRubySeparator(byte[] separator) { codeAreaRubySeparator.setData(new ByteArrayEditableData(separator)); } + public String getJWTHeader() { + return textAreaJSONWebSignatureHeader.getText(); + } + + public void setJWTHeader(String text) { + textAreaJSONWebSignatureHeader.setText(text); + } + + public String getJWTPayload() { + return textAreaJSONWebSignaturePayload.getText(); + } + + public void setJWTPayload(String text) { + textAreaJSONWebSignaturePayload.setText(text); + } + + public byte[] getJWTSignature() { + return FormUtils.getCodeAreaData(codeAreaJWTSignature); + } + + public void setJWTSignature(byte[] separator) { + codeAreaJWTSignature.setData(new ByteArrayEditableData(separator)); + } + + public byte[] getJWTSeparator() { + return FormUtils.getCodeAreaData(codeAreaJWTSeparator); + } + + public void setJWTSeparator(byte[] separator) { + codeAreaJWTSeparator.setData(new ByteArrayEditableData(separator)); + } + public String getUnknownMessage() { return textAreaUnknownStringMessage.getText(); } @@ -358,7 +402,7 @@ public void setUnknownSignature(String signature) { } public byte[] getUnknownSeparator() { - return Utils.getCodeAreaData(codeAreaUnknownSeparator); + return FormUtils.getCodeAreaData(codeAreaUnknownSeparator); } public void setUnknownSeparator(byte[] separator) { @@ -391,11 +435,18 @@ private void createUIComponents() { codeAreaTornadoSignature = hexCodeAreaFactory.build(); panelTornadoSignature.add(codeAreaTornadoSignature); - panelRubySeparator = new JPanel(new BorderLayout()); codeAreaRubySeparator = hexCodeAreaFactory.build(); panelRubySeparator.add(codeAreaRubySeparator); + panelJSONWebSignature = new JPanel(new BorderLayout()); + codeAreaJWTSignature = hexCodeAreaFactory.build(); + panelJSONWebSignature.add(codeAreaJWTSignature); + + panelJSONWebSeparator = new JPanel(new BorderLayout()); + codeAreaJWTSeparator = hexCodeAreaFactory.build(); + panelJSONWebSeparator.add(codeAreaJWTSeparator); + panelUnknownStringSeparator = new JPanel(new BorderLayout()); codeAreaUnknownSeparator = hexCodeAreaFactory.build(); panelUnknownStringSeparator.add(codeAreaUnknownSeparator); @@ -441,6 +492,8 @@ private void createUIComponents() { textAreaTornadoValue = rstaFactory.buildDefaultTextArea(); textAreaRubyMessage = rstaFactory.buildDefaultTextArea(); textAreaRubySignature = rstaFactory.buildDefaultTextArea(); + textAreaJSONWebSignatureHeader = rstaFactory.buildDefaultTextArea(); + textAreaJSONWebSignaturePayload = rstaFactory.buildDefaultTextArea(); textAreaUnknownStringMessage = rstaFactory.buildDefaultTextArea(); textAreaUnknownStringSignature = rstaFactory.buildDefaultTextArea(); } @@ -466,8 +519,8 @@ private void enableTabAtIndex(int index) { } public void setDangerousMode() { - mode = TAB_DANGEROUSE; - enableTabAtIndex(TAB_DANGEROUSE); + mode = TAB_DANGEROUS; + enableTabAtIndex(TAB_DANGEROUS); buttonBruteForceAttack.setEnabled(editable); buttonAttack.setEnabled(editable); textAreaDangerousPayload.setEditable(editable); @@ -536,6 +589,20 @@ public void setRubyMode() { codeAreaRubySeparator.setEditationAllowed(editationAllowed); } + public void setJWTMode() { + mode = TAB_JWT; + enableTabAtIndex(TAB_JWT); + buttonBruteForceAttack.setEnabled(editable); + buttonAttack.setEnabled(editable); + + textAreaJSONWebSignatureHeader.setEditable(editable); + textAreaJSONWebSignaturePayload.setEditable(editable); + + EditationAllowed editationAllowed = editable ? ALLOWED : READ_ONLY; + codeAreaJWTSignature.setEditationAllowed(editationAllowed); + codeAreaJWTSeparator.setEditationAllowed(editationAllowed); + } + public void setUnknownMode() { mode = TAB_UNKNOWN; enableTabAtIndex(TAB_UNKNOWN); diff --git a/src/main/java/one/d4d/sessionless/forms/SettingsView.form b/src/main/java/one/d4d/sessionless/forms/SettingsView.form index 7415ba0..3194c43 100644 --- a/src/main/java/one/d4d/sessionless/forms/SettingsView.form +++ b/src/main/java/one/d4d/sessionless/forms/SettingsView.form @@ -94,7 +94,7 @@ - + @@ -184,7 +184,7 @@ - + @@ -198,6 +198,22 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/java/one/d4d/sessionless/forms/SettingsView.java b/src/main/java/one/d4d/sessionless/forms/SettingsView.java index 74bc43f..74f3ab7 100644 --- a/src/main/java/one/d4d/sessionless/forms/SettingsView.java +++ b/src/main/java/one/d4d/sessionless/forms/SettingsView.java @@ -4,8 +4,8 @@ import burp.config.BurpConfig; import burp.config.ProxyConfig; import burp.config.SignerConfig; -import burp.config.Signers; import burp.proxy.HighlightColor; +import one.d4d.sessionless.itsdangerous.crypto.Signers; import javax.swing.*; import java.awt.*; @@ -29,6 +29,7 @@ public class SettingsView { private JCheckBox checkBoxEnableOAuthSignedString; private JCheckBox checkBoxEnableTornadoSignedString; private JCheckBox checkBoxEnableRubySignedString; + private JCheckBox checkBoxEnableJWT; public SettingsView(Window parent, BurpConfig burpConfig, UserInterface userInterface) { this.parent = parent; @@ -70,6 +71,10 @@ public SettingsView(Window parent, BurpConfig burpConfig, UserInterface userInte checkBoxEnableRubySignedString.addActionListener(e -> signerConfig.toggleEnabled(Signers.RUBY, checkBoxEnableRubySignedString.isSelected())); + checkBoxEnableJWT.setSelected(signerConfig.isEnabled(Signers.JWT)); + checkBoxEnableJWT.addActionListener(e -> + signerConfig.toggleEnabled(Signers.JWT, checkBoxEnableJWT.isSelected())); + checkBoxEnableUnknownSignedString.setSelected(signerConfig.isEnabled(Signers.UNKNOWN)); checkBoxEnableUnknownSignedString.addActionListener(e -> signerConfig.toggleEnabled(Signers.UNKNOWN, checkBoxEnableUnknownSignedString.isSelected())); diff --git a/src/main/java/one/d4d/sessionless/forms/dialog/AttackDialog.java b/src/main/java/one/d4d/sessionless/forms/dialog/AttackDialog.java index 4623fa9..393493d 100644 --- a/src/main/java/one/d4d/sessionless/forms/dialog/AttackDialog.java +++ b/src/main/java/one/d4d/sessionless/forms/dialog/AttackDialog.java @@ -10,7 +10,7 @@ import javax.swing.*; import java.awt.*; -import java.awt.event.*; +import java.awt.event.KeyEvent; import java.net.URL; import java.text.ParseException; import java.util.ArrayList; @@ -19,6 +19,7 @@ import static javax.swing.JOptionPane.WARNING_MESSAGE; public class AttackDialog extends AbstractDialog { + private final URL targetURL; private JPanel contentPane; private JButton buttonOK; private JButton buttonCancel; @@ -32,13 +33,13 @@ public class AttackDialog extends AbstractDialog { private JComboBox comboBoxSigningKey; private JCheckBox checkBoxUserAccessToken; private SignedToken tokenObject; - private final URL targetURL; + public AttackDialog( - Window parent, - ErrorLoggingActionListenerFactory actionListenerFactory, - List signingKeys, - URL targetURL, - SignedToken tokenObject) { + Window parent, + ErrorLoggingActionListenerFactory actionListenerFactory, + List signingKeys, + URL targetURL, + SignedToken tokenObject) { super(parent, "sign_dialog_title"); this.tokenObject = tokenObject; this.targetURL = targetURL; @@ -63,9 +64,9 @@ public AttackDialog( } - private JWTClaimsSet checkInput( SecretKey selectedKey) { + private JWTClaimsSet checkInput(SecretKey selectedKey) { List args = new ArrayList<>(); - if (checkBoxUserClaims.isSelected()){ + if (checkBoxUserClaims.isSelected()) { args.add(ClaimsUtils.generateUserClaim(targetURL)); } if (checkBoxUserWrappedClaims.isSelected()) { @@ -87,7 +88,7 @@ private JWTClaimsSet checkInput( SecretKey selectedKey) { args.add(ClaimsUtils.generateAuthenticatedClaims()); } if (checkBoxUserAccessToken.isSelected()) { - args.add(ClaimsUtils.generateUserAccessTokenPayload(targetURL,selectedKey)); + args.add(ClaimsUtils.generateUserAccessTokenPayload(targetURL, selectedKey)); } try { return ClaimsUtils.concatClaims(args); @@ -118,6 +119,8 @@ private void onOK() { s = new OauthProxyTokenSigner(selectedKey); } else if (tokenObject instanceof TornadoSignedToken) { s = new TornadoTokenSigner(selectedKey); + } else if (tokenObject instanceof JSONWebSignature) { + s = new JSONWebSignatureTokenSigner(selectedKey); } else if (tokenObject instanceof UnknownSignedToken) { s = new TokenSigner(selectedKey); } else { diff --git a/src/main/java/one/d4d/sessionless/forms/utils/FormUtils.java b/src/main/java/one/d4d/sessionless/forms/utils/FormUtils.java new file mode 100644 index 0000000..7b5ade1 --- /dev/null +++ b/src/main/java/one/d4d/sessionless/forms/utils/FormUtils.java @@ -0,0 +1,14 @@ +package one.d4d.sessionless.forms.utils; + +import org.exbin.deltahex.swing.CodeArea; +import org.exbin.utils.binary_data.BinaryData; + +public class FormUtils { + public static byte[] getCodeAreaData(CodeArea codeArea) { + BinaryData binaryData = codeArea.getData(); + int size = (int) binaryData.getDataSize(); + byte[] data = new byte[size]; + binaryData.copyToArray(0L, data, 0, size); + return data; + } +} diff --git a/src/main/java/one/d4d/sessionless/hexcodearea/HexCodeAreaCommandHandler.java b/src/main/java/one/d4d/sessionless/hexcodearea/HexCodeAreaCommandHandler.java index e82ef58..ff18f34 100644 --- a/src/main/java/one/d4d/sessionless/hexcodearea/HexCodeAreaCommandHandler.java +++ b/src/main/java/one/d4d/sessionless/hexcodearea/HexCodeAreaCommandHandler.java @@ -1,6 +1,7 @@ package one.d4d.sessionless.hexcodearea; import com.nimbusds.jose.util.Base64URL; +import one.d4d.sessionless.forms.utils.FormUtils; import one.d4d.sessionless.utils.Utils; import org.exbin.deltahex.EditationMode; import org.exbin.deltahex.swing.CodeArea; @@ -19,17 +20,17 @@ /** * Class to handle copy and paste from a CodeArea to/from hexadecimal strings - * + *

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

* Copyright (C) ExBin Project - * + *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -50,7 +51,7 @@ class HexCodeAreaCommandHandler extends DefaultCodeAreaCommandHandler { */ @Override public void copy() { - byte[] data = Utils.getCodeAreaData(codeArea); + byte[] data = FormUtils.getCodeAreaData(codeArea); Utils.copyToClipboard(encodeHex(data)); } @@ -59,13 +60,37 @@ public void copy() { */ @Override public void cut() { - byte[] data = Utils.getCodeAreaData(codeArea); + byte[] data = FormUtils.getCodeAreaData(codeArea); super.cut(); Utils.copyToClipboard(encodeHex(data)); } + /** + * Paste to the contents of the CodeArea from the clipboard, which can be binary, base64 or hexadecimal + */ + @Override + public void paste() { + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + try { + if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) { + String clipboardData = (String) clipboard.getData(DataFlavor.stringFlavor); + + if (Utils.isHex(clipboardData)) { + pasteByteArray(decodeHex(clipboardData)); + } else if (Utils.isBase64URL(clipboardData)) { + pasteByteArray(Base64URL.from(clipboardData).decode()); + } else { + super.paste(); + } + } + } catch (UnsupportedFlavorException | IOException e) { + super.paste(); + } + } + /** * Paste an array of bytes into the CodeArea + * * @param bytes bytes to paste */ private void pasteByteArray(byte[] bytes) { @@ -89,27 +114,4 @@ private void pasteByteArray(byte[] bytes) { this.codeArea.notifyCaretMoved(); this.codeArea.revealCursor(); } - - /** - * Paste to the contents of the CodeArea from the clipboard, which can be binary, base64 or hexadecimal - */ - @Override - public void paste() { - Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); - try { - if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) { - String clipboardData = (String) clipboard.getData(DataFlavor.stringFlavor); - - if (Utils.isHex(clipboardData)) { - pasteByteArray(decodeHex(clipboardData)); - } else if (Utils.isBase64URL(clipboardData)) { - pasteByteArray(Base64URL.from(clipboardData).decode()); - } else { - super.paste(); - } - } - } catch (UnsupportedFlavorException | IOException e) { - super.paste(); - } - } } diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/crypto/Signers.java b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/Signers.java new file mode 100644 index 0000000..9b49ffa --- /dev/null +++ b/src/main/java/one/d4d/sessionless/itsdangerous/crypto/Signers.java @@ -0,0 +1,5 @@ +package one.d4d.sessionless.itsdangerous.crypto; + +public enum Signers { + DANGEROUS, EXPRESS, OAUTH, TORNADO, RUBY, JWT, UNKNOWN +} diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/model/RubySignedToken.java b/src/main/java/one/d4d/sessionless/itsdangerous/model/RubySignedToken.java index f1d0eeb..353dc55 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/model/RubySignedToken.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/model/RubySignedToken.java @@ -1,7 +1,7 @@ package one.d4d.sessionless.itsdangerous.model; -import burp.config.Signers; import one.d4d.sessionless.itsdangerous.crypto.RubyTokenSigner; +import one.d4d.sessionless.itsdangerous.crypto.Signers; import one.d4d.sessionless.utils.HexUtils; public class RubySignedToken extends UnknownSignedToken { diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/model/SignedTokenObjectFinder.java b/src/main/java/one/d4d/sessionless/itsdangerous/model/SignedTokenObjectFinder.java index 755dd83..0b9351d 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/model/SignedTokenObjectFinder.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/model/SignedTokenObjectFinder.java @@ -1,10 +1,11 @@ package one.d4d.sessionless.itsdangerous.model; import burp.api.montoya.http.message.Cookie; +import burp.api.montoya.http.message.params.HttpParameterType; import burp.api.montoya.http.message.params.ParsedHttpParameter; import burp.config.SignerConfig; -import burp.config.Signers; import com.google.common.base.CharMatcher; +import one.d4d.sessionless.itsdangerous.crypto.Signers; import one.d4d.sessionless.utils.Utils; import org.apache.commons.lang3.StringUtils; @@ -21,20 +22,20 @@ public class SignedTokenObjectFinder { // Regular expressions for JWS/JWE extraction private static final String BASE64_REGEX = "[A-Za-z0-9-_]"; private static final String SEPARATOR_REGEX = "[.:!#$*;@|~]"; - private static final String SIGNER_REGEX = String.format("\\.?%s*%s+%s*%s+%s+", BASE64_REGEX, SEPARATOR_REGEX, BASE64_REGEX, SEPARATOR_REGEX, BASE64_REGEX); + private static final String SIGNER_REGEX = String.format("\\.?%s+%s+%s+%s+%s+", BASE64_REGEX, SEPARATOR_REGEX, BASE64_REGEX, SEPARATOR_REGEX, BASE64_REGEX); private static final Pattern SIGNER_OBJECT_PATTERN = Pattern.compile(String.format("(%s)", SIGNER_REGEX)); private static final String UNKNOWN_SIGNED_STRING_REGEXP = String.format("(%s*%s%s{26,86})", BASE64_REGEX, SEPARATOR_REGEX, BASE64_REGEX); private static final Pattern UNKNOWN_SIGNED_STRING_PATTERN = Pattern.compile(UNKNOWN_SIGNED_STRING_REGEXP); public static boolean containsSignedTokenObjects(SignerConfig signerConfig, String text, List cookies, List params) { List candidates = extractSignedTokenObjects(signerConfig, text, cookies, params); - return candidates.size() > 0; + return !candidates.isEmpty(); } public static List extractSignedTokenObjects(SignerConfig signerConfig, String text, List cookies, List params) { List signedTokensObjects = new ArrayList<>(); Map cookiesToHashMap = convertCookiesToHashMap(cookies); - Map paramsToHashMap = convertParamsToHashMap(params); + Map> paramsToHashMap = convertParamsToHashMap(params); if (signerConfig.isEnabled(Signers.DANGEROUS)) { Set tokenCandidates = findCandidateSignedTokenObjectsWithin(text); for (String candidate : tokenCandidates) { @@ -45,30 +46,36 @@ public static List extractSignedTokenObjects(SignerConfig si } if (signerConfig.isEnabled(Signers.EXPRESS)) { signedTokensObjects.addAll(parseExpressSignedParams(cookiesToHashMap)); - signedTokensObjects.addAll(parseExpressSignedParams(paramsToHashMap)); + paramsToHashMap.values().forEach(pairs -> signedTokensObjects.addAll(parseExpressSignedParams(pairs))); } if (signerConfig.isEnabled(Signers.OAUTH)) { cookiesToHashMap.forEach((name, value) -> { parseOauthProxySignedToken(name, value).ifPresent(v -> signedTokensObjects.add(new MutableSignedToken(value, v))); }); - paramsToHashMap.forEach((name, value) -> { - parseOauthProxySignedToken(name, value).ifPresent(v -> signedTokensObjects.add(new MutableSignedToken(value, v))); + paramsToHashMap.values().forEach(pairs -> { + pairs.forEach((name, value) -> { + parseOauthProxySignedToken(name, value).ifPresent(v -> signedTokensObjects.add(new MutableSignedToken(value, v))); + }); }); } if (signerConfig.isEnabled(Signers.TORNADO)) { cookiesToHashMap.forEach((name, value) -> { parseTornadoSignedToken(name, value).ifPresent(v -> signedTokensObjects.add(new MutableSignedToken(value, v))); }); - paramsToHashMap.forEach((name, value) -> { - parseTornadoSignedToken(name, value).ifPresent(v -> signedTokensObjects.add(new MutableSignedToken(value, v))); + paramsToHashMap.values().forEach(pairs -> { + pairs.forEach((name, value) -> { + parseTornadoSignedToken(name, value).ifPresent(v -> signedTokensObjects.add(new MutableSignedToken(value, v))); + }); }); } if (signerConfig.isEnabled(Signers.RUBY)) { cookiesToHashMap.forEach((name, value) -> { parseRubySignedToken(name, value).ifPresent(v -> signedTokensObjects.add(new MutableSignedToken(value, v))); }); - paramsToHashMap.forEach((name, value) -> { - parseRubySignedToken(name, value).ifPresent(v -> signedTokensObjects.add(new MutableSignedToken(value, v))); + paramsToHashMap.values().forEach(pairs -> { + pairs.forEach((name, value) -> { + parseRubySignedToken(name, value).ifPresent(v -> signedTokensObjects.add(new MutableSignedToken(value, v))); + }); }); } if (signerConfig.isEnabled(Signers.UNKNOWN)) { @@ -109,7 +116,8 @@ public static Set findCandidateUnknownSignedStringWithin(String text) { public static Optional parseToken(String candidate) { Optional dst = parseDjangoSignedToken(candidate); - return dst.isPresent() ? dst : parseDangerousSignedToken(candidate); + dst = dst.isPresent() ? dst : parseDangerousSignedToken(candidate); + return dst.isPresent() ? dst : parseJSONWebSignature(candidate); } private static List parseParameters(List params) { @@ -120,13 +128,13 @@ private static List parseCookies(List params) { return parseSignedTokenWithinCookies(params); } - private static Map convertParamsToHashMap(List params) { + private static Map> convertParamsToHashMap(List params) { if (params == null) return new HashMap<>(); - return params.stream() - .collect(Collectors.toMap( + return params.stream().collect(Collectors.groupingBy(ParsedHttpParameter::type, + Collectors.toMap( ParsedHttpParameter::name, - ParsedHttpParameter::value) - ); + ParsedHttpParameter::value, + (key1, key2) -> key1))); } private static Map convertCookiesToHashMap(List cookies) { @@ -454,7 +462,7 @@ public static Optional parseJSONWebSignature(String text) { } catch (Exception e) { return Optional.empty(); } - SignedToken t = new JSONWebSignature(header, body, signature, new byte[]{(byte) separator}); + JSONWebSignature t = new JSONWebSignature(header, body, signature, new byte[]{(byte) separator}); return Optional.of(t); } diff --git a/src/main/java/one/d4d/sessionless/itsdangerous/model/UnknownSignedToken.java b/src/main/java/one/d4d/sessionless/itsdangerous/model/UnknownSignedToken.java index 1ad981a..66c9a66 100644 --- a/src/main/java/one/d4d/sessionless/itsdangerous/model/UnknownSignedToken.java +++ b/src/main/java/one/d4d/sessionless/itsdangerous/model/UnknownSignedToken.java @@ -1,11 +1,11 @@ package one.d4d.sessionless.itsdangerous.model; -import burp.config.Signers; import com.nimbusds.jwt.JWTClaimsSet; import one.d4d.sessionless.itsdangerous.Algorithms; import one.d4d.sessionless.itsdangerous.Derivation; import one.d4d.sessionless.itsdangerous.MessageDerivation; import one.d4d.sessionless.itsdangerous.MessageDigestAlgorithm; +import one.d4d.sessionless.itsdangerous.crypto.Signers; import one.d4d.sessionless.itsdangerous.crypto.TokenSigner; public class UnknownSignedToken extends SignedToken { diff --git a/src/main/java/one/d4d/sessionless/presenter/EditorModel.java b/src/main/java/one/d4d/sessionless/presenter/EditorModel.java index 32ee1cd..af9e182 100644 --- a/src/main/java/one/d4d/sessionless/presenter/EditorModel.java +++ b/src/main/java/one/d4d/sessionless/presenter/EditorModel.java @@ -12,9 +12,8 @@ import java.util.concurrent.atomic.AtomicInteger; class EditorModel { - private final SignerConfig signerConfig; private static final String SERIALIZED_OBJECT_FORMAT_STRING = "%d - %s"; - + private final SignerConfig signerConfig; private final List mutableSerializedObjects = new ArrayList<>(); private final Object lock = new Object(); @@ -28,7 +27,7 @@ void setMessage(String content, List cookies, List synchronized (lock) { message = content; mutableSerializedObjects.clear(); - mutableSerializedObjects.addAll(SignedTokenObjectFinder.extractSignedTokenObjects(signerConfig,content,cookies,params)); + mutableSerializedObjects.addAll(SignedTokenObjectFinder.extractSignedTokenObjects(signerConfig, content, cookies, params)); } } diff --git a/src/main/java/one/d4d/sessionless/presenter/EditorPresenter.java b/src/main/java/one/d4d/sessionless/presenter/EditorPresenter.java index 1edddbf..0ca75f8 100644 --- a/src/main/java/one/d4d/sessionless/presenter/EditorPresenter.java +++ b/src/main/java/one/d4d/sessionless/presenter/EditorPresenter.java @@ -177,6 +177,21 @@ private void setRuby(RubySignedToken token) { view.setRubySeparator(token.getSeparator()); } + private JSONWebSignature getJSONWebSignature() { + String header = Base64.getUrlEncoder().withoutPadding().encodeToString(view.getJWTHeader().getBytes()); + String payload = Base64.getUrlEncoder().withoutPadding().encodeToString(view.getJWTPayload().getBytes()); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(view.getJWTSignature()); + byte[] separator = view.getJWTSeparator().length == 0 ? new byte[]{46} : view.getJWTSeparator(); + return new JSONWebSignature(header, payload, signature, separator); + } + + private void setJSONWebSignature(JSONWebSignature token) { + view.setJWTHeader(token.getHeader()); + view.setJWTPayload(token.getPayload()); + view.setJWTSignature(token.getSignature()); + view.setJWTSeparator(token.getSeparator()); + } + private UnknownSignedToken getUnknown() { String message = view.getUnknownMessage(); String signature = view.getUnknownSignature(); @@ -197,11 +212,12 @@ public void componentChanged() { SignedToken tokenObject; switch (view.getMode()) { - case EditorTab.TAB_DANGEROUSE -> tokenObject = getDangerous(); + case EditorTab.TAB_DANGEROUS -> tokenObject = getDangerous(); case EditorTab.TAB_EXPRESS -> tokenObject = getExpress(); case EditorTab.TAB_OAUTH -> tokenObject = getOAuth(); case EditorTab.TAB_TORNADO -> tokenObject = getTornado(); case EditorTab.TAB_RUBY -> tokenObject = getRuby(); + case EditorTab.TAB_JWT -> tokenObject = getJSONWebSignature(); default -> tokenObject = getUnknown(); } mutableSignedTokenObject.setModified(tokenObject); @@ -229,6 +245,9 @@ public void onSelectionChanged() { } else if (tokenObject instanceof RubySignedToken) { view.setRubyMode(); setRuby((RubySignedToken) tokenObject); + } else if (tokenObject instanceof JSONWebSignature) { + view.setJWTMode(); + setJSONWebSignature((JSONWebSignature) tokenObject); } else if (tokenObject instanceof UnknownSignedToken) { view.setUnknownMode(); setUnknown((UnknownSignedToken) tokenObject); @@ -254,7 +273,7 @@ private void attackDialog() { MutableSignedToken mutableJoseObject = model.getSignedTokenObject(view.getSelectedSignedTokenObjectIndex()); SignedToken tokenObject = mutableJoseObject.getModified(); - if (keysPresenter.getSigningKeys().size() == 0) { + if (keysPresenter.getSigningKeys().isEmpty()) { messageDialogFactory.showWarningDialog("error_title_no_signing_keys", "error_no_signing_keys"); return; } @@ -285,6 +304,9 @@ private void attackDialog() { } else if (signed instanceof RubySignedToken) { view.setRubyMode(); setRuby((RubySignedToken) signed); + } else if (signed instanceof JSONWebSignature) { + view.setJWTMode(); + setJSONWebSignature((JSONWebSignature) signed); } else if (signed instanceof UnknownSignedToken) { view.setUnknownMode(); setUnknown((UnknownSignedToken) signed); @@ -328,6 +350,9 @@ private void signingDialog() { } else if (signed instanceof RubySignedToken) { view.setRubyMode(); setRuby((RubySignedToken) signed); + } else if (signed instanceof JSONWebSignature) { + view.setJWTMode(); + setJSONWebSignature((JSONWebSignature) signed); } else if (signed instanceof UnknownSignedToken) { view.setUnknownMode(); setUnknown((UnknownSignedToken) signed); @@ -344,17 +369,17 @@ public void onAttackClicked(Attack mode) { Set attackKeys = keysPresenter.getSecrets(); Set attackSalts = keysPresenter.getSalts(); - if (attackKeys.size() == 0) { + if (attackKeys.isEmpty()) { messageDialogFactory.showWarningDialog("error_title_no_secrets", "error_no_secrets"); return; } - if (attackSalts.size() == 0) { + if (attackSalts.isEmpty()) { messageDialogFactory.showWarningDialog("error_title_no_salts", "error_no_salts"); return; } - if (keysPresenter.getSigningKeys().size() == 0 && mode == Attack.KNOWN) { + if (keysPresenter.getSigningKeys().isEmpty() && mode == Attack.KNOWN) { messageDialogFactory.showWarningDialog("error_title_no_signing_keys", "error_no_signing_keys"); return; } diff --git a/src/main/java/one/d4d/sessionless/utils/Utils.java b/src/main/java/one/d4d/sessionless/utils/Utils.java index 8634050..c37103c 100644 --- a/src/main/java/one/d4d/sessionless/utils/Utils.java +++ b/src/main/java/one/d4d/sessionless/utils/Utils.java @@ -1,13 +1,10 @@ package one.d4d.sessionless.utils; -import burp.api.montoya.http.message.MimeType; import com.google.common.primitives.Ints; import com.google.gson.Gson; import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; import org.apache.commons.lang3.StringUtils; -import org.exbin.deltahex.swing.CodeArea; -import org.exbin.utils.binary_data.BinaryData; import java.awt.*; import java.awt.datatransfer.Clipboard; @@ -27,7 +24,6 @@ import java.util.zip.Inflater; public class Utils { - public static final Set SUPPORTED_MIMETYPES = Set.of(MimeType.HTML, MimeType.PLAIN_TEXT, MimeType.JSON, MimeType.XML, MimeType.YAML); public static final int BRUTE_FORCE_CHUNK_SIZE = 4096; public static final int WORDLIST_ONE_CHAR = 256; public static final int WORDLIST_TWO_CHAR = 65536; @@ -383,13 +379,6 @@ public static String compactJSON(String json) { return stringBuilder.toString(); } - public static byte[] getCodeAreaData(CodeArea codeArea) { - BinaryData binaryData = codeArea.getData(); - int size = (int) binaryData.getDataSize(); - byte[] data = new byte[size]; - binaryData.copyToArray(0L, data, 0, size); - return data; - } public static Set deserializeFile(File f) { Set result = new HashSet<>(); diff --git a/src/main/resources/keys b/src/main/resources/keys new file mode 100644 index 0000000..f082c43 --- /dev/null +++ b/src/main/resources/keys @@ -0,0 +1,17 @@ +{"keyId":"Flask","secret":"secret","salt":"salt","separator":".","digestMethod":"1","keyDerivation":"5","messageDerivation":"0","messageDigestAlgorythm":"4"} +{"keyId":"Django","secret":"secret","salt":"django.contrib.sessions.backends.signed_cookies","separator":":","digestMethod":"1","keyDerivation":"4","messageDerivation":"0","messageDigestAlgorythm":"2"} +{"keyId":"Express1","secret":"key1","salt":"salt","separator":".","digestMethod":"4","keyDerivation":"2","messageDerivation":"0","messageDigestAlgorythm":"1"} +{"keyId":"Express2","secret":"key2","salt":"salt","separator":".","digestMethod":"4","keyDerivation":"2","messageDerivation":"0","messageDigestAlgorythm":"1"} +{"keyId":"ExpressMagic","secret":"magic","salt":"salt","separator":".","digestMethod":"4","keyDerivation":"2","messageDerivation":"0","messageDigestAlgorythm":"1"} +{"keyId":"OAuth2","secret":"j76h5PEMx3FIGr3caArJ5g\u003d\u003d","salt":"salt","separator":"|","digestMethod":"3","keyDerivation":"1","messageDerivation":"0","messageDigestAlgorythm":"7"} +{"keyId":"Tornado","secret":"secret","salt":"","separator":"|","digestMethod":"3","keyDerivation":"6","messageDerivation":"0","messageDigestAlgorythm":"7"} +{"keyId":"JSONWebSignature","secret":"secret","salt":"","separator":".","digestMethod":"3","keyDerivation":"6","messageDerivation":"0","messageDigestAlgorythm":"7"} +{"keyId":"Ruby","secret":"aeb977de013ade650b97e0aa5246813591104017871a7753fe186e9634c9129b367306606878985c759ca4fddd17d955207011bb855ef01ed414398b4ac8317b","salt":"signed encrypted cookie","separator":"--","digestMethod":"1","keyDerivation":"7","messageDerivation":"0","messageDigestAlgorythm":"7"} +{"keyId":"Ruby5","secret":"aeb977de013ade650b97e0aa5246813591104017871a7753fe186e9634c9129b367306606878985c759ca4fddd17d955207011bb855ef01ed414398b4ac8317b","salt":"signed encrypted cookie","separator":"--","digestMethod":"1","keyDerivation":"8","messageDerivation":"0","messageDigestAlgorythm":"7"} +{"keyId":"RubyTruncated","secret":"aeb977de013ade650b97e0aa5246813591104017871a7753fe186e9634c9129b367306606878985c759ca4fddd17d955207011bb855ef01ed414398b4ac8317b","salt":"signed encrypted cookie","separator":"--","digestMethod":"1","keyDerivation":"9","messageDerivation":"0","messageDigestAlgorythm":"7"} +{"keyId":"Superset","secret":"thisISaSECRET_1234","salt":"cookie-session","separator":".","digestMethod":"1","keyDerivation":"5","messageDerivation":"0","messageDigestAlgorythm":"4"} +{"keyId":"Superset2","secret":"CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET","salt":"cookie-session","separator":".","digestMethod":"1","keyDerivation":"5","messageDerivation":"0","messageDigestAlgorythm":"4"} +{"keyId":"Superset3","secret":"YOUR_OWN_RANDOM_GENERATED_SECRET_KEY","salt":"cookie-session","separator":".","digestMethod":"1","keyDerivation":"5","messageDerivation":"0","messageDigestAlgorythm":"4"} +{"keyId":"Superset4","secret":"TEST_NON_DEV_SECRET","salt":"cookie-session","separator":".","digestMethod":"1","keyDerivation":"5","messageDerivation":"0","messageDigestAlgorythm":"4"} +{"keyId":"Superset5","secret":"\u0002\u0001thisismyscretkey\u0001\u0002\\e\\y\\y\\h","salt":"cookie-session","separator":".","digestMethod":"1","keyDerivation":"5","messageDerivation":"0","messageDigestAlgorythm":"4"} +{"keyId":"Redash","secret":"c292a0a3aa32397cdb050e233733900f","salt":"cookie-session","separator":".","digestMethod":"1","keyDerivation":"5","messageDerivation":"0","messageDigestAlgorythm":"4"} diff --git a/src/main/resources/secrets b/src/main/resources/secrets index 58469bd..17c5cfe 100644 --- a/src/main/resources/secrets +++ b/src/main/resources/secrets @@ -29,6 +29,8 @@ "!payment$ecret!" "!secReT$123*" "#######" +"keys" +"keykeys" "#$Vby8o5ey+vbaio7vzw63v)273eyowhsukdt36iwu" "#RwwwWXXUu!pPMG^AtWhDbXh?2qXum@CGqaAF8QaYfnqMyh+r#_Eu!RQ&zftFucDf5BH#7%!&bVfQAVJxP3b-?tEvdmHBNxK!LgjW!ekeG9AP^=gcM" "#ZhiMaKaiMen1234@*" diff --git a/src/main/resources/strings.properties b/src/main/resources/strings.properties index 132bb4b..0dc8487 100644 --- a/src/main/resources/strings.properties +++ b/src/main/resources/strings.properties @@ -101,4 +101,9 @@ new_word_item_label=New item ruby_tab_label=Ruby ruby_message_label=Message ruby_signature_label=Signature -ruby_separator_label=Separator \ No newline at end of file +ruby_separator_label=Separator +jsonwebsignature_tab_label=JWT +jsonwebsignature_header_label=Header +jsonwebsignature_payload_label=Payload +jsonwebsignature_label=Signature +jsonwebsignature_separator_label=Separator \ No newline at end of file diff --git a/src/test/java/ExpressSignedCookieTest.java b/src/test/java/ExpressSignedCookieTest.java index 4c3c924..0348b9a 100644 --- a/src/test/java/ExpressSignedCookieTest.java +++ b/src/test/java/ExpressSignedCookieTest.java @@ -1,15 +1,16 @@ import burp.api.montoya.http.message.Cookie; +import one.d4d.sessionless.itsdangerous.Attack; +import one.d4d.sessionless.itsdangerous.BruteForce; import one.d4d.sessionless.itsdangerous.crypto.ExpressTokenSigner; import one.d4d.sessionless.itsdangerous.model.ExpressSignedToken; import one.d4d.sessionless.itsdangerous.model.MutableSignedToken; import one.d4d.sessionless.itsdangerous.model.SignedTokenObjectFinder; +import one.d4d.sessionless.keys.SecretKey; import one.d4d.sessionless.utils.TestCookie; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; public class ExpressSignedCookieTest { @Test @@ -26,15 +27,23 @@ void OauthProxyParserTest() { cookies.add(payloadCookie); cookies.add(signatureCookie); Assertions.assertDoesNotThrow(() -> { - Optional token = + Optional optionalSignedToken = SignedTokenObjectFinder.parseSignedTokenWithinCookies(cookies) .stream() .map(MutableSignedToken::getModified) .map(ExpressSignedToken.class::cast) .findFirst(); - if (token.isPresent()) { - token.get().setSigner(s); - token.get().unsign(); + if (optionalSignedToken.isPresent()) { + ExpressSignedToken token = optionalSignedToken.get(); + token.setSigner(s); + token.unsign(); + final Set secrets = new HashSet<>(List.of("key1")); + final Set salts = new HashSet<>(List.of("salt")); + final List knownKeys = new ArrayList<>(); + + BruteForce bf = new BruteForce(secrets, salts, knownKeys, Attack.Balanced, token); + SecretKey sk = bf.parallel(); + Assertions.assertNotNull(sk); } else throw new Exception("Missed cookie"); }); }