Hacker Time
Join Evan Johnson as he speaks with security professionals about recent security news!
Transcript (Beta)
All right. Good morning, everybody. Welcome to Hacker Time, the number one security show anywhere in the world, and the number one security show, especially on Cloudflare TV.
I'm here, your host, Evan Johnson from the Cloudflare product security team.
I'm here every Friday morning at 8.30 Pacific time. And this week, we're going to be doing some live programming, picking up where we left off a few weeks ago with our username and password field that we're working on.
And we are doing some programming with Cloudflare Workers, our edge computing programming environment that anybody can just pick up and start writing code with in just a few minutes.
It's really easy. But we're building something that's pretty tricky.
So username and password fields are something that we all interact with every single day, whether we like it or not.
And there's surprisingly a lot of logic behind it.
And we're going to get the basics right and talk about some of the complex aspects of it.
And I'm building this on workers. And I think workers is a great platform for building anything related to identity and access management, authentication, authorization, because the platform just scales.
Workers in our edge can meet the needs of really, really large scale applications.
And authentication and authorization especially is something that makes sense mentally to offload onto an edge computing platform.
So without further ado, let's take a look where we left off.
So we've got our directory here, my CFTV worker. Maybe I'll make this a little larger for everybody at home.
And also, this is what my iPhone looks like.
I've got my font huge on this thing. So this is great. I feel right at home.
And let's take a look at index.js. Okay, maybe a little smaller. I can't even see the whole line.
So we have one endpoint create account. Second one that's serving this page that we're looking at on the right side of screen.
And that's it.
And then we have our event listener for workers. This is how you every worker needs to have this.
So where we left off was we started parsing this form data, checking to see if we have both the username and a password, and decrypting the password, along with assault, and then responding with that.
So we're not in a workable place right now.
It's, it's just kind of midway through we're salting and hashing our password, and making sure that that kind of works.
Instead of writing unit tests, we could be writing unit tests like somebody like like a good software developer.
But one of the things I love about workers is just the hackability of it.
You're, you can sit there and hack at your keyboard and get something on the screen.
You can iterate really fast, like you're about to see. And, and like it feels a little bit like in the olden days, in a directory on Apache server, hacking together PHP programs, that, that kind of work as a website, not the best.
You can you can do some gross stuff with any software. And that's a, that's my goal here.
Okay, so we have 26 minutes. I think we can get all the way to a place where we're logging in, potentially implementing some basic kind of cookie, cookie store.
But we got to move fast. So the last thing I want to cover is this decrypt.
Decrypt is a really important thing to remember. If you have one takeaway from this, just remember decrypt.
When storing a password, you don't want to store a SHA-256 hashed password, you don't want to store a MD5 hashed password.
Decrypt is a hashing algorithm, specifically for passwords. And the properties of a good password hash for passwords is a password hashing algorithm for passwords is it's slow, and cryptographically secure.
So it takes a lot of resources to, if you had a big database of decrypted passwords, each password has a lot of complexity to, to attack versus something like SHA-256.
It's really fast, you can have specialized hardware that you can try a lot more combinations.
And, and you have a better chance of brute forcing passwords, should somebody ever get hold of your password database.
So let's actually try this out. So username, ASDF, password, ASDF1 exclamation point, got to be secure.
And this is our hash.
This is what a decrypt hash looks like. You'll note the $2A, $10. This is the, the $10 here is the cost of, of the hash, which is also right here.
So you can dial this up and it'll take longer to run.
If you move this to 12 or 13, it'll, it'll take longer to see a hash because there's more computation involved.
And then this is the actual salt in the hash encoded into this, this big string.
So it looks like it's working properly.
And we want to store this in worker KV. So there's that, along with your username.
So we want to store username as the key, your, and then an object containing your password hash as the value.
And I think that'll be a good, I think that'll, that'll allow us to at least log in.
So look and, and return whether you have the right password or not.
So let's try to get to Do we have KV set up here?
I don't see that. Let's check our Wrangler file. So we do have a KV namespace already set up in our Wrangler file.
And so we can just write to it.
It is bound as binding ejcx net. So let's try that. ejcx net.put, I believe.
First, let me load up my Cloudflare panel here and go to ejcx net workers.
We can actually see objects as they're put into the KV namespace in our, in our UI here.
ejcx net.
Three, six.
Here it is, worker ejcx net. We might have some cruft in here already. Yeah.
Look at all this stuff in here. This is, this is from our last program that we wrote together to the URL shortener.
So this is it. We're going to first create a constant, which will be our namespace in the KV.
So KV, not namespace, but like a prefix.
KV prefix equals, let's do slash users.
So before we write to our database, we will store users concatenated with the username.
So if I log in as Evan, we'll store a key users slash Evan, and then the value will be our password and object containing our password hash.
So let's actually try that. Let's do, let's make a function, async function.
And then we need both a key and a value.
And that's it. And we want to call ejcx net dot put key value.
Return await.
I think this will work.
Let's try it. But let's first create our object for writing.
And the value, we actually want to stringify this before it goes in.
And then we'll also need a get. And we want to parse it from that.
E for bytes or B because I'm just a lazy programmer and I don't want a meaningful name.
And we want to check to make sure B exists first.
If not B, false. Otherwise, we want to parse B as an object and return it.
Great.
A little sloppy, but it'll do. And I think this should work. Let's, uh, let us try.
So instead of calling put directly, oh, we need one more thing. We're putting this, we need to actually use that namespace and getting, we need to use our prefix.
KB prefix. What was the point of that if we're not going to use it?
KB prefix. So we're concatenating our KB prefix. So everything in this screen, when we write or read should start with slash users because that's kind of what we're building.
All right. Put. And we need an await because it's an async function.
And then we will do put username and the value will be object. Uh, and then that should be good for testing.
This, this should work. Um, this won't return anything.
So let's try this and then let's try to fetch it and return it.
So we're going to write and then read, and then we should see, uh, we should see our object that we just made that we wrote to the KB store reflected onto our screen.
All right.
Uh, let's give that a go. All right.
We have a syntax error. What'd we do? Let B webpack. Uh, you need appropriate loader to handle this file type.
I think I have a, just a return.
Oh, yeah. Oh, that return should not be there. As you bring in more and more dependencies, it's running webpack behind the scenes.
I'm webpacking up all my JavaScript and pushing it to our server.
Uh, it starts to take some time because you're checking all your node dependencies and, um, it's a really flexible development environment.
JavaScript and all of that can be a little slow.
Sometimes ASDF, ASDF, let us submit. And we broke our worker. What did we do? Uh, let's work, look up Cloudflare workers, get, uh, put, uh, KB.
The docs are fantastic for this.
I sometimes mess up some of the syntax. Um, all right.
Key value. So put has key and value. We got that right. Okay. Uh, we can also just put a try block here.
This is an easy way that I like to debug workers.
Try catch. And then if something breaks, just return a response.
Okay. We're going to publish.
Let's try it again. Uh, there's one.
I don't like this code. I don't think we'll ever reach this end statement.
Um, because you'll either get to this line in the try or you won't and it'll return here.
So this is just so I make sure that we have a response at the end.
Um, we'll remove it at just a moment. Username is not defined.
Yep. You're not wrong. So we're referencing body.username. How to do that.
Uh, or we could just do like, username, body.username. And then we don't have to worry about the body object getting tainted ever or anything like that.
The username deserves its own variable.
Let's be honest. Okay. I am more certain that this one will work.
Let's go. But this is what I was kind of talking about earlier where, um, it's a really good environment for, um, quickly hacking stuff, something together.
Cause I can, I can see the changes getting made in real time.
Um, I'm going to refresh and resubmit the code and look at that. It actually completely worked first try.
So what we're seeing here, we are seeing the username and password field to get submitted to our, uh, worker, the workers and hashing the password, um, creating this object that you're seeing here, this JavaScript object, and then writing this object to workers KV, reading that object from workers KV, and then, uh, stringifying it and reflecting it on our strength, uh, our, our screen here.
And so this works. Um, this is the signup form, but, um, I guess we need a login form or a different, uh, different URL.
How should we do this?
So this will create an account, but now we need a, uh, login. Uh, this is working.
Um, I'm going to remove this try catch. Um, and returns 200 on success.
Okay.
So let's start with login. Same thing. Login is not that different from, uh, create account.
But you take in a username and password, parse the form entry, just like the other one.
And then instead of hashing yourself, we're going to use a different function here, be crypt.
And it's going to be a comparison. So we want to take the password and we want to take the password that exists in the, uh, the key value store and compare them.
And to do that, you would hash the password that's submitted and then, um, and then, uh, compare it against the, the one that's in the database.
And so most B crypt functions, uh, packages usually have this, uh, function built in since that's the whole point of B crypt is a password comparison.
So it's usually, there's usually like a function compare password, and it's very easy to do.
So compare sync, we're doing this. Uh, there's two ways to use this library.
There's the async way and the, and we're just calling, uh, hash sync, which seems to work.
So we probably want compare sync, uh, and compare what's submitted to, um, to, um, what we have in the database.
But, uh, I will say like, I'm, I haven't done a security review of this, uh, that big asterisk on this, uh, this talk is I haven't done a security review of this B crypt JS.
If you're actually going to implement this yourself, um, there's a lot of complexity, which I'll cover in the last couple of minutes, uh, around, uh, other things to watch out for.
We're building a super simple toy example, but before you go cut and pasting this into your production environment, um, you, you might want to hold your horses just a little bit.
Okay. So first things first, we're going to get the username, uh, and then this will have the object.
Let's verify it does if B, if not B or not B dot, uh, hash.
So we really need to make sure just do some, uh, basic, uh, safety checking on this object that we're getting back from the database.
We probably want to make sure that it has the fields that, uh, we want.
There's no reason to suspect it wouldn't, um, but, uh, better to check than, uh, your code crash.
If you're, if you have an unhandled exception.
Um, and so if not, or not, if, uh, if any of this isn't here, this will also happen if, uh, somebody submits a username to log in to an account that doesn't exist.
So, uh, you probably just want to send like a, uh, a redirect back to login.
And, uh, that'll be good for now, but otherwise we want to compare B dot hash to the password submitted, body dot password, and then, and this will return true or false.
Um, we want to return, uh, um, I guess 401 status code for unauthorized or one age status code is unauthorized.
That seems appropriate. So we'll return a 401 redirect to slash.
And then otherwise we want to return, we can just return the string success for now successfully logged in.
And this should work.
I think this'll work end to end. We'll be able to log in. Okay. If I don't have any syntax errors or anything, Oh, I should make a cup of coffee real fast while we're webpacking.
All right, let's do it.
So we actually can't do it in the browser because this form only does, uh, we'll have to change that, but for now we can curl it.
Rolling is easy enough.
EJCX.net slash. Well, first let's create an account. I'm going to create my account.
EJCX password. Um, let's do PWPWPW. Don't hack me.
Okay. We have successfully created an account and now we should be able to see that in our UI here.
If we refresh the, uh, workers KB namespace and, uh, our Cloudflare dashboard, you see my hash here.
Look at that. Um, looks good. And then, um, let's go back and we want to sign in.
So dash D will make this a post request.
We need to send a post to slash login. So dash D will default to a post request since we're submitting data with curl.
Um, and then we want to do just a form submission field.
So username equals EJCX. And, um, let's try password equals one, two, three, not PWPWPW.
And what we should see, I'm going to do VS, uh, to one dev.
No, what we should see right here is a 401 or a four.
What, what have I done? I'm having trouble resizing my Tmux.
I think my control key is not working. Uh, yeah, there we go.
Oh, one of those days, huh?
Caps on. You know what?
I might not have, uh, done a Wrangler publish here with our most recent code.
Uh, but yeah, we got a 404 here. Let me just try one more Wrangler publish just to double check my work.
All right.
404. Oh, it's login, not sign in. What am I doing here? Right. It's not signing.
It's login. Login.
Okay. 500. Not the best. Let's get a nice helpful error message here. I catch the entire piece of code we just wrote and return a new response with our error.
And, uh, this should help us out.
I wonder what I did. We kind of put the entire function together.
So high likelihood of, uh, of an issue. Um, hmm.
And then KV, my KV has a dot hash.
Yeah, it should. Okay. Not sure yet. Let's see. 200, but we have dash S on, which means silent and dev null one, uh, standard out is going to can't actually see it.
Range error 401. Ah, so this is a tricky one. I did a redirect with a 400 status code.
Um, and it's not, uh, that's a good one. So, uh, I will not do a redirect.
I'll just do a, I'll just respond with a 302. So it's something unique.
Let's see that, uh, we didn't hit this, this one or this one. And then, um, we'll just see that the password's wrong.
So we should see a 302 now.
That's a tricky one. All right. I think this will work before time's up, but the thing that I want to get at is a thing that I want to add to all of this is username and password is deceptively simple.
Like the code that we've kind of enumerated here is simple and, uh, it's something that everybody can figure out, but they're, uh, unable to parse URL.
Uh, type error.
Huh.
We're going to parse the URL.
Um, password is here.
Wow.
This is really interesting. Okay. I'm not sure what I broke here.
Let's try without the username, password field.
And what we should do is hit a 301 here.
Uh, that means we're not even getting there. I might be submitting the form data wrong too.
Um, so I, uh, I will make sure that this code is all on GitHub for you all to see when it's finished.
Um, check here, uh, at, um, Ooh, a little slow here.
GitHub.com slash EJCX slash CFTV. Um, I'll make sure that the code is up here, uh, in the next half hour or so, but I would, we'll say that there's a lot of baggage that comes with username and password, especially because after that, after you put in your successful username and password, your correct username and password, what you'll, uh, something happens, which is you get a session cookie and, um, that's when things get interesting.
Um, there's a lot of nuance around when to remake that session cookie, when to revoke people's session cookies.
And a lot of the libraries don't handle it very well.
Um, underlying like session libraries don't handle it very well. And I think that's something we should cover at a future episode, but, uh, I hope you enjoyed spending time with me today, hacking on stuff and, uh, see you next week.
Adios.