保守を行っているサイトの作業環境(本番から複製したもの)を作っていたときに、公開フォルダに古いスクリプトがいくつも残っているのを見つけました。
何年も前に、ためしに置いたものです。API のテスト・通知のテスト・メールの確認などで、中には API のキーやパスワードがそのまま書いてありました。URL さえ分かれば、だれでも開ける場所です。
この記事では、こうしたスクリプトを手元のコピーで探す方法と、安全に片付ける順をまとめます。コマンドは、試しのフォルダ(ダミーのファイル)で確かめました。
はじめに:知らないスクリプトを外から開かない
見つけたとき、「外から開けるかだけ見よう」と、ヘッダーだけを取る形(curl -I・HEAD)で本番の URL を開きました。
すると、その中の 1 つが通知を送るスクリプトで、試しの通知が届いてしまいました。
| 思いこみ | 本当のこと |
|---|---|
| HEAD はヘッダーだけなので、プログラムは動かない | PHP は最後まで動く。返すときに本文を捨てているだけ |
| 開くだけなら何も起きない | メールや通知を送る・DB に書くスクリプトなら、開いただけで動く |
中身が分からないスクリプトは、本番では開かず、手元のコピーでファイルを読むのが決まりです。以下のコマンドも、全部手元のコピーで流します。
1. WordPress 本体ではない PHP を探す
一番上の階層を、本体のファイルの一覧と比べる
WordPress の一番上の階層にある PHP は、決まった 15 個です(wp-config.php を含む。WordPress 7.1.3 のファイル一式で数えた)。それ以外を出します。
cd ~/work/example.com/public_html # 手元のコピー core="index.php wp-activate.php wp-blog-header.php wp-comments-post.php wp-config-sample.php wp-config.php wp-cron.php wp-links-opml.php wp-load.php wp-login.php wp-mail.php wp-settings.php wp-signup.php wp-trackback.php xmlrpc.php" for f in *.php; do case " $core " in *" $f "*) ;; *) echo "$f" ;; esac done
| 書き方 | 役目 |
|---|---|
core="…" |
本体のファイルの名前を並べておく |
for f in *.php |
一番上の階層の PHP を 1 つずつ見る |
case " $core " in *" $f "*) |
一覧にあれば何もしない。なければ名前を出す |
WP-CLI が使えるなら、wp core verify-checksums --include-root でも、本体にないファイルを知らせてくれます(WordPress.org の値と比べるので、ネットにつながる環境で使う。この記事では試していません)。
wp core verify-checksums --include-root
画像のフォルダの中の PHP を探す
wp-content/uploads は画像などを置く場所で、ふつう PHP は入りません。あったら必ず中を見ます。
find wp-content/uploads -type f -name "*.php"
日時で見る
WordPress を更新すると、本体のファイルの日時はそろいます。そこから外れたものは、だれかが別に置いたものです。
ls -ltr *.php
# 最後に WordPress を更新した日より前から変わっていないもの find . -type f -name "*.php" ! -newermt "2026-09-01" -printf "%TY-%Tm-%Td %p\n" | sort # 最後に更新した日より後に、だれかが置いたもの find . -type f -name "*.php" -newermt "2026-09-21" -printf "%TY-%Tm-%Td %p\n" | sort
| 書き方 | 役目 |
|---|---|
ls -ltr |
古い順に並べる。何年も前の日時のものが上に来る |
! -newermt "日時" |
その日時より後に変わっていないもの |
-newermt "日時" |
その日時より後に変わったもの |
-printf "%TY-%Tm-%Td %p\n" |
日付とファイルの場所を 1 行で出す |
コピーするときに日時が変わると、この方法は使えません。rsync -a や tar など、日時を残す方法でコピーします。
2. 秘密の値が書かれていないかを探す(ファイル名だけ)
キーやパスワードが書いてありそうなファイルを、よくある言葉で探します。-l を付けて、ファイル名だけを出します。値が画面やログに残らないようにするためです。
grep -rliE "sk-[A-Za-z0-9]|api_key|password|token|secret" --include="*.php" .
| 書き方 | 役目 |
|---|---|
-r |
下のフォルダも全部見る |
-l |
見つかったファイルの名前だけを出す(中身の行は出さない) |
-i |
大文字と小文字を分けない |
-E "…|…" |
どれか 1 つに当たれば出す |
--include="*.php" |
PHP のファイルだけを見る |
wp-config.php には DB のパスワードがあるので、必ず出てきます。本体やプラグインも password の言葉で多く出るので、まずは外して見ます。
grep -rliE "sk-[A-Za-z0-9]|api_key|password|token|secret" --include="*.php" \ --exclude="wp-config.php" --exclude-dir="wp-admin" --exclude-dir="wp-includes" .
出てきたファイルは、手元のコピーでエディタで開いて読みます。言葉で探すだけなので、当たらない書き方のキーもあります。1 で見つけたファイルは、当たらなくても中を読みます。
確かめた結果
作業環境に試しのフォルダを作り、本体のふりをした 15 個のファイルと、次のダミーのファイルを置いて確かめました(値は sk-TESTDUMMY… のような、にせものです)。
| 置いたもの | 中身 | 日時 |
|---|---|---|
| api-test.php | api_key(sk-TESTDUMMY…) | 2019-03-01 |
| notify-test.php | token(ダミー) | 2019-03-01 |
| mail-check.php | password(ダミー) | 2019-03-01 |
| wp-content/uploads/2024/05/thumb.php | 文字を出すだけ | 2026-10-01 |
| 本体のふりをしたファイル・wp-config.php | —(wp-config.php にはダミーの DB のパスワード) | 2026-09-20 |
| 流したもの | 結果 |
|---|---|
| 一番上の階層を本体の一覧と比べる | api-test.php・mail-check.php・notify-test.php の 3 つだけが出た |
| uploads の中の PHP | thumb.php が出た |
ls -ltr *.php |
2019 年の 3 つが上に並んだ |
! -newermt "2026-09-01" |
2019 年の 3 つだけが出た |
-newermt "2026-09-21" |
uploads の thumb.php だけが出た(古いほうの探し方では出ない。両方流す) |
grep -rliE … |
3 つのスクリプトと wp-config.php の 4 つ。値は画面に出なかった |
grep … --exclude="wp-config.php" |
3 つのスクリプトだけ |
| バックアップ(tar)→ 公開フォルダの外へ移す | バックアップの一覧に 3 つ。公開フォルダから消えた |
| 移す前に開く(作業環境・中身がダミーと分かっているもの) | 200 |
| 移した後に開く | 302(トップへ転送)。スクリプトの文字は出なかった |
| キーを公開フォルダの外のファイルから読む形(下の 4) | php -l は問題なし。キーを読めた。公開フォルダの中の grep では出てこない |
移した後が 404 ではなく 302 だったのは、このサイトが「ないページはトップへ送る」作りにしているためです。大事なのは数字ではなく、スクリプトの中身が動かない・出てこないことです。
3. 片付けの順
いきなり消さずに、次の順で進めます。
| 順 | やること | 理由 |
|---|---|---|
| 1 | 手元のコピーで中身を読む | 何をするスクリプトか、どのキーが書いてあるかを知る |
| 2 | バックアップを取る(公開フォルダの外に置く) | あとで「あれは使っていた」と分かったときに戻せる |
| 3 | 公開フォルダの外へ移す | 外から開けなくなる。消すより戻しやすい |
| 4 | 外から開けないことを確かめる | 404 か、トップへの転送になっているか。中身が出ないか |
| 5 | 書いてあったキーを止める・作り直す | 何年も見える場所にあったので、もう知られていると考える |
| 6 | しばらく様子を見てから消す | 困ることが出なければ、移した先とバックアップも決めたときに消す |
cd ~/work/example.com/public_html tar czf ~/old-scripts-backup.tar.gz api-test.php notify-test.php mail-check.php tar tzf ~/old-scripts-backup.tar.gz # 中身の一覧だけを見る mkdir -p ~/moved-scripts mv api-test.php notify-test.php mail-check.php ~/moved-scripts/
for f in api-test.php notify-test.php mail-check.php; do
curl -s -o /dev/null -w "%{http_code} $f\n" "https://example.com/$f"
done
4 で開くのは、移した後だけです。ファイルがもう公開フォルダにないので、スクリプトは動きません。移す前に本番の URL を開くのはやめます。
5 のキーは、ファイルを移しても、それまでに見られていれば使われてしまいます。API のサービスの管理画面で、そのキーを止めるか作り直します。パスワードも変えます。
4. キーやパスワードをスクリプトに書かない
同じことをくり返さないように、秘密の値は公開フォルダの外のファイルに置き、スクリプトからはそれを読みます。
<?php
// 公開フォルダの外に置く(例:public_html と同じ階層の config/)
return [
'api_key' => 'ここに本物のキー',
];
<?php
$conf = require dirname(__DIR__) . '/config/secrets.php';
$apiKey = $conf['api_key'] ?? '';
if ($apiKey === '') {
http_response_code(500);
exit('設定がありません');
}
| 書き方 | 役目 |
|---|---|
dirname(__DIR__) |
このファイルのフォルダの 1 つ上(公開フォルダの外) |
return [ … ]; |
設定のファイルが、値の配列を返す |
$conf['api_key'] ?? '' |
値がなければ空にする |
http_response_code(500); exit(…) |
設定がないときは、そこで止める |
| 置き場所 | 向いているもの |
|---|---|
公開フォルダの外の設定ファイル(上の例・.env など) |
自作のスクリプト・道具 |
wp-config.php(1 つ上の階層に移すこともできる) |
WordPress のサイトで使う値 |
| スクリプトの中に直接 | 使わない |
WordPress の案内(Hardening WordPress)にも、wp-config.php は WordPress を入れたフォルダの 1 つ上に移せること、読めるのは自分とウェブサーバーだけにすること(400 か 440)が書いてあります。
ためしのスクリプトは、作業環境で動かすのがいちばんです。本番に置くときは、使い終わったらその日のうちに消します。
まとめ
| こと | 決まり |
|---|---|
| 知らないスクリプト | 本番で開かない。HEAD でも PHP は動く。手元のコピーで読む |
| 探し方 | 本体の一覧と比べる・uploads の PHP・日時。grep は -l でファイル名だけ |
| 片付け | バックアップ → 外へ移す → 開けないのを確かめる → キーを止める → 後で消す |
| 見つかったキー | 知られていると考えて、止めるか作り直す |
| これから | 秘密の値は公開フォルダの外。スクリプトに書かない |


