The value of fuzzing in your Security Development Lifecycle: A practical demonstration with OpenSSL

The value of fuzzing in your Security Development Lifecycle: A practical demonstration with OpenSSL

The recent high severity OpenSSL vulnerabilities give us a prime example to demonstrate the value fuzzing can have as part of any software testing and overall security development lifecycle activities.

Why do I think it is such a good example ? Well, this short article will show how it takes less than a second to find the same vulnerability, with pictures !

The vulnerabilities were introduced as part of adding support for punycode decoding. Punycode is a mechanism to represent the Unicode character set using limited ASCII characters. So e.g. München is encoded as Mnchen-3ya.

Before we get started, I want to make it clear that my goal here is not to point fingers or blame the OpenSSL developers, many of which are unpaid volunteers. OpenSSL actually uses fuzzing in their testing, and is even part of the oss-fuzz project. Unfortunately it seems the vulnerable code could not be reached from the test cases that were being used in their fuzzing. I still believe this bug is a good example showing how straightforward it can sometimes be to find, or avoid, security vulnerabilities before they make it into production. From experience, I can only say that this could have happened to many.

For this demonstration, I will use the excellent libfuzzer framework, which is a library for coverage-guided fuzzing that is part of the LLVM compiler suite.

The vulnerable ossl_punycode_decode() function takes four arguments and is defined in the crypto/punycode.h header file as

int ossl_punycode_decode 
    const char *pEncoded,
    const size_t enc_len,
    unsigned int *pDecoded,
    unsigned int *pout_length
);        

Working with libfuzzer requires a test harness to call the function you want to test, which in this case can be rather straight forward


#include <stddef.h>
#include <stdint.h>
#include "include/crypto/punycode.h"

int LLVMFuzzerTestOneInput(const char *pEncoded, size_t enc_len) {
        unsigned int pDecoded;
        unsigned int pout_length;

        /* Call the function you want to test with the necessary parameters*/

        ossl_punycode_decode(pEncoded, enc_len, &pDecoded, &pout_length);

        return 0;
}        

That's it, this is everything that we will be needing. As you can see, the calling convention for the function in the header was replicated, and the two parameters that we want to fuzz are defined as arguments for the LLVMFuzzerTestOneInput function.

After building OpenSSL and the fuzzer, one of the vulnerabilities is triggered in less than one second. The error is shown in red.

No alt text provided for this image

In this instance, the offending input that triggered the buffer overflow was 'OOOOOOOOO\012'

And the actual time required to trigger the vulnerability was hardly a third of a second.


real    0m0.359
user    0m0.011s
sys     0m0.061ss        

I hope this demonstration will be helpful to make this process more tangible to some of you. In case you feel this could happen to you or be relevant to your code base, consider integrating this kind of dynamic testing into your software test activities, we are lucky to have solutions available that can help avoid these situations.

If, apart from this quick demo, you are interested in reading an in-depth explanation of the vulnerabilities and exploitation potential, the people at Datadog Security Labs have done an excellent job at that.

Thank you to Dmitry Janushkevich and Mattias K. for reviewing an early draft and providing feedback.

To view or add a comment, sign in

More articles by Thierry Decroix

  • Stuff for the stash, week 45

    Here are some of the interesting news, links, papers and tools I encountered this week and added to my stash. What's my…

    1 Comment

Others also viewed

Explore content categories