Hacker Time
Presented by: Evan Johnson
Originally aired on August 23, 2021 @ 12:00 AM - 12:30 AM EDT
Join Evan Johnson as he speaks with security professionals about recent security news!
Original Airdate: September 4, 2020
English
Security
News
Transcript (Beta)
I am Evan Johnson. I'll be your host today on Hacker Time again. And this is our weekly show about computer security, about anything related to programming and hacking things.
And I'm from Cloudflare security team and I do the show every week.
So make sure you tune in and thanks for having me and watching me each and every week.
This week, we are going to continue kind of where we left off. We're gonna do episode three of live programming, but we're gonna do a new topic this week.
So the last two weeks we covered basic cryptographic operations in Golang.
You can go read all of that code here in my Hacker Time repo on GitHub. And we walked through how to do a basic encrypt, decrypt operation safely using the secret box API.
And kind of why you don't wanna use the crypto AES directly, unless you really know what you're doing.
And it wasn't the cleanest code, it wasn't the best code, but that's oftentimes the production code you're working with too.
So it was good enough.
And it was good enough for an example. And today we're gonna switch gears and we're gonna talk about Golang's exec.
And we're gonna talk about Golang's exec in a really, really roundabout way.
By first not talking about Golang's exec, we're gonna talk about PHP.
We're gonna start with PHP, a real live vulnerability that I've seen before in production.
We're gonna make a toy example.
We're gonna reproduce the same example that I have seen in production running on somebody's system before and exploited.
And we are going to kind of remake that toy example, show you how it works, talk about it, talk about what's bad about it, talk about what's good about it, and then try to do the same thing in Go.
And we're gonna be very surprised at our outcome. And so with that, I'll just get started, start programming and walking through kind of the toy example and toy scenario.
And feel free to ask any questions to the Cloudflare TV question button.
There should be a button below me on Cloudflare TV or live tweet me at ejcx underscore on Twitter.
And let's start with hack.php. Or vulnerable, let's give it a good name.
Vulnerable, I should make this much larger. Bear with me, folks.
We are going to call this, I actually normally program with the font this large, which it's not just for TV.
I just program like I don't wanna be squinting all the time.
Okay, so here is vulnerable code execution.php. And we are going to start with the PHP tags.
And PHP is probably the number one language in web programming.
It is ubiquitous. Things like WordPress programmed in PHP. There's so many different, what are they called?
Like there's Joomla, there's WordPress, all of these content management systems, all of these different things that you can just download and run a website out of the box.
The tooling for PHP is fantastic, where it is the easiest language to go from zero to hacking on something in seconds.
And so because that it's one of the first languages that I really learned in web programming and really got familiar with, because trying to set up other environments was difficult.
And PHP was just easy. You download your LAMP stack and you're good to go.
You download Apache, start writing index.php and you're running.
And so you see a lot of early programmers get their start with PHP, me included.
And there's a lot of bad things you can do with PHP.
There's no rules in PHP and they really provide great flexibility, which is both powerful, where you can hack something together really quickly and that where you can introduce a lot of vulnerabilities.
So when people say, oh, it's PHP, it's such a vulnerable language, it's so bad.
Like the language itself is fine.
There are a lot of wonky bits about it. And it's certainly not like a strongly typed language or the most secure, the language you'd be building any high assurance systems with.
But it is a great language for web programming.
It's good enough. And unless you're doing something horribly bad with object serialization and all of these and allowing yourself to be, unless you're doing something really, really bad or writing just horrible code yourself, then it's not gonna be, it's gonna be just fine for you.
People just end up shooting themselves in the foot with PHP, is the old programmer programming saying where you shoot yourself in the foot, lots of foot guns, lots of ways to make mistakes.
And we're gonna walk through one.
So let's say we're writing a website, we're building a website.
And in my fictitious example, I wanna be able to tell something is an image.
So we're gonna be able, we're gonna look at something and say, is this an image or not?
And this is common in PHP websites. You're gonna have a, you're gonna have profile pictures, for example, if you're building the next Facebook or the next big social media website, you're gonna have people uploading profile pictures, you're gonna be storing them.
And you're probably gonna wanna do some checks to make sure it's an image for good security.
And so you're gonna wanna be writing PHP that determines if something's an image.
And so this is a real life example that I've seen, but first we need an image.
And how about this one? We can use yours truly, me.
I'm gonna download this thing.
Let's go to my Twitter, pull up my profile picture and we'll download it.
And copy image address.
We are going to download this image using curl and me.jpg. So if we open me.jpg, we should see, that's the profile picture I just downloaded.
Yep, that's it. Okay, so we have a real image now and we wanna be able to tell in PHP world, is this a real image?
And there's a way to do that in PHP.
And there's a way to do that really easily on the command line. You can use the file program.
So you can run file on me.jpg and you see that you get this result back, JPEG image data.
You get all this information, density, segment, length.
File does a lot. And so, I don't know, I know about file. I know that's pretty easy.
Why don't I use the file programming, file program from PHP? And that kind of brings about the first big alarm bell.
If you're a security person, if you're shelling out to the terminal and you are running a program on the terminal, you should usually be asking yourself why you're doing that.
And is this the best thing to be doing?
And so in PHP world, what does this look like? So if we wanna run file, I believe we can just run file me.jpg.
What should we call this?
Will this work? It's been a while since I've written PHP. And yeah, take a look at that.
So now when we run our little program to determine if this is an image, it prints out twice.
And you might be wondering, why does it print out twice?
And there's a very good reason for that. And that's because system, this line is printing to standard out and this line is printing it again.
Let's pull out the system docs.
So yeah, system is like the C version of the function in that it executes the given command and outputs the result.
Maybe command won't print it out.
Oh, a command that will be executed. Return bar, if the return argument is present, then the return status of the executed command will be written to this variable.
Okay, so there are two arguments.
One is the command you're actually running, which was the file command.
And then the second one is pass by reference an integer to determine the exit status.
So whether it was successful or not. And okay, so we actually have using system, we have something that is good enough.
We can do a HP string contains, stir pass, good old stir pass in the world of PHP.
And this is actually one of the foot guns in PHP.
I have to Google this every time because in the world of PHP, haystack needle, it's not consistent where haystack is on one side and needle is on the other side.
So what this is referring to is haystack is string you're looking in and needle is what you're looking for.
And so we can use this to do stir pass, all our S that's the haystack, the thing we're looking for.
And then we're looking for JPG.
And we should get a, is JPEG.
Okay.
We're gonna have to figure out why, how to make it not output to standard out every time, but we can check if it's a JPEG.
Does it contain the letters JPG? And it's actually, we should have JPEG all caps.
And this should print a positive number, zero or a positive number and it prints eight.
And so we can do if is JPEG is greater than or equal to zero. And the reason why it would be equal to zero is if JPEG is at the beginning of a line, PHP actually will return negative one.
If it is, if it doesn't contain the string, it's very wonky, but we can print out.
Yes, this is a JPEG. We know it's an image.
We can write code. Oops. Yes, this is a JPEG. More likely though, you're gonna wanna be writing if it's, I don't know.
If this was production code that I was writing, I would be probably, is this too clever?
I don't know. If it equals to zero, I would be saying false, otherwise true.
Actually it's is JPEG and there's greater than or equal to zero.
And now it's a Boolean and we can just say, if is JPEG, yes, this is a JPEG and it should work properly.
All right, now let me try to figure out why it keeps printing out.
I don't wanna be looking at this every time we run a system.
Oops, I just messed with my screen.
Should be fine. Once we do that, we'll have a full working program that tells if me.jpg is an image or not, and we'll test it by passing in by our next change that we make.
And so system, there's also exec.
Let's try that. One of these, doesn't really matter which one we use.
The details here do matter a lot depending on what you're doing. However, a lot of times it doesn't matter if you're writing a toy example.
Try exec, let's see what we get back. Yes, this is a JPG and all right.
Let's, we think this is working now. We made this change where we just swapped out system for exec.
It's basically the same thing, but I'm not sure if it's working.
What we should really do to validate it's working is pass in a non-JPEG and make sure that it's telling us that it's not.
So let's do else.
Else, this is not a JPEG.
And in order to do that, we have to be able to change our program to be able to take a file name.
Since we wanna be able to, since we just don't wanna change this string every time, we don't wanna just change the string every time we pass in a file name.
And so in order to do that, we have to pass input to this.
We have to be able to pass input to the program from the command line, which we could either pass from standard in or just on the command line.
And in PHP, you can read your args.
So let's start out with a print arg, dollar arg. Uh, let's try that first.
I don't think it's gonna work. But you can see, oh, it didn't break.
PHP has a couple of built-in variables that will, um, and this arg, arg, argv will show you all of the arguments passed in on the command line after the letters PHP.
And so the only thing passed in is this program. So if we pass in something else, you'll see it here, ASDF, and it's very cool.
And we can use that to pass in a different file name.
So, uh, we can pass in a file and then pass that to the command line program file.
And so the first thing we'll wanna do is check to see if we have the right number of arguments.
We want it to always equal two, since one of them is gonna be the program name, and then one of them is gonna be the file name that we're passing.
And, uh, if it doesn't, then we should just say, pass in a file name.
And then we can exit.
And good enough.
Let's check if it works. Pass in only a file name. All right, it's working.
Okay. And now we can get the file name. This is, uh, this is building up.
It's gonna be very exciting in just a moment. Very excited.
Okay, so now we have a file name. We wanna run the file program on it. And it is very tempting to do this.
And, um, and this will work. We're gonna run this code and we will see when we pass in vulnerable code execution PHP.
If everything went right with switching system to exec and we haven't swapped any of these evaluative statements here, then we will be able to get the correct result that something is or is not a JPEG.
And so let's try it. Vulnerable code execution. Meet at JPG.
Yes, this is a JPEG. Let's try readme.md. Yes, this is a JPEG. So we have done something horribly wrong.
Let's fix it. It's gotta be this thing. Let me check stir pass.
This is one of these PHP foot guns. Returns. Find the numeric position.
Returns a position where the needle exists. Returns false if the needle is not found.
Okay, so it does not return negative one. I'm thinking of something else, some different PHP function.
Some of them return false. Some of them return, some of them don't.
And so we actually want, if it's greater than or equal to zero, we want, actually we want if stir pass is not equal, false.
And I believe this should work.
This is not a JPEG, right? Yes, this is a JPEG. Okay, so we have working code.
And this is where you potentially could have an aha moment where you start to notice these, restart to see the issue.
And the issue is, what happens if you pass in a malicious file name?
What happens if, and this is a real world example that I saw.
What if you're a web, a website that's allowing customers to upload their own profile picture with their own file name, and you're saving it to your local directory as many people did before, back when they ran virtual machines for everything and had no serverless storage options, you might save it to a local directory and use your actual file system for this, which is not a great idea because of this reason.
And then you run file on it.
You wanna make sure that you're doing some sanity checking. And so how do you do that?
You might run a file on it and make sure that it's like a JPEG or a GIF or a PNG.
And that's where problems start because if you pass in something like, well, first, if we pass in something that doesn't exist, like SDF in quotes, I'm also gonna re-add print R here to demonstrate kind of what's going on.
So if we also pass SDF, you'll notice the quotes are gone.
And that's based on how Bash is doing its command line parsing. But what happens if you pass in a file name like this?
What just happened?
Everything broke and that's not good.
Well, what happens is let me give it for an example.
So we got, this is not a JPEG.
Everything worked properly. However, what's going on here?
You'll notice that touch, I ran touch.lol and it exists here.
That's not good. And we have just created code that allows you to kind of run arbitrary commands that get passed in on the command line.
And the reason is because what's actually getting run here last time we ran this was this actually got run.
It ran two commands. And so the real issue here is well, the real issue here is concatenation.
String concatenation is something that you should be looking out for when you're dealing with SQL statements as well as running things on the command line.
But this is hopefully kind of demonstrates the danger in shelling out to programs and especially with untrusted input in that.
And so like, how do we even do this safely?
I guess before we talk about how to do this safely, the main thing I would wonder is like, how could this be really bad?
How could this be abused?
And you don't want people to run commands on your systems, first of all.
They could try to steal private keys. They could modify or they could, with this type of capability, an attacker can trivially turn it into an exploit where they get themselves a shell on your system, ransack your entire system, use that as a pivot point into other systems in your network.
And so it would take five more minutes on stream to really hack something together to turn it into a shell where I can, where when this program runs, I can type on the command line and five more minutes after that to make that work over a network.
So this is trivially easy to turn into something really, to turn into a really bad day.
And so you generally do not want to just be shelling out with untrusted input.
But anytime you see string concatenation, you should immediately, it should raise alarm bells quite quickly in your mind, because just like in the world of SQL and SQL injection, this is command injection and it's no different.
And the solution is also no different.
In SQL, in the world of SQL, the solution is to parameterize your queries.
And that's no different than what we're gonna see with Golang's exec. And so you'll notice that in the Golang exec package, this is one of the reasons I really took to liking Go early.
And the reason is when you run a command, you actually have to put it all in these quotes.
You have to put every argument, every flag into these quotes.
And that's, I saw this and I immediately thought, wow, that's really interesting.
Why do they make you do that? And the reason is because just like a parameterized query in the world of SQL, Golang actually does this on when you wanna run something on the command, they parameterize it on the command line.
They parameterize what you're running before it runs on the command line.
And that is a awesome tool to have in your tool belt, programming tool belt, because it allows you to be writing Go using a really powerful programming language, and then also get the flexibility of being able to shell out to your commands, to your command line.
And so we can trivially take this program actually and run it on the command line and try to run the exact same exploit.
We'll see it does not work.
So what happens if I add a, that's going doesn't work here.
Do you think this is gonna run or not? And I already know the answer.
This is actually not going to, in all caps some input and it didn't work.
So we're running this TR command. We can run LS.
We should probably run that to demonstrate that this works. And run it out of time.
So it's a race to beat the clock. Will this work?
It did not.
So I'll kind of touch up more on this next time and really show why the exec command, what the exec command is doing.
But I didn't wanna keep you waiting.
I wanted to make sure that I didn't leave you on a cliffhanger. And I wanted to show that actually that same exploit kind of changing what's getting passed into the command line here when we run LS will not work in Golang.
And the real mystery to unpack and understand is why.
And the magic is all of this stuff that you're seeing here where it's all in quotes and separated by commas and you're not passing in a string, you're passing in a series of arguments where they build what you wanna run on the command line in a safe way.
And it's not just taking whatever you give it and running it on the command line, which might be sometimes what you want, but usually not, definitely not with untrusted input.
And so I'm glad you all watched me today.
So I really appreciate everyone watching me and I will see you next week at the same time, same place on Cloudflare TV at 8.30 AM.