jwt-security-scanner 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jwt_security_scanner-1.0.0/PKG-INFO +186 -0
- jwt_security_scanner-1.0.0/README.md +159 -0
- jwt_security_scanner-1.0.0/jwt_scanner/__init__.py +5 -0
- jwt_security_scanner-1.0.0/jwt_scanner/scanner.py +720 -0
- jwt_security_scanner-1.0.0/jwt_security_scanner.egg-info/PKG-INFO +186 -0
- jwt_security_scanner-1.0.0/jwt_security_scanner.egg-info/SOURCES.txt +11 -0
- jwt_security_scanner-1.0.0/jwt_security_scanner.egg-info/dependency_links.txt +1 -0
- jwt_security_scanner-1.0.0/jwt_security_scanner.egg-info/entry_points.txt +2 -0
- jwt_security_scanner-1.0.0/jwt_security_scanner.egg-info/requires.txt +4 -0
- jwt_security_scanner-1.0.0/jwt_security_scanner.egg-info/top_level.txt +1 -0
- jwt_security_scanner-1.0.0/pyproject.toml +3 -0
- jwt_security_scanner-1.0.0/setup.cfg +4 -0
- jwt_security_scanner-1.0.0/setup.py +31 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jwt-security-scanner
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A tool to scan JWT tokens for security vulnerabilities
|
|
5
|
+
Home-page: https://github.com/tabassumfathima28/JWT-Security-Scanner
|
|
6
|
+
Author: Tabassum Fathima
|
|
7
|
+
Author-email: tabassumfathima2812@gmail.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Security
|
|
12
|
+
Requires-Python: >=3.7
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: pyjwt>=2.0.0
|
|
15
|
+
Requires-Dist: requests>=2.0.0
|
|
16
|
+
Requires-Dist: colorama>=0.4.0
|
|
17
|
+
Requires-Dist: rich>=10.0.0
|
|
18
|
+
Dynamic: author
|
|
19
|
+
Dynamic: author-email
|
|
20
|
+
Dynamic: classifier
|
|
21
|
+
Dynamic: description
|
|
22
|
+
Dynamic: description-content-type
|
|
23
|
+
Dynamic: home-page
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
# JWT Security Scanner 🔐
|
|
29
|
+
|
|
30
|
+
> A Python-based command-line tool that automatically scans JWT tokens for security vulnerabilities and generates professional security reports.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## What is This Tool?
|
|
35
|
+
|
|
36
|
+
JWT (JSON Web Token) is used by millions of websites to handle user authentication. But poorly configured JWT tokens can be exploited by hackers to bypass login, steal accounts, or gain admin access.
|
|
37
|
+
|
|
38
|
+
This tool acts like a **security doctor for JWT tokens** — you give it a token, it runs 5 security checks and tells you exactly what's wrong and how to fix it.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- Detects 5 critical JWT vulnerability categories
|
|
45
|
+
- Beautiful colored terminal output
|
|
46
|
+
- Generates professional HTML security report
|
|
47
|
+
- Generates text report for documentation
|
|
48
|
+
- Includes security recommendations
|
|
49
|
+
- Works on any JWT token
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Vulnerabilities Detected
|
|
54
|
+
|
|
55
|
+
| Check | What it detects | Risk Level |
|
|
56
|
+
|-------|----------------|------------|
|
|
57
|
+
| Algorithm Check | Dangerous 'none' algorithm usage | CRITICAL |
|
|
58
|
+
| Weak Secret Key | Guesses secret key from common passwords | CRITICAL |
|
|
59
|
+
| Token Expiry | Missing or expired expiry time | HIGH |
|
|
60
|
+
| Sensitive Data | Passwords or private data in payload | HIGH |
|
|
61
|
+
| Token Claims | Missing issuer, subject, audience | MEDIUM |
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Tools and Technologies
|
|
66
|
+
|
|
67
|
+
- **Python 3** — Core programming language
|
|
68
|
+
- **PyJWT** — JWT token decoding and validation
|
|
69
|
+
- **Rich** — Beautiful terminal formatting
|
|
70
|
+
- **Colorama** — Terminal color support
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
### Step 1 — Clone the repository
|
|
77
|
+
```bash
|
|
78
|
+
git clone https://github.com/YOURUSERNAME/JWT-Security-Scanner.git
|
|
79
|
+
cd JWT-Security-Scanner
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Step 2 — Install dependencies
|
|
83
|
+
```bash
|
|
84
|
+
pip install -r requirements.txt
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Usage
|
|
90
|
+
|
|
91
|
+
### Run the scanner
|
|
92
|
+
```bash
|
|
93
|
+
python jwt_scanner.py
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Or pass token directly
|
|
97
|
+
```bash
|
|
98
|
+
python jwt_scanner.py YOUR_JWT_TOKEN_HERE
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Generate a test vulnerable token
|
|
102
|
+
```bash
|
|
103
|
+
python -c "import jwt; print(jwt.encode({'user': 'admin', 'password': 'secret123'}, 'secret', algorithm='HS256'))"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Example Output
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
JWT Security Scanner
|
|
112
|
+
Finds vulnerabilities in JWT tokens
|
|
113
|
+
|
|
114
|
+
SECURITY FINDINGS
|
|
115
|
+
CRITICAL WEAK SECRET KEY FOUND: 'secret'
|
|
116
|
+
HIGH No expiry time found!
|
|
117
|
+
HIGH Sensitive fields found: password
|
|
118
|
+
|
|
119
|
+
SECURITY SUMMARY
|
|
120
|
+
CRITICAL 1
|
|
121
|
+
HIGH 2
|
|
122
|
+
MEDIUM 0
|
|
123
|
+
LOW 5
|
|
124
|
+
|
|
125
|
+
CRITICAL VULNERABILITIES FOUND! This token is DANGEROUS!
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Reports Generated
|
|
131
|
+
|
|
132
|
+
After every scan the tool automatically generates:
|
|
133
|
+
|
|
134
|
+
### 1. Text Report
|
|
135
|
+
Plain text file with all findings — perfect for documentation
|
|
136
|
+
|
|
137
|
+
### 2. HTML Report
|
|
138
|
+
Professional security report with color coded risk levels, summary dashboard, findings table, recommendations and cybersecurity quotes
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## What I Learned Building This
|
|
143
|
+
|
|
144
|
+
- JWT token structure — Header, Payload, Signature
|
|
145
|
+
- Common JWT vulnerabilities and how attackers exploit them
|
|
146
|
+
- Python security tool development
|
|
147
|
+
- Automated vulnerability scanning techniques
|
|
148
|
+
- Professional security report generation
|
|
149
|
+
- The difference between encoding and encryption
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Real World Application
|
|
154
|
+
|
|
155
|
+
This tool simulates what penetration testers and security engineers do when auditing web applications. JWT vulnerabilities are listed in the **OWASP Top 10** and are responsible for thousands of security breaches every year.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Project Structure
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
JWT-Security-Scanner/
|
|
163
|
+
│
|
|
164
|
+
├── jwt_scanner.py Main scanner with all checks
|
|
165
|
+
├── requirements.txt Python dependencies
|
|
166
|
+
└── README.md Project documentation
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Also Check Out
|
|
172
|
+
|
|
173
|
+
My other cybersecurity project:
|
|
174
|
+
- [AI-Powered SIEM Home Lab](https://github.com/tabassumfathima28/SIEM-HOME-LAB-PROJECT) — Detects real cyber attacks using Elastic SIEM and Tines automation
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Connect With Me
|
|
179
|
+
|
|
180
|
+
Built by an aspiring SOC Analyst learning cybersecurity through hands-on projects.
|
|
181
|
+
|
|
182
|
+
Connect on [LinkedIn](#)
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
> "Security is not a product, but a process." — Bruce Schneier
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# JWT Security Scanner 🔐
|
|
2
|
+
|
|
3
|
+
> A Python-based command-line tool that automatically scans JWT tokens for security vulnerabilities and generates professional security reports.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What is This Tool?
|
|
8
|
+
|
|
9
|
+
JWT (JSON Web Token) is used by millions of websites to handle user authentication. But poorly configured JWT tokens can be exploited by hackers to bypass login, steal accounts, or gain admin access.
|
|
10
|
+
|
|
11
|
+
This tool acts like a **security doctor for JWT tokens** — you give it a token, it runs 5 security checks and tells you exactly what's wrong and how to fix it.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- Detects 5 critical JWT vulnerability categories
|
|
18
|
+
- Beautiful colored terminal output
|
|
19
|
+
- Generates professional HTML security report
|
|
20
|
+
- Generates text report for documentation
|
|
21
|
+
- Includes security recommendations
|
|
22
|
+
- Works on any JWT token
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Vulnerabilities Detected
|
|
27
|
+
|
|
28
|
+
| Check | What it detects | Risk Level |
|
|
29
|
+
|-------|----------------|------------|
|
|
30
|
+
| Algorithm Check | Dangerous 'none' algorithm usage | CRITICAL |
|
|
31
|
+
| Weak Secret Key | Guesses secret key from common passwords | CRITICAL |
|
|
32
|
+
| Token Expiry | Missing or expired expiry time | HIGH |
|
|
33
|
+
| Sensitive Data | Passwords or private data in payload | HIGH |
|
|
34
|
+
| Token Claims | Missing issuer, subject, audience | MEDIUM |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Tools and Technologies
|
|
39
|
+
|
|
40
|
+
- **Python 3** — Core programming language
|
|
41
|
+
- **PyJWT** — JWT token decoding and validation
|
|
42
|
+
- **Rich** — Beautiful terminal formatting
|
|
43
|
+
- **Colorama** — Terminal color support
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
### Step 1 — Clone the repository
|
|
50
|
+
```bash
|
|
51
|
+
git clone https://github.com/YOURUSERNAME/JWT-Security-Scanner.git
|
|
52
|
+
cd JWT-Security-Scanner
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Step 2 — Install dependencies
|
|
56
|
+
```bash
|
|
57
|
+
pip install -r requirements.txt
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
### Run the scanner
|
|
65
|
+
```bash
|
|
66
|
+
python jwt_scanner.py
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Or pass token directly
|
|
70
|
+
```bash
|
|
71
|
+
python jwt_scanner.py YOUR_JWT_TOKEN_HERE
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Generate a test vulnerable token
|
|
75
|
+
```bash
|
|
76
|
+
python -c "import jwt; print(jwt.encode({'user': 'admin', 'password': 'secret123'}, 'secret', algorithm='HS256'))"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Example Output
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
JWT Security Scanner
|
|
85
|
+
Finds vulnerabilities in JWT tokens
|
|
86
|
+
|
|
87
|
+
SECURITY FINDINGS
|
|
88
|
+
CRITICAL WEAK SECRET KEY FOUND: 'secret'
|
|
89
|
+
HIGH No expiry time found!
|
|
90
|
+
HIGH Sensitive fields found: password
|
|
91
|
+
|
|
92
|
+
SECURITY SUMMARY
|
|
93
|
+
CRITICAL 1
|
|
94
|
+
HIGH 2
|
|
95
|
+
MEDIUM 0
|
|
96
|
+
LOW 5
|
|
97
|
+
|
|
98
|
+
CRITICAL VULNERABILITIES FOUND! This token is DANGEROUS!
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Reports Generated
|
|
104
|
+
|
|
105
|
+
After every scan the tool automatically generates:
|
|
106
|
+
|
|
107
|
+
### 1. Text Report
|
|
108
|
+
Plain text file with all findings — perfect for documentation
|
|
109
|
+
|
|
110
|
+
### 2. HTML Report
|
|
111
|
+
Professional security report with color coded risk levels, summary dashboard, findings table, recommendations and cybersecurity quotes
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## What I Learned Building This
|
|
116
|
+
|
|
117
|
+
- JWT token structure — Header, Payload, Signature
|
|
118
|
+
- Common JWT vulnerabilities and how attackers exploit them
|
|
119
|
+
- Python security tool development
|
|
120
|
+
- Automated vulnerability scanning techniques
|
|
121
|
+
- Professional security report generation
|
|
122
|
+
- The difference between encoding and encryption
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Real World Application
|
|
127
|
+
|
|
128
|
+
This tool simulates what penetration testers and security engineers do when auditing web applications. JWT vulnerabilities are listed in the **OWASP Top 10** and are responsible for thousands of security breaches every year.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Project Structure
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
JWT-Security-Scanner/
|
|
136
|
+
│
|
|
137
|
+
├── jwt_scanner.py Main scanner with all checks
|
|
138
|
+
├── requirements.txt Python dependencies
|
|
139
|
+
└── README.md Project documentation
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Also Check Out
|
|
145
|
+
|
|
146
|
+
My other cybersecurity project:
|
|
147
|
+
- [AI-Powered SIEM Home Lab](https://github.com/tabassumfathima28/SIEM-HOME-LAB-PROJECT) — Detects real cyber attacks using Elastic SIEM and Tines automation
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Connect With Me
|
|
152
|
+
|
|
153
|
+
Built by an aspiring SOC Analyst learning cybersecurity through hands-on projects.
|
|
154
|
+
|
|
155
|
+
Connect on [LinkedIn](#)
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
> "Security is not a product, but a process." — Bruce Schneier
|
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
import jwt
|
|
2
|
+
import json
|
|
3
|
+
import base64
|
|
4
|
+
import sys
|
|
5
|
+
from html import escape
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from colorama import init, Fore, Style
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich import print as rprint
|
|
12
|
+
|
|
13
|
+
# Initialize colorama
|
|
14
|
+
init()
|
|
15
|
+
console = Console()
|
|
16
|
+
# Common weak secret keys hackers try
|
|
17
|
+
WEAK_KEYS = [
|
|
18
|
+
"secret", "password", "123456", "key", "private",
|
|
19
|
+
"mysecret", "jwt_secret", "supersecret", "admin",
|
|
20
|
+
"test", "hello", "qwerty", "abc123", "password123",
|
|
21
|
+
"secretkey", "mykey", "token", "jwt", "auth"
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
# Sensitive fields that should never be in JWT
|
|
25
|
+
SENSITIVE_FIELDS = [
|
|
26
|
+
"password", "passwd", "pwd", "credit_card", "ccv",
|
|
27
|
+
"ssn", "social_security", "bank_account", "private_key",
|
|
28
|
+
"secret", "api_key", "access_key", "pin"
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def print_banner():
|
|
33
|
+
"""Print tool banner"""
|
|
34
|
+
console.print(Panel.fit(
|
|
35
|
+
"[bold cyan]JWT Security Scanner[/bold cyan]\n"
|
|
36
|
+
"[white]Finds vulnerabilities in JWT tokens[/white]\n"
|
|
37
|
+
"[dim]Built for cybersecurity portfolio[/dim]",
|
|
38
|
+
border_style="cyan"
|
|
39
|
+
))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def decode_token_parts(token):
|
|
43
|
+
"""Decode JWT token without verification"""
|
|
44
|
+
try:
|
|
45
|
+
parts = token.split('.')
|
|
46
|
+
if len(parts) != 3:
|
|
47
|
+
return None, None, "Invalid JWT format - must have 3 parts"
|
|
48
|
+
|
|
49
|
+
# Decode header
|
|
50
|
+
header_padded = parts[0] + '=' * (4 - len(parts[0]) % 4)
|
|
51
|
+
header = json.loads(base64.urlsafe_b64decode(header_padded))
|
|
52
|
+
|
|
53
|
+
# Decode payload
|
|
54
|
+
payload_padded = parts[1] + '=' * (4 - len(parts[1]) % 4)
|
|
55
|
+
payload = json.loads(base64.urlsafe_b64decode(payload_padded))
|
|
56
|
+
|
|
57
|
+
return header, payload, None
|
|
58
|
+
except Exception as e:
|
|
59
|
+
return None, None, str(e)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def check_algorithm(header):
|
|
63
|
+
"""Check 1 - Algorithm Security"""
|
|
64
|
+
results = []
|
|
65
|
+
algorithm = header.get('alg', 'unknown').upper()
|
|
66
|
+
|
|
67
|
+
if algorithm == 'NONE':
|
|
68
|
+
results.append({
|
|
69
|
+
"status": "CRITICAL",
|
|
70
|
+
"finding": "Algorithm is set to 'none'!",
|
|
71
|
+
"detail": "Token has NO signature! Anyone can forge this token!",
|
|
72
|
+
"risk": "CRITICAL"
|
|
73
|
+
})
|
|
74
|
+
elif algorithm in ['HS256', 'HS384', 'HS512']:
|
|
75
|
+
results.append({
|
|
76
|
+
"status": "INFO",
|
|
77
|
+
"finding": f"Algorithm: {algorithm}",
|
|
78
|
+
"detail": "Symmetric algorithm — safe if secret key is strong",
|
|
79
|
+
"risk": "LOW"
|
|
80
|
+
})
|
|
81
|
+
elif algorithm in ['RS256', 'RS384', 'RS512']:
|
|
82
|
+
results.append({
|
|
83
|
+
"status": "PASS",
|
|
84
|
+
"finding": f"Algorithm: {algorithm}",
|
|
85
|
+
"detail": "Asymmetric algorithm — good choice!",
|
|
86
|
+
"risk": "LOW"
|
|
87
|
+
})
|
|
88
|
+
else:
|
|
89
|
+
results.append({
|
|
90
|
+
"status": "WARNING",
|
|
91
|
+
"finding": f"Unknown algorithm: {algorithm}",
|
|
92
|
+
"detail": "Unrecognized algorithm detected",
|
|
93
|
+
"risk": "MEDIUM"
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
return results
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def check_expiry(payload):
|
|
100
|
+
"""Check 2 - Token Expiry"""
|
|
101
|
+
results = []
|
|
102
|
+
|
|
103
|
+
if 'exp' not in payload:
|
|
104
|
+
results.append({
|
|
105
|
+
"status": "HIGH",
|
|
106
|
+
"finding": "No expiry time found!",
|
|
107
|
+
"detail": "Token never expires! Stolen token works forever!",
|
|
108
|
+
"risk": "HIGH"
|
|
109
|
+
})
|
|
110
|
+
else:
|
|
111
|
+
exp_time = datetime.fromtimestamp(payload['exp'])
|
|
112
|
+
now = datetime.now()
|
|
113
|
+
|
|
114
|
+
if now > exp_time:
|
|
115
|
+
results.append({
|
|
116
|
+
"status": "WARNING",
|
|
117
|
+
"finding": "Token is EXPIRED",
|
|
118
|
+
"detail": f"Expired at: {exp_time}",
|
|
119
|
+
"risk": "MEDIUM"
|
|
120
|
+
})
|
|
121
|
+
else:
|
|
122
|
+
diff = exp_time - now
|
|
123
|
+
results.append({
|
|
124
|
+
"status": "PASS",
|
|
125
|
+
"finding": "Token has valid expiry",
|
|
126
|
+
"detail": f"Expires at: {exp_time} ({diff.days} days remaining)",
|
|
127
|
+
"risk": "LOW"
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
if 'iat' not in payload:
|
|
131
|
+
results.append({
|
|
132
|
+
"status": "WARNING",
|
|
133
|
+
"finding": "No 'issued at' time found",
|
|
134
|
+
"detail": "Cannot verify when token was created",
|
|
135
|
+
"risk": "LOW"
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
return results
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def check_weak_secret(token, header):
|
|
142
|
+
"""Check 3 - Weak Secret Key"""
|
|
143
|
+
results = []
|
|
144
|
+
algorithm = header.get('alg', '').upper()
|
|
145
|
+
|
|
146
|
+
if algorithm.startswith('HS'):
|
|
147
|
+
cracked_key = None
|
|
148
|
+
for key in WEAK_KEYS:
|
|
149
|
+
try:
|
|
150
|
+
jwt.decode(token, key, algorithms=[algorithm])
|
|
151
|
+
cracked_key = key
|
|
152
|
+
break
|
|
153
|
+
except jwt.InvalidSignatureError:
|
|
154
|
+
continue
|
|
155
|
+
except Exception:
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
if cracked_key:
|
|
159
|
+
results.append({
|
|
160
|
+
"status": "CRITICAL",
|
|
161
|
+
"finding": f"WEAK SECRET KEY FOUND: '{cracked_key}'",
|
|
162
|
+
"detail": "Secret key was guessed from common passwords list!",
|
|
163
|
+
"risk": "CRITICAL"
|
|
164
|
+
})
|
|
165
|
+
else:
|
|
166
|
+
results.append({
|
|
167
|
+
"status": "PASS",
|
|
168
|
+
"finding": "Secret key not found in common keys list",
|
|
169
|
+
"detail": "Key appears strong enough to resist basic attacks",
|
|
170
|
+
"risk": "LOW"
|
|
171
|
+
})
|
|
172
|
+
else:
|
|
173
|
+
results.append({
|
|
174
|
+
"status": "INFO",
|
|
175
|
+
"finding": "Asymmetric algorithm — secret key check skipped",
|
|
176
|
+
"detail": "RS/ES algorithms use public/private key pairs",
|
|
177
|
+
"risk": "LOW"
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
return results
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def check_sensitive_data(payload):
|
|
184
|
+
"""Check 4 - Sensitive Data in Payload"""
|
|
185
|
+
results = []
|
|
186
|
+
found_sensitive = []
|
|
187
|
+
|
|
188
|
+
for field in payload:
|
|
189
|
+
if field.lower() in SENSITIVE_FIELDS:
|
|
190
|
+
found_sensitive.append(field)
|
|
191
|
+
|
|
192
|
+
if found_sensitive:
|
|
193
|
+
results.append({
|
|
194
|
+
"status": "HIGH",
|
|
195
|
+
"finding": f"Sensitive fields found: {', '.join(found_sensitive)}",
|
|
196
|
+
"detail": "JWT payload is base64 encoded NOT encrypted! Anyone can read it!",
|
|
197
|
+
"risk": "HIGH"
|
|
198
|
+
})
|
|
199
|
+
else:
|
|
200
|
+
results.append({
|
|
201
|
+
"status": "PASS",
|
|
202
|
+
"finding": "No sensitive data found in payload",
|
|
203
|
+
"detail": "Payload looks clean",
|
|
204
|
+
"risk": "LOW"
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
return results
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def check_claims(payload):
|
|
211
|
+
"""Check 5 - Important Claims"""
|
|
212
|
+
results = []
|
|
213
|
+
|
|
214
|
+
if 'iss' not in payload:
|
|
215
|
+
results.append({
|
|
216
|
+
"status": "WARNING",
|
|
217
|
+
"finding": "No 'issuer' claim found",
|
|
218
|
+
"detail": "Cannot verify who created this token",
|
|
219
|
+
"risk": "LOW"
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
if 'sub' not in payload:
|
|
223
|
+
results.append({
|
|
224
|
+
"status": "WARNING",
|
|
225
|
+
"finding": "No 'subject' claim found",
|
|
226
|
+
"detail": "Token does not identify who it belongs to",
|
|
227
|
+
"risk": "LOW"
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
if 'aud' not in payload:
|
|
231
|
+
results.append({
|
|
232
|
+
"status": "INFO",
|
|
233
|
+
"finding": "No 'audience' claim found",
|
|
234
|
+
"detail": "Token can be used on any service",
|
|
235
|
+
"risk": "LOW"
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
return results
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def print_results(all_results, header, payload):
|
|
242
|
+
"""Print beautiful security report"""
|
|
243
|
+
|
|
244
|
+
# Count findings by risk
|
|
245
|
+
critical = sum(1 for r in all_results if r['risk'] == 'CRITICAL')
|
|
246
|
+
high = sum(1 for r in all_results if r['risk'] == 'HIGH')
|
|
247
|
+
medium = sum(1 for r in all_results if r['risk'] == 'MEDIUM')
|
|
248
|
+
low = sum(1 for r in all_results if r['risk'] == 'LOW')
|
|
249
|
+
|
|
250
|
+
# Print token info
|
|
251
|
+
console.print("\n[bold cyan]═══ TOKEN INFORMATION ═══[/bold cyan]")
|
|
252
|
+
info_table = Table(show_header=True, header_style="bold cyan")
|
|
253
|
+
info_table.add_column("Field", style="cyan")
|
|
254
|
+
info_table.add_column("Value", style="white")
|
|
255
|
+
|
|
256
|
+
info_table.add_row("Algorithm", header.get('alg', 'unknown'))
|
|
257
|
+
info_table.add_row("Token Type", header.get('typ', 'unknown'))
|
|
258
|
+
|
|
259
|
+
for key, value in payload.items():
|
|
260
|
+
if key == 'exp' or key == 'iat':
|
|
261
|
+
value = str(datetime.fromtimestamp(value))
|
|
262
|
+
info_table.add_row(key, str(value))
|
|
263
|
+
|
|
264
|
+
console.print(info_table)
|
|
265
|
+
|
|
266
|
+
# Print findings
|
|
267
|
+
console.print("\n[bold cyan]═══ SECURITY FINDINGS ═══[/bold cyan]")
|
|
268
|
+
|
|
269
|
+
findings_table = Table(show_header=True, header_style="bold cyan")
|
|
270
|
+
findings_table.add_column("Risk", style="bold", width=10)
|
|
271
|
+
findings_table.add_column("Finding", width=35)
|
|
272
|
+
findings_table.add_column("Detail", width=45)
|
|
273
|
+
|
|
274
|
+
for result in all_results:
|
|
275
|
+
risk = result['risk']
|
|
276
|
+
if risk == 'CRITICAL':
|
|
277
|
+
risk_style = "[bold red]CRITICAL[/bold red]"
|
|
278
|
+
elif risk == 'HIGH':
|
|
279
|
+
risk_style = "[red]HIGH[/red]"
|
|
280
|
+
elif risk == 'MEDIUM':
|
|
281
|
+
risk_style = "[yellow]MEDIUM[/yellow]"
|
|
282
|
+
else:
|
|
283
|
+
risk_style = "[green]LOW[/green]"
|
|
284
|
+
|
|
285
|
+
findings_table.add_row(
|
|
286
|
+
risk_style,
|
|
287
|
+
result['finding'],
|
|
288
|
+
result['detail']
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
console.print(findings_table)
|
|
292
|
+
|
|
293
|
+
# Print summary
|
|
294
|
+
console.print("\n[bold cyan]═══ SECURITY SUMMARY ═══[/bold cyan]")
|
|
295
|
+
summary_table = Table(show_header=False)
|
|
296
|
+
summary_table.add_column("Level", style="bold")
|
|
297
|
+
summary_table.add_column("Count")
|
|
298
|
+
|
|
299
|
+
summary_table.add_row("[bold red]CRITICAL[/bold red]", str(critical))
|
|
300
|
+
summary_table.add_row("[red]HIGH[/red]", str(high))
|
|
301
|
+
summary_table.add_row("[yellow]MEDIUM[/yellow]", str(medium))
|
|
302
|
+
summary_table.add_row("[green]LOW/INFO[/green]", str(low))
|
|
303
|
+
|
|
304
|
+
console.print(summary_table)
|
|
305
|
+
|
|
306
|
+
# Overall verdict
|
|
307
|
+
if critical > 0:
|
|
308
|
+
console.print(Panel("[bold red]⚠ CRITICAL VULNERABILITIES FOUND! This token is DANGEROUS![/bold red]", border_style="red"))
|
|
309
|
+
elif high > 0:
|
|
310
|
+
console.print(Panel("[red]⚠ HIGH RISK VULNERABILITIES FOUND! Immediate action needed![/red]", border_style="red"))
|
|
311
|
+
elif medium > 0:
|
|
312
|
+
console.print(Panel("[yellow]⚠ MEDIUM RISK FINDINGS. Review and fix recommended.[/yellow]", border_style="yellow"))
|
|
313
|
+
else:
|
|
314
|
+
console.print(Panel("[green]✓ No critical issues found. Token appears reasonably secure.[/green]", border_style="green"))
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def scan_token(token):
|
|
318
|
+
"""Main scanning function"""
|
|
319
|
+
console.print("\n[cyan]Scanning JWT token...[/cyan]\n")
|
|
320
|
+
|
|
321
|
+
# Decode token parts
|
|
322
|
+
header, payload, error = decode_token_parts(token)
|
|
323
|
+
|
|
324
|
+
if error:
|
|
325
|
+
console.print(f"[red]Error decoding token: {error}[/red]")
|
|
326
|
+
return
|
|
327
|
+
|
|
328
|
+
# Run all checks
|
|
329
|
+
all_results = []
|
|
330
|
+
|
|
331
|
+
console.print("[cyan]Running Check 1: Algorithm Security...[/cyan]")
|
|
332
|
+
all_results.extend(check_algorithm(header))
|
|
333
|
+
|
|
334
|
+
console.print("[cyan]Running Check 2: Token Expiry...[/cyan]")
|
|
335
|
+
all_results.extend(check_expiry(payload))
|
|
336
|
+
|
|
337
|
+
console.print("[cyan]Running Check 3: Weak Secret Key...[/cyan]")
|
|
338
|
+
all_results.extend(check_weak_secret(token, header))
|
|
339
|
+
|
|
340
|
+
console.print("[cyan]Running Check 4: Sensitive Data...[/cyan]")
|
|
341
|
+
all_results.extend(check_sensitive_data(payload))
|
|
342
|
+
|
|
343
|
+
console.print("[cyan]Running Check 5: Token Claims...[/cyan]")
|
|
344
|
+
all_results.extend(check_claims(payload))
|
|
345
|
+
|
|
346
|
+
# Print results
|
|
347
|
+
print_results(all_results, header, payload)
|
|
348
|
+
filename = save_report(token, all_results, header, payload)
|
|
349
|
+
console.print(f"\n[green]Text report saved to: {filename}[/green]")
|
|
350
|
+
|
|
351
|
+
html_filename = save_html_report(token, all_results, header, payload)
|
|
352
|
+
console.print(f"[green]HTML report saved to: {html_filename}[/green]")
|
|
353
|
+
console.print(f"\n[cyan]Open the HTML file in your browser to see the full report![/cyan]")
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def main():
|
|
357
|
+
print_banner()
|
|
358
|
+
|
|
359
|
+
if len(sys.argv) > 1:
|
|
360
|
+
token = sys.argv[1]
|
|
361
|
+
else:
|
|
362
|
+
console.print("\n[cyan]Enter JWT token to scan:[/cyan]")
|
|
363
|
+
token = input("> ").strip()
|
|
364
|
+
|
|
365
|
+
if not token:
|
|
366
|
+
console.print("[red]No token provided![/red]")
|
|
367
|
+
return
|
|
368
|
+
|
|
369
|
+
scan_token(token)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def save_report(token, all_results, header, payload):
|
|
373
|
+
"""Save scan results to a text report"""
|
|
374
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
375
|
+
filename = f"jwt_report_{timestamp}.txt"
|
|
376
|
+
|
|
377
|
+
with open(filename, 'w', encoding='utf-8') as f:
|
|
378
|
+
f.write("=" * 60 + "\n")
|
|
379
|
+
f.write("JWT SECURITY SCAN REPORT\n")
|
|
380
|
+
f.write(f"Generated: {datetime.now()}\n")
|
|
381
|
+
f.write("=" * 60 + "\n\n")
|
|
382
|
+
|
|
383
|
+
f.write("TOKEN INFORMATION\n")
|
|
384
|
+
f.write("-" * 40 + "\n")
|
|
385
|
+
f.write(f"Algorithm: {header.get('alg', 'unknown')}\n")
|
|
386
|
+
f.write(f"Token Type: {header.get('typ', 'unknown')}\n")
|
|
387
|
+
for key, value in payload.items():
|
|
388
|
+
if key in ['exp', 'iat']:
|
|
389
|
+
value = str(datetime.fromtimestamp(value))
|
|
390
|
+
f.write(f"{key}: {value}\n")
|
|
391
|
+
|
|
392
|
+
f.write("\nSECURITY FINDINGS\n")
|
|
393
|
+
f.write("-" * 40 + "\n")
|
|
394
|
+
for result in all_results:
|
|
395
|
+
f.write(f"[{result['risk']}] {result['finding']}\n")
|
|
396
|
+
f.write(f" Detail: {result['detail']}\n\n")
|
|
397
|
+
|
|
398
|
+
critical = sum(1 for r in all_results if r['risk'] == 'CRITICAL')
|
|
399
|
+
high = sum(1 for r in all_results if r['risk'] == 'HIGH')
|
|
400
|
+
medium = sum(1 for r in all_results if r['risk'] == 'MEDIUM')
|
|
401
|
+
|
|
402
|
+
f.write("\nSECURITY SUMMARY\n")
|
|
403
|
+
f.write("-" * 40 + "\n")
|
|
404
|
+
f.write(f"CRITICAL: {critical}\n")
|
|
405
|
+
f.write(f"HIGH: {high}\n")
|
|
406
|
+
f.write(f"MEDIUM: {medium}\n")
|
|
407
|
+
|
|
408
|
+
if critical > 0:
|
|
409
|
+
f.write("\nVERDICT: CRITICAL VULNERABILITIES FOUND!\n")
|
|
410
|
+
elif high > 0:
|
|
411
|
+
f.write("\nVERDICT: HIGH RISK VULNERABILITIES FOUND!\n")
|
|
412
|
+
else:
|
|
413
|
+
f.write("\nVERDICT: No critical issues found.\n")
|
|
414
|
+
|
|
415
|
+
return filename
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def save_html_report(token, all_results, header, payload):
|
|
419
|
+
"""Save scan results to an HTML report"""
|
|
420
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
421
|
+
filename = f"jwt_report_{timestamp}.html"
|
|
422
|
+
|
|
423
|
+
critical = sum(1 for r in all_results if r['risk'] == 'CRITICAL')
|
|
424
|
+
high = sum(1 for r in all_results if r['risk'] == 'HIGH')
|
|
425
|
+
medium = sum(1 for r in all_results if r['risk'] == 'MEDIUM')
|
|
426
|
+
low = sum(1 for r in all_results if r['risk'] == 'LOW')
|
|
427
|
+
total = len(all_results)
|
|
428
|
+
|
|
429
|
+
payload_html = []
|
|
430
|
+
for key, value in payload.items():
|
|
431
|
+
if key in ['exp', 'iat']:
|
|
432
|
+
value = str(datetime.fromtimestamp(value))
|
|
433
|
+
payload_html.append(
|
|
434
|
+
f"<tr><td style='color: #e91e63; font-weight: 700;'>{escape(str(key))}:</td><td>{escape(str(value))}</td></tr>"
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
findings_html = []
|
|
438
|
+
for idx, result in enumerate(all_results, 1):
|
|
439
|
+
risk = result['risk']
|
|
440
|
+
if risk == 'CRITICAL':
|
|
441
|
+
dot_color = '#dc2626'
|
|
442
|
+
elif risk == 'HIGH':
|
|
443
|
+
dot_color = '#ea580c'
|
|
444
|
+
elif risk == 'MEDIUM':
|
|
445
|
+
dot_color = '#b45309'
|
|
446
|
+
else:
|
|
447
|
+
dot_color = '#4b5563'
|
|
448
|
+
|
|
449
|
+
findings_html.append(
|
|
450
|
+
f"<tr>"
|
|
451
|
+
f"<td>{idx}</td>"
|
|
452
|
+
f"<td><span style='color: {dot_color}; font-weight: 700;'>● {escape(risk)}</span></td>"
|
|
453
|
+
f"<td>{escape(result.get('finding', ''))}</td>"
|
|
454
|
+
f"<td>{escape(result.get('detail', ''))}</td>"
|
|
455
|
+
f"</tr>"
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
html = f"""<!DOCTYPE html>
|
|
459
|
+
<html lang="en">
|
|
460
|
+
<head>
|
|
461
|
+
<meta charset="UTF-8" />
|
|
462
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
463
|
+
<title>JWT Security Assessment Report</title>
|
|
464
|
+
<style>
|
|
465
|
+
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
|
466
|
+
|
|
467
|
+
body {{
|
|
468
|
+
font-family: Arial, sans-serif;
|
|
469
|
+
background: #fef5f9;
|
|
470
|
+
color: #333;
|
|
471
|
+
line-height: 1.6;
|
|
472
|
+
padding: 40px 20px;
|
|
473
|
+
}}
|
|
474
|
+
|
|
475
|
+
.container {{
|
|
476
|
+
max-width: 900px;
|
|
477
|
+
margin: 0 auto;
|
|
478
|
+
background: white;
|
|
479
|
+
padding: 40px;
|
|
480
|
+
}}
|
|
481
|
+
|
|
482
|
+
.header {{
|
|
483
|
+
text-align: center;
|
|
484
|
+
margin-bottom: 40px;
|
|
485
|
+
border-bottom: 2px solid #ffc0e3;
|
|
486
|
+
padding-bottom: 30px;
|
|
487
|
+
}}
|
|
488
|
+
|
|
489
|
+
h1 {{
|
|
490
|
+
color: #e91e63;
|
|
491
|
+
font-size: 32px;
|
|
492
|
+
margin-bottom: 8px;
|
|
493
|
+
font-weight: 700;
|
|
494
|
+
letter-spacing: 1px;
|
|
495
|
+
}}
|
|
496
|
+
|
|
497
|
+
.subtitle {{
|
|
498
|
+
color: #666;
|
|
499
|
+
font-size: 13px;
|
|
500
|
+
margin-bottom: 4px;
|
|
501
|
+
}}
|
|
502
|
+
|
|
503
|
+
.date {{
|
|
504
|
+
color: #999;
|
|
505
|
+
font-size: 12px;
|
|
506
|
+
margin-bottom: 20px;
|
|
507
|
+
}}
|
|
508
|
+
|
|
509
|
+
.heart {{
|
|
510
|
+
color: #e91e63;
|
|
511
|
+
font-size: 20px;
|
|
512
|
+
margin: 15px 0;
|
|
513
|
+
}}
|
|
514
|
+
|
|
515
|
+
.quote {{
|
|
516
|
+
font-style: italic;
|
|
517
|
+
color: #e91e63;
|
|
518
|
+
text-align: center;
|
|
519
|
+
margin: 20px 0;
|
|
520
|
+
font-size: 15px;
|
|
521
|
+
line-height: 1.8;
|
|
522
|
+
}}
|
|
523
|
+
|
|
524
|
+
.section-title {{
|
|
525
|
+
color: #e91e63;
|
|
526
|
+
font-size: 16px;
|
|
527
|
+
font-weight: 700;
|
|
528
|
+
text-transform: uppercase;
|
|
529
|
+
letter-spacing: 2px;
|
|
530
|
+
margin-top: 35px;
|
|
531
|
+
margin-bottom: 15px;
|
|
532
|
+
border-bottom: 1px solid #ffc0e3;
|
|
533
|
+
padding-bottom: 10px;
|
|
534
|
+
}}
|
|
535
|
+
|
|
536
|
+
.overview {{
|
|
537
|
+
background: #fef5f9;
|
|
538
|
+
padding: 20px;
|
|
539
|
+
border-radius: 8px;
|
|
540
|
+
margin-bottom: 20px;
|
|
541
|
+
line-height: 1.8;
|
|
542
|
+
font-size: 14px;
|
|
543
|
+
}}
|
|
544
|
+
|
|
545
|
+
.stats {{
|
|
546
|
+
display: grid;
|
|
547
|
+
grid-template-columns: repeat(5, 1fr);
|
|
548
|
+
gap: 15px;
|
|
549
|
+
margin-bottom: 30px;
|
|
550
|
+
}}
|
|
551
|
+
|
|
552
|
+
.stat {{
|
|
553
|
+
text-align: center;
|
|
554
|
+
padding: 15px;
|
|
555
|
+
background: #f9f9f9;
|
|
556
|
+
border-radius: 8px;
|
|
557
|
+
border-left: 3px solid #ffc0e3;
|
|
558
|
+
}}
|
|
559
|
+
|
|
560
|
+
.stat-label {{
|
|
561
|
+
color: #666;
|
|
562
|
+
font-size: 12px;
|
|
563
|
+
margin-bottom: 8px;
|
|
564
|
+
font-weight: 600;
|
|
565
|
+
}}
|
|
566
|
+
|
|
567
|
+
.stat-value {{
|
|
568
|
+
font-size: 28px;
|
|
569
|
+
font-weight: 700;
|
|
570
|
+
color: #e91e63;
|
|
571
|
+
}}
|
|
572
|
+
|
|
573
|
+
table {{
|
|
574
|
+
width: 100%;
|
|
575
|
+
border-collapse: collapse;
|
|
576
|
+
margin-bottom: 30px;
|
|
577
|
+
font-size: 14px;
|
|
578
|
+
}}
|
|
579
|
+
|
|
580
|
+
th {{
|
|
581
|
+
text-align: left;
|
|
582
|
+
padding: 12px 10px;
|
|
583
|
+
background: white;
|
|
584
|
+
border-bottom: 2px solid #ffc0e3;
|
|
585
|
+
color: #e91e63;
|
|
586
|
+
font-weight: 700;
|
|
587
|
+
text-transform: uppercase;
|
|
588
|
+
font-size: 12px;
|
|
589
|
+
letter-spacing: 1px;
|
|
590
|
+
}}
|
|
591
|
+
|
|
592
|
+
td {{
|
|
593
|
+
padding: 12px 10px;
|
|
594
|
+
border-bottom: 1px solid #f0f0f0;
|
|
595
|
+
vertical-align: top;
|
|
596
|
+
}}
|
|
597
|
+
|
|
598
|
+
tr:last-child td {{
|
|
599
|
+
border-bottom: none;
|
|
600
|
+
}}
|
|
601
|
+
|
|
602
|
+
.token-box {{
|
|
603
|
+
background: #f9f9f9;
|
|
604
|
+
border: 1px solid #ddd;
|
|
605
|
+
border-radius: 6px;
|
|
606
|
+
padding: 15px;
|
|
607
|
+
font-family: monospace;
|
|
608
|
+
font-size: 12px;
|
|
609
|
+
word-break: break-all;
|
|
610
|
+
color: #333;
|
|
611
|
+
max-height: 200px;
|
|
612
|
+
overflow-y: auto;
|
|
613
|
+
margin-bottom: 30px;
|
|
614
|
+
}}
|
|
615
|
+
|
|
616
|
+
ul {{
|
|
617
|
+
margin-left: 20px;
|
|
618
|
+
margin-bottom: 30px;
|
|
619
|
+
}}
|
|
620
|
+
|
|
621
|
+
li {{
|
|
622
|
+
margin-bottom: 8px;
|
|
623
|
+
color: #333;
|
|
624
|
+
}}
|
|
625
|
+
|
|
626
|
+
.footer {{
|
|
627
|
+
text-align: center;
|
|
628
|
+
color: #999;
|
|
629
|
+
font-size: 12px;
|
|
630
|
+
margin-top: 40px;
|
|
631
|
+
padding-top: 20px;
|
|
632
|
+
border-top: 1px solid #ffc0e3;
|
|
633
|
+
}}
|
|
634
|
+
|
|
635
|
+
.footer-icon {{
|
|
636
|
+
color: #e91e63;
|
|
637
|
+
font-size: 16px;
|
|
638
|
+
}}
|
|
639
|
+
</style>
|
|
640
|
+
</head>
|
|
641
|
+
<body>
|
|
642
|
+
<div class="container">
|
|
643
|
+
<div class="header">
|
|
644
|
+
<h1>JWT SECURITY ASSESSMENT REPORT</h1>
|
|
645
|
+
<p class="subtitle">Automated Vulnerability Scan | Confidential Security Document</p>
|
|
646
|
+
<p class="date">Report Generated: {datetime.now().strftime("%B %d, %Y at %H:%M:%S")}</p>
|
|
647
|
+
<div class="heart">💗</div>
|
|
648
|
+
<p class="quote">"The goal of security is not to eliminate risk,<br>but to bring it to an acceptable level."</p>
|
|
649
|
+
</div>
|
|
650
|
+
|
|
651
|
+
<h2 class="section-title">Report Overview</h2>
|
|
652
|
+
<div class="overview">
|
|
653
|
+
This report presents the results of an automated security assessment performed on the provided JSON Web Token (JWT). The scanner analyzed the token against multiple security best practices and identified potential vulnerabilities.
|
|
654
|
+
</div>
|
|
655
|
+
|
|
656
|
+
<div class="stats">
|
|
657
|
+
<div class="stat">
|
|
658
|
+
<div class="stat-label">Total Findings</div>
|
|
659
|
+
<div class="stat-value">{total}</div>
|
|
660
|
+
</div>
|
|
661
|
+
<div class="stat">
|
|
662
|
+
<div class="stat-label">Critical</div>
|
|
663
|
+
<div class="stat-value" style="color: #dc2626;">{critical}</div>
|
|
664
|
+
</div>
|
|
665
|
+
<div class="stat">
|
|
666
|
+
<div class="stat-label">High</div>
|
|
667
|
+
<div class="stat-value" style="color: #ea580c;">{high}</div>
|
|
668
|
+
</div>
|
|
669
|
+
<div class="stat">
|
|
670
|
+
<div class="stat-label">Medium</div>
|
|
671
|
+
<div class="stat-value" style="color: #b45309;">{medium}</div>
|
|
672
|
+
</div>
|
|
673
|
+
<div class="stat">
|
|
674
|
+
<div class="stat-label">Low</div>
|
|
675
|
+
<div class="stat-value" style="color: #4b5563;">{low}</div>
|
|
676
|
+
</div>
|
|
677
|
+
</div>
|
|
678
|
+
|
|
679
|
+
<h2 class="section-title">Security Findings</h2>
|
|
680
|
+
<table>
|
|
681
|
+
<tr>
|
|
682
|
+
<th style="width: 40px;">#</th>
|
|
683
|
+
<th style="width: 80px;">Severity</th>
|
|
684
|
+
<th style="width: 200px;">Category</th>
|
|
685
|
+
<th>Finding</th>
|
|
686
|
+
<th>Details</th>
|
|
687
|
+
</tr>
|
|
688
|
+
{''.join(findings_html)}
|
|
689
|
+
</table>
|
|
690
|
+
|
|
691
|
+
<h2 class="section-title">Token Details</h2>
|
|
692
|
+
<table>
|
|
693
|
+
{''.join(payload_html)}
|
|
694
|
+
</table>
|
|
695
|
+
|
|
696
|
+
<h2 class="section-title">Recommendations</h2>
|
|
697
|
+
<ul>
|
|
698
|
+
<li>Never use 'none' algorithm. Always use a secure algorithm like RS256 or HS256.</li>
|
|
699
|
+
<li>Always set an expiration time (exp) for tokens.</li>
|
|
700
|
+
<li>Avoid including sensitive information in the payload.</li>
|
|
701
|
+
<li>Use strong secret keys (minimum 32 characters for HS256).</li>
|
|
702
|
+
<li>Follow the principle of least privilege when assigning permissions.</li>
|
|
703
|
+
</ul>
|
|
704
|
+
|
|
705
|
+
<div class="footer">
|
|
706
|
+
Generated by <strong>JWT Security Scanner</strong><br>
|
|
707
|
+
Stay safe, stay pink, stay secure! <span class="footer-icon">🌸</span>
|
|
708
|
+
</div>
|
|
709
|
+
</div>
|
|
710
|
+
</body>
|
|
711
|
+
</html>"""
|
|
712
|
+
|
|
713
|
+
with open(filename, 'w', encoding='utf-8') as f:
|
|
714
|
+
f.write(html)
|
|
715
|
+
|
|
716
|
+
return filename
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
if __name__ == "__main__":
|
|
720
|
+
main()
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jwt-security-scanner
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A tool to scan JWT tokens for security vulnerabilities
|
|
5
|
+
Home-page: https://github.com/tabassumfathima28/JWT-Security-Scanner
|
|
6
|
+
Author: Tabassum Fathima
|
|
7
|
+
Author-email: tabassumfathima2812@gmail.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Security
|
|
12
|
+
Requires-Python: >=3.7
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: pyjwt>=2.0.0
|
|
15
|
+
Requires-Dist: requests>=2.0.0
|
|
16
|
+
Requires-Dist: colorama>=0.4.0
|
|
17
|
+
Requires-Dist: rich>=10.0.0
|
|
18
|
+
Dynamic: author
|
|
19
|
+
Dynamic: author-email
|
|
20
|
+
Dynamic: classifier
|
|
21
|
+
Dynamic: description
|
|
22
|
+
Dynamic: description-content-type
|
|
23
|
+
Dynamic: home-page
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
# JWT Security Scanner 🔐
|
|
29
|
+
|
|
30
|
+
> A Python-based command-line tool that automatically scans JWT tokens for security vulnerabilities and generates professional security reports.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## What is This Tool?
|
|
35
|
+
|
|
36
|
+
JWT (JSON Web Token) is used by millions of websites to handle user authentication. But poorly configured JWT tokens can be exploited by hackers to bypass login, steal accounts, or gain admin access.
|
|
37
|
+
|
|
38
|
+
This tool acts like a **security doctor for JWT tokens** — you give it a token, it runs 5 security checks and tells you exactly what's wrong and how to fix it.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- Detects 5 critical JWT vulnerability categories
|
|
45
|
+
- Beautiful colored terminal output
|
|
46
|
+
- Generates professional HTML security report
|
|
47
|
+
- Generates text report for documentation
|
|
48
|
+
- Includes security recommendations
|
|
49
|
+
- Works on any JWT token
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Vulnerabilities Detected
|
|
54
|
+
|
|
55
|
+
| Check | What it detects | Risk Level |
|
|
56
|
+
|-------|----------------|------------|
|
|
57
|
+
| Algorithm Check | Dangerous 'none' algorithm usage | CRITICAL |
|
|
58
|
+
| Weak Secret Key | Guesses secret key from common passwords | CRITICAL |
|
|
59
|
+
| Token Expiry | Missing or expired expiry time | HIGH |
|
|
60
|
+
| Sensitive Data | Passwords or private data in payload | HIGH |
|
|
61
|
+
| Token Claims | Missing issuer, subject, audience | MEDIUM |
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Tools and Technologies
|
|
66
|
+
|
|
67
|
+
- **Python 3** — Core programming language
|
|
68
|
+
- **PyJWT** — JWT token decoding and validation
|
|
69
|
+
- **Rich** — Beautiful terminal formatting
|
|
70
|
+
- **Colorama** — Terminal color support
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
### Step 1 — Clone the repository
|
|
77
|
+
```bash
|
|
78
|
+
git clone https://github.com/YOURUSERNAME/JWT-Security-Scanner.git
|
|
79
|
+
cd JWT-Security-Scanner
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Step 2 — Install dependencies
|
|
83
|
+
```bash
|
|
84
|
+
pip install -r requirements.txt
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Usage
|
|
90
|
+
|
|
91
|
+
### Run the scanner
|
|
92
|
+
```bash
|
|
93
|
+
python jwt_scanner.py
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Or pass token directly
|
|
97
|
+
```bash
|
|
98
|
+
python jwt_scanner.py YOUR_JWT_TOKEN_HERE
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Generate a test vulnerable token
|
|
102
|
+
```bash
|
|
103
|
+
python -c "import jwt; print(jwt.encode({'user': 'admin', 'password': 'secret123'}, 'secret', algorithm='HS256'))"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Example Output
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
JWT Security Scanner
|
|
112
|
+
Finds vulnerabilities in JWT tokens
|
|
113
|
+
|
|
114
|
+
SECURITY FINDINGS
|
|
115
|
+
CRITICAL WEAK SECRET KEY FOUND: 'secret'
|
|
116
|
+
HIGH No expiry time found!
|
|
117
|
+
HIGH Sensitive fields found: password
|
|
118
|
+
|
|
119
|
+
SECURITY SUMMARY
|
|
120
|
+
CRITICAL 1
|
|
121
|
+
HIGH 2
|
|
122
|
+
MEDIUM 0
|
|
123
|
+
LOW 5
|
|
124
|
+
|
|
125
|
+
CRITICAL VULNERABILITIES FOUND! This token is DANGEROUS!
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Reports Generated
|
|
131
|
+
|
|
132
|
+
After every scan the tool automatically generates:
|
|
133
|
+
|
|
134
|
+
### 1. Text Report
|
|
135
|
+
Plain text file with all findings — perfect for documentation
|
|
136
|
+
|
|
137
|
+
### 2. HTML Report
|
|
138
|
+
Professional security report with color coded risk levels, summary dashboard, findings table, recommendations and cybersecurity quotes
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## What I Learned Building This
|
|
143
|
+
|
|
144
|
+
- JWT token structure — Header, Payload, Signature
|
|
145
|
+
- Common JWT vulnerabilities and how attackers exploit them
|
|
146
|
+
- Python security tool development
|
|
147
|
+
- Automated vulnerability scanning techniques
|
|
148
|
+
- Professional security report generation
|
|
149
|
+
- The difference between encoding and encryption
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Real World Application
|
|
154
|
+
|
|
155
|
+
This tool simulates what penetration testers and security engineers do when auditing web applications. JWT vulnerabilities are listed in the **OWASP Top 10** and are responsible for thousands of security breaches every year.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Project Structure
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
JWT-Security-Scanner/
|
|
163
|
+
│
|
|
164
|
+
├── jwt_scanner.py Main scanner with all checks
|
|
165
|
+
├── requirements.txt Python dependencies
|
|
166
|
+
└── README.md Project documentation
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Also Check Out
|
|
172
|
+
|
|
173
|
+
My other cybersecurity project:
|
|
174
|
+
- [AI-Powered SIEM Home Lab](https://github.com/tabassumfathima28/SIEM-HOME-LAB-PROJECT) — Detects real cyber attacks using Elastic SIEM and Tines automation
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Connect With Me
|
|
179
|
+
|
|
180
|
+
Built by an aspiring SOC Analyst learning cybersecurity through hands-on projects.
|
|
181
|
+
|
|
182
|
+
Connect on [LinkedIn](#)
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
> "Security is not a product, but a process." — Bruce Schneier
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
jwt_scanner/__init__.py
|
|
5
|
+
jwt_scanner/scanner.py
|
|
6
|
+
jwt_security_scanner.egg-info/PKG-INFO
|
|
7
|
+
jwt_security_scanner.egg-info/SOURCES.txt
|
|
8
|
+
jwt_security_scanner.egg-info/dependency_links.txt
|
|
9
|
+
jwt_security_scanner.egg-info/entry_points.txt
|
|
10
|
+
jwt_security_scanner.egg-info/requires.txt
|
|
11
|
+
jwt_security_scanner.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
jwt_scanner
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="jwt-security-scanner",
|
|
5
|
+
version="1.0.0",
|
|
6
|
+
author="Tabassum Fathima",
|
|
7
|
+
author_email="tabassumfathima2812@gmail.com",
|
|
8
|
+
description="A tool to scan JWT tokens for security vulnerabilities",
|
|
9
|
+
long_description=open("README.md", encoding="utf-8").read(),
|
|
10
|
+
long_description_content_type="text/markdown",
|
|
11
|
+
url="https://github.com/tabassumfathima28/JWT-Security-Scanner",
|
|
12
|
+
packages=find_packages(),
|
|
13
|
+
install_requires=[
|
|
14
|
+
"pyjwt>=2.0.0",
|
|
15
|
+
"requests>=2.0.0",
|
|
16
|
+
"colorama>=0.4.0",
|
|
17
|
+
"rich>=10.0.0",
|
|
18
|
+
],
|
|
19
|
+
entry_points={
|
|
20
|
+
"console_scripts": [
|
|
21
|
+
"jwt-scanner=jwt_scanner.scanner:main",
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
classifiers=[
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"License :: OSI Approved :: MIT License",
|
|
27
|
+
"Operating System :: OS Independent",
|
|
28
|
+
"Topic :: Security",
|
|
29
|
+
],
|
|
30
|
+
python_requires=">=3.7",
|
|
31
|
+
)
|