Smash Spring4Shell attacks with WAF and Gloo Edge

Another day, another zero-day Remote Code Execution exploit unleashed on Java systems.

Just when you thought it was safe to go back to the water after the Log4Shell exploit a mere four months ago, now the Spring4Shell (or SpringShell) exploit is emerging as another threat to enterprise systems.

The root cause is an issue in the commonly used open-source Java library Spring. Spring has been used by enterprise developers (including yours truly) for many years to simplify Java development and deployment.

Spring4Shell does not appear to be as much of a threat as Log4Shell at this writing, since it is applicable only to users of specific Spring modules and under certain deployment conditions. Log4Shell has a much larger set of targets, since it could infect many Java systems just because they used the popular log4j framework. That being said, Spring4Shell is still a critical RCE vulnerability.

In this post, we’ll address three primary questions.

  1. Are Solo.io products vulnerable to Spring4Shell?
  2. Are my systems fronted by Gloo Edge and Gloo Mesh Gateway vulnerable to Spring4Shell?
  3. If so, how can I remediate those risks? Can Gloo Edge help?

Let’s start with the easiest question first. Solo.io products are NOT vulnerable to Spring4Shell. Solo.io product microservices are implemented most commonly using Go. Envoy proxy is a key open source component deployed with all Solo.io products and is implemented in C++. Consequently, none of them have any Spring dependencies.

But what about my systems?

The fact that Solo.io products themselves aren’t exposed by Spring4Shell is a nice first step. But that doesn’t fully address the second question of whether a customer’s systems that use Gloo Edge as their API gateway are vulnerable.

There are multiple recent CVEs that are loosely associated with Spring4Shell. The one garnering the most attention as of this writing is CVE-2022-22965, which is rated Critical. This exploit takes advantage of a vulnerability in the Spring data binding feature to potentially allow an attacker to execute arbitrary code on a compromised system. It targets applications using particular Spring frameworks (spring-webmvc or spring-webflux), and that are deployed in a Tomcat container as a WAR.

Another related CVE (CVE-2022-22963) targets applications that use the Spring Cloud module. It takes advantage of the Event Routing feature that allows users to pass in routing expressions via a specific HTTP header whose value could contain malicious code.

Good examples of both of these exploits are available in this Fastly post.

Check the resources linked in this section to determine for sure whether you’re vulnerable to Spring4Shell.

I might be vulnerable. How do I respond?

If your systems are potentially at-risk, there are a number of avenues to mitigate that.

  1. Update to the latest patched Spring framework versions. Both the Spring Core and the Spring Cloud framework have already released patched versions that shut down these attack vectors, as shown in the figure below.
  2. Initiate a vulnerability scan. There are both open source and enterprise tools that can thoroughly analyze your systems for vulnerabilities to Spring4Shell (and a hundred others). At Solo.io, we’re particularly fond of our partner Snyk, which offers scanning tools in both free and paid flavors.

Source: sysdig.com

In addition to these two approaches, Gloo Edge can help! The Enterprise edition provides Web Application Firewall support with a widely used framework called ModSecurity, which began life as an Apache project. From the Gloo Edge documentation:

“API Gateways act as a control point for the outside world to access the various application services running in your environment. A Web Application Firewall offers a standard way to inspect and handle all incoming traffic. ModSecurity is one such firewall. ModSecurity uses a simple rules language to interpret and process incoming http traffic.”

The most popular ModSecurity ruleset is sponsored by OWASP and called CoreRuleSet, and it is packaged for use with Gloo Edge Enterprise. The CRS community will doubtless produce rules to thwart Spring4Shell attacks, although none has been published as of this writing. But we don’t need to wait for that to happen.

Later in this post, we will create our own custom ModSecurity rules to block any potentially malicious traffic trying to exploit the Spring4Shell vulnerability. We will implement this in a pair of simple ModSecurity rules, and we will deploy it in a Gloo Edge Gateway component. In this way we can protect any service fronted by that Gateway without modifying the underlying application code.

Example: Use Gloo Edge to block Spring4Shell attacks

We’ll build and test an example to show how you can use Gloo Edge to protect systems that are vulnerable to Spring4Shell attacks.

If you’d like to follow along in your own environment, all you’ll need is a Kubernetes cluster and associated tools like kubectl. We’re using a GKE cluster to perform our testing, but any proper Kubernetes cluster should work fine. In addition, you’ll need an instance of Gloo Edge Enterprise to complete this guide. Installation instructions are here. If you don’t have access to a Gloo Edge Enterprise license key, you can request a free trial here.

We’ll use the popular httpbin service as a stand-in for the systems we need to protect. We’ll then build a simple Gloo Edge VirtualService with no protection against Spring4Shell and simulate some common attack vectors. Finally, we’ll apply simple ModSecurity rules to our Web Application Firewall and demonstrate how those attacks are repulsed with Gloo Edge.

Establish a target service

HTTPBIN is a great little REST service that can be used to test a variety of http operations and echo the request and response elements back to the consumer. We’ll use it throughout this exercise as being representative of systems that are potentially vulnerable to Spring4Shell attacks. First, we’ll install a Gloo Edge Upstream to our cluster that acts as a facade for the public httpbin service.

apiVersion: gloo.solo.io/v1
kind: Upstream
metadata:
  name: httpbin-us
  namespace: gloo-system
spec:
  static:
    hosts:
      - addr: httpbin.org
        port: 80

Run this command to apply the Upstream to your Kubernetes cluster:

kubectl apply -f https://raw.githubusercontent.com/solo-io/solo-blog/main/spring4shell/1-httpbin-us.yaml

You should see this response:

upstream.gloo.solo.io/httpbin-us created

Build a routing policy

We’ll next create a simple Gloo Edge VirtualService to route any request that is presented to our API gateway to the httpbin service.

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: httpbin
  namespace: gloo-system
spec:
  virtualHost:
    domains:
      -  "*"
    routes:
      - matchers:
          - prefix: /
        routeAction:
          single:
            upstream:
              name: httpbin-us
              namespace: gloo-system

Apply that VirtualService using this command:

kubectl apply -f https://raw.githubusercontent.com/solo-io/solo-blog/main/spring4shell/2-httpbin-vs.yaml

Expect this response in return:

virtualservice.gateway.solo.io/httpbin created

The initial service has a security problem!

The attack vectors for the Spring4Shell vulnerabilities that we’re considering here are slightly different. Let’s explore each in turn.

Spring Cloud attack

For the Spring Cloud vulnerability, the attack contains malicious code in a particular request header spring.cloud.function.routing-expression. See this exploit code for an example. In unpatched versions of Spring Cloud, this header is not validated properly and will be executed by the Spring Expression Language (SpEL) interpreter.

We’ll use curl to simulate the attack by passing in the malicious code via the SpEL header through the VirtualService we created in the previous section:

curl -X POST -H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("touch /tmp/pwned")' $(glooctl proxy url)/anything -i

Note that this command will fail if you don’t have the glooctl utility installed on your workstation.

The /anything endpoint of httpbin simply echoes back the request headers passed into it.

You can expect a response like the one below. Note in particular the routing-expression entry in the headers response. Our malicious code was accepted without an error. If httpbin were based on Spring Cloud, which it isn’t, then we might have real exposure to this attack.

HTTP/1.1 200 OK
date: Tue, 05 Apr 2022 15:00:20 GMT
content-type: application/json
content-length: 536
server: envoy
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 56

{
  "args": {},
  "data": "",
  "files": {},
  "form": {},
  "headers": {
    "Accept": "*/*",
    "Content-Length": "0",
    "Host": "35.185.51.108",
    "Spring.Cloud.Function.Routing-Expression": "T(java.lang.Runtime).getRuntime().exec(\"touch /tmp/pwned\")",
    "User-Agent": "curl/7.77.0",
    "X-Amzn-Trace-Id": "Root=1-624c5984-6dd016410a6b8d547cad17cf",
    "X-Envoy-Expected-Rq-Timeout-Ms": "15000"
  },
  "json": null,
  "method": "POST",
  "origin": "35.196.249.10",
  "url": "http://35.185.51.108/anything"
}

Spring MVC and WebFlux attack

For the Spring MVC and WebFlux vulnerability, the attacker POSTs to a vulnerable server a request that manipulates the Java classLoader into using an alternative location to load its malicious code. One fingerprint of this attack is that it POSTs an argument named class.module.classLoader.resources.context.parent.pipeline.first.pattern as shown in this example exploit.

We’ll use curl to simulate the attack by passing in the malicious code via the POST body of the request:

curl -X POST -d "class.module.classLoader.resources.context.parent.pipeline.first.pattern=some-malicious-pattern" $(glooctl proxy url)/anything -i

You can expect a response like the one below. Note in particular that our  classLoader entry was accepted as part of the request. If httpbin were based on Spring MVC or WebFlex and was deployed as a WAR on a Tomcat container, then we might have real exposure to this attack.

HTTP/1.1 200 OK
date: Tue, 05 Apr 2022 14:45:09 GMT
content-type: application/json
content-length: 590
server: envoy
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 39

{
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "class.module.classLoader.resources.context.parent.pipeline.first.pattern": "some-malicious-pattern"
  },
  "headers": {
    "Accept": "*/*",
    "Content-Length": "95",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "35.185.51.108",
    "User-Agent": "curl/7.77.0",
    "X-Amzn-Trace-Id": "Root=1-624c55f5-4d6cbca508fd729913052245",
    "X-Envoy-Expected-Rq-Timeout-Ms": "15000"
  },
  "json": null,
  "method": "POST",
  "origin": "35.196.249.10",
  "url": "http://35.185.51.108/anything"
}

Integrate a simple WAF policy

We’ll craft a policy using two simple WAF rules, one for each of the attack vectors in the previous section. Here is the rule we’ll use to block the Spring Cloud attack.

SecRule REQUEST_HEADERS_NAMES 
  "@contains spring.cloud.function.routing-expression" 
  "id:1001,deny,status:403,log,t:lowercase,msg:'Potential Remote Command Execution: Spring4Shell CVE-2022-22963'"

We won’t attempt a ModSecurity rule authoring tutorial here. There are better sources for that information, like the  official community GitHub site.

But let’s examine briefly what this rule does. Line 1 identifies ModSec variables we want to match. In this case there’s just one: REQUEST_HEADERS_NAMES. This expands to a list of all HTTP header names (not values) associated with the request.

Line 2 is the condition that is applied to the variables listed in Line 1. In this case, we’re using the routing-expression header as the fingerprint of this attack. That is the header that SpEL uses to find code to execute. Note that we are assuming no legitimate uses for this header in external user requests, which seems like a safe assumption for typical applications.

Finally, Line 3 defines the action to be taken when the rule is matched. In this case, if we see this header, we are denying the request and passing back a 403 Forbidden error code.

The second rule to block the Spring MVC and WebFlux attack exhibits a similar structure.

SecRule REQUEST_BODY  
   "@contains class.module.classLoader.resources.context.parent.pipeline.first.pattern" 
   "id:1002,phase:2,deny,status:403,log,msg:'Potential Remote Command Execution: Spring4Shell CVE-2022-22965'"

In this case, the primary difference is that we are looking for the attack fingerprint in the request body rather than in the set of request header names. And as before, we implicitly assume that there are no legitimate uses of the classLoader argument above in typical application requests.

We’ll apply both of these rules at the Gateway level of Gloo Edge. That means that even though we only have one service being managed by this gateway, the rule would equally apply if we had 100 services deployed behind that same Gateway.

This highlights one of the core strengths of Gloo Edge, the ability to apply sophisticated policies once and have them automatically applied to multiple backend services, with no changes to the target services. This is not only true of WAF policies, but also complex authNZ policies too. See our partner Snyk’s recent post about normalizing authentication policies across a variety of backend services.

Here is the full Gateway implementation that we’ll apply to our cluster. Note in particular the waf: configuration.

apiVersion: gateway.solo.io/v1
kind: Gateway
metadata:
  name: gateway-proxy
  namespace: gloo-system
spec:
  bindAddress: '::'
  bindPort: 8080
  proxyNames:
  - gateway-proxy
  httpGateway:
    options:
      waf:
        customInterventionMessage: 'Spring4Shell malicious payload'
        ruleSets:
        - ruleStr: |
            SecRuleEngine On
            SecRule REQUEST_HEADERS_NAMES 
              "@contains spring.cloud.function.routing-expression" 
              "id:1001,deny,status:403,log,t:lowercase,msg:'Potential Remote Command Execution: Spring4Shell CVE-2022-22963'"
        - ruleStr: |
            SecRuleEngine On
            SecRequestBodyAccess On
            SecRule REQUEST_BODY  
              "@contains class.module.classLoader.resources.context.parent.pipeline.first.pattern" 
              "id:1002,phase:2,deny,status:403,log,msg:'Potential Remote Command Execution: Spring4Shell CVE-2022-22965'"

We’ll apply the new Gateway configuration to our cluster using kubectl:

kubectl apply -f https://raw.githubusercontent.com/solo-io/solo-blog/main/spring4shell/3-spring4shell-gw.yaml

Expect a response like this:

gateway.gateway.solo.io/gateway-proxy configured

Now let’s thwart some EVIL!

Can our new Gateway configuration defeat a sampling of Spring4Shell attacks? We’ll answer that by retrying our earlier “attacks” against our service.

Spring Cloud attack

We’ll use curl to simulate the Spring Cloud attack we used before with the unprotected service. Recall that the service fulfilled the request with no problems, which would have created a problem had the backend service been vulnerable to Spring4Shell.

curl -X POST -H 'spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec("touch /tmp/pwned")' $(glooctl proxy url)/anything -i

But observe the difference with our WAF protection in place. The malicious request is now rejected with a 403 Forbidden error.

HTTP/1.1 403 Forbidden
content-length: 30
content-type: text/plain
date: Tue, 05 Apr 2022 18:11:40 GMT
server: envoy

Spring4Shell malicious payload

The Spring Cloud attack is defeated!

Spring MVC and WebFlux attack

Now we’ll repeat the simulated Spring MVC and WebFlux attack:

curl -X POST -d "class.module.classLoader.resources.context.parent.pipeline.first.pattern=some-malicious-pattern" $(glooctl proxy url)/anything -i

The response shows that the ModSec rule is again doing its job as expected:

HTTP/1.1 403 Forbidden
content-length: 30
content-type: text/plain
date: Tue, 05 Apr 2022 18:12:52 GMT
server: envoy

Spring4Shell malicious payload

The MVC and WebFlux attack is defeated!

Clean up

Would you like to reset your environment to its previous state before beginning this exercise? It’s simple. Just issue the following commands to reset the Gateway to not use our WAF policy, and to delete the Upstream and VirtualService we created.

# Reset the Gateway to its original state
kubectl apply -f https://raw.githubusercontent.com/solo-io/solo-blog/main/spring4shell/4-default-gw.yaml
# Delete the Upstream and VirtualService we created
kubectl delete upstream httpbin-us -n gloo-system
kubectl delete virtualservice httpbin -n gloo-system

Learn more

In this post, we confirmed that users of Solo.io products like Gloo Edge do not need to be concerned about those products being vulnerable to Spring4Shell attacks. We also demonstrated how to use the Gloo Edge Web Application Firewall to block Spring4Shell attacks against your own systems with a couple of simple ModSecurity rules. In other words, you can protect against attacks like this at the API gateway level without waiting on updates to the upstream applications.

IMPORTANT NOTE: These are still early days for the Spring4Shell exploit. As of this writing, our collective understanding of how best to protect against it is evolving. Do not accept our simple rule in this post as the final word on the subject. Please do not deploy these sample rules to production without conducting your own due diligence. We recommend following developments in security-focused communities like CoreRuleSet to keep up with the latest best practices.

All resources used to build the example in this post are available on GitHub.

Do you want to explore further how Solo and Gloo Edge can help you secure your microservice and legacy environments?