入力サニタイズ
ユーザー入力を決して信頼しないでください。すべての入力データを使用する前に常に検証およびサニタイズしてください。XOOPSはテキスト入力をサニタイズするためのMyTextSanitizerクラスと、検証用のさまざまなヘルパー関数を提供します。
関連ドキュメント
Section titled “関連ドキュメント”- セキュリティベストプラクティス - 包括的なセキュリティガイド
- CSRF保護 - トークンシステムとXoopsSecurityクラス
- SQLインジェクション防止 - データベースセキュリティプラクティス
ユーザー入力を決して信頼しないでください。 外部ソースからのすべてのデータは:
- 検証: 期待される形式と型に一致することを確認
- サニタイズ: 潜在的に危険な文字を削除またはエスケープ
- エスケープ: 出力時に特定のコンテキスト(HTML、JavaScript、SQL)用にエスケープ
MyTextSanitizerクラス
Section titled “MyTextSanitizerクラス”XOOPSはテキストサニタイズ用にMyTextSanitizerクラス(一般に$mytsとして参照)を提供します。
インスタンスの取得
Section titled “インスタンスの取得”// シングルトンインスタンスを取得$myts = MyTextSanitizer::getInstance();基本的なテキストサニタイズ
Section titled “基本的なテキストサニタイズ”$myts = MyTextSanitizer::getInstance();
// プレーンテキストフィールド(HTMLは許可されない)$title = $myts->htmlSpecialChars($_POST['title']);
// これは以下に変換:// < to <// > to >// & to &// " to "// ' to 'テキストエリアコンテンツ処理
Section titled “テキストエリアコンテンツ処理”displayTarea()メソッドは包括的なテキストエリア処理を提供します:
$myts = MyTextSanitizer::getInstance();
$content = $myts->displayTarea( $_POST['content'], $allowhtml = 0, // 0 = HTMLは許可されない、1 = HTMLを許可 $allowsmiley = 1, // 1 = スマイリーを有効化 $allowxcode = 1, // 1 = XOOPSコード(BBCode)を有効化 $allowimages = 1, // 1 = 画像を許可 $allowlinebreak = 1 // 1 = 改行を<br>に変換);一般的なサニタイズメソッド
Section titled “一般的なサニタイズメソッド”$myts = MyTextSanitizer::getInstance();
// HTML特殊文字エスケープ$safe_text = $myts->htmlSpecialChars($text);
// マジッククォートが有効な場合はスラッシュを削除$text = $myts->stripSlashesGPC($text);
// XOOPSコード(BBCode)をHTMLに変換$html = $myts->xoopsCodeDecode($text);
// スマイリーを画像に変換$html = $myts->smiley($text);
// クリック可能なリンクを作成$html = $myts->makeClickable($text);
// プレビュー用の完全なテキスト処理$preview = $myts->previewTarea($text, $allowhtml, $allowsmiley, $allowxcode);整数値の検証
Section titled “整数値の検証”// 整数IDを検証$id = isset($_REQUEST['id']) ? (int)$_REQUEST['id'] : 0;
if ($id <= 0) { redirect_header('index.php', 3, '無効なID'); exit();}
// filter_varを使用した別の方法$id = filter_var($_REQUEST['id'] ?? 0, FILTER_VALIDATE_INT);if ($id === false || $id <= 0) { redirect_header('index.php', 3, '無効なID'); exit();}メールアドレスの検証
Section titled “メールアドレスの検証”$email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);
if (!$email) { redirect_header('form.php', 3, '無効なメールアドレス'); exit();}URLの検証
Section titled “URLの検証”$url = filter_var($_POST['url'], FILTER_VALIDATE_URL);
if (!$url) { redirect_header('form.php', 3, '無効なURL'); exit();}
// 許可されるプロトコルを追加チェック$parsed = parse_url($url);$allowed_schemes = ['http', 'https'];if (!in_array($parsed['scheme'], $allowed_schemes)) { redirect_header('form.php', 3, 'HTTPおよびHTTPSのURLのみを許可します'); exit();}$date = $_POST['date'] ?? '';
// 日付形式を検証 (YYYY-MM-DD)if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) { redirect_header('form.php', 3, '無効な日付形式'); exit();}
// 実際の日付妥当性を検証$parts = explode('-', $date);if (!checkdate($parts[1], $parts[2], $parts[0])) { redirect_header('form.php', 3, '無効な日付'); exit();}ファイル名の検証
Section titled “ファイル名の検証”// 英数字、アンダースコア、ハイフンを除くすべての文字を削除$filename = preg_replace('/[^a-zA-Z0-9_-]/', '', $_POST['filename']);
// またはホワイトリスト方法を使用$allowed_chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-';$filename = '';foreach (str_split($_POST['filename']) as $char) { if (strpos($allowed_chars, $char) !== false) { $filename .= $char; }}異なる入力タイプの処理
Section titled “異なる入力タイプの処理”$myts = MyTextSanitizer::getInstance();
// 短いテキスト(タイトル、名前)$title = $myts->htmlSpecialChars(trim($_POST['title']));
// 長さを制限if (strlen($title) > 255) { $title = substr($title, 0, 255);}
// 空の必須フィールドを確認if (empty($title)) { redirect_header('form.php', 3, 'タイトルが必要です'); exit();}// 整数$count = (int)$_POST['count'];$count = max(0, min($count, 1000)); // 0-1000の範囲を確認
// 浮動小数点$price = (float)$_POST['price'];$price = round($price, 2); // 小数第2位に丸め
// 範囲を検証if ($price < 0 || $price > 99999.99) { redirect_header('form.php', 3, '無効な価格'); exit();}// チェックボックス値$is_active = isset($_POST['is_active']) ? 1 : 0;
// または明示的な値チェック$is_active = ($_POST['is_active'] ?? '') === '1' ? 1 : 0;// 配列入力を検証(例:複数チェックボックス)$selected_ids = [];if (isset($_POST['ids']) && is_array($_POST['ids'])) { foreach ($_POST['ids'] as $id) { $clean_id = (int)$id; if ($clean_id > 0) { $selected_ids[] = $clean_id; } }}選択/オプション入力
Section titled “選択/オプション入力”// 許可された値に対して検証$allowed_statuses = ['draft', 'published', 'archived'];$status = $_POST['status'] ?? '';
if (!in_array($status, $allowed_statuses)) { redirect_header('form.php', 3, '無効なステータス'); exit();}リクエストオブジェクト (XMF)
Section titled “リクエストオブジェクト (XMF)”XMFを使用する場合、Requestクラスはより清潔な入力処理を提供します:
use Xmf\Request;
// 整数を取得$id = Request::getInt('id', 0);
// 文字列を取得$title = Request::getString('title', '');
// 配列を取得$ids = Request::getArray('ids', []);
// メソッド指定で取得$id = Request::getInt('id', 0, 'POST');$search = Request::getString('q', '', 'GET');
// リクエストメソッドを確認if (Request::getMethod() !== 'POST') { redirect_header('form.php', 3, '無効なリクエストメソッド'); exit();}検証クラスの作成
Section titled “検証クラスの作成”複雑なフォームの場合は、専用の検証クラスを作成してください:
<?phpnamespace XoopsModules\MyModule;
class Validator{ private $errors = [];
public function validateItem(array $data): bool { $this->errors = [];
// タイトル検証 if (empty($data['title'])) { $this->errors['title'] = 'タイトルが必要です'; } elseif (strlen($data['title']) > 255) { $this->errors['title'] = 'タイトルは255文字以内である必要があります'; }
// メール検証 if (!empty($data['email'])) { if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) { $this->errors['email'] = '無効なメール形式'; } }
// ステータス検証 $allowed = ['draft', 'published']; if (!in_array($data['status'], $allowed)) { $this->errors['status'] = '無効なステータス'; }
return empty($this->errors); }
public function getErrors(): array { return $this->errors; }
public function getError(string $field): ?string { return $this->errors[$field] ?? null; }}使用方法:
$validator = new Validator();$data = [ 'title' => $_POST['title'], 'email' => $_POST['email'], 'status' => $_POST['status'],];
if (!$validator->validateItem($data)) { $errors = $validator->getErrors(); // ユーザーにエラーを表示}データベース保存用のサニタイズ
Section titled “データベース保存用のサニタイズ”データをデータベースに保存する場合:
$myts = MyTextSanitizer::getInstance();
// 保存用 (表示時に再度処理される)$title = $myts->addSlashes($_POST['title']);
// より良い方法: パラメータ化されたステートメントを使用(SQL インジェクション防止を参照)$sql = "INSERT INTO " . $xoopsDB->prefix('mytable') . " (title) VALUES (?)";$result = $xoopsDB->query($sql, [$_POST['title']]);表示用のサニタイズ
Section titled “表示用のサニタイズ”異なるコンテキストには異なるエスケープが必要です:
$myts = MyTextSanitizer::getInstance();
// HTMLコンテキストecho $myts->htmlSpecialChars($title);
// HTML属性内echo htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
// JavaScriptコンテキストecho json_encode($title);
// URLパラメータecho urlencode($title);
// 完全なURLecho htmlspecialchars($url, ENT_QUOTES, 'UTF-8');一般的な落とし穴
Section titled “一般的な落とし穴”二重エンコード
Section titled “二重エンコード”問題: データが複数回エンコードされる
// 悪い - 二重エンコード$title = $myts->htmlSpecialChars($myts->htmlSpecialChars($_POST['title']));
// 良い - 一度だけエンコード、適切なタイミングで$title = $_POST['title']; // 生のデータを保存echo $myts->htmlSpecialChars($title); // 出力時にエンコード不一貫なエンコード
Section titled “不一貫なエンコード”問題: 一部の出力はエンコードされ、一部はされていない
解決方法: 常に一貫した方法を使用し、最初は出力時のエンコードが望ましい:
// テンプレート割り当て$GLOBALS['xoopsTpl']->assign('title', htmlspecialchars($title, ENT_QUOTES, 'UTF-8'));検証なしでサニタイズ
Section titled “検証なしでサニタイズ”問題: 検証なしでサニタイズするだけ
解決方法: 常に最初に検証し、次にサニタイズしてください:
// 最初に検証if (!preg_match('/^[a-z0-9_]+$/', $_POST['username'])) { redirect_header('form.php', 3, 'ユーザー名に無効な文字が含まれています'); exit();}
// 次にサニタイズして保存/表示$username = $myts->htmlSpecialChars($_POST['username']);ベストプラクティス要約
Section titled “ベストプラクティス要約”- MyTextSanitizerを使用 テキストコンテンツ処理用
- filter_var()を使用 特定の形式検証用
- 型キャストを使用 数値の場合
- ホワイトリスト許可値 選択入力用
- 検証してからサニタイズ
- 出力時にエスケープ、入力時ではなく
- パラメータ化されたステートメントを使用 データベースクエリ用
- 複雑なフォーム用の検証クラスを作成
- クライアント側検証を決して信頼しない - 常にサーバー側で検証
- 入力 - サーバー側でこれを実施
#セキュリティ #サニタイズ #検証 #xoops #MyTextSanitizer #入力処理