煤礦中的金絲雀——利用Canary對PHP應用程式進行輸入檢測和響應
Canary(中文名:金絲雀)庫結合了輸入匹配和自動通知,可以更輕鬆的檢測潛在的攻擊。
煤礦中的金絲雀
當人們談論安全和發現問題時,我確信你已經聽說過“煤礦中的金絲雀”這句話了。在過去,礦工們在挖掘煤礦時,會有有毒氣體釋放而被困的危險。其中一些氣體難以被人類檢測到,如果人類吸入足夠多的有毒氣體,可能會導致疾病甚至死亡。為了能夠發現並避免這樣的問題,礦工們會把金絲雀(鳥)帶到礦井裡。這種鳥對有毒氣體更加敏感,如果有毒氣體釋放或者在其他危險接近的時候,金絲雀就會做出反應並提醒礦工們警惕危險。
在安全領域中,我們可以引入“金絲雀值”的概念,這與上面所講述的內容非常相似。“金絲雀值”是指那些真實的或是偽造的並以某種方式暴露在你的系統之外的問題。當使用這個值時,則需要立即通知到你,以便收集有關使用情況和相關問題的更多資訊並採取措施。下面是一些可能存在的例子:
1.管理員帳戶上已鎖定的使用者名稱
2.一直返回失敗的API令牌
3.實際上並不存在的實體ID
基本上,“金絲雀值”可以是你想要檢測的任何資料,這些資料可能是真實的或偽造的。當攻擊者使用其中一個值,你就會馬上收到通知;這可能意味著有人在不應該待著的地方徘徊,或者可能你應用程式中的其他資訊已被洩露。PHP應用程式現在可以使用psecio/canary庫輕鬆處理檢測和通知過程。
金絲雀軟體包
psecio/canary軟體包的建立是因為我們在某些值被檢測到時需要觸發對多個服務的通知。該軟體包提供了兩項功能:
·它允許你定義在傳入的資料中要查詢的“canary”鍵/值對。預設情況下,該資料將包括PHP超全域性變數$_GET,$_POST和$_SERVER['REQUEST_URI']。
·它提供了各種介面卡,可在檢測到的鍵/值匹配時處理通知。
至今,可提供的通知方法包括:標準PHP錯誤日誌,通過Monolog記錄,通過PagerDuty和Slack通知進行訊息傳遞。它還允許ofollow,noindex"> 定義自定義回撥 ,所以你可以根據你的需求進行更多的自定義設定。
安裝Canary
Canary軟體包 可以通過 Composer/">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的文章