wp-security-review
WordPressのセキュリティコード審査と脆弱性検出を実施します。WordPressのPHPコードをセキュリティの観点から審査したい場合、テーマやプラグインの脆弱性を監査したい場合、リリース前にコードを確認したい場合、AJAX/RESTハンドラーの脆弱性を分析したい場合、XSS、SQLインジェクション、CSRF、認可回避などを検出したい場合に使用できます。ユーザーが「セキュリティ審査」「脆弱性」「XSS」「SQLインジェクション」「CSRF」「nonce」「サニタイゼーション」「エスケープ」「権限確認」「権限昇格」「ファイルアップロードセキュリティ」「不安全なコード」「認証回避」「セキュリティ監査」などのキーワードを述べた際にも対応します。入力処理、出力エスケープ、認可、nonce、データベースクエリ、ファイルアップロード、危険な関数における脆弱性を検出できます。
description の原文を見る
WordPress security code review and vulnerability detection. Use when reviewing WordPress PHP code for security issues, auditing themes/plugins for vulnerabilities, checking code before launch, analyzing AJAX/REST handlers for exploits, detecting XSS, SQL injection, CSRF, or authorization bypass, or when user mentions "security review", "vulnerability", "XSS", "SQL injection", "CSRF", "nonce", "sanitization", "escaping", "capability check", "privilege escalation", "file upload security", "insecure code", "auth bypass", or "security audit". Detects vulnerabilities in input handling, output escaping, authorization, nonces, database queries, file uploads, and dangerous functions.
SKILL.md 本文
WordPressセキュリティレビュースキル
概要
WordPressのテーマ、プラグン、カスタムコードのシステマティックなセキュリティコードレビュー。コアの原則: 3つの柱セキュリティモデル: (1) 入力を早期にサニタイズする、(2) 認可を検証する(nonce + 権限)、(3) 出力を遅くエスケープする。重大な問題から順にスキャン(公開コードのSQLインジェクション、エスケープされていない出力のXSS、状態変更操作における欠落したnonce)、その後警告、その後情報レベルの改善。行番号、重大度、CWE参照、および悪い/良いコードペアでレポートします。
使用する場合
使用する場合:
- WordPressテーマまたはプラグインのセキュリティ監査
- ローンチ前のセキュリティレビュー
- 報告された脆弱性の調査
- フォームハンドラー、AJAXエンドポイント、またはREST APIルートのレビュー
- ユーザー入力処理または出力レンダリングの分析
- エクスプロイト可能なパターン(XSS、SQLインジェクション、CSRF、認証回避)のコードレビュー
使用しない場合:
- サーバーのハードニングまたはインフラストラクチャセキュリティ(Apache/nginxコンフィグ、ファイアウォールルール)
- Web Application Firewall (WAF) コンフィグレーション
- SSL/TLS証明書管理
- パフォーマンスのみの監査(wp-performance-reviewを使用)
- 一般的な非WordPressのPHPセキュリティ
コードレビューワークフロー
- ファイルタイプを特定し、以下の関連チェックを適用する
- 重大なパターンを最初にスキャン (公開コードのSQLインジェクション、エスケープされていない出力のXSS、状態変更操作における欠落したnonce、検証なしのファイルアップロード)
- 警告パターンをチェック (認可の問題、管理者限定XSS、不完全な検証)
- 情報改善を記録 (多層防御、セキュリティ定数、ハードニング機会)
- コンテキスト認識型の重大度調整を適用 (管理者限定コード = 低い重大度、公開コード = 最高の重大度、WP-CLI/cron = nonceは不要、REST API = 異なる認可モデル)
- 行番号を使用してレポートし、以下の出力形式を使用する
ファイルタイプ固有のチェック
プラグイン/テーマPHPファイル (functions.php、plugin.php、*.php)
スキャン対象:
- トップにある
defined( 'ABSPATH' ) || exit;の欠落 → 警告: 直接ファイルアクセスが可能 eval(→ 重大: コードインジェクションベクトル (CWE-95)exec(、shell_exec(、system(、passthru(→ 重大: ユーザー入力がある場合、コマンドインジェクション (CWE-78)base64_decode( $_→ 重大: エンコードされたペイロード実行の可能性unserialize( $_→ 重大: オブジェクトインジェクション (CWE-502)sanitize_*なしの$_GET[、$_POST[、$_REQUEST[→ 警告: サニタイズされていない入力 (CWE-20)include $_、require $_、include_once $_、require_once $_→ 重大: パストトラバーサル/インクルージョン (CWE-22)- 生の
move_uploaded_file(→ 重大: 代わりにwp_handle_upload()を使用
フォームハンドラー (admin-post.php、admin_post_* フック)
3段階パターンをスキャン (すべてが存在する必要があります):
- Nonce検証 →
wp_verify_nonce( $_POST['nonce_field'], 'action_name' )またはcheck_admin_referer()→ 欠落時は重大 (CWE-352) - 権限チェック →
current_user_can( 'capability' )→ 欠落時は重大 (CWE-862) - 入力サニタイゼーション →
sanitize_text_field()、sanitize_email()など → 欠落時は警告 (CWE-20)
いずれかのステップを欠落 = 脆弱性。状態変更操作はすべての3つが必須です。
AJAXハンドラー (wp_ajax_*、wp_ajax_nopriv_* フック)
スキャン対象:
check_ajax_referer( 'action_name', 'nonce_field' )→ 状態変更操作で欠落時は重大 (CWE-352)current_user_can( 'capability' )→ 権限が必要な場合に欠落時は重大 (CWE-862)- nonce なしの
wp_ajax_nopriv_*→ 重大: CSRF保護なしの公開エンドポイント sanitize_*なしの$_POSTからの入力 → 警告: サニタイズされていない入力 (CWE-20)- ユーザー入力をエコーする時にエスケープなしの
wp_send_json()→ 警告: JSONインジェクション
REST APIエンドポイント (register_rest_route)
スキャン対象:
- 欠落した
permission_callback→ 重大: WordPress 5.5+ では必須 (CWE-862) - 書き込み操作での
'permission_callback' => '__return_true'→ 重大: 未認可アクセスを許可 - 公開読み取りエンドポイントでの
'permission_callback' => '__return_true'→ OK (脆弱性ではない) - パーミッションコールバック内の欠落した
current_user_can()→ 警告: 権限昇格を許可する可能性 (CWE-863) $request->get_param()における欠落した入力検証 → 警告: サニタイズされていない入力 (CWE-20)X-WP-NonceヘッダーまたはアプリケーションパスワードによるREST認証 → 情報: クライアントがヘッダーを送信することを確認
データベースコード ($wpdb->query、$wpdb->get_results など)
スキャン対象:
- 文字列連結または補間を伴う
$wpdb->query(または$wpdb->get_results(→ 重大: SQLインジェクション (CWE-89) - ユーザー入力がある場合の欠落した
$wpdb->prepare()→ 重大: SQLインジェクション (CWE-89) $wpdb->prepare( "... LIKE '%{$term}%'" )→ 警告: 最初に$wpdb->esc_like()を使用すべき- 二重準備(既に準備された文字列の準備) → 警告: エスケープ文字をエスケープ
- クエリにおける
{$wpdb->prefix}の直接使用 → OK (脆弱性ではない、標準パターン)
安全なパターン: $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->posts} WHERE ID = %d", $post_id ) )
テンプレート/出力ファイル (テーマ内の *.php、テンプレート)
スキャン対象:
- エスケープ関数なしの
echo $またはprint $→ 重大: XSS (CWE-79) <input value="<?php echo $→ 重大:esc_attr()を使用 (CWE-79)<a href="<?php echo $→ 重大:esc_url()を使用 (CWE-79)- PHP変数を伴う
<script>ブロック → 重大:esc_js()またはwp_localize_script()を使用 (CWE-79) wp_kses()またはwp_kses_post()なしのリッチHTML出力 → 警告: 安全でないタグを許可する可能性
安全なパターン: 常に出力ポイントでエスケープ、入力または保存時ではなく。
ファイルアップロードハンドラー
スキャン対象:
wp_handle_upload()の代わりにmove_uploaded_file(→ 重大: WPセキュリティチェックをバイパス (CWE-434)- 欠落した
current_user_can()チェック → 重大: 未認可ファイルアップロード (CWE-862) - MIMEタイプ検証なし → 警告: 実行可能ファイルのアップロードを許可する可能性
- ファイルサイズ制限なし → 情報: DoSを可能にする可能性
- 欠落した
wp_check_filetype_and_ext()→ 警告: ファイルタイプスプーフィング可能 (CWE-434)
JavaScriptファイル (*.js、*.jsx)
スキャン対象:
X-WP-NonceヘッダーなしのREST APIへのfetch()または$.ajax()→ 警告: CSRF脆弱性 (CWE-352)- データに nonce なしの
$.post( ajaxurl, ...)→ 警告: CSRF脆弱性 (CWE-352) innerHTML = userInputまたはdangerouslySetInnerHTML→ 重大: DOMベースXSS (CWE-79)eval(→ 重大: コードインジェクションベクトル (CWE-95)
迅速検出のための検索パターン (SEC-20)
# 重大: SQLインジェクションパターン
grep -rn "\$wpdb->query(" . | grep -v "prepare"
grep -rn "\$wpdb->get_results(" . | grep -v "prepare"
grep -rn "\$wpdb->get_var(" . | grep -v "prepare"
grep -rn "\$wpdb->get_row(" . | grep -v "prepare"
# 重大: XSS (エスケープされていない出力)
grep -rn "echo \$_" .
grep -rn "print \$_" .
grep -rn "<?php echo \$" . | grep -v "esc_"
# 重大: 危険な関数
grep -rn "eval(" .
grep -rn "exec(" .
grep -rn "shell_exec(" .
grep -rn "system(" .
grep -rn "passthru(" .
grep -rn "base64_decode(\$_" .
grep -rn "unserialize(\$_" .
# 重大: WP関数なしのファイルアップロード
grep -rn "move_uploaded_file(" .
# 重大: permission_callback なしのREST API
grep -rn "register_rest_route" . | grep -v "permission_callback"
# 重大: パストトラバーサル/動的インクルージョン
grep -rn "include \$_" .
grep -rn "require \$_" .
grep -rn "include_once \$_" .
grep -rn "require_once \$_" .
# 警告: サニタイズされていない入力の使用
grep -rn "\$_GET\[" . | grep -v "sanitize_"
grep -rn "\$_POST\[" . | grep -v "sanitize_"
grep -rn "\$_REQUEST\[" . | grep -v "sanitize_"
# 警告: 公開AJAXでのnonce欠落
grep -rn "wp_ajax_nopriv_" .
# 警告: 権限チェック欠落
grep -rn "admin_post_" . | grep -v "current_user_can"
grep -rn "wp_ajax_" . | grep -v "current_user_can"
# 情報: ABSPATH チェック欠落
grep -rn "<?php" . | head -20 | grep -v "ABSPATH"
# 情報: セキュリティ定数欠落 (wp-config.phpをチェック)
grep -n "DISALLOW_FILE_EDIT" wp-config.php
grep -n "FORCE_SSL_ADMIN" wp-config.php
grep -n "DISALLOW_UNFILTERED_HTML" wp-config.php
プラットフォームコンテキスト
異なるホスティング環境は、異なるセキュリティレイヤーを提供します:
マネージドWordPressホスト (WP Engine、Pantheon、Pressable、WordPress VIP など):
- しばしば、プラットフォームレベルのWAFとマルウェアスキャンを備える
- 特定の危険な関数(eval、exec)をプラットフォームレベルでブロックする可能性
- セキュリティ上の追加要件についてはホストドキュメントを確認
- コードはポータビリティのために安全に書かれるべき
WordPress VIP:
- 厳密なコードレビュー要件
- 多くの危険な関数が禁止
- 追加のセキュリティヘルパーが利用可能 (
wpcom_vip_*関数) - 必須パターンについてはVIPドキュメントを参照
自己ホスト/標準ホスティング:
- コードが実装するもの以外のプラットフォームレベルセキュリティなし
- セキュアなコーディングプラクティスの責任が増加
- 3つの柱すべて(サニタイズ、認可、エスケープ)が実装されていることを確認
クイックリファレンス: 重大なセキュリティパターン
XSS防止 (出力エスケープ)
// ❌ 重大: エスケープされていない出力 (CWE-79)
<h1><?php echo $_GET['title']; ?></h1>
<input value="<?php echo $user_input; ?>">
<a href="<?php echo $url; ?>">Link</a>
<script>var data = "<?php echo $json; ?>";</script>
// ✅ 良い: コンテキスト適切なエスケープ
<h1><?php echo esc_html( $_GET['title'] ); ?></h1>
<input value="<?php echo esc_attr( $user_input ); ?>">
<a href="<?php echo esc_url( $url ); ?>">Link</a>
<script>var data = <?php echo wp_json_encode( $data ); ?>;</script>
// ❌ 重大: 早期エスケープ (間違ったパターン)
$title = esc_html( $_POST['title'] );
update_post_meta( $post_id, 'title', $title ); // HTMLエンティティを保存
// ✅ 良い: 遅延エスケープ (正しいパターン)
$title = sanitize_text_field( $_POST['title'] );
update_post_meta( $post_id, 'title', $title );
echo '<h1>' . esc_html( get_post_meta( $post_id, 'title', true ) ) . '</h1>';
// ✅ 良い: 信頼できるコンテンツのリッチHTMLと wp_kses_post()
echo wp_kses_post( $content ); // 安全なHTMLタグのみを許可
// ✅ 良い: wp_kses() を使用したカスタム許可タグ
$allowed_tags = array(
'a' => array( 'href' => array(), 'title' => array() ),
'br' => array(),
'strong' => array(),
);
echo wp_kses( $user_content, $allowed_tags );
SQLインジェクション防止
// ❌ 重大: 直接補間 (CWE-89)
$results = $wpdb->get_results( "SELECT * FROM {$wpdb->users} WHERE ID = $user_id" );
$results = $wpdb->query( "UPDATE {$wpdb->posts} SET post_title = '$title'" );
// ✅ 良い: プレースホルダーで $wpdb->prepare() を使用
$results = $wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->users} WHERE ID = %d",
$user_id
) );
$wpdb->query( $wpdb->prepare(
"UPDATE {$wpdb->posts} SET post_title = %s WHERE ID = %d",
$title,
$post_id
) );
// ✅ 良い: 複数のプレースホルダー
$wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_author = %d",
$status,
$author_id
) );
// ❌ 警告: esc_like() なしのLIKE
$wpdb->prepare( "SELECT * FROM {$wpdb->posts} WHERE post_title LIKE '%%{$term}%%'" );
// ✅ 良い: LIKEクエリに esc_like() を使用
$like_term = '%' . $wpdb->esc_like( $term ) . '%';
$wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_title LIKE %s",
$like_term
) );
// ✅ OK: ユーザー入力なしのハードコードSQL (管理者コンテキスト)
if ( current_user_can( 'manage_options' ) ) {
$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name = 'temp_setting'" );
}
CSRF/Nonce検証 (3段階パターン)
// ❌ 重大: Nonce検証欠落 (CWE-352)
add_action( 'admin_post_save_settings', function() {
update_option( 'my_setting', $_POST['value'] );
} );
// ❌ 重大: 権限チェック欠落 (CWE-862)
add_action( 'admin_post_save_settings', function() {
check_admin_referer( 'save_settings_action', 'settings_nonce' );
update_option( 'my_setting', $_POST['value'] );
} );
// ✅ 良い: 完全な3段階パターン
add_action( 'admin_post_save_settings', function() {
// ステップ1: Nonceを検証
if ( ! isset( $_POST['settings_nonce'] ) ||
! wp_verify_nonce( $_POST['settings_nonce'], 'save_settings_action' ) ) {
wp_die( 'Invalid nonce' );
}
// ステップ2: 権限をチェック
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( 'Insufficient permissions' );
}
// ステップ3: 入力をサニタイズ
$value = sanitize_text_field( $_POST['value'] );
update_option( 'my_setting', $value );
wp_redirect( admin_url( 'admin.php?page=settings&updated=true' ) );
exit;
} );
// ✅ 良い: Nonceフィールド付きフォーム
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<?php wp_nonce_field( 'save_settings_action', 'settings_nonce' ); ?>
<input type="hidden" name="action" value="save_settings">
<input type="text" name="value" value="<?php echo esc_attr( $current_value ); ?>">
<?php submit_button(); ?>
</form>
// ✅ 良い: Nonceを伴うAJAX
add_action( 'wp_ajax_update_user_meta', function() {
check_ajax_referer( 'update_meta_nonce', 'nonce' );
if ( ! current_user_can( 'edit_users' ) ) {
wp_send_json_error( 'Insufficient permissions' );
}
$user_id = absint( $_POST['user_id'] );
$value = sanitize_text_field( $_POST['value'] );
update_user_meta( $user_id, 'custom_field', $value );
wp_send_json_success();
} );
// NonceのJavaScript
jQuery.post( ajaxurl, {
action: 'update_user_meta',
nonce: myAjax.nonce, // wp_localize_script() から
user_id: 123,
value: 'new value'
} );
認可/権限チェック
// ❌ 重大: 権限チェックなし (CWE-862)
add_action( 'admin_post_delete_user', function() {
wp_delete_user( $_POST['user_id'] );
} );
// ❌ 警告: 不正な権限 (CWE-863)
add_action( 'admin_post_delete_user', function() {
if ( ! current_user_can( 'edit_posts' ) ) { // 間違った権限!
wp_die( 'Insufficient permissions' );
}
wp_delete_user( $_POST['user_id'] );
} );
// ✅ 良い: 正しい権限チェック
add_action( 'admin_post_delete_user', function() {
if ( ! current_user_can( 'delete_users' ) ) {
wp_die( 'Insufficient permissions' );
}
check_admin_referer( 'delete_user_action', 'delete_nonce' );
wp_delete_user( absint( $_POST['user_id'] ) );
} );
// ✅ 良い: 特定の投稿を編集できるかをチェック
if ( ! current_user_can( 'edit_post', $post_id ) ) {
wp_die( 'You cannot edit this post' );
}
// ✅ 良い: REST APIパーミッションコールバック
register_rest_route( 'myapp/v1', '/users/(?P<id>\d+)', array(
'methods' => 'DELETE',
'callback' => 'myapp_delete_user',
'permission_callback' => function( $request ) {
return current_user_can( 'delete_users' );
},
) );
// ✅ OK: 公開読み取りエンドポイントでの __return_true
register_rest_route( 'myapp/v1', '/posts', array(
'methods' => 'GET',
'callback' => 'myapp_get_posts',
'permission_callback' => '__return_true', // 公開読み取りはOK
) );
ファイルアップロードセキュリティ
// ❌ 重大: 直接 move_uploaded_file() (CWE-434)
if ( isset( $_FILES['upload'] ) ) {
move_uploaded_file( $_FILES['upload']['tmp_name'], '/uploads/' . $_FILES['upload']['name'] );
}
// ❌ 重大: 権限チェックなし (CWE-862)
$file = wp_handle_upload( $_FILES['upload'], array( 'test_form' => false ) );
// ✅ 良い: 完全なファイルアップロードセキュリティ
add_action( 'admin_post_upload_file', function() {
// ステップ1: Nonceを検証
check_admin_referer( 'upload_file_action', 'upload_nonce' );
// ステップ2: 権限をチェック
if ( ! current_user_can( 'upload_files' ) ) {
wp_die( 'Insufficient permissions' );
}
// ステップ3: ファイルが存在することを検証
if ( ! isset( $_FILES['upload'] ) || UPLOAD_ERR_OK !== $_FILES['upload']['error'] ) {
wp_die( 'File upload failed' );
}
// ステップ4: wp_handle_upload() を使用 (MIMEタイプ、拡張子を検証)
require_once( ABSPATH . 'wp-admin/includes/file.php' );
$file = wp_handle_upload( $_FILES['upload'], array( 'test_form' => false ) );
if ( isset( $file['error'] ) ) {
wp_die( 'Upload error: ' . esc_html( $file['error'] ) );
}
// ステップ5: ファイル情報を保存
$attachment_id = wp_insert_attachment( array(
'post_title' => sanitize_file_name( $_FILES['upload']['name'] ),
'post_mime_type' => $file['type'],
'post_status' => 'inherit',
), $file['file'] );
} );
// ✅ 良い: 許可されたMIMEタイプを制限
add_filter( 'upload_mimes', function( $mimes ) {
// 危険な可能性があるタイプを削除
unset( $mimes['exe'] );
unset( $mimes['php'] );
// 必要に応じてカスタム許可タイプを追加
$mimes['svg'] = 'image/svg+xml';
return $mimes;
} );
// ✅ 良い: ファイルタイプ検証
$filetype = wp_check_filetype_and_ext( $_FILES['upload']['tmp_name'], $_FILES['upload']['name'] );
if ( ! $filetype['ext'] ) {
wp_die( 'Invalid file type' );
}
オブジェクトインジェクション防止
// ❌ 重大: ユーザー入力のunserialize (CWE-502)
$data = unserialize( $_POST['data'] );
// ❌ 重大: クッキーからのunserialize
$data = unserialize( $_COOKIE['cart_data'] );
// ✅ 良い: ユーザー向けデータには、serializeの代わりにJSONを使用
$data = json_decode( $_POST['data'], true );
if ( JSON_ERROR_NONE !== json_last_error() ) {
wp_die( 'Invalid JSON' );
}
// ✅ OK: 信頼できる内部データのみのunserialize
$data = get_option(
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- jorgerosal
- ライセンス
- MIT
- 最終更新
- 2026/4/17
Source: https://github.com/jorgerosal/wordpress-skills / ライセンス: MIT