导语:Canary(中文名:金丝雀)库结合了输入匹配和自动通知,可以更轻松地检测潜在的攻击。
Canary(中文名:金丝雀)库结合了输入匹配和自动通知,可以更轻松的检测潜在的攻击。
煤矿中的金丝雀
当人们谈论安全和发现问题时,我确信你已经听说过“煤矿中的金丝雀”这句话了。在过去,矿工们在挖掘煤矿时,会有有毒气体释放而被困的危险。其中一些气体难以被人类检测到,如果人类吸入足够多的有毒气体,可能会导致疾病甚至死亡。为了能够发现并避免这样的问题,矿工们会把金丝雀(鸟)带到矿井里。这种鸟对有毒气体更加敏感,如果有毒气体释放或者在其他危险接近的时候,金丝雀就会做出反应并提醒矿工们警惕危险。
在安全领域中,我们可以引入“金丝雀值”的概念,这与上面所讲述的内容非常相似。“金丝雀值”是指那些真实的或是伪造的并以某种方式暴露在你的系统之外的问题。当使用这个值时,则需要立即通知到你,以便收集有关使用情况和相关问题的更多信息并采取措施。下面是一些可能存在的例子:
1.管理员帐户上已锁定的用户名
2.一直返回失败的API令牌
3.实际上并不存在的实体ID
基本上,“金丝雀值”可以是你想要检测的任何数据,这些数据可能是真实的或伪造的。当攻击者使用其中一个值,你就会马上收到通知;这可能意味着有人在不应该待着的地方徘徊,或者可能你应用程序中的其他信息已被泄露。PHP应用程序现在可以使用psecio/canary库轻松处理检测和通知过程。
金丝雀软件包
psecio/canary软件包的创建是因为我们在某些值被检测到时需要触发对多个服务的通知。该软件包提供了两项功能:
· 它允许你定义在传入的数据中要查找的“canary”键/值对。默认情况下,该数据将包括PHP超全局变量$_GET,$_POST和$_SERVER['REQUEST_URI']。
· 它提供了各种适配器,可在检测到的键/值匹配时处理通知。
至今,可提供的通知方法包括:标准PHP错误日志,通过Monolog记录,通过PagerDuty和Slack通知进行消息传递。它还允许定义自定义回调,所以你可以根据你的需求进行更多的自定义设置。
安装Canary
Canary软件包可以通过Composer软件包管理工具轻松安装。使用下面的命令将该软件包导入你的项目:
composer require psecio/canary
psecio/canary库有一些可选项的被标为“建议”的依赖项,为不同的通知服务提供以下功能:
· monolog/monolog 用于Monolog日志记录
· nmcquay/pagerduty用于PagerDuty消息传递
· maknz/slack用于发送Slack消息
如果你没有为匹配项定义自定义处理程序,则该软件包将会默认报告与标准PHP错误日志相匹配的项。
基本用法
安装软件包后,使用它就很简单了。Canary具有流畅且“用户友好”的设计界面,非常的易于理解。你可以使用if/then的逻辑关系定义匹配项和通知。在if中定义要匹配的键和值,在then中定义通知方法。
下面是一个基本的示例。请记住,通知方法默认为PHP错误日志:
<?php require_once 'vendor/autoload.php'; \Psecio\Canary\Instance::build() ->if('username', 'foobar') ->execute(); ?>
执行此代码时,将检查传入数据中的username值。如果匹配到了,Canary则会根据我们的标准来检查这个值。在这种情况下,如果传入值与我们的“foobar”要求相匹配,则会向PHP错误日志发送通知,如下所示:
Canary match: {"type":"equals","key":"username","value":"foobar"}
输出的内容包括匹配的JSON数据:匹配类型,检测到的key和这个key的值。此JSON输出格式在不同的通知方法中是一致的。
注意:目前看来,没有办法更改输出的字符串或内容。但是,如果你使用Monolog做集成,则可以随时使用自定义的处理器修改消息内容。
如果你想使用其他集成方式,则需要使用then方法调用进行设置。例如,如果我们想要设置Slack通知,我们首先需要创建我们的客户端,然后在我们匹配到相应的值时,将变量传入then方法:
<?php require_once 'vendor/autoload.php'; $settings = [ 'channel' => '#notifications' ]; $slack = new Maknz\Slack\Client('https://hooks.slack.com/services/....', $settings); \Psecio\Canary\Instance::build() ->if('username', 'foobar') ->then($slack) ->execute(); ?>
在上面的代码中,我们使用自定义的配置($settings变量)创建了Maknz\Slack\Client实例,可以将消息发送到指定的地址。然后将此实例添加到匹配中,并将实例作为参数传入并调用then方法。结果类似于前面的示例,不同点在于消息不是通过PHP错误日志而是通过webhook集成发送到了Slack通道。
所以,你可能不禁会问 :“一次只匹配一个键/值集的用途是什么?这似乎不太灵活。”Canary允许你定义多个键/值对,同时监视所有的键并将通知发送到单个目标。下面的代码也许可以实现:
<?php require_once 'vendor/autoload.php'; $matches = [ 'username' => 'foo', 'test' => 'bar' ]; \Psecio\Canary\Instance::build()->if($matches)->execute(); ?>
上面的代码显示通过使用基本数组一次定义多个匹配。由于没有调用then方法,所以如果匹配到了指定的值则消息将被发送到错误日志。当你像这样定义多个匹配时,通知只能发送到一个目标。如果你希望不同的“金丝雀值”通知到不同的目标,你需要多次调用if/ then:
<?php require_once 'vendor/autoload.php'; $canary = \Psecio\Canary\Instance::build(); $slack = new Maknz\Slack\Client('https://hooks.slack.com/services/....', [...]); $matches = [ 'username' => 'foo', 'test' => 'bar' ]; // Goes to just the error log $canary->if($matches); // Send this one to Slack instead $canary->if('username', 'testuser')->then($slack); // Execute all checks and notify $canary->execute(); ?>
动态标准和回调
我想要介绍的另外两个功能有助于你更灵活的使用Canary库:使用类/方法路径来定义多个匹配并使用自定义闭包回调来做通知。
首先让我们看一个使用类路径动态提取匹配项的示例:
<?php require_once 'vendor/autoload.php'; $path = '\Foo\Bar::criteria'; \Psecio\Canary\Instance::build()->if($criteria)->execute(); ?>
代码中使用我们的源定义了$path变量的值,该值指向了可以返回数组的静态方法。使用这种方法,我们可以抽象出逻辑,因此在我们的检测点中并非一直都是硬编码的。类定义如下所示:
<?php namespace Foo; class Bar { public static function criteria() { return [ 'username' => 'foo' ]; } }
显然,在这个例子中,该方法只返回了一组硬编码的键/值对,但你可以将返回值更新为你从数据源中获取的任何数据。这种方式防止你在每个入口点的代码中定义自定义的逻辑。
接下来,我想说明的一个用于then处理程序的custom closure(自定义闭包)的例子。虽然已经集成了几个外部服务,但有时你可能需要一个自定义的解决方案来记录其他内容。在这种情况下,你可以使用回调并使用use方法导入调用的依赖项。示例如下:
<?php require_once 'vendor/autoload.php'; $path = '\Foo\Bar::criteria'; \Psecio\Canary\Instance::build() ->if($criteria) ->then(function($criteria) use ($adapter){ $adapter->send($criteria->toArray()); // It also allows for JSON encoding echo json_encode($criteria); }) ->execute(); ?>
这个例子非常简单,但它显示了我所描述的基本的想法。闭包是通过手动调用then方法传入的,当找到匹配时,会使用 $criteria匹配的信息调用$adapter。然后可以通过toArray调用格式化输出,或者你可以对输出进行json_encode来获得与其他的目标类型中的输出所类似的字符串。
在中间件中的使用方法
上面我已经展示了如何单独使用Canary,现在让我们看一下如何在实际的PHP应用程序中的中间件中使用Canary。为简单起见,我将使用Slim框架应用程序,其中包含了每个请求的自定义中间件。
首先让我们安装依赖项:
composer require psecio/canary slim/slim
安装完成后,在当前目录中创建index.php文件,文件包含以下内容:
<?php require_once 'vendor/autoload.php'; $app = new \Slim\App(); $app->add(function($request, $response, $next) { \Psecio\Canary\Instance::build()->if('username', 'test')->execute(); $response = $next($request, $response); return $response; }); $app->get('/', function($request, $response) { $output = '<a href="/?username=foo">no trigger</a><br/>'; $output .= '<a href="/?username=test">trigger</a><br/>'; $response->getBody()->write($output); return $response; }); // Run the application $app->run(); ?>
要运行该应用程序,你可以使用PHP的内置Web服务器。在与index.php文件相同的目录中,使用命令行执行以下命令:
php -S localhost:8080
然后访问http://localhost:8080。你会看到包含两个链接的页面:“无触发”和“触发”。你还会注意到,运行PHP命令的窗口显示了根路径的请求日志。当触发匹配时,在这个命令窗口会显示错误信息(基本上是内置的PHP Web服务器的“错误日志”)。
如果单击“无触发”链接,你会重定向到一个在URL上指定了username的值作为参数的页面。在这种情况下,username的值为“foo”。由于我们的中间件中正在寻找的值是“test”,因此不会触发任何通知。现在点击另一个链接 —— “触发” —— 你会在日志中看到不同的输出:
[Tue Feb 27 14:22:30 2018] Canary match: {"type":"equals","key":"username","value":"test"} [Tue Feb 27 14:22:30 2018] ::1:49471 [200]: /?username=test
username值与我们在if检查中定义的值是相匹配的,因此会触发通知并将输出发送到错误日志记录。通知是在记录页面输出信息之前发生的,因为中间件在任何路由的请求处理发生之前就已触发。这里有更多关于Slim的运作方式,然而,这些方式并不适用于所有框架。这个例子说明了如何在中间件中使用Canary并在每个请求上调用匹配查询,但Canary肯定能做到比代码中展示的更专业的事情。你可能只是在请求某些端点时调用匹配查询,甚至也可以使用更复杂的条件来做限制。
封装
Canary库提供了一种简单的方法来检测输入并在匹配时处理通知。本文未涉及Canary库的高级用法,但在项目的README中有详细的说明。当然,Canary是一个开源项目,所以如果你有任何建议或发现了任何问题,请随时提交你发现的问题,甚至提交更新请求!
资源
Monolog日志记录的独白/独白
用于PagerDuty消息传递的nmcquay / pagerduty
maknz / slack用于发送Slack消息
nfosec Island网站上的一篇关于Canary的文章