CVE-2022-39227 (published 2022-09-23T07:15Z)

python-jwt is a module for generating and verifying JSON Web Tokens. Versions prior to 3.3.4 are subject to Authentication Bypass by Spoofing, resulting in identity spoofing, session hijacking or authentication bypass. An attacker who obtains a JWT can arbitrarily forge its contents without knowing the secret key. Depending on the application, this may for example enable the attacker to spoof other user's identities, hijack their sessions, or bypass authentication. Users should upgrade to version 3.3.4. There are no known workarounds.

CVSS v3 base score: 9.1, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N)

Weakness information:
cwe_id: CWE-290
cwe_name: Authentication Bypass by Spoofing
description: This attack-focused weakness is caused by incorrectly implemented authentication schemes that are subject to spoofing attacks.
extended_description: 
            
            
         
         

Fix information:
repo_url: https://github.com/davedoesdev/python-jwt
commit_details: repo_url: https://github.com/davedoesdev/python-jwt
author: David Halls
author_date: 2022-08-31 20:10:25+01:00
msg: FIX VULNERABILITY

jwcrypto accepts both compact and JSON formats.
It was possible to use this to present a token with arbitrary
claims with a signature from another valid token.

See test/vulnerability_vows.py for an example.
repository: repo_url: https://github.com/davedoesdev/python-jwt
repo_name: davedoesdev/python-jwt
description: Python module for generating and verifying JSON Web Tokens
repo_language: Python
owner: davedoesdev
file_changes: filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -2,6 +2,7 @@
 
 Module for generating and verifying [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html).
 
+- **Note:** Versions 3.3.4 and later fix a vulnerability in JSON Web Token verification which lets an attacker with a valid token to re-use its signature with modified claims. CVE to follow. Please upgrade!
 - **Note:** From version 2.0.1 the namespace has changed from `jwt` to `python_jwt`, in order to avoid conflict with [PyJWT](https://github.com/jpadilla/pyjwt).
 - **Note:** Versions 1.0.0 and later fix [a vulnerability](https://www.timmclean.net/2015/02/25/jwt-alg-none.html) in JSON Web Token verification so please upgrade if you're using this functionality. The API has changed so you will need to update your application. [verify_jwt](http://rawgit.davedoesdev.com/davedoesdev/python-jwt/master/docs/_build/html/index.html#python_jwt.verify_jwt) now requires you to specify which signature algorithms are allowed.
 - Uses [jwcrypto](https://jwcrypto.readthedocs.io) to do the heavy lifting.

programming_language: Markdown
filename: coverage.xml
change_type: ModificationType.MODIFY
diff: @@ -1,5 +1,5 @@
 <?xml version="1.0" ?>
-<coverage version="6.4.1" timestamp="1656832390379" lines-valid="88" lines-covered="88" line-rate="1" branches-valid="58" branches-covered="58" branch-rate="1" complexity="0">
+<coverage version="6.4.1" timestamp="1661973013420" lines-valid="96" lines-covered="96" line-rate="1" branches-valid="60" branches-covered="60" branch-rate="1" complexity="0">
 	<!-- Generated by coverage.py: https://coverage.readthedocs.io -->
 	<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
 	<sources>
@@ -16,89 +16,97 @@
 						<line number="7" hits="1"/>
 						<line number="8" hits="1"/>
 						<line number="9" hits="1"/>
-						<line number="12" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="15" hits="1"/>
-						<line number="17" hits="1"/>
-						<line number="59" hits="1"/>
-						<line number="64" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="65" hits="1"/>
-						<line number="66" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="67" hits="1"/>
+						<line number="10" hits="1"/>
+						<line number="13" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="16" hits="1"/>
+						<line number="18" hits="1"/>
+						<line number="60" hits="1"/>
+						<line number="65" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="66" hits="1"/>
+						<line number="67" hits="1" branch="true" condition-coverage="100% (2/2)"/>
 						<line number="68" hits="1"/>
-						<line number="70" hits="1"/>
-						<line number="72" hits="1"/>
-						<line number="74" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="75" hits="1"/>
-						<line number="77" hits="1"/>
+						<line number="69" hits="1"/>
+						<line number="71" hits="1"/>
+						<line number="73" hits="1"/>
+						<line number="75" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="76" hits="1"/>
 						<line number="78" hits="1"/>
-						<line number="80" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="81" hits="1"/>
-						<line number="82" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="83" hits="1"/>
-						<line number="85" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="86" hits="1"/>
-						<line number="88" hits="1"/>
+						<line number="79" hits="1"/>
+						<line number="81" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="82" hits="1"/>
+						<line number="83" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="84" hits="1"/>
+						<line number="86" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="87" hits="1"/>
 						<line number="89" hits="1"/>
 						<line number="90" hits="1"/>
 						<line number="91" hits="1"/>
-						<line number="93" hits="1"/>
-						<line number="101" hits="1"/>
-						<line number="142" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="143" hits="1"/>
-						<line number="145" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="147" hits="1"/>
-						<line number="149" hits="1"/>
-						<line number="151" hits="1"/>
+						<line number="92" hits="1"/>
+						<line number="94" hits="1"/>
+						<line number="102" hits="1"/>
+						<line number="103" hits="1"/>
+						<line number="104" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="105" hits="1"/>
+						<line number="107" hits="1"/>
+						<line number="150" hits="1"/>
+						<line number="152" hits="1" branch="true" condition-coverage="100% (2/2)"/>
 						<line number="153" hits="1"/>
-						<line number="154" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="155" hits="1"/>
-						<line number="156" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="155" hits="1" branch="true" condition-coverage="100% (2/2)"/>
 						<line number="157" hits="1"/>
-						<line number="159" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="160" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="161" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="162" hits="1"/>
-						<line number="163" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="164" hits="1"/>
+						<line number="159" hits="1"/>
+						<line number="161" hits="1"/>
+						<line number="163" hits="1"/>
+						<line number="164" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="165" hits="1"/>
 						<line number="166" hits="1" branch="true" condition-coverage="100% (2/2)"/>
 						<line number="167" hits="1"/>
-						<line number="168" hits="1"/>
-						<line number="169" hits="1"/>
+						<line number="169" hits="1" branch="true" condition-coverage="100% (2/2)"/>
 						<line number="170" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="171" hits="1"/>
-						<line number="173" hits="1"/>
-						<line number="175" hits="1"/>
-						<line number="176" hits="1"/>
+						<line number="171" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="172" hits="1"/>
+						<line number="173" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="174" hits="1"/>
+						<line number="176" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="177" hits="1"/>
 						<line number="178" hits="1"/>
-						<line number="179" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="180" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="181" hits="1"/>
-						<line number="182" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="183" hits="1"/>
-						<line number="185" hits="1"/>
-						<line number="186" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="187" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="188" hits="1"/>
-						<line number="189" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="190" hits="1"/>
+						<line number="179" hits="1"/>
+						<line number="180" hits="1"/>
+						<line number="181" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="182" hits="1"/>
+						<line number="184" hits="1"/>
+						<line number="186" hits="1"/>
+						<line number="187" hits="1"/>
+						<line number="189" hits="1"/>
+						<line number="190" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="191" hits="1" branch="true" condition-coverage="100% (2/2)"/>
 						<line number="192" hits="1"/>
 						<line number="193" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="194" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="195" hits="1"/>
-						<line number="196" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="197" hits="1"/>
+						<line number="194" hits="1"/>
+						<line number="196" hits="1"/>
+						<line number="197" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="198" hits="1" branch="true" condition-coverage="100% (2/2)"/>
 						<line number="199" hits="1"/>
 						<line number="200" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="201" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="202" hits="1"/>
-						<line number="203" hits="1" branch="true" condition-coverage="100% (2/2)"/>
-						<line number="204" hits="1"/>
+						<line number="201" hits="1"/>
+						<line number="203" hits="1"/>
+						<line number="204" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="205" hits="1" branch="true" condition-coverage="100% (2/2)"/>
 						<line number="206" hits="1"/>
+						<line number="207" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="208" hits="1"/>
 						<line number="210" hits="1"/>
-						<line number="222" hits="1"/>
-						<line number="223" hits="1"/>
-						<line number="224" hits="1"/>
-						<line number="225" hits="1"/>
+						<line number="211" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="212" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="213" hits="1"/>
+						<line number="214" hits="1" branch="true" condition-coverage="100% (2/2)"/>
+						<line number="215" hits="1"/>
+						<line number="217" hits="1"/>
+						<line number="221" hits="1"/>
+						<line number="233" hits="1"/>
+						<line number="234" hits="1"/>
+						<line number="235" hits="1"/>
+						<line number="236" hits="1"/>
+						<line number="237" hits="1"/>
 					</lines>
 				</class>
 			</classes>

programming_language: TypeScript
filename: index.html
change_type: ModificationType.MODIFY
diff: @@ -47,7 +47,7 @@
         </form>
         <p class="text">
             <a class="nav" href="https://coverage.readthedocs.io">coverage.py v6.4.1</a>,
-            created at 2022-07-03 08:13 +0100
+            created at 2022-08-31 20:10 +0100
         </p>
     </div>
 </header>
@@ -67,23 +67,23 @@
         <tbody>
             <tr class="file">
                 <td class="name left"><a href="d_7bfe6de028c7ad70___init___py.html">python_jwt/__init__.py</a></td>
-                <td>88</td>
+                <td>96</td>
                 <td>0</td>
                 <td>0</td>
-                <td>58</td>
+                <td>60</td>
                 <td>0</td>
-                <td class="right" data-ratio="146 146">100%</td>
+                <td class="right" data-ratio="156 156">100%</td>
             </tr>
         </tbody>
         <tfoot>
             <tr class="total">
                 <td class="name left">Total</td>
-                <td>88</td>
+                <td>96</td>
                 <td>0</td>
                 <td>0</td>
-                <td>58</td>
+                <td>60</td>
                 <td>0</td>
-                <td class="right" data-ratio="146 146">100%</td>
+                <td class="right" data-ratio="156 156">100%</td>
             </tr>
         </tfoot>
     </table>
@@ -95,7 +95,7 @@
     <div class="content">
         <p>
             <a class="nav" href="https://coverage.readthedocs.io">coverage.py v6.4.1</a>,
-            created at 2022-07-03 08:13 +0100
+            created at 2022-08-31 20:10 +0100
         </p>
     </div>
     <aside class="hidden">

programming_language: HTML
filename: status.json
change_type: ModificationType.MODIFY
diff: @@ -1 +1 @@
-{"format":2,"version":"6.4.1","globals":"808b5cd956a194fa2027f5f6eb2aaf06","files":{"d_7bfe6de028c7ad70___init___py":{"hash":"eee4edc908a3614db375ba99356b09c0","index":{"nums":[0,1,88,0,0,58,0,0],"html_filename":"d_7bfe6de028c7ad70___init___py.html","relative_filename":"python_jwt/__init__.py"}}}}
\ No newline at end of file
+{"format":2,"version":"6.4.1","globals":"808b5cd956a194fa2027f5f6eb2aaf06","files":{"d_7bfe6de028c7ad70___init___py":{"hash":"a3460828b320d5b58e5d56aeda5982f0","index":{"nums":[0,1,96,0,0,60,0,0],"html_filename":"d_7bfe6de028c7ad70___init___py.html","relative_filename":"python_jwt/__init__.py"}}}}
\ No newline at end of file

programming_language: SQL
filename: .buildinfo
change_type: ModificationType.MODIFY
diff: @@ -1,4 +1,4 @@
 # Sphinx build info version 1
 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
-config: e1ca4cbd0b61431a5536cbdf2d794e81
+config: 0bdc22a8bc7fdc3e0b3b52be398c1405
 tags: 645f666f9bcd5a90fca523b33c5a78b7

programming_language: Markdown
filename: documentation_options.js
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,6 @@
 var DOCUMENTATION_OPTIONS = {
     URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
-    VERSION: '3.3.3',
+    VERSION: '3.3.4',
     LANGUAGE: 'None',
     COLLAPSE_INDEX: false,
     BUILDER: 'html',

programming_language: JavaScript
filename: genindex.html
change_type: ModificationType.MODIFY
diff: @@ -5,7 +5,7 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Index &#8212; python-jwt 3.3.3 documentation</title>
+    <title>Index &#8212; python-jwt 3.3.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/nature.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -24,7 +24,7 @@
         <li class="right" >
           <a href="py-modindex.html" title="Python Module Index"
              >modules</a> |</li>
-        <li class="nav-item nav-item-0"><a href="index.html">python-jwt 3.3.3 documentation</a> &#187;</li>
+        <li class="nav-item nav-item-0"><a href="index.html">python-jwt 3.3.4 documentation</a> &#187;</li>
         <li class="nav-item nav-item-this"><a href="">Index</a></li> 
       </ul>
     </div>  
@@ -121,7 +121,7 @@
         <li class="right" >
           <a href="py-modindex.html" title="Python Module Index"
              >modules</a> |</li>
-        <li class="nav-item nav-item-0"><a href="index.html">python-jwt 3.3.3 documentation</a> &#187;</li>
+        <li class="nav-item nav-item-0"><a href="index.html">python-jwt 3.3.4 documentation</a> &#187;</li>
         <li class="nav-item nav-item-this"><a href="">Index</a></li> 
       </ul>
     </div>

programming_language: HTML
filename: index.html
change_type: ModificationType.MODIFY
diff: @@ -6,7 +6,7 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 
-    <title>python_jwt module &#8212; python-jwt 3.3.3 documentation</title>
+    <title>python_jwt module &#8212; python-jwt 3.3.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/nature.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -25,7 +25,7 @@
         <li class="right" >
           <a href="py-modindex.html" title="Python Module Index"
              >modules</a> |</li>
-        <li class="nav-item nav-item-0"><a href="#">python-jwt 3.3.3 documentation</a> &#187;</li>
+        <li class="nav-item nav-item-0"><a href="#">python-jwt 3.3.4 documentation</a> &#187;</li>
         <li class="nav-item nav-item-this"><a href="">python_jwt module</a></li> 
       </ul>
     </div>  
@@ -204,7 +204,7 @@
         <li class="right" >
           <a href="py-modindex.html" title="Python Module Index"
              >modules</a> |</li>
-        <li class="nav-item nav-item-0"><a href="#">python-jwt 3.3.3 documentation</a> &#187;</li>
+        <li class="nav-item nav-item-0"><a href="#">python-jwt 3.3.4 documentation</a> &#187;</li>
         <li class="nav-item nav-item-this"><a href="">python_jwt module</a></li> 
       </ul>
     </div>

programming_language: HTML
filename: py-modindex.html
change_type: ModificationType.MODIFY
diff: @@ -5,7 +5,7 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Python Module Index &#8212; python-jwt 3.3.3 documentation</title>
+    <title>Python Module Index &#8212; python-jwt 3.3.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/nature.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -31,7 +31,7 @@
         <li class="right" >
           <a href="#" title="Python Module Index"
              >modules</a> |</li>
-        <li class="nav-item nav-item-0"><a href="index.html">python-jwt 3.3.3 documentation</a> &#187;</li>
+        <li class="nav-item nav-item-0"><a href="index.html">python-jwt 3.3.4 documentation</a> &#187;</li>
         <li class="nav-item nav-item-this"><a href="">Python Module Index</a></li> 
       </ul>
     </div>  
@@ -89,7 +89,7 @@
         <li class="right" >
           <a href="#" title="Python Module Index"
              >modules</a> |</li>
-        <li class="nav-item nav-item-0"><a href="index.html">python-jwt 3.3.3 documentation</a> &#187;</li>
+        <li class="nav-item nav-item-0"><a href="index.html">python-jwt 3.3.4 documentation</a> &#187;</li>
         <li class="nav-item nav-item-this"><a href="">Python Module Index</a></li> 
       </ul>
     </div>

programming_language: HTML
filename: search.html
change_type: ModificationType.MODIFY
diff: @@ -5,7 +5,7 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Search &#8212; python-jwt 3.3.3 documentation</title>
+    <title>Search &#8212; python-jwt 3.3.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/nature.css" />
     
@@ -30,7 +30,7 @@
         <li class="right" >
           <a href="py-modindex.html" title="Python Module Index"
              >modules</a> |</li>
-        <li class="nav-item nav-item-0"><a href="index.html">python-jwt 3.3.3 documentation</a> &#187;</li>
+        <li class="nav-item nav-item-0"><a href="index.html">python-jwt 3.3.4 documentation</a> &#187;</li>
         <li class="nav-item nav-item-this"><a href="">Search</a></li> 
       </ul>
     </div>  
@@ -90,7 +90,7 @@
         <li class="right" >
           <a href="py-modindex.html" title="Python Module Index"
              >modules</a> |</li>
-        <li class="nav-item nav-item-0"><a href="index.html">python-jwt 3.3.3 documentation</a> &#187;</li>
+        <li class="nav-item nav-item-0"><a href="index.html">python-jwt 3.3.4 documentation</a> &#187;</li>
         <li class="nav-item nav-item-this"><a href="">Search</a></li> 
       </ul>
     </div>

programming_language: HTML
filename: conf.py
change_type: ModificationType.MODIFY
diff: @@ -52,7 +52,7 @@ copyright = u'2015, David Halls'
 # The short X.Y version.
 version = '3.3'
 # The full version, including alpha/beta/rc tags.
-release = '3.3.3'
+release = '3.3.4'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.

programming_language: Python
filename: __init__.py
change_type: ModificationType.MODIFY
diff: @@ -2,6 +2,7 @@
 Functions for generating and verifying JSON Web Tokens.
 """
 
+import re
 from datetime import datetime, timedelta
 from calendar import timegm
 from os import urandom
@@ -98,6 +99,11 @@ def generate_jwt(claims, priv_key=None,
 
 #pylint: disable=R0912,too-many-locals
 
+_jwt_re = re.compile(r'^[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]*$')
+def _check_jwt_format(jwt):
+    if not _jwt_re.match(jwt):
+        raise _JWTError('invalid JWT format')
+
 def verify_jwt(jwt,
                pub_key=None,
                allowed_algs=None,
@@ -139,6 +145,10 @@ def verify_jwt(jwt,
 
     :raises: If the token failed to verify.
     """
+    #pylint: disable=too-many-statements
+
+    _check_jwt_format(jwt)
+
     if allowed_algs is None:
         allowed_algs = []
 
@@ -167,10 +177,11 @@ def verify_jwt(jwt,
         token = JWS()
         token.allowed_algs = allowed_algs
         token.deserialize(jwt, pub_key)
+        parsed_claims = json_decode(token.payload)
     elif 'none' not in allowed_algs:
         raise _JWTError('no key but none alg not allowed')
-
-    parsed_claims = json_decode(base64url_decode(claims))
+    else:
+        parsed_claims = json_decode(base64url_decode(claims))
 
     utcnow = datetime.utcnow()
     now = timegm(utcnow.utctimetuple())
@@ -219,6 +230,7 @@ def process_jwt(jwt):
     :rtype: tuple
     :returns: ``(header, claims)``
     """
+    _check_jwt_format(jwt)
     header, claims, _ = jwt.split('.')
     parsed_header = json_decode(base64url_decode(header))
     parsed_claims = json_decode(base64url_decode(claims))

programming_language: Python
filename: setup.py
change_type: ModificationType.MODIFY
diff: @@ -6,7 +6,7 @@ def read(name):
 
 setup(
     name='python_jwt',
-    version='3.3.3',
+    version='3.3.4',
     description="Module for generating and verifying JSON Web Tokens",
     long_description=read('README.md'),
     long_description_content_type='text/markdown',

programming_language: Python
filename: vulnerability_vows.py
change_type: ModificationType.ADD
diff: @@ -0,0 +1,38 @@
+""" Test claim forgery vulnerability fix """
+from datetime import timedelta
+from json import loads, dumps
+from test.common import generated_keys
+from test import python_jwt as jwt
+from pyvows import Vows, expect
+from jwcrypto.common import base64url_decode, base64url_encode
+
+@Vows.batch
+class ForgedClaims(Vows.Context):
+    """ Check we get an error when payload is forged using mix of compact and JSON formats """
+    def topic(self):
+        """ Generate token """
+        payload = {'sub': 'alice'}
+        return jwt.generate_jwt(payload, generated_keys['PS256'], 'PS256', timedelta(minutes=60))
+
+    class PolyglotToken(Vows.Context):
+        """ Make a forged token """
+        def topic(self, topic):
+            """ Use mix of JSON and compact format to insert forged claims including long expiration """
+            [header, payload, signature] = topic.split('.')
+            parsed_payload = loads(base64url_decode(payload))
+            parsed_payload['sub'] = 'bob'
+            parsed_payload['exp'] = 2000000000
+            fake_payload = base64url_encode((dumps(parsed_payload, separators=(',', ':'))))
+            return '{"  ' + header + '.' + fake_payload + '.":"","protected":"' + header + '", "payload":"' + payload + '","signature":"' + signature + '"}'
+
+        class Verify(Vows.Context):
+            """ Check the forged token fails to verify """
+            @Vows.capture_error
+            def topic(self, topic):
+                """ Verify the forged token """
+                return jwt.verify_jwt(topic, generated_keys['PS256'], ['PS256'])
+
+            def token_should_not_verify(self, r):
+                """ Check the token doesn't verify due to mixed format being detected """
+                expect(r).to_be_an_error()
+                expect(str(r)).to_equal('invalid JWT format')

programming_language: Python