PHP
Vulkro reads PHP source with a set of dedicated detectors and a short-range taint pass, and checks composer.lock against the local CVE bundle. It does not build an endpoint map or an access model for PHP, so the API-level checks that need one do not run.
PHP is covered by vulkro (Core). Salesforce code is covered by the separate vulkro-sf binary.
vulkro scan .
What is analysed
| Area | Coverage |
|---|---|
| Project detection | composer.json, a Laravel artisan file, wp-config.php, or any PHP source file |
| Files read | .php, .phtml, .php4, .php5, .phps (.inc is not read as PHP) |
| Request sources | The superglobals ($_GET, $_POST, $_REQUEST, $_COOKIE, $_FILES, $_SERVER, $_ENV, $GLOBALS) and framework request objects: Laravel, Symfony, PSR-7 / Slim / Mezzio, CodeIgniter 4, CakePHP |
| Taint | Within one function, through local variables (copy, . and .=, string interpolation, a ternary, and plain string helpers such as trim or sprintf). Any other call is treated as a barrier. One call into a project function is also followed (see Limits) |
| Taint sinks | SQL queries, OS commands, HTML output (echo / print), include / require, eval / assert, unserialize, outbound fetch and file URLs |
| Endpoints and auth | Not analysed: no route map, no auth tier per endpoint |
| Dependencies | composer.lock (packages and packages-dev), matched as Packagist against the local CVE bundle. See Dependencies and CVEs |
Detectors
The injection rules fire when a request value appears in the dangerous call itself, and stay silent when the matching escaper or safe form is used (prepared statements, htmlspecialchars, escapeshellarg, allowed_classes => false and so on). The rest match a risky call shape.
| Rule | What it finds | CWE |
|---|---|---|
PHP-SQLI-001 | Request input in a SQL query string | 89 |
PHP-XSS-001 | Request input echoed or printed without encoding | 79 |
PHP-CMDI-001 | Request input in system, exec, shell_exec or backticks | 78 |
PHP-CODE-001 | eval, assert or a variable function on request input | 95 |
PHP-LFI-001 | Request input in an include / require path | 98 |
PHP-EXTRACT-001 | extract, one-argument parse_str and similar on request input | 621 |
PHP-DESER-001 | unserialize of request input | 502 |
PHP-PHAR-001 | A phar:// path reaching a filesystem call | 502 |
PHP-SSRF-001 | Request input in a fetch or file URL | 918 |
PHP-UPLOAD-001 | An upload stored under the client's file name | 434 |
PHP-HEADER-001 | Request input in header(): open redirect or header injection | 601, 113 |
PHP-MAILPARAM-001 | Request input in the extra-parameters argument of mail() | 88 |
PHP-LDAPX-001 | Request input in an LDAP filter or an XPath query | 90, 643 |
PHP-SSTI-001 | Request input compiled as a template | 1336 |
PHP-XXE-001 | XML parsed with external entities enabled | 611 |
PHP-MASS-ASSIGN-001 | A whole request passed to an Eloquent write | 915 |
PHP-TYPEJUGGLE-001 | A loose == comparison on a password, hash or token | 697 |
PHP-CRYPTO-001 | md5 / sha1 on a secret, weak ciphers | 327 |
PHP-RAND-001 | rand / mt_rand used to make a token or key | 330 |
PHP-TLS-001 | Certificate verification turned off in an HTTPS client | 295 |
PHP-COOKIE-001 | Cookies or the session cookie without HttpOnly / Secure | 1004, 614 |
The taint pass reports under php-taint-sql, php-taint-command, php-taint-xss, php-taint-file-inclusion, php-taint-code, php-taint-unserialize and php-taint-ssrf. Secrets, IaC and container checks run on a PHP repository as on any other.
Example
function find_user($conn) {
$q = "SELECT * FROM users WHERE id = " . $_GET['id'];
mysqli_query($conn, $q);
}
vulkro scan . reports this as php-taint-sql (CWE-89, High) on the mysqli_query line, with a two-step trace: the line where $_GET entered $q, then the sink. A finding with a trace is reported as proven.
Limits
- Short range. Taint is followed inside one function, plus one call: a request value passed to a project function whose parameter reaches a sink, or a project function that returns request data. A chain of two or more helper calls is not followed, and neither is data stored and read back later (second-order).
- Method calls are a guess. A call through
$obj->method()is matched by method name only, so such a finding has no trace and is reported at Medium confidence. A free function with one definition, orClass::methodon a unique class, carries a trace. - No endpoint map and no auth model. Routes, middleware and auth checks are not read, so the OWASP API Top 10 checks that need an endpoint (missing auth, IDOR, function-level authorization) do not run for PHP.
- No template escaping context. Blade
{!! !!}and Twig|raware not modelled. - Dynamic code is out of reach. Variable variables (
$$name), dynamic dispatch and runtimeincludepaths are not resolved. - Lockfile only. A project with
composer.jsonbut nocomposer.lockgets no dependency CVE check.
See Supported languages for how PHP compares with the other languages.