/**
* セキュリティカテゴリ
*/
クラス CI_Security {
//URL
のランダムなハッシュ値
protected $_xss_hash = '';
//CSRF攻撃を防ぐためのCookieタグのハッシュ値
protected $_csrf_hash = '';
//アンチCSRF Cookieの有効期限
保護された $_csrf_expire = 7200;
//anticsrf 用の Cookie 名
protected $_csrf_token_name = 'ci_csrf_token';
//CSRFを防ぐためのトークン名
protected $_csrf_cookie_name = 'ci_csrf_token';
//許可されていない文字列の配列
protected $_never_allowed_str = array(
'document.cookie' => '[削除]',
'document.write' => '[削除]',
'.parentNode' => '[削除]',
'.innerHTML' => '[削除]',
'window.location' => '[削除]',
'-moz-binding' => '[削除]',
'」 =>「」、
' ';
'<コメント>' =>'
);
//許可されていない正規表現の配列
protected $_never_allowed_regex = array(
'javascripts*:',
'expressions*((|()', // CSS と IE
'vbscripts*:', // IE、驚きです!
「リダイレクト+302」、
"(["'])?datas*:[^\1]*?base64[^\1]*?,[^\1]*?\1?"
);
//コンストラクター
パブリック関数 __construct()
{
// CSRF 保護が有効かどうか
if (config_item('csrf_protection') === TRUE)
{
// CSRF設定
foreach (array('csrf_expire', 'csrf_token_name', 'csrf_cookie_name') as $key)
{
If (FALSE !== ($val = config_item($key)))
{
$this->{'_'.$key} = $val;
}
}
// _csrf_cookie_name と Cookie プレフィックス
If (config_item('cookie_prefix'))
{
$this->_csrf_cookie_name = config_item('cookie_prefix').$this->_csrf_cookie_name;
}
//csrfのハッシュ値を設定
$this->_csrf_set_hash();
}
log_message('debug', "セキュリティ クラスが初期化されました");
}
//------------------------------------------------ ------------------
/**
* クロスサイトリクエストフォージェリプロテクションを検証します
*
* @return オブジェクト
*/
パブリック関数 csrf_verify()
{
// 投稿リクエストでない場合は、csrf
のCookie値を設定します
if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST')
{
$this->csrf_set_cookie();
を返す
}
// トークンは _POST 配列と _COOKIE 配列の両方に存在しますか?
if ( ! isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name]))
{
$this->csrf_show_error();
}
// トークンは一致しますか?
if ($_POST[$this->_csrf_token_name] != $_COOKIE[$this->_csrf_cookie_name])
{
$this->csrf_show_error();
}
// 終わったので強制終了します
// _POST 配列を汚染します
unset($_POST[$this->_csrf_token_name]);
// 永遠に続くものはないはずです
unset($_COOKIE[$this->_csrf_cookie_name]);
$this->_csrf_set_hash();
$this->csrf_set_cookie();
log_message('デバッグ', 'CSRF トークンが検証されました');
$this を返します;
}
//------------------------------------------------ ------------------
/**
* csrfのCookie値を設定
*/
パブリック関数 csrf_set_cookie()
{
$expire = time() + $this->_csrf_expire;
$secure_cookie = (config_item('cookie_secure') === TRUE) ? 1:0;
if ($secure_cookie && (empty($_SERVER['HTTPS']) OR strto lower($_SERVER['HTTPS']) === 'off'))
{
FALSE を返します;
}
setcookie($this->_csrf_cookie_name, $this->_csrf_hash, $expire, config_item('cookie_path'), config_item('cookie_domain'), $secure_cookie);
log_message('デバッグ', "CRSF Cookie セット");
$this を返します;
}
//csrf保存
パブリック関数 csrf_show_error()
{
show_error('要求したアクションは許可されていません。');
}
//csrf のハッシュを取得
パブリック関数 get_csrf_hash()
{
$this->_csrf_hash を返す;
}
//csrf のトークンを取得
パブリック関数 get_csrf_token_name()
{
return $this->_csrf_token_name;
}
/**
* XSSフィルター
*/
パブリック関数 xss_clean($str, $is_image = FALSE)
{
//否かは数组
if (is_array($str))
{
while (list($key) = each($str))
{
$str[$key] = $this->xss_clean($str[$key]);
}
$str;
を返します
}
//去掉可见字符串
$str = Remove_invisible_characters($str);
// 验证实体url
$str = $this->_validate_entities($str);
/*
* URL解読
*
* このようなものが送信された場合に備えて:
*
*
Google
*
* 注: プラス記号が削除されないように rawurldecode() を使用してください
*
*/
$str = rawurldecode($str);
/*
* 文字エンティティを ASCII に変換します
*
* これにより、以下のテストが確実に動作するようになります。
* タグ内のエンティティのみを変換します
* これらはセキュリティ上の問題を引き起こすものです。
*
*/
$str = preg_replace_callback("/[a-z]+=(['"]).*?\1/si", array($this, '_convert_attribute'), $str);
$str = preg_replace_callback("/
|<|$)/si", array($this, '_decode_entity'), $str);
/*
* 見えない文字をもう一度削除してください!
*/
$str = Remove_invisible_characters($str);
/*
* すべてのタブをスペースに変換します
*
* これにより、次のような文字列が防止されます: ja vascript
* 注: 文字間のスペースについては後で扱います。
* 注: ここでは preg_replace が驚くほど遅いことが判明しました
* データの大きなブロックがあるため、str_replace を使用します。
*/
if (strpos($str, "t") !== FALSE)
{
$str = str_replace("t", ' ', $str);
}
/*
* 後で比較するために変換された文字列をキャプチャします
*/
$converted_string = $str;
// 決して許可されない文字列を削除します
$str = $this->_do_never_allowed($str);
/*
* PHP タグを安全にします
*
* 注: XML タグも誤って置き換えられます:
*
*
*
※でも問題ないようです
*/
if ($is_image === TRUE)
{
// 画像は PHP のオープニングが短い傾向があり、
// 終了タグは頻繁にあるので、それらのみをスキップします
// 長い開始タグを実行します。
$str = preg_replace('/(php)/i', "\1", $str);
}
それ以外
{
$str = str_replace(array('', '?'.'>'), array('', '?>'), $str);
}
/*
* 展開された単語を圧縮します
*
* これは次のような単語を修正します: j a v a s c r i p t
* これらの単語は正しい状態に圧縮されます。
*/
$words = 配列(
'javascript'、'式'、'vbscript'、'スクリプト'、'base64'、
「アプレット」、「アラート」、「ドキュメント」、「書き込み」、「クッキー」、「ウィンドウ」
);
foreach ($words を $word として)
{
$temp = '';
for ($i = 0, $wordlen = strlen($word); $i
{
$temp .= substr($word, $i, 1)."s*";
}
// これを行うのは、単語以外の文字が後に続く場合のみです
// そうすれば、「dealer to」のような有効なものは「dealerto」にはなりません
$str = preg_replace_callback('#('.substr($temp, 0, -3).')(W)#is', array($this, '_compact_exploded_words'), $str);
}
/*
* リンクまたは img タグ内の禁止されている Javascript を削除します
* 以前は PHP5 のバージョン比較と Stripos の使用を行っていました
※ただし、これらの簡易非キャプチャと比べるとかなり遅いです
* preg_match()、特にパターンが文字列に存在する場合
*/
する
{
$original = $str;
if (preg_match("/
{
$str = preg_replace_callback("#]*?)(>|$)#si", array($this, '_js_link_removal'), $str);
}
if (preg_match("/
{
$str = preg_replace_callback("#]*?)(s?/?>|$)#si", array($this, '_js_img_removal'), $str);
}
if (preg_match("/script/i", $str) OR preg_match("/xss/i", $str))
{
$str = preg_replace("##si", '[削除]', $str);
}
}
while($original != $str);
unset($original);
// style、onclick、xmlns などの有害な属性を削除します
$str = $this->_remove_evil_attributes($str, $is_image);
/*
* 不正な HTML 要素をサニタイズします
*
*リスト内の単語のいずれかを含むタグの場合
* 以下が見つかると、タグがエンティティに変換されます。
*
* これは次のとおりです: <点滅>
* になります: <点滅>
*/
$naughty = 'alert|applet|audio|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|isindex|layer|link|meta|オブジェクト|プレーンテキスト|スタイル|スクリプト|テキストエリア|タイトル|ビデオ|xml|xss';
$str = preg_replace_callback('#<(/*s*)('.$naughty.')([^><]*)([><]*)#is', array($this 、'_sanitize_naughty_html')、$str);
/*
* いたずらなスクリプト要素をサニタイズ
*
* 上記と同様ですが、
を探す代わりにのみ
* PHP および JavaScript コマンドを検索するタグ
* 禁止されているもの。
を削除するのではなく
* コードでは、括弧をエンティティに変換するだけです
* コードを実行不能にします。
*
* 例: eval('some code')
* になります: eval('some code')
*/
$str = preg_replace('#(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(s*)((.*?))#si', " \1\2(\3)", $str);
// 最終クリーンアップ
// これにより、万が一の場合に備えて少しの追加の予防策が追加されます
// 上記のフィルターを通過したものがあります
$str = $this->_do_never_allowed($str);
/*
※画像は特殊な方法で扱われます
* - 本質的に、私たちはキャラクターのすべてを知りたいのです
* 変換は、不要な XSS コードが見つかったかどうかに関係なく実行されます。
* そうでない場合は、画像がきれいであるため、TRUE を返します。
※ただし、変換後の文字列が一致しない場合は
* XSS の削除後の文字列、不要な XSS があったため失敗します
* 処理中にコードが見つかり、削除/変更されました。
*/
if ($is_image === TRUE)
{
return ($str == $converted_string) ?正: 偽;
}
log_message('debug', "XSS フィルタリングが完了しました");
$str;
を返します
}
//------------------------------------------------ ------------------
// URL のランダムなハッシュ値を保護します
パブリック関数 xss_hash()
{
if ($this->_xss_hash == '')
{
mt_srand();
$this->_xss_hash = md5(time() + mt_rand(0, 1999999999));
}
$this->_xss_hash;
を返します
}
//------------------------------------------------ ------------------
/**
* HTML エンティティのトランスコーディング
*/
パブリック関数entity_decode($str, $charset='UTF-8')
{
if (stristr($str, '&') === FALSE)
{
$str;
を返します
}
$str = html_entity_decode($str, ENT_COMPAT, $charset);
$str = preg_replace('~(0*[0-9a-f]{2,5})~ei', 'chr(hexdec("\1"))', $str);
return preg_replace('~([0-9]{2,4})~e', 'chr(\1)', $str);
}
//------------------------------------------------ ------------------
//ファイル名をフィルタリングしてファイル名のセキュリティを確保します
パブリック関数 sanitize_filename($str, $relative_path = FALSE)
{
$bad = 配列(
「../」、
「」、
「<」、
「>」、
「」、
「」、
「&」、
「$」、
'#'、
'{'、
'}'、
'['、
「]」、
'='、
';'、
「?」、
"%20"、
"%22"、
"%3c", //
"%253c", //
"%3e", // >
"%0e", // >
"%28", // (
"%29", // )
"%2528", // (
"%26", // &
"%24", // $
"%3f", // ?
"%3b", // ;
"%3d" // =
);
if ( ! $relative_path)
{
$bad[] = './';
$bad[] = '/';
}
$str = Remove_invisible_characters($str, FALSE);
ストリップスラッシュを返します(str_replace($bad, '', $str));
}
// javascript などの単語を javascript に圧縮します
保護された関数 _compact_exploded_words($matches)
{
return preg_replace('/s+/s', '', $matches[1]).$matches[2];
}
//------------------------------------------------ ------------------
/*
* いくつかの有害な HTML 属性を削除します
*/
保護された関数 _remove_evil_attributes($str, $is_image)
{
// すべての JavaScript イベント ハンドラー (例: onload、onclick、onmouseover)、style、および xmlns
$evil_attributes = array('onw*', 'style', 'xmlns', 'formaction');
if ($is_image === TRUE)
{
/*
* Adobe Photoshop は XML メタデータを JFIF 画像に挿入します
* 名前空間を含むため、画像に対してこれを許可する必要があります。
*/
Unset($evil_attributes[array_search('xmlns', $evil_attributes)]);
}
やります{
$count = 0;
$attribs = array();
// 引用符を含む不正な属性文字列の出現を検索します (042 と 047 は 8 進引用符です)
Preg_match_all('/('.implode('|', $evil_attributes).')s*=s*(
foreach ($attr として $matches)
{
$attribs[] = preg_quote($attr[0], '/');
}
// 引用符のない不正な属性文字列の出現を検索します
preg_match_all('/('.implode('|', $evil_attributes).')s*=s*([^s>]*)/is', $str, $matches, PREG_SET_ORDER);
foreach ($attr として $matches)
{
$attribs[] = preg_quote($attr[0], '/');
}
// HTML タグ内の不正な属性文字列を置き換えます
if (count($attribs) > 0)
{
$str = preg_replace('/()(/?[^><]+?)([^A-Za-z<>-])(.*?)('.implode(' |', $attribs).')(.*?)([s>
}
} while ($count);
$str;
を返します
}
// ------------------------------------------------ --------------------
/**
* HTMLを精製し、閉じられていないタグを完成させます
*/
保護された関数 _sanitize_naughty_html($matches)
{
// 左中括弧をエンコードします
$str = '<'.$matches[1].$matches[2].$matches[3];
// 再帰ベクトルを防ぐために、キャプチャされた左中括弧または右中括弧をエンコードします
$str .= str_replace(array('>', '<'), array('>', '<'),
$matches[4]);
$str;
を返します
}
// ------------------------------------------------ --------------------
/**
* ハイパーリンク内の js をフィルターします
*/
保護された関数 _js_link_removal($match)
{
return str_replace(
$match[1],
preg_replace(
'#href=.*?(alert(|alert(|javascript:|livescript:|mocha:|charset=|window.|document.|.cookie|
”、
$this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]))
)、
$match[0]
);
}
// ------------------------------------------------ --------------------
/**
* 画像リンク内の js をフィルター
*/
保護された関数 _js_img_removal($match)
{
return str_replace(
$match[1],
preg_replace(
'#src=.*?(alert(|alert(|javascript:|livescript:|mocha:|charset=|window.|document.|.cookie|
”、
$this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]))
)、
$match[0]
);
}
// ------------------------------------------------ --------------------
/**
* 属性を変換して一部の文字をエンティティに変換します
*/
保護された関数 _convert_attribute($match)
{
return str_replace(array('>', '<', '\'), array('>', '<', '\\'), $match[0]);
}
// ------------------------------------------------ --------------------
//过滤html标签プロパティ
保護された関数 _filter_attributes($str)
{
$out = '';
if (preg_match_all('#s*[a-z-]+s*=s*( 42| 47)([^\1]*?)\1#is', $str, $matches))
{
foreach ($matches[0] として $match)
{
$out .= preg_replace("#/*.*?*/#s", '', $match);
}
}
$out を返します;
}
// ------------------------------------------------ --------------------
//html实体转码
保護された関数 _decode_entity($match)
{
return $this->entity_decode($match[0], strtoupper(config_item('charset')));
}
// ------------------------------------------------ --------------------
/**
* URL エンティティを確認します
*/
保護された関数 _validate_entities($str)
{
/*
* URL 内の GET 変数を保護します
*/
// 901119URL5918AMP18930PROTECT8198
$str = preg_replace('|&([a-z_0-9-]+)=([a-z_0-9-]+)|i', $this->xss_hash()."\1=\2 ", $str);
/*
* 標準の文字エンティティを検証します
*
※省略されている場合はセミコロンを追加してください。 有効にするためにこれを行います
* 後でエンティティを ASCII に変換します。
*
*/
$str = preg_replace('#(?[0-9a-z]{2,})([x00-x20])*;?#i', "\1;\2", $str);
/*
* UTF16 の 2 バイトエンコーディング (x00) を検証します
*
* 上記と同様に、不足している場合はセミコロンを追加します。
*
*/
$str = preg_replace('#(?)([0-9A-F]+);?#i',"\1\2;",$str);
/*
* URL 内の GET 変数の保護を解除します
*/
$str = str_replace($this->xss_hash(), '&', $str);
$str;
を返します
}
//------------------------------------------------ ------------------------
//許可されていない文字列をフィルタリングします
保護された関数 _do_never_allowed($str)
{
$str = str_replace(array_keys($this->_never_allowed_str), $this->_never_allowed_str, $str);
foreach ($this->_never_allowed_regex as $regex)
{
$str = preg_replace('#'.$regex.'#is', '[削除]', $str);
}
$str;
を返します
}
//------------------------------------------------ ------------------
//csrfのハッシュ値を設定
保護された関数 _csrf_set_hash()
{
if ($this->_csrf_hash == '')
{
// _csrf_cookie_name が存在する場合は、それを直接 csrf ハッシュ値として使用します
If (isset($_COOKIE[$this->_csrf_cookie_name]) &&
Preg_match('#^[0-9a-f]{32}$#iS', $_COOKIE[$this->_csrf_cookie_name]) === 1)
{
$this->_csrf_hash = $_COOKIE[$this->_csrf_cookie_name];
を返します
}
//それ以外の場合は、ランダムな md5 文字列
戻り値 $this->_csrf_hash = md5(uniqid(rand(), TRUE));
}
$this->_csrf_hash;
を返します
}
}