← Back to Feed CACHED · 2026-10-07 05:08:31 · CACHE_KEY CVE-2026-63223
CVE-2026-63223 · CWE-434 · Disclosed 2026-07-31

CodeIgniter 4 is_image/mime_in File Upload Extension Validation Bypass (RCE)

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

A bouncer who only checks your ID photo but never reads the name — anyone with a stolen headshot walks right in

CVE-2026-63223 is an unrestricted file upload flaw in CodeIgniter 4's is_image and mime_in validation rules. These rules inspect only the content-derived MIME type (magic bytes like GIF89a or \x89PNG) but never verify the client-supplied filename extension. An attacker prepends image magic bytes to a PHP webshell, names it shell.php, and the validator waves it through. All CodeIgniter 4 versions from 4.0.0-alpha.3 through 4.7.3 are affected; the fix ships in v4.7.4. The attack is unauthenticated when the upload endpoint is publicly accessible.

The vendor's CVSS 9.8 CRITICAL reflects the *worst-case outcome* — unauthenticated RCE — and the CVSS vector is technically correct for that scenario. However, the 9.8 silently assumes three application-level conditions all hold: the app uses is_image/mime_in *without* an independent ext_in check, the app saves files using the original client filename, and uploads land in a web-accessible directory where PHP execution is enabled. Each condition is individually common in tutorial-grade CI4 code, but the simultaneous AND of all three plus a publicly-reachable upload endpoint narrows the truly-exploitable population substantially. The CVSS score does not model this conditional chain, making the vendor label an overstatement for fleet-wide prioritization.

"Framework-level upload bypass is real, but three app-level conditions gate the chain to RCE"
02 · The Attack Path

5 steps from start to impact.

STEP 01

Identify a CodeIgniter 4 upload endpoint

The attacker discovers a web application running CodeIgniter 4 < 4.7.4 with a file upload form. CI4 fingerprints (X-Powered-By headers, default error pages, /writable/ path leaks) or Wappalyzer-style technology detection can reveal the framework. The upload endpoint must accept file submissions — profile pictures, document uploads, avatar changes, etc.
Conditions required:
  • Target runs CodeIgniter 4 < 4.7.4
  • A file upload endpoint is reachable from the attacker's network position
Where this breaks in practice:
  • Not all CI4 apps expose upload functionality
  • Many upload endpoints require authentication, reducing the unauthenticated attack surface
Detection/coverage: Wappalyzer, BuiltWith, or custom fingerprinting scripts can identify CI4 installs. Web scanners (Burp Suite, Nikto) flag common upload paths.
STEP 02

Craft a polyglot PHP-image payload

The attacker prepends image magic bytes (GIF89a, \xFF\xD8\xFF\xE0, or \x89PNG\r\n\x1A\n) to a PHP webshell payload (e.g., <?php system($_GET['cmd']); ?>). The resulting file has a .php extension but begins with valid image headers. The imbas007/CVE-2026-63223-POC Python script automates this step, generating the polyglot and uploading it in one command.
Conditions required:
  • Knowledge of the magic bytes trick (widely documented, automated by public PoC)
Where this breaks in practice:
  • Essentially zero — the technique is decades old and fully automated by the published PoC
Detection/coverage: WAFs with deep content inspection (ModSecurity with OWASP CRS ≥ 4.x) can detect PHP tags inside image uploads. Yara rules matching GIF89a followed by <?php catch this pattern.
STEP 03

Upload passes is_image / mime_in validation

The CI4 validation layer calls finfo_file() or equivalent, which reads the magic bytes and returns an image MIME type. The is_image rule confirms it is a recognized image type; mime_in confirms the MIME is in the allowed list. Neither rule inspects the .php extension. The file is accepted.
Conditions required:
  • Application uses is_image or mime_in without a companion ext_in rule
  • Application preserves the client-supplied filename (does not call getRandomName())
Where this breaks in practice:
  • Apps that add ext_in[jpg,png,gif] alongside is_image block this step entirely
  • Apps using getRandomName() strip the .php extension, neutralizing the payload
  • Security-conscious CI4 configurations are not uncommon — the official docs mention ext_in
Detection/coverage: Static analysis tools (Psalm, PHPStan with security rules) can flag missing ext_in in upload validation chains. SAST scanners detect move() with client filename.
STEP 04

File stored in web-accessible directory with PHP execution

The application moves the uploaded file into a directory under the web root (e.g., /public/uploads/) where the web server (Apache, Nginx) is configured to execute .php files. The attacker now has a webshell at a predictable URL path.
Conditions required:
  • Uploads stored under web root
  • PHP execution not disabled for the upload directory
Where this breaks in practice:
  • Best practice is to store uploads outside web root or in cloud object storage (S3, GCS)
  • Adding php_flag engine off (.htaccess) or location ~ \.php$ { deny all; } (Nginx) in the upload directory blocks execution
  • Containerized deployments with read-only filesystem layers prevent writes to web root
Detection/coverage: File integrity monitoring (OSSEC, Wazuh, Tripwire) alerts on new .php files in upload directories. EDR detects PHP process spawning shell commands.
STEP 05

Webshell execution — full RCE

The attacker requests the uploaded PHP file via HTTP (e.g., GET /uploads/shell.php?cmd=id). The web server parses it as PHP, executes the embedded code, and returns command output. From here the attacker has code execution as the web server user (typically www-data or apache), with access to database credentials in .env, session storage, and potential lateral movement vectors.
Conditions required:
  • Web server processes .php files in the upload directory
  • No application-level access control on the uploads path
Where this breaks in practice:
  • SELinux/AppArmor policies may confine the web server process
  • Cloud-native deployments on read-only containers limit post-exploitation pivoting
Detection/coverage: EDR/HIDS detects anomalous child processes from PHP-FPM/Apache (e.g., sh, whoami, curl). SIEM rules for webshell indicators (encoded eval, base64 decode in access logs). GreyNoise and honeypot networks would detect scanning for uploaded shells.
03 · Compensating Control

1
HIGH 7.8→LOW 2.0
SEVERITY REDUCED
Add ext_in[jpg,png,gif,webp] to all upload validation rule sets — The root cause is missing extension validation. Adding ext_in alongside is_image/mime_in rejects any file whose client-supplied extension is not in the safe list, neutralizing the polyglot attack entirely. This is a one-line code change per upload handler. Deploy within 30 days per the noisgate mitigation SLA for HIGH.
2
HIGH 7.8→LOW 2.5
SEVERITY REDUCED
Switch to randomized filenames via $file->getRandomName() — Even if a .php payload passes validation, renaming it to a random string with a safe extension (e.g., 1696012345_abc123.jpg) strips the executable extension. The webshell cannot be triggered. Deploy within 30 days.
3
HIGH 7.8→LOW 2.0
SEVERITY REDUCED
Disable PHP execution in upload directories — Add php_flag engine off in .htaccess (Apache) or location ~ \.php$ { deny all; } (Nginx) for the uploads path. Even if a .php file lands in the directory, the web server returns it as plaintext or 403. This is an infrastructure-level control independent of application code. Deploy within 30 days.
4
HIGH 7.8→IGNORE 0.5
SEVERITY REDUCED
Move uploads outside the web root or to object storage (S3/GCS) — Storing uploads in a non-web-accessible path (e.g., /var/uploads/) or cloud object storage eliminates the ability to request the file via HTTP and trigger PHP execution. Serve uploads through an application-controlled download endpoint instead. Deploy within 30 days.
5
HIGH 7.8→MEDIUM 5.5
SEVERITY REDUCED
Deploy WAF rules blocking PHP content in image uploads — ModSecurity with OWASP CRS ≥ 4.x or Cloudflare WAF custom rules can detect <?php or <?= inside multipart file upload bodies. This provides defense-in-depth but is bypassable with encoding tricks (short tags, base64 eval), so treat as a supplementary layer only. Deploy within 30 days.
What doesn't work
  • CSP headers — Content-Security-Policy governs browser-side script execution, not server-side PHP interpretation. A webshell executes on the server; CSP is irrelevant.
  • Rate limiting on the upload endpoint — Slows brute-force but doesn't prevent a single successful upload. The attacker needs exactly one request.
  • Antivirus scanning of uploads — Most AV engines detect known webshell signatures but the polyglot technique with custom payloads easily evades signature-based scanning. Not a reliable control.
  • MIME type validation alone (the vulnerable control itself) — Adding more MIME checks without extension validation is the exact flaw this CVE describes. Do not rely on mime_in as a fix for is_image or vice versa.
04 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationNo confirmed in-the-wild exploitation. Not listed on CISA KEV. No campaigns attributed. GreyNoise shows no tagged scanning activity for this CVE as of 2026-10-07.
Proof-of-ConceptPublic, weaponized. imbas007/CVE-2026-63223-POC — Python script automating payload generation, upload, and interactive shell. Also referenced in the asvorg/CVE-2026-PoCs and SecureWithUmer/CVE-2026-PoCs community collections. A second PoC by shinthink was noted in search results. No Nuclei template confirmed yet.
EPSS0.008 (0.8%) — well below the 90th percentile threshold (~0.10). Indicates low predicted exploitation probability in the next 30 days despite the CRITICAL CVSS label.
CISA KEV StatusNot listed as of 2026-10-07.
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H — Network-reachable, no authentication, no user interaction, full CIA impact. The vector is technically accurate for the worst-case scenario but does not model the three application-level prerequisites.
Affected VersionsCodeIgniter 4 from 4.0.0-alpha.3 through 4.7.3 inclusive. All minor branches (4.0.x–4.7.x) are affected.
Fixed VersionCodeIgniter 4.7.4 (patch commit b6e9a4f). Adds cross-validation of getClientExtension() against guessExtension() in upload rules.
Exposure / Installed BaseApproximately 40,000–70,000 websites running CodeIgniter (Datanyze, Wappalyzer, WebTechSurvey). ~0.76% market share in web frameworks (Enlyft). Top markets: India (23%), United States, Brazil. Named users include Bank Rakyat Indonesia, Supreme Court of India.
Disclosure Date2026-07-31 (GHSA-mmj4-63m4-r6h5 published simultaneously with the v4.7.4 fix release).
ReporterNot publicly attributed in the GHSA. PoC published by imbas007 (GitHub).

Sources.

  1. GitHub Security Advisory (GHSA-mmj4-63m4-r6h5)
  2. OSV Vulnerability Database
  3. imbas007 PoC Repository
  4. SecurityOnline — CodeIgniter4 RCE Analysis
  5. GitLab Advisory Database
  6. OpenCVE — CVE-2026-63223 Detail
  7. CI4 Upload Documentation (v4.7.4)
  8. Patch Commit b6e9a4f
05 · The Call

Final Verdict
↓ DOWNGRADED to HIGH (7.8/10)

Why this verdict

  • Conditional chain narrows exploitable population: The CVSS 9.8 assumes a clean path to RCE, but three application-level conditions must *all* hold: is_image/mime_in used without ext_in, client filename preserved, and uploads in a PHP-executable web directory. Each condition independently filters out a material share of CI4 installs. The AND of all three plus an unauthenticated upload endpoint plausibly covers ~5–15% of the CI4 installed base. This compounding friction justifies pulling below CRITICAL.
  • PoC availability holds severity above MEDIUM: A fully weaponized Python exploit (imbas007) exists with single-target and mass-scan modes. The magic-bytes technique is trivial and decades old. Any attacker who finds a qualifying upload endpoint can exploit it with zero skill. This keeps the floor at HIGH.
  • EPSS corroborates limited real-world traction: At 0.008 (0.8%), EPSS is well below the exploitation-activity threshold. No KEV listing, no GreyNoise tags, no attributed campaigns. The gap between the scary CVSS label and actual exploitation evidence is substantial.
  • Role multiplier: CodeIgniter 4 is a general-purpose PHP web framework — it is *not* a canonical high-value infrastructure component (not an IdP, hypervisor, PAM, backup, DC role, or network edge appliance). (a) *Low-value role:* dev sandboxes, hobby sites — many of the ~40–70k installs. RCE impact is negligible. (b) *Typical role:* line-of-business web apps, internal portals, CMS. RCE yields www-data shell + database credentials — host-level blast radius. (c) *High-value role:* banking portals (Bank Rakyat Indonesia), government apps (Supreme Court of India). These represent <5% of CI4's installed base. When the chain succeeds here, blast radius is data-tier (customer PII, financial records) but still host-scoped — not domain/fleet/supply-chain scale. No canonical ≥10% high-value-role floor applies. The floor is HIGH based on weaponized PoC + unauthenticated RCE when conditions hold.

Why not higher?

Elevating to CRITICAL would require that the exploit chain succeed across the majority of CI4 installs without additional application-level conditions. In practice, three non-trivial preconditions must simultaneously hold, and the installed base is modest (~40–70k sites, <1% of PHP framework market). EPSS at 0.8% and zero KEV/in-the-wild signal confirm the theoretical CVSS 9.8 is not translating to real-world CRITICAL urgency.

Why not lower?

Dropping to MEDIUM would underweight the fact that each individual precondition is common in tutorial-following CI4 projects, the exploit is fully automated and unauthenticated, and the end state is unrestricted RCE — not information disclosure or DoS. A weaponized unauthenticated RCE PoC with a plausible 5–15% hit rate on the installed base merits HIGH.

06 · Verification

Crowdsourced verification payload.

Run this script on each host running a CodeIgniter 4 application. It checks the installed framework version via Composer's installed.json. Execute as any user with read access to the application directory: bash check_cve_2026_63223.sh /var/www/myapp

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# CVE-2026-63223 Checker — CodeIgniter 4 is_image/mime_in upload bypass
# Usage: bash check_cve_2026_63223.sh /path/to/ci4/project
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN

set -euo pipefail

APP_DIR="${1:-}"

if [[ -z "$APP_DIR" ]]; then
  echo "Usage: $0 /path/to/ci4/project"
  exit 2
fi

# Locate composer installed.json
INSTALLED="$APP_DIR/vendor/composer/installed.json"
if [[ ! -f "$INSTALLED" ]]; then
  echo "UNKNOWN — Could not find $INSTALLED. Is this a Composer-managed CI4 project?"
  exit 2
fi

# Extract codeigniter4/framework version
VERSION=$(python3 -c "
import json, sys
with open('$INSTALLED') as f:
    data = json.load(f)
    packages = data.get('packages', data) if isinstance(data, dict) else data
    for pkg in packages:
        name = pkg.get('name', '')
        if name == 'codeigniter4/framework':
            v = pkg.get('version', pkg.get('version_normalized', ''))
            print(v.lstrip('v'))
            sys.exit(0)
print('NOT_FOUND')
" 2>/dev/null)

if [[ "$VERSION" == "NOT_FOUND" || -z "$VERSION" ]]; then
  echo "UNKNOWN — codeigniter4/framework not found in $INSTALLED"
  exit 2
fi

echo "Detected CodeIgniter4 version: $VERSION"

# Compare version — vulnerable if < 4.7.4
REQUIRED="4.7.4"
if python3 -c "
from packaging.version import Version
import sys
try:
    if Version('$VERSION') >= Version('$REQUIRED'):
        sys.exit(0)
    else:
        sys.exit(1)
except Exception:
    sys.exit(2)
" 2>/dev/null; then
  echo "PATCHED — CodeIgniter4 $VERSION >= $REQUIRED (CVE-2026-63223 fixed)"
  exit 0
else
  RC=$?
  if [[ $RC -eq 1 ]]; then
    echo "VULNERABLE — CodeIgniter4 $VERSION < $REQUIRED (CVE-2026-63223 applies)"
    # Bonus: check if ext_in is used alongside is_image/mime_in in app code
    if grep -rq 'ext_in' "$APP_DIR/app/" 2>/dev/null; then
      echo "  NOTE: ext_in rule found in application code — may be partially mitigated"
    else
      echo "  WARNING: No ext_in rule found — application likely exploitable if uploads are present"
    fi
    exit 1
  else
    echo "UNKNOWN — Could not parse version '$VERSION'"
    exit 2
  fi
fi