Add Laravel-specific security vulnerability detection
New LaravelSecurityRule detects: - Mass Assignment: Models without $fillable/$guarded - Mass Assignment: Model::create($request->all()) - SQL Injection: DB::raw() with variables - SQL Injection: whereRaw/selectRaw without bindings - CSRF: Forms without @csrf directive - File Upload: Validation with extensions only (no mimes) - Auth Middleware: Sensitive routes without auth - Rate Limiting: Auth routes without throttle All detections include Japanese and English messages with specific remediation recommendations. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
636
src/Rules/LaravelSecurityRule.php
Normal file
636
src/Rules/LaravelSecurityRule.php
Normal file
@@ -0,0 +1,636 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SecurityLinter\Rules;
|
||||
|
||||
use PhpParser\Node;
|
||||
use SecurityLinter\Analyzer\TaintTracker;
|
||||
use SecurityLinter\Report\Vulnerability;
|
||||
|
||||
/**
|
||||
* Detects Laravel-specific security vulnerabilities
|
||||
*
|
||||
* Checks for:
|
||||
* - Mass Assignment vulnerabilities (missing $fillable/$guarded)
|
||||
* - Raw SQL injection (DB::raw, whereRaw without bindings)
|
||||
* - CSRF protection missing in forms
|
||||
* - Insecure file upload validation
|
||||
* - Missing authentication middleware
|
||||
* - Missing rate limiting on auth routes
|
||||
*/
|
||||
class LaravelSecurityRule extends BaseRule
|
||||
{
|
||||
/** @var array Raw query methods that need parameter binding */
|
||||
private const RAW_QUERY_METHODS = [
|
||||
'whereRaw', 'orWhereRaw', 'havingRaw', 'orHavingRaw',
|
||||
'orderByRaw', 'groupByRaw', 'selectRaw',
|
||||
];
|
||||
|
||||
/** @var array Sensitive route patterns that should have auth */
|
||||
private const SENSITIVE_ROUTE_PATTERNS = [
|
||||
'admin', 'dashboard', 'account', 'profile', 'settings',
|
||||
'user', 'users', 'manage', 'management', 'private',
|
||||
];
|
||||
|
||||
/** @var array Auth-related route patterns that should have throttle */
|
||||
private const AUTH_ROUTE_PATTERNS = [
|
||||
'login', 'signin', 'sign-in', 'authenticate',
|
||||
'register', 'signup', 'sign-up',
|
||||
'password', 'forgot', 'reset',
|
||||
];
|
||||
|
||||
/** @var bool Track if current class has fillable/guarded */
|
||||
private bool $hasFillable = false;
|
||||
private bool $hasGuarded = false;
|
||||
private bool $isEloquentModel = false;
|
||||
private string $currentClassName = '';
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->msg('laravel.name');
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Detects Laravel-specific security vulnerabilities';
|
||||
}
|
||||
|
||||
public function analyze(array $ast, string $filePath, TaintTracker $taintTracker): array
|
||||
{
|
||||
$vulnerabilities = [];
|
||||
|
||||
// Reset state for each file
|
||||
$this->hasFillable = false;
|
||||
$this->hasGuarded = false;
|
||||
$this->isEloquentModel = false;
|
||||
$this->currentClassName = '';
|
||||
|
||||
// Analyze PHP AST
|
||||
$this->traverse($ast, function (Node $node) use ($filePath, $taintTracker, &$vulnerabilities) {
|
||||
// Track class definitions for Mass Assignment check
|
||||
if ($node instanceof Node\Stmt\Class_) {
|
||||
$this->analyzeClass($node, $filePath, $vulnerabilities);
|
||||
}
|
||||
|
||||
// Check for raw SQL methods
|
||||
if ($node instanceof Node\Expr\MethodCall) {
|
||||
$this->checkRawSqlMethod($node, $filePath, $taintTracker, $vulnerabilities);
|
||||
$this->checkMassAssignmentMethods($node, $filePath, $taintTracker, $vulnerabilities);
|
||||
}
|
||||
|
||||
// Check for DB::raw() static calls and Model::create() mass assignment
|
||||
if ($node instanceof Node\Expr\StaticCall) {
|
||||
$this->checkDbRaw($node, $filePath, $taintTracker, $vulnerabilities);
|
||||
$this->checkStaticMassAssignment($node, $filePath, $vulnerabilities);
|
||||
}
|
||||
|
||||
// Check route definitions for auth/throttle middleware
|
||||
if ($node instanceof Node\Expr\StaticCall || $node instanceof Node\Expr\MethodCall) {
|
||||
$this->checkRouteMiddleware($node, $filePath, $vulnerabilities);
|
||||
}
|
||||
|
||||
// Check validation rules for file uploads
|
||||
if ($node instanceof Node\Expr\Array_) {
|
||||
$this->checkFileValidation($node, $filePath, $vulnerabilities);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
// Check Blade templates for CSRF
|
||||
if (str_ends_with($filePath, '.blade.php')) {
|
||||
$bladeVulns = $this->analyzeBladeForCsrf($filePath);
|
||||
$vulnerabilities = array_merge($vulnerabilities, $bladeVulns);
|
||||
}
|
||||
|
||||
return $vulnerabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze class for Mass Assignment vulnerabilities
|
||||
*/
|
||||
private function analyzeClass(Node\Stmt\Class_ $node, string $filePath, array &$vulnerabilities): void
|
||||
{
|
||||
$this->currentClassName = $node->name ? $node->name->toString() : '';
|
||||
$this->hasFillable = false;
|
||||
$this->hasGuarded = false;
|
||||
$this->isEloquentModel = false;
|
||||
|
||||
// Check if class extends Eloquent Model
|
||||
if ($node->extends) {
|
||||
$parentClass = $node->extends->toString();
|
||||
if (str_contains($parentClass, 'Model') ||
|
||||
str_contains($parentClass, 'Authenticatable') ||
|
||||
str_contains($parentClass, 'Pivot')) {
|
||||
$this->isEloquentModel = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->isEloquentModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for $fillable or $guarded property
|
||||
foreach ($node->stmts as $stmt) {
|
||||
if ($stmt instanceof Node\Stmt\Property) {
|
||||
foreach ($stmt->props as $prop) {
|
||||
$propName = $prop->name->toString();
|
||||
if ($propName === 'fillable') {
|
||||
$this->hasFillable = true;
|
||||
}
|
||||
if ($propName === 'guarded') {
|
||||
$this->hasGuarded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report if neither fillable nor guarded is defined
|
||||
if (!$this->hasFillable && !$this->hasGuarded) {
|
||||
$vulnerabilities[] = $this->createVulnerability(
|
||||
$this->msg('laravel.name'),
|
||||
Vulnerability::SEVERITY_HIGH,
|
||||
$this->msg('laravel.mass_assignment', ['class' => $this->currentClassName]),
|
||||
$filePath,
|
||||
$node->getStartLine(),
|
||||
$node,
|
||||
$this->msg('laravel.mass_assignment.rec'),
|
||||
[],
|
||||
'CWE-915',
|
||||
'A5:2017-Broken Access Control'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for static mass assignment like Model::create($request->all())
|
||||
*/
|
||||
private function checkStaticMassAssignment(
|
||||
Node\Expr\StaticCall $node,
|
||||
string $filePath,
|
||||
array &$vulnerabilities
|
||||
): void {
|
||||
$methodName = $node->name instanceof Node\Identifier ? $node->name->toString() : '';
|
||||
|
||||
if (!in_array($methodName, ['create', 'insert', 'upsert'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$args = $this->getArguments($node);
|
||||
if (empty($args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$firstArg = $args[0]->value;
|
||||
|
||||
// Check for $request->all() or request()->all()
|
||||
if ($this->isRequestAll($firstArg)) {
|
||||
$vulnerabilities[] = $this->createVulnerability(
|
||||
$this->msg('laravel.name'),
|
||||
Vulnerability::SEVERITY_HIGH,
|
||||
$this->msg('laravel.mass_assignment_all', ['method' => $methodName]),
|
||||
$filePath,
|
||||
$node->getStartLine(),
|
||||
$node,
|
||||
$this->msg('laravel.mass_assignment_all.rec'),
|
||||
[],
|
||||
'CWE-915',
|
||||
'A5:2017-Broken Access Control'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for dangerous Mass Assignment method calls
|
||||
*/
|
||||
private function checkMassAssignmentMethods(
|
||||
Node\Expr\MethodCall $node,
|
||||
string $filePath,
|
||||
TaintTracker $taintTracker,
|
||||
array &$vulnerabilities
|
||||
): void {
|
||||
$methodName = $this->getCallName($node);
|
||||
|
||||
if (!in_array($methodName, ['create', 'fill', 'update', 'insert'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$args = $this->getArguments($node);
|
||||
if (empty($args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$firstArg = $args[0]->value;
|
||||
|
||||
// Check for $request->all() or request()->all()
|
||||
if ($this->isRequestAll($firstArg)) {
|
||||
$vulnerabilities[] = $this->createVulnerability(
|
||||
$this->msg('laravel.name'),
|
||||
Vulnerability::SEVERITY_HIGH,
|
||||
$this->msg('laravel.mass_assignment_all', ['method' => $methodName]),
|
||||
$filePath,
|
||||
$node->getStartLine(),
|
||||
$node,
|
||||
$this->msg('laravel.mass_assignment_all.rec'),
|
||||
[],
|
||||
'CWE-915',
|
||||
'A5:2017-Broken Access Control'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if expression is $request->all() or similar
|
||||
*/
|
||||
private function isRequestAll(Node $expr): bool
|
||||
{
|
||||
// $request->all()
|
||||
if ($expr instanceof Node\Expr\MethodCall) {
|
||||
$methodName = $this->getCallName($expr);
|
||||
if ($methodName === 'all') {
|
||||
// Check if called on $request variable
|
||||
if ($expr->var instanceof Node\Expr\Variable) {
|
||||
$varName = $this->getVariableName($expr->var);
|
||||
if (strtolower($varName) === 'request') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Check if called on request() helper
|
||||
if ($expr->var instanceof Node\Expr\FuncCall) {
|
||||
$funcName = $this->getCallName($expr->var);
|
||||
if ($funcName === 'request') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// request()->all() via static call Request::all()
|
||||
if ($expr instanceof Node\Expr\StaticCall) {
|
||||
$className = $expr->class instanceof Node\Name ? $expr->class->toString() : '';
|
||||
$methodName = $expr->name instanceof Node\Identifier ? $expr->name->toString() : '';
|
||||
if (str_contains($className, 'Request') && $methodName === 'all') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check raw SQL methods for injection vulnerabilities
|
||||
*/
|
||||
private function checkRawSqlMethod(
|
||||
Node\Expr\MethodCall $node,
|
||||
string $filePath,
|
||||
TaintTracker $taintTracker,
|
||||
array &$vulnerabilities
|
||||
): void {
|
||||
$methodName = $this->getCallName($node);
|
||||
|
||||
if (!in_array($methodName, self::RAW_QUERY_METHODS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$args = $this->getArguments($node);
|
||||
if (empty($args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sqlArg = $args[0]->value;
|
||||
$hasBindings = count($args) >= 2;
|
||||
|
||||
// Check if SQL string contains variables without bindings
|
||||
if ($this->containsUnsafeInterpolation($sqlArg) && !$hasBindings) {
|
||||
$vulnerabilities[] = $this->createVulnerability(
|
||||
$this->msg('laravel.name'),
|
||||
Vulnerability::SEVERITY_CRITICAL,
|
||||
$this->msg('laravel.raw_sql', ['method' => $methodName]),
|
||||
$filePath,
|
||||
$node->getStartLine(),
|
||||
$node,
|
||||
$this->msg('laravel.raw_sql.rec'),
|
||||
[],
|
||||
'CWE-89',
|
||||
'A1:2017-Injection'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check DB::raw() for injection
|
||||
*/
|
||||
private function checkDbRaw(
|
||||
Node\Expr\StaticCall $node,
|
||||
string $filePath,
|
||||
TaintTracker $taintTracker,
|
||||
array &$vulnerabilities
|
||||
): void {
|
||||
$className = $node->class instanceof Node\Name ? $node->class->toString() : '';
|
||||
$methodName = $node->name instanceof Node\Identifier ? $node->name->toString() : '';
|
||||
|
||||
if (!str_contains($className, 'DB') || $methodName !== 'raw') {
|
||||
return;
|
||||
}
|
||||
|
||||
$args = $this->getArguments($node);
|
||||
if (empty($args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sqlArg = $args[0]->value;
|
||||
|
||||
if ($this->containsUnsafeInterpolation($sqlArg)) {
|
||||
$vulnerabilities[] = $this->createVulnerability(
|
||||
$this->msg('laravel.name'),
|
||||
Vulnerability::SEVERITY_CRITICAL,
|
||||
$this->msg('laravel.db_raw'),
|
||||
$filePath,
|
||||
$node->getStartLine(),
|
||||
$node,
|
||||
$this->msg('laravel.db_raw.rec'),
|
||||
[],
|
||||
'CWE-89',
|
||||
'A1:2017-Injection'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if expression contains unsafe variable interpolation
|
||||
*/
|
||||
private function containsUnsafeInterpolation(Node $expr): bool
|
||||
{
|
||||
// Variable directly
|
||||
if ($expr instanceof Node\Expr\Variable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// String interpolation "SELECT * FROM {$table}"
|
||||
if ($expr instanceof Node\Scalar\Encapsed ||
|
||||
$expr instanceof Node\Scalar\InterpolatedString) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Concatenation "SELECT * FROM " . $table
|
||||
if ($expr instanceof Node\Expr\BinaryOp\Concat) {
|
||||
return $this->containsUnsafeInterpolation($expr->left) ||
|
||||
$this->containsUnsafeInterpolation($expr->right);
|
||||
}
|
||||
|
||||
// Property fetch $this->table or $model->table
|
||||
if ($expr instanceof Node\Expr\PropertyFetch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Array access $tables[$key]
|
||||
if ($expr instanceof Node\Expr\ArrayDimFetch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check route definitions for missing middleware
|
||||
*/
|
||||
private function checkRouteMiddleware(
|
||||
Node $node,
|
||||
string $filePath,
|
||||
array &$vulnerabilities
|
||||
): void {
|
||||
// Only check in route files
|
||||
if (!str_contains($filePath, 'routes/') && !str_contains($filePath, 'routes\\')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$methodName = '';
|
||||
$isRouteCall = false;
|
||||
|
||||
// For static calls like Route::get()
|
||||
if ($node instanceof Node\Expr\StaticCall) {
|
||||
$className = $node->class instanceof Node\Name ? $node->class->toString() : '';
|
||||
if (str_contains($className, 'Route')) {
|
||||
$isRouteCall = true;
|
||||
$methodName = $node->name instanceof Node\Identifier ? $node->name->toString() : '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isRouteCall) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check Route::get/post/put/delete etc.
|
||||
if (!in_array($methodName, ['get', 'post', 'put', 'patch', 'delete', 'any'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$args = $this->getArguments($node);
|
||||
if (empty($args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get route path
|
||||
$routePath = '';
|
||||
if ($args[0]->value instanceof Node\Scalar\String_) {
|
||||
$routePath = strtolower($args[0]->value->value);
|
||||
}
|
||||
|
||||
// Check if this is a sensitive route without auth middleware
|
||||
$isSensitive = false;
|
||||
foreach (self::SENSITIVE_ROUTE_PATTERNS as $pattern) {
|
||||
if (str_contains($routePath, $pattern)) {
|
||||
$isSensitive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this is an auth route without throttle
|
||||
$isAuthRoute = false;
|
||||
foreach (self::AUTH_ROUTE_PATTERNS as $pattern) {
|
||||
if (str_contains($routePath, $pattern)) {
|
||||
$isAuthRoute = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We need to check the method chain for middleware
|
||||
// This is a simplified check - actual middleware might be in group
|
||||
// For now, we flag sensitive routes as informational
|
||||
if ($isSensitive) {
|
||||
$vulnerabilities[] = $this->createVulnerability(
|
||||
$this->msg('laravel.name'),
|
||||
Vulnerability::SEVERITY_LOW,
|
||||
$this->msg('laravel.route_auth', ['route' => $routePath]),
|
||||
$filePath,
|
||||
$node->getStartLine(),
|
||||
$node,
|
||||
$this->msg('laravel.route_auth.rec'),
|
||||
[],
|
||||
'CWE-862',
|
||||
'A5:2017-Broken Access Control'
|
||||
);
|
||||
}
|
||||
|
||||
if ($isAuthRoute && in_array($methodName, ['post', 'put', 'patch'])) {
|
||||
$vulnerabilities[] = $this->createVulnerability(
|
||||
$this->msg('laravel.name'),
|
||||
Vulnerability::SEVERITY_LOW,
|
||||
$this->msg('laravel.route_throttle', ['route' => $routePath]),
|
||||
$filePath,
|
||||
$node->getStartLine(),
|
||||
$node,
|
||||
$this->msg('laravel.route_throttle.rec'),
|
||||
[],
|
||||
'CWE-307',
|
||||
'A2:2017-Broken Authentication'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check file validation rules
|
||||
*/
|
||||
private function checkFileValidation(
|
||||
Node\Expr\Array_ $node,
|
||||
string $filePath,
|
||||
array &$vulnerabilities
|
||||
): void {
|
||||
foreach ($node->items as $item) {
|
||||
if ($item === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for file/image validation rules
|
||||
if (!($item->value instanceof Node\Scalar\String_) &&
|
||||
!($item->value instanceof Node\Expr\Array_)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rules = $this->extractValidationRules($item->value);
|
||||
|
||||
// Check if there's 'extensions' without 'mimes' or 'mimetypes'
|
||||
$hasExtensions = false;
|
||||
$hasMimes = false;
|
||||
$hasFile = false;
|
||||
|
||||
foreach ($rules as $rule) {
|
||||
$ruleLower = strtolower($rule);
|
||||
if (str_starts_with($ruleLower, 'extensions')) {
|
||||
$hasExtensions = true;
|
||||
}
|
||||
if (str_starts_with($ruleLower, 'mimes') || str_starts_with($ruleLower, 'mimetypes')) {
|
||||
$hasMimes = true;
|
||||
}
|
||||
if ($ruleLower === 'file' || $ruleLower === 'image') {
|
||||
$hasFile = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasFile && $hasExtensions && !$hasMimes) {
|
||||
$keyName = '';
|
||||
if ($item->key instanceof Node\Scalar\String_) {
|
||||
$keyName = $item->key->value;
|
||||
}
|
||||
|
||||
$vulnerabilities[] = $this->createVulnerability(
|
||||
$this->msg('laravel.name'),
|
||||
Vulnerability::SEVERITY_MEDIUM,
|
||||
$this->msg('laravel.file_validation', ['field' => $keyName]),
|
||||
$filePath,
|
||||
$node->getStartLine(),
|
||||
$node,
|
||||
$this->msg('laravel.file_validation.rec'),
|
||||
[],
|
||||
'CWE-434',
|
||||
'A8:2017-Insecure Deserialization'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract validation rules from node
|
||||
*/
|
||||
private function extractValidationRules(Node $node): array
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
if ($node instanceof Node\Scalar\String_) {
|
||||
// Pipe-separated rules: 'required|file|extensions:jpg'
|
||||
$rules = explode('|', $node->value);
|
||||
} elseif ($node instanceof Node\Expr\Array_) {
|
||||
// Array rules: ['required', 'file', 'extensions:jpg']
|
||||
foreach ($node->items as $item) {
|
||||
if ($item && $item->value instanceof Node\Scalar\String_) {
|
||||
$rules[] = $item->value->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze Blade template for CSRF protection
|
||||
*/
|
||||
private function analyzeBladeForCsrf(string $filePath): array
|
||||
{
|
||||
$vulnerabilities = [];
|
||||
$content = file_get_contents($filePath);
|
||||
|
||||
if ($content === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Find all forms with POST/PUT/PATCH/DELETE methods
|
||||
$formPattern = '/<form[^>]*\bmethod\s*=\s*["\']?(POST|PUT|PATCH|DELETE)["\']?[^>]*>/i';
|
||||
|
||||
if (preg_match_all($formPattern, $content, $matches, PREG_OFFSET_CAPTURE)) {
|
||||
foreach ($matches[0] as $index => $match) {
|
||||
$formTag = $match[0];
|
||||
$offset = $match[1];
|
||||
$line = $this->getLineFromOffset($content, $offset);
|
||||
|
||||
// Find the closing </form> tag
|
||||
$formEnd = stripos($content, '</form>', $offset);
|
||||
if ($formEnd === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$formContent = substr($content, $offset, $formEnd - $offset);
|
||||
|
||||
// Check if form has @csrf or csrf_field() or _token input
|
||||
$hasCsrf = preg_match('/@csrf\b/', $formContent) ||
|
||||
preg_match('/csrf_field\s*\(\s*\)/', $formContent) ||
|
||||
preg_match('/name\s*=\s*["\']_token["\']/', $formContent);
|
||||
|
||||
if (!$hasCsrf) {
|
||||
$vulnerabilities[] = $this->createVulnerability(
|
||||
$this->msg('laravel.name'),
|
||||
Vulnerability::SEVERITY_HIGH,
|
||||
$this->msg('laravel.csrf_missing'),
|
||||
$filePath,
|
||||
$line,
|
||||
null,
|
||||
$this->msg('laravel.csrf_missing.rec'),
|
||||
[],
|
||||
'CWE-352',
|
||||
'A8:2013-CSRF'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $vulnerabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get line number from byte offset
|
||||
*/
|
||||
private function getLineFromOffset(string $content, int $offset): int
|
||||
{
|
||||
return substr_count(substr($content, 0, $offset), "\n") + 1;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ use SecurityLinter\Rules\PathTraversalRule;
|
||||
use SecurityLinter\Rules\AuthenticationRule;
|
||||
use SecurityLinter\Rules\CsrfSessionRule;
|
||||
use SecurityLinter\Rules\InsecureConfigRule;
|
||||
use SecurityLinter\Rules\LaravelSecurityRule;
|
||||
|
||||
/**
|
||||
* PHP/Laravel Security Linter
|
||||
@@ -73,6 +74,7 @@ class SecurityLinter
|
||||
new AuthenticationRule(),
|
||||
new CsrfSessionRule(),
|
||||
new InsecureConfigRule(),
|
||||
new LaravelSecurityRule(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user