XSS Stored 測試
dvwa儲存型XSS
儲存型XSS:會把使用者輸入的資料“儲存”在伺服器端,一般出現在需要使用者可以輸入資料的地方,比如網站的留言板、評論等地方,當網站這些地方過濾不嚴格的時候,就會被黑客注入惡意攻擊程式碼,儲存在伺服器端,每當使用者載入該頁面時,都會受到攻擊,所以這種攻擊行為也稱為“持久型XSS(Persistent XSS)”。
- low級別
在Name和Message輸入框中分別輸入一個String(admin)發現輸入的資料會顯示在當前頁面中,同時可以知道資料提交是以POST請求的方式
檢視原始碼:
<?php if( isset( $_POST[ 'btnSign' ] ) ) { // Get input $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); // Sanitize message input $message = stripslashes( $message ); $message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // Sanitize name input $name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // Update database $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); //mysql_close(); } ?>
程式碼審計發現,並不存在Protect,trim()函式、stripslashes()函式、mysqli_real_escape_string()
函式等不會對JS程式碼進行過濾
trim(string,charlist) 函式移除字串兩側的空白字元或其他預定義字元。 string 必需。規定要檢查的字串。 charlist 可選。規定從字串中刪除哪些字元。如果被省略,則移除以下所有字元: "\0" - NULL "\t" - 製表符 "\n" - 換行 "\x0B" - 垂直製表符 "\r" - 回車 " " - 空格 ######################################################################################## stripslashes(string) 函式刪除由 addslashes() 函式新增的反斜槓。 string 必需。規定要檢查的字串。 提示:該函式可用於清理從資料庫中或者從 HTML 表單中取回的資料。 ######################################################################################## addslashes(string) 函式返回在預定義字元之前新增反斜槓的字串。 預定義字元是: 單引號(') 雙引號(") 反斜槓(\) NULL string 必需。規定要轉義的字串。 提示:該函式可用於為儲存在資料庫中的字串以及資料庫查詢語句準備字串。 ######################################################################################## mysql_real_escape_string(string,connection) 函式轉義 SQL 語句中使用的字串中的特殊字元。 下列字元受影響: \x00 \n \r \ ' " \x1a 如果成功,則該函式返回被轉義的字串。如果失敗,則返回 false。 string 必需。規定要轉義的字串。 connection 可選。規定 MySQL 連線。如果未規定,則使用上一個連線。 提示:可使用本函式來預防資料庫攻擊。
構造payload,執行最簡單的XSS攻擊
POST data
txtName=admin&mtxMessage=<script>alert("_XSS_")</script>&btnSign=Sign+Guestbook
惡意程式碼被存入服務端,每當使用者開啟該網頁都會受到攻擊
服務端資料庫檔案已存入攻擊者的惡意程式碼
進入該網頁,觸發惡意攻擊
- medium 級別
<?php if( isset( $_POST[ 'btnSign' ] ) ) { // Get input $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); // Sanitize message input $message = strip_tags( addslashes( $message ) ); $message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); $message = htmlspecialchars( $message ); // Sanitize name input $name = str_replace( '<script>', '', $name ); $name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // Update database $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); //mysql_close(); } ?>
經過程式碼審計,可以發現medium級別進行了Protect,對POST請求到的的內容進行過濾:
str_replace()函式 對 Name data進行<script>標籤匹配置空
strip_tags()函式 對 message data進行標籤匹配刪除
str_replace(find,replace,string,count) 函式以其他字元替換字串中的一些字元(區分大小寫)。
該函式必須遵循下列規則:
如果搜尋的字串是陣列,那麼它將返回陣列。
如果搜尋的字串是陣列,那麼它將對陣列中的每個元素進行查詢和替換。
如果同時需要對陣列進行查詢和替換,並且需要執行替換的元素少於查詢到的元素的數量,那麼多餘元素將用空字串進行替換
如果查詢的是陣列,而替換的是字串,那麼替代字串將對所有查詢到的值起作用。
註釋:該函式區分大小寫。請使用 str_ireplace() 函式執行不區分大小寫的搜尋。
引數 描述
find 必需。規定要查詢的值。
replace 必需。規定替換 find 中的值的值。
string 必需。規定被搜尋的字串。
count 可選。對替換數進行計數的變數。
###########################################################################################
strip_tags(string,allow) 函式剝去字串中的 HTML、XML 以及 PHP 的標籤。
註釋:該函式始終會剝離 HTML 註釋。這點無法通過 allow 引數改變。
引數 描述
string 必需。規定要檢查的字串。
allow 可選。規定允許的標籤。這些標籤不會被刪除。
分析可知要想利用XSS攻擊,只有對Name data進行注入,利用<script>標籤大小寫進行繞過,但你會發現Name data length=10 受到限制
篡改資料,繞過length限制
構造payload
POST Name data
txtName=%3CScript%3Ealert%281111%29%3C%2FscriPt%3E&mtxMessage=admin&btnSign=Sign+Guestbook
Name data的繞過
服務端資料庫檔案已存入攻擊者的惡意程式碼
在這裡也可以使用<img>標籤進行繞過:<img src=# onerror=alert("_XSS_")>
- high 級別
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
程式碼審計之後,發現high級別的Name data存在另外一種的Protect,利用preg_replace()函式進行匹配“< s r i p t”等字元,將其置為空,此時Name data裡面的<script>標籤是不能使用的,不管是大小進行區分寫都不可以進行繞過
所以,就需要利用其它標籤進行XSS攻擊,此處可以利用 medium 級別 中提到的<img>標籤進行繞過preg_replace()函式的Protect
構造payload
POST Name data
txtName=%3Cimg+src%3D%23+onerror%3Dalert%28%22_XSS_%22%29%3E&mtxMessage=Hello+admin&btnSign=Sign+Guestbook
<img>標籤成功繞過preg_replace()
服務端資料庫檔案已存入攻擊者的惡意程式碼
- impossible 級別
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = stripslashes( $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$name = htmlspecialchars( $name );
// Update database
$data = $db->prepare( 'INSERT INTO guestbook ( comment, name ) VALUES ( :message, :name );' );
$data->bindParam( ':message', $message, PDO::PARAM_STR );
$data->bindParam( ':name', $name, PDO::PARAM_STR );
$data->execute();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
impossible 級別的Protect不能被繞過,由於Name data和Message data都受到htmlspecialchars()函式的保護作用
htmlspecialchars() 函式把預定義的字元轉換為 HTML 實體。
預定義的字元是:
& (和號)成為 &
" (雙引號)成為 "
' (單引號)成為 '
< (小於)成為 <
> (大於)成為 >
它的語法如下:
htmlspecialchars(string,flags,character-set,double_encode)
其中第二個引數flags需要重要注意,很多開發者就是因為沒有注意到這個引數導致使用htmlspecialchars()函式過濾XSS時被繞過。因為flags引數對於引號的編碼如下:
可用的引號型別:
ENT_COMPAT - 預設。僅編碼雙引號。
ENT_QUOTES - 編碼雙引號和單引號。
ENT_NOQUOTES - 不編碼任何引號。
預設是隻編碼雙引號的
因為輸入的所有標籤都被轉義,所以此處不存在XSS攻擊,但是要注意flags屬性,使用不當過濾XSS時就會被繞過