{"id":728,"date":"2025-03-06T23:31:55","date_gmt":"2025-03-06T21:31:55","guid":{"rendered":"https:\/\/www.cloudtango.net\/blog\/?p=728"},"modified":"2025-03-06T23:31:56","modified_gmt":"2025-03-06T21:31:56","slug":"a-guide-to-json-web-tokens-jwts","status":"publish","type":"post","link":"https:\/\/www.cloudtango.net\/blog\/2025\/03\/06\/a-guide-to-json-web-tokens-jwts\/","title":{"rendered":"A Guide to JSON Web Tokens (JWTs)"},"content":{"rendered":"<p>Not too long ago, JSON Web Tokens (JWTs) were widely regarded as a go-to solution for authentication, praised for their security, scalability, and simplicity. However, today, the penetration testing team at CybaVerse\u2014along with other security researchers\u2014frequently uncovers high and critical vulnerabilities in their implementations.<\/p>\n<p><span data-contrast=\"auto\">The thing is automated scanners don\u2019t typically pick up JWT misconfigurations with default settings. A thorough penetration test, however, can expose serious flaws that, if exploited, could undermine confidentiality, integrity, and availability. These three principles form the foundation of cyber security, and failing in any of them can lead to financial loss, reputational damage, or regulatory fines.<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">JWTs are just one option for authentication and authorisation, both of which are equally essential to security. Authentication confirms who you are, while authorisation determines what you can access. JWTs often include claims that define a user\u2019s roles, permissions, and access levels, but if these are not properly validated, attackers can modify them to escalate privileges or bypass restrictions altogether.<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">Understanding the risks is the first step to securing JWTs effectively. The next section covers how attackers exploit them, followed by ways to strengthen their security.<\/span><\/p>\n<h4><span role=\"presentation\">They Work and Why They Matter<\/span><\/h4>\n<p><span role=\"presentation\">Before diving into exploits, it\u2019s important to understand what JWTs are and how they work. As previously mentioned, JWTs are widely used for authentication and authorisation. They allow systems to verify a user\u2019s identity and access level without requiring session data to be stored on the server. Unlike traditional session tokens, all the necessary data is stored on the client-side within the JWT itself. This makes them a popular choice for highly distributed websites, allowing users to interact seamlessly across multiple back-end servers.<\/span><\/p>\n<div id=\"hs_cos_wrapper_widget_c340fb66-5bc5-4f87-9478-9d83ee17ca90\" class=\"hs_cos_wrapper hs_cos_wrapper_widget hs_cos_wrapper_type_module\" data-hs-cos-general-type=\"widget\" data-hs-cos-type=\"module\"><img loading=\"lazy\" decoding=\"async\" class=\"c-image-rounded-corners \" src=\"https:\/\/www.cybaverse.co.uk\/hs-fs\/hubfs\/figure%201.png?width=1395&amp;height=735&amp;name=figure%201.png\" alt=\"Figure 1 decoded example of a JWT\" width=\"1395\" height=\"735\" \/><\/div>\n<p style=\"text-align: center;\"><span role=\"presentation\"><em><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"none\">Figure\u00a0<\/span><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"none\">1<\/span><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"none\">\u00a0decoded example of a JWT<\/span><\/em>\u00a0<\/span><\/p>\n<p><span role=\"presentation\">A JWT consists of three parts: the\u00a0header,\u00a0payload, and\u00a0signature. These are Base64-encoded and joined together with dots, forming a compact token that looks like this:<\/span><\/p>\n<p>eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkN5YmFWZXJzZSIsImFkbWluIjp0cnVlLCJpYXQiOjE3Mzg2MTEzOTksImV4cCI6MTczODYxNDk5OX0.aS3zuaxqSRln7x1w8yoDZh6ikgxok4iHykNORwNsrVY<\/p>\n<p>Let\u2019s break it down.<\/p>\n<h3>Header<\/h3>\n<p><span data-contrast=\"auto\">The header contains information about how the JWT is signed.<\/span><\/p>\n<p><span data-contrast=\"none\">{<\/span><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"none\">\u00a0 &#8220;typ&#8221;: &#8220;JWT&#8221;,<\/span><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"none\">\u00a0 &#8220;alg&#8221;: &#8220;HS256&#8221;<\/span><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"none\">}<\/span><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">The\u00a0<\/span><strong><i><span data-contrast=\"none\">alg<\/span><\/i><\/strong><span data-contrast=\"auto\">\u00a0field specifies the signing algorithm, such as HS256 (HMAC SHA-256) or RS256 (RSA SHA-256). The<\/span><strong><i><span data-contrast=\"none\">\u00a0typ<\/span><\/i><\/strong><span data-contrast=\"auto\">\u00a0field just indicates this is a JWT.<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<h3>Payload<\/h3>\n<p><span data-contrast=\"auto\">The payload holds the claims, which are details about the user and their permissions.<\/span><\/p>\n<p><i><span data-contrast=\"none\">{<\/span><\/i><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><i><span data-contrast=\"none\">\u00a0 &#8220;sub&#8221;: &#8220;1234567890&#8221;,<\/span><\/i><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><i><span data-contrast=\"none\">\u00a0 &#8220;name&#8221;: &#8220;CybaVerse&#8221;,<\/span><\/i><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><i><span data-contrast=\"none\">\u00a0 &#8220;admin&#8221;: true,<\/span><\/i><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><i><span data-contrast=\"none\">\u00a0 &#8220;iat&#8221;: 1738611399,<\/span><\/i><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><i><span data-contrast=\"none\">\u00a0 &#8220;exp&#8221;: 1738614999<\/span><\/i><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><i><span data-contrast=\"none\">}<\/span><\/i><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">In this example:<\/span><\/p>\n<ul>\n<li data-leveltext=\"\uf0b7\" data-font=\"Symbol\" data-listid=\"17\" data-list-defn-props=\"{&quot;335552541&quot;:1,&quot;335559685&quot;:720,&quot;335559991&quot;:360,&quot;469769226&quot;:&quot;Symbol&quot;,&quot;469769242&quot;:[8226],&quot;469777803&quot;:&quot;left&quot;,&quot;469777804&quot;:&quot;\uf0b7&quot;,&quot;469777815&quot;:&quot;hybridMultilevel&quot;}\" data-aria-posinset=\"1\" data-aria-level=\"1\"><strong><i><span data-contrast=\"none\">sub<\/span><\/i><\/strong><span data-contrast=\"auto\">\u00a0is the user ID<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/li>\n<\/ul>\n<ul>\n<li data-leveltext=\"\uf0b7\" data-font=\"Symbol\" data-listid=\"17\" data-list-defn-props=\"{&quot;335552541&quot;:1,&quot;335559685&quot;:720,&quot;335559991&quot;:360,&quot;469769226&quot;:&quot;Symbol&quot;,&quot;469769242&quot;:[8226],&quot;469777803&quot;:&quot;left&quot;,&quot;469777804&quot;:&quot;\uf0b7&quot;,&quot;469777815&quot;:&quot;hybridMultilevel&quot;}\" data-aria-posinset=\"2\" data-aria-level=\"1\"><strong><i><span data-contrast=\"auto\">name<\/span><\/i><\/strong><span data-contrast=\"auto\">\u00a0<\/span><span data-contrast=\"auto\">is &#8220;CybaVerse&#8221;<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/li>\n<\/ul>\n<ul>\n<li data-leveltext=\"\uf0b7\" data-font=\"Symbol\" data-listid=\"17\" data-list-defn-props=\"{&quot;335552541&quot;:1,&quot;335559685&quot;:720,&quot;335559991&quot;:360,&quot;469769226&quot;:&quot;Symbol&quot;,&quot;469769242&quot;:[8226],&quot;469777803&quot;:&quot;left&quot;,&quot;469777804&quot;:&quot;\uf0b7&quot;,&quot;469777815&quot;:&quot;hybridMultilevel&quot;}\" data-aria-posinset=\"3\" data-aria-level=\"1\"><strong><i><span data-contrast=\"auto\">role<\/span><\/i><\/strong><span data-contrast=\"auto\">\u00a0is &#8220;admin&#8221;, meaning the user has high-level access<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/li>\n<\/ul>\n<ul>\n<li data-leveltext=\"\uf0b7\" data-font=\"Symbol\" data-listid=\"17\" data-list-defn-props=\"{&quot;335552541&quot;:1,&quot;335559685&quot;:720,&quot;335559991&quot;:360,&quot;469769226&quot;:&quot;Symbol&quot;,&quot;469769242&quot;:[8226],&quot;469777803&quot;:&quot;left&quot;,&quot;469777804&quot;:&quot;\uf0b7&quot;,&quot;469777815&quot;:&quot;hybridMultilevel&quot;}\" data-aria-posinset=\"4\" data-aria-level=\"1\"><strong><i><span data-contrast=\"auto\">exp<\/span><\/i><\/strong><span data-contrast=\"auto\">\u00a0is the expiration time, stopping the token from being used indefinitely<\/span><\/li>\n<\/ul>\n<p><span data-contrast=\"auto\">Claims like\u00a0<\/span><strong><i><span data-contrast=\"none\">sub<\/span><\/i><\/strong><i><span data-contrast=\"none\">,\u00a0<\/span><\/i><strong><i><span data-contrast=\"none\">exp<\/span><\/i><\/strong><i><span data-contrast=\"none\">,\u00a0<\/span><\/i><span data-contrast=\"auto\">and\u00a0<\/span><strong><i><span data-contrast=\"none\">role<\/span><\/i><\/strong><span data-contrast=\"auto\">\u00a0are often used for authorisation, helping systems decide what a user is allowed to do.<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<h3>Signature<\/h3>\n<p><span data-contrast=\"auto\">The signature makes sure the JWT hasn\u2019t been tampered with. If someone tries to change the payload, the signature will no longer match, and the JWT should be rejected.<\/span><\/p>\n<p><span data-contrast=\"auto\">Next, we will look at how JWTs can be exploited and what can be done to secure them.<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<h4><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"auto\">Common JWT Security Exploits<\/span><\/h4>\n<p><span data-contrast=\"auto\">Before getting into some of the documented common exploits, the most frequent issue found is authorisation. While this is not a direct flaw of JWTs, it is often a misconfiguration. Many applications have multiple user levels, yet access control is either poorly implemented or inconsistently applied across different areas.<\/span><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">Just because an\u00a0<\/span><strong><i><span data-contrast=\"none\">\/api\/admin\u00a0<\/span><\/i><\/strong><span data-contrast=\"auto\">endpoint is not visible on the web application, does not mean attackers won\u2019t find it. Endpoint discovery is straightforward, and without proper access control, critical actions like creating an admin user can often be performed by unauthorised accounts.<\/span><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">Even during black-box testing (meaning no information provided) with a self-registered account or even with no authentication, privilege escalation can be surprisingly straightforward, especially when verbose error messages from the server give away key information. An example is when we fuzz the correct endpoint, and find something like the below:<\/span><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><a href=\"https:\/\/webapp.com\/api\/users\/create\"><i><span data-contrast=\"none\">https:\/\/webapp.com\/api\/users\/create<\/span><\/i><\/a><i><span data-contrast=\"none\">\u00a0<\/span><\/i><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">The server returns a 200 OKAY and then guides us through the process, first requesting an email, then a password, and eventually requesting a role parameter. With the right input, it is not long before privilege escalation is achieved.<\/span><span data-ccp-props=\"{&quot;134233117&quot;:true,&quot;134233118&quot;:true,&quot;201341983&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">Issues like this are all too common, especially during a company\u2019s first penetration test. While the list of JWT-specific vulnerabilities below is important, misconfigured authorisation remains one of the most frequent real-world security gaps.<\/span><\/p>\n<h5><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"auto\">1. Sensitive Information Disclosure<\/span><\/h5>\n<p><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">JWTs are Base64-encoded, but they are not encrypted. A common mistake is storing sensitive information in the payload. Things like user IDs, email addresses, API keys or even plaintext passwords.<\/span><\/p>\n<p><strong>How Attackers Exploit It:\u00a0<\/strong><\/p>\n<p>1. Capture a JWT from a request using a proxy like Burp Suite.<\/p>\n<p>2. Decode it with echo &lt;token&gt; | base64 -d or use jwt.io.<\/p>\n<p>3. Extract sensitive data and use it to escalate privileges or impersonate users.<\/p>\n<p><strong>Mitigation:\u00a0<\/strong><\/p>\n<ol>\n<li>Never store sensitive data inside a JWT payload.<\/li>\n<li>Encrypt sensitive data if absolutely necessary.<\/li>\n<\/ol>\n<div id=\"hs_cos_wrapper_widget_a3ae4681-8f5c-401b-904d-2394c88e0cfc\" class=\"hs_cos_wrapper hs_cos_wrapper_widget hs_cos_wrapper_type_module\" data-hs-cos-general-type=\"widget\" data-hs-cos-type=\"module\"><img loading=\"lazy\" decoding=\"async\" class=\"c-image-rounded-corners \" src=\"https:\/\/www.cybaverse.co.uk\/hs-fs\/hubfs\/figure%202.webp?width=1118&amp;height=331&amp;name=figure%202.webp\" alt=\"Figure 2 example of a password in JWT which can be seen after Base64 Decoding \" width=\"1118\" height=\"331\" \/><\/div>\n<p style=\"text-align: center;\"><em><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"none\">Figure\u00a0<\/span><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"none\">2<\/span><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"none\">\u00a0example of a password in JWT which can be seen after Base64 Decoding<\/span><\/em><\/p>\n<h5><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"none\">2.\u00a0<\/span>Signature Validation Bypass<\/h5>\n<p><span data-contrast=\"auto\">JWTs use a signature to verify that the token hasn\u2019t been altered. If a server does not properly check the signature, attackers can modify claims and still gain access. In some cases, misconfigured systems even allow the\u00a0<\/span><strong><i><span data-contrast=\"none\">none<\/span><\/i><\/strong><span data-contrast=\"auto\">\u00a0algorithm, meaning no signature is required at all.<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<p><strong><span data-contrast=\"auto\">How Attackers Exploit It:<\/span><\/strong><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">1. Capture a valid JWT.<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">2. Modify the payload, for example, changing\u00a0<\/span><strong><i><span data-contrast=\"none\">&#8220;admin&#8221;: false<\/span><\/i><\/strong><span data-contrast=\"auto\">\u00a0to\u00a0<\/span><strong><i><span data-contrast=\"none\">&#8220;admin&#8221;: true<\/span><\/i><\/strong><span data-contrast=\"auto\">.<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">3. Change the header to specify\u00a0<\/span><strong><i><span data-contrast=\"none\">&#8220;alg&#8221;: &#8220;none&#8221;<\/span><\/i><\/strong><span data-contrast=\"auto\">, removing the need for a signature:<\/span><\/p>\n<p><span data-contrast=\"none\">{<\/span><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"none\">\u00a0 &#8220;alg&#8221;: &#8220;none&#8221;,<\/span><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"none\">\u00a0 &#8220;typ&#8221;: &#8220;JWT&#8221;<\/span><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"none\">}<\/span><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">1. Remove the signature entirely or replace it with random data.<\/span><\/p>\n<p><span data-contrast=\"auto\">2. If the server accepts the modified token without verifying the signature, unauthorised access is granted.<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<p><strong><span data-contrast=\"auto\">Mitigation:<\/span><\/strong><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<ol>\n<li data-leveltext=\"\uf0b7\" data-font=\"Symbol\" data-listid=\"20\" data-list-defn-props=\"{&quot;335552541&quot;:1,&quot;335559685&quot;:720,&quot;335559991&quot;:360,&quot;469769226&quot;:&quot;Symbol&quot;,&quot;469769242&quot;:[8226],&quot;469777803&quot;:&quot;left&quot;,&quot;469777804&quot;:&quot;\uf0b7&quot;,&quot;469777815&quot;:&quot;hybridMultilevel&quot;}\" data-aria-posinset=\"1\" data-aria-level=\"1\"><span data-contrast=\"auto\">Always validate the signature before processing a JWT.<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/li>\n<li><span data-contrast=\"auto\">Use secure JWT libraries that enforce signature verification and reject unsigned tokens.<\/span><\/li>\n<li><span data-contrast=\"auto\">Ensure the server does not allow<\/span><strong><i><span data-contrast=\"none\">\u00a0alg: none\u00a0<\/span><\/i><\/strong><span data-contrast=\"auto\">unless explicitly required for a specific use case.<\/span><\/li>\n<\/ol>\n<h5>3. Brute-Forcing Weak Signing Keys<\/h5>\n<p><span data-contrast=\"auto\">If JWTs are signed with a symmetric algorithm for example HS256 they can be signed with weak keys (like\u00a0<\/span><span data-contrast=\"auto\">password123<\/span><span data-contrast=\"auto\">) and these are easy targets for brute-force attacks. If an attacker can guess the key, they can sign their own tokens and gain access.<\/span><span data-ccp-props=\"{&quot;134233117&quot;:true,&quot;134233118&quot;:true,&quot;201341983&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<p><strong><span data-contrast=\"auto\">How Attackers Exploit It:<\/span><\/strong><span data-ccp-props=\"{&quot;134233117&quot;:true,&quot;134233118&quot;:true,&quot;201341983&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">1. Capture a JWT.<\/span><span data-ccp-props=\"{&quot;134233117&quot;:true,&quot;134233118&quot;:true,&quot;201341983&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">2. Use a brute-force tool like Hashcat:<\/span><\/p>\n<p><span data-contrast=\"none\">hashcat -m 16500 -a 0 jwt_hash.txt wordlist.txt<\/span><span data-ccp-props=\"{}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">3. Generate valid JWTs with escalated privileges.<\/span><\/p>\n<p><strong><span data-contrast=\"auto\">Mitigation:<\/span><\/strong><span data-ccp-props=\"{&quot;134233117&quot;:true,&quot;134233118&quot;:true,&quot;201341983&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<ol>\n<li data-leveltext=\"\uf0b7\" data-font=\"Symbol\" data-listid=\"6\" data-list-defn-props=\"{&quot;335552541&quot;:1,&quot;335559685&quot;:720,&quot;335559991&quot;:360,&quot;469769226&quot;:&quot;Symbol&quot;,&quot;469769242&quot;:[8226],&quot;469777803&quot;:&quot;left&quot;,&quot;469777804&quot;:&quot;\uf0b7&quot;,&quot;469777815&quot;:&quot;multilevel&quot;}\" data-aria-posinset=\"1\" data-aria-level=\"1\"><span data-contrast=\"auto\">Use long, randomly generated signing keys.<\/span><span data-ccp-props=\"{&quot;134233117&quot;:true,&quot;134233118&quot;:true,&quot;201341983&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/li>\n<li data-leveltext=\"\uf0b7\" data-font=\"Symbol\" data-listid=\"6\" data-list-defn-props=\"{&quot;335552541&quot;:1,&quot;335559685&quot;:720,&quot;335559991&quot;:360,&quot;469769226&quot;:&quot;Symbol&quot;,&quot;469769242&quot;:[8226],&quot;469777803&quot;:&quot;left&quot;,&quot;469777804&quot;:&quot;\uf0b7&quot;,&quot;469777815&quot;:&quot;multilevel&quot;}\" data-aria-posinset=\"2\" data-aria-level=\"1\"><span data-contrast=\"auto\">Never use hardcoded or default keys.<\/span><\/li>\n<\/ol>\n<div id=\"hs_cos_wrapper_widget_588eeed5-be34-4893-9abd-85f217da53d9\" class=\"hs_cos_wrapper hs_cos_wrapper_widget hs_cos_wrapper_type_module\" data-hs-cos-general-type=\"widget\" data-hs-cos-type=\"module\"><img loading=\"lazy\" decoding=\"async\" class=\"c-image-rounded-corners \" src=\"https:\/\/www.cybaverse.co.uk\/hs-fs\/hubfs\/Figure%203.webp?width=1037&amp;height=456&amp;name=Figure%203.webp\" alt=\"Figure 3 if the secret is very weak\/common it can automatically be detected with the right configuration\" width=\"1037\" height=\"456\" \/><\/div>\n<p style=\"text-align: center;\"><em><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"none\">Figure\u00a0<\/span><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"none\">3<\/span><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"none\">\u00a0if the secret is very weak\/common it can automatically be detected with the right configuration<\/span>\u00a0<\/em><\/p>\n<h5>4. JWT Authentication Bypass via kid Header Path Traversal<\/h5>\n<p>The kid (key ID) header is used by servers to identify which key should be used to verify the JWT signature. However, if improperly implemented, it can be abused to reference arbitrary files on the server, potentially exposing sensitive information or allowing attackers to bypass authentication.<\/p>\n<p><strong>How to Spot:<\/strong><\/p>\n<ul>\n<li>Check if the kid value is directly used to reference keys on the file system.<\/li>\n<li>Test for directory traversal by injecting values such as:..\/..\/..\/..\/var\/www\/html\/config.json..\/..\/..\/..\/dev\/null<\/li>\n<li>Observe whether the server attempts to retrieve a file from an unintended location. You may even get content back but more likely you will get indications it\u2019s trying to read the key. So, the next steps are.<\/li>\n<\/ul>\n<p>Bypassing Authentication with a Leaked Key<\/p>\n<p>If the application loads signing keys dynamically, an attacker can point kid to a stored key file, such as:<\/p>\n<p>{<\/p>\n<p>&#8220;alg&#8221;: &#8220;HS256&#8221;,<\/p>\n<p>&#8220;typ&#8221;: &#8220;JWT&#8221;,<\/p>\n<p>&#8220;kid&#8221;: &#8220;..\/..\/..\/..\/dev\/null&#8221;<\/p>\n<p>}<\/p>\n<p>If successful, the server will use the attacker-specified key, allowing them to forge a valid JWT and gain unauthorised access.<\/p>\n<ul>\n<li>Alternatively misconfigured applications may allow file uploads and later reference them as signing keys. If an attacker can upload their own key and specify it in the kid value, they gain full control over JWT verification.<\/li>\n<\/ul>\n<p><strong>Mitigation:\u00a0<\/strong><\/p>\n<ul>\n<li>Validate the kid value against a predefined set of trusted key IDs.<\/li>\n<li>Reject absolute or relative file paths and ensure kid values are not used directly in file operations.<\/li>\n<li>Sanitise user input to prevent directory traversal attacks.<\/li>\n<li>Use asymmetric encryption (RS256) instead of HMAC (HS256) to limit key-based manipulation.<\/li>\n<\/ul>\n<div id=\"hs_cos_wrapper_widget_d8a7ce44-95f1-4ba4-9a4d-7161aed2f277\" class=\"hs_cos_wrapper hs_cos_wrapper_widget hs_cos_wrapper_type_module\" data-hs-cos-general-type=\"widget\" data-hs-cos-type=\"module\"><img loading=\"lazy\" decoding=\"async\" class=\"c-image-rounded-corners \" src=\"https:\/\/www.cybaverse.co.uk\/hs-fs\/hubfs\/Figure%204.webp?width=1416&amp;height=844&amp;name=Figure%204.webp\" alt=\"Figure 4 shows a modified kid that was signed as NULL (00), which allowed us to sign our own JWT as an administrator and access the admin page shown to delete users\" width=\"1416\" height=\"844\" \/><\/div>\n<p style=\"text-align: center;\"><em><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"none\"><span data-ccp-parastyle=\"caption\">Figure\u00a0<\/span><\/span><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"none\">4<\/span><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"none\"><span data-ccp-parastyle=\"caption\">\u00a0shows a modified kid that was signed as NULL (00), which allowed us to sign our own JWT as an administrator and access the admin page shown to\u00a0<\/span><span data-ccp-parastyle=\"caption\">delete<\/span><span data-ccp-parastyle=\"caption\">\u00a0users<\/span><\/span><\/em><\/p>\n<h5><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"auto\">5. JWT Algorithm Confusion Attack<\/span><\/h5>\n<p><span data-contrast=\"auto\">JWTs allow different signing algorithms, and if the server isn\u2019t strict, attackers can change the algorithm to bypass verification.<\/span><span data-ccp-props=\"{&quot;134233117&quot;:true,&quot;134233118&quot;:true,&quot;201341983&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<p><strong>How Attackers Exploit It:\u00a0<\/strong><\/p>\n<p><span data-contrast=\"auto\">1. Obtain the application\u2019s public key, for example, from\u00a0<\/span><span data-contrast=\"auto\">\/jwks.json<\/span><span data-contrast=\"auto\">\u00a0or\u00a0<\/span><span data-contrast=\"auto\">.well-known\/jwks.json<\/span><span data-contrast=\"auto\">.<\/span><span data-ccp-props=\"{&quot;134233117&quot;:true,&quot;134233118&quot;:true,&quot;201341983&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">2. Modify the JWT header to\u00a0<\/span><strong><i><span data-contrast=\"none\">&#8220;alg&#8221;: &#8220;HS256&#8221;<\/span><\/i><\/strong><span data-contrast=\"auto\">.<\/span><span data-ccp-props=\"{&quot;134233117&quot;:true,&quot;134233118&quot;:true,&quot;201341983&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">3. Sign the JWT using the public key as the secret.<\/span><span data-ccp-props=\"{&quot;134233117&quot;:true,&quot;134233118&quot;:true,&quot;201341983&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">4. If the server allows switching from RS256 to HS256, it treats the public key as an HMAC secret and validates the token.<\/span><span data-ccp-props=\"{&quot;134233117&quot;:true,&quot;134233118&quot;:true,&quot;201341983&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<p><strong>Mitigation:<\/strong><\/p>\n<ul>\n<li><span data-contrast=\"auto\">Enforce strict algorithm validation and never allow switching from RS256 to HS256.<\/span><span data-ccp-props=\"{&quot;134233117&quot;:true,&quot;134233118&quot;:true,&quot;201341983&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/li>\n<li><span data-contrast=\"auto\">Prevent clients from modifying JWT headers.<\/span><span data-ccp-props=\"{&quot;134233117&quot;:true,&quot;134233118&quot;:true,&quot;201341983&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/li>\n<\/ul>\n<div id=\"hs_cos_wrapper_widget_d6c27ef3-66d8-4f1f-8e60-d68f94987300\" class=\"hs_cos_wrapper hs_cos_wrapper_widget hs_cos_wrapper_type_module\" data-hs-cos-general-type=\"widget\" data-hs-cos-type=\"module\"><img loading=\"lazy\" decoding=\"async\" class=\"c-image-rounded-corners \" src=\"https:\/\/www.cybaverse.co.uk\/hs-fs\/hubfs\/figure%205.webp?width=977&amp;height=185&amp;name=figure%205.webp\" alt=\"Figure 5 shows an example jwks publicly accessible that could be used in the attack detailed above\" width=\"977\" height=\"185\" \/><\/div>\n<p style=\"text-align: center;\"><em><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"none\">Figure\u00a0<\/span><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"none\">5<\/span><span lang=\"EN-GB\" xml:lang=\"EN-GB\" data-contrast=\"none\">\u00a0shows an example jwks publicly accessible that could be used in the attack detailed above<\/span>\u00a0<\/em><\/p>\n<p>These are just some of the documented attacks; there are many more, but hopefully, this provides insight into known JWT exploits.<\/p>\n<h4>Considerations To Lock JWTs Down<\/h4>\n<h6>1. Use Strong Signing Methods<\/h6>\n<ul>\n<li>It is generally better to use asymmetric signing (RS256 for example) instead of HS256.<\/li>\n<li>Ensure the server does not accept any variant of none, NONE, None as an algorithm.<\/li>\n<\/ul>\n<h6>2. Enforce Proper Authorisation<\/h6>\n<ul>\n<li>Authorisation claims should not be editable.<\/li>\n<li>Validate scopes, roles and permissions properly.<\/li>\n<li>Ensure critical claims like admin, role, or user_id cannot be modified by users.<\/li>\n<li>Ideally don\u2019t allow anything to be modified!<\/li>\n<\/ul>\n<h6>3. Implement Short Expiry &amp; Rotation<\/h6>\n<ul>\n<li>Keep access tokens short-lived.<\/li>\n<li>Use refresh tokens to generate new access tokens.<\/li>\n<li>Implement token revocation mechanisms like denylists.<\/li>\n<\/ul>\n<h6>4. Secure Storage<\/h6>\n<ul>\n<li>JWTs in localStorage or sessionStorage, could be vulnerable to XSS attacks.<\/li>\n<li>Use HttpOnly, Secure, SameSite cookies instead.<\/li>\n<\/ul>\n<h6>5. Prevent JWKS &amp; Header Injection Attacks<\/h6>\n<ul>\n<li>Hardcode trusted JWKS URLs and do not allow dynamic configuration.<\/li>\n<li>Validate kid (key ID) and jku (JWK Set URL) parameters against an allowlist.<\/li>\n<li>Sanitise user input to prevent path traversal in JWT headers.<\/li>\n<\/ul>\n<h6>6. Monitor &amp; Log Suspicious Activity<\/h6>\n<ul>\n<li>Log failed JWT validation attempts.<\/li>\n<li>Detect unusual login patterns (e.g., multiple logins from different locations).<\/li>\n<li>Rate limit authentication requests to prevent brute-force attacks.<\/li>\n<\/ul>\n<h4>Final Thoughts<\/h4>\n<p>JWTs are powerful, but they must be used securely. It is not uncommon for developers to assume JWTs are \u201csecure by design,\u201d but as we\u2019ve seen, poor implementations lead to serious vulnerabilities.<\/p>\n<p>Automated scanners won\u2019t find these issues out-of-the-box, which is why manual penetration testing is essential. If you\u2019re building or testing an application using JWTs, take the time to harden your implementation, before someone else finds the holes for you.<\/p>\n<p>At CybaVerse, our award-winning penetration testing services go beyond automated scans, uncovering critical vulnerabilities before attackers do. Get in touch to see how we can help secure your applications.<\/p>\n<p><a class=\" c-button c-button--primary-color c-button--medium\" href=\"https:\/\/www.cybaverse.co.uk\/pen-testing\">Learn More About Penetration Testing<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Not too long ago, JSON Web Tokens (JWTs) were widely regarded as a go-to solution for authentication, praised for their security, scalability, and simplicity. However, today, the penetration testing team at CybaVerse\u2014along with other security researchers\u2014frequently uncovers high and critical vulnerabilities in their implementations. The thing is automated scanners don\u2019t typically pick up JWT misconfigurations[\u2026] <a class=\"read-more\" href=\"https:\/\/www.cloudtango.net\/blog\/2025\/03\/06\/a-guide-to-json-web-tokens-jwts\/\">Read<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" enable-background=\"new 0 0 24 24\" height=\"16px\" viewBox=\"0 0 24 24\" width=\"16px\" fill=\"#091926\"><rect fill=\"none\" height=\"16\" width=\"16\"\/><path d=\"M14.29,5.71L14.29,5.71c-0.39,0.39-0.39,1.02,0,1.41L18.17,11H3c-0.55,0-1,0.45-1,1v0c0,0.55,0.45,1,1,1h15.18l-3.88,3.88 c-0.39,0.39-0.39,1.02,0,1.41l0,0c0.39,0.39,1.02,0.39,1.41,0l5.59-5.59c0.39-0.39,0.39-1.02,0-1.41L15.7,5.71 C15.32,5.32,14.68,5.32,14.29,5.71z\"\/><\/svg><\/a><\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7,15],"tags":[],"class_list":["post-728","post","type-post","status-publish","format-standard","hentry","category-cybersecurity","category-mssps"],"_links":{"self":[{"href":"https:\/\/www.cloudtango.net\/blog\/wp-json\/wp\/v2\/posts\/728","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cloudtango.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cloudtango.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cloudtango.net\/blog\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cloudtango.net\/blog\/wp-json\/wp\/v2\/comments?post=728"}],"version-history":[{"count":2,"href":"https:\/\/www.cloudtango.net\/blog\/wp-json\/wp\/v2\/posts\/728\/revisions"}],"predecessor-version":[{"id":731,"href":"https:\/\/www.cloudtango.net\/blog\/wp-json\/wp\/v2\/posts\/728\/revisions\/731"}],"wp:attachment":[{"href":"https:\/\/www.cloudtango.net\/blog\/wp-json\/wp\/v2\/media?parent=728"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cloudtango.net\/blog\/wp-json\/wp\/v2\/categories?post=728"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cloudtango.net\/blog\/wp-json\/wp\/v2\/tags?post=728"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}