Damn Vulnerable Web App (DVWA) 是一個基於 PHP 和 MySQL 實作的 web 應用。如同它的名字一樣,這是一個充滿漏洞的應用,因為這個應用的主要目標,就是幫助 web 程式設計師了解安全性的問題。

若要安裝 DVWA,請到 http://www.dvwa.co.uk/,下載後解壓縮,並放到網頁伺服器可讀取的目錄下。接著,我們要將 config 資料夾下的 config.inc.php.dist 複製為 config.inc.php,並修改其中的資料庫帳密;裡面預設使用 root,你也可以(或者必須,依系統而定)新增一個專門的使用者帳號來測試。上述設定完成後,請用瀏覽器連線到 setup.php 進行安裝前的設定的確認,若有出現紅色提示,代表你的伺服器缺少某些東西,或者設定比較安全,從而可能無法測試某些安全問題(以下範例將假設 allow_url_fopen 和 allow_url_include 為 off)。若設定上沒有問題,就可以按下"Create / Reset Database",讓程式自動幫你設定好測試用的資料庫。

接著要連到 login 登入。DVWA 的文件(docs/DVWA_v1.3.pdf)中提到,預設帳密是 admin/password,當然因為資料庫在自己手上,所以也可以直接用 phpmyadmin 連進去看密碼。DVWA 將密碼用 md5 加密儲存,不過因為範例的密碼是很常見的密碼,因此也可以將密文放到網路上搜尋或者自己嘗試字典法暴力破解,例如 gordonb 的密碼是 abc123。登入後,在左下方的"DVWA Security",可以讓你調整安全層級,這裡分成 low/medium/high 三種安全層級狀況讓你測試,並有 impossible 層級,來示範安全的作法(若你玩過較舊的版本,則舊版本的 high 就是現在的 impossible)。


首先我們來看"Brute Force",原始碼的路徑位於 vulnerabilities/brute/source。這裡的範例是輸入帳號密碼,若正確的話就會顯示歡迎訊息,否則顯示錯誤。在 low 層級當中,寫法是這樣的(稍微經過了一點簡化,移除跟破解較無關的部分):

$user = $_GET['username'];	
$pass = $_GET['password'];
$pass = md5($pass);
$query = "SELECT * FROM `users` WHERE user='$user' AND password='$pass';";
$result = mysqli_query( $qry ) or die( '<pre>' . mysqli_error() . '</pre>' );

if( $result && mysql_num_rows( $result ) == 1 ) {
	// ...
}

很明顯的,雖然密碼是加密過才送進去比對,但是 $user 可以被 SQL injection,各位可以用帳號為「admin' -- 」(請注意尾巴有空白),搭配隨意密碼來測試。而 medium 和 high 層級,雖然依序加上了 mysqli_real_escape_string 和 stripslashes,以及在登入錯誤時會暫停數秒等機制,但仍然有辦法透過暴力嘗試來登入。

Impossible 層級則是在資料庫中新增了 last_login 和 failed_login 欄位,來分別代表最後嘗試登入的時間和失敗次數,以藉此判斷是否應該鎖定帳號禁止登入,概念如下:

$total_failed_login = 3;
$lockout_time       = 15;
$account_locked     = false;

Check whether the user has been locked out: select failed_login, last_login from table
if( has_result && num of failed_login > $total_failed_login){
	Calculate when the user would be allowed to login again
	if(not allowed){
		$account_locked = true;
	}
}

Check whether username matches the password
if( matched && $account_locked == false ){
	Reset `failed_login` of table
}
else {
	Random sleep
	Increase `failed_login` of table
}

Update `last_login` of table



接著是"Command Injection",原始碼在 vulnerabilities/exec/source。這裡的範例是輸入 IP,網頁會幫你顯示 ping 指令的結果。Low 層級當中對輸入沒有做任何過濾,因此你可以使用例如「127.0.0.1 && dir D:\」之類的指令,來對該段程式碼進行設計外的操作;medium 層級當中,將用以區隔指令的"&&"和";"(for Linux)過濾掉,但仍可以用「127.0.0.1 | dir D:\」來繞過;high 層級則是把指令中常見的幾個標點符號過濾掉,但很不幸的在過濾 pipe 時多了一個空白(如下),因此仍能繞過。

// Set blacklist
$substitutions = array(
	'&'  => '',
	';'  => '',
	'| ' => '',
	'-'  => '',
	'$'  => '',
	'('  => '',
	')'  => '',
	'`'  => '',
	'||' => '',
);

// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );

Impossible 層級則僅允許用"."分隔的四個數字。


CSRF 的全名是 Cross Site Request Forgery,中文名稱為跨站請求偽造。DVWA 使用的範例是變更密碼,參數使用 GET 傳遞,原始碼在 vulnerabilities/csrf/source。Low 層級沒有驗證包括舊密碼在內的任何內容,因此只要提供 http://localhost/DVWA/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change# 這樣的一個連結,就可以任意地進行修改。Medium 和 high 層級則因為加入 HTTP_REFERER 甚至於隨機數值(CSRF token)來驗證,因此需要用修改 HTTP header 等技巧,甚至套用其他漏洞,才能從站外頁面攻擊(因為 php7 移除 HTTP_REFERER,因此可能無法測試)。Impossible 層級則是加入了舊密碼的驗證,因此除了暴力嘗試以外無法破解。


File Inclusion 顧名思義就是含括檔案,原始碼在 vulnerabilities/fi/source。你可以在網頁上看到 file1.php、file2.php 及 file3.php 被列出,而我們的目的是 file4.php 甚至於其他檔案。Low 層級直接把 $_GET 的內容做為檔名,也就是完全沒有防禦。Medium 層級以如下的方式過濾了"../"、"http://"等字串,但你仍可以用"....//"及其他 http 以外的協定來達到惡意目的。

$file = $_GET[ 'page' ];

// Input validation
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\"" ), "", $file );

High 層級只剩下 file4.php 沒有擋住,Impossible 層級則是使用白名單。


File Upload 的原始碼在 vulnerabilities/upload/source,範例中的功能是上傳圖片檔,上傳後的儲存位置在 hackable/uploads。Low 層級沒有任何防禦,可以任意上傳 php 檔案,如果再搭配前面的 File Inclusion,則即使上傳後的位置不在網頁伺服器可讀取的範圍內,也可以經由 include 而被當作 php 執行。

Medium 層級則是使用 MIME type 來驗證,如下:

// File information
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];

// Is it an image?
if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) && ( $uploaded_size < 100000 ) ) {
	// Do something
}

但用 tamper data 或其他修改封包內容的技巧,很容易就可以繞過。例如使用以下的 python 程式碼,其中的 PHPSESSID 為登入的相關資訊,需要自行代換;'Upload' 和 'uploaded' 則為示範頁面的表單欄位名稱:

import requests
 
response = requests.post(
	'http://localhost/dvwa/vulnerabilities/upload/',
	data={'Upload': '12345'},
	headers={'Cookie': 'PHPSESSID=fr9tae13skqg4hdvr6rosek17d; security=medium'},
	files=[('uploaded', ('aaa.php', open('aaa.php', 'rb'), 'image/jpeg'))]
)
 
print(response.content)

High 層級使用附檔名加上了 getimagesize 來檢驗(如下),但是我們可以把惡意程式碼藏在圖片的 EXIF 欄位當中上傳,如果配合較低等級的 File Inclusion 漏洞,就有辦法把它當作 php 來執行。你可以在上傳一張圖片後,將安全層級切換為 Low (Medium 也可以,但是需要另外繞過),以 http://localhost/dvwa/vulnerabilities/fi/?page=../../hackable/uploads/[剛才上傳的檔名] 這個連結來測試。

// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext  = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);

// Is it an image?
if( (
		strtolower( $uploaded_ext ) == "jpg" ||
		strtolower( $uploaded_ext ) == "jpeg" ||
		strtolower( $uploaded_ext ) == "png"
	) && ( $uploaded_size < 100000 ) && getimagesize( $uploaded_tmp ) ) {
	// Do something
}

Impossible 則是重新讀取圖片內容後再次存檔。


SQL Injection 的原始碼在 vulnerabilities/sqli/source,其範例是輸入使用者編號後顯示資料。Low 層級沒有做任何防禦,且會把查詢到的所有結果顯示出來。我們可以嘗試以下輸入:

至於 medium 層級,雖然使用了 mysql_real_escape_string 以跳脫單引號等字元,但是由於 SQL 語句卻移除了單引號,所以將前述部分範例進行簡單代換後,例如「1 or 1 = 1」,依然可以使用。High 層級使用「$_SESSION['id']」來取值,但如果有其他漏洞能改掉 SESSION 則也會被攻擊,而 impossible 層級則是限制了輸入的使用者編號必須是數字。


XSS 依據出現的地方不同,分成 DOM、reflected,以及 stored 等三種;由於這幾個部分非常相似,故此處以 reflected 做示範,其範例是將輸入的文字印出,原始碼在 vulnerabilities/xss_r/source,測試此範例時,請留意較新版的 Chrome 預設會擋掉 XSS,因此可能需要用 Firefox 或 IE 來測試。Low 層級沒有任何防禦,因此可以輸入任意的 html 或 JavaScript 片段。Medium 層級用以下方法將「<script>」標籤刪除,但依然可以用「<script >」、「<scri<script>pt>」或者「<ScrIpt>」等方法來繞過。

$name = str_replace('<script>', '', $_GET['name']);

High 層級當中雖然禁止了 script 標籤的各種大小寫,也不允許你在中間插入字串,但仍能用「<img src=1 onerror=alert('xss')>」等方法繞過。Impossible 層級則是使用 htmlspecialchars 來過濾使用者輸入內容。