A brief look into securing calls to Software Libraries and frameworks

Posted by Janani Kehelwala on November 09, 2018 · 11 mins read

One of the most common uses of APIs is integrating libraries and frameworks that has a required behavior to a new software in order to cut down on the development time. It is indeed a very efficient and encouraged mode of developing new software, because

  1. It would be reinventing the wheel
  2. It would likely be reinventing the wheel, only badly, because your implementation may have many flaws while the existing solution is seasoned through years of usage

Therefore any security vulnerabilities exposed through these calls has to be mitigated by the application itself, thus the secure use of library APIs should be thoroughly emphasized during software development. The security concept of protecting oneself against its environment, no matter how fundamental the environment is to the software reemerges here, as observed in security of Virtual Machine Operating Systems (Refer – Overshadow). The idea further relates to the network designing concept of Zero-trust.

Heartbleed

A recent example of a library related vulnerability is the Heartbleed bug (CVE-2014-0160) of the OpenSSL implementation’s TLS/DTLS (transport layer security protocols) heartbeat extension. The recipient of a heartbeat message responds with the same message, but with different random padding. More critically, recipient depends on the sender to specify correct payload lengths. So a malicious sender who modified payload length beyond his message would obtain a reply from the recipient who had read memory beyond the actual message, leaking secure data. Since the bug has been around for a while before public disclosure, the impact was severe. (Refer Attacks of SSL for more information.)

Vulnerable Code

memcpy(bp, pl, payload);

Interpretation: Copy memory from “pl” location to “bp” location, up to the length “payload”.

Fix

/* Read type and payload length first */
       if (1 + 2 + 16 > s->s3->rrec.length)
               return 0; /* silently discard */
...
       if (1 + 2 + payload + 16 > s->s3->rrec.length)
               return 0; /* silently discard per RFC 6520 sec. 4 */

A noteworthy point here would be that heartbeat extension was only required for encrypted UDP communication, and thus could have been disabled for many servers who didn’t require it. (Recompiled with an option to disable heartbeat protocol). So a significant recommendation of secure use of APIs would be practicing system hardening when using open source libraries. Another point to note would be that despite open source software is reviewed by many users, oversights are still possible and implicit trust should not be placed upon their security.

However, while vigilance can be exercised, the responsibility of securing a program against this type of vulnerability may not be a developer responsibility.

Java Serialization Bug

A more developer oriented example would be the Java Serialization Bug (CVE-2015-6420), which also had a massive impact. Serialization is a process used to transfer application native objects from one point to another, (e.g. between clients and servers) over a network as a byte stream. Application can deserialize the stream of data, and the object would be constructed, ready to use by the end point. However, serialized data received by Java applications were not validated upon deserialization. Thus if malicious, specially crafted objects were sent to a server, the server would execute it. Researchers demonstrated that major frameworks such as WebSphere, WebLogic, and JBoss were exploited through this bug, which were made vulnerable through inclusion of Apache Commons Collections library.

The fault exists in both parties, such that untrusted deserialization should not exist on seasoned libraries, or the subsequent applications or frameworks that use the library should not place trust it to implement their security for them.

New releases of Commons Collections library has disabled the insecure functionality, but due to commonality of serialization, other vulnerable libraries are likely to exist. Careful validation before object construction from the deserialized data (which is when arbitrary code execution happens) could protect against this vulnerability. CERT Oracle Coding Standard for Java guidelines SER12-J and SER13-J provides adequate instructions on how to properly overcome this vulnerability by developers. The code extracted from these instructions are included below.

SER12-J. Prevent deserialization of untrusted data guideline suggests a whitelisting approach.

Vulnerable Code

class DeserializeExample {
  public static Object deserialize(byte[] buffer) throws IOException, ClassNotFoundException {
    Object ret = null;
    try (ByteArrayInputStream bais = new ByteArrayInputStream(buffer)) {
      try (ObjectInputStream ois = new ObjectInputStream(bais)) {
        ret = ois.readObject();
      }
    }
    return ret;
  }
}

Here object is directly returned before any validation. Thus, any method that receives this output would use it “as is”, allowing malicious code execution.

Fix – Whitelisting

Inspect the class of any object being deserialized, before its readObject() method is invoked. A set of whitelisted class names are given and equality of class name and serialVersionUID is considered before deserialization. This solution is called “Look-Ahead Java Deserialization”

class WhitelistedObjectInputStream extends ObjectInputStream {
  public Set whitelist;

  public WhitelistedObjectInputStream(InputStream inputStream, Set wl) throws IOException {
    super(inputStream);
    whitelist = wl;
  }

  @Override
  protected Class<?> resolveClass(ObjectStreamClass cls) throws IOException, ClassNotFoundException {
    if (!whitelist.contains(cls.getName())) {
      throw new InvalidClassException("Unexpected serialized class", cls.getName());
    }
    return super.resolveClass(cls);
  }
}

class DeserializeExample {
  private static Object deserialize(byte[] buffer) throws IOException, ClassNotFoundException {
    Object ret = null;
    Set whitelist = new HashSet<String>(Arrays.asList(new String[]{"GoodClass1","GoodClass2"}));
    try (ByteArrayInputStream bais = new ByteArrayInputStream(buffer)) {
      try (WhitelistedObjectInputStream ois = new WhitelistedObjectInputStream(bais, whitelist)) {
        ret = ois.readObject();
      }
    }
    return ret;
  }
}

SEC58-J. Deserialization methods should not perform potentially dangerous operations guideline suggests a restrictive approach.

Vulnerable Code

class OpenedFile implements Serializable {
  String filename;
  BufferedReader reader;

  public OpenedFile(String filename) throws FileNotFoundException {
    this.filename = filename;
    init();
  }

  private void init() throws FileNotFoundException {
    reader = new BufferedReader(new FileReader(filename));
  }
  private void writeObject(ObjectOutputStream out) throws IOException {
    out.writeUTF(filename);
  }

  private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    filename = in.readUTF();
    init();
  }
}

The init method which reads the file is called in the constructor, thus the file is read during deserialization. Reading while receiving a number of such requests could cause consume all available resources.

Fix A

Instantiate first. Initialize later if necessary.

class OpenedFile implements Serializable {
  String filename;
  BufferedReader reader;
  boolean isInitialized;

  public OpenedFile(String filename) {
    this.filename = filename;
    isInitialized = false;
 }

  public void init() throws FileNotFoundException {
    reader = new BufferedReader(new FileReader(filename));
    isInitialized = true;
 }
  private void writeObject(ObjectOutputStream out) throws IOException {
    out.writeUTF(filename);
  }

  private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    filename = in.readUTF();
    isInitialized = false;
 }
}

Fix B

If constructor contains dangerous operations, then the class must be restricted from being deserialized. Thus, readObject() method is overridden to throw an exception.

class Unchangeable implements Serializable {

  // ...
}
class OpenedFile extends Unchangeable { // Serializable, unfortunately
  String filename;
  BufferedReader reader;
  boolean isInitialized;

  public OpenedFile(String filename) {
    this.filename = filename;
    isInitialized = false;
 }

  public void init() throws FileNotFoundException {
    reader = new BufferedReader(new FileReader(filename));
    isInitialized = true;
 }

  private void writeObject(ObjectOutputStream out) throws IOException {
    out.writeUTF(filename);
  }

  private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    throw new NotSerializableException(OpenedFile.getClass().getName());
 }
}

Takeaway

In summary, the recommended practice would be to not have any illusions regarding the weaknesses external libraries present to the security posture of your application, and therein the threat landscape you have to navigate. In summary, following recommendations for securing your application when using external libraries can be suggested.

  • Browse vulnerability databases for the library before inclusion. Understand the implications of vulnerabilities and carefully evaluate inclusion, or implement protection mechanisms.
  • Extra features of libraries may entail security risks. Use a library that provides the bare minimum, or harden it to allow only the required minimum.
  • Verification whether the library meets required security standards
  • Do not pass security responsibilities to libraries. Do not place implicit trust upon any third party software when dealing with security sensitive data.
  • Be wary of defaults enabled by libraries.
  • Different libraries included by your software may include a common library but with different versions. Thus inclusion order should be considered so that the latest version is the one finally included. (This may raise compatibility issues however.)
  • Multiple interfaces may be allowed by the library (e.g. A command line or a web interface), or certain default credentials, both of which must be explicitly disabled before deployment.
  • Validate dynamic inclusions through cryptographic means. For example, externally hosted JavaScript poses a very high vulnerability. Maintain the required files locally and disable Cross Origin Resource Sharing.
  • Validate data flow between your application and included libraries. Obfuscate when necessary.

References

“Understand How Integrating External Components Changes Your Attack Surface.” IEEE Cybersecurity [Online]

“Add heartbeat extension bounds check.” Commit Diff by Dr. Stephen Henson. [Online]

“SEC58-J. Deserialization methods should not perform potentially dangerous operations” [Online]

“SER12-J. Prevent deserialization of untrusted data” [Online]

“What is the Heartbleed bug, how does it work and how was it fixed?”. Josh Fruhlinger [Online]

“Why The Java Deserialization Bug Is A Big Deal”. Jai Vijayan [Online]

“The 10 Worst Vulnerabilities of The Last 10 Years”. Jai Vijayan [Online]

“Apache Commons Collections Java library insecurely deserializes data” [Online]