PHP Allows Remote File Inclusion
Checks whether include() and require() may load code from URLs.
- Why It Matters
- With allow_url_include on, a single file-inclusion bug lets an attacker run their own PHP code from their own server (remote file inclusion).
- How It Detects
- Asks the PHP command-line binary for ini_get('allow_url_include') (the only PHP code OpenVAPT may run). Web PHP-FPM pools can use a different php.ini; verify those too.
- How to Fix
- In php.ini set allow_url_include = Off (the default since PHP 5.2) and restart PHP.
- How to Verify
php -r "echo ini_get('allow_url_include');" and check the FPM pool's php.ini or php_admin_value settings.- CVSS v3.1 Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H- Control Category
- Secure Configuration: CIS Controls v8 4; NIST CSF 2.0 PR.PS
- Fix Effort
- Low
- CIS Controls v8 Safeguard
- 16.7
- NIST CSF 2.0 Subcategory
- PR.PS-01
- CWE
- CWE-98
- OWASP Top 10
- A03:2021, A05:2021
- OWASP ASVS 4.0.3
- 12.3.3
- NIST SP 800-53 Rev. 5
- CM-6, CM-7
- References
- PHP manual, php.ini directives
OWASP PHP Configuration Cheat Sheet
Source Code
The complete file that defines this check. Run it with openvapt scan --only WEB-PHP-003, or read it offline with openvapt explain WEB-PHP-003.
internal/checks/web/php.go · Lines 1-177View on GitHub at This Commit
| 1 | package web |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "regexp" |
| 6 | "strconv" |
| 7 | "strings" |
| 8 | |
| 9 | "github.com/openvapt/openvapt/internal/check" |
| 10 | "github.com/openvapt/openvapt/internal/sys" |
| 11 | ) |
| 12 | |
| 13 | var refPHPIni = check.Ref{Title: "PHP manual, php.ini directives", URL: "https://www.php.net/manual/en/ini.list.php"} |
| 14 | |
| 15 | // phpIni asks the PHP CLI for configuration values using only echo ini_get(), |
| 16 | // which is enforced by the sys allow-list. Note: the CLI may use a different |
| 17 | // php.ini from PHP-FPM; the evidence says which binary was asked. |
| 18 | func phpIni(ctx context.Context, s sys.System, keys ...string) (map[string]string, bool) { |
| 19 | var b strings.Builder |
| 20 | for _, k := range keys { |
| 21 | b.WriteString("echo ini_get('" + k + "'),\"\\n\";") |
| 22 | } |
| 23 | out, err := s.Run(ctx, "php", "-r", b.String()) |
| 24 | if err != nil { |
| 25 | return nil, false |
| 26 | } |
| 27 | vals := strings.Split(out, "\n") |
| 28 | m := map[string]string{} |
| 29 | for i, k := range keys { |
| 30 | if i < len(vals) { |
| 31 | m[k] = strings.TrimSpace(vals[i]) |
| 32 | } |
| 33 | } |
| 34 | return m, true |
| 35 | } |
| 36 | |
| 37 | func on(v string) bool { |
| 38 | v = strings.ToLower(v) |
| 39 | return v == "1" || v == "on" || v == "yes" || v == "true" || v == "stdout" || v == "stderr" |
| 40 | } |
| 41 | |
| 42 | // phpEOL: end of security support per https://www.php.net/supported-versions.php |
| 43 | var phpEOL = map[string]string{ |
| 44 | "5.6": "2018-12-31", "7.0": "2019-01-10", "7.1": "2019-12-01", "7.2": "2020-11-30", "7.3": "2021-12-06", |
| 45 | "7.4": "2022-11-28", "8.0": "2023-11-26", "8.1": "2025-12-31", "8.2": "2026-12-31", "8.3": "2027-12-31", "8.4": "2028-12-31", "8.5": "2029-12-31", |
| 46 | } |
| 47 | |
| 48 | func init() { |
| 49 | iniRule := func(id, title, key, summary, rationale, fix string, sev check.Severity, cvss string, std check.Standards, bad func(string) bool) { |
| 50 | check.Register(&check.Check{ |
| 51 | ID: id, Title: title, Domain: check.DomainWeb, Severity: sev, |
| 52 | CVSS: cvss, |
| 53 | Summary: summary, |
| 54 | Rationale: rationale, |
| 55 | Detection: "Asks the PHP command-line binary for ini_get('" + key + "') (the only PHP code OpenVAPT may run). Web PHP-FPM pools can use a different php.ini; verify those too.", |
| 56 | Fix: fix, |
| 57 | Verify: "php -r \"echo ini_get('" + key + "');\" and check the FPM pool's php.ini or php_admin_value settings.", |
| 58 | Refs: []check.Ref{refPHPIni, {Title: "OWASP PHP Configuration Cheat Sheet", URL: "https://cheatsheetseries.owasp.org/cheatsheets/PHP_Configuration_Cheat_Sheet.html"}}, |
| 59 | Standards: std, |
| 60 | Run: func(ctx context.Context, s sys.System) []check.Result { |
| 61 | m, ok := phpIni(ctx, s, key) |
| 62 | if !ok { |
| 63 | return []check.Result{check.SkipR("php", "PHP command-line binary not found")} |
| 64 | } |
| 65 | if bad(m[key]) { |
| 66 | return []check.Result{check.FailR("php", key+" = "+m[key])} |
| 67 | } |
| 68 | return []check.Result{check.PassR("php", key+" = "+orOff(m[key]))} |
| 69 | }, |
| 70 | }) |
| 71 | } |
| 72 | iniRule("WEB-PHP-001", "PHP Displays Errors to Visitors", "display_errors", |
| 73 | "Checks whether PHP prints error messages into web pages.", |
| 74 | "Error messages reveal file paths, SQL queries and code structure, and can leak data. They help attackers turn a small bug into a working exploit.", |
| 75 | "In php.ini set display_errors = Off and log_errors = On , then restart PHP-FPM or the web server.", check.Medium, "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", check.Standards{CWE: []string{"CWE-209"}, OWASP: []string{"A05:2021"}, ASVS: []string{"14.3.2"}, WSTG: []string{"WSTG-ERRH-01"}, NIST: []string{"SI-11", "CM-6"}}, on) |
| 76 | iniRule("WEB-PHP-002", "PHP Version Exposed in Headers", "expose_php", |
| 77 | "Checks whether PHP adds an X-Powered-By header with its version.", |
| 78 | "The header advertises the exact PHP version to every visitor and scanner.", |
| 79 | "In php.ini set expose_php = Off and restart PHP.", check.Low, "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", check.Standards{CWE: []string{"CWE-497"}, OWASP: []string{"A05:2021"}, ASVS: []string{"14.3.3"}, WSTG: []string{"WSTG-INFO-02"}, NIST: []string{"CM-7", "SI-11"}}, on) |
| 80 | iniRule("WEB-PHP-003", "PHP Allows Remote File Inclusion", "allow_url_include", |
| 81 | "Checks whether include() and require() may load code from URLs.", |
| 82 | "With allow_url_include on, a single file-inclusion bug lets an attacker run their own PHP code from their own server (remote file inclusion).", |
| 83 | "In php.ini set allow_url_include = Off (the default since PHP 5.2) and restart PHP.", check.High, "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", check.Standards{CWE: []string{"CWE-98"}, OWASP: []string{"A03:2021", "A05:2021"}, ASVS: []string{"12.3.3"}, NIST: []string{"CM-6", "CM-7"}}, on) |
| 84 | |
| 85 | check.Register(&check.Check{ |
| 86 | ID: "WEB-PHP-004", |
| 87 | Title: "PHP Version Past End of Security Support", |
| 88 | Domain: check.DomainWeb, |
| 89 | Severity: check.High, |
| 90 | CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", |
| 91 | Summary: "Checks whether the installed PHP branch still receives security fixes from the PHP project.", |
| 92 | Rationale: "Unsupported PHP versions never get fixes for newly found vulnerabilities, some of which allow remote code execution.", |
| 93 | Detection: "Runs php -v, extracts the major.minor version and compares it with the PHP project's published end-of-security-support dates. Distribution vendors may backport fixes; the evidence notes this.", |
| 94 | Fix: "Upgrade to a supported PHP branch (see php.net/supported-versions) and test the applications, or confirm your distribution still backports security fixes to this version.", |
| 95 | Verify: "php -v", |
| 96 | Refs: []check.Ref{{Title: "PHP, Supported Versions", URL: "https://www.php.net/supported-versions.php"}}, |
| 97 | Standards: check.Standards{CWE: []string{"CWE-1104"}, OWASP: []string{"A06:2021"}, ASVS: []string{"14.2.1"}, NIST: []string{"SI-2"}}, |
| 98 | Run: func(ctx context.Context, s sys.System) []check.Result { |
| 99 | out, err := s.Run(ctx, "php", "-v") |
| 100 | if err != nil { |
| 101 | return []check.Result{check.SkipR("php", "PHP command-line binary not found")} |
| 102 | } |
| 103 | m := regexp.MustCompile(`PHP (\d+\.\d+)\.(\d+)`).FindStringSubmatch(out) |
| 104 | if m == nil { |
| 105 | return []check.Result{check.SkipR("php", "PHP version not recognised")} |
| 106 | } |
| 107 | end, ok := phpEOL[m[1]] |
| 108 | if !ok && phpOlderThanTable(m[1]) { |
| 109 | return []check.Result{check.FailR("php", "PHP "+m[1]+" is older than every branch in the support table (oldest 5.6, ended 2018-12-31)", "Check whether your distribution backports security fixes to this version")} |
| 110 | } |
| 111 | if !ok { |
| 112 | return []check.Result{check.SkipR("php", "PHP "+m[1]+" not in the support table")} |
| 113 | } |
| 114 | if end < today() { |
| 115 | return []check.Result{check.FailR("php", "PHP "+m[1]+" security support ended on "+end, "Check whether your distribution backports security fixes to this version")} |
| 116 | } |
| 117 | return []check.Result{check.PassR("php", "PHP "+m[1]+" supported until "+end)} |
| 118 | }, |
| 119 | }) |
| 120 | } |
| 121 | |
| 122 | func orOff(v string) string { |
| 123 | if v == "" { |
| 124 | return "Off" |
| 125 | } |
| 126 | return v |
| 127 | } |
| 128 | |
| 129 | func init() { |
| 130 | check.Register(&check.Check{ |
| 131 | ID: "WEB-PHP-005", Title: "PHP Session Cookies Not Hardened", Domain: check.DomainWeb, |
| 132 | Severity: check.Medium, Category: check.CatWebApp, Effort: check.EffortLow, |
| 133 | CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:N", |
| 134 | Summary: "Checks PHP's session settings: HttpOnly and SameSite cookies and strict session id mode.", |
| 135 | Rationale: "Session cookies are the keys to logged-in accounts. Without HttpOnly any XSS bug can steal them; without strict mode PHP accepts attacker-chosen session ids (session fixation).", |
| 136 | Detection: "Asks the PHP command-line binary for ini_get('session.cookie_httponly'), ini_get('session.use_strict_mode') and ini_get('session.cookie_samesite'). PHP-FPM pools may use a different php.ini; verify those too.", |
| 137 | Fix: "In php.ini set session.cookie_httponly = 1 , session.use_strict_mode = 1 , session.cookie_samesite = Lax and, for HTTPS sites, session.cookie_secure = 1 , then restart PHP-FPM.", |
| 138 | Verify: "php -r \"echo ini_get('session.cookie_httponly'), ini_get('session.use_strict_mode'), ini_get('session.cookie_samesite');\"", |
| 139 | Refs: []check.Ref{refPHPIni, {Title: "PHP manual, Session security", URL: "https://www.php.net/manual/en/session.security.php"}}, |
| 140 | Standards: check.Standards{CWE: []string{"CWE-1004", "CWE-384"}, OWASP: []string{"A07:2021"}, ASVS: []string{"3.4.2", "3.2.1"}, WSTG: []string{"WSTG-SESS-02", "WSTG-SESS-03"}, NIST: []string{"SC-23", "CM-6"}}, |
| 141 | Run: func(ctx context.Context, s sys.System) []check.Result { |
| 142 | m, ok := phpIni(ctx, s, "session.cookie_httponly", "session.use_strict_mode", "session.cookie_samesite") |
| 143 | if !ok { |
| 144 | return []check.Result{check.SkipR("php", "PHP command-line binary not found")} |
| 145 | } |
| 146 | var bad []string |
| 147 | if !on(m["session.cookie_httponly"]) { |
| 148 | bad = append(bad, "session.cookie_httponly = "+orOff(m["session.cookie_httponly"])) |
| 149 | } |
| 150 | if !on(m["session.use_strict_mode"]) { |
| 151 | bad = append(bad, "session.use_strict_mode = "+orOff(m["session.use_strict_mode"])) |
| 152 | } |
| 153 | if strings.TrimSpace(m["session.cookie_samesite"]) == "" { |
| 154 | bad = append(bad, "session.cookie_samesite is empty") |
| 155 | } |
| 156 | if len(bad) > 0 { |
| 157 | return []check.Result{check.FailR("php", "PHP session cookies are not hardened", bad...)} |
| 158 | } |
| 159 | return []check.Result{check.PassR("php", "PHP session cookies are hardened")} |
| 160 | }, |
| 161 | }) |
| 162 | } |
| 163 | |
| 164 | // phpOlderThanTable reports a major.minor version below 5.6, the oldest row |
| 165 | // of phpEOL; every such branch is long past end of support. |
| 166 | func phpOlderThanTable(v string) bool { |
| 167 | maj, min, ok := strings.Cut(v, ".") |
| 168 | if !ok { |
| 169 | return false |
| 170 | } |
| 171 | a, err1 := strconv.Atoi(maj) |
| 172 | b, err2 := strconv.Atoi(min) |
| 173 | if err1 != nil || err2 != nil { |
| 174 | return false |
| 175 | } |
| 176 | return a < 5 || a == 5 && b < 6 |
| 177 | } |
Also available as plain Markdown for notes, scripts and AI tools. Same facts, same source, no styling.
Cite This Page
If this page helped your work, please cite it. The version and web address are filled in; the access date is today's date in your browser. Every other style, and when credit is required, is on How to Cite OpenVAPT.
OpenVAPT. (2026). WEB-PHP-003 PHP Allows Remote File Inclusion (Version 0.1.0-alpha.6). OpenVAPT Check Catalogue. Retrieved October 16, 2026, from https://openvapt.com/checks/WEB-PHP-003.html
In text: (OpenVAPT, 2026). Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.
@misc{openvapt_web_php_003,
author = {{OpenVAPT}},
title = {{WEB-PHP-003 PHP Allows Remote File Inclusion}},
year = {2026},
howpublished = {\url{https://openvapt.com/checks/WEB-PHP-003.html}},
note = {OpenVAPT Check Catalogue, version 0.1.0-alpha.6. Accessed 16 October 2026}
}
Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.
Citing OpenVAPT does not mean OpenVAPT endorses, approves or has reviewed your work.