2020年11月底, 在为我们的一个客户进行安全审计时, 我们发现了一个基于Laravel的网站. 虽然这个网站的安全状态很好, 但我们注意到它是在调试模式下运行的, 因此显示了大量的错误信息, 包括堆栈痕迹:
经过进一步的检查, 我们发现这些堆栈痕迹是由Ignition生成的, 而Ignition是Laravel第6版开始的默认错误页面生成器. 在穷尽了其他漏洞载体之后, 我们开始对这个包进行更精确的检查.
Ignition <= 2.5.1
除了显示漂亮的堆栈痕迹, Ignition还附带了解决方案, 小段的代码可以解决你在开发应用时可能遇到的问题. 例如,如果我们在模板中使用一个未知变量,会发生这样的情况:
通过点击 "使变量可选",我们模板中的{ { $username }}会自动被{ { $username ? '' }}. 如果我们检查我们的HTTP日志,我们可以看到被调用的端点:
除了解决方案的类名之外,我们还发送了一个文件路径和一个我们想要替换的变量名。这看起来很有趣。
让我们先检查一下类名向量:我们可以实例化任何东西吗?
class SolutionProviderRepository implements SolutionProviderRepositoryContract{...public function getSolutionForClass(string $solutionClass): ?Solution{if (! class_exists($solutionClass)) {return null;}if (! in_array(Solution::class, class_implements($solutionClass))) {return null;}return app($solutionClass);}}
不是:Ignition会确保我们指向的类实现了RunnableSolution。
那我们就来仔细看看这个类吧。负责这个的代码位于./vendor/facade/ignition/src/Solutions/MakeViewVariableOptionalSolution.php中。也许我们可以改变一个任意文件的内容?
class MakeViewVariableOptionalSolution implements RunnableSolution{...public function run(array $parameters = []){$output = $this->makeOptional($parameters);if ($output !== false) {file_put_contents($parameters['viewFile'], $output);}}public function makeOptional(array $parameters = []){$originalContents = file_get_contents($parameters['viewFile']); // [1]$newContents = str_replace('$'.$parameters['variableName'], '$'.$parameters['variableName']." ?? ''", $originalContents);$originalTokens = token_get_all(Blade::compileString($originalContents)); // [2]$newTokens = token_get_all(Blade::compileString($newContents));$expectedTokens = $this->generateExpectedTokens($originalTokens, $parameters['variableName']);if ($expectedTokens !== $newTokens) { // [3]return false;}return $newContents;}protected function generateExpectedTokens(array $originalTokens, string $variableName): array{$expectedTokens = [];foreach ($originalTokens as $token) {$expectedTokens[] = $token;if ($token[0] === T_VARIABLE && $token[1] === '$'.$variableName) {$expectedTokens[] = [T_WHITESPACE, ' ', $token[2]];$expectedTokens[] = [T_COALESCE, '??', $token[2]];$expectedTokens[] = [T_WHITESPACE, ' ', $token[2]];$expectedTokens[] = [T_CONSTANT_ENCAPSED_STRING, "''", $token[2]];}}return $expectedTokens;}...}
这段代码比我们预想的要复杂一些:读取给定的文件路径[1]后,将$variableName替换为$variableName ? '',初始文件和新文件都将被标记化[2]。如果我们的代码结构没有超出预期的变化,文件将被替换成新的内容。否则,makeOptional将返回false[3],新文件将不会被写入。因此,我们无法使用variableName做太多事情。
唯一剩下的输入变量是viewFile。如果我们对variableNam