php后台增加权限控制和XSS

  • 背景
    最近在对接某大厂,部署差不多了,但是在漏洞扫描环节有问题,前端是用vue代码写的。后端是php。发现前端路由可以拦截未登录的url。但是后端php接口不用登录就能访问,很危险

  • 解决方法

一、创建 Auth 中间件
首先创建一个专门用于验证 Session 的中间件: 里面可以放开不用登录的接口

<?php
// 目录位置 app/middleware/Auth.php
namespace app\middleware;

use think\facade\Session;
use think\Response;

class Auth
{
    // 定义不需要验证的接口路径
    protected $exceptPaths = [
        'Auth/login',       // 登录接口
        'Auth/logout',    // 注册接口
        'Auth/getCode',    // 注册接口
        'Index/getTitle',    // 注册接口
        // 可添加更多无需验证的接口...
    ];
    public function handle($request, \Closure $next)
    {
        // 获取当前请求的路径(不含域名和参数)
        $path = $request->pathinfo();

        // 检查是否为排除的路径
        if ($this->shouldPassThrough($path)) {
            return $next($request); // 跳过验证,直接继续
        }
        // 检查Session中是否有登录用户信息
        if (!Session::has(SESSION_LOGIN_KEY)) {
            // 未登录,返回JSON错误响应
            return json([
                'code' => 0,
                'msg' => '请先登录',
                'data' => 401,
                'url' => null,
                'wait' => 3
            ]);
        }

        // 已登录,将用户信息注入请求对象,方便后续使用
        $request->user = Session::get(SESSION_LOGIN_KEY);

        // 继续执行后续请求处理
        return $next($request);
    }

    // 判断请求是否应跳过验证
    protected function shouldPassThrough($path)
    {
        foreach ($this->exceptPaths as $except) {
            if (strpos($path, $except) === 0) {
                return true;
            }
        }
        return false;
    }
}

二、注册中间件
有两种方式注册中间件,根据你的需求选择:

  1. 全局中间件(所有请求都验证)
    打开 目录位置app/middleware.php 文件,添加中间件:
// app/middleware.php
return [
    // 其他中间件...
    \app\middleware\Auth::class,
];
  1. 路由中间件(按需验证)
    如果你只想验证部分接口,在路由定义中使用中间件:
// app/route/route.php
use think\facade\Route;

// 应用Auth中间件到整个Video控制器组
Route::group('video', function () {
    Route::get('getIqiyiLists', 'Video/getIqiyiLists');
    Route::get('getSohuLists', 'Video/getSohuLists');
    // 其他接口...
})->middleware(\app\middleware\Auth::class);

// 或者只应用到特定接口
Route::get('video/getIqiyiLists', 'Video/getIqiyiLists')
    ->middleware(\app\middleware\Auth::class);

--------------------------------分割线------------------------------
针对ThinkPHP 3.2.x 老版本的实现
行为扩展的实现步骤

  1. 创建行为类文件
    在项目目录下创建以下文件:
/application
    /Common
        /Behavior
            - CheckLoginBehavior.php  # 行为类文件
    /Conf
        - behavior.php  # 行为配置文件
  1. 编写行为类代码
    CheckLoginBehavior.php 文件内容:
<?php
namespace Common\Behavior;
use Think\Behavior;

class CheckLoginBehavior extends Behavior {
    public function run(&$params) {
        // 检查是否登录
        if(empty(session(SESSION_LOGIN_KEY))) {
            // 获取当前请求的控制器和操作
            $controller = CONTROLLER_NAME;
            $action = ACTION_NAME;
            
            // 定义白名单(无需登录即可访问的控制器/操作)
            $whiteList = [
                'Auth/login',
                'Auth/logout',
                'User/login',
                'User/logout',
                'User/getCode'
            ];
            
            // 检查当前请求是否在白名单中
            $currentUrl = "$controller/$action";
            if(!in_array($currentUrl, $whiteList)) {
                // 设置响应头为JSON格式
                header('Content-Type:application/json;charset=utf-8');
                
                // 输出统一的错误响应
                exit(json_encode([
                    'code' => 401,
                    'msg' => '未登录,请先登录',
                    'data' => null
                ]));
            }
        }
    }
}
  1. 配置行为扩展
    在 application/Conf/behavior.php 中添加以下内容:
<?php
// 行为扩展配置
return [
    // 在应用结束时执行登录检查
    'app_end' => ['Common\\Behavior\\CheckLoginBehavior']
];

行为扩展增加XSS
也是在行为扩展里面增加的

<?php
// application/Behavior/XssCheckBehavior.php
namespace Behavior;
use Think\Behavior;

class XssCheckBehavior extends Behavior {
    
    // 行为执行入口
    public function run(&$params) {
        // 只对POST请求进行XSS检测
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            // 获取原始请求数据
            $rawData = file_get_contents('php://input');
            
            // 强制解析JSON数据(无论Content-Type如何)
            $postData = json_decode($rawData, true);
            
            // 检查JSON解析是否成功
            if (json_last_error() !== JSON_ERROR_NONE || empty($postData)) {
                // JSON解析失败,尝试从$_POST获取
                $postData = $_POST;
                
                // 如果$_POST也为空,使用原始数据
                if (empty($postData)) {
                    $postData = ['raw_data' => $rawData];
                }
            }
            
            // 检查数据
            if (!empty($postData) && !$this->checkPostData($postData)) {
                $this->returnError();
            }
        }
    }
    
    // 检查POST数据
    private function checkPostData($data, $parentKey = '') {
        foreach ($data as $key => $value) {
            $fullKey = $parentKey ? "{$parentKey}[{$key}]" : $key;
            
            if (is_array($value)) {
                // 递归检查数组
                if (!$this->checkPostData($value, $fullKey)) {
                    return false;
                }
            } else {
                // 检查字符串值
                if ($this->containsXss($value)) {
                    \Think\Log::record("XSS检测到风险: {$fullKey} => {$value}", 'ERR');
                    return false;
                }
            }
        }
        
        return true;
    }
    
    // 检测是否包含XSS风险
    private function containsXss($str) {
        // 空值不检测
        if (empty($str)) {
            return false;
        }
        
        // 检测常见的XSS模式
        $patterns = [
            '/<script/i',                  // 直接的script标签
            '/<[^>]*on\w+ *=/i',           // 事件处理函数
            '/javascript:/i',              // javascript协议
            '/vbscript:/i',                // vbscript协议
            '/<iframe/i',                  // iframe标签
            '/<style/i',                   // style标签
            '/<meta/i',                    // meta标签
            '/<link/i',                    // link标签
            '/data:text\/html/i',          // data URI
            '/<svg/i',                     // SVG注入
            '/&#[xX]?[0-9a-fA-F]+;/i',     // HTML实体编码
        ];
        
        foreach ($patterns as $pattern) {
            if (preg_match($pattern, $str)) {
                return true;
            }
        }
        
        return false;
    }
    
    // 返回错误信息
    private function returnError() {
        if (IS_AJAX) {
            // AJAX请求返回JSON错误
            $result = [
                'status' => 0,
                'info' => '提交内容包含不安全字符,请检查!'
            ];
            exit(json_encode($result));
        } else {
            // 普通请求跳转
            $this->error('提交内容包含不安全字符,请检查!');
        }
    }
}
  • 总结
    大概就是这样玩的。ThinkPHP 的中间件机制还是挺不错。后续你还可以加入权限的控制之类的。如果发现博文有问题,欢迎老鸟指点一二
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hello Bug

谢谢老板,老板大气,老板硬邦邦

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值