rewrite / try_files
都是對 $uri(不包含 $query_string)
進行處理,但 rewrite
會保持原請求 $query_string
,try_files
會丟棄,這也是爲何 try_files
重寫時,一般都會加上 $query_string
。php
location / { if (!-e $request_filename) { # rewrite 處理的是 $uri,^(.*)$ 匹配出來的 $1 其實就是 $uri rewrite ^(.*)$ /index.php$1 last; rewrite ^(.*)$ /index.php$uri last; # 這樣寫也沒區別 break; } # 非 pathinfo 重寫 # 適用於使用 $_SERVER['request_uri'] 中的路徑作路由解析的框架 laravel/yii2 # 因此重寫不傳遞 $uri 也不要緊 # 但必須得加上 $is_args$query_string,不然 $_GET 就空了 try_files $uri $uri/ /index.php$is_args$query_string; # pathinfo 重寫 # 適用於使用 $_SERVER['path_info'] 作路由解析的框架 thinkphp # $is_args$query_string 能夠不加 # 由於規範的 pathinfo 要求參數也路徑化在 $uri 中了 # 不須要 $_GET 參數 try_files $uri $uri/ /index.php$uri$is_args$query_string; }
這個簡單,網站的根目錄,沒什麼nginx
server { root /home/wwwroot/site/public; }
請求的資源定位符。即你在瀏覽器中輸入的原版 url
(去掉主機),$request_uri
在整個請求會話中是固定不變的($uri
可能會由於重寫規則被 nginx
從新定義,但$request_uri
不會變)。注意:請求資源定位符是包含 queryString
的修飾的。laravel
/index.php/news/index?p=1&ps=10 /news/index?p=1&ps=10
請求的資源名,和資源定位符$request_uri
的區別是,資源定位符只是一個 symbol
,可能會被映射重寫,$uri
則是 nginx
對 $request_uri
解析後所的出的資源名。thinkphp
$uri = 不帶 $query_string 參數的請求的最終資源名
,由於 nginx
可能會對你的 $request_uri
進行重寫,起初的 $uri
= $request_uri
,但重寫處理後,最終的 $uri
可能就與 $request_uri
不一樣了,因此 $uri
在沒有發生重寫時就是 $request_uri
去掉可能攜帶的 $query_string
,發生了重寫處理就要看重寫後最終的資源名了。瀏覽器
# 重寫規則 location / { try_files $uri $uri/ /index.php$uri; } location ~ [^/]\.php(/|$) { .... } # 直接命中 location 沒有發生重寫 /index.php/news/index?p=1&ps=10 $request_uri = /index.php/news/index?p=1&ps=10 $uri = /index.php/news/index # 命中了 / 觸發了 try_files 中的重寫 /news/index?p=1&ps=10 一、$request_uri = $uri = /news/index?p=1&ps=10 二、命中 location / { try_files $uri $uri/ /index.php$uri; } 三、重寫解析 四、$request_uri = /news/index?p=1&ps=10 五、$uri = /index.php/news/index
$query_string / $args & $is_args
url
的 queryString
參數,$query_string
與 $args
與其徹底一致,$is_args
是友好的表示是否攜帶了 queryString
,攜帶爲'?'
未攜帶 ''
。服務器
/news/list?p=1&ps=10 $query_string = $args = p=1&ps=10 $is_args = ? # 重寫時使用 $is_args 追加 $query_string 更爲規範 location / { try_files $uri $uri/ /index.php$is_args$query_string; }
計算表達式:$request_filename = $document_root$uri
請求的資源文件路徑,這個變量是在你的 $request_uri
被解析處理好後獲得了最終的 $uri
,才結合 $document_root
生成的。yii2
server { root /home/wwwroot/site/public/index.php/news/list; } /index.php/new/list?p=1&ps=10 $request_uri = /index.php/news/list?p=1&ps=10 $uri = /index.php/new/list $request_filename = $document_root$uri = /home/wwwroot/site/public/index.php/news/list
這兩個變量放一塊兒說比較好,默認狀況下,$fastcgi_script_name
= $uri
,但咱們的 url
爲了美觀大都採用了 pathinfo
風格。因此,若是直接把 /index.php/news/index
傳遞給 php
,那 $_SERVER['SCRIPT_NAME'] = '/index.php/news/index'
了。而通過了 pathinfo
處理:框架
# fastcgi_split_path_info 指令會作以下處理 # 將 $1 賦值給 $fastcgi_script_name # 將 $2 賦值給 $fastcgi_path_info # 這樣咱們就得到可真正要執行的 php 腳本名 腳本文件名 和 腳本後攜帶的路徑 fastcgi_split_path_info ^(.+?\.php)(/.+)$; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
一、請求:/news/index?p=1&ps=10 $request_uri = /news/index?p=1&ps=10 $uri = /news/index?p=1&ps=10 二、重寫:/index.php/news/index?p=1&ps=10 $uri = /index.php/news/index $request_filename = /home/wwwroot/site/public/index.php/news/index $fastcgi_script_name = $uri 三、pahtinfo 解析 fastcgi_split_path_info ^(.+?\.php)(/.+)$; $fastcgi_script_name = /index.php $fastcgi_path_info = /news/index # 給 php 的一些 $_SERVER 變量填充 fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
咱們就可使用 $_SERVER['PATH_INFO']
作路由和參數處理了。yii
如今不少開發者可能直接使用框架入門,框架給予效率的同時,也可能讓開發者們忽略的不少底層細節,好比對於單入口文件類型的框架,全部請求的資源文件其實都只有 index.php
,而框架負責將你輸入的 "資源路徑"
進行解析,路由,處理。函數
常規的 url
/index.php?ctrl=news&action=index&p=1&ps=10
框架實際理解和處理的 url
則如上,框架最終總會把你的 url
解析映射至相應的 controller
& action
。nginx
的 $uri
是 /index.php
。
資源化的 url
/index.php/news/index?p=1&ps=10 /news/index?p=1&ps=10
框架:yii2
/ laravel
。請求資源 路徑
化,查詢參數
仍是常規方式。對於 seo 的爬蟲來講,服務上存在一個資源:/news/index
,nginx
的 $uri
是 /index.php/news/index
或 /news/index
,比 常規的 url
更爲友好。
pathinfo 化的 url
/news/index/p/1/ps/10
框架:thinkphp
。查詢參數也 路徑
化,對於 seo 的爬蟲來講,服務上存在一個資源:/news/index/p/1/ps/10
,nginx
的 $uri
是 /news/index/p/1/ps/10
。
pathinfo
的 url
模式,thinkphp
徹底支持,傳入和生成 url
都不須要作特別的處理。yii2
/ laravel
的 路由聲明
並不是 100% 支持,沒辦法作到 queryString
自動 pathinfo
。對於 yii2
/ laravel
來講,queryString
參數也 pathinfo
化,但 路由聲明
在一些場景下會很冗餘(參數少而固定還好,多且不固定就尷尬了)。
TP
有 pathinfo
模式的 url
,能夠解析和處理 pathinfo
化的 queryString
,使用框架的 url
助手方法生成 pathinfo
風格的連接。
爲何說 TP
對 pathinfo
很推崇呢,官方給出的 url
重寫規則就是不攜帶 queryString
的 try_files
重寫。因此,TP
是在告訴你,queryString
參數也要 pathinfo
化到 $uri
中。
# pathinfo 模式的重寫規則 location / { # try_files 本質上不是重寫,你傳什麼它請求什麼,這裏只傳了 $uri,沒有 $query_string # 那請求再入時,$query_string 就是空的了 try_files $uri $uri/ /index.php$uri; # 兼容老版本的 nginx if (!-e $request_filename) { # 二選一 沒區別 # rewrite 只處理 $uri(沒有$query_string) # ^(.+)$ 正則的也是 $uri 因此 $1 裏沒有 $query_string # 但 rewrite 會隱式保持請求上下文裏的 $query_string 即使重寫沒傳遞 # 在重寫後的請求裏 $query_string 仍是有的 rewrite ^(.+)$ /index.php$1 last; break; rewrite $uri /index.php$uri last; break; } }
url
助手函數生成的連接風格
/?s=/news/index&p=1&ps=10 //普通模式 /news/index/p/1/ps/10 //pathinfo模式
Yii2
的 prettyUrl
並不是徹底的 pathinfo
模式,只是把請求的"資源文件"
路徑化,資源文件的描述參數 queryString
依然仍是使用常規模式。
prettyUrl 模式的重寫規則
location / { # try_files 必需要加上 $query_string 不加 get 參數就丟了 try_files $uri $uri/ /index.php$is_args$query_string; # 兼容老版本的 nginx # rewrite 加不加 $query_string 都同樣 上下文會保持 if (!-e $request_filename) { rewrite ^(.+)$ /index.php last; break; rewrite $uri /index.php last; break; } }
有沒有注意到 yii2
非必須攜帶 $uri
轉發?下面的 laravel
也是如此。由於 yii2
/laravel
的路由解析的是 $request_uri
,重寫的 url
只是用來把 queryString
傳遞給框架,框架內部會使用 nginx::$request_uri => php::$_SERVER['REQUEST_URI']
中的路徑信息進行路由解析。
/news/index?p=1&ps=10 nginx::$request_uri = /news/index?p=1&ps=10 php::$_SERVER['REQUEST_URI'] = /news/index?p=1&ps=10 $routePath = '/news/index' $routeCtrl = 'news' $routeAction = 'index' $requestQueryString = $_GET
'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false,// Url 助手生成連接時隱藏 /index.php 'enableStrictParsing' => true, 'rules' => [ [ 'class' => 'yii\rest\UrlRule', 'controller' => ['v1/news'], 'pluralize' => false,//自動複數 ], 'GET /<controller:\w+>' => '<controller>/index', 'GET /<controller:\w+>/<id:\d+>' => '<controller>/detail', 'POST /<controller:\w+>' => '<controller>/create', 'PUT /<controller:\w+>/<id:\d+>' => '<controller>/createOrUpdate', 'PATCH /<controller:\w+>/<id:\d+>' => '<controller>/update', 'DELETE /<controller:\w+>/<id:\d+>' => '<controller>/delete', 'OPTIONS /<controller:\w+>/<id:\d+>' => '<controller>/options', 'HEAD /<controller:\w+>/<id:\d+>' => '<controller>/head', '/' => 'site/default', // default route ] ]
url
助手函數生成的連接風格
Url::to(["/news/index", "p" => 1, "ps" => 10]) // enablePrettyUrl = false /index.php?r=news/index&p=1&ps=20 // enablePrettyUrl = true /news/index?p=1&ps=10
Laravel
同 yii2
相似,並不是 yii2
/ laravel
不能 pathinfo
,而是說 TP
對 pathinfo
親和力比較高,能夠友好的解析和生成 url
,yii2
/ laravel
須要聲明 url
,若是咱們想 pathinfo
化的參數比較多,那聲明 url
就比較坑了。
// yii2 /new/index/1/10 'GET <controller:\w+>/<action:\w+>/<p:\d+>/<ps:\d+>' => '<controller>/<action>' // laravel /new/index/1/10 laravel Route::get('/news/index/{p?}/{ps?}', function ($p = 1, $ps = 10) { })->where(['p' => '\d+', 'ps' => '\d+'])->name('news.index'); // /news/index/2 url('news.index', ['p' => 2]);
簡單講一下 php
的 cgi.fix_pathinfo = 1
時潛在的漏洞,思考下面的url
在沒有過多處理(估計如今很多線上的網站都沒過多處理)會觸發什麼場景
# 在上傳目錄 /uploads/ 下上傳了一張把 index.php 改成 index.jpg 的 "圖片" hackUrl: /uploads/index.jpg/hack.php/your/site # location 規則 location ~ [^/]\.php(/|$) { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi.conf; include pathinfo.conf; }
這個 hackUrl
是能夠命中給出的 location
規則的,hack.php
的目的就是被上面的 location
規則捕獲。
而後 nginx
解析獲得的 script_name
和 pathinfo
分別是
fastcgi_split_path_info ^(.+?\.php)(/.+)$; SCRIPT_NAME /uploads/index.jpg/hack.php PATH_INFO /your/site
fastcgi
將 SCRIPT_NAME
傳遞給 php
後,php
發現其並不是有效的腳本文件,fix_pathinfo = 1
時,自動修正 pathinfo
,開始以下嘗試
/uploads/index.jpg/hack.php splitTo [/uploads, /index.jpg, /hack.php] 一、/uploads 是個目錄,不是文件,不是腳本 二、/uploads/index.jpg 是個文件,那就是要執行的腳本了,後面的都是 pathinfo 三、而後你的 php 就把 index.jpg 當作腳本給執行了,index.jpg 裏的 php 代碼操做權限但是 站點root 級別的
修復漏洞
一、這種路徑尋址在 nginx 層就能攔截掉$request_filename
最終會指向一個肯定的 php
腳本資源,咱們把其中的 php
腳本文件名提取出來,若是服務器上不存在這個 php
文件就當即返回。這樣就能夠關閉惡意使用攜帶 .php
的 url
去觸發捕獲了。
location ~ [^/].php(/|$) { if ($request_filename ~* (.*\.php)) { set $php_script_name $1; if (!-e $php_script_name) { return 403; } } .... }
二、或者定義 location
規則,保護上傳目錄
location ~* /uploads/(.*\.php)([/|$]) { return 403; }