Hacker Time
Presented by: Evan Johnson
Originally aired on March 5, 2023 @ 7:30 AM - 8:00 AM EST
Join Evan Johnson as he speaks with security professionals about recent security news!
English
Security
News
Transcript (Beta)
Thanks for having me. This is Hacker Time. I'm your host, Evan Johnson. I work at Cloudflare on the security team.
I work on product security at Cloudflare, helping secure all the products that we build, helping to make sure that the things we release work and work well.
And that's not what this show is about though.
This show is about just hacking in general, security and anything just interesting.
And so today I'll be picking up right where we left off and diving into a specific bug we're looking at.
And I'm glad you all are joining me. Thanks.
I hope everybody's staying safe from the fire, safe from the pandemic, safe from the bad air if you're here in San Francisco.
Sure is a lot going on, but not for this half hour, we're gonna be programming.
And so I'm gonna dive in right where I left off and first things first, connect to my old trusty Tmux session here and we'll be off.
So actually I realized just before I started the show, I forgot to commit last week's code up to GitHub.
And so I think that's an important thing to start with.
Let's start there. And so the first thing is I'm gonna add the vulnerable code PHP.
And what is me.jpg?
Was that actually a picture of me? It appears so, let me double check. Nope, it's my Twitter profile picture.
So we definitely need that in there. And then the rest of the stuff is just junk.
I can't remember what is in ASDF. Do we need that?
Yeah.
Okay, that looks good. And we do need that. And then one more thing. Let me remove this file, LOL.
Okay, bit of a recap about what the heck we were talking about.
Let's start with what this program was. And the whole point of what we were talking about was we were talking about how programs interact with shelling out to other programs.
And you'll a lot of times be writing code and encounter a time where you're writing Golang or PHP or any language, it can be anything.
And you are like, wow, the thing that I need to do is so easy to do in one line of code on the command line.
It is so hard to do it in the language that I want to do it in. And I don't know how to do it in the language that I'm working in.
And so I'm just gonna use the command line.
I'm gonna set up, write this to a file and shell out to the command line.
And we really saw that this after building this PHP program, that it was hard to do the right thing here.
So actually what, so if we walk through it, we have some just like input validation, making sure that we are have the right number of arguments being passed to our program.
Next, we just have a print R debugging kind of the entire environment, printing all of the arguments passed to the program.
We take one of the arguments and make it a file name. And then we run the file command on that file.
And this is actually the whole point of the toy example, this line here, where you see that running the file command and concatenating the file name actually can yield unexpected results where if you pass in a file name with special characters, or let's just say you put a, the letter A followed by a semicolon followed by another command, it'll execute both the first command and the second command.
And so let's just run it and I'll show you. So here's in our directory, we've got nothing here that says, that says there's a file named LOL.
And so if we run PHP vulnerable code, PHP pass our file name that we want, but then also let's put it in quotes.
And then also say, touch LOL. What you'll see is, we have this little LOL file here that was created by vulnerable code PHP, shelling out to the command line and then running both.
It ran file me.jpg, which says, yes, this is a JPEG.
And it also ran touch LOL. So both of those commands were executed.
And I've seen this in a real life system that I was able to exploit this exact vulnerability.
And that's why I wanted to show it to you. And the system that I was able to exploit this in, was actually really similar to this.
And that's why I chose me.jpg and running file on the command, because the system that I was able to do this in was allowing users to upload files to a PHP backend web server.
And they wanted to validate that it was only JPEG images and zip files.
Also zip files, you probably don't wanna be, it's probably a complicated file type to be allowing users to upload, also a bad idea.
But what the program was doing was, it was just allowing running file on whatever file was passed up to the server.
And it had a small allow list of allowed file types that would check the file output exactly like this, the output from the file command, exactly like this does.
And then either accept or reject the file name, the file.
And so this is a real life example. It's a toy example of that, where we don't have all the web server and everything, but this is a real bug that I was able to exploit.
So it is a real concern. And the hard part about it is like, how do you even fix this?
It's kind of wonky. So you've running file on this file name.
And ideally, what's the solution here? Well, there's two approaches you can take.
And one is to parameterize the contents that you're passing to file.
And you might be like, well, let me try something. What happens if you pass me.jpg in quotes?
Okay, it still works. And you might be tempted to say, okay, well, we just need some quotes in here, right?
I think that's correct.
And then when you pass in PHP vulnerable code, me.jpg still works. And it still knows that vulnerable code PHP is not a JPEG.
It still knew that the JPEG was a JPEG.
So it must be working, but it actually is just harder to exploit. You just have to be a little more clever where you say me.jpg and do one of these, one of these, try two, should be a file name, try two.
I might've got something wrong here, but I think it's possible that I didn't.
And well, I think this demonstrates that I don't think I got it.
I didn't on the first try, but I think it does demonstrate that all you kind of have to do is add another quote in there.
And the quotes, your quote defense has been bypassed because of this contains a quote.
When this all gets concatenated together, what you're gonna have is just more quotes.
And, oh, I actually do know the issue. Let me, I think I can make this work.
Um, me.jpg, I think this might work.
Oh, well, that's too bad.
I won't waste our time since we have a short show, but the point is that, yeah, we've kind of demonstrated that we can mess with these quotes and this isn't a very good defense.
It will take me more than 45 seconds to hack it, but probably not more than four or five minutes to get something working again.
And so I'm not gonna make you watch me while I figure out the bug that I'm hunting for here.
But it is, the point is that these quotes are not a valid defense.
And so, okay, quotes don't work.
What else might work? Well, we can make sure that the contents of the file name are, don't have weird characters.
And so we can, we could say, for example, that do some light regex kind of matching on the file name and make sure it doesn't have any characters that we don't like.
And I suppose this does work if you build it properly.
And that's the big if. File name can contain any characters that, and if you pass in, if you're expecting that to be your only line of defense, then what you're kind of saying is that you completely understand the shell and all of the nuances of passing arguments into the command line from the PHP world and how every single character and escape sequence is gonna be interpreted when it's passed to the command line.
And I'm not willing to bet myself that I understand all of the nuances of exactly what's happening under the hood.
And so I would be uncomfortable relying on that as a security control, because you never know.
We use the semicolon here as the character, but there are undoubtedly other characters that will do the same thing.
That will get the same result of delimiting the one file, the one, the previous command, and then running the second one.
And another, like another good example of this might be, there's many, many different ways, me.jpg, and then we can run.
I wonder if this will work.
Let's try three.
I'm not sure if this will execute before on this command or the next command line, because we're running through two command lines here.
And I believe it did run in the context that I expected it to run in.
Actually, we can debug that by saying, whatever.
The point is, is there's more than one way to run something on the command line.
You don't have to pass.
It doesn't have to be a strictly the semicolon solution that I had.
And so this isn't, it's a brittle defense trying to create an allow list of characters that are allowed.
I think it's one worth having where you can sanity check, but the string being passed in is like mostly printable characters, but that's probably where I would stop.
You probably don't want a really crazy file name with all sorts of unprintable characters, but also you're probably not gonna get as much security as you hoped for by having a very strict allow list of alphanumeric characters or like dash, plus dashes, plus underscores.
And so you're kind of left with, well, shoot, how do I actually make this secure?
And Golang actually does that.
And that's the point of all of this. So I wanted to show you this toy example with my Go program that I copied and pasted last time at the very last moment and showed that this doesn't work.
So if we run, I'm gonna just do a quick high level of this program, super basic main function.
All it's doing is running the ls-l command, and then it's trying to tack onto this ls-l command exactly how we did it.
And I'm running this touch, Golang doesn't work. And you might be thinking, well, how do you know this isn't gonna work while we're about to run it?
And this, what you'll see here is when we run this, it should print out the ls-l output, but we shouldn't have a new file in there or anything that touch created by the touch program.
So, but this looked a little funky to me. I don't think we need this.
So, it's status one.
In all caps, why is this?
This example I printed out is not very good. Let me get a new, a new example that will be a little more baked than this one.
Cause this, I just want to run a command and print the output.
So, luckily the Go docs are fantastic.
Shout out to the Go team who works hard on these docs. And they're all auto-generated, which is a thing of beauty.
So, let's get this. Well, I actually, I suppose this should work, but it's not quite what I want.
Command stack overflow, no.
I just want a command that runs like something on the command line.
That one is technically doing it, but I don't really want to debug it to get it exactly how I want it.
Stack overflow, run command on command line filling.
Yep, this is it.
All right. This, oh, what is this? This is sure is a lot of, oh, they're using the special package.
All right.
This one, let's just try it.
Great security advice, blindly running things that you found on stack overflow.
I can tell this isn't doing anything bad. So, we should be fine.
And exec, what the heck?
Should be an OS exec. This must be really old Golang. Yeah, this is really not looking like Golang I've ever seen.
Okay, I'm just going to go back to the old example.
And this is part of debugging and live streaming everything.
Nothing ever works. And that is just all right, because it's the process that counts.
So, here we've got sleep-1 and let's make it sleep-3, something that we can actually visibly see.
And okay, we see it waiting, we see it waiting, and then it finishes, okay.
So, that's our output, seeing something wait. And I think that's plenty for what we need.
So, let's do a touch, run the command. So, we should see if this is hackable, if the same thing we were doing in PHP world is working on Go, we should see a new file exactly how we did with our little PHP program.
But we're not going to see that here after we press LS.
And that's the whole point of this demonstration is something Go is doing is working.
And that's what I wanted to show you.
So, what this is actually, you'll notice that it's passed in in little chunks.
And what Go is doing is parameterizing the results. And that is really interesting, just like a SQL query or something.
These results are being parameterized before they're passed in on the command line so that sleep is the first argument to the program.
This entirety is the second argument to the program on the command line.
And so, you have no situations where because of what Go is doing under the hood, and I'll pull up the Go code in just a moment that's doing this.
Because of what Go is doing under the hood, there's no possibility to kind of break out with a weird delimiter and run something that's unexpected on the command line.
So, that's not to say that this is a bulletproof solution. Go, I think has done as best as they can preventing this.
But sometimes pro command line programs have exec dash dash arguments, where you can do dash dash exec and you run your own command alongside the program that's running.
And Go can't really do much about that because it's the program that's running functionality that it's executing something, not how the Go program is interfacing with the command line.
It's how the program that's running was designed. And so, it's kind of not something Go can fix.
But Go has done a fantastic job fighting this flaw that has existed for so long.
And the reason is we're running exec command. So, we can just go see the Go code that's doing this.
And I think that is amazing. So, this looks very simple.
They're creating a new command with path and arguments. And basically they're taking all of the things that we passed in.
So, we passed in sleep and then this as the second argument and they're passing it.
They're creating this new structure with kind of just taking that data and moving it into a more structured format.
And then they are returning the command. And then, so the command is just a little two pieces of data, the path and the arguments to it.
And then they run, run.
And that's where it's actually c.start from run, it goes to start.
And what happens in here is really the kind of crux of it all.
And they're doing a bunch of actually crazy stuff that we're not gonna cover here because they're looking at if it's windows, they're looking at all of the file descriptors that are going, they're looking at a bunch of stuff.
And the important part is that they are, with the way that they're handling this, I think there's one critical piece here to note.
And the way that they're handling this is you're not seeing string concatenation anywhere.
And then they're just firing off this command and forgetting about it.
They're actually making sure that this is the first argument and this is the second argument to the program.
And I think that is the most important thing here. So I think it's amazing that anytime you're wondering how Go or programming language does something right, you can actually go read it.
And this is just Go code that implements the Go exec package and that's fantastic.
And yeah, this is a really complicated problem.
And I don't see, I see as we move to better languages like Go, and I don't wanna call it a better language, but more tight, more strict languages.
And it all depends on what you're doing with the language.
If you really need that rigidity of a typed language, you should have it.
Sometimes if you're writing a five line script, it doesn't make sense to have types and because you're just not getting any benefit out of it.
But when you, I think a lot of people are embracing stricter languages, more opinionated languages, and security is one of the big reasons because Go has stronger opinions about what, how programs should interface with the command line.
And so, yeah, I hope that makes sense.
I'm gonna write a commit message and commit all of this up to GitHub.
And we never actually committed the one that I started at the beginning of the show.
So we need to give it a nice commit message. Basic exec in security and secure example.
Go is more secure. This context, PHP is dangerous.
Words to live by. And that should do it.
All right, and so that's kind of what I expected to cover in the show.
And I think in the next show or two, I'm gonna cover something completely different and talk about chess.
I think it's something that I've been learning and I think one of the things that's important for all hackers everywhere is to constantly be learning and curious about new things.
And this is chess.com. They're a fantastic website that you can play chess on.
And I think next time around, I think I can walk through some puzzles that I've played.
And I hope to teach you something else other than computers, if you're gonna be spending all this time watching me.
And so I'll do one puzzle for you right here and as a teaser of what it's kind of like.
And in this position, you see the point of a puzzle, actually, before I dive in.
The point of a puzzle is to get a position and you're forced to make a winning move usually, or saving move.
Usually you get a position, it's pretty complicated and you're not really sure.
Sometimes it's really easy, sometimes they're really hard.
So it can be really obvious where you're just supposed to checkmate the king and it's super, super simple.
Sometimes it's really complicated where after a six move sequence, you might win a pawn and it's not easy to know.
And sometimes you make a good move in a puzzle and it's actually not the solution to the puzzle because there was a much better move that you missed.
And so the fascinating thing about chess is, it's really hard, is what I'd say the fascinating thing is, there's so many different moves to make, there's so much complexity and thinking through the positions is really challenging.
And so it's something that I've been learning and I think spending a show where maybe you might learn something would be really interesting.
And so we have here, we have one, two, three pieces and one, two, three, four and the queen, the white pieces has the queen still.
So that is not good.
I'm supposed to be moving as the black pieces and it doesn't look good for me because I'm way behind in material.
The queen is the most powerful piece on the board and that's, but how can we win?
Well, this is an obvious move. The rooks move up and down and side to side, but not diagonally.
And so these rooks are just facing each other in a good old standoff.
So the goal is to take the king here.
And so taking this will result in check, meaning the king is under attack.
And I don't think that's a solution because then this Bishop can block and we haven't quite, oh, wait, it is a solution for sure.
So in typical chess fashion, that's how you solve the puzzle. And it's not clear if you, it's such a complicated position, it takes time to explain.
And I've always found that understanding the puzzle at the end of it is the most important thing.
It doesn't matter how long it takes and I can't wait to do that next week.
So thanks for joining me and this has been great. This is Hacker Time.