Skip to content

Commit

Permalink
## [0.0.6] - 2024-02-06
Browse files Browse the repository at this point in the history
### Added

- Ruby signed cookie Tab
- Multithreading feature is available for brute force attack

### Changed

- Brute force Deep mode supports Ruby, Ruby5 and Ruby truncated hashing key derivation
  • Loading branch information
Doge committed Feb 6, 2024
1 parent 7c65531 commit 4e62397
Show file tree
Hide file tree
Showing 55 changed files with 1,446 additions and 614 deletions.
26 changes: 25 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,40 @@
# Changelog

## [0.0.6] - 2024-02-06

### Added

- Ruby signed cookie Tab
- Multithreading feature is available for brute force attack

### Changed

- Brute force Deep mode supports Ruby, Ruby5 and Ruby truncated hashing key derivation

## [0.0.5] - 2024-01-07

### Added

- Manual secret and salt item creation

### Changed

- Brute force uses all known keys for all attacks mode by default

## [0.0.4] - 2024-01-07

### Changed

- Github actions

## [0.0.2] - 2024-01-05

### Added
- Unknown signed string tab.

- Unknown signed string tab.
- Enabled signers setting added to the main tab
- _Known keys_ brute force technic added to the Attack mode

### Changed

- Upgrade dependencies: org.json:json
76 changes: 51 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,46 @@
# Sessionless

Sessionless is a Burp Suite extension for editing, signing, verifying, attacking signed tokens: [Django TimestampSigner](https://docs.djangoproject.com/en/5.0/topics/signing/#verifying-timestamped-values), [ItsDangerous Signer](https://itsdangerous.palletsprojects.com/en/2.1.x/signer/), [Express cookie-session middleware](https://expressjs.com/en/resources/middleware/cookie-session.html), [OAuth2 Proxy](https://github.com/oauth2-proxy/oauth2-proxy), [Tornado’s signed cookies](https://www.tornadoweb.org/en/stable/guide/security.html) and Unknown signed string.
Sessionless is a Burp Suite extension for editing, signing, verifying, attacking signed
tokens: [Django TimestampSigner](https://docs.djangoproject.com/en/5.0/topics/signing/#verifying-timestamped-values), [ItsDangerous Signer](https://itsdangerous.palletsprojects.com/en/2.1.x/signer/), [Express cookie-session middleware](https://expressjs.com/en/resources/middleware/cookie-session.html), [OAuth2 Proxy](https://github.com/oauth2-proxy/oauth2-proxy), [Tornado’s signed cookies](https://www.tornadoweb.org/en/stable/guide/security.html), [Ruby Rails Signed cookies](https://api.rubyonrails.org/classes/ActiveSupport/MessageVerifier.html)
and Unknown signed string.

It provides automatic detection and in-line editing of token within HTTP requests/responses and WebSocket messages, signing of tokens and automation of brute force attacks against signed tokens implementations.
It provides automatic detection and in-line editing of token within HTTP requests/responses and WebSocket messages,
signing of tokens and automation of brute force attacks against signed tokens implementations.

It was inspired by Fraser Winterborn and Dolph Flynn JWT Token extension. The original source code can be found [here](https://github.com/blackberry/jwt-editor) and [here](https://github.com/DolphFlynn/jwt-editor).
It was inspired by Fraser Winterborn and Dolph Flynn JWT Token extension. The original source code can be
found [here](https://github.com/blackberry/jwt-editor) and [here](https://github.com/DolphFlynn/jwt-editor).

## Build Instructions

* 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.4.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 channel if there are issues with the latest stable release)
* This should place the JAR file `token-library-0.0.6.jar` within the `build/libs` directory
* This can be loaded into Burp by navigating to the `Extensions` tab, `Installed` sub-tab, clicking `Add` and loading
the JAR file
* This BApp is using the newer Montoya API so it's best to use the latest version of Burp (try the earlier adopter
channel if there are issues with the latest stable release)

## Wordlist View

<img src="gitimg/wordlist_view.png" width="600"/>

The `Wordlist View` allows to import secrets and salts list files. Extension has own prebuild dictionary lists. Most secrets are taken from [jwt-secrets](https://github.com/wallarm/jwt-secrets). As an option, [Flask-Unsign-Wordlist](https://github.com/Paradoxis/Flask-Unsign-Wordlist) can be used. Extension supports JSON strings format for special chars, to use it quot the secret string with `"`.
The `Wordlist View` allows to import secrets and salts list files. Extension has own prebuild dictionary lists. Most
secrets are taken from [jwt-secrets](https://github.com/wallarm/jwt-secrets). As an
option, [Flask-Unsign-Wordlist](https://github.com/Paradoxis/Flask-Unsign-Wordlist) can be used. Extension supports JSON
strings format for special chars, to use it quot the secret string with `"`.

## Editor View

<img src="gitimg/editor_flask.png" width="600"/>

The `Editor View` supports a number of signed tokens: Django, Dangerous, Flask, Express, OAuth2 and Tornado. It allows modification of the signed tokens at Burp Suite's HTTP Request/Response view in the Proxy, History and Repeater tools.
The `Editor View` supports a number of signed tokens: Django, Dangerous, Flask, Express, OAuth2 and Tornado. It allows
modification of the signed tokens at Burp Suite's HTTP Request/Response view in the Proxy, History and Repeater tools.

The Dangerous tab can be used for both, `Flask` and `Django` tokens, which are selected depending on whether a Dangerous or Django token is detected.
The Dangerous tab can be used for both, `Flask` and `Django` tokens, which are selected depending on whether a Dangerous
or Django token is detected.

The Unknown tab can be used to brute force unknown signed strings. Guessing mode works only with _Balanced_ brute force attack. It supports different message derivation technics, including:
The Unknown tab can be used to brute force unknown signed strings. Guessing mode works only with _Balanced_ and _Deep_
brute force attacks. It supports different message derivation technics, including:

* _None_ message will be used as is
* _CONCAT_ separator byte will be removed from the message and that new value will be used to calculate signature
Expand All @@ -43,30 +58,34 @@ A JSON text editor is provided to edit each component that contain JSON content:

A timestamp editor is provided to edit each component that contain it:

* Dangerous timestamp
* Dangerous timestamp
* Django timestamp
* OAuth2 Proxy timestamp
* Tornado timestamp

A hex editor is provided to all signed tokens, except Express signatures. __NOTE__ Express Tab doesn't support signature auto update yet. Please copy it manually to corresponding signature cookie.
A hex editor is provided to all signed tokens, except Express signatures. __NOTE__ Express Tab doesn't support signature
auto update yet. Please copy it manually to corresponding signature cookie.

### Sign
`Sign` presents a signing dialog that can be used to update the Signature by signing the token using a key from the `Keys View` that has signing capabilities

`Sign` presents a signing dialog that can be used to update the Signature by signing the token using a key from
the `Keys View` that has signing capabilities

<img src="gitimg/key_dialog.png" width="400"/>

### Brute force

`Brute force` will attempt to find secret key that was used for signature generation. If a secret key was found, a dialog will be presented.
`Brute force` will attempt to find secret key that was used for signature generation. If a secret key was found, a
dialog will be presented.

<img src="gitimg/brute_force_attack.png" width="600"/>

The `Brute force` option implements three types of attacks against signed tokens Signatures:

* _Known keys_ will use previously found secret keys only
* _Fast_ will use default hashing algorithm and key derivation
* _Balanced_ will use all known key derivation technics, except PBKDF2HMAC
* _Deep_ will use all key derivation technics, including PBKDF2HMAC
* _Balanced_ will use all known key derivation technics, except PBKDF2WithHmacSHA1, PBKDF2WithHmacSHA256
* _Deep_ will use all key derivation technics, including different types supported by Ruby Rails framework

### Attack

Expand All @@ -83,41 +102,48 @@ The `Attack` option implements eight well-known authorization attacks against si
* Authenticated claims
* User access_token

These are described in more detail below.
These are described in more detail below.

# Attacks

All of these attacks can be used together. Please bear in mind that extension doesn't support token payload modification at Attack mod, so your payload will be replaced with new one.
All of these attacks can be used together. Please bear in mind that extension doesn't support token payload modification
at Attack mod, so your payload will be replaced with new one.

### User claims

OpenID connect ID token format usually used by signing libraries to store information about authenticated user. Extension will generate placeholder for `admin` user ID token.
OpenID connect ID token format usually used by signing libraries to store information about authenticated user.
Extension will generate placeholder for `admin` user ID token.

### Wrapped user claims

Same as `User claims` attack but will put it into `user` JSON attribute.
Same as `User claims` attack but will put it into `user` JSON attribute.

### Username and password claims

Another common way to store user details is `username` and `password` JSON attributes. Extension will generate placeholder for `admin` user.
Another common way to store user details is `username` and `password` JSON attributes. Extension will generate
placeholder for `admin` user.

### Flask claims

Flask authenticated user session information if stored at client side should include `id` and `_id` and `user_id` or `_user_id` JSON attribute. Extension will generate session for the first user, usually admin.
Flask authenticated user session information if stored at client side should include `id` and `_id` and `user_id`
or `_user_id` JSON attribute. Extension will generate session for the first user, usually admin.

### Express claims

Express framework uses `passport` JSON attribute to store user details. Extension will generate placeholder for `admin` user.
Express framework uses `passport` JSON attribute to store user details. Extension will generate placeholder for `admin`
user.

### Account user claims

Some frameworks may use `account` wrapper to store information about authenticated user. For exploitation, it might be required to use `Authenticated claims` too.
Some frameworks may use `account` wrapper to store information about authenticated user. For exploitation, it might be
required to use `Authenticated claims` too.

### Authenticated claims

The Authenticated claims implements 12 well-known authorization flags.

### User access_token

The `User access_token` option generated JWT OpenID connect ID token signed with the same key and same hashing algorithm without any key derivation preformed.
The `User access_token` option generated JWT OpenID connect ID token signed with the same key and same hashing algorithm
without any key derivation preformed.

14 changes: 11 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@

plugins {
id 'java-library'
}

group = 'one.d4d'
version = '0.0.4'
version = '0.0.6'
description = 'token-signer'

repositories {
Expand Down Expand Up @@ -63,8 +62,17 @@ tasks.withType(JavaCompile).configureEach {
}
}

def integrationTest = tasks.register("integrationTest", Test) {
useJUnitPlatform {
includeTags "slow"
}
shouldRunAfter test
}

test {
useJUnitPlatform()
useJUnitPlatform {
excludeTags 'slow'
}
}

jar {
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/burp/config/BurpKeysModelPersistence.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import one.d4d.sessionless.utils.Utils;

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

public class BurpKeysModelPersistence {
static final String BURP_SETTINGS_NAME = "one.d4d.sessionless.keys";
Expand All @@ -29,15 +29,15 @@ public KeysModel loadOrCreateNew() {
Gson gson = GsonHelper.customGson;
KeysModel keysModel = gson.fromJson(json, KeysModel.class);
if (keysModel.getSaltsFilePath() != null) {
List<String> result = Utils.deserializeFile(new File(keysModel.getSaltsFilePath()));
Set<String> result = Utils.deserializeFile(new File(keysModel.getSaltsFilePath()));
if (result.isEmpty()) {
keysModel.setSalts(loadDefaultSalts());
} else {
keysModel.setSalts(result);
}
}
if (keysModel.getSecretsFilePath() != null) {
List<String> result = Utils.deserializeFile(new File(keysModel.getSecretsFilePath()));
Set<String> result = Utils.deserializeFile(new File(keysModel.getSecretsFilePath()));
if (result.isEmpty()) {
keysModel.setSecrets(loadDefaultSecrets());
} else {
Expand All @@ -55,11 +55,11 @@ public void save(KeysModel model) {
}


private List<String> loadDefaultSecrets() {
private Set<String> loadDefaultSecrets() {
return Utils.readResourceForClass("/secrets", this.getClass());
}

private List<String> loadDefaultSalts() {
private Set<String> loadDefaultSalts() {
return Utils.readResourceForClass("/salts", this.getClass());
}

Expand Down
45 changes: 26 additions & 19 deletions src/main/java/burp/config/KeysModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,15 @@
import com.google.gson.annotations.Expose;
import one.d4d.sessionless.keys.SecretKey;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.stream.IntStream;

public class KeysModel {
private final Object lock;
private final Object lockKeys = new Object();
@Expose
private final List<SecretKey> keys = new ArrayList<>();
private List<String> secrets = new ArrayList<>();
private List<String> salts = new ArrayList<>();
private Set<String> secrets = new HashSet<>();
private Set<String> salts = new HashSet<>();
@Expose
private String secretsFilePath;
@Expose
Expand All @@ -22,23 +20,22 @@ public class KeysModel {

public KeysModel() {
this.modelListener = new KeysModelListener.InertKeyModelListener();
this.lock = new Object();
}

public List<String> getSecrets() {
public Set<String> getSecrets() {
return secrets;
}

public void setSecrets(List<String> secrets) {
this.secrets = secrets;
public void setSecrets(Set<String> secrets) {
this.secrets.addAll(secrets);
}

public List<String> getSalts() {
public Set<String> getSalts() {
return salts;
}

public void setSalts(List<String> salts) {
this.salts = salts;
public void setSalts(Set<String> salts) {
this.salts.addAll(salts);
}

public String getSecretsFilePath() {
Expand Down Expand Up @@ -69,29 +66,39 @@ public void removeSecret(String s) {
secrets.remove(s);
}

public void addSecret(String s) {
secrets.add(s);
}

public void removeSalt(String s) {
salts.remove(s);
}

public void addSalt(String s) {
salts.add(s);
}

public Optional<SecretKey> getKey(String keyId) {
return keys.stream().filter(x -> keyId.equals(x.getID())).findFirst();
synchronized (lockKeys) {
return keys.stream().filter(x -> keyId.equals(x.getID())).findFirst();
}
}

public SecretKey getKey(int index) {
synchronized (lock) {
synchronized (lockKeys) {
return keys.get(index);
}
}

public void addKey(SecretKey key) {
synchronized (lock) {
synchronized (lockKeys) {
keys.add(key);
}
modelListener.notifyKeyInserted(key);
}

public List<SecretKey> getSigningKeys() {
synchronized (lock) {
synchronized (lockKeys) {
return keys;
}
}
Expand All @@ -103,7 +110,7 @@ public void addKeyModelListener(KeysModelListener modelListener) {
public void deleteKey(SecretKey keyId) {
int rowIndex;

synchronized (lock) {
synchronized (lockKeys) {
rowIndex = keys.indexOf(keyId);
keys.remove(keyId);
}
Expand All @@ -114,7 +121,7 @@ public void deleteKey(SecretKey keyId) {
}

public void deleteKeys(int[] indices) {
synchronized (lock) {
synchronized (lockKeys) {
List<SecretKey> idsToDelete = IntStream.of(indices).mapToObj(this::getKey).toList();

for (SecretKey id : idsToDelete) {
Expand Down
Loading

0 comments on commit 4e62397

Please sign in to comment.