今天,我们来尝试探索并渗透一款比较知名的PHP框架,它的名字是 Laravel !
本文章仅提供学习,切勿将其用于不法手段!
想要有效防御,必须先要做到有效攻击!有授权的攻击,远比无授权的攻击,要安全得多!
任何软件,任何框架,都是存在漏洞的,Laravel 框架,也不会例外!
那么,就让我们进入 Laravel 的 世界 ,去探索存在于 Laravel 框架内部的 Bug 吧 !
或许,这很像是在玩一场游戏!那么,网络安全爱好者们,让我们一起来尽情狂欢吧!
首先,让我们进入 Laravel 框架 源码 根目录 下 的 public 目录,让我们找到 index.php !
为什么要找到它呢?它是 Laravel 框架 的 执行入口 !
作为一名白帽黑客,作为一名漏洞挖掘者,我们必须从细节入手,从根源开始寻找 它 可能存在的问题 !
首先,我们进入 index.php 的 源码空间 !
我们发现了,下面的代码:
if(file_exists($maintenance = __DIR__ .'/../storage/framework/maintenance.php')){
require $maintenance;
}
通过分析这段代码,我们可以知道它的用途!
如果 $maintenance 对应的源码文件存在,那么就载入并执行它!
这里,我们可以进行一个假设!
如果 maintenance.php 这个文件的内容,不幸被污染,那么当 Laravel 框架被执行时,是不是意味着 maintenance.php 这个文件,可能会做出一些超乎控制的事情呢?!
更好的做法,是在检测到 maintenance.php 文件存在时,对比一下 它 的 sha1 或者 md5 值,从而判断,它的原始文件内容,是否已被修改(当发现文件内容已被篡改时,应紧急停止程序执行,并做好安全日志记录)!当然,这需要事先计算并保存好原始文件的 sha1 或者 md5 值!这里,可能会使用到 sha1_file 或者 md5_file 函数 !有的童鞋儿会说,sha1_file 和 md5_file 函数,并不太安全,可能被 碰撞攻击 突破!但是,我们不得不承认 它们的 计算速度很快,生成哈希值相对容易,非常适合快速校验!如果在 程序入口 消耗太多校验资源与时间,是并不明智的!虽然 sha256 算法(可以使用 hash_file("sha256",$file)),更加的安全,更加的难以被实施碰撞与逆向攻击,但是对应的,sha256算法 的 计算开销,要比 sha1 和 md5 算法 要大得多!综合分析,在程序执行的入口(可能会面临海量并发的情况,进行 sha256 校验 ,并不比进行 sha1 或 md5 校验 更加合适!
我们继续分析,接下来的一些可以被利用的点,当然,前提是,你可以通过某种方式(例如,文件上传、文件下载、文件修改等)去修改目标文件的内容!
require __DIR__ . '/../vendor/autoload.php';
$app = require_once __DIR__ . '/../bootstrap/app.php';
这两句代码,都存在文件包含漏洞 !当然,前提是,你能够去替换或修改这些文件中的内容!
$app->handleRequest(Request::capture());
让我们来分析上面这句代码!
上面的这句代码,由两部分组成,分别为 $app->handleRequest() 和 Request::capture() 。
鉴于程序代码执行的原则,让我们先来分析 Request::capture() 这个类静态方法的细节实现!
在 Request::capture() 这个类静态方法 中,调用了两个方法!
分别为 static::enableHttpMethodParameterOverride() 和 static::createFromBase(SymfonyRequest::createFromGlobals()) !
我们追踪 static::enableHttpMethodParameterOverride() 方法 的 定义,我们发现,它的作用是 self::$httpMethodParameterOverride 的值 置为 真 true !
根据代码追踪,我们已知,self 对应的 类 为 http-foundation/Request.php 文件中的 Request 类 !
而 Request 类的 静态成员属性 $httpMethodParameterOverride 的初始值 为 假 False 。
接下来,我们要做什么?我们要分析这个属性的用途!
如果我们没有说明文档,我们怎么去分析呢(并非所有源码,都会提供给我们足够的说明文件或者官方手册,以供参考,我更多分享给大家的,是一种漏洞挖掘的思维!通过代码审计的方式,去挖掘软件源码中可能存在的安全漏洞)?
假设,我们并没有 Laravel 这款框架的官方手册 或者 说明文件!
那么,我们怎么去分析 $httpMethodParameterOverride 这个类静态成员属性的用途呢?
所以,我们先看一下它的类名称,程序员们,往往会存在一个习惯,就是根据它的用途,去定义它的名称!而作为白帽黑客,这恰恰是我们感兴趣的点 ^_^ ,想要挖掘漏洞,我们必须理解它的内部原理以及实现机制!
我们来翻译一下,一些有价值的信息!
namespace Symfony\Component\HttpFoundation;
这句话,定义了一个命名空间!
通过大数据搜索,我们得知 Symfony\Component\HttpFoundation 是 Symfony 框架的核心组件之一,重点来了!Symfony\Component\HttpFoundation 是专门用于处理 HTTP 请求和响应的 !
Request 类,封装了 $_GET、$_POST、$_COOKIE、$_SERVER 等超全局变量,并提供了 链式方法 来访问请求数据!
什么是链式方法呢?
大家可以理解为 ,链式方法 通过 返回对象自身(this)来实现连续调用!
例如,db->from([['table1','t1'],['table2','t2']])->select(['t1','id'],['t2','parent_id'])->where([['t1','id','in',[1,2,3,4,5]],'and',['t2','parent_id',’eq',0]]);
通过封装处理,Request 类 成为了 Request 类中所有类方法调用者 与 $_GET、$_POST、$_COOKIE、$_SERVER 等这些超全局变量之间的一座桥梁!
在 Request::capture() 方法 中,我们发现了
static::createFromBase(SymfonyRequest::createFromGlobals());
因此,我们先去追踪 SymfonyRequest::createFromGlobals(),在 这个方法中,我们发现了有趣的地方 ,例如
$request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER);
我们发现,并没有传入 $_REQUEST 这个超全局变量!那么 $_REQUEST 是否可以在渗透时,加以利用呢?但是 self::createRequestFromFactory 传递了 $_GET 和 $_POST ,也就是说,利用 $_REQUEST 可能需要采用一些特殊手段,例如 变量覆盖? 或者 数组内容 新增 或 删除?这方面,有待后续深入研究。
在 self::createRequestFromFactory 这个方法中,我们发现了
if (self::$requestFactory) { $request = (self::$requestFactory)($query, $request, $attributes, $cookies, $files, $server, $content); if (!$request instanceof self) { throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.'); } return $request; }
self::$requestFactory 有点类似于 C语言中的函数指针,这里使用了间接引用的方式,来执行相应的方法过程!
下面我们来研究一下,请求工厂模式!
class RequestBuilder {
protected static ?\Closure $requestFactory = null;
public static function setFactory(\Closure $factory): void {
self::$requestFactory = $factory;
}
public static function createRequest(array $data): Request {
return self::$requestFactory ? self::$requestFactory($data) : new Request($data);
}
}
// 使用示例
RequestBuilder::setFactory(fn($data) => new JsonRequest($data));
$request = RequestBuilder::createRequest(['key' => 'value']);
我们定义了一个类静态成员属性(它有点类似于一个函数指针,因为它的类型为
?\Closure !Closure 是 闭包的意思,这里涉及到了 闭包 的概念! \Closure 是 PHP 内置的闭包类型(匿名函数),支持捕获外部变量并动态执行!?符号代表 要么 为 null 空,要么为 一个 具体类型!
?\Closure ,代表 要么 是 null 空,要么 是 \Closure 闭包类型!闭包类型,是什么?是匿名函数!
?\Closure
表示变量可以是闭包对象,也可以是 null
,常用于需要动态绑定逻辑的场景。
闭包类型,这个东西,是在 PHP7以后出现的,所以,我们要去研究它!因为 Larvael 框架中 大量使用了 这种新出现的东西!
闭包,是伪装成函数的对象!至少,在PHP语言中,是这个样子的!
特性 | 匿名函数 | 闭包 |
---|---|---|
定义 | 无名称的函数表达式 | 匿名函数 + 捕获外部作用域变量 |
变量访问 | 默认无法访问外部变量 | 通过 use 关键字显式捕获外部变量 |
类型 | 普通函数类型 | Closure 类的实例 |
生命周期 | 执行完毕后立即销毁 | 可能因变量引用而延长生命周期 |
让我们看一下,上面的表格,我们会发现,闭包 是 匿名函数 + 可捕获外部作用域变量 特性 的 新生产物!它的两个特点:一是 匿名函数,二是 可捕获外部作用域变量 !
匿名函数,默认是无法访问外部变量的(这里涉及到了 局部变量 和 全局变量 的概念,外部变量,指的是 在 匿名函数外部 定义的变量!
闭包,可以通过 use 关键字来访问外部变量!
匿名函数,是普通的函数类型,而 闭包 则是 Closure 类的实例对象!
让我们看一下 Closure 类的定义!
它的定义,在 Core_c.php 文件中,下面是这个类定义的内容!
final class Closure { /** * This method exists only to disallow instantiation of the Closure class. * Objects of this class are created in the fashion described on the anonymous functions page. * @link https://secure.php.net/manual/en/closure.construct.php */ private function __construct() {} /** * This is for consistency with other classes that implement calling magic, * as this method is not used for calling the function. * @param mixed ...$_ [optional] * @return mixed * @link https://secure.php.net/manual/en/class.closure.php */ public function __invoke(...$_) {} /** * Duplicates the closure with a new bound object and class scope * @link https://secure.php.net/manual/en/closure.bindto.php * @param object|null $newThis The object to which the given anonymous function should be bound, or NULL for the closure to be unbound. * @param object|class-string|null $newScope The class scope to which associate the closure is to be associated, or 'static' to keep the current one. * If an object is given, the type of the object will be used instead. * This determines the visibility of protected and private methods of the bound object. * @return Closure|null Returns the newly created Closure object or null on failure */ #[Pure] public function bindTo(?object $newThis, object|string|null $newScope = 'static'): ?Closure {} /** * This method is a static version of Closure::bindTo(). * See the documentation of that method for more information. * @link https://secure.php.net/manual/en/closure.bind.php * @param Closure $closure The anonymous functions to bind. * @param object|null $newThis The object to which the given anonymous function should be bound, or NULL for the closure to be unbound. * @param object|class-string|null $newScope The class scope to which associate the closure is to be associated, or 'static' to keep the current one. * If an object is given, the type of the object will be used instead. * This determines the visibility of protected and private methods of the bound object. * @return Closure|null Returns the newly created Closure object or null on failure */ #[Pure] public static function bind(Closure $closure, ?object $newThis, object|string|null $newScope = 'static'): ?Closure {} /** * Temporarily binds the closure to newthis, and calls it with any given parameters. * @link https://php.net/manual/en/closure.call.php * @param object $newThis The object to bind the closure to for the duration of the call. * @param mixed $args [optional] Zero or more parameters, which will be given as parameters to the closure. * @return mixed * @since 7.0 */ public function call(object $newThis, mixed ...$args): mixed {} /** * @param callable $callback * @return Closure * @since 7.1 */ public static function fromCallable(callable $callback): Closure {} }
Closure 类,是最终(final)的,这意味着,它不能够被子类集成,也就是说, Closure 类 不会存在子类!
(未完待续)