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; }