CVE-2021-43808: Use of a Broken or Risky Cryptographic Algorithm (CWE-327)
value: Laravel is a web application framework. Laravel prior to versions 8.75.0, 7.30.6, and 6.20.42 contain a possible cross-site scripting (XSS) vulnerability in the Blade templating engine. A broken HTML element may be clicked and the user taken to another location in their browser due to XSS. This is due to the user being able to guess the parent placeholder SHA-1 hash by trying common names of sections. If the parent template contains an exploitable HTML structure an XSS vulnerability can be exposed. This vulnerability has been patched in versions 8.75.0, 7.30.6, and 6.20.42 by determining the parent placeholder at runtime and using a random hash that is unique to each request.
CWE description: The product uses a broken or risky cryptographic algorithm or protocol.
CVSS v3 base score: 6.1
Published: 2021-12-08T00:15Z
Repository: https://github.com/laravel/framework
Language: PHP
Files changed: src/Illuminate/View/Compilers/Compiler.php, src/Illuminate/View/Compilers/Concerns/CompilesLayouts.php, src/Illuminate/View/Concerns/ManagesLayouts.php, tests/View/ViewBladeCompilerTest.php
Commit date: 2021-12-06 11:14:03 -0600
Commit message: [6.x] Fix parent call (#39908)

* Fix parent call

* Apply fixes from StyleCI

Co-authored-by: Taylor Otwell <taylorotwell@users.noreply.github.com>
Diff: diff --git a/src/Illuminate/View/Compilers/Compiler.php b/src/Illuminate/View/Compilers/Compiler.php
index 08648ad17b..d2a9f26ce5 100755
--- a/src/Illuminate/View/Compilers/Compiler.php
+++ b/src/Illuminate/View/Compilers/Compiler.php
@@ -46,11 +46,11 @@ public function __construct(Filesystem $files, $cachePath)
      * @param  string  $path
      * @return string
      */
     public function getCompiledPath($path)
     {
-        return $this->cachePath.'/'.sha1($path).'.php';
+        return $this->cachePath.'/'.sha1('v2'.$path).'.php';
     }
 
     /**
      * Determine if the view at the given path is expired.
      *
diff --git a/src/Illuminate/View/Compilers/Concerns/CompilesLayouts.php b/src/Illuminate/View/Compilers/Concerns/CompilesLayouts.php
index aaef617476..fc6a63afad 100644
--- a/src/Illuminate/View/Compilers/Concerns/CompilesLayouts.php
+++ b/src/Illuminate/View/Compilers/Concerns/CompilesLayouts.php
@@ -1,11 +1,9 @@
 <?php
 
 namespace Illuminate\View\Compilers\Concerns;
 
-use Illuminate\View\Factory as ViewFactory;
-
 trait CompilesLayouts
 {
     /**
      * The name of the last section that was started.
      *
@@ -48,11 +46,13 @@ protected function compileSection($expression)
      *
      * @return string
      */
     protected function compileParent()
     {
-        return ViewFactory::parentPlaceholder($this->lastSection ?: '');
+        $escapedLastSection = strtr($this->lastSection, ['\\' => '\\\\', "'" => "\\'"]);
+
+        return "<?php echo \Illuminate\View\Factory::parentPlaceholder('{$escapedLastSection}'); ?>";
     }
 
     /**
      * Compile the yield statements into valid PHP.
      *
diff --git a/src/Illuminate/View/Concerns/ManagesLayouts.php b/src/Illuminate/View/Concerns/ManagesLayouts.php
index 29d71552a8..82fb77b074 100644
--- a/src/Illuminate/View/Concerns/ManagesLayouts.php
+++ b/src/Illuminate/View/Concerns/ManagesLayouts.php
@@ -1,10 +1,11 @@
 <?php
 
 namespace Illuminate\View\Concerns;
 
 use Illuminate\Contracts\View\View;
+use Illuminate\Support\Str;
 use InvalidArgumentException;
 
 trait ManagesLayouts
 {
     /**
@@ -26,10 +27,17 @@ trait ManagesLayouts
      *
      * @var mixed
      */
     protected static $parentPlaceholder = [];
 
+    /**
+     * The parent placeholder salt for the request.
+     *
+     * @var string
+     */
+    protected static $parentPlaceholderSalt;
+
     /**
      * Start injecting content into a section.
      *
      * @param  string  $section
      * @param  string|null  $content
@@ -166,16 +174,32 @@ public function yieldContent($section, $default = '')
      * @return string
      */
     public static function parentPlaceholder($section = '')
     {
         if (! isset(static::$parentPlaceholder[$section])) {
-            static::$parentPlaceholder[$section] = '##parent-placeholder-'.sha1($section).'##';
+            $salt = static::parentPlaceholderSalt();
+
+            static::$parentPlaceholder[$section] = '##parent-placeholder-'.sha1($salt.$section).'##';
         }
 
         return static::$parentPlaceholder[$section];
     }
 
+    /**
+     * Get the parent placeholder salt.
+     *
+     * @return string
+     */
+    protected static function parentPlaceholderSalt()
+    {
+        if (! static::$parentPlaceholderSalt) {
+            return static::$parentPlaceholderSalt = Str::random(40);
+        }
+
+        return static::$parentPlaceholderSalt;
+    }
+
     /**
      * Check if section exists.
      *
      * @param  string  $name
      * @return bool
diff --git a/tests/View/ViewBladeCompilerTest.php b/tests/View/ViewBladeCompilerTest.php
index d211bd08b8..1bc9e94839 100644
--- a/tests/View/ViewBladeCompilerTest.php
+++ b/tests/View/ViewBladeCompilerTest.php
@@ -16,11 +16,11 @@ protected function tearDown(): void
     }
 
     public function testIsExpiredReturnsTrueIfCompiledFileDoesntExist()
     {
         $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
-        $files->shouldReceive('exists')->once()->with(__DIR__.'/'.sha1('foo').'.php')->andReturn(false);
+        $files->shouldReceive('exists')->once()->with(__DIR__.'/'.sha1('v2foo').'.php')->andReturn(false);
         $this->assertTrue($compiler->isExpired('foo'));
     }
 
     public function testCannotConstructWithBadCachePath()
     {
@@ -31,35 +31,35 @@ public function testCannotConstructWithBadCachePath()
     }
 
     public function testIsExpiredReturnsTrueWhenModificationTimesWarrant()
     {
         $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
-        $files->shouldReceive('exists')->once()->with(__DIR__.'/'.sha1('foo').'.php')->andReturn(true);
+        $files->shouldReceive('exists')->once()->with(__DIR__.'/'.sha1('v2foo').'.php')->andReturn(true);
         $files->shouldReceive('lastModified')->once()->with('foo')->andReturn(100);
-        $files->shouldReceive('lastModified')->once()->with(__DIR__.'/'.sha1('foo').'.php')->andReturn(0);
+        $files->shouldReceive('lastModified')->once()->with(__DIR__.'/'.sha1('v2foo').'.php')->andReturn(0);
         $this->assertTrue($compiler->isExpired('foo'));
     }
 
     public function testCompilePathIsProperlyCreated()
     {
         $compiler = new BladeCompiler($this->getFiles(), __DIR__);
-        $this->assertEquals(__DIR__.'/'.sha1('foo').'.php', $compiler->getCompiledPath('foo'));
+        $this->assertEquals(__DIR__.'/'.sha1('v2foo').'.php', $compiler->getCompiledPath('foo'));
     }
 
     public function testCompileCompilesFileAndReturnsContents()
     {
         $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
         $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World');
-        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('foo').'.php', 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
+        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('v2foo').'.php', 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
         $compiler->compile('foo');
     }
 
     public function testCompileCompilesAndGetThePath()
     {
         $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
         $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World');
-        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('foo').'.php', 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
+        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('v2foo').'.php', 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
         $compiler->compile('foo');
         $this->assertSame('foo', $compiler->getPath());
     }
 
     public function testCompileSetAndGetThePath()
@@ -71,11 +71,11 @@ public function testCompileSetAndGetThePath()
 
     public function testCompileWithPathSetBefore()
     {
         $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
         $files->shouldReceive('get')->once()->with('foo')->andReturn('Hello World');
-        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('foo').'.php', 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
+        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('v2foo').'.php', 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
         // set path before compilation
         $compiler->setPath('foo');
         // trigger compilation with $path
         $compiler->compile();
         $this->assertSame('foo', $compiler->getPath());
@@ -101,11 +101,11 @@ public function testRawTagsCanBeSetToLegacyValues()
      */
     public function testIncludePathToTemplate($content, $compiled)
     {
         $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
         $files->shouldReceive('get')->once()->with('foo')->andReturn($content);
-        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('foo').'.php', $compiled);
+        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('v2foo').'.php', $compiled);
 
         $compiler->compile('foo');
     }
 
     /**
@@ -155,20 +155,20 @@ public function appendViewPathDataProvider()
 
     public function testDontIncludeEmptyPath()
     {
         $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
         $files->shouldReceive('get')->once()->with('')->andReturn('Hello World');
-        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('').'.php', 'Hello World');
+        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('v2').'.php', 'Hello World');
         $compiler->setPath('');
         $compiler->compile();
     }
 
     public function testDontIncludeNullPath()
     {
         $compiler = new BladeCompiler($files = $this->getFiles(), __DIR__);
         $files->shouldReceive('get')->once()->with(null)->andReturn('Hello World');
-        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1(null).'.php', 'Hello World');
+        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('v2').'.php', 'Hello World');
         $compiler->setPath(null);
         $compiler->compile();
     }
 
     public function testShouldStartFromStrictTypesDeclaration()

Vulnerable code:         return $this->cachePath.'/'.sha1($path).'.php';
use Illuminate\View\Factory as ViewFactory;

        return ViewFactory::parentPlaceholder($this->lastSection ?: '');
            static::$parentPlaceholder[$section] = '##parent-placeholder-'.sha1($section).'##';
        $files->shouldReceive('exists')->once()->with(__DIR__.'/'.sha1('foo').'.php')->andReturn(false);
        $files->shouldReceive('exists')->once()->with(__DIR__.'/'.sha1('foo').'.php')->andReturn(true);
        $files->shouldReceive('lastModified')->once()->with(__DIR__.'/'.sha1('foo').'.php')->andReturn(0);
        $this->assertEquals(__DIR__.'/'.sha1('foo').'.php', $compiler->getCompiledPath('foo'));
        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('foo').'.php', 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('foo').'.php', 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('foo').'.php', 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('foo').'.php', $compiled);
        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('').'.php', 'Hello World');
        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1(null).'.php', 'Hello World');
Fixed code:         return $this->cachePath.'/'.sha1('v2'.$path).'.php';
        $escapedLastSection = strtr($this->lastSection, ['\\' => '\\\\', "'" => "\\'"]);

        return "<?php echo \Illuminate\View\Factory::parentPlaceholder('{$escapedLastSection}'); ?>";
use Illuminate\Support\Str;
    /**
     * The parent placeholder salt for the request.
     *
     * @var string
     */
    protected static $parentPlaceholderSalt;

            $salt = static::parentPlaceholderSalt();

            static::$parentPlaceholder[$section] = '##parent-placeholder-'.sha1($salt.$section).'##';
    /**
     * Get the parent placeholder salt.
     *
     * @return string
     */
    protected static function parentPlaceholderSalt()
    {
        if (! static::$parentPlaceholderSalt) {
            return static::$parentPlaceholderSalt = Str::random(40);
        }

        return static::$parentPlaceholderSalt;
    }

        $files->shouldReceive('exists')->once()->with(__DIR__.'/'.sha1('v2foo').'.php')->andReturn(false);
        $files->shouldReceive('exists')->once()->with(__DIR__.'/'.sha1('v2foo').'.php')->andReturn(true);
        $files->shouldReceive('lastModified')->once()->with(__DIR__.'/'.sha1('v2foo').'.php')->andReturn(0);
        $this->assertEquals(__DIR__.'/'.sha1('v2foo').'.php', $compiler->getCompiledPath('foo'));
        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('v2foo').'.php', 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('v2foo').'.php', 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('v2foo').'.php', 'Hello World<?php /**PATH foo ENDPATH**/ ?>');
        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('v2foo').'.php', $compiled);
        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('v2').'.php', 'Hello World');
        $files->shouldReceive('put')->once()->with(__DIR__.'/'.sha1('v2').'.php', 'Hello World');