PHP代码中的静态分析规则定制:强制执行安全相关的编码标准

PHP 代码静态分析规则定制:强制执行安全相关的编码标准

大家好!今天我们来聊聊如何定制 PHP 代码静态分析规则,以强制执行安全相关的编码标准。在软件开发过程中,安全问题至关重要。静态分析是一种在不实际运行代码的情况下,检查代码错误的有效方法。通过定制静态分析规则,我们可以尽早发现并修复潜在的安全漏洞,提高代码的安全性。

1. 静态分析工具的选择与配置

PHP 世界里有很多静态分析工具,其中比较流行的有:

  • PHPStan: 一个专注于发现代码错误的静态分析器,具有强大的类型推断能力。
  • Psalm: 另一个强大的静态分析器,提供更细粒度的类型检查和错误报告。
  • Rector: 不仅仅是分析器,还可以自动重构代码,修复一些常见问题。
  • Security Code Scan (SCS): 专门用于检测安全漏洞的静态分析工具。

选择哪个工具取决于你的项目需求和团队熟悉程度。通常情况下,我会建议结合使用多个工具,以获得更全面的安全检查。

配置 PHPStan (示例):

首先,通过 Composer 安装 PHPStan:

composer require --dev phpstan/phpstan

然后,创建一个 phpstan.neon 配置文件:

parameters:
    level: 5  # 设置分析级别,1-9,数字越大,分析越严格
    paths:
        - src  # 指定要分析的代码目录

    # 自定义规则配置
    rules:
        - AppRulesNoEvalRule
        - AppRulesNoUnvalidatedRedirectRule

这里,level 定义了分析的严格程度,paths 指定了要分析的目录,rules 列出了自定义的规则。

2. 安全相关的编码标准与常见漏洞

在定制规则之前,我们需要了解一些常见的 PHP 安全漏洞和相应的编码标准:

漏洞类型 描述 常见预防措施
SQL 注入 攻击者通过构造恶意 SQL 语句,绕过身份验证或访问敏感数据。 使用预处理语句 (Prepared Statements) 或参数化查询,对用户输入进行严格的验证和转义。
跨站脚本攻击 (XSS) 攻击者将恶意脚本注入到网页中,当用户浏览网页时,脚本会在用户浏览器中执行。 对用户输入进行 HTML 转义,使用 Content Security Policy (CSP) 来限制可执行的脚本来源。
跨站请求伪造 (CSRF) 攻击者伪造用户请求,在用户不知情的情况下执行某些操作。 使用 CSRF Token 来验证请求的合法性,设置 SameSite Cookie 属性。
文件包含漏洞 攻击者通过包含恶意文件,执行任意代码。 限制允许包含的文件类型,禁用远程文件包含,对用户输入的文件路径进行严格验证。
命令注入 攻击者通过注入恶意命令,在服务器上执行任意代码。 避免使用 system()exec() 等函数,如果必须使用,对用户输入进行严格的验证和转义,使用白名单机制。
会话劫持 攻击者窃取用户的会话 ID,冒充用户身份。 使用 HTTPS 加密会话,设置 Session Cookie 的 httpOnlysecure 属性,定期更新会话 ID,使用双因素认证。
不安全的反序列化 攻击者通过构造恶意的序列化数据,执行任意代码。 避免使用 unserialize() 函数,如果必须使用,对序列化数据进行签名和验证,使用白名单机制限制可反序列化的类。
不安全的随机数生成 使用弱随机数生成器可能导致预测随机数,进而被攻击者利用。 使用安全的随机数生成器,如 random_int()random_bytes()
信息泄露 将敏感信息暴露给未授权用户。 避免在代码中硬编码敏感信息,如密码、API 密钥等,使用环境变量或配置文件来管理敏感信息,对日志进行审查,避免泄露敏感信息。

3. 定制静态分析规则 (PHPStan 示例)

现在,我们来看一些定制 PHPStan 规则的示例,以强制执行安全相关的编码标准。

示例 1: 禁止使用 eval() 函数

eval() 函数可以将字符串作为 PHP 代码执行,非常危险,容易导致命令注入。

首先,创建一个 NoEvalRule.php 文件:

<?php

namespace AppRules;

use PhpParserNode;
use PhpParserNodeExprEval_;
use PHPStanAnalyserScope;
use PHPStanRulesRule;
use PHPStanRulesRuleError;
use PHPStanRulesRuleErrorBuilder;

class NoEvalRule implements Rule
{
    public function getNodeType(): string
    {
        return Eval_::class;
    }

    /**
     * @param Eval_ $node
     * @param Scope $scope
     * @return RuleError[]
     */
    public function processNode(Node $node, Scope $scope): array
    {
        return [
            RuleErrorBuilder::message('禁止使用 eval() 函数,存在安全风险。')->build()
        ];
    }
}

这个规则会检测代码中是否使用了 eval() 函数,如果使用了,会报告一个错误。

然后在 phpstan.neon 文件中启用该规则:

parameters:
    level: 5
    paths:
        - src
    rules:
        - AppRulesNoEvalRule

示例 2: 检测未验证的重定向

未验证的重定向漏洞 (Open Redirect) 允许攻击者将用户重定向到恶意网站。

创建一个 NoUnvalidatedRedirectRule.php 文件:

<?php

namespace AppRules;

use PhpParserNode;
use PhpParserNodeExprFuncCall;
use PhpParserNodeScalarString_;
use PHPStanAnalyserScope;
use PHPStanRulesRule;
use PHPStanRulesRuleError;
use PHPStanRulesRuleErrorBuilder;

class NoUnvalidatedRedirectRule implements Rule
{
    public function getNodeType(): string
    {
        return FuncCall::class;
    }

    /**
     * @param FuncCall $node
     * @param Scope $scope
     * @return RuleError[]
     */
    public function processNode(Node $node, Scope $scope): array
    {
        if (!($node->name instanceof PhpParserNodeName)) {
            return [];
        }

        if ($node->name->toString() !== 'header') {
            return [];
        }

        if (count($node->args) === 0) {
            return [];
        }

        $firstArg = $node->args[0]->value;

        if (!($firstArg instanceof String_)) {
            return [];
        }

        $headerValue = $firstArg->value;

        if (strpos(strtolower($headerValue), 'location:') === 0) {
            // 简单地检测是否存在 'Location:' 头部,实际情况需要更复杂的逻辑来判断是否进行了验证
            return [
                RuleErrorBuilder::message('检测到未验证的重定向,请确保对 URL 进行验证。')->build()
            ];
        }

        return [];
    }
}

这个规则会检测是否使用了 header() 函数来设置 Location 头部,并提示开发者对 URL 进行验证。注意,这个规则只是一个简单的示例,实际情况需要更复杂的逻辑来判断是否进行了验证,例如,检查 URL 是否在白名单中,或者使用 filter_var() 函数进行验证。

然后在 phpstan.neon 文件中启用该规则:

parameters:
    level: 5
    paths:
        - src
    rules:
        - AppRulesNoEvalRule
        - AppRulesNoUnvalidatedRedirectRule

示例 3: 强制使用预处理语句 (Prepared Statements) 防止 SQL 注入

这个规则比较复杂,需要检查所有执行 SQL 查询的地方,并判断是否使用了预处理语句。这里给出一个简化版的示例,假设我们使用 PDO 来执行 SQL 查询。

<?php

namespace AppRules;

use PhpParserNode;
use PhpParserNodeExprMethodCall;
use PhpParserNodeIdentifier;
use PhpParserNodeName;
use PHPStanAnalyserScope;
use PHPStanRulesRule;
use PHPStanRulesRuleError;
use PHPStanRulesRuleErrorBuilder;

class UsePreparedStatementsRule implements Rule
{
    public function getNodeType(): string
    {
        return MethodCall::class;
    }

    /**
     * @param MethodCall $node
     * @param Scope $scope
     * @return RuleError[]
     */
    public function processNode(Node $node, Scope $scope): array
    {
        // 检查是否是 PDO 对象的 query() 方法
        if (!($node->name instanceof Identifier && $node->name->name === 'query')) {
            return [];
        }

        if (!($node->var instanceof PhpParserNodeExprVariable)) {
            return [];
        }

        // 假设 PDO 对象存储在 $db 变量中
        if ($node->var->name !== 'db') {
            return [];
        }

        // 简单地检查是否使用了 query() 方法,没有检查是否使用了预处理语句
        // 实际情况需要更复杂的逻辑来判断是否使用了预处理语句,例如,检查是否使用了 prepare() 和 execute() 方法
        return [
            RuleErrorBuilder::message('请使用预处理语句 (Prepared Statements) 来防止 SQL 注入。')->build()
        ];
    }
}

这个规则会检测是否使用了 PDO 对象的 query() 方法,并提示开发者使用预处理语句。同样,这个规则只是一个简单的示例,实际情况需要更复杂的逻辑来判断是否使用了预处理语句,例如,检查是否使用了 prepare()execute() 方法。

4. 使用 Security Code Scan (SCS) 检测安全漏洞

Security Code Scan (SCS) 是一个专门用于检测安全漏洞的静态分析工具,它可以检测多种常见的 PHP 安全漏洞,例如 SQL 注入、XSS、CSRF 等。

首先,通过 Composer 安装 SCS:

composer require --dev security-code-scan/security-code-scan

然后,运行 SCS:

./vendor/bin/scs scan src

SCS 会扫描 src 目录下的代码,并报告发现的安全漏洞。

5. 集成到 CI/CD 流程

为了确保代码的安全性,最好将静态分析集成到 CI/CD 流程中。这样,每次提交代码时,都会自动运行静态分析工具,并报告发现的安全漏洞。

例如,可以使用 GitHub Actions 来实现自动化静态分析。

创建一个 .github/workflows/static-analysis.yml 文件:

name: Static Analysis

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  phpstan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.1'
          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, gd, intl
          tools: composer:v2

      - name: Install Dependencies
        run: composer install --no-interaction --no-progress --no-suggest

      - name: Run PHPStan
        run: ./vendor/bin/phpstan analyse --memory-limit=2G

  security-code-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.1'
          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, gd, intl
          tools: composer:v2

      - name: Install Dependencies
        run: composer install --no-interaction --no-progress --no-suggest

      - name: Run Security Code Scan
        run: ./vendor/bin/scs scan src

这个 workflow 会在每次提交代码或创建 pull request 时,自动运行 PHPStan 和 Security Code Scan,并报告发现的安全漏洞。

6. 不断更新和维护规则

安全漏洞是不断变化的,因此,我们需要不断更新和维护静态分析规则,以应对新的安全威胁。可以定期审查规则,添加新的规则,或者更新现有规则。

重点回顾

  • 静态分析是发现代码错误的有效方法,尤其是在安全方面。
  • 选择合适的静态分析工具,并根据项目需求进行配置。
  • 了解常见的 PHP 安全漏洞和相应的编码标准。
  • 定制静态分析规则,强制执行安全相关的编码标准。
  • 将静态分析集成到 CI/CD 流程中,实现自动化安全检查。
  • 不断更新和维护规则,以应对新的安全威胁。

希望今天的分享能够帮助大家更好地利用静态分析工具,提高 PHP 代码的安全性。

代码安全是持续的旅程

定制静态分析规则并将其集成到开发流程中,是提高 PHP 代码安全性的重要步骤。这不仅能帮助开发者早期发现并修复潜在的安全漏洞,还能在团队内部推广安全编码的最佳实践。安全不是一次性的任务,而是一个持续改进的过程。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注