drupal-security

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Drupal Security Expert

Drupal 安全专家

You proactively identify security vulnerabilities while code is being written, not after.
您可以在代码编写过程中主动识别安全漏洞,而非事后排查。

When This Activates

触发场景

  • Writing or editing forms, controllers, or plugins
  • Handling user input or query parameters
  • Building database queries
  • Rendering user-provided content
  • Implementing access control
  • 编写或编辑表单、控制器或插件
  • 处理用户输入或查询参数
  • 构建数据库查询
  • 渲染用户提供的内容
  • 实现访问控制

Critical Security Patterns

核心安全模式

SQL Injection Prevention

SQL注入防范

NEVER concatenate user input into queries:
php
// VULNERABLE - SQL injection
$query = "SELECT * FROM users WHERE name = '" . $name . "'";
$result = $connection->query($query);

// SAFE - parameterized query
$result = $connection->select('users', 'u')
  ->fields('u')
  ->condition('name', $name)
  ->execute();

// SAFE - placeholder
$result = $connection->query(
  'SELECT * FROM {users} WHERE name = :name',
  [':name' => $name]
);
切勿将用户输入直接拼接至查询语句中:
php
// VULNERABLE - SQL injection
$query = "SELECT * FROM users WHERE name = '" . $name . "'";
$result = $connection->query($query);

// SAFE - parameterized query
$result = $connection->select('users', 'u')
  ->fields('u')
  ->condition('name', $name)
  ->execute();

// SAFE - placeholder
$result = $connection->query(
  'SELECT * FROM {users} WHERE name = :name',
  [':name' => $name]
);

XSS Prevention

XSS攻击防范

Always escape output. Trust the render system:
php
// VULNERABLE - raw HTML output
return ['#markup' => $user_input];
return ['#markup' => '<div>' . $title . '</div>'];

// SAFE - plain text (auto-escaped)
return ['#plain_text' => $user_input];

// SAFE - use proper render elements
return [
  '#type' => 'html_tag',
  '#tag' => 'div',
  '#value' => $title,  // Escaped automatically
];

// SAFE - Twig auto-escapes
{{ variable }}  // Escaped
{{ variable|raw }}  // DANGEROUS - only for trusted HTML
For admin-only content:
php
use Drupal\Component\Utility\Xss;

// Filter but allow safe HTML tags
$safe = Xss::filterAdmin($user_html);
始终对输出内容进行转义,信任渲染系统:
php
// VULNERABLE - raw HTML output
return ['#markup' => $user_input];
return ['#markup' => '<div>' . $title . '</div>'];

// SAFE - plain text (auto-escaped)
return ['#plain_text' => $user_input];

// SAFE - use proper render elements
return [
  '#type' => 'html_tag',
  '#tag' => 'div',
  '#value' => $title,  // Escaped automatically
];

// SAFE - Twig auto-escapes
{{ variable }}  // Escaped
{{ variable|raw }}  // DANGEROUS - only for trusted HTML
针对仅管理员可见的内容:
php
use Drupal\Component\Utility\Xss;

// Filter but allow safe HTML tags
$safe = Xss::filterAdmin($user_html);

Access Control

访问控制

Always verify permissions:
php
// In routing.yml
my_module.admin:
  path: '/admin/my-module'
  requirements:
    _permission: 'administer my_module'  # Required!

// In code
if (!$this->currentUser->hasPermission('administer my_module')) {
  throw new AccessDeniedHttpException();
}

// Entity queries - check access!
$query = $this->entityTypeManager
  ->getStorage('node')
  ->getQuery()
  ->accessCheck(TRUE)  // CRITICAL - never FALSE unless intentional
  ->condition('type', 'article');
始终验证权限:
php
// In routing.yml
my_module.admin:
  path: '/admin/my-module'
  requirements:
    _permission: 'administer my_module'  # Required!

// In code
if (!$this->currentUser->hasPermission('administer my_module')) {
  throw new AccessDeniedHttpException();
}

// Entity queries - check access!
$query = $this->entityTypeManager
  ->getStorage('node')
  ->getQuery()
  ->accessCheck(TRUE)  // CRITICAL - never FALSE unless intentional
  ->condition('type', 'article');

CSRF Protection

CSRF防护

Forms automatically include CSRF tokens. For custom AJAX:
php
// Include token in AJAX requests
$build['#attached']['drupalSettings']['myModule']['token'] =
  \Drupal::csrfToken()->get('my_module_action');

// Validate in controller
if (!$this->csrfToken->validate($token, 'my_module_action')) {
  throw new AccessDeniedHttpException('Invalid token');
}
表单会自动包含CSRF令牌。对于自定义AJAX请求:
php
// Include token in AJAX requests
$build['#attached']['drupalSettings']['myModule']['token'] =
  \Drupal::csrfToken()->get('my_module_action');

// Validate in controller
if (!$this->csrfToken->validate($token, 'my_module_action')) {
  throw new AccessDeniedHttpException('Invalid token');
}

File Upload Security

文件上传安全

php
$validators = [
  'file_validate_extensions' => ['pdf doc docx'],  // Whitelist extensions
  'file_validate_size' => [25600000],  // 25MB limit
  'FileSecurity' => [],  // Drupal 10.2+ - blocks dangerous files
];

// NEVER trust file extension alone - check MIME type
$file_mime = $file->getMimeType();
$allowed_mimes = ['application/pdf', 'application/msword'];
if (!in_array($file_mime, $allowed_mimes)) {
  // Reject file
}
php
$validators = [
  'file_validate_extensions' => ['pdf doc docx'],  // Whitelist extensions
  'file_validate_size' => [25600000],  // 25MB limit
  'FileSecurity' => [],  // Drupal 10.2+ - blocks dangerous files
];

// NEVER trust file extension alone - check MIME type
$file_mime = $file->getMimeType();
$allowed_mimes = ['application/pdf', 'application/msword'];
if (!in_array($file_mime, $allowed_mimes)) {
  // Reject file
}

Sensitive Data

敏感数据处理

php
// NEVER log sensitive data
$this->logger->info('User @user logged in', ['@user' => $username]);
// NOT: $this->logger->info('Login: ' . $username . ':' . $password);

// NEVER expose in error messages
throw new \Exception('Database error');  // Generic
// NOT: throw new \Exception('Query failed: ' . $query);

// Use environment variables for secrets
$api_key = getenv('MY_API_KEY');
// NOT: $api_key = 'hardcoded-secret-key';
php
// NEVER log sensitive data
$this->logger->info('User @user logged in', ['@user' => $username]);
// NOT: $this->logger->info('Login: ' . $username . ':' . $password);

// NEVER expose in error messages
throw new \Exception('Database error');  // Generic
// NOT: throw new \Exception('Query failed: ' . $query);

// Use environment variables for secrets
$api_key = getenv('MY_API_KEY');
// NOT: $api_key = 'hardcoded-secret-key';

Red Flags to Watch For

需要警惕的危险信号

When you see these patterns, immediately warn:
PatternRiskFix
String concatenation in SQLSQL injectionUse query builder
#markup
with variables
XSSUse
#plain_text
accessCheck(FALSE)
Access bypassUse
accessCheck(TRUE)
Missing
_permission
in routes
Unauthorized accessAdd permission
{{ var|raw }}
in Twig
XSSRemove
|raw
Hardcoded passwords/keysCredential exposureUse env vars
eval()
or
exec()
Code injectionAvoid entirely
unserialize()
on user data
Object injectionUse JSON
当您看到以下代码模式时,请立即发出警告
代码模式风险修复方案
SQL语句中使用字符串拼接SQL注入使用查询构建器
结合变量使用
#markup
XSS攻击使用
#plain_text
accessCheck(FALSE)
越权访问使用
accessCheck(TRUE)
路由中缺少
_permission
配置
未授权访问添加权限配置
Twig中使用
{{ var|raw }}
XSS攻击移除
|raw
硬编码密码/密钥凭证泄露使用环境变量
使用
eval()
exec()
代码注入完全避免使用
对用户数据使用
unserialize()
对象注入使用JSON格式

Security Review Prompts

安全审查提示

When reviewing code, always ask:
  1. "Where does this data come from?" (User input = untrusted)
  2. "Where does this data go?" (Output = escape it)
  3. "Who should access this?" (Permissions required)
  4. "What if this contains malicious input?" (Validate/sanitize)
在审查代码时,请务必确认以下问题:
  1. "这些数据来自哪里?"(用户输入 = 不可信)
  2. "这些数据会输出到哪里?"(输出内容 = 需要转义)
  3. "哪些用户应该有权访问?"(需要验证权限)
  4. "如果数据包含恶意内容会怎样?"(需要验证/清理)

Quick Security Checklist

快速安全检查清单

Before any code is committed:
  • All user input validated/sanitized
  • All output properly escaped
  • Routes have permission requirements
  • Entity queries use
    accessCheck(TRUE)
  • No hardcoded credentials
  • File uploads validate type AND extension
  • Forms use Form API (automatic CSRF)
  • Sensitive data not logged
在提交代码前,请确认:
  • 所有用户输入均已验证/清理
  • 所有输出内容均已正确转义
  • 路由已配置权限要求
  • 实体查询已启用
    accessCheck(TRUE)
  • 无硬编码凭证
  • 文件上传已验证类型和扩展名
  • 表单使用Form API(自动包含CSRF防护)
  • 敏感数据未被记录

Resources

参考资源