特定の種類の HTML タグを許可しながら、SQL インジェクションや XSS 攻撃のユーザー入力をサニタイズするのにうまく機能するキャッチオール関数はどこかにありますか?
18 に答える
ユーザー入力をフィルタリングできるというのはよくある誤解です。PHP には、この考えに基づいて構築された、magic-quotesと呼ばれる (現在は廃止された) 「機能」さえあります。それはナンセンスです。フィルタリング(またはクリーニング、または人々がそれを何と呼ぶか)を忘れてください。
問題を回避するためにすべきことは非常に単純です。外部コード内にデータを埋め込むときはいつでも、そのコードのフォーマット規則に従ってそれを処理する必要があります。ただし、そのようなルールは複雑すぎてすべてを手動で実行しようとすることはできないことを理解しておく必要があります。たとえば、SQL では、文字列、数値、および識別子のルールがすべて異なります。便宜上、ほとんどの場合、そのような埋め込み専用のツールがあります。たとえば、SQL クエリで PHP 変数を使用する必要がある場合は、すべての適切な書式設定/処理を処理する準備済みステートメントを使用する必要があります。
もう 1 つの例は HTML です。HTML マークアップ内に文字列を埋め込む場合は、 でエスケープする必要がありますhtmlspecialchars
。これは、すべてのecho
orprint
ステートメントで を使用する必要があることを意味しますhtmlspecialchars
。
3 番目の例はシェル コマンドです。文字列 (引数など) を外部コマンドに埋め込み、 でそれらを呼び出す場合はexec
、 と を使用する必要がありescapeshellcmd
ますescapeshellarg
。
また、非常に説得力のある例は JSON です。ルールは非常に多く、複雑であるため、手動ですべてに従うことはできません。json_encode()
そのため、JSON 文字列を手動で作成してはならず、データのすべてのビットを正しくフォーマットする専用の関数を常に使用してください。
などなど...
データを積極的にフィルタリングする必要がある唯一のケースは、事前にフォーマットされた入力を受け入れる場合です。たとえば、ユーザーが HTML マークアップを投稿できるようにする場合、それをサイトに表示する予定です。ただし、これはどんな犠牲を払っても避けるのが賢明です。どんなにうまくフィルタしても、常に潜在的なセキュリティ ホールになるからです。
入力データをサニタイズして SQL インジェクションを防ごうとしないでください。
代わりに、SQL コードの作成にデータを使用することを許可しないでください。バインドされた変数を使用するプリペアド ステートメントを使用します (つまり、テンプレート クエリでパラメーターを使用します)。これは、SQL インジェクションに対して保証される唯一の方法です。
SQL インジェクションの防止について詳しくは、私の Web サイトhttp://bobby-tables.com/を参照してください。
いいえ。目的のコンテキストがなければ、一般的にデータをフィルター処理することはできません。SQL クエリを入力として取りたい場合もあれば、HTML を入力として取りたい場合もあります。
ホワイトリストで入力をフィルタリングする必要があります-データが期待する仕様と一致することを確認してください。次に、使用するコンテキストに応じて、使用する前にエスケープする必要があります。
SQL インジェクションを防ぐために SQL のデータをエスケープするプロセスは、XSS を防ぐために (X)HTML のデータをエスケープするプロセスとは大きく異なります。
PHP には新しい便利な関数があります。たとえば、組み込みの型filter_input
があるため、「究極の電子メール正規表現」を見つけることから解放されます。FILTER_VALIDATE_EMAIL
私自身のフィルター クラス (JavaScript を使用してエラーのあるフィールドを強調表示する) は、ajax リクエストまたは通常のフォーム ポストのいずれかによって開始できます。(以下の例を参照) <? /** * ポーク フォームバリデータ。正規表現によってフィールドを検証し、サニタイズできます。PHP の filter_var 組み込み関数と追加の正規表現を使用 * @package pig */
/**
* Pork.FormValidator
* Validates arrays or properties by setting up simple arrays.
* Note that some of the regexes are for dutch input!
* Example:
*
* $validations = array('name' => 'anything','email' => 'email','alias' => 'anything','pwd'=>'anything','gsm' => 'phone','birthdate' => 'date');
* $required = array('name', 'email', 'alias', 'pwd');
* $sanitize = array('alias');
*
* $validator = new FormValidator($validations, $required, $sanitize);
*
* if($validator->validate($_POST))
* {
* $_POST = $validator->sanitize($_POST);
* // now do your saving, $_POST has been sanitized.
* die($validator->getScript()."<script type='text/javascript'>alert('saved changes');</script>");
* }
* else
* {
* die($validator->getScript());
* }
*
* To validate just one element:
* $validated = new FormValidator()->validate('blah@bla.', 'email');
*
* To sanitize just one element:
* $sanitized = new FormValidator()->sanitize('<b>blah</b>', 'string');
*
* @package pork
* @author SchizoDuckie
* @copyright SchizoDuckie 2008
* @version 1.0
* @access public
*/
class FormValidator
{
public static $regexes = Array(
'date' => "^[0-9]{1,2}[-/][0-9]{1,2}[-/][0-9]{4}\$",
'amount' => "^[-]?[0-9]+\$",
'number' => "^[-]?[0-9,]+\$",
'alfanum' => "^[0-9a-zA-Z ,.-_\\s\?\!]+\$",
'not_empty' => "[a-z0-9A-Z]+",
'words' => "^[A-Za-z]+[A-Za-z \\s]*\$",
'phone' => "^[0-9]{10,11}\$",
'zipcode' => "^[1-9][0-9]{3}[a-zA-Z]{2}\$",
'plate' => "^([0-9a-zA-Z]{2}[-]){2}[0-9a-zA-Z]{2}\$",
'price' => "^[0-9.,]*(([.,][-])|([.,][0-9]{2}))?\$",
'2digitopt' => "^\d+(\,\d{2})?\$",
'2digitforce' => "^\d+\,\d\d\$",
'anything' => "^[\d\D]{1,}\$"
);
private $validations, $sanatations, $mandatories, $errors, $corrects, $fields;
public function __construct($validations=array(), $mandatories = array(), $sanatations = array())
{
$this->validations = $validations;
$this->sanitations = $sanitations;
$this->mandatories = $mandatories;
$this->errors = array();
$this->corrects = array();
}
/**
* Validates an array of items (if needed) and returns true or false
*
*/
public function validate($items)
{
$this->fields = $items;
$havefailures = false;
foreach($items as $key=>$val)
{
if((strlen($val) == 0 || array_search($key, $this->validations) === false) && array_search($key, $this->mandatories) === false)
{
$this->corrects[] = $key;
continue;
}
$result = self::validateItem($val, $this->validations[$key]);
if($result === false) {
$havefailures = true;
$this->addError($key, $this->validations[$key]);
}
else
{
$this->corrects[] = $key;
}
}
return(!$havefailures);
}
/**
*
* Adds unvalidated class to thos elements that are not validated. Removes them from classes that are.
*/
public function getScript() {
if(!empty($this->errors))
{
$errors = array();
foreach($this->errors as $key=>$val) { $errors[] = "'INPUT[name={$key}]'"; }
$output = '$$('.implode(',', $errors).').addClass("unvalidated");';
$output .= "new FormValidator().showMessage();";
}
if(!empty($this->corrects))
{
$corrects = array();
foreach($this->corrects as $key) { $corrects[] = "'INPUT[name={$key}]'"; }
$output .= '$$('.implode(',', $corrects).').removeClass("unvalidated");';
}
$output = "<script type='text/javascript'>{$output} </script>";
return($output);
}
/**
*
* Sanitizes an array of items according to the $this->sanitations
* sanitations will be standard of type string, but can also be specified.
* For ease of use, this syntax is accepted:
* $sanitations = array('fieldname', 'otherfieldname'=>'float');
*/
public function sanitize($items)
{
foreach($items as $key=>$val)
{
if(array_search($key, $this->sanitations) === false && !array_key_exists($key, $this->sanitations)) continue;
$items[$key] = self::sanitizeItem($val, $this->validations[$key]);
}
return($items);
}
/**
*
* Adds an error to the errors array.
*/
private function addError($field, $type='string')
{
$this->errors[$field] = $type;
}
/**
*
* Sanitize a single var according to $type.
* Allows for static calling to allow simple sanitization
*/
public static function sanitizeItem($var, $type)
{
$flags = NULL;
switch($type)
{
case 'url':
$filter = FILTER_SANITIZE_URL;
break;
case 'int':
$filter = FILTER_SANITIZE_NUMBER_INT;
break;
case 'float':
$filter = FILTER_SANITIZE_NUMBER_FLOAT;
$flags = FILTER_FLAG_ALLOW_FRACTION | FILTER_FLAG_ALLOW_THOUSAND;
break;
case 'email':
$var = substr($var, 0, 254);
$filter = FILTER_SANITIZE_EMAIL;
break;
case 'string':
default:
$filter = FILTER_SANITIZE_STRING;
$flags = FILTER_FLAG_NO_ENCODE_QUOTES;
break;
}
$output = filter_var($var, $filter, $flags);
return($output);
}
/**
*
* Validates a single var according to $type.
* Allows for static calling to allow simple validation.
*
*/
public static function validateItem($var, $type)
{
if(array_key_exists($type, self::$regexes))
{
$returnval = filter_var($var, FILTER_VALIDATE_REGEXP, array("options"=> array("regexp"=>'!'.self::$regexes[$type].'!i'))) !== false;
return($returnval);
}
$filter = false;
switch($type)
{
case 'email':
$var = substr($var, 0, 254);
$filter = FILTER_VALIDATE_EMAIL;
break;
case 'int':
$filter = FILTER_VALIDATE_INT;
break;
case 'boolean':
$filter = FILTER_VALIDATE_BOOLEAN;
break;
case 'ip':
$filter = FILTER_VALIDATE_IP;
break;
case 'url':
$filter = FILTER_VALIDATE_URL;
break;
}
return ($filter === false) ? false : filter_var($var, $filter) !== false ? true : false;
}
}
もちろん、使用しているデータベースのタイプに応じて、SQL クエリのエスケープも行う必要があることに注意してください (たとえば、SQL サーバーでは mysql_real_escape_string() は役に立ちません)。おそらく、ORM のような適切なアプリケーション層でこれを自動的に処理したいと思うでしょう。また、上記のように: html に出力するには、htmlspecialchars などの他の PHP 専用関数を使用します;)
削除されたクラスやタグを使用した HTML 入力を実際に許可するには、専用の xss 検証パッケージのいずれかに依存します。HTML を解析するために独自の正規表現を記述しないでください。
いいえ、ありません。
まず第一に、SQL インジェクションは入力フィルタリングの問題であり、XSS は出力をエスケープする問題です。したがって、コード ライフサイクルでこれら 2 つの操作を同時に実行することさえありません。
基本的な経験則
- SQL クエリの場合、パラメーターをバインドする (PDO と同様) か、クエリ変数に対してドライバー ネイティブのエスケープ関数を使用する (など
mysql_real_escape_string()
) strip_tags()
不要な HTML を除外するために使用します- 他のすべての出力をエスケープし
htmlspecialchars()
、ここで 2 番目と 3 番目のパラメーターに注意してください。
XSS の問題に対処するには、HTML Purifierをご覧ください。それはかなり設定可能であり、まともな実績があります.
SQL インジェクション攻撃に関しては、必ずユーザー入力を確認してから、mysql_real_escape_string() で実行してください。ただし、関数はすべてのインジェクション攻撃を打ち負かすわけではないため、データをクエリ文字列にダンプする前に確認することが重要です。
より良い解決策は、準備済みステートメントを使用することです。PDO ライブラリと mysqli 拡張機能はこれらをサポートしています。
PHP 5.2 でこのfilter_var
関数が導入されました。
SANITIZE
多数の,VALIDATE
フィルタをサポートしています。
次のようなページがあり、WHERE 句で id を使用する特定の状況で役立つトリックの 1 つ/mypage?id=53
は、次のように、id が確実に整数であることを確認することです。
if (isset($_GET['id'])) {
$id = $_GET['id'];
settype($id, 'integer');
$result = mysql_query("SELECT * FROM mytable WHERE id = '$id'");
# now use the result
}
もちろん、これは特定の攻撃を 1 つだけ除外するので、他のすべての回答を読んでください。(そして、上記のコードが優れていないことはわかっていますが、特定の防御を示しています。)
対処すべき懸念事項が複数あるため、包括的な機能はありません。
- SQL インジェクション- 現在、一般的に、すべての PHP プロジェクトはPHP データ オブジェクト (PDO) を介してプリペアド ステートメントを使用することをベスト プラクティスとして使用する必要があります。また、データベースにアクセスする最も柔軟で安全な方法でもあります。
PDOについて知る必要があるほとんどすべてについては、(唯一の適切な) PDO チュートリアルを参照してください。(この主題に関するこの素晴らしいリソースについて、SO のトップ貢献者である @YourCommonSense に心から感謝します。)
- XSS - 途中でデータをサニタイズします...
HTML Purifierは長い間存在しており、現在も活発に更新されています。これを使用して、悪意のある入力をサニタイズしながら、寛大で構成可能なタグのホワイトリストを許可できます. 多くの WYSIWYG エディターでうまく機能しますが、一部のユース ケースでは重い場合があります。
HTML/Javascript をまったく受け入れたくない他の例では、この単純な関数が便利であることがわかりました (そして、XSS に対する複数の監査に合格しました)。
/* Prevent XSS input */ function sanitizeXSS () { $_GET = filter_input_array(INPUT_GET, FILTER_SANITIZE_STRING); $_POST = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING); $_REQUEST = (array)$_POST + (array)$_GET + (array)$_REQUEST; }
- XSS - 途中でデータをサニタイズします...データベースに追加する前にデータが適切にサニタイズされていることを保証しない限り、ユーザーに表示する前にサニタイズする必要があります。これらの便利な PHP 関数を利用できます。
- ユーザー指定の値を呼び出し
echo
たり表示したりする場合は、データが安全に適切にサニタイズされ、HTML の表示が許可されている場合を除き、使用してください。print
htmlspecialchars
json_encode
ユーザーが提供する値を PHP から Javascript に提供する安全な方法です。
exec()
またはsystem()
関数を使用して外部シェル コマンドを呼び出しますか、それともbacktick
オペレーターに対して呼び出しますか? その場合、SQL インジェクションと XSS に加えて、ユーザーがサーバーで悪意のあるコマンドを実行しているという問題に対処する必要があるかもしれません。escapeshellcmd
コマンド全体をエスケープする場合、またはescapeshellarg
個々の引数をエスケープする場合に使用する必要があります。
ここで説明しているのは、2 つの別々の問題です。
- ユーザー入力データのサニタイズ/フィルタリング。
- エスケープ出力。
1) ユーザー入力は常に悪いと想定する必要があります。
準備済みステートメントの使用、または mysql_real_escape_string によるフィルタリングは、間違いなく必須です。PHP には filter_input も組み込まれており、これは開始するのに適しています。
2) これは大きなトピックであり、出力されるデータのコンテキストに依存します。HTML には、htmlpurifier などのソリューションがあります。経験則として、出力したものは常にエスケープしてください。
両方の問題は大きすぎて 1 回の投稿では説明できませんが、より詳細な投稿が多数あります。
PostgreSQL を使用している場合、PHP からの入力は次のようにエスケープできます。pg_escape_literal()
$username = pg_escape_literal($_POST['username']);
ドキュメントから:
pg_escape_literal()
PostgreSQL データベースに問い合わせるためのリテラルをエスケープします。エスケープされたリテラルを PostgreSQL 形式で返します。
出力エスケープの件名にそれを追加したかっただけです。php DOMDocument を使用して HTML 出力を作成すると、適切なコンテキストで自動的にエスケープされます。属性 (value="") と <span> の内部テキストが等しくありません。XSS に対して安全であるために、これを読んでください: OWASP XSS 防止チート シート
フィルター拡張 ( howto-link、manual ) があり、すべての GPC 変数でうまく機能します。魔法のように何でもできるわけではありませんが、それでも使用する必要があります。