Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alg #22

Merged
merged 3 commits into from
Feb 12, 2024
Merged

Alg #22

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ Additionally it facilitates several well-known attacks against JWT implementatio

## Changelog

**2.2**
- Allow resigning of JWS tokens during fuzzing (Thanks to [@BafDyce](https://github.com/BafDyce)).

**2.1.1 2024-01-22**
- Use split panes to improve JWT editor with small screens or large font sizes (Thanks to [@eldstal](https://github.com/eldstal)).

Expand Down
3 changes: 1 addition & 2 deletions src/main/java/burp/JWTEditorExtension.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import com.blackberry.jwteditor.view.rsta.RstaFactory;

import java.awt.*;
import java.util.Optional;

import static burp.api.montoya.core.BurpSuiteEdition.COMMUNITY_EDITION;
import static burp.api.montoya.core.BurpSuiteEdition.PROFESSIONAL;
Expand Down Expand Up @@ -106,7 +105,7 @@ public void initialize(MontoyaApi api) {
);

Intruder intruder = api.intruder();
intruder.registerPayloadProcessor(new JWSPayloadProcessor(burpConfig.intruderConfig(), Optional.of(api.logging()), Optional.of(keysModel)));
intruder.registerPayloadProcessor(new JWSPayloadProcessor(burpConfig.intruderConfig(), api.logging(), keysModel));

if (api.burpSuite().version().edition() != COMMUNITY_EDITION) {
api.scanner().registerInsertionPointProvider(new JWSHeaderInsertionPointProvider(burpConfig.scannerConfig()));
Expand Down
9 changes: 8 additions & 1 deletion src/main/java/burp/intruder/IntruderConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
package burp.intruder;

import static burp.intruder.FuzzLocation.PAYLOAD;
import static com.blackberry.jwteditor.utils.Constants.INTRUDER_NO_SIGNING_KEY_ID_LABEL;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;

public class IntruderConfig {
private String fuzzParameter;
Expand Down Expand Up @@ -53,13 +55,18 @@ public String signingKeyId() {

public void setSigningKeyId(String signingKeyId) {
this.signingKeyId = signingKeyId;
this.resign = resign && isSigningKeyIdValid();
}

public boolean resign() {
return resign;
}

public void setResign(boolean resign) {
this.resign = resign;
this.resign = resign && isSigningKeyIdValid();
}

private boolean isSigningKeyIdValid() {
return !INTRUDER_NO_SIGNING_KEY_ID_LABEL.equals(signingKeyId) && isNotEmpty(signingKeyId);
}
}
47 changes: 22 additions & 25 deletions src/main/java/burp/intruder/JWSPayloadProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import burp.api.montoya.intruder.PayloadProcessingResult;
import burp.api.montoya.intruder.PayloadProcessor;
import burp.api.montoya.logging.Logging;

import com.blackberry.jwteditor.exceptions.SigningException;
import com.blackberry.jwteditor.model.jose.JOSEObject;
import com.blackberry.jwteditor.model.jose.JWS;
Expand All @@ -22,11 +21,11 @@
import static com.blackberry.jwteditor.utils.Constants.INTRUDER_NO_SIGNING_KEY_ID_LABEL;

public class JWSPayloadProcessor implements PayloadProcessor {
Optional<Logging> logging;
private final Logging logging;
private final IntruderConfig intruderConfig;
private final Optional<KeysModel> keysModel;
private final KeysModel keysModel;

public JWSPayloadProcessor(IntruderConfig intruderConfig, Optional<Logging> logging, Optional<KeysModel> keysModel) {
public JWSPayloadProcessor(IntruderConfig intruderConfig, Logging logging, KeysModel keysModel) {
this.logging = logging;
this.intruderConfig = intruderConfig;
this.keysModel = keysModel;
Expand All @@ -53,36 +52,34 @@ public PayloadProcessingResult processPayload(PayloadData payloadData) {
? Base64URL.encode(targetJson.toString())
: jws.getEncodedPayload();

JWS updatedJws = this.createJWS(updatedHeader, updatedPayload, jws.getEncodedSignature());
JWS updatedJws = createJWS(updatedHeader, updatedPayload, jws.getEncodedSignature());
baseValue = ByteArray.byteArray(updatedJws.serialize());
}
}

return PayloadProcessingResult.usePayload(baseValue);
}

@Override
public String displayName() {
return "JWS payload processor";
}

private Optional<Key> loadKey() {
if (keysModel.isPresent()) {
String keyId = intruderConfig.signingKeyId();
// only try to load key if the input value is non-empty
if (keyId.trim().length() > 0 && keyId != INTRUDER_NO_SIGNING_KEY_ID_LABEL) {
Key key = keysModel.get().getKey(intruderConfig.signingKeyId());
if (key != null) {
return Optional.of(key);
} else {
this.logging.ifPresent(log -> log.logToError("Key with ID " + intruderConfig.signingKeyId() + " not found"));
}
}
} else {
this.logging.ifPresent(log -> log.logToOutput("No keysModel available. Will not be able to sign JWS"));
String keyId = intruderConfig.signingKeyId();

// only try to load key if the input value is non-empty
if (keyId == INTRUDER_NO_SIGNING_KEY_ID_LABEL || keyId == null || keyId.trim().isEmpty()) {
return Optional.empty();
}

return Optional.empty();
Key key = keysModel.getKey(intruderConfig.signingKeyId());

if (key == null) {
logging.logToError("Key with ID " + intruderConfig.signingKeyId() + " not found.");
}

return Optional.ofNullable(key);
}

@Override
public String displayName() {
return "JWS payload processor";
}

// Creates a JWS object from the given attributes. Signs the JWS if possible (i.e., available key selected in Intruder settings)
Expand All @@ -93,7 +90,7 @@ private JWS createJWS(Base64URL header, Base64URL payload, Base64URL originalSig
try {
result = Optional.of(JWSFactory.sign(key, key.getSigningAlgorithms()[0], header, payload));
} catch (SigningException ex) {
this.logging.ifPresent(log -> log.logToError("Failed to sign JWS: " + ex));
logging.logToError("Failed to sign JWS: " + ex);
}

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ public class KeysModel {
private final Map<String, Key> keys;
private final Object lock;

private List<KeysModelListener> modelListeners;
private final List<KeysModelListener> modelListeners;

public KeysModel() {
this.keys = new LinkedHashMap<>();
this.modelListeners = new ArrayList<KeysModelListener>();
this.modelListeners = new ArrayList<>();
this.lock = new Object();
}

Expand Down
41 changes: 41 additions & 0 deletions src/test/java/burp/api/montoya/logging/StubLogging.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package burp.api.montoya.logging;

import java.io.PrintStream;

public class StubLogging implements Logging {
public static final Logging LOGGING = new StubLogging();

@Override
public PrintStream output() {
return null;
}

@Override
public PrintStream error() {
return null;
}

@Override
public void logToOutput(String message) {
}

@Override
public void logToError(String message) {
}

@Override
public void raiseDebugEvent(String message) {
}

@Override
public void raiseInfoEvent(String message) {
}

@Override
public void raiseErrorEvent(String message) {
}

@Override
public void raiseCriticalEvent(String message) {
}
}
4 changes: 2 additions & 2 deletions src/test/java/burp/config/BurpConfigPersistenceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -240,11 +240,11 @@ private static Stream<Arguments> validIntruderConfigJson() {
null
),
arguments(
"{\"intruder_payload_processor_fuzz_location\":\"header\",\"intruder_payload_processor_parameter_name\":\"sub\",\"intruder_payload_processor_resign\":true}",
"{\"intruder_payload_processor_fuzz_location\":\"header\",\"intruder_payload_processor_parameter_name\":\"sub\",\"intruder_payload_processor_resign\":true, \"intruder_payload_processor_signing_key_id\": \"uuid\"}",
HEADER,
"sub",
true,
null
"uuid"
),
arguments(
"{\"intruder_payload_processor_fuzz_location\":\"header\",\"intruder_payload_processor_parameter_name\":\"sub\",\"intruder_payload_processor_signing_key_id\":\"131da5fb-8484-4717-b0d2-b79925978596\"}",
Expand Down
78 changes: 78 additions & 0 deletions src/test/java/burp/config/IntruderConfigTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
Author : Dolph Flynn

Copyright 2022 Dolph Flynn

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

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.
See the License for the specific language governing permissions and
limitations under the License.
*/

package burp.config;

import burp.intruder.IntruderConfig;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class IntruderConfigTest {
@Test
void givenNullKeyID_whenResignIsSetTrue_thenResignIsFalse() {
IntruderConfig config = new IntruderConfig();
config.setSigningKeyId(null);

config.setResign(true);

assertThat(config.resign()).isFalse();
}

@Test
void givenEmptyKeyID_whenResignIsSetTrue_thenResignIsFalse() {
IntruderConfig config = new IntruderConfig();
config.setSigningKeyId("");

config.setResign(true);

assertThat(config.resign()).isFalse();
}

@Test
void givenValidKeyID_whenResignIsSetTrue_thenResignIsTrue() {
IntruderConfig config = new IntruderConfig();
config.setSigningKeyId("keyID");

config.setResign(true);

assertThat(config.resign()).isTrue();
}

@Test
void givenResignIsSetTrue_whenNullKeyID_thenResignIsFalse() {
IntruderConfig config = new IntruderConfig();
config.setSigningKeyId("keyId");
config.setResign(true);

config.setSigningKeyId(null);

assertThat(config.resign()).isFalse();
}

@Test
void givenResignIsSetTrue_whenEmptyKeyID_thenResignIsFalse() {
IntruderConfig config = new IntruderConfig();
config.setSigningKeyId("keyId");
config.setResign(true);

config.setSigningKeyId("");

assertThat(config.resign()).isFalse();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,25 @@
import burp.api.montoya.intruder.FakePayloadProcessingResult;
import burp.api.montoya.intruder.PayloadData;
import burp.api.montoya.intruder.PayloadProcessingResult;
import burp.api.montoya.logging.Logging;

import com.blackberry.jwteditor.model.keys.KeysModel;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.stubbing.Answer;

import com.blackberry.jwteditor.model.keys.KeysModel;

import static burp.api.montoya.intruder.FakePayloadData.payloadData;
import static burp.api.montoya.intruder.PayloadProcessingAction.USE_PAYLOAD;
import static burp.api.montoya.logging.StubLogging.LOGGING;
import static burp.intruder.FuzzLocation.HEADER;
import static burp.intruder.FuzzLocation.PAYLOAD;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

import java.util.Optional;

@ExtendWith(MontoyaExtension.class)
class JWTPayloadProcessorTest {
class JWSPayloadProcessorTest {

@BeforeEach
void configureMocks() {
MontoyaObjectFactory factory = ObjectFactoryLocator.FACTORY;
Expand All @@ -45,9 +42,8 @@ void configureMocks() {
void givenBaseValueNotJWS_whenPayloadProcessed_thenPayloadLeftUnchanged() {
String baseValue = "isogeny";
PayloadData payloadData = payloadData().withBaseValue(baseValue).build();
Optional<Logging> emptyLogging = Optional.empty();
Optional<KeysModel> emptyKeysModel = Optional.empty();
JWSPayloadProcessor processor = new JWSPayloadProcessor(intruderConfig("role", PAYLOAD), emptyLogging, emptyKeysModel);
KeysModel keysModel = new KeysModel();
JWSPayloadProcessor processor = new JWSPayloadProcessor(intruderConfig("role", PAYLOAD), LOGGING, keysModel);

PayloadProcessingResult result = processor.processPayload(payloadData);

Expand All @@ -59,9 +55,8 @@ void givenBaseValueNotJWS_whenPayloadProcessed_thenPayloadLeftUnchanged() {
void givenBaseValueJWSAndFuzzParameterNotPresent_whenPayloadProcessed_thenPayloadLeftUnchanged() {
String baseValue = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
PayloadData payloadData = payloadData().withBaseValue(baseValue).build();
Optional<Logging> emptyLogging = Optional.empty();
Optional<KeysModel> emptyKeysModel = Optional.empty();
JWSPayloadProcessor processor = new JWSPayloadProcessor(intruderConfig("role", PAYLOAD), emptyLogging, emptyKeysModel);
KeysModel keysModel = new KeysModel();
JWSPayloadProcessor processor = new JWSPayloadProcessor(intruderConfig("role", PAYLOAD), LOGGING, keysModel);

PayloadProcessingResult result = processor.processPayload(payloadData);

Expand All @@ -73,9 +68,8 @@ void givenBaseValueJWSAndFuzzParameterNotPresent_whenPayloadProcessed_thenPayloa
void givenBaseValueJWSAndFuzzParameterPresentInWrongContext_whenPayloadProcessed_thenPayloadLeftUnchanged() {
String baseValue = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
PayloadData payloadData = payloadData().withBaseValue(baseValue).build();
Optional<Logging> emptyLogging = Optional.empty();
Optional<KeysModel> emptyKeysModel = Optional.empty();
JWSPayloadProcessor processor = new JWSPayloadProcessor(intruderConfig("alg", PAYLOAD), emptyLogging, emptyKeysModel);
KeysModel keysModel = new KeysModel();
JWSPayloadProcessor processor = new JWSPayloadProcessor(intruderConfig("alg", PAYLOAD), LOGGING, keysModel);

PayloadProcessingResult result = processor.processPayload(payloadData);

Expand All @@ -87,9 +81,8 @@ void givenBaseValueJWSAndFuzzParameterPresentInWrongContext_whenPayloadProcessed
void givenBaseValueJWSAndFuzzParameterPresentInHeader_whenPayloadProcessed_thenPayloadModified() {
String baseValue = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
PayloadData payloadData = payloadData().withBaseValue(baseValue).withCurrentPayload("RS256").build();
Optional<Logging> emptyLogging = Optional.empty();
Optional<KeysModel> emptyKeysModel = Optional.empty();
JWSPayloadProcessor processor = new JWSPayloadProcessor(intruderConfig("alg", HEADER), emptyLogging, emptyKeysModel);
KeysModel keysModel = new KeysModel();
JWSPayloadProcessor processor = new JWSPayloadProcessor(intruderConfig("alg", HEADER), LOGGING, keysModel);

PayloadProcessingResult result = processor.processPayload(payloadData);

Expand All @@ -101,9 +94,8 @@ void givenBaseValueJWSAndFuzzParameterPresentInHeader_whenPayloadProcessed_thenP
void givenBaseValueJWSAndFuzzParameterPresentInPayload_whenPayloadProcessed_thenPayloadModified() {
String baseValue = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
PayloadData payloadData = payloadData().withBaseValue(baseValue).withCurrentPayload("emanon").build();
Optional<Logging> emptyLogging = Optional.empty();
Optional<KeysModel> emptyKeysModel = Optional.empty();
JWSPayloadProcessor processor = new JWSPayloadProcessor(intruderConfig("name", PAYLOAD), emptyLogging, emptyKeysModel);
KeysModel keysModel = new KeysModel();
JWSPayloadProcessor processor = new JWSPayloadProcessor(intruderConfig("name", PAYLOAD), LOGGING, keysModel);

PayloadProcessingResult result = processor.processPayload(payloadData);

Expand Down
Loading