CVE-2022-39227: Authentication Bypass by Spoofing (CWE-290)
value: 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.
CWE description: This attack-focused weakness is caused by incorrectly implemented authentication schemes that are subject to spoofing attacks.
CVSS v3 base score: 9.1
Published: 2022-09-23T07:15Z
Repository: https://github.com/davedoesdev/python-jwt
Language: HTML
Files changed: README.md, coverage/coverage.xml, coverage/html/index.html, coverage/html/status.json, docs/_build/html/.buildinfo, docs/_build/html/_static/documentation_options.js, docs/_build/html/genindex.html, docs/_build/html/index.html, docs/_build/html/py-modindex.html, docs/_build/html/search.html, docs/conf.py, python_jwt/__init__.py, setup.py, test/vulnerability_vows.py
Commit date: 2022-08-31 20:10:25 +0100
Commit message: 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.

Diff: diff --git a/README.md b/README.md
index 7af34cf..d7e2514 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,10 @@
 # python-jwt&nbsp;&nbsp;&nbsp;[![Build Status](https://github.com/davedoesdev/python-jwt/actions/workflows/ci.yml/badge.svg)](https://github.com/davedoesdev/python-jwt/actions/workflows/ci.yml) [![Coverage Status](https://coveralls.io/repos/github/davedoesdev/python-jwt/badge.svg?branch=master)](https://coveralls.io/github/davedoesdev/python-jwt?branch=master) [![PyPI version](https://badge.fury.io/py/python-jwt.svg)](https://badge.fury.io/py/python-jwt)
 
 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.
 - Supports [__RS256__, __RS384__, __RS512__](https://tools.ietf.org/html/rfc7518#section-3.3), [__PS256__, __PS384__, __PS512__](https://tools.ietf.org/html/rfc7518#section-3.5), [__HS256__, __HS384__, __HS512__](https://tools.ietf.org/html/rfc7518#section-3.2), [__ES256__, __ES384__, __ES512__](https://tools.ietf.org/html/rfc7518#section-3.4), [__ES256K__](https://tools.ietf.org/html/draft-ietf-cose-webauthn-algorithms-05#section-3.2), [__EdDSA__](https://tools.ietf.org/html/rfc8037#section-3.1) and [__none__](http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-14#section-3.6) signature algorithms.
 - Unit tests, including tests for interoperability with [jose](https://github.com/panva/jose).
diff --git a/coverage/coverage.xml b/coverage/coverage.xml
index 6f56598..37f7e58 100644
--- a/coverage/coverage.xml
+++ b/coverage/coverage.xml
@@ -1,7 +1,7 @@
 <?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>
 		<source>/home/david/python-jwt</source>
 	</sources>
@@ -14,93 +14,101 @@
 						<line number="5" hits="1"/>
 						<line number="6" hits="1"/>
 						<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>
 		</package>
 	</packages>
diff --git a/coverage/html/index.html b/coverage/html/index.html
index 7cf8506..5f76858 100644
--- a/coverage/html/index.html
+++ b/coverage/html/index.html
@@ -45,11 +45,11 @@
         <form id="filter_container">
             <input id="filter" type="text" value="" placeholder="filter..." />
         </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>
 <main id="index">
     <table class="index" data-sortable>
@@ -65,27 +65,27 @@
             </tr>
         </thead>
         <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>
     <p id="no_rows">
         No items found using the specified filter.
@@ -93,11 +93,11 @@
 </main>
 <footer>
     <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">
         <a id="prevFileLink" class="nav" href="d_7bfe6de028c7ad70___init___py.html"/>
         <a id="nextFileLink" class="nav" href="d_7bfe6de028c7ad70___init___py.html"/>
diff --git a/coverage/html/status.json b/coverage/html/status.json
index 4655306..0e8403a 100644
--- a/coverage/html/status.json
+++ b/coverage/html/status.json
@@ -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
diff --git a/docs/_build/doctrees/environment.pickle b/docs/_build/doctrees/environment.pickle
index 6d0b67f..8f84cbc 100644
Binary files a/docs/_build/doctrees/environment.pickle and b/docs/_build/doctrees/environment.pickle differ
diff --git a/docs/_build/html/.buildinfo b/docs/_build/html/.buildinfo
index 633d6c1..8a657c4 100644
--- a/docs/_build/html/.buildinfo
+++ b/docs/_build/html/.buildinfo
@@ -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
diff --git a/docs/_build/html/_static/documentation_options.js b/docs/_build/html/_static/documentation_options.js
index 5582773..4071b41 100644
--- a/docs/_build/html/_static/documentation_options.js
+++ b/docs/_build/html/_static/documentation_options.js
@@ -1,8 +1,8 @@
 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',
     FILE_SUFFIX: '.html',
     LINK_SUFFIX: '.html',
diff --git a/docs/_build/html/genindex.html b/docs/_build/html/genindex.html
index 9bd339d..50e4569 100644
--- a/docs/_build/html/genindex.html
+++ b/docs/_build/html/genindex.html
@@ -3,11 +3,11 @@
 
 <html>
   <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>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
@@ -22,11 +22,11 @@
           <a href="#" title="General Index"
              accesskey="I">index</a></li>
         <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>  
 
     <div class="document">
@@ -119,11 +119,11 @@
           <a href="#" title="General Index"
              >index</a></li>
         <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>
     <div class="footer" role="contentinfo">
         &#169; Copyright 2015, David Halls.
diff --git a/docs/_build/html/index.html b/docs/_build/html/index.html
index 6bf4ffd..014e61e 100644
--- a/docs/_build/html/index.html
+++ b/docs/_build/html/index.html
@@ -4,11 +4,11 @@
 <html>
   <head>
     <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>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
@@ -23,11 +23,11 @@
           <a href="genindex.html" title="General Index"
              accesskey="I">index</a></li>
         <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>  
 
     <div class="document">
@@ -202,11 +202,11 @@
           <a href="genindex.html" title="General Index"
              >index</a></li>
         <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>
     <div class="footer" role="contentinfo">
         &#169; Copyright 2015, David Halls.
diff --git a/docs/_build/html/py-modindex.html b/docs/_build/html/py-modindex.html
index 06c3188..7e3bedd 100644
--- a/docs/_build/html/py-modindex.html
+++ b/docs/_build/html/py-modindex.html
@@ -3,11 +3,11 @@
 
 <html>
   <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>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
@@ -29,11 +29,11 @@
           <a href="genindex.html" title="General Index"
              accesskey="I">index</a></li>
         <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>  
 
     <div class="document">
@@ -87,11 +87,11 @@
           <a href="genindex.html" title="General Index"
              >index</a></li>
         <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>
     <div class="footer" role="contentinfo">
         &#169; Copyright 2015, David Halls.
diff --git a/docs/_build/html/search.html b/docs/_build/html/search.html
index 71a6207..2c40f9d 100644
--- a/docs/_build/html/search.html
+++ b/docs/_build/html/search.html
@@ -3,11 +3,11 @@
 
 <html>
   <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" />
     
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
@@ -28,11 +28,11 @@
           <a href="genindex.html" title="General Index"
              accesskey="I">index</a></li>
         <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>  
 
     <div class="document">
@@ -88,11 +88,11 @@
           <a href="genindex.html" title="General Index"
              >index</a></li>
         <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>
     <div class="footer" role="contentinfo">
         &#169; Copyright 2015, David Halls.
diff --git a/docs/conf.py b/docs/conf.py
index f70ed67..16ef325 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -50,11 +50,11 @@ copyright = u'2015, David Halls'
 # built documents.
 #
 # 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.
 #language = None
 
diff --git a/python_jwt/__init__.py b/python_jwt/__init__.py
index 4ff686a..cf58f99 100644
--- a/python_jwt/__init__.py
+++ b/python_jwt/__init__.py
@@ -1,9 +1,10 @@
 """
 Functions for generating and verifying JSON Web Tokens.
 """
 
+import re
 from datetime import datetime, timedelta
 from calendar import timegm
 from os import urandom
 from jwcrypto.jws import JWS, JWSHeaderRegistry
 from jwcrypto.common import base64url_encode, base64url_decode, \
@@ -96,10 +97,15 @@ def generate_jwt(claims, priv_key=None,
         signature
     )
 
 #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,
                iat_skew=timedelta(),
                checks_optional=False,
@@ -137,10 +143,14 @@ def verify_jwt(jwt,
       - Its claims must contain a property **nbf** which represents a date in the past.
       - Its claims must contain a property **exp** which represents a date in the future.
 
     :raises: If the token failed to verify.
     """
+    #pylint: disable=too-many-statements
+
+    _check_jwt_format(jwt)
+
     if allowed_algs is None:
         allowed_algs = []
 
     if not isinstance(allowed_algs, list):
         # jwcrypto only supports list of allowed algorithms
@@ -165,14 +175,15 @@ def verify_jwt(jwt,
 
     if pub_key:
         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())
 
     typ = parsed_header.get('typ')
@@ -217,9 +228,10 @@ def process_jwt(jwt):
     :type jwt: str or unicode
 
     :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))
     return parsed_header, parsed_claims
diff --git a/setup.py b/setup.py
index 7b3ab9f..a87e9a2 100644
--- a/setup.py
+++ b/setup.py
@@ -4,11 +4,11 @@ from setuptools import setup
 def read(name):
     return open(os.path.join(os.path.dirname(__file__), name)).read()
 
 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',
     keywords='',
     author='David Halls',
diff --git a/test/vulnerability_vows.py b/test/vulnerability_vows.py
new file mode 100644
index 0000000..65c1d14
--- /dev/null
+++ b/test/vulnerability_vows.py
@@ -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')

Vulnerable code: <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">
						<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="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="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="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="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="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="168" hits="1"/>
						<line number="169" hits="1"/>
						<line number="171" hits="1"/>
						<line number="173" hits="1"/>
						<line number="175" hits="1"/>
						<line number="176" 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="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="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="222" hits="1"/>
						<line number="223" hits="1"/>
						<line number="224" hits="1"/>
						<line number="225" hits="1"/>
            created at 2022-07-03 08:13 +0100
                <td>88</td>
                <td>58</td>
                <td class="right" data-ratio="146 146">100%</td>
                <td>88</td>
                <td>58</td>
                <td class="right" data-ratio="146 146">100%</td>
            created at 2022-07-03 08:13 +0100
{"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"}}}}
config: e1ca4cbd0b61431a5536cbdf2d794e81
    VERSION: '3.3.3',
    <title>Index &#8212; python-jwt 3.3.3 documentation</title>
        <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.3 documentation</a> &#187;</li>
    <title>python_jwt module &#8212; python-jwt 3.3.3 documentation</title>
        <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.3 documentation</a> &#187;</li>
    <title>Python Module Index &#8212; python-jwt 3.3.3 documentation</title>
        <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.3 documentation</a> &#187;</li>
    <title>Search &#8212; python-jwt 3.3.3 documentation</title>
        <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.3 documentation</a> &#187;</li>
release = '3.3.3'

    parsed_claims = json_decode(base64url_decode(claims))
    version='3.3.3',
Fixed code: - **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!
<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">
						<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="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="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="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="155" hits="1" branch="true" condition-coverage="100% (2/2)"/>
						<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="169" hits="1" branch="true" condition-coverage="100% (2/2)"/>
						<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="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="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="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="207" hits="1" branch="true" condition-coverage="100% (2/2)"/>
						<line number="208" 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"/>
            created at 2022-08-31 20:10 +0100
                <td>96</td>
                <td>60</td>
                <td class="right" data-ratio="156 156">100%</td>
                <td>96</td>
                <td>60</td>
                <td class="right" data-ratio="156 156">100%</td>
            created at 2022-08-31 20:10 +0100
{"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"}}}}
config: 0bdc22a8bc7fdc3e0b3b52be398c1405
    VERSION: '3.3.4',
    <title>Index &#8212; python-jwt 3.3.4 documentation</title>
        <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-0"><a href="index.html">python-jwt 3.3.4 documentation</a> &#187;</li>
    <title>python_jwt module &#8212; python-jwt 3.3.4 documentation</title>
        <li class="nav-item nav-item-0"><a href="#">python-jwt 3.3.4 documentation</a> &#187;</li>
        <li class="nav-item nav-item-0"><a href="#">python-jwt 3.3.4 documentation</a> &#187;</li>
    <title>Python Module Index &#8212; python-jwt 3.3.4 documentation</title>
        <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-0"><a href="index.html">python-jwt 3.3.4 documentation</a> &#187;</li>
    <title>Search &#8212; python-jwt 3.3.4 documentation</title>
        <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-0"><a href="index.html">python-jwt 3.3.4 documentation</a> &#187;</li>
release = '3.3.4'
import re
_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')

    #pylint: disable=too-many-statements

    _check_jwt_format(jwt)

        parsed_claims = json_decode(token.payload)
    else:
        parsed_claims = json_decode(base64url_decode(claims))
    _check_jwt_format(jwt)
    version='3.3.4',
""" 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')