応用編
🔒 セキュリティ対策
WordPress開発における最重要課題であるセキュリティ対策を学びます。XSS、SQLインジェクション、CSRFなどの攻撃から守る方法と、安全なコードを書くためのベストプラクティスを習得しましょう。
難易度: ★★★★☆ 上級(必須知識)なぜセキュリティが重要か?
セキュリティの脆弱性は、サイトの信頼性を損ない、ユーザーデータの漏洩や改ざんにつながります。
🚨 セキュリティ侵害の影響
- データ漏洩: ユーザー情報やパスワードの流出
- サイト改ざん: マルウェアの埋め込み、コンテンツの書き換え
- 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開発の最重要課題です。
- ✅ 出力時は必ずエスケープ
- ✅ 入力時は必ずサニタイズ
- ✅ すべてのフォームにnonce
- ✅ 権限チェックを実装
- ✅ データベースはprepare()を使用
🎊 セキュアなテーマ開発完了!
セキュリティ対策を習得すれば、プロフェッショナルなWordPress開発者として信頼されるテーマを作成できます。学んだ知識を必ず実践に活かしましょう。