[Best Practice series] PHP Security Trigger: escape of filtering, verification, and escaping & exploring the principle of Blade template engine in preventing XSS attacks

Source: Internet
Author: User
[Best Practice series] PHP Security three axes: escape for filtering, verification, and escaping & Blade template engine exploring PHP escape implementation

When rendering the output into a webpage or API response, it must be escaped. this is also a protection measure to avoid rendering malicious code and XSS attacks, it also prevents application users from inadvertently executing malicious code.

We can use the htmlentities function mentioned above to transfer the output. The second parameter of this function must use ENT_QUOTES to escape single quotation marks and double quotation marks, the following example also demonstrates how to escape HTML output before rendering by specifying the appropriate character encoding (usually UTF-8) in the third parameter:

 Script alert ("Welcome to Laravel college! ") Script

'; Echo htmlentities ($ output, ENT_QUOTES, 'utf-8 ');

If direct output is not escaped, a prompt box is displayed:

After escaping, the output is changed:

Script alert ("Welcome to Laravel college! "); Script

Modern PHP supports many Template engines. these templates have been used for escape processing at the underlying level. for example, the popular twig/twig and smarty/smarty will automatically escape the output. This default processing method is awesome, providing powerful security protection for PHP Web applications.

How the Blade template engine avoids XSS attacks

Laravel uses the template engine Blade. For more information about the use of Blade, see its official documentation. here we will briefly discuss how Laravel can escape the output.

In general, the returned view content in Laravel will do this:

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

This is a simple example, which means we will find test in the resources/views directory. blade. php view file, pass the $ data variable into it, and return the final rendering result as the response content to the user. Which of the following underlying source codes has been processed in this process? if $ data contains script code (such as JavaScript script), how can this problem be solved? Next let's take a look at it.

First, let's start with the helper function view. of course, we can also use View: make here. but for simplicity, we generally use the view function, which is defined in Illuminate \ Foundation \ helpers. php file:

function view($view = null, $data = [], $mergeData = []){    $factory = app(ViewFactory::class);    if (func_num_args() === 0) {        return $factory;    }    return $factory->make($view, $data, $mergeData);}

The logic in this function is to retrieve the $ factory instance corresponding to ViewFactory of The View factory interface from the container (the binding relationship is registered in the register method of Illuminate \ View \ ViewServiceProvider, in addition, the template engine parser EngineResolver, including PhpEngine and CompilerEngine loading BladeCompiler, and the View File Finder FileViewFinder, is also registered here ), if a parameter is input, call the make method on $ factory:

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;}

This method is located in Illuminate \ View \ Factory. what we do here is to obtain the complete path of the View file and merge the input variables, $ this-> getEngineFromPath obtains the corresponding template engine through the view file suffix, for example, we use. blade. the View file ending with php obtains CompilerEngine (that is, the Blade template engine). otherwise, PhpEngine is obtained, and the View (Illuminate \ View) is instantiated based on the corresponding parameters) object and return. Note that the _ toString method is rewritten in the View class:

public function __toString(){    return $this->render();}

So when we print the $ view instance, we will actually call the render method of The View class, so next we should study what the render method has done:

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;    }}

Here we will focus on the $ this-> renderContents () method. we will continue to study the renderContents method in the View class:

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;}

Focus on $ this-> getContents () here, go to the getContents method:

protected function getContents(){    return $this->engine->get($this->path, $this->gatherData());}

As we have mentioned earlier, the $ this-> engine corresponds to CompilerEngine (Illuminate \ View \ Engines \ CompilerEngine), so we enter the get method of CompilerEngine:

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;}

As we mentioned earlier, CompilerEngine uses BladeCompiler, so $ this-> compiler is the Blade compiler. let's first look at $ this-> compiler-> compile ($ path ); this line (the first run or compiled view template will be entered here if it has expired) and enter the compile method of BladeCompiler:

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);    }}

What we do here is to compile the view file content first, and then store the compiled content to the corresponding file in the view compilation path (storage \ framework \ views) (one compilation, run multiple times to improve performance). here we focus on the $ this-> compileString method, which uses the token_get_all function to split the view file code into multiple fragments, if the fragment is an array, call the $ this-> parseToken method cyclically:

protected function parseToken($token){    list($id, $content) = $token;    if ($id == T_INLINE_HTML) {        foreach ($this->compilers as $type) {            $content = $this->{"compile{$type}"}($content);        }    }    return $content;}

Here, we are very close to the truth. for HTML code (including the Blade command code), compileExtensions, compileStatements, compileComments, and compileEchos methods are called cyclically. we focus on the output method compileEchos, by default, the Blade engine provides three output methods: compileRawEchos, compileEscapedEchos, and compileRegularEchos. the corresponding commands are {!! !!} , {{}}And {{}}, as the name suggests, compileRawEchos corresponds to native output:

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) : '
 compileEchoDefaults($matches[2]).'; ?>'.$whitespace;    };    return preg_replace_callback($pattern, $callback, $value);}

In the Blade view {!! !!} The wrapped variable outputs HTML in the native. this method is recommended if you want to display images and links.

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) : '
 '.$whitespace;    };    return preg_replace_callback($pattern, $callback, $value);}

$ This-> echoFormat corresponds to e (% s). this method is also used in 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] : '
 compileEchoDefaults($matches[2]).'); ?>'.$whitespace;    };    return preg_replace_callback($pattern, $callback, $value);}

The helper function e () is defined in Illuminate \ Support \ helpers. php:

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

The function is to escape the input value.

After such escaping, the {$ data} in the view is compiled In the end, let's go back to the get method of CompilerEngine to pass $ data into the view output. let's look at this section:

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

The compiled view file path and input variable $ data are passed in evaluatePath. the method is defined as follows:

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());}

The PHP System function extract is called to import the input variable from the array to the current symbol table (introduced through include $ __path ), the function is to replace all the variables in the compiled view file with the passed variable values (through key name ing ).

Okay, this is the basic process from rendering to output of the Blade view template. we can see that we escape the output through {} to avoid XSS attacks.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.