Secure Software Design and Development
A Software Development Life Cycle (SDLC) is a framework that defines processes for building applications or information systems. Some of these phases/process may include: Planning, Analysis, Design, Implementation, Testing, Deployment and Maintenance. Traditionally security-related activities were performed late in the development phases. In order to build secure applications, security-activities need to be incorporated in every phase of the SDLC to end up with a Secure SDLC.
The following is an analysis of one of the most widely used software libraries. The analysis illustrates how a Secure SDLC could help prevent some of the major vulnerabilities that have been found in the OpenSSL library.
The following is an analysis of one of the most widely used software libraries. The analysis illustrates how a Secure SDLC could help prevent some of the major vulnerabilities that have been found in the OpenSSL library.
OpenSSL Analysis
Overview
This document provides an analysis of three major design flaws that have resulted in vulnerabilities in OpenSSL. It also identifies best practices in software development that could be applied to design, implementation and maintenance of OpenSSL to avoid or reduce such vulnerabilities. This document also proposes verification and validation techniques that could be used in an effort to reduce or eliminate these types of vulnerabilities.
Assumptions
This document assumes that “major design flaws that have resulted in vulnerabilites” refers to critical or high severity vulnerabilities published in the NIST NVD database. The vulnerabilities used in this document are the result of searching the NIST NVD database for vulnerabilities published within the last year and of a High (7-8.9) or Critical (9-10) CVSS V3 Severity.
With no knowledge of pratices, processes or testing techniques used at OpenSSL.org, this document is making assumptions based on readings and presentations of this course, articles searched on-line , code downloaded and reviewed and the knowledge acquired during this course.
OpenSSL Flaws Analyzed
Overview
This document provides an analysis of three major design flaws that have resulted in vulnerabilities in OpenSSL. It also identifies best practices in software development that could be applied to design, implementation and maintenance of OpenSSL to avoid or reduce such vulnerabilities. This document also proposes verification and validation techniques that could be used in an effort to reduce or eliminate these types of vulnerabilities.
Assumptions
This document assumes that “major design flaws that have resulted in vulnerabilites” refers to critical or high severity vulnerabilities published in the NIST NVD database. The vulnerabilities used in this document are the result of searching the NIST NVD database for vulnerabilities published within the last year and of a High (7-8.9) or Critical (9-10) CVSS V3 Severity.
With no knowledge of pratices, processes or testing techniques used at OpenSSL.org, this document is making assumptions based on readings and presentations of this course, articles searched on-line , code downloaded and reviewed and the knowledge acquired during this course.
OpenSSL Flaws Analyzed
CVE-2016-6309
This flaw was a critical vulnerability that was introduced when applying a patch to address CVE-2016-6307. Which was, if a message larger than 16k was received then the underlying buffer to store the incoming message was reallocated and moved. Unfortunately the pointer to the old location was left unmodified, which resulted in an attempt to write to the previously freed location. This likely would result in a crash, however it could potentially lead to execution of arbitrary code. The following are code snippets of the before and after the modification of the code to fix the bug.
This flaw was a critical vulnerability that was introduced when applying a patch to address CVE-2016-6307. Which was, if a message larger than 16k was received then the underlying buffer to store the incoming message was reallocated and moved. Unfortunately the pointer to the old location was left unmodified, which resulted in an attempt to write to the previously freed location. This likely would result in a crash, however it could potentially lead to execution of arbitrary code. The following are code snippets of the before and after the modification of the code to fix the bug.
Figure 1 – the before code, a call to the BUF_MEM_grow_clean function.
As part of the fix the following new function was added. This function called the old function, but in addition it reset the init_msg so it does not point to the freed memory anymore.
As part of the fix the following new function was added. This function called the old function, but in addition it reset the init_msg so it does not point to the freed memory anymore.
Figure 2 – the after code, the new function which calls the old function, but also fixes the pointer
The patch added the new function grow_init_buf to replace the function BUF_MEM_grow_clean, and the new function will update init_msg when OpenSSL reallocates new memory to make sure it points to valid memory.
The patch added the new function grow_init_buf to replace the function BUF_MEM_grow_clean, and the new function will update init_msg when OpenSSL reallocates new memory to make sure it points to valid memory.
Figure 3
BUF_MEM_grow_clean, the existing function, calls sec_alloc_realloc reallocates new memory, and then frees the old memory (highlighted ‘comment’ above). At this point, however, before the fix s->init_msg was still pointing to the old memory that has been freed by sec_alloc_realloc. Later when s->init_msg was referenced, the program would either crash, or in some cases be forced to access the attacker-controlled memory, which could lead to code execution.
BUF_MEM_grow_clean, the existing function, calls sec_alloc_realloc reallocates new memory, and then frees the old memory (highlighted ‘comment’ above). At this point, however, before the fix s->init_msg was still pointing to the old memory that has been freed by sec_alloc_realloc. Later when s->init_msg was referenced, the program would either crash, or in some cases be forced to access the attacker-controlled memory, which could lead to code execution.
Figure 4. Source from openssl-1.1.0b \ssl\statem\statem.c and openssl-1.1.0a \ssl\statem\statem.c
Analysis of CVE-2016-6309
This vulnerability was introduced while addressing CVE-2016-6307. The fix for CVE-2016-6307 was set to allocate additional buffer space for an incoming message. One thing this fix did not take into account was that if the message was larger than 16k the space realloc’ed for the message would move. The SSL structure keeps a pointer to the buffer and when the space re-allocated for the message moved, the pointer should have been updated in the SSL struct.
In order to maintain existing code efficiently and successfully there are several things that have to be in place. Coding standards, a process for analyzing, designing, implementing and testing the code, the code needs to be readable, well documented and commented and have the necessary tools to support the process. OpenSSL is a complex library. In this specific example, there was a need to ‘grow’ a buffer. An existing function was used to accomplish this, but a detail was left out, some other part of the code was keeping track of the location of the buffer. And that pointer needed to be updated. This issue might have been introduced to the new release of OpenSSL because of lack of testing. One might argue that perhaps if there were proper testing procedure in place this issue might have been caught. It seems that some boundary testing could have possible detected this issue. When testing code the handles buffers typically one should include testcases that test for extremes cases, very large data, no data and somewhere in between. In this case some rigorous test while using large data could have found the bug. Of course analyzing an known issue after the fact is a lot easier than before it happens. But, a well defined process with the right set of tools and due dilligence in following the process can go a long way.
CVE-2016-6303
Integer overflow in the MDC2_Update function in crypto/mdc2/mdc2dgst.c in OpenSSL before 1.0.2i allows remote attackers to cause a denial of service (out-of-bounds write and application crash) or possibly have unspecified other impact via unknown vectors.
Analysis of CVE-2016-6309
This vulnerability was introduced while addressing CVE-2016-6307. The fix for CVE-2016-6307 was set to allocate additional buffer space for an incoming message. One thing this fix did not take into account was that if the message was larger than 16k the space realloc’ed for the message would move. The SSL structure keeps a pointer to the buffer and when the space re-allocated for the message moved, the pointer should have been updated in the SSL struct.
In order to maintain existing code efficiently and successfully there are several things that have to be in place. Coding standards, a process for analyzing, designing, implementing and testing the code, the code needs to be readable, well documented and commented and have the necessary tools to support the process. OpenSSL is a complex library. In this specific example, there was a need to ‘grow’ a buffer. An existing function was used to accomplish this, but a detail was left out, some other part of the code was keeping track of the location of the buffer. And that pointer needed to be updated. This issue might have been introduced to the new release of OpenSSL because of lack of testing. One might argue that perhaps if there were proper testing procedure in place this issue might have been caught. It seems that some boundary testing could have possible detected this issue. When testing code the handles buffers typically one should include testcases that test for extremes cases, very large data, no data and somewhere in between. In this case some rigorous test while using large data could have found the bug. Of course analyzing an known issue after the fact is a lot easier than before it happens. But, a well defined process with the right set of tools and due dilligence in following the process can go a long way.
CVE-2016-6303
Integer overflow in the MDC2_Update function in crypto/mdc2/mdc2dgst.c in OpenSSL before 1.0.2i allows remote attackers to cause a denial of service (out-of-bounds write and application crash) or possibly have unspecified other impact via unknown vectors.
Compare openssl 1.0.2h crypto\mdc2\mdcdgst.c to openssl 1.0.2i crypto\mdc2\mdcdgst.c
An overflow can occur in MDC2_Update() either if called directly or through the EVP_DigestUpdate() function using MDC2. If an attacker is able to supply very large amounts of input data after a previous
call to EVP_EncryptUpdate() with a partial block then a length check can overflow resulting in a heap corruption. The amount of data needed is comparable to SIZE_MAX which is impractical on most platforms.
The function MDC2_Update uses in a comparison i, len, which are unsigned integers. The value of i comes from the MDC2_CTX data structure member num which is also an unsigned integer, provided that these are sufficiently large numbers the sum of i + len can overflow. To address this,
if (i + len < MDC2_BLOCK) was replaced by if (len < MDC2_BLOCK - i).
Analysis of CVE-2016-6303
This vulnerability was about a integer overflow. There was some arithmetic with unsigned integers that if sufficiently large it could result in a overflow calculation. This is the type vulnerability that could possibly be detected during static analysis. This is something that could be checked by a static analysis tool. If in Window OS environment, a compiler like Visual Studio 2015, has options in its Advanced Build Settings to detect arithmetic overflow. Other compilers and IDEs offer plugins that can perform static analysis while coding. If it is not an option in the compiler being used, but using an extensible compiler perhaps this could be a candidate for writing a compiler extension. If this possible overflow condition is not caught during development, it could be caught while testing or the old fashion way code review, manual static analysis, which in a code base this size probably would make sense for specific parts of the code, like recently modified functions.
CVE-2016-2182
The function BN_bn2dec() does not check the return value of BN_div_word(). This can cause an OOB write if an application uses this function with an overly large BIGNUM. This could be a problem if an overly large certificate or CRL is printed out from an untrusted source. TLS is not affected because record limits will reject an oversized certificate before it is parsed.
An overflow can occur in MDC2_Update() either if called directly or through the EVP_DigestUpdate() function using MDC2. If an attacker is able to supply very large amounts of input data after a previous
call to EVP_EncryptUpdate() with a partial block then a length check can overflow resulting in a heap corruption. The amount of data needed is comparable to SIZE_MAX which is impractical on most platforms.
The function MDC2_Update uses in a comparison i, len, which are unsigned integers. The value of i comes from the MDC2_CTX data structure member num which is also an unsigned integer, provided that these are sufficiently large numbers the sum of i + len can overflow. To address this,
if (i + len < MDC2_BLOCK) was replaced by if (len < MDC2_BLOCK - i).
Analysis of CVE-2016-6303
This vulnerability was about a integer overflow. There was some arithmetic with unsigned integers that if sufficiently large it could result in a overflow calculation. This is the type vulnerability that could possibly be detected during static analysis. This is something that could be checked by a static analysis tool. If in Window OS environment, a compiler like Visual Studio 2015, has options in its Advanced Build Settings to detect arithmetic overflow. Other compilers and IDEs offer plugins that can perform static analysis while coding. If it is not an option in the compiler being used, but using an extensible compiler perhaps this could be a candidate for writing a compiler extension. If this possible overflow condition is not caught during development, it could be caught while testing or the old fashion way code review, manual static analysis, which in a code base this size probably would make sense for specific parts of the code, like recently modified functions.
CVE-2016-2182
The function BN_bn2dec() does not check the return value of BN_div_word(). This can cause an OOB write if an application uses this function with an overly large BIGNUM. This could be a problem if an overly large certificate or CRL is printed out from an untrusted source. TLS is not affected because record limits will reject an oversized certificate before it is parsed.
Compare openssl 1.0.2h crypto/bn/bn_print.c to openssl 1.0.2i crypto/bn/bn_print.c
The BN_bn2dec function is part of OpenSSL’s implementation of BIGNUMs. The BN_bn2dec returns printable strings containing decimal encoding of the argument. The return of the BN_div_word function used in BN_bn2dec was not being checked. If an oversized BIGNUM is passed to this function it could cause BN_div_word() to fail resulting in an Out of Bounds write to the bn_data buffer and eventually crash.
Analysis of CVE-2016-2182
This vulnerability is about an internal function BN_div_word being called within the BN_bn2dec function. Under certain circumstance the BN_div_word can fail and result in an out of bounds write. As a general rule it is always good practice to test the return of a function before using it. It may not be always practical or possible (aside from functions that ‘return’ void) but in general the return from a function should be tested before use.
Recommendations for a Better OpenSSL
OpenSSL is widely used. It is built into most versions of Linux and powers almost all SSL for websites. It also available on Windows. OpenSSL is used for many things. Verisign and other companies that issue SSL certificates use it to generate keys. Android, Apple, Microsoft, and other systems that use the AES encryption algorithm to encrypt disk and individual files use it as well. But OpenSSL does not just create keys. It is loaded with encryption and hashing algorithms, like DES3, Blowfish, SHA-1, RSA, etc.
There are many issues with OpenSSL. According information on LibreSSL.org the OpenSSL code needs to be modernized, it needs to be made easier to audit, understand and repair. The state of the code is a product of not having a best-practice development process in place.
Based on many articles online that I have read, the multiple versions of OpenSSL that I have downloaded to identify and diff code to see the before and after of the fixes for specific vulnerabilities and the information provided in libressl.org there are many improvements that could be applied to OpenSSL to make it a more safe and secure library.
As we have learned in this course it much easier to build in security than build it on after the fact. In an ideal situation the OpenSSL replacement could be written from scratch. Where before starting any design and development, processes would be put in place perhaps modelled after or taking concepts and guidelines from NIST SP 800-160. A process for implementing secure software should be created. Where security is a major consideration in each phase of the SDLC.
System Requirements, all technical as well as the security requirements would be identified in this phase.
Functional and Technical designs, again incorporating security into the designs. Perhaps considering hardware-based security support, to make use of secure cryptoprocessors like TPM by the new library.
Coding phase, make sure the right tools are used. IDEs with static analysis plugins or compilers that performs static analysis of the code before or during compilation. Compilers with secure features like Control Flow Integrity and of course full-featured debuggers for debugging, that can also help with dynamically going through the code and unit testing the code.
Testing, for the testing phase, it is important that an effective and efficient process be put in place to identify deficiencies and vulnerabilities that are properly tracked and managed. In order to do this it is very important to have the right tools. Not only effective testing tools but also bug tracking and managing tools. The different types of testing need to be identified and appropriate tools for each of these types need to be acquired. Test engineers to verify the code without actually running it will need static analysis tools. Static analysis tools can find the following vulnerabilities: detect uninitialized variables, bounds checking against array declarations, identify deprecated functions, SQL injection, hard-coded password. In addition to detecting these deficiencies some tools can provide some stats on the code, whitespace, and comments. The software should also be dynamically analyzed to detect vulnerabilities in memory corruption such as buffer overflows or accesses to a dangling pointers. Or use of uninitialized memory accesses or use of already freed memory. Dynamic analysis tools can also find thread deadlocks and issues with synchronization objects. In addition to these tests, the software need to be verified against specification and prior to all this the specifications had to be validated in order to assure that the software will do what it is intended to do.
Maintenance and Release Process
To tie all this together and to avoid situations like with the CVE-2016-6307 fix that introduce an even more severe vulnerability CVE-2016-6309 a maintenance and release process needs to be in place. A process that keeps track of the defects. The current state of the defects or vulnerabilities through the fix cycle. Controls in place that assure that a fix has exited certain gates, like code complete, unit test, integration testing, build, all the testing cycles in the test phase have passed and then release. There has to be a body, a group that oversees the whole development cycle in order to ensure quality.
Conclusions
Security is not something that can be added to software as an afterthought, an insecure library, application or tool may require extensive redesign to secure it.
OpenSSL is widely used, it is everywhere. Replacing this import library is a major undertaking because of the industry dependancy on it. Attempting to replace it by starting from scratch may not be feasible. It would probably take years of development and an enourmous budget. LibreSSL is probably the best next thing to completely replacing the OpenSSL. By implementing best-practice development processes, code reviews, frequent releases, and an open development process and working on cleaning up the code and removing obsolete functionality and providing documentation, perhaps one day LibreSSL will be the better the more secure OpenSSL and it will be widely used as OpenSSL is today.
References
https://nvd.nist.gov/vuln/search
Liu, R. (2016, Dec 12). How to Protect Against OpenSSL 1.1.0a Vulnerability CVE-2016-6309. Retrieved from https://securingtomorrow.mcafee.com/mcafee-labs/how-to-protect-against-openssl-1-1-0a-vulnerability-cve-2016-6309/
https://www.openssl.org/news/secadv/20160922.txt
Rowe, W. (2017, February 09). LibreSSL replacement for OpenSSL. Retrieved from https://www.cursivesecurity.com/blog/2017/libressl-replacement-openssl/
https://www.freebsd.org/security/advisories/
The BN_bn2dec function is part of OpenSSL’s implementation of BIGNUMs. The BN_bn2dec returns printable strings containing decimal encoding of the argument. The return of the BN_div_word function used in BN_bn2dec was not being checked. If an oversized BIGNUM is passed to this function it could cause BN_div_word() to fail resulting in an Out of Bounds write to the bn_data buffer and eventually crash.
Analysis of CVE-2016-2182
This vulnerability is about an internal function BN_div_word being called within the BN_bn2dec function. Under certain circumstance the BN_div_word can fail and result in an out of bounds write. As a general rule it is always good practice to test the return of a function before using it. It may not be always practical or possible (aside from functions that ‘return’ void) but in general the return from a function should be tested before use.
Recommendations for a Better OpenSSL
OpenSSL is widely used. It is built into most versions of Linux and powers almost all SSL for websites. It also available on Windows. OpenSSL is used for many things. Verisign and other companies that issue SSL certificates use it to generate keys. Android, Apple, Microsoft, and other systems that use the AES encryption algorithm to encrypt disk and individual files use it as well. But OpenSSL does not just create keys. It is loaded with encryption and hashing algorithms, like DES3, Blowfish, SHA-1, RSA, etc.
There are many issues with OpenSSL. According information on LibreSSL.org the OpenSSL code needs to be modernized, it needs to be made easier to audit, understand and repair. The state of the code is a product of not having a best-practice development process in place.
Based on many articles online that I have read, the multiple versions of OpenSSL that I have downloaded to identify and diff code to see the before and after of the fixes for specific vulnerabilities and the information provided in libressl.org there are many improvements that could be applied to OpenSSL to make it a more safe and secure library.
As we have learned in this course it much easier to build in security than build it on after the fact. In an ideal situation the OpenSSL replacement could be written from scratch. Where before starting any design and development, processes would be put in place perhaps modelled after or taking concepts and guidelines from NIST SP 800-160. A process for implementing secure software should be created. Where security is a major consideration in each phase of the SDLC.
System Requirements, all technical as well as the security requirements would be identified in this phase.
Functional and Technical designs, again incorporating security into the designs. Perhaps considering hardware-based security support, to make use of secure cryptoprocessors like TPM by the new library.
Coding phase, make sure the right tools are used. IDEs with static analysis plugins or compilers that performs static analysis of the code before or during compilation. Compilers with secure features like Control Flow Integrity and of course full-featured debuggers for debugging, that can also help with dynamically going through the code and unit testing the code.
Testing, for the testing phase, it is important that an effective and efficient process be put in place to identify deficiencies and vulnerabilities that are properly tracked and managed. In order to do this it is very important to have the right tools. Not only effective testing tools but also bug tracking and managing tools. The different types of testing need to be identified and appropriate tools for each of these types need to be acquired. Test engineers to verify the code without actually running it will need static analysis tools. Static analysis tools can find the following vulnerabilities: detect uninitialized variables, bounds checking against array declarations, identify deprecated functions, SQL injection, hard-coded password. In addition to detecting these deficiencies some tools can provide some stats on the code, whitespace, and comments. The software should also be dynamically analyzed to detect vulnerabilities in memory corruption such as buffer overflows or accesses to a dangling pointers. Or use of uninitialized memory accesses or use of already freed memory. Dynamic analysis tools can also find thread deadlocks and issues with synchronization objects. In addition to these tests, the software need to be verified against specification and prior to all this the specifications had to be validated in order to assure that the software will do what it is intended to do.
Maintenance and Release Process
To tie all this together and to avoid situations like with the CVE-2016-6307 fix that introduce an even more severe vulnerability CVE-2016-6309 a maintenance and release process needs to be in place. A process that keeps track of the defects. The current state of the defects or vulnerabilities through the fix cycle. Controls in place that assure that a fix has exited certain gates, like code complete, unit test, integration testing, build, all the testing cycles in the test phase have passed and then release. There has to be a body, a group that oversees the whole development cycle in order to ensure quality.
Conclusions
Security is not something that can be added to software as an afterthought, an insecure library, application or tool may require extensive redesign to secure it.
OpenSSL is widely used, it is everywhere. Replacing this import library is a major undertaking because of the industry dependancy on it. Attempting to replace it by starting from scratch may not be feasible. It would probably take years of development and an enourmous budget. LibreSSL is probably the best next thing to completely replacing the OpenSSL. By implementing best-practice development processes, code reviews, frequent releases, and an open development process and working on cleaning up the code and removing obsolete functionality and providing documentation, perhaps one day LibreSSL will be the better the more secure OpenSSL and it will be widely used as OpenSSL is today.
References
https://nvd.nist.gov/vuln/search
Liu, R. (2016, Dec 12). How to Protect Against OpenSSL 1.1.0a Vulnerability CVE-2016-6309. Retrieved from https://securingtomorrow.mcafee.com/mcafee-labs/how-to-protect-against-openssl-1-1-0a-vulnerability-cve-2016-6309/
https://www.openssl.org/news/secadv/20160922.txt
Rowe, W. (2017, February 09). LibreSSL replacement for OpenSSL. Retrieved from https://www.cursivesecurity.com/blog/2017/libressl-replacement-openssl/
https://www.freebsd.org/security/advisories/