In December 2020, an advanced persistent threat attacked many companies by injecting malicious code into a vendor application that belonged to SolarWinds. This technique is called a "supply chain attack," because instead of targeting the victim directly, the attacker damaged something higher up the supply chain and simply waited. The US government uses SolarWinds, so by attacking SolarWinds in a way that would infiltrate the US government, the attacker effectively got their target.
The SolarWinds attack is unique in that the hackers did not exploit a vulnerability in an application, rather they broke into the company and attacked the development pipeline. The attackers’ implant worked in the build process, injecting new code into SolarWinds Orion as it was built to enable command & control capabilities on target systems that ran the application.
For Java developers and architects who design, build, and run applications, there are two core take-aways:
- Monitoring software at runtime can catch anomalous behavior.
- Existing integrity checks in the Java platform can identify many code manipulation attempts.
Monitoring Software to Catch Anomalous or Unsafe Behavior
One way that the SolarWinds attack was caught came from its outreach to a command and control server in its primary domain (avsvmcloud.com). While Java developers recognize that a URL commonly makes its way to a URLConnection, there are some primary JDK capabilities that will monitor this API, as well as many others.
JDK Flight Recorder
OpenJDK Flight Recorder is not a security tool, rather it enables developers to perform deep analysis of a target JVM’s performance operation. Originally written by Appeal Virtual Machines for JRockit, it forms the foundation of the WebLogic Development Framework, and has been freely available inside OpenJDK-based distributions since Java 11. Several Java 8 distributions have backported the feature, including the free Azul Zulu with Zulu Mission Control.
The aspect that makes JDK Flight Recorder effective for security monitoring is that it records all JVM-level I/O operations, identifying situations like: what URLConnections does the application open, what files does it open for read/write, what SQL calls does the application run, and more. While the primary audience of JDK Mission Control is performance engineers, it will also detect if the application reaches out to an unexpected hostname such as the SUNBURST command & control server. Teams that leverage JDK Flight Recorder may detect various unexpected connections like libraries that phone home, or an application reading remote/local XML Document Type Definitions.
Another capability of JDK Mission Control is its event-streaming, which sends these events outside the system to be reviewed elsewhere. The ability to store data local to the JFR black-box file as well as stream to a central server match the common practices of NIST 800-92 section 3 for log retention and centralized aggregation.
JDK Flight Recorder is intended for use in production environments with low overhead of about 1-2%, making it viable for enterprises to understand what their workloads are doing.
Flight Recorder only offers visibility and does not enable operators to control the action, for example to prohibit a certain URLConnection within the JDK.
Instrumented Monitoring like Contrast Security
The benefit of instrumented security monitoring is the ability to see information, build security intelligence, and take action like report or block.
Instrumentation has been part of Java since JDK 1.5 and enables agents to modify bytecode, typically onMethodEntry and onMethodExit, often to watch incoming values or adjust counters. Monitoring method arguments gives them the ability to see I/O values similar to what can be seen with JDK Flight Recorder. A key benefit of agents over the standard Java SecurityManager is their ability to adapt on context: where the SecurityManager can only allow/deny known files, the adaptive instrumented agent can allow files except when a filename is controlled by the user.
Java developers that monitor their applications with Contrast Community Edition can detect unique security issues to their own application: when the application reads remote data, the agent marks that data as coming from a user (onMethodExit watching the return value). When the application goes to access another asset, such as a File or Runtime.exec, the agent can compare the incoming argument to see if it came from user input (onMethodEntry watching the argument). This would give the ability to detect and correct injection issues, such as Path Traversal and Command Injection. It will similarly give a listing of items like what cryptographic algorithms are in use, where hardcoded passwords are, and a fair amount of other security-oriented information.
Verify Integrity Through Signed JAR Files
Java offers signed JAR files as a way of verifying the integrity of a library. These signatures leverage the public key infrastructure and certificate authorities so that a file can be verified without knowing the original author. Combined with timestamping, developers can know who published a library and when it was signed. In most cases, this integrity verification acts as a safeguard against anyone modifying the library contents either as it goes over the network or by changing a file anywhere in the build pipeline. The SolarWinds case was different, in that attackers targeted the build pipeline before the signature was applied. While this means that the SUNBURST malware would have been signed, signed JAR files represent a net benefit for most developers to validate their software.
The most common process for applying signatures is, an automated tool checks out code and compiles it, packages the class files into a JAR file, then signs the JAR file (or hands it to a signing service). Other users can then verify the signature by verifying signed JAR files even if they do not know the original author. The command for this is:
jarsigner -verify file.jar -verbose
This command will inform whether the signature is valid or if anything has been modified. Software alone cannot validate if the signature was by the right party, so it is critical to verify that the output chains to a valid root certificate authority and that the signer’s name matches what is expected.
If a JAR file is not signed, one of the ways that it can be modified is:
- Extract a selected class file or unzip the entire JAR.
- Decompile any class that you want to target.
- Recompile that source code file, using the JAR file as the classpath.
- Repackage the unsigned JAR file with your modification.
Teams that build applications should verify signatures of any libraries that their application uses, and then sign all libraries that they distribute. While this may overwrite the original signature of a library, it authenticates that the library version went with the application itself. It is not feasible for a standard user to understand who made each library and to check that the signature for each library was signed by the correct party.