How (not) to Fix Command Injection Vulnerabilities

Joshua Smith (@kernelsmith)

Michael Flanders

whoarewe

Josh

"Jock"

"Nerd"

Ezekiel 25:17

Nuclear ICBMs

Broken

Michael

Trouble Maker

Infamous

Dental Office

Motorhead

Pistola

 

"Most athletic" in HS

Suspended for semester for DoS'ing school

"Most likely to be famous" in HS

Used to work in a dental office

President of the National Honor Society in HS

Can recite Pulp Fiction Ezekiel 25:17 speech

Used to flip motorcycles

Was in charge of 50 nuclear ICBMS on 9/11

Bought a pistol off a Dell employee in a Lowe's parking lot

Since DefCon 23, surgeries +=2, metal +=2

whoarewe

Josh

BS Aeronautical Engr

MA Management of Info Sys

Maj, USAF (Ret)

"FuzzOps" Mgr & Senior Security Researcher

Michael

BS Electrical & Computer Engr

UT Austin

Vuln Intelligence Intern @ZDI

Analyzing Submitted Cases

 

The Flow

Original vulnerability

How not to fix it

How to learn nothing from the last one

How to take a better whack at it

How to do it pretty well

Lingering concerns

https://slides.com/kernelsmith/bsidesaustin2018/

Background

ZDI-14-385 / CVE-2014-8420

Disclaimer

ZDI-14-385

Late 2014

Brandon Perry

Dell Sonicwall GMS Virtual Appliance

Web App Auth'd -> root on VA

Command Injection

AKA CVE-2014-8420

"Global Management Systems provides a holistic way to manage your entire network security environment by business processes and service levels."

Analysis

Typically easy for VAs
    Mount virtual disk & partitions in another (Linux) VM
    Explore filesystem
    Grab bins, source, bytecode etc
    Alter boot files/services to get shell
    RE & decompile as nec. (JSP etc)

If you can get a shell (from exploit or above)
    Debug
    Explore
    Tar-up & pull bins, source, bytecode etc

The Code Flow

Is...a bit messy

*.sh

dispatcher

gmc.jar

XmlRpcClient.class

ApplianceMainPage.class

HostConfigManager.class

hostConfig.jsp

0.0.0.0:21009 (TCP)

java (XML-RPC)

0.0.0.0:8035 (TCP)

dispatcher (XML-RPC)

Input

Input

The input validation is here

The bug (root cause) is here

Analysis

Post -> /appliance/applianceMainPage

Add vmdk to another VM & explore

lsblk /dev/sdb -o name,label,fstype,size

NAME   LABEL    FSTYPE   SIZE
sdb                      250G
├─sdb1 ROOT     ext3     7.5G
├─sdb2 SAFEMODE ext3     1.9G
├─sdb3 SYSINFO  ext3     102M
├─sdb4                     1K  # auto-mounting Linux systems will call this 00
├─sdb5          swap     1.9G
└─sdb6 DATA     ext4   238.7G

Mount each & explore

In ROOT:

conf.img   detect-box.sh GMSVP-image.ext2
img.config initrd.img    modules.tar.bz2
root.img   sw.conf       vmlinux

*.sh

dispatcher

gmc.*

XmlRpcClient.*

ApplianceMainPage.*

HostConfigManager.*

hostConfig.jsp

Analysis

Lots of potentially interesting items but...

mount -t ext2 GMSVP-image.ext2 /mnt/GMSVP-image/
cd /mnt/GMSVP-image
# try to find the page by name
find . -iname "*appliancemainpage*"
./Tomcat/webapps/appliance/WEB-INF/classes/com/sonicwall/\
appliance/servlets/ApplianceMainPage.class
#snip
./Tomcat/webapps/appliance/applianceMainPage.jsp
# try to find the page using a string on the page
grep -Rni "IPv4 Settings" * 2>/dev/null
Tomcat/webapps/appliance/hostConfig.jsp:500:
<span class=objItemSpacing><font class=sectionFont> IPv4 Settings </font></span>

ApplianceMainPage.*

hostConfig.jsp

Decompile w/favorite java decompiler

*.sh

dispatcher

gmc.*

XmlRpcClient.*

ApplianceMainPage.*

HostConfigManager.*

hostConfig.jsp

Analysis

Posts are handled by `doPost` in jsp

grep -R "doPost" * | grep -v LogUtil
classes/com/sonicwall/appliance/servlets/ApplianceMainPage.class
#snip
$ grep -R XMLRPCClient *
util/XMLRPCClient.java: public class XMLRPCClient
# looking there you'll also find
config.setServerURL(new URL("http://localhost:21009"));

$ netstat -antp | grep 21009
tcp  0  0 :::21009  :::*  LISTEN  2094/java

$ ps aux | grep 2094
root  2094  0.0  2.0 559896 63004 ? Sl /opt/GMSVP/jre/bin/java -cp...

Now it's just "following your nose"

Refs HostConfigManager & data stored in hmInfo

hmInfo = (HashMap)XMLRPCClient.obtainRpcClient().invoke("get_hostname");

XmlRpcClient.*

HostConfigManager.*

*.sh

dispatcher

gmc.jar

XmlRpcClient.*

ApplianceMainPage.*

HostConfigManager.*

hostConfig.jsp

Analysis

Java command line

    The `-cp` option lists the class paths

    The -D sets a property value

-cp /opt/GMSVP//Scheduler/gmc.jar:\
/opt/GMSVP//Scheduler/applianceUtil.jar \
#snip

-DconfigFile=/opt/GMSVP//conf/sgmsConfig.xml com.sonicwall.appliance.gmc.GMCService \
./GMCService.properties

So configFile set to sgmsConfig.xml for GMCService

Properties file defines the XML-RPC interface

gmc.jar

0.0.0.0:21009 (TCP)

java (XML-RPC)

Analysis

Where does the GMC service handle the RPC?

GMCService.properties:

gmc.service.port=21009
[method]get_hostname=com.sonicwall.appliance.gmc.DispatcherHandler
[method]route=com.sonicwall.appliance.gmc.DispatcherHandler
[method]set_hostname=com.sonicwall.appliance.gmc.DispatcherHandler
#snip
SocketXmlRpcClient client = new SocketXmlRpcClient();
return client.execute(methodName, params);

DispatchHandler.class

So...moar XML-RPC?

*.sh

dispatcher

gmc.jar

XmlRpcClient.*

ApplianceMainPage.*

HostConfigManager.*

hostConfig.jsp

0.0.0.0:21009 (TCP)

java (XML-RPC)

0.0.0.0:8035 (TCP)

dispatcher (XML-RPC)

Analysis

DispatcherHandler.class

    SocketXmlRpcClient.class

        XmlRpcSocketTransportFactory.class

            XmlRpcSocketTransport.class

socket = new Socket("127.0.0.1", 8035);

So what's on 8035?

$ netstat -antp |grep 8035
tcp   0  0  0.0.0.0:8035  0.0.0.0:*  LISTEN  1886/dispatcher

$ ps aux | grep 1886
root  1886  0.0  0.0  ... /opt/vsa/bin/dispatcher -d -c \
  /mnt/old_root/mnt/rootdev/00/default.xml

gmc.jar

0.0.0.0:21009 (TCP)

java (XML-RPC)

dispatcher

0.0.0.0:8035 (TCP)

dispatcher (XML-RPC)

*.sh

dispatcher

gmc.jar

XmlRpcClient.*

ApplianceMainPage.*

HostConfigManager.*

hostConfig.jsp

Analysis

Dispatcher binary

ConfigManager::updateHostName(char const*, char const*)
//...
mov     esi, offset aSetname_shName ; "/setName.sh --name="
mov     rdi, rbx
call    __ZNSs6appendEPKcm ; std::string::append(char const*,ulong)
//...
call    __ZN4snwl6systemERKSs ; snwl::system(std::string const&)

Not all vulnerable fields follow

the same path:

//for the route vuln, dispatcher: calls 
snwl::system("/sbin/route add", attacker_str)

// -> in libsnwlcommon.so
system("/sbin/route add, attacker_str")

dispatcher

*.sh

The Vuln Report for #1

ZDI-14-385 / CVE-2014-8420

Our Vendor Advisory

  • All the stuff you just saw
  • Minus the fancy graphics :)
  • Vulnerable fields
  • Fix should be "low" enough to address all vectors
  • All user input should be treated as malicious and sanitized as needed
  • execve > system
  • Metasploit exploit module

 

DNS      -> dnsSetup.sh
Domain   -> setName.sh
Gateway  -> gwSetup.sh
Hostname -> setName.sh
IP       -> ifSetup.sh
Netmask  -> ifSetup.sh
NTP      -> ntpSetup.sh
Route    -> system("/sbin/route add", str)
post = {
  'action'     => 'host_config',
  'task'       => 'save',
  'hostname'   => "fdsa",
  'domain'     => 'example.com',
  'ip'         => "`#{payload.encoded}`",
  'subnetMask' => '255.255.255.0',
  'gateway'    => '192.168.1.254',
  'dnsServer1' => '8.8.8.8',
  'dnsServer2' => '',
  'dnsServer3' => ''
}

However, vendor knows code base way better than you/us

The Patch for #1

How not to do it

The Story

Don't normally test patches

2014: Nearly 1400 submissions (430 published)

   

2017: Nearly 2400 submissions (1009 published)

 

+ research + conferences + software installation etc...

(1400 + 430 * 2)/250 = 9/bizday
(2400 + 1014 * 2)/250 = 18/bizday

It couldn't hurt to change the injection char right?

But...

*.sh

dispatcher.cpp

gmc.jar

XmlRpcClient.*

ApplianceMainPage.*

HostConfigManager.*

hostConfig.jsp

The Patch

ApplianceMainPage.class

ApplianceMainPage.*

if(paramValues[i] != null && paramValues[i].indexOf('`') >= 0)
{ 
  badParamName = paramName; 
  badParamValue = paramValues[i]; 
  isValid = false; 
  break; 
} 
//... 
if(!isValid) 
  throw new ServletException((new 
  StringBuilder()).append("LinuxCommandInjectionAttempt:")\
    .append(badParamName).append(":").append(badParamValue).toString()); 
else 
  return isValid; 
}

Thoughts?

The Vuln Report for #2

ZDI-15-231 / CVE-2015-3990

Not Much Different

Failed patch of ZDI-14-385

Command injection in multiple fields can't be fixed by looking for a `

The Patch for #2

How not to do it

Now...you know I gotta take a peek

*.sh

dispatcher.cpp

gmc.jar

XmlRpcClient.*

ApplianceMainPage.*

HostConfigManager.*

hostConfig.jsp

The Patch

ApplianceMainPage.*

ApplianceMainPage.*

protected boolean validateFormElementForCommandInjection(HttpServletRequest request) 
throws ServletException 
{ 
  boolean isValid = true; 
  Enumeration parameterNames = request.getParameterNames(); 
  String commandSubstitutePattern = ".*\\$\\(.*\\).*"; // <--- 
//<snip>

if(paramValues[i] != null && paramValues[i].indexOf('`') >= 0) 
{ 
  badParamName = paramName; 
  badParamValue = paramValues[i]; 
  isValid = false; 
  break; 
} 

if(paramValues[i] != null && paramValues[i].matches(commandSubstitutePattern)) 
{ //<snip>

Thoughts?

The Vuln Report for #3

ZDI-16-164 / CVE-2016-2396

Not Much to Change

Failed patch of ZDI-15-231

Command injection in multiple fields can't be fixed by looking for ` and stripping out $()

The Patch for #3

How not to do it

Story

I quickly tried all injection approaches I could think of

Fail.

Yay!

Right?

Don't have the time to pursue further, put on backburner

But, did write a script to "auto-mount"

Return to SonicWall

January 2018

hostConfig.jsp

Domain/Host

Regex

ApplianceMainPage.class

$() and `` check

Normal User

Josh

hostConfigManager.class

Command Injection

Domain/Host

Regex

/^([a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]\.)+[a-zA-Z][-a-zA-Z0-9]*[a-z0-9]$/g
/(?=^.{1,254}$)(^(?:[a-zA-Z0-9-_]{1,63}\.?)+(?:[a-zA-Z0-9]{2,})$)/g

Immediate Differences

hostConfig.jsp Version 8.0

hostConfig.jsp Version 8.2+

Revisiting Bug #3's Patch

ZDI-16-164 / CVE-2016-2396

Input

*.sh

dispatcher.cpp

gmc.jar

XmlRpcClient.class

ApplianceMainPage.class

HostConfigManager.class

hostConfig.jsp

public Object execute(final XmlRpcRequest request) 
        throws XmlRpcException {

        ...

        if (this.checkPayload(methodName, params, ret)) {
            final SocketXmlRpcClient client =     
                new SocketXmlRpcClient();
            ret = client.execute(methodName, params);
        }
        return ret;
    }

gmc.jar

dispatcher.cpp

*.sh

DispatcherHandler.class

private boolean checkPayload(String methodName, 
                                Object[] params, 
                                Object returnMsg)
  {
    boolean ret = false;
    switch (methodName)
    {
    case "set_hostname": 
      ret = validateHostName(params, returnMsg); break;
    case "set_net_if": 
      ret = validateNetInterface(params, returnMsg); break;
    case "set_dns": 
      ret = validateDNS(params, returnMsg); break;
    case "route": 
      ret = validateRoute(params, returnMsg); break;
    case "set_ntp": 
      ret = validateNTP(params, returnMsg); break;
    default: 
      ret = true;
    }
    return ret;
  }

The New Defenses

This seems familiar...

private static String hostnamePattern = 
    "(?=^.{1,64}$)^[a-zA-Z0-9-]+(?<!-)$";

private static String domainPattern =
    "(?=^.{1,254}$)(^(?:[a-zA-Z0-9-_]{1,63}\\.?)+(?:[a-zA-Z0-9]{2,})$)";
/(?=^.{1,254}$)(^(?:[a-zA-Z0-9-_]{1,63}\.?)+(?:[a-zA-Z0-9]{2,})$)/g

The Digital Defense Bug

DDI-VRT-2016-55

Input

*.sh

dispatcher.cpp

gmc.jar

XmlRpcClient.class

ApplianceMainPage.class

HostConfigManager.class

hostConfig.jsp

0.0.0.0:21009 (TCP)

java (XML-RPC)

0.0.0.0:8035 (TCP)

dispatcher (XML-RPC)

PropertyHandlerMapping File

gmc.service.port=21009
[method]set_hostname=com.sonicwall.appliance.gmc.DispatcherHandler
[method]set_net_if=com.sonicwall.appliance.gmc.DispatcherHandler
[method]set_dns=com.sonicwall.appliance.gmc.DispatcherHandler
[method]route=com.sonicwall.appliance.gmc.DispatcherHandler
[method]set_ntp=com.sonicwall.appliance.gmc.DispatcherHandler
[method]set_time_config=com.sonicwall.appliance.gmc.DispatcherHandler

    {
    case "set_hostname": 
    case "set_net_if": 
    case "set_dns": 
    case "route": 
    case "set_ntp": 
    default: 
      ret = true;
    }

checkPayload(...)

The Patch for XML-RPC

The Digital Defense Bugs

The Current Setup

. $VSA_HOME/bin/functions.sh
...

MYTZ=`getParam "$1"`

...

if [ -n "$MYTZ" ]; then
        rm /etc/localtime
        ln -s /usr/share/zoneinfo/ \
            $MYTZ/etc/localtime
fi

The Patch

defaultTimeZone.put("3", new String[] { "3", "America/Anchorage", ...);
defaultTimeZone.put("4", new String[] { "4", "America/Los_Angeles", ...);
defaultTimeZone.put("290", new String[] { "290", "Pacific/Auckland", ...);
defaultTimeZone.put("300", new String[] { "300", "Pacific/Tongatapu", ...);

gmc.jar

this.webserver = new GMCWebServer(getPort());
this.webserver.setParanoid(true);
this.webserver.acceptClient("127.0.0.1");

What Did We Learn?

Probably Not Much, But Let's Check Anyways

The Current State of Defenses

Input

*.sh

dispatcher.cpp

gmc.jar

XmlRpcClient.class

ApplianceMainPage.class

HostConfigManager.class

hostConfig.jsp

0.0.0.0:21009 (TCP)

java (XML-RPC)

0.0.0.0:8035 (TCP)

dispatcher (XML-RPC)

`whoami`

$(whoami)

What We're Going to Conclude

Security bug fixes should probably be reviewed by someone with a security background

Patching the crunchy outer shell is easier, but riskier

Coordinated disclosure works

Sometimes you have to be patient

Dell has made some great improvements but...

Still Have Some Lingering Concerns

XRefs to

    snwl::system()

The Hunt Continues...

Stock photos FTW!

Joshua Smith (@kernelsmith)

Michael Flanders

as Time Permits ;)

Questions?

https://github.com/thezdi

https://slides.com/kernelsmith/BSidesAustin2018

whoarewe

Josh

"Jock"

"Nerd"

Ezekiel 25:17

Nuclear ICBMs

Broken

Michael

Trouble Maker

Infamous

Dental Office

Motorhead

Pistola

 

"Most athletic" in HS

Suspended for semester for DoS'ing school

"Most likely to be famous" in HS

Used to work in a dental office

President of the National Honor Society in HS

Can recite Pulp Fiction Ezekiel 25:17 speech

Used to flip motorcycles

Was in charge of 50 nuclear ICBMS on 9/11

Bought a pistol off a Dell employee in a Lowe's parking lot

Since DefCon 23 surgeries +=2, metal +=2

BSidesAustin2018-HowNotToFixCommandInjection

By kernelsmith

BSidesAustin2018-HowNotToFixCommandInjection

  • 1,936