PHP 的連線方式
緬甸皇家國際:www.hao-jiang.com
apche2-module
把 php 當做 apache 的一個模組,實際上 php 就相當於 apache 中的一個 dll 或一個 so 檔案,phpstudy 的非 nts 模式就是預設以 module 方式連線的:
CGI 模式
此時 php 是一個獨立的程式比如 php-cgi.exe,web 伺服器也是一個獨立的程式比如 apache.exe,然後當 Web 伺服器監聽到 HTTP 請求時,會去呼叫 php-cgi 程式,他們之間通過 cgi 協議,伺服器把請求內容轉換成 php-cgi 能讀懂的協議資料傳遞給 cgi 程式,cgi 程式拿到內容就會去解析對應 php 檔案,得到的返回結果在返回給 web 伺服器,最後 web 伺服器返回到客戶端,但隨著網路技術的發展,CGI 方式的缺點也越來越突出。每次客戶端請求都需要建立和銷燬程式。因為 HTTP 要生成一個動態頁面,系統就必須啟動一個新的程式以執行 CGI 程式,不斷地 fork 是一項很消耗時間和資源的工作。
FastCGI 模式
fastcgi 本身還是一個協議,在 cgi 協議上進行了一些優化,眾所周知,CGI 程式的反覆載入是 CGI 效能低下的主要原因,如果 CGI 直譯器保持在記憶體中 並接受 FastCGI 程式管理器排程,則可以提供良好的效能、伸縮性、Fail-Over 特性等等。
簡而言之,CGI 模式是 apache2 接收到請求去呼叫 CGI 程式,而 fastcgi 模式是 fastcgi 程式自己管理自己的 cgi 程式,而不再是 apache 去主動呼叫 php-cgi,而 fastcgi 程式又提供了很多輔助功能比如記憶體管理,垃圾處理,保障了 cgi 的高效性,並且 CGI 此時是常駐在記憶體中,不會每次請求重新啟動
PHP-FPM
這個大家肯定都不陌生,在 linux 下裝 php 環境的時候,經常會用到 php-fpm,那 php-fpm 是什麼?
上面提到,fastcgi 本身是一個協議,那麼就需要有一個程式去實現這個協議,php-fpm 就是實現和管理 fastcgi 協議的程式,fastcgi 模式的記憶體管理等功能,都是由 php-fpm 程式所實現的
下面引用 p 師傅的部落格文章:
Nginx 等伺服器中介軟體將使用者請求按照 fastcgi 的規則打包好通過 TCP 傳給誰?其實就是傳給 FPM。
FPM 按照 fastcgi 的協議將 TCP 流解析成真正的資料。
舉個例子,使用者訪問
http://127.0.0.1/index.php?a=1&b=2
,如果 web 目錄是/var/www/html
,那麼 Nginx 會將這個請求變成如下 key-value 對:
{ 'GATEWAY_INTERFACE': 'FastCGI/1.0', 'REQUEST_METHOD': 'GET', 'SCRIPT_FILENAME': '/var/www/html/index.php', 'SCRIPT_NAME': '/index.php', 'QUERY_STRING': '?a=1&b=2', 'REQUEST_URI': '/index.php?a=1&b=2', 'DOCUMENT_ROOT': '/var/www/html', 'SERVER_SOFTWARE': 'php/fcgiclient', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '12345', 'SERVER_ADDR': '127.0.0.1', 'SERVER_PORT': '80', 'SERVER_NAME': "localhost", 'SERVER_PROTOCOL': 'HTTP/1.1' }
這個陣列其實就是 PHP 中
$_SERVER
陣列的一部分,也就是 PHP 裡的環境變數。但環境變數的作用不僅是填充$_SERVER
陣列,也是告訴 fpm:“我要執行哪個 PHP 檔案”。PHP-FPM 拿到 fastcgi 的資料包後,進行解析,得到上述這些環境變數。然後,執行
SCRIPT_FILENAME
的值指向的 PHP 檔案,也就是/var/www/html/index.php
。
本質上 fastcgi 模式也只是對 cgi 模式做了一個封裝,本質上只是從原來 web 伺服器去呼叫 cgi 程式變成了 web 伺服器通知 php-fpm 程式並由 php-fpm 程式去呼叫 php-cgi 程式。
判斷連線模式
就拿 *CTF
來說,如何判斷一個 php 的連線模式?在接觸不到伺服器檔案的情況下,我們可以通過 phpinfo 來判斷:
phpinfo 的第三行代表了 PHP 的連線模式,第一張圖的 Apache 2.0 Handler 代表了這個 php 使用了 apache-module 模式,第二張圖的 CGI/FastCGI 代表了用 CGI 模式進行通訊,第三張圖的 FPM 代表了 php-fpm 程式的 fastcgi 模式
一般來說,apache 伺服器常用 module 方式起 php,nginx 伺服器常用 fastcgi 模式起 php,所以接下來我已 nginx 為例
php-fpm 的模式
是不是很繞,php-fpm 下還可以繼續分,如果使用 fastcgi 模式,nginx 與 php-fpm 通訊可以通過兩種模式,一種是 TCP 模式,一種是 unix 套接字 (socket) 模式
TCP 模式
TCP 模式即是 php-fpm 程式會監聽本機上的一個埠(預設 9000),然後 nginx 會把客戶端資料通過 fastcgi 協議傳給 9000 埠,php-fpm 拿到資料後會呼叫 cgi 程式解析
nginx的配置檔案像這個樣子:
/etc/nginx/sites-available/default
location ~ \.php$ {
php-fpm 的配置檔案像這個樣子
/etc/php/7.3/fpm/pool.d/www.conf
listen=127.0.0.1:9000
Unix Socket
unix socket 其實嚴格意義上應該叫 unix domain socket,它是 unix 系統程式間通訊(IPC)的一種被廣泛採用方式,以檔案(一般是.sock)作為 socket 的唯一標識(描述符),需要通訊的兩個程式引用同一個 socket 描述符檔案就可以建立通道進行通訊了。
具體原理這裡就不講了,但是此通訊方式的效能會優於 TCP
/etc/nginx/sites-available/default
location~\.php${
/etc/php/7.3/fpm/pool.d/www.conf
listen = /run/php/php7.3-fpm.sock
php-fpm 未授權漏洞
既然 nginx 通過 fastcgi 協議與 php-fpm 通訊。那麼理論上,我們可以偽造 fastcgi 協議包,欺騙 php-fpm 程式,從而執行任意程式碼
這裡有個 bug 的地方是,除 disable_function 以外的大部分 php 配置,都可以在協議包裡面更改,包括 php 手冊規定的:
《php.ini 配置選項列》
fastcgi 協議中只可以傳輸配置資訊及需要被執行的檔名及客戶端傳進來的 get,post,cookie 等資料。看上去我們即使能傳輸任意協議包也不能任意程式碼執行,但是我們可以通過更改配置資訊來執行任意程式碼
auto_prepend_file
auto_prepend_file
是告訴 PHP,在執行目標檔案之前,先包含auto_prepend_file
中指定的檔案,並且auto_prepend_file可以使用php偽協議
我們先把配置auto_prepend_file
修改為 php://input
php://input
php://input
是客戶端所有的 POST 資料, 因為 fastcgi 協議中可以控制資料段 POST 資料所以這樣就可以包含 post 傳進來的資料,但是 php://input
需要開啟allow_url_include
,官方手冊雖然這個配置規定只能在 php.ini
中修改,但是 bug 的是,fastcgi 協議的 PHP_ADMIN_VALUE 選項可以修改幾乎所有的配置,所以通過 PHP_ADMIN_VALUE 把 allow_url_include 修改為 True,這樣就可以通過 fastcgi 協議任意程式碼執行
檔名
但是還需要注意的是,我們需要知道一個服務端已知的php檔案,因為php-fpm拿到fastcgi資料包後,先回去判斷客戶端請求的檔案是否存在(即fastcgi資料包中的檔名),如果不存在就不會執行,並且由於security.limit_extensions這個配置,檔名必須是php字尾。如果服務端解析php,直接猜絕對路徑用/var/www/html/index.php就行了,但是如果不知道Web的絕對路徑或者web目錄下沒有php檔案,就可以指定一些php預設安裝就存在的php檔案 可以使用find / -
比如/usr/local/lib/php/PEAR.php
或 /usr/share/php/PEAR.php
接下來就可以用別人的指令碼啦!
指令碼(來源於 p 師傅)
import socket
用法:
python exp.py -c phpcode -p port host filename
比如:
python exp.py -c "" 127.0.0.1 /var/www/html/test.php`
post 預設為 9000 埠,這樣就可以攻擊未授權任意程式碼執行啦!
SSRF+Gopher
除了攻擊未授權,現在大部分 php-fpm 應用都是繫結在 127.0.0.1,所以我們當然可以通過 SSRF 來攻擊 php-fpm,我改了一下 p 師傅的指令碼,暫時只能用 python2 跑:
import socket
用法跟上面的一樣,只不過會直接返回給你 gopher 的 exp,如果是 php 頁面存在 ssrf 漏洞記得生成 exp 還要在 Url 編一次碼,因為 exp 進入 web 伺服器會解一次碼,php 在請求一次又會解一次碼。
當然也可以使用(Gopherus):
這個工具也是特別好用,並且支援生成 gopher 打多種服務的 exp,當然這裡主講 php-fpm,不知道的可以去了解
攻擊套接字
上面講的都是 php-fpm 通過 TCP 方式與 nginx 連線,那如果 php-fpm 通過 unix 套接字與 nginx 連線該怎麼辦 接下來請欣賞 php 的魔法
<?php
預設套接字的位置在 /run/php/php7.3-fpm.sock
(7.3 是 php 版本號)
當然,如果不在的話可以通過預設 /etc/php/7.3/fpm/pool.d/www.conf
配置檔案檢視套接字路徑 或者 TCP 模式的埠號。
當然,如果採用套接字的方式連線,我們暫時不能使用 ssrf 來攻擊 php-fpm,只能通過 linux 的資料流來進行資料傳遞,相對於 tcp 還是比較安全的 exp 的話,把上面那個 exp 的最後三行改下就行了,如果是 base64 資料傳輸換成 base64encode,如果直接傳的話把 gopher 的字串去掉
*CTF echohub
從 echohub 這道題來說,題目環境裝了 apache 伺服器和 apache-module 模式的 php 模組,並且題目環境就是以 apache-module 執行的 php,但是環境也安裝了 php-fpm,並且最後還啟動了所有的服務
什麼意思,就是我題目環境除了 apache-module 的 php,還要裝一個 php-fpm,相當於伺服器有兩個 php 環境,apache 使用的是 module 的 php,另一個 php-fpm 雖然開啟但是是一個無用的程式沒有 web 伺服器與他通訊
題目的 web 環境也就是 apache-module 的 php 的 disable_function 限制的特別死無法執行系統命令,於是呢就可以通過攻擊另一個 php 環境來執行系統命令(不同的 php 環境使用不一樣的配置檔案)
這道題 exp 就是上面攻擊套接字那個,通過攻擊另一個沒啥限制的 php-fpm 來執行系統命令
預設安裝的 php-fpm 如果不做改動的話,都是預設以套接字方式進行通訊(php7 以上都是)
ubuntu 安裝 php
如何安裝 php 可能可以幫助深入理解一下這個過程
如何安裝 apache-module
apt update
安裝 nginx + fastcgi
apt update
然後:
vim /etc/nginx/sites-enabled/default
把 56 開始註釋掉,然後選擇你想要的連線模式,註釋 60 行或者 62 行,socket 的話記得更改後面 php7.0 為對應版本號:
如果需要 TCP 模式,把 listen = /run/php/php7.3-fpm.sock
替換為 listen = 127.0.0.1:9000
,然後
/etc/init.d/php7.3-fpm start# php-fpm 是一個獨立的程式,需要單獨啟動
service nginx start