なぜセキュリティが重要か?

セキュリティの脆弱性は、サイトの信頼性を損ない、ユーザーデータの漏洩や改ざんにつながります。

🚨 セキュリティ侵害の影響

  • データ漏洩: ユーザー情報やパスワードの流出
  • サイト改ざん: マルウェアの埋め込み、コンテンツの書き換え
  • SEOダメージ: Googleからペナルティ、検索順位の下落
  • 信頼の喪失: ユーザーや顧客からの信頼を失う
  • 法的責任: GDPR違反などによる罰金

主要な脅威と対策

1. XSS(クロスサイトスクリプティング)

悪意のあるスクリプトを埋め込まれる攻撃です。最も一般的な脆弱性の一つです。

❌ 脆弱なコード

<?php // ユーザー入力をそのまま出力(危険) echo $_POST['name']; echo get_post_meta($post_id, 'custom_field', true); ?>

→ スクリプトタグが実行されてしまう

✅ 安全なコード

<?php // エスケープして出力(安全) echo esc_html($_POST['name']); echo esc_html(get_post_meta($post_id, 'custom_field', true)); ?>

→ スクリプトタグが無害化される

2. SQLインジェクション

データベースクエリに悪意のあるSQL文を挿入される攻撃です。

❌ 脆弱なコード

<?php global $wpdb; // ユーザー入力を直接SQL文に埋め込む(危険) $user_id = $_GET['user_id']; $results = $wpdb->get_results( "SELECT * FROM {$wpdb->users} WHERE ID = $user_id" ); ?>

→ SQL文が改ざんされる可能性

✅ 安全なコード

<?php global $wpdb; // prepare()を使用(安全) $user_id = absint($_GET['user_id']); $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->users} WHERE ID = %d", $user_id ) ); ?>

→ プレースホルダーで安全に処理

3. CSRF(クロスサイトリクエストフォージェリ)

ユーザーの意図しないリクエストを強制的に送信させる攻撃です。

❌ 脆弱なコード

<?php // nonce検証なし(危険) if (isset($_POST['delete'])) { wp_delete_post($_POST['post_id']); } ?>

→ 他サイトから不正なリクエスト可能

✅ 安全なコード

<?php // nonce検証あり(安全) if (isset($_POST['delete'])) { check_admin_referer('delete_post_nonce'); wp_delete_post($_POST['post_id']); } // フォーム内 wp_nonce_field('delete_post_nonce'); ?>

→ トークンで正規リクエストを確認

エスケープ関数一覧

出力時に必ず使用する関数です。用途に応じて適切な関数を選びます。

esc_html()

HTML内のテキスト出力

echo esc_html($text);

esc_attr()

HTML属性値の出力

value="<?php echo esc_attr($value); ?>"

esc_url()

URLの出力

href="<?php echo esc_url($url); ?>"

esc_js()

JavaScript内の文字列

var text = '<?php echo esc_js($text); ?>';

esc_textarea()

textareaの内容

<textarea><?php echo esc_textarea($content); ?></textarea>

wp_kses()

許可したHTMLのみ出力

echo wp_kses($content, $allowed_tags);

wp_kses_post()

投稿で許可されたHTMLのみ

echo wp_kses_post($content);

💡 エスケープのルール

  • 出力の直前でエスケープする
  • データベースに保存する前ではなく、表示する時にエスケープ
  • エスケープは何度行っても安全(二重エスケープの心配不要)
  • 信頼できるデータでも必ずエスケープする習慣を

サニタイズ関数一覧

入力データを保存・処理する前に使用する関数です。

sanitize_text_field()

テキストフィールドの値

$clean = sanitize_text_field($_POST['name']);

sanitize_email()

メールアドレス

$email = sanitize_email($_POST['email']);

sanitize_url()

URL(esc_url_raw()推奨)

$url = esc_url_raw($_POST['website']);

sanitize_key()

キー(小文字英数字のみ)

$key = sanitize_key($_POST['option_name']);

sanitize_file_name()

ファイル名

$filename = sanitize_file_name($_FILES['file']['name']);

absint()

正の整数

$id = absint($_POST['user_id']);

intval()

整数(負数も含む)

$number = intval($_POST['count']);

sanitize_hex_color()

カラーコード

$color = sanitize_hex_color($_POST['color']);

Nonce(ワンタイムトークン)の使い方

CSRF攻撃を防ぐための仕組みです。すべてのフォーム処理で必須です。

基本的な使い方

フォーム側<form method="post"> <?php wp_nonce_field('my_action', 'my_nonce'); ?> <input type="text" name="username"> <button type="submit">送信</button> </form>
処理側<?php if (isset($_POST['my_nonce'])) { // nonce検証 if (!wp_verify_nonce($_POST['my_nonce'], 'my_action')) { wp_die('セキュリティチェックに失敗しました'); } // 検証成功後、処理を実行 $username = sanitize_text_field($_POST['username']); // データベース保存など } ?>

管理画面での簡単な方法

check_admin_referer()を使用<?php // フォーム wp_nonce_field('delete_user'); // 処理 if (isset($_POST['delete'])) { check_admin_referer('delete_user'); // 削除処理 } ?>

URLパラメータでの使用

URL生成<?php $url = wp_nonce_url( admin_url('admin.php?action=delete&id=123'), 'delete_item_123' ); echo '<a href="' . esc_url($url) . '">削除</a>'; ?>
URL検証<?php if (isset($_GET['action']) && $_GET['action'] === 'delete') { if (!wp_verify_nonce($_GET['_wpnonce'], 'delete_item_123')) { wp_die('不正なリクエストです'); } // 削除処理 } ?>

権限チェック

ユーザーが操作を実行する権限があるか確認します。

基本的な権限チェック

current_user_can()の使用<?php // 投稿を編集できるか if (!current_user_can('edit_posts')) { wp_die('権限がありません'); } // 特定の投稿を編集できるか if (!current_user_can('edit_post', $post_id)) { wp_die('この投稿を編集する権限がありません'); } // 管理者のみ if (!current_user_can('manage_options')) { wp_die('管理者のみ実行できます'); } ?>

主な権限(Capability)

  • manage_options: 管理者のみ
  • edit_posts: 投稿の編集
  • publish_posts: 投稿の公開
  • delete_posts: 投稿の削除
  • edit_pages: 固定ページの編集
  • edit_users: ユーザー管理
  • upload_files: ファイルのアップロード

データベース操作のセキュリティ

$wpdb->prepare() の正しい使い方

プレースホルダー<?php global $wpdb; // %d = 整数 $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->posts} WHERE ID = %d", $post_id ) ); // %s = 文字列 $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->users} WHERE user_login = %s", $username ) ); // %f = 浮動小数点数 $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->postmeta} WHERE meta_value = %f", $price ) ); // 複数のプレースホルダー $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->posts} WHERE post_author = %d AND post_status = %s AND post_date > %s", $author_id, $status, $date ) ); ?>

⚠️ 注意点

  • テーブル名やカラム名にはプレースホルダーを使えない
  • IN句で配列を使う場合は特別な処理が必要
  • LIKE検索では %(ワイルドカード)を別途処理

ファイルアップロードのセキュリティ

安全なファイルアップロード処理<?php // nonceと権限チェック check_admin_referer('upload_file'); if (!current_user_can('upload_files')) { wp_die('ファイルをアップロードする権限がありません'); } // ファイル検証 if (isset($_FILES['upload_file'])) { $file = $_FILES['upload_file']; // ファイルサイズチェック $max_size = 5 * 1024 * 1024; // 5MB if ($file['size'] > $max_size) { wp_die('ファイルサイズが大きすぎます'); } // MIMEタイプチェック $allowed_types = array('image/jpeg', 'image/png', 'image/gif'); $file_type = wp_check_filetype($file['name']); if (!in_array($file_type['type'], $allowed_types)) { wp_die('許可されていないファイル形式です'); } // WordPressのアップロード処理を使用 require_once(ABSPATH . 'wp-admin/includes/file.php'); $uploaded = wp_handle_upload($file, array('test_form' => false)); if (isset($uploaded['error'])) { wp_die($uploaded['error']); } // アップロード成功 $file_url = $uploaded['url']; } ?>

セキュリティチェックリスト

入力処理

  • すべてのユーザー入力をサニタイズ
  • 型変換(absint, intval など)を適切に使用
  • ファイルアップロードの検証を実装

出力処理

  • すべての出力をエスケープ
  • 用途に応じた適切なエスケープ関数を使用
  • 信頼できるデータでもエスケープ

データベース

  • $wpdb->prepare() を必ず使用
  • 直接SQL文にユーザー入力を埋め込まない
  • 適切なプレースホルダー(%d, %s, %f)を使用

認証・認可

  • すべてのフォームにnonceを実装
  • 権限チェック(current_user_can)を実装
  • Ajax処理でもnonce検証を実装

その他

  • エラーメッセージで詳細情報を漏らさない
  • デバッグモードを本番環境で無効化
  • 定期的なWordPress/プラグインのアップデート
  • Theme Check プラグインでセキュリティ検証

実践例:安全なフォーム処理

完全なセキュリティ対策例<?php // 管理画面ページの登録 function mytheme_add_admin_menu() { add_menu_page( 'セキュアフォーム', 'セキュアフォーム', 'manage_options', 'secure-form', 'mytheme_secure_form_page' ); } add_action('admin_menu', 'mytheme_add_admin_menu'); // フォーム表示 function mytheme_secure_form_page() { // 権限チェック if (!current_user_can('manage_options')) { wp_die('権限がありません'); } // フォーム処理 if (isset($_POST['submit'])) { // nonce検証 check_admin_referer('mytheme_secure_form'); // 入力値のサニタイズ $name = sanitize_text_field($_POST['name']); $email = sanitize_email($_POST['email']); $url = esc_url_raw($_POST['website']); $age = absint($_POST['age']); // バリデーション if (empty($name)) { echo '<div class="error"><p>名前を入力してください</p></div>'; } elseif (!is_email($email)) { echo '<div class="error"><p>有効なメールアドレスを入力してください</p></div>'; } else { // データベースに保存 global $wpdb; $wpdb->insert( $wpdb->prefix . 'my_table', array( 'name' => $name, 'email' => $email, 'website' => $url, 'age' => $age, ), array('%s', '%s', '%s', '%d') ); echo '<div class="updated"><p>保存しました</p></div>'; } } // フォーム表示 ?> <div class="wrap"> <h1>セキュアフォーム</h1> <form method="post"> <?php wp_nonce_field('mytheme_secure_form'); ?> <table class="form-table"> <tr> <th><label for="name">名前</label></th> <td> <input type="text" id="name" name="name" value="<?php echo isset($_POST['name']) ? esc_attr($_POST['name']) : ''; ?>" class="regular-text"> </td> </tr> <tr> <th><label for="email">メール</label></th> <td> <input type="email" id="email" name="email" value="<?php echo isset($_POST['email']) ? esc_attr($_POST['email']) : ''; ?>" class="regular-text"> </td> </tr> <tr> <th><label for="website">ウェブサイト</label></th> <td> <input type="url" id="website" name="website" value="<?php echo isset($_POST['website']) ? esc_attr($_POST['website']) : ''; ?>" class="regular-text"> </td> </tr> <tr> <th><label for="age">年齢</label></th> <td> <input type="number" id="age" name="age" value="<?php echo isset($_POST['age']) ? absint($_POST['age']) : ''; ?>" class="small-text"> </td> </tr> </table> <?php submit_button('保存'); ?> </form> </div> <?php } ?>

まとめ

セキュリティ対策は、WordPress開発の最重要課題です。

🎊 セキュアなテーマ開発完了!

セキュリティ対策を習得すれば、プロフェッショナルなWordPress開発者として信頼されるテーマを作成できます。学んだ知識を必ず実践に活かしましょう。