Over the week, I wrote a useful command line tool called tlsenum.py that attempts to enumerate what TLS cipher suites a server supports and list them in order of priority.

This was a rather interesting exercise for me because I had to craft the raw TLS ClientHello record by hand since OpenSSL does not expose an API to send only the TLS ClientHello. This means hours reading up on the TLS RFCs and researching the TLS handshake process. The process was highly educational.

I will talk about the TLS ClientHello and ServerHello record format in a future blog post but today I’ll like to explain how tlsenum.py works and how to use it.

The TLS handshake process begins by the client sending a ClientHello message to the server. Amongst other information, the maximum TLS version as well as the list of ciphers the client supports is encoded in the ClientHello message. While the client is allowed to express the cipher suites it wants to use in order of preference, the final decision lies entirely with the server. The server is allowed to randomly pick any one of the cipher suites it wanted and still comply with the RFCs. In practice however, the servers usually picks the cipher suite according to it’s own priority list and ignores the priority indicated by the client. While this is not guaranteed by the TLS specification, tlsenum.py relies on this implementation detail to work.

tlsenum.py works by continually sending out ClientHello messages with a list of all the TLS cipher suites and removing the one the server indicates it wants to use for the next ClientHello message. It does this until it receives a handshake failure which indicates that the server does not support any of the cipher suites listed.

Using the tlsenum.py itself is very simple, here is an example of the results when scanning twitter.com.

[ayrx@division tlsenum]$ ./tlsenum.py twitter.com 443
Maximum TLS version supported by server:  1.2
Supported Cipher suites in order of priority:
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_RSA_WITH_RC4_128_SHA
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
TLS_RSA_WITH_AES_128_GCM_SHA256
TLS_RSA_WITH_RC4_128_SHA
TLS_RSA_WITH_RC4_128_MD5
TLS_RSA_WITH_AES_128_CBC_SHA
TLS_RSA_WITH_AES_256_CBC_SHA
TLS_RSA_WITH_3DES_EDE_CBC_SHA

So, what’s next? Code cleanup is definitely priority, but after that I would want to add more features like certificate verification to tlsenum.py. The goal is to hopefully reach feature-parity with SSL Labs awesome SSL Server Test. While the results provided by SSL Labs is fantastic, it is sometimes helpful to have a command line tool that you can automate and parse easily. That was my motivation for working on tlsenum.py in the first place.

Again, the tool can be found on Github. I hope it proves uesful.

Today I will walk through how the basics of properly encrypting data using a symmetric cipher like AES. Code examples will be written in Python using the cryptography library for primitives.

First things first. What is a cipher? Simply put, a cipher is an algorithm for encrypting and decrypting data. Modern symmetric ciphers can be broadly divided into two categories. Stream ciphers and block ciphers. A stream cipher works by encrypting and decrypting data one bit at a time while a block cipher works by encrypting and decrypting data in fixed-length groups of bits known as blocks. salsa20 is a good example of a strong, modern stream cipher while AES is a good example of a strong block cipher.

Next, it is important to understand the concept of an encryption key. Modern cryptography principles dictates that the a cipher must remain secure even when assuming that the adversary knows everything about the cipher except for the encryption key used. This is known as Kerckhoffs’s principle. An encryption key is a piece of data that determines the output of a cipher.

For this blog post, I will be using the AES cipher. AES is widely used block cipher standardized by the NIST. Now, unlike stream ciphers, block ciphers cannot be used alone. They have to be used with a mode of operation to be secure. There are several mode of operations out there, ranging from the broken ECB mode to the excellent GCM mode. I will be using the CBC mode of operation.

The CBC mode of operation requires an initialization vector(IV). The IV has to be unique for each encryption. The IV should not be reused. The easiest way to achieve this is to generate the IV randomly.

import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend

key = os.urandom(16)
iv = os.urandom(16)

# encrypting stuff
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), default_backend())
encryptor = cipher.encryptor()
encryptor.update(b"Hello, World!")
ciphertext = encryptor.finalize()

# decrypting stuff
decryptor = cipher.decryptor()
decryptor.update(ciphertext)
plaintext = decryptor.finalize()

assert plaintext == b"Hello, World!"

Now, this looks right. But if you actually try to run this an exception will be thrown.

ValueError: The length of the provided data is not a multiple of the block length

This is because AES encrypts data in 16 byte blocks. At least in CBC mode, padding the message is required for data with lengths that are not a multiple of 16 bytes. Luckily, the cryptography library comes with a padding mode we can use.

import os
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend

key = os.urandom(16)
iv = os.urandom(16)

# encrypting stuff
padder = padding.PKCS7(algorithms.AES.block_size).padder()
padder.update(b"Hello, World!")
padded_data = padder.finalize()

cipher = Cipher(algorithms.AES(key), modes.CBC(iv), default_backend())
encryptor = cipher.encryptor()
ciphertext = encryptor.update(padded_data) + encryptor.finalize()

# decrypting stuff
decryptor = cipher.decryptor()
plaintext = decryptor.update(ciphertext) + decryptor.finalize()

unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
unpadder.update(plaintext)
unpadded_data = unpadder.finalize()
assert unpadded_data == b"Hello, World!"

Now, we are not done yet! What we currently have is unauthenticated encryption. This is potentially insecure against an adversary that has the ability to intercept and tamper with the ciphertext to be decrypted. A classic example of this is the padding oracle attack. I may do a future blog post with more details about this in the future, but all you need to know for now is that we have to add a message authentication code(MAC) into the encryption process. Now, there are several ideas about when to apply the MAC. While there has been debate about this, it is commonly accepted that Encrypt-then-MAC is the correct method. (Here is a nice post if you want to learn more.) This means that the MAC should be applied on the ciphertext output of the encryption process. The most commonly used MAC is the HMAC algorithm based on hash functions.

import os
from cryptography.hazmat.primitives.hmac import HMAC
from cryptography.hazmat.primitives import hashes, padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend

encryption_key = os.urandom(16)
hmac_key = os.urandom(16)
iv = os.urandom(16)

# encrypting stuff
padder = padding.PKCS7(algorithms.AES.block_size).padder()
padder.update(b"Hello, World!")
padded_data = padder.finalize()

cipher = Cipher(algorithms.AES(encryption_key), modes.CBC(iv), default_backend())
encryptor = cipher.encryptor()
ciphertext = encryptor.update(padded_data) + encryptor.finalize()
h = HMAC(hmac_key, hashes.SHA256(), default_backend())
h.update(ciphertext)
hmac = h.finalize()

# decrypting stuff
h = HMAC(hmac_key, hashes.SHA256(), default_backend())
h.update(ciphertext)
h.verify(hmac)
decryptor = cipher.decryptor()
plaintext = decryptor.update(ciphertext) + decryptor.finalize()

unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
unpadder.update(plaintext)
unpadded_data = unpadder.finalize()
assert unpadded_data == b"Hello, World!"

The idea here is that the decryption process should not happen if the MAC verification fails.

What we have so far is reasonably secure. A symmetric encryption method using AES in CBC mode with a random IV, proper padding and a MAC. Of course, I hope that the process so far highlights how difficult it is to get something as simple as encrypting a piece of data right. Thankfully, at least for people writing Python code, the cryptography library has provided an easy way to accomplish exactly this.

from cryptography.fernet import Fernet
key = Fernet.generate_key()
f = Fernet(key)
ciphertext = f.encrypt(b"my deep dark secret")
plaintext = f.decrypt(ciphertext)
assert plaintext == 'my deep dark secret'

The Fernet recipe is exactly what I implemented in this blog post. Symmetric encryption using AES in CBC mode with PKCS7 padding and HMAC applied.

Thank you for reading and as with all things Crypto, the lesson here today is “Don’t roll your own”!

P.S Do drop me an email if I made any factual errors. I’ll be happy to fix them!

I was at Syscan again this year as a volunteer.

As usual, all the talks are very interesting. I’ll write about the few that stood out.

The very first talk of the conference had a really interesting title. Car Hacking for Poories by Charlie Miller and Chris Valasek. It is essentially about how they setup a test bench simulating the environment of a car to test against so that people do not have to purchase a brand new car to perform security research. Very interesting stuff.

Anton Sapozhnikov had a really interesting talk about how to recover the password of the user you compromised on a Windows machine without gaining access to admin privileges through flaws in the Windows SSPI implementation.

Dean Carter and Shahn Harris had a really hilarious skit about the various infosec fails. One of them was dressed up as failymonster. Did I mention this took place in a bar?

The first day concluded with good food and plenty of beer.

The next day, Joxean Koret had an amazing talk about Breaking Anti-Virus software. He spoke about how the results of his fuzzing research against anti-virus software revealed plenty of holes in them. This included gems like injecting non-ALSRed DLLs in processes system-wide, downloading updates over HTTP and a particular AV that ran exec() with user supplied inputs. This talk was really the highlight of the conference.

Josh m0nk Thomas had a really good talk about breaking the SnapDragon SoCs through regulating the power sent to it. Really interesting stuff but most of it sadly went over my head.

Alex Ionescu gave a talk filled with technical details about the RPC, LRPC, ALPC and LPC implementations in Windows. He showed us how the various *PC servers can be exploited to heap-spray and DoS a Windows system. Great talk choked full of information.

The final talk of the day was by Snare who talked about exploiting DMA with Thunderbolt. Really nice stuff as well.

All in all, it was a great conference with nice food and lots of beer. The slides are already available if anyone is interested!

Opencart is a open source ecommerce solution built on PHP. It appears to be a rather popular choice.

Vulnerability #1

While looking through the source code, I noticed a vulnerability in the form of bad password hashing for user accounts in at least two files. admin/model/user/user.php and system/library/customer.php.

Here is the offending code from admin/model/user/user.php.

public function editPassword($user_id, $password) {
    $this->db->query("UPDATE `" . DB_PREFIX . "user` SET salt = '" . $this->db->escape($salt = substr(md5(uniqid(rand(), true)), 0, 9)) . "', password = '" . $this->db->escape(sha1($salt . sha1($salt . sha1($password)))) . "', code = '' WHERE user_id = '" . (int)$user_id . "'");
}

Although the password hash is salted, three iterations of SHA1 is most definitely not slow enough to be a good password hash. I have since reported the issue and a fix should hopefully be incoming. This issue is present on the latest version of Opencart as of this post, v1.5.6.1.

As usual, here is a link to a great essay on the topic of password hashing by Thomas Pornin. Use pbkdf2, bcrypt or scrypt and don’t roll your own.

Vulnerability #2

From upload/system/library/encryption.php,

final class Encryption {
    private $key;

    public function __construct($key) {
        $this->key = hash('sha256', $key, true);
    }

    public function encrypt($value) {
        return strtr(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, hash('sha256', $this->key, true), $value, MCRYPT_MODE_ECB)), '+/=', '-_,');
    }

    public function decrypt($value) {
        return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, hash('sha256', $this->key, true), base64_decode(strtr($value, '-_,', '+/=')), MCRYPT_MODE_ECB));
    }
}

This Encryption class appears to be used in several places in the codebase to encrypt certain values stored in the database. The obvious problem here is that the ECB mode of operation is used. ECB mode is horribly broken because it leaks information about the plaintext. The most famous visual representation of the issue is of course the ECB penguin.

ecb-penguin

A secondary issue is that the code does not apply a MAC on the ciphertext output. This leaves it open to manipulation without a way to verify authenticity. While it isn’t a problem here as far as I can tell given the usage so far, it is generally never a bad idea to apply a cheaply computed MAC like HMAC.

Here is the link to the GitHub issue.

Update So Daniel Kerr closed both my issues calling one of them a “waste of time”. I’m done with looking through Opencart’s source code. I’m very sure that there are other more serious vulnerabilities lurking inside given the lead developer is completely lacking in knowledge and attitude when it comes to security. I strongly suggest that everyone considering using Opencart to steer clear.

As a bonus, here is the crossdomain.xml file present in HEAD of the master branch.

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*"/>
</cross-domain-policy>

If it gets into a released version, every single website running Opencart will be open to a Same Origin Policy bypass through a targeted, malicious flash application. Huzzah!

I am now hosting this blog on Github Pages.

I had been hosting this blog on a Linode VPS for the past year. Linode offers great machines and service for the price and I can strongly recommend them to anyone looking for a VPS. In the end though, a VPS is complete overkill for a simple blog especially after I made the switch from Wordpress to a completely static Jekyll blog. So I decided to move away from Linode to save money and maintenance effort.

Why Github Pages? I was initially considering switching to Amazon S3 with the free CloudFlare CDN plan. However, Github Pages seemed much easier because I already use Github to version control my blog and I did not want to bother with setting up a AWS account.

Setting up the blog itself on Github Pages was a very simple task. Github has great documentation on how to do it. It mostly involved creating and pushing the generated HTML pages to a Github repository and modifying the DNS configurations.

Here’s to blogging more often this year!