Hacker Time
Presented by: Evan Johnson
Originally aired on February 18, 2021 @ 6:30 PM - 7:00 PM EST
Join Evan Johnson as he speaks with security professionals about recent security news!
English
Security
Transcript (Beta)
All right, good morning and welcome to Hacker Time. I'm Evan Johnson from Cloudflare's security team and I'm going to be talking to you about things that are more regularly scheduled on Hacker Time.
The past couple weeks I've been talking about chess and playing some chess on stream trying to teach you all a little bit of something that I've been trying to learn in the spirit of hacking and learning more and today we're going back to the same thing, learning more and going deep on a certain topic but this time it's going to be about something more related to computers, less related to chess, JSON web tokens.
And I thought this would be a valuable discussion to have because they are so controversial, so important and they are a really important tool to understand deeply and there's a lot of cruft to get through when it comes to JWTs to understand kind of at the root what they are, what they're doing, why they exist.
But once you get it, it makes a lot of sense and they're just a tool like any other tool in your toolbox for building distributed systems.
And so with that I'll kind of jump in. This is my agenda here in Vim.
I want to go over what they are. I want to do a vocabulary lesson because there is a lot of vocabulary.
I want to talk deeply about the controversies around them and then I'll code up a quick example and I don't think we'll get through all of this in 30 minutes.
Maybe we will, maybe we won't. Maybe this will be a multi-part series but we'll see and I won't leave you hanging.
If I don't get through it all, I'll make sure that I get all of this information out there.
And so without further ado, I'm going to try to jump in, start explaining them and then carry on with the agenda.
So I'm sure you have all seen this website. This is jwt.io, the debugger.
And this is a tool that many people referenced in order to kind of in order to play with the JWT syntax and see how it all works.
And so the most important thing or the first thing to notice about a JWT and the first thing to note is that they have a very specific format.
And so you'll see in the JWT here, you see it has three parts.
This red section, this purple section, and this blue section.
And the first section is the header. The second section is the actual payload and the last is the cryptographic signature.
And so JWTs, you'll see, these are just cryptographic tokens.
It is a cryptographic token that has been cryptographically signed by a key.
And the thing to note about them is they're just a string of characters with some data inside.
And so there's a lot of other similar technologies out there that do the same thing.
It could just be like an encrypted message or an encrypted message.
It's just a bunch of characters with some data inside and usually a signature verifying what it is.
Could be an encrypted cookie or like a Ruby on Rails session, the way that those work, where it's just a bunch of data packaged up and signed by a key.
And so the real kind of thing that's different about JSON Web Tokens is they have this header section.
And the header section is very important because the header section describes the way that the rest of the token was encrypted.
And so you'll see that because this token is delimited by periods, there's one here, one here, it's nice and neat to parse this thing.
You can really do it in a single command in JavaScript, where you just say split on the period and you'll end up with three parts, the header, the payload and the signature.
And so the header, the way that JSON Web Tokens work is the header tells you what algorithm to use to encrypt this payload.
And then also, or sorry, to sign this payload in this case, I'm using the wrong vocabulary here.
It tells you what algorithm to use to sign or encrypt the payload.
You can do either, in this case, it is a signature and also by extension, how you verify the token.
And there's a famous cryptographic doom principle.
I think that term might've been coined by Moxie Marlinspike, a kind of well-known security researcher, where the cryptographic doom principle, it might predate him, but I'm not exactly sure on the history of that term.
But the cryptographic doom principle says you always should be verifying signatures and verifying the cryptography on something before you use any of the data from it.
And so JWTs technically violate that rule because in order to, let's say I have a string or a piece of data.
In this case, I want to sign the word John Doe. I want to prove that this little payload says I'm John Doe in the JWT.
In order to verify this signature, this thing as a receiver of this token, I'd have to first look at this, which isn't encrypted or protected in any way.
Well, it technically is, but we'll get into that.
I'd have to look at this, take a value from this, technically the HS256, and use that to verify this signature.
And so the ordering, a lot of cryptographers and a lot of just security researchers in general say that this is a fundamentally bad design.
And so JWTs are very, very controversial for that reason amongst others.
And so that's one thing to note. But overall, I hope that this has been kind of an all over introduction to JWTs.
But the important thing to note is they're just strings of characters that hold data.
And there's some signature or encryption operation done to it.
And it can be treated like encrypted anything, really.
It's just a piece of protected data. No different than an encrypted session or an encrypted message or whatever.
Okay. So that is the kind of introduction to what a JWT is.
But there is so much other vocabulary. This is one of my core criticisms here.
So I'm pulling up a slideshow that I made many, many years ago, kind of getting into all of this.
And I believe I have a slide here that...
Okay. So I don't have... I don't know which slide it is that I'm looking for.
But the thing that I really knock JWT for and that I don't like about it is there is so much vocabulary.
There is technically JWT, JSON Web Token is part of a larger specification called Hosea, which is JavaScript object, something, an encryption JavaScript object, signing and encryption.
That was a tough one. So it's JWT is part of the Hosea specification.
And within the Hosea specification, there's JSON Web Tokens, there's JSON Web Signatures, there's JSON Web Encryption, there's JSON Web Keys, there are...
Here's JSON Web Signature is specified in RFC 7575.
You've got JSON Web Encryption in RFC 7516, which specifies, like, if you're going to encrypt the data, the one we looked at previously was just signed.
If you're just going to encrypt the data, how do you do that?
If you're going to... And the tokens have four dots and then four periods in them instead of the one that we just saw, which has two.
And then within each of these specifications, there's all these other parameters and notations and all of this.
There's within JSON Web Algorithms, there's a JWA, which is in RFC 7518.
There is all this other stuff, EPK, APU, ABV, specifying how you use an initialization vector and all of this stuff.
It's absolutely bonkers.
There's just too much stuff here. And that's the one big criticism that I have of this.
And so the important thing to know is if you see Jose, this is a well-known Go repository that uses the term Jose because it's all part of the Jose specification.
JSON Web Tokens are part of the JOSC. I'm not sure if other people in the world call it Jose, but I do.
And if you see Jose or JWE or JWS or JWT or JWA or JWK, if you see JWA anything, I would assume it's part of this.
And then Jose as well, which is important. So the important thing to note is that there are many, many specifications here and there's many different types of JWTs, but the most common and the one that you will see most regularly is this type of JWT in this example, where it is a piece of data that is signed that you can actually, and not encrypted for one party to send to another party and prove kind of an identity or something.
So in this case, you're proving that you're John Doe or that whoever signed this proved you're John Doe or whoever signed this is trying to tell the receiver of this, that you're John Doe.
Okay.
So that's kind of an intro to like the vocabulary, the complexity of the specs and all of that.
And some of the controversies, the other kind of, besides the cryptographic doom principle, this, the cryptographic doom principle has led to several violating that cryptographic doom principle I was talking about has led to several serious, serious vulnerabilities in applications that are using JWTs.
And that's, that is pretty interesting.
And the reason for that is like I was saying, the algorithm for figuring for, for validating a JWT, you first take the algorithm out of here.
So ALG HS256, which is specified in those RFCs we were just looking at, which is just a symmetric HMAC algorithm.
And then you, you use that algorithm to know which algorithm you need to run.
So I look at this, I say, okay, random piece of data I just received.
What algorithm do I need to use to verify this? In this case, it says HS256.
You use the key that you have, try to validate the signature, try to regenerate the signature using these and it'll either match or it won't.
And like, that sounds like it would be all hunky-dory. However, one of the algorithms that is valid is the word none, which means like no security, no encryption, no anything.
And so there's been a bunch of famous issues where, where the software was written in a way where they, it supported none.
And they looked in here, in this string of characters, they said, they unencoded it, since this is Base64 encoded, they saw algorithm none.
And didn't matter if it was actually signed or anything, they just decided not to do any verification.
The software short circuits and just says, okay, the algorithm says none.
And so I'm not going to try to verify this token.
And that's an extremely serious vulnerability when your JWT validation software does that, because anybody can just take any JWT, it doesn't matter if it's valid or not, change that initial header to say none.
And then they're, they're able to, to impersonate or forge any JWT they'd like.
So that's obviously very bad. And there's been a few kind of famous examples of that.
Okay. I think the best way to proceed with our last 15 minutes is probably to, to go ahead and start coding up an example.
So I am going to start that in just a moment here.
And I'm going to use this GoJose library we're just looking at, because it's very well known, and I'm going to do a heck of a lot of cut and pasting.
So bear with me here. And I'm going to try to get up to speed on how people do this.
So let me just download, I'll try to do the normal Go thing and download this.
I will go get GoJose.
Uh oh. That's not great.
What about this one? I'll try v2. One of these is bound to work.
And so I'm downloading the packages. It looks like I got them successfully.
And what I'd like to do is just create a basic example where I'm generating a JWT and then validating it.
And then I'd like to go into how you can keep yourself on the guardrails.
Because while I'm starting this up, my two cents is really that JWTs are kind of scary.
Like there is a lot of complexity. And if you don't need all of that complexity, you probably shouldn't use JWTs to begin with if you don't need to.
However, they solve a real problem. And they are a standard that was necessary for people to solve their real world problems.
And so they're kind of here to stay, whether people like it or not.
And there's no reason why you can't use JWTs safely, especially if you have the resources of a security team or you make some smart decisions.
So I'm trying to find an example here where I can just cut and paste some code.
And I want a signer, a symmetric signer. But I don't want to do all of this.
Okay, maybe I will.
Let's try it. Maybe, maybe not.
So I'm going to Google Go Jose B2 signer. And see what we can find together.
People write all sorts of things and the best way to learn.
I used to have a professor who said you just type it into the Google machine and you will get a helpful answer.
Okay, so this looks very, this looks very helpful.
So let's just run it. It looks reasonably safe. They're generating a RSA key.
They are creating a key with a specific signing algorithm RS256. They are doing some safe looking things and then signing something.
So looks safe to just cut and paste.
And let us try to say signer.
See why it breaks.
Undefined RSA private key.
I see why it's not defined. Yes, it certainly is not defined.
So it should probably, this might have a example of a typo where this prive key was defined and unused and then they use something of the different name here.
Yep, okay, that works.
So we printed out some object looking thing. And we have a signer, but now we need to sign something.
And right now we're just doing a lot of cut and pasting and with hopes that we will have something at the end of it, something that looks like a JWT.
So I have a signer.
What do I do with it? Called new signer and I have an RSA signer.
Let's pull up the actual docs. That might be helpful. Signer returns a signer.
Signer has a sign and options. So it will return a JSON web signature.
Let's just try to call sign on a byte string and see what happens since it looks like it takes in a bunch of bytes and returns a pointer to a JSON web signature.
And it might just work since the new signer returns a signer interface. And so I'll bet it just works.
RSA signer .sign. And then let's just do something foolish like this.
This is not how you write Go code.
And I believe that's all.
Yep. And it should return a signature and an error if it failed.
So this might give us a full JWT that we can take a look at. Did not sign our magic object.
I'll call it. Print out the JSON web signature. Is a struct with a signature.
A signature has a bunch of stuff. I'm hoping it has a string. Nope. Signature has a header.
Header is a bunch of stuff. Okay. If we print this out, it should look all right.
We'll be able to see some stuff even if it's not exactly what we want.
So JWS. Try to run it.
Yeah. Okay. So it generated a bunch of stuff. This looks right. It did.
It looks right. So now we want to visualize it and we should actually see one of those objects that have the dots in it.
That should have the two dots since we're doing signing and not encryption.
And JSON web signature. So what I would like is compact serialize appears to be what we want which returns a string and an error.
So serialize on JWS and we have the actual token. I don't think it should return an error.
Maybe it does. It does return an error and we will print the token.
And our error message could not serialize our magic object and see how it looks.
Looks right to me.
Look at that. All right. We've got it. Let's plug this thing into JWT.io.
And yep, that looks right to me.
So they have correctly identified that we just put ASDF in here.
And so when you look at the actual token, you see the header looks like any normal header.
It has RS256. It has some like algorithm-y stuff in the front that kind of shows that it's a kind of tells you the rules of the road for validating and signing that token and how it's signed.
Then for the actual payload, this is usually a bunch of data.
It's usually like has some fields and like it's a little more structured than this.
But they just realized that we just put an ASDF in there.
And then there's a big long signature and this is all the cryptographic stuff hanging out at the end of our signature that somebody needs to be able to validate our very important secret message of ASDF.
So we did it. We went from nothing to something.
And I want to talk about what you can do to ensure that you don't run into any problems with when you're writing JWT software where you don't make something accidentally vulnerable.
And so the first thing I want to do is, I don't know if we'll have time to get to all of that, but I promise I'll go over it next week if we don't, since we have about five minutes left on the show.
But I want to validate this token.
And so we have signed something. And so the opposite of signing is to verify the signature.
So let's take a look at this.
Let's go back to the docs, dig up some examples. New signer. We've got a new signer.
I want a new validator. Where's that? So the verify function, we have a signature here.
At this point, this is a, no, this is a signature.
After you call sign, sign returns a JSON web signature. Verify requires a JSON web signature.
And you call verify and then pass a verification key.
Which is, is that also used in sign?
Is what happens when you copy and paste too much?
No, where, I have my key, but where did I, what do I do with it? New signer key.
It's a signing key.
Okay, let's try verify and just passing it the, the, our key here.
So let's try jws.verify. And actually verify needs a payload somehow.
So detached verify looks nice. What does detached mean?
Validates a detached signature on the given payload. In most cases, you'll probably want to use verify instead.
Okay. So I see.
So detached means you have like kind of a chopped up token. You don't have a string with all of the pieces all together.
You have kind of the signature in one variable and the, the payload and everything else and the other one.
So we probably want verify because we have the whole thing.
And let's try to pull up an example because there are some good examples here.
Or that blog post that we were looking at earlier was pretty helpful.
Let's try to go there. Somehow I ended up at somebody's source graph instance for this.
An implementation of Jose.
This, this blog has been very helpful. Who, this medium blog has been very helpful.
Shout out to Richard. You've been a big service to me today.
You want to find where they verify the signature.
And this looks pretty complicated because there's a lot of claims.
Okay. So here's what we'll do.
I'm going to figure this out offline and commit it up to the repo, which I have here at github.com slash ejcx hacker time, because I'm not going to figure out how to verify this thing in the next two minutes because programming is hard and I don't get to do it enough anymore.
And so I'm going to commit that up here and I'll, I'll clean this code up.
I'll make sure that it's in a good place. And then next week, what I'd like to do is I'd like to go over and work on our implementation of a JSON web token.
So that we are very sure that should any oddities happen in the underlying JWT library, that we will be covered and safe and we won't have any vulnerabilities because today we're relying, it's important to understand where the security is in all of this.
And in, in this, since, since JWT is a security tool, we're very much relying on Jose and go Jose and specifically this, this, this going package and should anything change in there or any vulnerabilities introduced or found, we want to be sure that it doesn't affect us.
And so this week, leading up to our next talk, I'll be cleaning up this implementation.
And next week when we pick up, we'll have a fully working implementation and we're going to write some tests to make sure that nothing bad can happen with it because it's, it's important to, to write tests and it's important to write tests of your security guarantees that you need.
And with that, I really appreciate you watching and spending some time programming with me.
It wasn't as smooth because actually this is tough code to write.
There's a lot going into the JWTs and there's a lot of kind of complexity that makes libraries more complex.
And so I appreciate you hanging in there with me and watching and hopefully learning something.
And so with that, have a good day.