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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
use clap::{Parser, Subcommand};
use log::error;
use std::path::PathBuf;
mod crack;
mod decode;
mod encode;
mod mcp;
mod payload;
mod scan;
mod server;
mod shell;
mod verify;
mod version;
/// Parses command-line arguments in "key=value" format for custom header parameters
fn parse_key_value(s: &str) -> Result<(String, String), String> {
let pos = s
.find('=')
.ok_or_else(|| format!("invalid KEY=value: no `=` found in `{s}`"))?;
Ok((s[..pos].to_string(), s[pos + 1..].to_string()))
}
/// Command-line interface for the jwt-hack tool
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
/// Path to configuration file
#[arg(long, global = true)]
config: Option<PathBuf>,
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
/// Decodes a JWT token and displays its header, payload, and validation info
Decode {
/// JWT token to decode
token: String,
},
/// Encodes JSON data into a JWT token with specified algorithm and signing options
Encode {
/// JSON data to encode
json: String,
/// Secret key for HMAC algorithms (HS256, HS384, HS512)
#[arg(long)]
secret: Option<String>,
/// RSA, ECDSA, or EdDSA private key in PEM format for asymmetric algorithms
#[arg(long)]
private_key: Option<PathBuf>,
/// Algorithm to use
#[arg(long, default_value = "HS256")]
algorithm: String,
/// Use 'none' algorithm (no signature)
#[arg(long)]
no_signature: bool,
/// Add custom header parameter (format: key=value)
#[arg(long, value_parser = parse_key_value)]
header: Vec<(String, String)>,
/// Compress payload using DEFLATE compression (adds "zip":"DEF" header)
#[arg(long)]
compress: bool,
/// Create JWE (JSON Web Encryption) token instead of JWT
#[arg(long)]
jwe: bool,
},
/// Verifies a JWT token's signature and optionally validates its expiration claim
Verify {
/// JWT token to verify
token: String,
/// Secret key for HMAC algorithms (HS256, HS384, HS512)
#[arg(long)]
secret: Option<String>,
/// RSA, ECDSA, or EdDSA private key in PEM format for asymmetric algorithms
#[arg(long)]
private_key: Option<PathBuf>,
/// Validate expiration claim (exp)
#[arg(long)]
validate_exp: bool,
},
/// Attempts to crack a JWT token using dictionary or bruteforce methods
Crack {
/// JWT token to crack
token: String,
/// Cracking mode, you can use 'dict' or 'brute'
#[arg(short, long, default_value = "dict")]
mode: String,
/// Wordlist file (for dictionary attack)
#[arg(short, long)]
wordlist: Option<PathBuf>,
/// Character list (for bruteforce attack)
#[arg(long, default_value = "abcdefghijklmnopqrstuvwxyz0123456789")]
chars: String,
/// Character set preset (for bruteforce attack): az, AZ, aZ, 19, aZ19, ascii
#[arg(long)]
preset: Option<String>,
/// Concurrency level
#[arg(short, long, default_value = "20")]
concurrency: usize,
/// Max length (for bruteforce attack)
#[arg(long, default_value = "4")]
max: usize,
/// Use all CPU cores
#[arg(long)]
power: bool,
/// Show testing log
#[arg(long)]
verbose: bool,
},
/// Generates various JWT attack payloads for security testing
Payload {
/// JWT token to use for payload generation
token: String,
/// A trusted domain for jku&x5u (e.g google.com)
#[arg(long)]
jwk_trust: Option<String>,
/// An attack payload domain for jku&x5u (e.g hahwul.com)
#[arg(long)]
jwk_attack: Option<String>,
/// jku&x5u protocol (http/https)
#[arg(long, default_value = "https")]
jwk_protocol: String,
/// Target payload types (comma-separated: all,none,jku,x5u,alg_confusion,kid_sql,x5c,cty)
#[arg(long, default_value = "all")]
target: Option<String>,
},
/// Scans a JWT token for common vulnerabilities and security issues
Scan {
/// JWT token to scan
token: String,
/// Skip dictionary-based secret cracking
#[arg(long)]
skip_crack: bool,
/// Skip generating attack payloads
#[arg(long)]
skip_payloads: bool,
/// Wordlist file for checking weak secrets (default: common passwords)
#[arg(short, long)]
wordlist: Option<PathBuf>,
/// Maximum number of secrets to test during weak secret check
#[arg(long, default_value = "100")]
max_crack_attempts: usize,
},
/// Displays version information and project details
Version,
/// Runs jwt-hack as an MCP (Model Context Protocol) server
Mcp,
/// Starts an interactive shell for JWT operations
Shell,
/// Starts a REST API server for JWT operations
Server {
/// Host address to bind to
#[arg(long, default_value = "127.0.0.1")]
host: String,
/// Port number to listen on
#[arg(long, default_value = "3000")]
port: u16,
/// API key to secure the REST API (validated against X-API-KEY header)
#[arg(long)]
api_key: Option<String>,
},
}
/// Parses command-line arguments and executes the appropriate command
pub fn execute() {
let cli = Cli::parse();
// Load configuration
let _config = match crate::config::Config::load(cli.config.as_deref()) {
Ok(config) => config,
Err(e) => {
error!("Failed to load configuration: {}", e);
std::process::exit(1);
}
};
match &cli.command {
Some(Commands::Decode { token }) => {
decode::execute(token);
}
Some(Commands::Encode {
json,
secret,
private_key,
algorithm,
no_signature,
header,
compress,
jwe,
}) => {
encode::execute(
json,
secret.as_deref(),
private_key.as_ref(),
algorithm,
*no_signature,
header,
*compress,
*jwe,
);
}
Some(Commands::Verify {
token,
secret,
private_key,
validate_exp,
}) => {
verify::execute(
token,
secret.as_deref(),
private_key.as_ref(),
*validate_exp,
);
}
Some(Commands::Crack {
token,
mode,
wordlist,
chars,
preset,
concurrency,
max,
power,
verbose,
}) => {
crack::execute(
token,
mode,
wordlist,
chars,
preset,
*concurrency,
*max,
*power,
*verbose,
);
}
Some(Commands::Payload {
token,
jwk_trust,
jwk_attack,
jwk_protocol,
target,
}) => {
payload::execute(
token,
jwk_trust.as_deref(),
jwk_attack.as_deref(),
jwk_protocol,
target.as_deref(),
);
}
Some(Commands::Scan {
token,
skip_crack,
skip_payloads,
wordlist,
max_crack_attempts,
}) => {
scan::execute(
token,
*skip_crack,
*skip_payloads,
wordlist.as_ref(),
*max_crack_attempts,
);
}
Some(Commands::Version) => {
version::execute();
}
Some(Commands::Mcp) => {
mcp::execute();
}
Some(Commands::Shell) => {
shell::execute();
}
Some(Commands::Server {
host,
port,
api_key,
}) => {
let runtime = tokio::runtime::Runtime::new().unwrap();
if let Some(key) = api_key.as_deref() {
runtime.block_on(server::execute_with_api_key(host, *port, key));
} else {
runtime.block_on(server::execute(host, *port));
}
}
None => {
error!("No command specified. Use --help for usage information.");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_key_value_valid() {
let result = parse_key_value("key=value");
assert_eq!(result, Ok(("key".to_string(), "value".to_string())));
}
#[test]
fn test_parse_key_value_with_multiple_equals() {
let result = parse_key_value("key=value=more");
assert_eq!(result, Ok(("key".to_string(), "value=more".to_string())));
}
#[test]
fn test_parse_key_value_empty_key() {
let result = parse_key_value("=value");
assert_eq!(result, Ok(("".to_string(), "value".to_string())));
}
#[test]
fn test_parse_key_value_empty_value() {
let result = parse_key_value("key=");
assert_eq!(result, Ok(("key".to_string(), "".to_string())));
}
#[test]
fn test_parse_key_value_invalid_no_equals() {
let result = parse_key_value("keyvalue");
assert!(result.is_err());
assert_eq!(
result.unwrap_err(),
"invalid KEY=value: no `=` found in `keyvalue`"
);
}
#[test]
fn test_parse_key_value_invalid_empty() {
let result = parse_key_value("");
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "invalid KEY=value: no `=` found in ``");
}
}