From 4cb2be127d5a9fd4a964aea1d7d5d92f639bc99a Mon Sep 17 00:00:00 2001 From: "Mr.Chung" <39075420+zhongshaofa@users.noreply.github.com> Date: Wed, 15 Sep 2021 02:08:49 +0800 Subject: [PATCH] Hotfix/csrf (#102) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [fix]完善csrf过滤功能 * [fix]修改提示信息 * [feat]兼容CK编辑器上传功能 * [fix]删除多余文件 * [fix]删除调试代码 --- .example.env | 2 +- app/admin/controller/Ajax.php | 2 + app/admin/controller/Index.php | 2 + app/admin/controller/mall/Goods.php | 2 +- app/admin/controller/system/Admin.php | 7 ++- app/admin/controller/system/Auth.php | 1 + app/admin/controller/system/Config.php | 1 + app/admin/controller/system/Menu.php | 6 ++- app/admin/controller/system/Node.php | 2 + app/admin/middleware.php | 3 ++ app/admin/middleware/CsrfMiddleware.php | 53 ++++++++++++++++++++ app/admin/traits/Curd.php | 6 ++- app/admin/view/layout/default.html | 1 + app/admin/view/mall/goods/add.html | 1 - app/common/controller/AdminController.php | 10 ++++ public/static/plugs/ckeditor4/ckeditor.js | 2 +- public/static/plugs/easy-admin/easy-admin.js | 11 ++++ 17 files changed, 102 insertions(+), 10 deletions(-) create mode 100644 app/admin/middleware/CsrfMiddleware.php diff --git a/.example.env b/.example.env index daf0fff0..ee1d0bbe 100644 --- a/.example.env +++ b/.example.env @@ -1 +1 @@ -APP_DEBUG=true [APP] DEFAULT_TIMEZONE=Asia/Shanghai [DATABASE] TYPE=mysql HOSTNAME=host.docker.internal DATABASE=easyadmin USERNAME=root PASSWORD=root HOSTPORT=3306 CHARSET=utf8 DEBUG=true PREFIX=ea_ [LANG] default_lang=zh-cn # 后台配置项组 [EASYADMIN] # 后台地址后缀名称 ADMIN=admin # 后台登录验证码开关 CAPTCHA=true # 是否为演示环境 IS_DEMO=true # CDN配置项组 CDN= EXAMPLE=true # 静态文件路径前缀 STATIC_PATH=/static # OSS静态文件路径前缀 OSS_STATIC_PREFIX=static_easyadmin \ No newline at end of file +APP_DEBUG=true [APP] DEFAULT_TIMEZONE=Asia/Shanghai [DATABASE] TYPE=mysql HOSTNAME=host.docker.internal DATABASE=easyadmin USERNAME=root PASSWORD=root HOSTPORT=3306 CHARSET=utf8 DEBUG=true PREFIX=ea_ [LANG] default_lang=zh-cn # 后台配置项组 [EASYADMIN] # 后台地址后缀名称 ADMIN=admin # 后台登录验证码开关 CAPTCHA=true # 是否为演示环境 IS_DEMO=true # CDN配置项组 CDN= EXAMPLE=true # 是否开启CSRF过滤 IS_CSRF=true # 静态文件路径前缀 STATIC_PATH=/static # OSS静态文件路径前缀 OSS_STATIC_PREFIX=static_easyadmin \ No newline at end of file diff --git a/app/admin/controller/Ajax.php b/app/admin/controller/Ajax.php index c30aa46a..ff3b579a 100644 --- a/app/admin/controller/Ajax.php +++ b/app/admin/controller/Ajax.php @@ -63,6 +63,7 @@ public function clearCache() */ public function upload() { + $this->checkPostRequest(); $data = [ 'upload_type' => $this->request->post('upload_type'), 'file' => $this->request->file('file'), @@ -96,6 +97,7 @@ public function upload() */ public function uploadEditor() { + $this->checkPostRequest(); $data = [ 'upload_type' => $this->request->post('upload_type'), 'file' => $this->request->file('upload'), diff --git a/app/admin/controller/Index.php b/app/admin/controller/Index.php index 62acda4c..78851884 100644 --- a/app/admin/controller/Index.php +++ b/app/admin/controller/Index.php @@ -49,6 +49,7 @@ public function welcome() */ public function editAdmin() { + $this->checkPostRequest(); $id = session('admin.id'); $row = (new SystemAdmin()) ->withoutField('password') @@ -81,6 +82,7 @@ public function editAdmin() */ public function editPassword() { + $this->checkPostRequest(); $id = session('admin.id'); $row = (new SystemAdmin()) ->withoutField('password') diff --git a/app/admin/controller/mall/Goods.php b/app/admin/controller/mall/Goods.php index 3cc6bac8..b353511b 100644 --- a/app/admin/controller/mall/Goods.php +++ b/app/admin/controller/mall/Goods.php @@ -67,7 +67,7 @@ public function stock($id) { $row = $this->model->find($id); empty($row) && $this->error('数据不存在'); - if ($this->request->isAjax()) { + if ($this->request->isPost()) { $post = $this->request->post(); $rule = []; $this->validate($post, $rule); diff --git a/app/admin/controller/system/Admin.php b/app/admin/controller/system/Admin.php index ed40b092..17331d1e 100644 --- a/app/admin/controller/system/Admin.php +++ b/app/admin/controller/system/Admin.php @@ -78,7 +78,7 @@ public function index() */ public function add() { - if ($this->request->isAjax()) { + if ($this->request->isPost()) { $post = $this->request->post(); $authIds = $this->request->post('auth_ids', []); $post['auth_ids'] = implode(',', array_keys($authIds)); @@ -101,7 +101,7 @@ public function edit($id) { $row = $this->model->find($id); empty($row) && $this->error('数据不存在'); - if ($this->request->isAjax()) { + if ($this->request->isPost()) { $post = $this->request->post(); $authIds = $this->request->post('auth_ids', []); $post['auth_ids'] = implode(',', array_keys($authIds)); @@ -128,6 +128,7 @@ public function edit($id) */ public function password($id) { + $this->checkPostRequest(); $row = $this->model->find($id); empty($row) && $this->error('数据不存在'); if ($this->request->isAjax()) { @@ -159,6 +160,7 @@ public function password($id) */ public function delete($id) { + $this->checkPostRequest(); $row = $this->model->whereIn('id', $id)->select(); $row->isEmpty() && $this->error('数据不存在'); $id == AdminConstant::SUPER_ADMIN_ID && $this->error('超级管理员不允许修改'); @@ -180,6 +182,7 @@ public function delete($id) */ public function modify() { + $this->checkPostRequest(); $post = $this->request->post(); $rule = [ 'id|ID' => 'require', diff --git a/app/admin/controller/system/Auth.php b/app/admin/controller/system/Auth.php index 57235f00..970fdb5a 100644 --- a/app/admin/controller/system/Auth.php +++ b/app/admin/controller/system/Auth.php @@ -62,6 +62,7 @@ public function authorize($id) */ public function saveAuthorize() { + $this->checkPostRequest(); $id = $this->request->post('id'); $node = $this->request->post('node', "[]"); $node = json_decode($node, true); diff --git a/app/admin/controller/system/Config.php b/app/admin/controller/system/Config.php index e6b727ad..1c959b35 100644 --- a/app/admin/controller/system/Config.php +++ b/app/admin/controller/system/Config.php @@ -47,6 +47,7 @@ public function index() */ public function save() { + $this->checkPostRequest(); $post = $this->request->post(); try { foreach ($post as $key => $val) { diff --git a/app/admin/controller/system/Menu.php b/app/admin/controller/system/Menu.php index 9bc30f17..3dabc9e1 100644 --- a/app/admin/controller/system/Menu.php +++ b/app/admin/controller/system/Menu.php @@ -77,7 +77,7 @@ public function add($id = null) if ($id == $homeId) { $this->error('首页不能添加子菜单'); } - if ($this->request->isAjax()) { + if ($this->request->isPost()) { $post = $this->request->post(); $rule = [ 'pid|上级菜单' => 'require', @@ -110,7 +110,7 @@ public function edit($id) { $row = $this->model->find($id); empty($row) && $this->error('数据不存在'); - if ($this->request->isAjax()) { + if ($this->request->isPost()) { $post = $this->request->post(); $rule = [ 'pid|上级菜单' => 'require', @@ -144,6 +144,7 @@ public function edit($id) */ public function delete($id) { + $this->checkPostRequest(); $row = $this->model->whereIn('id', $id)->select(); empty($row) && $this->error('数据不存在'); try { @@ -164,6 +165,7 @@ public function delete($id) */ public function modify() { + $this->checkPostRequest(); $post = $this->request->post(); $rule = [ 'id|ID' => 'require', diff --git a/app/admin/controller/system/Node.php b/app/admin/controller/system/Node.php index 4faeb603..fdc6f3fb 100644 --- a/app/admin/controller/system/Node.php +++ b/app/admin/controller/system/Node.php @@ -66,6 +66,7 @@ public function index() */ public function refreshNode($force = 0) { + $this->checkPostRequest(); $nodeList = (new NodeService())->getNodelist(); empty($nodeList) && $this->error('暂无需要更新的系统节点'); $model = new SystemNode(); @@ -102,6 +103,7 @@ public function refreshNode($force = 0) */ public function clearNode() { + $this->checkPostRequest(); $nodeList = (new NodeService())->getNodelist(); $model = new SystemNode(); try { diff --git a/app/admin/middleware.php b/app/admin/middleware.php index 95e33e89..56b68978 100644 --- a/app/admin/middleware.php +++ b/app/admin/middleware.php @@ -8,6 +8,9 @@ // 系统操作日志 \app\admin\middleware\SystemLog::class, + // Csrf安全校验 + \app\admin\middleware\CsrfMiddleware::class, + // 后台视图初始化 // \app\admin\middleware\ViewInit::class, diff --git a/app/admin/middleware/CsrfMiddleware.php b/app/admin/middleware/CsrfMiddleware.php new file mode 100644 index 00000000..6ba1aed4 --- /dev/null +++ b/app/admin/middleware/CsrfMiddleware.php @@ -0,0 +1,53 @@ +method(), ['GET', 'HEAD', 'OPTIONS'])) { + + // 跨域校验 + $refererUrl = $request->header('REFERER', null); + $refererInfo = parse_url($refererUrl); + $host = $request->host(); + if (!isset($refererInfo['host']) || $refererInfo['host'] != $host) { + $this->error('当前请求不合法!'); + } + + // CSRF校验 + // @todo 兼容CK编辑器上传功能 + $ckCsrfToken = $request->post('ckCsrfToken', null); + $data = !empty($ckCsrfToken) ? ['__token__' => $ckCsrfToken] : []; + + $check = $request->checkToken('__token__', $data); + if (!$check) { + $this->error('请求验证失败,请重新刷新页面!'); + } + + } + } + return $next($request); + } +} \ No newline at end of file diff --git a/app/admin/traits/Curd.php b/app/admin/traits/Curd.php index 146db389..fcd0f0ab 100644 --- a/app/admin/traits/Curd.php +++ b/app/admin/traits/Curd.php @@ -60,7 +60,7 @@ public function index() */ public function add() { - if ($this->request->isAjax()) { + if ($this->request->isPost()) { $post = $this->request->post(); $rule = []; $this->validate($post, $rule); @@ -81,7 +81,7 @@ public function edit($id) { $row = $this->model->find($id); empty($row) && $this->error('数据不存在'); - if ($this->request->isAjax()) { + if ($this->request->isPost()) { $post = $this->request->post(); $rule = []; $this->validate($post, $rule); @@ -101,6 +101,7 @@ public function edit($id) */ public function delete($id) { + $this->checkPostRequest(); $row = $this->model->whereIn('id', $id)->select(); $row->isEmpty() && $this->error('数据不存在'); try { @@ -143,6 +144,7 @@ public function export() */ public function modify() { + $this->checkPostRequest(); $post = $this->request->post(); $rule = [ 'id|ID' => 'require', diff --git a/app/admin/view/layout/default.html b/app/admin/view/layout/default.html index d774c9b0..6ead6f45 100644 --- a/app/admin/view/layout/default.html +++ b/app/admin/view/layout/default.html @@ -19,6 +19,7 @@ AUTOLOAD_JS: "{$autoloadJs|default='false'}", IS_SUPER_ADMIN: "{$isSuperAdmin|default='false'}", VERSION: "{$version|default='1.0.0'}", + CSRF_TOKEN: "{:token()}", }; diff --git a/app/admin/view/mall/goods/add.html b/app/admin/view/mall/goods/add.html index 69365f85..e323c059 100644 --- a/app/admin/view/mall/goods/add.html +++ b/app/admin/view/mall/goods/add.html @@ -1,6 +1,5 @@
-
diff --git a/app/common/controller/AdminController.php b/app/common/controller/AdminController.php index 10a45550..9cb1bece 100644 --- a/app/common/controller/AdminController.php +++ b/app/common/controller/AdminController.php @@ -280,4 +280,14 @@ private function checkAuth(){ } } + + /** + * 严格校验接口是否为POST请求 + */ + protected function checkPostRequest(){ + if (!$this->request->isPost()) { + $this->error("当前请求不合法!"); + } + } + } \ No newline at end of file diff --git a/public/static/plugs/ckeditor4/ckeditor.js b/public/static/plugs/ckeditor4/ckeditor.js index 87bc6bd2..fe269f20 100644 --- a/public/static/plugs/ckeditor4/ckeditor.js +++ b/public/static/plugs/ckeditor4/ckeditor.js @@ -38,7 +38,7 @@ writeCssText:function(a,b){var e,c=[];for(e in a)c.push(e+":"+a[e]);b&&c.sort(); break}catch(b){a=a?a.replace(/.+?(?:\.|$)/,""):document.domain;if(!a)break;document.domain=a}return!!a},eventsBuffer:function(a,b,e){return new this.buffers.event(a,b,e)},enableHtml5Elements:function(a,b){for(var e="abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup main mark meter nav output progress section summary time video".split(" "),c=e.length,g;c--;)g=a.createElement(e[c]),b&&a.appendChild(g)},checkIfAnyArrayItemMatches:function(a,b){for(var e= 0,c=a.length;ee;e++)a.push(Math.floor(256*Math.random()));for(e=0;ee;e++)a.push(Math.floor(256*Math.random()));for(e=0;en)for(l=n;3>l;l++)g[l]=0;d[0]=(g[0]&252)>>2;d[1]=(g[0]&3)<<4|g[1]>>4;d[2]=(g[1]&15)<<2|(g[2]&192)>>6;d[3]=g[2]&63;for(l=0;4>l;l++)b=l<=n? b+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(d[l]):b+"\x3d"}return b},style:{parse:{_colors:{aliceblue:"#F0F8FF",antiquewhite:"#FAEBD7",aqua:"#00FFFF",aquamarine:"#7FFFD4",azure:"#F0FFFF",beige:"#F5F5DC",bisque:"#FFE4C4",black:"#000000",blanchedalmond:"#FFEBCD",blue:"#0000FF",blueviolet:"#8A2BE2",brown:"#A52A2A",burlywood:"#DEB887",cadetblue:"#5F9EA0",chartreuse:"#7FFF00",chocolate:"#D2691E",coral:"#FF7F50",cornflowerblue:"#6495ED",cornsilk:"#FFF8DC",crimson:"#DC143C", diff --git a/public/static/plugs/easy-admin/easy-admin.js b/public/static/plugs/easy-admin/easy-admin.js index d117fa4d..14968a63 100644 --- a/public/static/plugs/easy-admin/easy-admin.js +++ b/public/static/plugs/easy-admin/easy-admin.js @@ -28,6 +28,9 @@ define(["jquery", "tableSelect", "ckeditor"], function ($, tableSelect, undefine url: function (url) { return '/' + CONFIG.ADMIN + '/' + url; }, + headers: function () { + return {'X-CSRF-TOKEN': window.CONFIG.CSRF_TOKEN}; + }, //js版empty,判断变量是否为空 empty: function (r) { var n, t, e, f = [void 0, null, !1, 0, "", "0"]; @@ -87,6 +90,7 @@ define(["jquery", "tableSelect", "ckeditor"], function ($, tableSelect, undefine type: type, contentType: "application/x-www-form-urlencoded; charset=UTF-8", dataType: "json", + headers:admin.headers(), data: option.data, timeout: 60000, success: function (res) { @@ -102,6 +106,9 @@ define(["jquery", "tableSelect", "ckeditor"], function ($, tableSelect, undefine ex(this); }); return false; + }, + complete: function(){ + // @todo 刷新csrf-token } }); } @@ -188,6 +195,7 @@ define(["jquery", "tableSelect", "ckeditor"], function ($, tableSelect, undefine options.id = options.id || options.init.table_render_id; options.layFilter = options.id + '_LayFilter'; options.url = options.url || admin.url(options.init.index_url); + options.headers = admin.headers(); options.page = admin.parame(options.page, true); options.search = admin.parame(options.search, true); options.skin = options.skin || 'line'; @@ -1299,6 +1307,7 @@ define(["jquery", "tableSelect", "ckeditor"], function ($, tableSelect, undefine url: admin.url(init.upload_url), accept: 'file', exts: exts, + headers:admin.headers(), // 让多图上传模式下支持多选操作 multiple: (uploadNumber !== 'one') ? true : false, done: function (res) { @@ -1417,6 +1426,8 @@ define(["jquery", "tableSelect", "ckeditor"], function ($, tableSelect, undefine } }, editor: function () { + CKEDITOR.tools.setCookie('ckCsrfToken', window.CONFIG.CSRF_TOKEN); + var editorList = document.querySelectorAll(".editor"); if (editorList.length > 0) { $.each(editorList, function (i, v) {