【问题标题】:Optimize a method that checks url segments issue优化检查 url 段问题的方法
【发布时间】:2021-07-01 23:22:09
【问题描述】:

我一直在 Laravel 中间件中使用一种方法来检查任何 URL 段中的字符串,如果它与“黑名单”字符串匹配,则阻止 IP。

一开始,我只有几个字符串要检查,但现在,列表越来越多,当我尝试优化它以使用 blacklist array 时,我最终在代码中一团糟,在我的想法。

我相信这是可以做到的,但无法找出优化此中间件的最佳方法。下面是一个中间件代码示例,其中包含我遇到问题的注释。

handle($request, Closure $next) 方法中为所有列入黑名单的字符串调用$this->inUrl() 方法。

我尝试添加一个protected $blacklisted 数组,用于$this->inUrl(),但无法使其工作。

提前感谢您提出的任何建议,我们将不胜感激和欢迎。我还考虑在优化后将代码作为要点提供在 GitHub 上。

namespace App\Http\Middleware;

/**
 * Class VerifyBlacklistedRequests
 *
 * @package App\Http\Middleware
 */
class VerifyBlacklistedRequests
{

    /**
     * The array of blacklisted request string segments
     *
     * @access protected
     * @var array|string[]
     */
    protected array $blacklisted = [
        '.env', '.ftpconfig', '.vscode', ',git', '.git/HEAD'
        // etc...
    ];

    /**
     * Handle an incoming request.
     *
     * @access public
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure                 $next
     *
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if($this->inUrl('.env')
            || $this->inUrl('.ftpconfig')
            || $this->inUrl('.vscode')
            || $this->inUrl('.git')
            || $this->inUrl('.git/HEAD')
 
           // many more checks below the above ones

        ) {
            // logic that blocks the IP goes here and working fine
        }

        return $next($request);
    }

     /**
     * Check if the string is in any URL segment or at the one specified.
     *
     * @access protected
     *
     * @param string|mixed $value   Segment value/content.
     * @param integer      $segment Segment position.
     *
     * @return bool
     */
    protected function inUrl(string $value, $segment = -1)
    {
        if($segment !== -1 && request()->segment($segment) === $value) {
            return true;
        }

        collect(request()->segments())->each(function ($segment) use ($value) {
            if($segment === $value) {
                return true;
            }
        });

        return false;
    }

}

【问题讨论】:

    标签: laravel security optimization middleware blacklist


    【解决方案1】:

    在所有建议之后,请在这里发布,我最终得到了一个使用一些建议方法的解决方案。

    结果是页面加载时间减少了 1 秒以上。

    我的最终实现:

    1. 创建了一个配置文件security.php,其中包含列入黑名单的请求字符串和列入白名单的 IP 的候选名单。

    security.php 配置文件

    <?php
    return [
        /*
        |--------------------------------------------------------------------------
        | Whitelisted IPs configuration
        |--------------------------------------------------------------------------
        |
        | These are the settings for the whitelisted IPs. The array contains
        | the IPs that should not trigger the IP block.
        |
        */
        'whitelisted_ips' => [
            // whitelisted IPs array
        ],
    
        /*
        |--------------------------------------------------------------------------
        | Blacklisted request strings configuration
        |--------------------------------------------------------------------------
        |
        | These are the settings for the blacklisted request strings. The array contains
        | the strings that should trigger the IP to be blocked.
        |
        */
        'blacklisted_requests' => [
            '.env',
            '.ftpconfig',
            '.vscode',
            '.git',
            '.git/HEAD',
            '_profiler',
            '__media__',
            'administrator',
            //...
        ];
    ];
    
    1. 优化了去除inUrl()方法循环的中间件

    VerifyBlacklistedRequests 中间件

    <?php
    namespace App\Http\Middleware;
    
    use Closure;
    
    /**
     * Class VerifyHackingAttemptsRequests
     *
     * @property \Illuminate\Config\Repository|\Illuminate\Contracts\Foundation\Application|mixed white_listed_ips
     * @property \Illuminate\Config\Repository|\Illuminate\Contracts\Foundation\Application|mixed blacklist
     * @package App\Http\Middleware
     */
    class VerifyHackingAttemptsRequests
    {
        /**
         * @access protected
         * @var \Illuminate\Config\Repository|\Illuminate\Contracts\Foundation\Application|mixed
         */
        protected $blacklist;
    
        /**
         * @access protected
         * @var \Illuminate\Config\Repository|\Illuminate\Contracts\Foundation\Application|mixed
         */
        protected $white_listed_ips;
    
        /**
         * VerifyHackingAttemptsRequests constructor
         *
         * @access public
         */
        public function __construct()
        {
            $this->blacklist        = config('security.blacklisted_requests');
            $this->white_listed_ips = config('security.whitelisted_ips');
        }
    
        /**
         * Handle an incoming request.
         *
         * @access public
         *
         * @param \Illuminate\Http\Request $request
         * @param \Closure                 $next
         *
         * @return mixed
         * @since  2.8.1
         */
        public function handle($request, Closure $next)
        {
            $exists = false;
    
            foreach(request()->segments() as $segment) {
                if(in_array($segment, $this->blacklist)) {
                    $exists = true;
                }
            }
    
            if($exists) {
                $this->blockIp($request)
            }
    
            return $next($request);
        }
    
        /**
         * Method to save an IP in the Blocked IP database table
         *
         * @access protected
         *
         * @param \Illuminate\Http\Request $request
         *
         * @return \App\Models\BlockedIp
         */
        protected function blockIp(Request $request, $notes = null)
        {
            // the logic to persist the data through the BlockedIp model
        }
    }
    

    总而言之,inUrl() 方法已被移除,移除了所有循环和方法调用,并且如上所述,页面的加载时间减少了 50% 以上。

    感谢所有帮助我解决问题的建议方法。

    【讨论】:

    • 是的 @McRui ,使用 arrayin_array($segment, $this-&gt;blacklist) 是优化检查速度的好决定:+1
    • 感谢@Abilogos 的评论。
    【解决方案2】:

    我建议你创建文字路由,这样更容易维护。转到RouteServiceProvider 并创建一个类似于 web 或 api 的新读取,因此该新文件中的任何路由都会禁止/阻止 IP。

    【讨论】:

    • 感谢您的建议,但在我看来,如果我理解您的建议,情况会更糟,因为我必须为每个列入黑名单的字符串(超过 100现在)
    • 是的,你为什么要这样做,因为没有人可以访问这些文件,因为这些文件不在 public 文件夹中?
    • 这些只是示例,它是作为针对漏洞利用者的安全措施而实施的。它还允许不要用 404 淹没 laravel.log 并向 AbuseIPDB 报告。大多数请求段甚至在 Laravel 中都不存在(例如:'phpmyadmin'、'shell.php'、'wp-admin'、'xmlrpc.php' 等),但它们会触发 404 并允许阻止黑客攻击来自多个 IP。
    • 我不是 100% 确定,但我认为你应该通过 NGINX 或 Apache 而不是 Laravel 来解决这个问题,因为它不是 Laravel 的责任,而且你一直在做这个检查,当网络服务器甚至不应该提供那个糟糕的 URL 时,会稍微减慢你的应用程序
    • 感谢您的回复。我在这里反馈后才找到解决方案。一旦允许,我会尽快用解决方案回答我的问题。但是使用 array_diff_assoc($request-&gt;segments(), $this-&gt;blacklisted 需要 0 毫秒,而当前代码需要 + 5 毫秒。因为$request-&gt;segments() 已经在做我的inUrl() 方法正在做的事情,而且效率更高。
    【解决方案3】:

    我不知道这样做是否会优化代码,但我认为代码更具可读性。

    namespace App\Http\Middleware;
    
    /**
     * Class VerifyBlacklistedRequests
     *
     * @package App\Http\Middleware
     */
    class VerifyBlacklistedRequests
    {
    
        /**
         * The array of blacklisted request string segments
         *
         * @access protected
         * @var array|string[]
         */
        protected array $blacklisted = [
            '.env', '.ftpconfig', '.vscode', ',git', '.git/HEAD'
            // etc...
        ];
    
        /**
         * Handle an incoming request.
         *
         * @access public
         *
         * @param \Illuminate\Http\Request $request
         * @param \Closure                 $next
         *
         * @return mixed
         */
        public function handle($request, Closure $next)
        {
            //loop over the list instead of that long conditions
            foreach($this->blacklisted as $blacklistedItem) {
                if($this->inUrl($blacklistedItem)) 
                {
                    // logic that blocks the IP goes here and working fine
                }
            }
        }
    
         /**
         * Check if the string is in any URL segment or at the one specified.
         *
         * @access protected
         *
         * @param string|mixed $value   Segment value/content.
         * @param integer      $segment Segment position.
         *
         * @return bool
         */
        protected function inUrl(string $value, $segment = -1)
        {
            if($segment !== -1 && request()->segment($segment) === $value) {
                return true;
            }
    
            foreach(request()->segments() as $segment) {
                if($segment === $value) {
                    return true;
                }
            }
    
            return false;
        }
    
    }
    

    【讨论】:

    • 感谢 Nahu 的建议,但这似乎并没有检查请求段字符串。它只会检查$this-&gt;blacklisted 数组
    【解决方案4】:

    我相信您可以使用 DB 并将黑名单设为 DB 中的indexed, 数据库将使用其内部引擎自行处理和搜索。

    protected function urlWasInBlackList($segment = -1){
        $segmentStrings= $segment != -1 ? [request()->segment($segment)] : request()->segments();
    
        return DB::table('blacklisted')->select('*')->whereIn('pattern',$segmantStrings)->exists()
    }
    

    并使用类似这样的函数而不是inUrl()。它会检查违禁词与 DB。


    更新:避免使用 DB

    但是 DB 使用其 InnoDB 引擎快速处理这种情况, 也许您想避免 DB,因为连接开销,或者仅仅因为一项功能而不依赖您的项目,

    如果你想避免使用数据库, 您必须使用 loop 检查每个 black 列出的单词 的 url,它将是 O(n), 如果您乘以 节数,它将是 O(n*m)

    最佳方法是避免循环,并像 DB 一样制作 哈希表

    所以我在 php 中查找 hash_tables 并在 php array doc 中找到 php 关联数组是散列图。 并且函数 array_key_exists() 正在查看键的哈希表,您可以在 php 源中看到 array_key_exists() 代码在此处将 ht 寻址为哈希映射:

    array.c 第 6071 行

    zen_hash.h 第 529 行

    所以我建议使用这样的东西:

    protected array $blacklisted = [
            '.env' => null, '.ftpconfig' => null, '.vscode' => null, ',git' => null, '.git/HEAD' => null
          
        ];
    

    用于黑名单定义。 和

        $segmentStrings= $segment != -1 ? [request()->segment($segment)] : request()->segments();
    
    foreach(segmentStrings as $segmentString){
        return array_key_exists($segmentString, $blacklisted);
    }
    

    【讨论】:

    • 感谢@Abilogos 的建议。使用数据库曾经是,现在也是,我一直想避免的事情。
    • 不客气@McRui,我已经更新了避免 DB 的答案
    • 感谢您的更新。尽管如此,我已经测试过,例如,对于 /.git/HEAD 它返回 false,可能是因为斜线。
    猜你喜欢
    • 1970-01-01
    • 2020-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-09
    相关资源
    最近更新 更多