Hacker Time
Presented by: Evan Johnson
Originally aired on January 17, 2022 @ 6:30 PM - 7:00 PM EST
Join Evan Johnson as he speaks with security professionals about recent security news!
Original Airdate: October 23, 2020
English
Hacker Time
Security
News
Transcript (Beta)
Good morning everybody. I'm Evan Johnson from the product security team and welcome to Hacker Time.
I am happy that you're all with me this morning and we're going to pick up where we left off last week this week and we're going to be talking more about JWTs, one of the hottest pieces of of technologies over the past few years and just about any system that you're modern system that you're using is using JWTs for authentication and authorization.
In some contexts if you peel back the layers enough on any distributed system you're going to find some JWTs somewhere and they're very controversial in the community as in the security community as something where that's been problematic over the last few years and that's they're super valid criticisms of them but you're also not going to avoid them.
They're here to stay and they are an important piece of technology in every stack and so we're going to talk about just some best practices to to handle them properly and last week where we left off we were discussing JWTs.
I kind of gave you showed you the ropes a little bit it was a probably a little bit terse information to cover since it's seven RFCs in a 30-minute segment and I did some live coding but it was I thought it went okay and we're going to pick up where we left off there but I'll do a real quick short recap on where we left off and what JWTs are and then we'll pick up with some live coding showing you how to be safe with JWTs and putting you kind of in a real world situation and showing you a way that you can be successful and secure with JWTs and so JWTs are these cryptographic tokens and they're they're used to this jwt.io kind of a famous website now for JWTs and great for visualizing what's actually happening under the hood you'll see that they're made up of base64 encoded segments the middle segment depending on the type of token there's many types of there's multiple types of tokens but depending on the type of token one of these base64 encoded segments is going to be the actual data in this case it's the purple section and you'll see that oops I resized and it's gone here we go you'll see that in this token there's just a little bit of information sub name and the issued time and then the signature back here is the cryptographically signed signature of all of this and so it's there's many different algorithms you can use to protect these tokens there's many different many different like cryptographic operations you can do to sign a token you can use asymmetric though you can use in this case this is symmetric decryptography there's there's a bunch of there's a bunch of possibilities for JWT tokens and so there's a lot of information unpacked but the important thing to realize is no matter what JWT or algorithm you're using or whatever there's going to be these base64 encoded segments and the whole point of it is to store some data and usually prove who you are prove prove that you are that somebody says you are who you say you are or in some cases if it's a symmetric crypto system you can just prove that you have the key there's there's a lot of possibilities and it really depends on the details of what the the system is doing but they are super super hackable I guess in the sense that you can kind of fit JWTs into a lot of use cases and that's why you've seen them be so prolific over the last few years okay so I think that's enough of a short recap on where we were and I want to pick up with some of the code we're writing we're writing a little bit of go code here and let me make sure that you can all read this because you're watching on Cloudflare TV this is perfect and we've got our agenda on the right and our code on the left and so you'll see that I've got some some code here that is creating a new JWT it is kind of doing a couple things creating a new JWT it is signing some of that data with an RSA private key and then it is verifying that same data and so it is basically the bare bones of what you'll see in any JSON web token system something's issuing the tokens something's signing them they're signing some data and then something's receiving them something is verifying them so it's this is basically the bare bones of what you'll see as both an issuer on the issuer side and on the the consumer side where you're using the JWT and so last week I was actually using a different package I was using I got stuck a little bit while live programming because I couldn't really navigate the docs but was using the old square GoJose JWT library and I had a lot more success when I retried what I was doing with the newer package I'm not sure why I didn't use this one I think I go I used go get to fetch this package and it didn't work for some reason but it worked worked the second time I tried and so and then I got this code working in just a minute or two and so let's take a look at this code so our goal is to make sure that we have something usable where we're creating a key we understand the key and the relationship between issuers and consumers then we want to create a new we want to create a new JWT token that has some data in it look at it make sure it's right and then we want to verify that and then last we want to have some tests in place to make sure make sure that all of this is is going properly so let's start with the private key you'll see here that we are generating an RSA private key of 2048 bits and using the RSA crypto RSA package and pretty basic stuff here we can print out the key just to make sure that we like it and so having a different key each time tells us that the signature on the JWT token is going to be different each time every time we run our little program so but we can actually take a look at this and go run the program and we should see uh-oh go run main.go we should see this big key print out every single time and it should be just a bunch of random junk that's the actual RSA public and private keys because this RSA private key object has both of them in it so it's a lot of text you'll see a bunch of exponents and whatnot okay so that's the private key looks right to me I got I'm can't really verify it uh offhand here but the next part that's actually doing something JWT related is we're creating a new signing key and so having a private key generating a RSA key implies that we're going to be the ones with the private key and receivers are going to be using the public key to verify that to be to be verifying the tokens that we have so the relationship will be kind of a client server model where the server is going to be issuing JWT tokens potentially to many clients and many clients will be able to use this public key from uh that's being distributed by the server to verify all the JWT tokens and that can be a lot to kind of unpack but this is pretty common in some real world examples like the go uh the google's open id connect keys are our public keys where google is telling the world these are the public keys of the of the open id connect tokens that you'll be validating and they share those out with the world and then every consumer can use those public keys to validate that anything that's signed is from google so that's a common real world example of this exact type of thing I don't think it's 2048 bit RSA key but it's the same same kind of model um okay so we create this new signer and this new signer is uh we might want to pull up the docs just to take a look at what it actually is but go jose new designer so we'll get us there pretty fast so we can look at the object make sure we understand it make sure we know what it does okay this is kind of small i'll make sure to make it bigger so a new signer returns you guessed it a signer this is uh kind of sounds very object oriented ish and um with a public key that's what we've got here um it takes the it takes in the private key and the algorithm that you're using and generates you a new signing key doesn't look like it's doing much though because it looks like you're passing most of the attributes this new signer function is probably not doing that much yeah calling new multi signer which uh which populates some extra stuff and and uh returns it so this signer object is mostly for using these uh going to be for using these signing functions so let's let's make sure we find those signer signer.sign where's that sign yeah so signer is an interface so all signers have a sign and an options which makes a lot of sense so any signer that you have you can just assume that it has a dot sign and a dot options and uh and that's really kind of a simple api to use because you just call that sign and you have a json web signature which is really nice so um that's exactly what we do here we create a new payload which is just lorem ipsum this is actually the um this code that i'm using is the example code from this package for new signing and verifying operation it's just uh the example code and so they have lorem ipsum in here and uh they turn it into a byte uh array of bytes and then they sign it and you get back what do you get when you call signer .sign a json web signature which has a list of signatures and what else can you do with this thing i'm sure there's some functions you can call on it you can call uh compact serialize detach a bunch of stuff uh on this on this json web signature so this might be actually instead of object i'm going to call this json web signature jws all right we're improving the uh the example here so good for us okay so we've got that we've we've called signer.sign here and if it doesn't work we're gonna handle that error by family and then last we serialize the object so uh this actually compact serialize when when we're visualizing the jwt with the dots and the base64 encoded segments of it that's actually what we're looking at the the serialized version of the json web signature to be a string so um once we take this signed json web signature we've got to serialize it into into being an actual token and so um here we can actually print it so i'm gonna get rid of printing that key up there a lot of data oh i already did it great i only have two print statements one is lorem ipsum and one is uh what's being serialized so let's take a look should look like a bunch of base64 dot some base64 dot base64 and it does we've got json we've got some base64 here a dot right here some base64 here all the way to the end uh we've got so base64 dot base64 this the payload dot and then a really hefty signature here um pretty long but uh we don't really care how long that is but you'll notice that the uh this base64 encoded text is about the same length as lorem ipsum and that's because this is this is the lorem ipsum that data is not encrypted in any way it's all in plain text and so um it just says lorem ipsum here base64 encoded and um and we can validate that pretty simply by doing one of these echo sometimes this doesn't work i'll show you my life hack for making it work yeah uh we've got our lorem ipsum here sometimes since this is compact serialized and you're if you're running base64 like this it'll fail and uh you actually need to add some padding so you'll see that the uh my output is the full lorem ipsum text now because i added this this extra um equal sign here okay that's a lot but uh we've printed out our jwt token and now as somebody with a signed jwt token i could send it to you i could send it to whomever and they could verify it using the public key um but how do you actually verify that with the public key let's let's do two things first i want to print out the public key so we can actually look at it second of all i want to verify this and and uh and luckily it's very simple you just call jws verify uh we'll we'll check out the docs there in just a second but the first thing i want to do is probably just print out the public key i want to make sure that uh what we're looking at here and you'll you'll uh remember earlier i said that the private key object this is actually a right when you press when you run our rsa generate key it actually is a returning an object which has your private key and your public key in it and so we can just pull out that public key here to print it um so on this all right so this is the actual public key you'll see our um our uh exponent you'll see some other numbers must be some some uh a lot of math going on behind the scenes in in rsa that i'm not gonna uh try to explain the discrete logarithm or whatever problems that are happening behind the scenes but uh this looks like a public key to me it looks right and um i want to be able to verify this text and you'll see that we call let's take a look at the docs and we call verify on this on a json web signature we just call verify and it takes in verification key interface which in this case is a public key interface the verification he uh that's a bit of a mystery where's that coming from verify validates a signature on the object and returns a payload the function does not support multi-signature if you desire multi-sign verification use verify multi be careful when verifying signatures based on embedded jwks inside the payload header you cannot assume huh and let's take a look at the verify function and see what it calls calls detached verify pass the verification key and detach verify calls uh creates a new verifier from the verification key and does a bunch of stuff all right well that leads us to our final problem uh that we're going to address today so we are uh we've verified uh the jwt token with this verify function but there's a lot of stuff going on there and a lot of math uh that is not always easy to debug and make sure it's working properly so one thing that i like to do when i'm using a new um when i'm using a new jwt package you'll see we're using go jose here version 2 which is probably the most used jwt package on the planet since since uh since go jose was written uh pretty early on in the use of jwts and then after after a lot of going projects started uh using jwts for things like uh a lot of the new kind of going systems that have been built over the last couple years so uh go jose has been around the block and is pretty trusted you'll see it has about 1.7k stars uh active community where um you'll see in go jose v2 um in the source code you'll see it's not getting updated daily but it's uh for a long time it was a big project that people worked on and um and it should be pretty robust and free of bugs so uh but how can we make sure of that i think a lot of times people find themselves in a situation where they're not writing uh code using the best uh packages where maybe they're writing in a language where there's not great jwt support go jose has been around for years but a lot of languages might not have uh great jwt support libraries and so um so what do you do and the short answer is you should well the long the the short answer and easy answer is you should probably be testing your library to make sure it's working properly a little bit before uh putting all of your eggs in that and some ways to do that is uh some basic unit tests and i think this is a great way to prevent vulnerabilities in your code or find vulnerabilities in other people's code and so the first thing i'm going to do is uh call this the jwt package and kind of rename this from main to um to uh and sign verify so we're generating a key we're signing a jwt and then we're verifying it and um and i'm going to re -architect this code and pull pieces of it out to make sure that we're testing some ways that jwts can fail and so the most obvious one is signature verification um i'm uh what if this math that's going on behind the scenes and all of this rsa what if there's a flaw in the way that uh this this this package is uh verifying the uh the function or verifying the jwt so the first thing i'm going to do is let's rename uh let us rename this to be jwt.gov mv main.gov to be jwt.gov and we want to open jwttest.gov and we want to start writing some tests and we want to first have a package that goes a test uh test uh basic signature validation and then uh let's have to google this for some reason the testing interface i always get wrong when i'm writing it and go i think this is right there so i always have to double check though because i always get it wrong yeah testing.t i don't see i don't know why i did the capital t here um okay so we want to basically probably we want to use one consistent key here so for all of our tests so we probably want to first well where i'm where's my cursor we probably want to first have a private key which is a rsa private key and wow this code is really mad at me i'm going to fix this what is it um oh it seems okay with me now we have a private key here and then in the init function we're going to actually instantiate the private key and that way all of our tests will have the same private key um we can actually panic in an init i think that's actually reasonable but we probably don't want to be panicking elsewhere and then we should just like have a private key let's print the private key and run the test and make sure so we can iterate on it uh go test output declaration declared and not used okay we're gonna have a lot of problems here 40 output declaration declared and not used print it so it doesn't complain and then private key declared and not used uh this is a more nuanced one oh man all right this will do cannot use type and this needs to be a pointer of course okay so we're actually in a place where we can test the code now i believe go test undefined error where who says so yeah we we just don't need this code anymore and there's going to be a problem we need to pass in an rsa private key called and this might work nope undefined rsa private key just doing some uh re-architecting here oh yeah duh we need a dot here and it works okay we can actually run the code we've re-architected a little bit where we're generating a private key and we can start to actually test this function is working properly and so what we want to do is we want to be able to test that the signing is uh is working and so i hope we can do that in the next two minutes 45 seconds and so how to test that the signing is working you have this big long uh dot delimited jwt and you want to mess with some of the characters uh before the very last segment the last segment's always a signature and you just want to mess with some of the characters and make sure that uh that the thing is still getting signed properly uh and so uh to do that we want to generate a token and then verify the token uh after we mess with it and so we want to now this is just sign verify and we want to remove sign uh and return a string we want to verify and return a bool so sign should give us the token verify should tell us yes no and this is going to take in an rsa public key uh pub and it's going to be a pointer or save public key i believe that's how it works and um let's pub and we'll uh false here otherwise return pro and here after we have output and output no uh compact serial this is actually what we wanted to turn up here yep this looks right to me i think this will work how much time do we have one minute oh no we're not going to finish that is too bad okay uh i will make sure that this code is on github and finish it in the next minute or so uh after this ends but i appreciate you joining me and the thing that i want to really drive home here is when you have a new library or you have jwts there's a lot of complexity to fear especially when you're first getting started with it but you can actually try to keep things on the rails quite a bit by writing some basic tests and testing for some of the things that can go wrong with jwts and they they are scary and there is a lot to learn and a lot of complexity but um but the basic ones the basic issues that keep coming back with them are pretty easy to test for and so um taking some time and writing a test for the famous algorithm equals none bug if you don't know about that google it because it's so fascinating it's pretty easy and so thank you for joining me adios everybody