ホームページ  >  記事  >  バックエンド開発  >  [ベスト プラクティス シリーズ] PHP セキュリティの 3 つの柱: フィルタリング、検証、エスケープ - Blade テンプレート エンジンを使用して XSS 攻撃を回避する原則の探求

[ベスト プラクティス シリーズ] PHP セキュリティの 3 つの柱: フィルタリング、検証、エスケープ - Blade テンプレート エンジンを使用して XSS 攻撃を回避する原則の探求

WBOY
WBOYオリジナル
2016-06-20 12:26:501011ブラウズ

PHP エスケープ実装

出力を Web ページまたは API 応答にレンダリングする場合、これは悪意のあるコードのレンダリングや XSS の発生を避けるための保護手段でもあります。攻撃を防止し、アプリのユーザーが誤って悪意のあるコードを実行することを防ぎます。

出力を転送するには、前述の htmlentities 関数を使用できます。関数の 2 番目のパラメーターでは、一重引用符と二重引用符をエスケープするために ENT_QUOTES を使用する必要があります。また、3 番目のパラメーターでも適切な文字エンコーディングを指定します。 (通常は UTF-8)。次の例は、レンダリング前に HTML 出力をエスケープする方法を示しています:

<?php$output = '<p><script>alert(“欢迎来到Laravel学院!")</script></p>';echo htmlentities($output, ENT_QUOTES, ‘UTF-8');

エスケープせずに直接出力すると、プロンプト ボックスが表示されます:

エスケープ後の出力は次のようになります:

<p><script>alert("欢迎访问Laravel学院!");</script></p>

最新の PHP は多くのテンプレート エンジンをサポートしており、これらのテンプレート エンジンは現在、人気のある小枝/小枝やSmarty/smarty は出力を自動的にエスケープします。このデフォルトの処理は優れており、PHP Web アプリケーションに強力なセキュリティ保証を提供します。

XSS 攻撃を回避するための Blade テンプレート エンジンの原理

Laravel で使用されるテンプレート エンジンは Blade です。Blade の使用方法については、ここで簡単に説明します。 Laravel の最下層は出力処理をエスケープします。

通常、Laravel では次のようにビューのコンテンツを返します。

return view(’test’, [‘data’=>$data]);

これは非常に単純な例です。つまり、resources/views php ビューに test.blade が見つかります。ファイルを作成し、そのファイルに $data 変数を渡し、最終的なレンダリング結果を応答のコンテンツとしてユーザーに返します。では、このプロセスではどのような基礎的なソース コード処理が行われたのでしょうか? $data 変数にスクリプト コード (JavaScript スクリプトなど) が含まれている場合、それはどのように処理されるべきでしょうか?次に詳しく見てみましょう。

まず、補助関数 view から始めます。 もちろん、ここで View:make を使用することもできますが、簡単にするために、通常は IlluminateFoundationhelpers.php ファイルで定義されている view 関数を使用します。 >

function view($view = null, $data = [], $mergeData = []){    $factory = app(ViewFactory::class);    if (func_num_args() === 0) {        return $factory;    }    return $factory->make($view, $data, $mergeData);}
この関数のロジックは、ビューファクトリインターフェイス ViewFactory に対応するインスタンス $factory をコンテナから取り出すことです (このバインディング関係は IlluminateViewViewServiceProvider の register メソッドに登録されています。また、テンプレート エンジンのリゾルバーここには、PhpEngine とローダーを含む EngineResolver も登録されます。BladeCompiler の CompilerEngine とビュー ファイル ファインダー FileViewFinder を入力します。つまり、パラメータが渡されると、$factory の make メソッドがここに登録されます。呼び出し先:

public function make($view, $data = [], $mergeData = []){    if (isset($this->aliases[$view])) {        $view = $this->aliases[$view];    }    $view = $this->normalizeName($view);    $path = $this->finder->find($view);    $data = array_merge($mergeData, $this->parseData($data));    $this->callCreator($view = new View($this, $this->getEngineFromPath($path), $view, $path, $data));    return $view;}
このメソッドは IlluminateViewFactory にあります。ここで行うことは、ビュー ファイルの完全なパスを取得し、$this->getEngineFromPath が対応するテンプレート エンジンを取得することです。たとえば、ビュー ファイルの接尾辞に .blade.php を使用します。ビュー ファイルは CompilerEngine (つまり、Blade テンプレート エンジン) を取得し、それ以外の場合は PhpEngine を取得し、対応するパラメータに従って View (IlluminateViewView) オブジェクトをインスタンス化します。そして戻ります。 __toString メソッドが View クラスでオーバーライドされていることに注意してください:

public function __toString(){    return $this->render();}
したがって、$view インスタンスを印刷すると、View クラスの render メソッドが実際に呼び出されるので、次のステップでrender メソッドの動作を調べます:

public function render(callable $callback = null){    try {        $contents = $this->renderContents();        $response = isset($callback) ? call_user_func($callback, $this, $contents) : null;        // Once we have the contents of the view, we will flush the sections if we are        // done rendering all views so that there is nothing left hanging over when        // another view gets rendered in the future by the application developer.        $this->factory->flushSectionsIfDoneRendering();        return ! is_null($response) ? $response : $contents;    } catch (Exception $e) {        $this->factory->flushSections();        throw $e;    } catch (Throwable $e) {        $this->factory->flushSections();         throw $e;    }}
ここでの焦点は $this->renderContents() メソッドです。引き続き View クラスの renderContents メソッドを詳しく見ていきましょう。 🎜>

ここでは $this->getContents() に焦点を当て、getContents メソッドを入力します。
protected function renderContents(){    // We will keep track of the amount of views being rendered so we can flush    // the section after the complete rendering operation is done. This will    // clear out the sections for any separate views that may be rendered.    $this->factory->incrementRender();    $this->factory->callComposer($this);    $contents = $this->getContents();    // Once we've finished rendering the view, we'll decrement the render count    // so that each sections get flushed out next time a view is created and    // no old sections are staying around in the memory of an environment.    $this->factory->decrementRender();    return $contents;}

ここの $this->engine が CompilerEngine に対応することは前に述べました。 (IlluminateViewEnginesCompilerEngine) なので、CompilerEngine の get メソッドに入ります。
protected function getContents(){    return $this->engine->get($this->path, $this->gatherData());}

同様に、CompilerEngine で使用されるコンパイラは BladeCompiler であると前述したため、$this->compiler は Blade コンパイラになります。 $ を見てみましょう。 this->compiler first. ->compile($path); この行 (初めて実行するとき、またはコンパイルされたビュー テンプレートが期限切れになったときにここに入力します)、BladeCompiler のコンパイル メソッドを入力します。 🎜>
public function get($path, array $data = []){    $this->lastCompiled[] = $path;    // If this given view has expired, which means it has simply been edited since    // it was last compiled, we will re-compile the views so we can evaluate a    // fresh copy of the view. We'll pass the compiler the path of the view.    if ($this->compiler->isExpired($path)) {        $this->compiler->compile($path);    }    $compiled = $this->compiler->getCompiledPath($path);    // Once we have the path to the compiled file, we will evaluate the paths with    // typical PHP just like any other templates. We also keep a stack of views    // which have been rendered for right exception messages to be generated.    $results = $this->evaluatePath($compiled, $data);    array_pop($this->lastCompiled);    return $results;}
ここで行うことは、最初にファイルのコンテンツを表示し、コンパイルされたコンテンツをビューのコンパイル パス (storageframeworkviews) の下にある対応するファイルに保存することです (パフォーマンスを向上させるために、1 回コンパイルして複数回実行します)。 $this->compileString メソッド。 token_get_all 関数は、ビュー ファイル コードを複数のフラグメントに分割するメソッドで使用されます。フラグメントが配列の場合、$this->parseToken メソッドがループで呼び出されます。 🎜>

この時点で、HTML コード (Blade 命令コードを含む) については、compileExtensions、compileStatements、compileComments、およびcompileEchos メソッドが循環的に呼び出されることになります。ブレード エンジンは、デフォルトで、compileRawEchos、compileEscapedEchos、および COMPLEIREGULAREchos の 3 つの出力メソッドを提供します。対応する命令は、名前が示すように、compileRawEchos です。出力:
public function compile($path = null){    if ($path) {        $this->setPath($path);    }    if (! is_null($this->cachePath)) {        $contents = $this->compileString($this->files->get($this->getPath()));        $this->files->put($this->getCompiledPath($this->getPath()), $contents);    }}
protected function compileRawEchos($value){    $pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->rawTags[0], $this->rawTags[1]);    $callback = function ($matches) {        $whitespace = empty($matches[3]) ? '' : $matches[3].$matches[3];        return $matches[1] ? substr($matches[0], 1) : '<?php echo '.$this->compileEchoDefaults($matches[2]).'; ?>'.$whitespace;    };    return preg_replace_callback($pattern, $callback, $value);}

即Blade视图中以 {!! !!} 包裹的变量会原生输出HTML,如果要显示图片、链接,推荐这种方式。

{{{}}} 对应的 CompileEscapedEchos ,这个在Laravel 4.2及以前版本中用于转义,现在已经替换成了 {{}} ,即调用 compileRegularEchos 方法:

protected function compileRegularEchos($value){    $pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->contentTags[0], $this->contentTags[1]);    $callback = function ($matches) {        $whitespace = empty($matches[3]) ? '' : $matches[3].$matches[3];        $wrapped = sprintf($this->echoFormat, $this->compileEchoDefaults($matches[2]));        return $matches[1] ? substr($matches[0], 1) : '<?php echo '.$wrapped.'; ?>'.$whitespace;    };    return preg_replace_callback($pattern, $callback, $value);}

其中 $this->echoFormat 对应 e(%s) ,无独有偶, compileEscapedEchos 中也用到这个方法:

protected function compileEscapedEchos($value){    $pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->escapedTags[0], $this->escapedTags[1]);    $callback = function ($matches) {        $whitespace = empty($matches[3]) ? '' : $matches[3].$matches[3];        return $matches[1] ? $matches[0] : '<?php echo e('.$this->compileEchoDefaults($matches[2]).'); ?>'.$whitespace;    };    return preg_replace_callback($pattern, $callback, $value);}

辅助函数 e() 定义在 Illuminate\Support\helpers.php 中:

function e($value){    if ($value instanceof Htmlable) {        return $value->toHtml();    }    return htmlentities($value, ENT_QUOTES, 'UTF-8', false);}

其作用就是对输入的值进行转义。

经过这样的转义,视图中的 {{ $data }} 或被编译成 0c0f1111e02c0ddad0b0315959e5d21f ,最终如何将 $data 传入视图输出,我们再回到 CompilerEngine 的 get 方法,看这一段:

$results = $this->evaluatePath($compiled, $data);

evaluatePath 中传入了编译后的视图文件路径和传入的变量 $data ,该方法定义如下:

protected function evaluatePath($__path, $__data){   $obLevel = ob_get_level();ob_start();    extract($__data, EXTR_SKIP);    // We'll evaluate the contents of the view inside a try/catch block so we can    // flush out any stray output that might get out before an error occurs or    // an exception is thrown. This prevents any partial views from leaking.    try {        include $__path;    } catch (Exception $e) {        $this->handleViewException($e, $obLevel);    } catch (Throwable $e) {        $this->handleViewException(new FatalThrowableError($e), $obLevel);    }    return ltrim(ob_get_clean());}

这里面调用了PHP系统函数 extract 将传入变量从数组中导入当前符号表(通过 include $__path 引入),其作用也就是将编译后视图文件中的变量悉数替换成传入的变量值(通过键名映射)。

好了,这就是Blade视图模板从渲染到输出的基本过程,可以看到我们通过 {{}} 来转义输出,从而达到避免XSS攻击的目的。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。