Created
December 2, 2025 15:53
-
-
Save daviddarke/078c8f4e79e0542d7dce6c09a65aef6e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # | |
| # Scan a WordPress database for suspicious content. | |
| # | |
| # Usage (from WP root): | |
| # bash scan_wp_db.sh | |
| # | |
| # Or specify a path to the WP root: | |
| # bash scan_wp_db.sh /var/www/html | |
| # | |
| WP_ROOT="${1:-.}" | |
| WP_CONFIG="$WP_ROOT/wp-config.php" | |
| if [ ! -f "$WP_CONFIG" ]; then | |
| echo "Could not find wp-config.php at: $WP_CONFIG" >&2 | |
| exit 1 | |
| fi | |
| if ! command -v php >/dev/null 2>&1; then | |
| echo "php CLI is required to read wp-config.php" >&2 | |
| exit 1 | |
| fi | |
| if ! command -v mysql >/dev/null 2>&1; then | |
| echo "mysql CLI client is required" >&2 | |
| exit 1 | |
| fi | |
| echo "Reading DB credentials from: $WP_CONFIG" | |
| # Extract DB credentials and table prefix via PHP | |
| CONF_STR=$(php -r " | |
| define('SHORTINIT', true); | |
| include '$WP_CONFIG'; | |
| global \$table_prefix; | |
| echo DB_NAME . '|' . DB_USER . '|' . DB_PASSWORD . '|' . DB_HOST . '|' . \$table_prefix; | |
| " 2>/dev/null) | |
| if [ -z "$CONF_STR" ]; then | |
| echo "Failed to read DB settings from wp-config.php" >&2 | |
| exit 1 | |
| fi | |
| IFS='|' read -r DB_NAME DB_USER DB_PASSWORD DB_HOST TABLE_PREFIX <<< "$CONF_STR" | |
| echo "DB_NAME: $DB_NAME" | |
| echo "DB_USER: $DB_USER" | |
| echo "DB_HOST: $DB_HOST" | |
| echo "TABLE_PREFIX: $TABLE_PREFIX" | |
| echo | |
| mysql_cmd=(mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME") | |
| echo "Testing DB connection..." | |
| if ! "${mysql_cmd[@]}" -e "SELECT 1" >/dev/null 2>&1; then | |
| echo "Could not connect to MySQL with the credentials from wp-config.php" >&2 | |
| exit 1 | |
| fi | |
| echo "Connection OK." | |
| echo | |
| ############################################### | |
| # 1) Suspicious patterns in wp_options table # | |
| ############################################### | |
| echo "=== Suspicious patterns in ${TABLE_PREFIX}options.option_value ===" | |
| "${mysql_cmd[@]}" -e " | |
| SELECT option_id, | |
| option_name, | |
| LEFT(option_value, 200) AS sample_value | |
| FROM ${TABLE_PREFIX}options | |
| WHERE option_value REGEXP | |
| '(base64_decode|gzinflate|eval\\(|fromCharCode|document\\.cookie| | |
| <script|<iframe|onerror=|onload=|javascript:|data:text/html)' | |
| ORDER BY option_id DESC | |
| LIMIT 200; | |
| " | |
| echo | |
| ######################################################### | |
| # 2) Suspicious patterns in posts (post_content) # | |
| ######################################################### | |
| echo '=== Suspicious patterns in ${TABLE_PREFIX}posts.post_content ===' | |
| "${mysql_cmd[@]}" -e " | |
| SELECT ID, | |
| post_type, | |
| post_status, | |
| LEFT(post_title, 60) AS title, | |
| LEFT(post_content, 200) AS sample_content | |
| FROM ${TABLE_PREFIX}posts | |
| WHERE post_content REGEXP | |
| '(base64_decode|gzinflate|eval\\(|fromCharCode|document\\.cookie| | |
| <script|<iframe|onerror=|onload=|javascript:|data:text/html)' | |
| ORDER BY ID DESC | |
| LIMIT 200; | |
| " | |
| echo | |
| ######################################################### | |
| # 3) Admin users (check for unexpected administrator) # | |
| ######################################################### | |
| echo "=== Administrator accounts (check for unexpected users) ===" | |
| "${mysql_cmd[@]}" -e " | |
| SELECT u.ID, | |
| u.user_login, | |
| u.user_email, | |
| m.meta_value AS capabilities | |
| FROM ${TABLE_PREFIX}users u | |
| JOIN ${TABLE_PREFIX}usermeta m | |
| ON u.ID = m.user_id | |
| WHERE m.meta_key LIKE '%capabilities' | |
| AND m.meta_value LIKE '%\"administrator\"%'; | |
| " | |
| echo | |
| ######################################################### | |
| # 4) Site URL / Home URL (check for hijacked URLs) # | |
| ######################################################### | |
| echo "=== Site URL / Home URL (ensure these are correct) ===" | |
| "${mysql_cmd[@]}" -e " | |
| SELECT option_id, option_name, option_value | |
| FROM ${TABLE_PREFIX}options | |
| WHERE option_name IN ('siteurl', 'home'); | |
| " | |
| echo | |
| ######################################################### | |
| # 5) Option names that look suspicious # | |
| ######################################################### | |
| echo "=== Suspicious-looking option names (often used by malware) ===" | |
| "${mysql_cmd[@]}" -e " | |
| SELECT option_id, option_name, LEFT(option_value, 200) AS sample_value | |
| FROM ${TABLE_PREFIX}options | |
| WHERE option_name REGEXP '(malware|backdoor|shell|spam|inject|redir|redirect)' | |
| ORDER BY option_id DESC | |
| LIMIT 200; | |
| " | |
| echo | |
| echo "Scan complete. Review the above output carefully." | |
| echo "Anything returned here is *suspicious*, not automatically malicious." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment