2026年5月20日

2026年5月20日

WordPressのバックドアを検出・削除する方法

はじめに

バックドアとは攻撃者がマルウェア除去後も再侵入できるように仕掛けた隠し経路です。表面上のマルウェアを除去しても、バックドアが残っていると何度でも再感染します。バックドアを完全に検出・削除しないと根本的な解決になりません。

症状・原因

  • マルウェアを除去したのに短期間で再感染する
  • 不明なURLへのアクセスがサーバーログに記録されている
  • wp core verify-checksums でエラーがないのに不審な動作が続く
  • uploads や wp-includes に見覚えのないPHPファイルがある

解決手順

ステップ1:一般的なバックドアパターンを検索する

# ① eval() を使った動的コード実行
grep -r "eval\s*(" /var/www/html --include="*.php" -l | \
    grep -v "/vendor/" | grep -v "/cache/"

# ② base64デコードしてeval実行(最もよく見られるパターン)
grep -r "eval(base64_decode\|eval(str_rot13\|eval(gzinflate" \
    /var/www/html --include="*.php" -l

# ③ POSTデータからコードを実行するWebシェル
grep -rP "\\\$_(POST|GET|REQUEST|COOKIE)\s*\[.+\]\s*\)" \
    /var/www/html --include="*.php" -l | \
    xargs grep -l "eval\|assert\|system\|exec\|passthru"

# ④ システムコマンド実行関数
grep -rn "system\s*(\|exec\s*(\|passthru\s*(\|shell_exec\s*(" \
    /var/www/html/wp-content --include="*.php" | \
    grep -v "wp-comments-post\|wp-cron"

# ⑤ ファイル作成・書き込み機能
grep -r "file_put_contents\|fwrite.*\$_\|move_uploaded_file" \
    /var/www/html/wp-content/uploads --include="*.php"
# ⑥ WordPress が本来持たないファイルを検索
# uploads ディレクトリ内のPHPファイル
find /var/www/html/wp-content/uploads -name "*.php" -o -name "*.phtml"

# wp-includes 内の追加PHPファイル(コアに含まれないもの)
wp core verify-checksums 2>&1 | grep "should not exist"

# 隠しファイル(ドット始まり)
find /var/www/html -name ".*.php" -o -name ".*config*"

# ランダムな名前のファイル(英数字8文字以上)
find /var/www/html -name "*.php" | grep -E "/[a-z0-9]{8,}\.php$"

ステップ2:Webシェルを検出する

# Webシェルの典型的なパターンを検索
grep -rn "c99\|r57\|b374k\|wso shell\|WSO " \
    /var/www/html --include="*.php"

# $_FILES を使ったアップロード機能
grep -rn "\$_FILES\[" /var/www/html/wp-content \
    --include="*.php" | grep -v "class-wp-hook"

# リモートURLからコードをダウンロードして実行
grep -r "file_get_contents.*http\|curl_exec\|allow_url_fopen" \
    /var/www/html/wp-content --include="*.php" -l | \
    xargs grep -l "eval\|include\|require"

# 変数名を難読化した典型的なパターン
grep -rn "\$[a-zA-Z_]\{1,3\}\s*=\s*\$[a-zA-Z_]\{1,3\}\s*(" \
    /var/www/html/wp-content --include="*.php" | head -20
# アクセスログからWebシェルへのアクセスを検索
# POST リクエストが多いファイルを特定
grep "POST" /var/log/apache2/access.log | \
    awk '{print $7}' | sort | uniq -c | sort -rn | head -20

# 外国IPからのPOSTリクエスト(管理画面外)
grep "POST" /var/log/apache2/access.log | \
    grep -v "wp-login\|wp-admin\|wp-cron\|xmlrpc" | \
    awk '{print $1, $7}' | sort | uniq -c | sort -rn | head -20

ステップ3:データベース内のバックドアを検出する

# wp_options に仕込まれたバックドア
wp db query "
SELECT option_name, LEFT(option_value, 200)
FROM wp_options
WHERE option_value LIKE '%eval(%'
   OR option_value LIKE '%base64_decode%'
   OR option_value LIKE '%assert(%'
   OR option_value LIKE '%system(%'
LIMIT 20"

# wp_users に不正管理者が追加されていないか
wp db query "
SELECT u.ID, u.user_login, u.user_email, u.user_registered,
       m.meta_value as capabilities
FROM wp_users u
JOIN wp_usermeta m ON u.ID = m.user_id
WHERE m.meta_key = 'wp_capabilities'
  AND m.meta_value LIKE '%administrator%'
ORDER BY u.user_registered DESC"

# wp_posts に隠しページ・スパムページがないか
wp db query "
SELECT ID, post_title, post_status, post_type, post_date
FROM wp_posts
WHERE post_status NOT IN ('publish','draft','trash','auto-draft','inherit')
   OR (post_type = 'page' AND post_status = 'publish'
       AND post_title = '')
LIMIT 20"

ステップ4:バックドアを完全に除去する

# ① 特定されたバックドアファイルを削除
# ※ 削除前に内容を確認すること
cat /var/www/html/wp-content/uploads/suspicious-file.php
rm /var/www/html/wp-content/uploads/suspicious-file.php

# ② WordPress コアを完全に再インストール
wp core download --force --locale=ja

# ③ 全プラグインを公式リポジトリから再インストール
wp plugin list --format=ids | xargs wp plugin install --force

# ④ uploads の実行権限を削除
find /var/www/html/wp-content/uploads -name "*.php" -delete

# ⑤ uploads の .htaccess でPHP実行を禁止
cat > /var/www/html/wp-content/uploads/.htaccess << 'EOF'
<FilesMatch "\.(php|phtml|php3|php4|php5|phar)$">
    Order Deny,Allow
    Deny from all
</FilesMatch>
EOF
// functions.php: 管理者以外のファイルアップロードを制限
add_filter('upload_mimes', function(array $mimes): array {
    // 危険な拡張子を全て禁止
    $dangerous = ['php', 'php3', 'php4', 'php5', 'phtml', 'phar',
                  'js', 'htm', 'html', 'shtml', 'xhtml'];
    foreach ($dangerous as $ext) {
        unset($mimes[$ext]);
    }
    return $mimes;
});

// アップロードファイルの拡張子を二重チェック
add_filter('wp_handle_upload_prefilter', function(array $file): array {
    $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    $blocked = ['php', 'php3', 'php4', 'php5', 'phtml', 'phar'];
    if (in_array($ext, $blocked, true)) {
        $file['error'] = 'このファイル形式はアップロードできません。';
    }
    return $file;
});

ステップ5:再侵入を完全に防ぐ

# ① 全パスワードをリセット
wp config shuffle-salts
# 管理者パスワードを変更
wp user update 1 --user_pass="$(openssl rand -base64 32)"

# ② 不審な管理者アカウントを削除
wp user delete {SUSPICIOUS_ID} --reassign=1

# ③ SSH authorized_keys を確認(不明な鍵が追加されていないか)
cat ~/.ssh/authorized_keys

# ④ crontab に不審なジョブが追加されていないか確認
crontab -l
cat /etc/cron.d/* | grep -v "^#"

# ⑤ 全セッションを無効化
wp eval "
global \$wpdb;
\$wpdb->query(\"DELETE FROM {\$wpdb->usermeta} WHERE meta_key = 'session_tokens'\");
echo 'All sessions cleared' . PHP_EOL;
"

注意事項

  • バックドアは複数箇所に仕掛けられることが多いです。1つ見つけて安心せず、全ディレクトリを徹底的にスキャンしてください
  • eval() はWordPressコアでも使用されているため、全てのeval()が悪意あるものではありません。eval(base64_decode(...))eval($_POST[...])のパターンが危険です
  • バックドア除去後は必ずパスワードを全て変更し、セッションを無効化してください。攻撃者がログイン済みセッションを保持している可能性があります

まとめ

バックドア検出は①grep -r "eval(base64_decode" + grep -rP "\$_(POST|GET)" + find ... -name "*.php"で怪しいファイルを特定、②アクセスログで不審なPOSTリクエストを確認、③データベースのwp_optionswp_usersを確認、④特定ファイルを削除後にwp core download --forceでコアを再インストール、⑤shuffle-salts・全パスワードリセット・crontab確認で完全封鎖します。

お気軽にご相談ください

お見積りへ お問い合わせへ