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 的 httpOnly 和 secure 属性,定期更新会话 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 代码安全性的重要步骤。这不仅能帮助开发者早期发现并修复潜在的安全漏洞,还能在团队内部推广安全编码的最佳实践。安全不是一次性的任务,而是一个持续改进的过程。