Posted by disclosure via Fulldisclosure on Oct 060day Rubbish Research Team is publicly disclosing a vulnerability in Maian
Cart 3.8, the self-hosted PHP shopping-cart system from Maian Media (Maian
Script World), verified end to end against a real installation of that
version.
Type: unrestricted upload of a file with a dangerous type (CWE-434), realized
as OS command execution (CWE-78) through an attacker-supplied PHP webshell,
with CWE-269 bearing on the execution privilege context. The...
Full Disclosure
mailing list archives
0day Rubbish Research Team is publicly disclosing a vulnerability in Maian Cart 3.8, the self-hosted PHP shopping-cart system from Maian Media (Maian Script World), verified end to end against a real installation of that version. Type: unrestricted upload of a file with a dangerous type (CWE-434), realized as OS command execution (CWE-78) through an attacker-supplied PHP webshell, with CWE-269 bearing on the execution privilege context. The Store Banners page of the administration backend (route /admin/index.php?p=settings&s=9) dispatches multipart uploads to addBanners() in admin/control/classes/class.system.php:376-413, which reads the raw client filename at line 386, derives the stored extension from it at line 392 with strrchr on the lower-cased name, composes the stored name at line 393 as the banner prefix plus a counter from the banners table plus that extension, and writes the file with move_uploaded_file at line 401, a second call site being recorded at line 410. There is no extension allow-list, no MIME or finfo validation and no getimagesize() content check anywhere on the path; mc_safeImport at line 377 is an SQL-escaping pass over $_POST and never touches $_FILES, and is_uploaded_file only confirms the file arrived over HTTP. With the shipped defaults RENAME_BANNERS=1 and BANNER_PREFIX='img_', an upload named shell.php is stored as img_1.php: the rename regenerates the base name and preserves the attacker's extension. The destination, content/_theme_default/images/banners/, is inside the document root and carries no deny rule, in explicit contrast to admin/import/.htaccess, admin/attachments/.htaccess and content/_theme_default/cache/.htaccess, all of which ship Deny from all, so a plain GET for the stored img_<id>.php is handed to the PHP interpreter and a one-line shell_exec webshell yields command execution in the web server process context. move_uploaded_file runs before the INSERT INTO banners statement, so the shell survives even a failing INSERT. The product's own image allow-list ($imgAllow at class.products.php:9, applied at :1213 and :1907 in addAdditionalProductPictures) is the one-line control this handler omits. Scoring. Four readings are published with their vectors, labelled, rather than one number: - PRIMARY, and the only classification this advisory claims: 7.2 High, CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H. The sink sits behind the webmaster gate at admin/control/system-load.php:79 (mc_isWebmasterLoggedIn, whose underlying test at functions.php:324-336 requires a fully populated webmaster session array). - CONDITIONAL, 9.8 Critical, CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H, for a deployment that retains an unchanged, known or guessable administrator credential. This reading is this advisory's own construction, not a figure produced by the research record, which scores the finding only under the 7.2 vector and records that the unauthenticated objective was not reached. It describes deployment state rather than product state: the product ships no fixed factory credential, the install wizard writes the operator-chosen USERNAME and PASSWORD into access.php, and no installation whose credentials were guessable was verified. Its sole basis is the companion observation that the installer-provisioned credential is never forced to rotate (a CWE-798-class note about persistent provisioned credentials without a rotation gate). It does not change the primary classification. - CONSIDERED AND NOT ADOPTED: 8.8 High, CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H, which differs from the primary vector only in Privileges Required and was not adopted because no role below webmaster reaches the settings module in the recorded gate model; and 9.1 High, CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H, which would require Scope Changed and does not fit, the vulnerable component and the impacted component being the same security authority. Authentication: administrator session. The login handler (admin/control/modules/system/portal.php:26-47) requires process=1, a cs_rf CSRF token matching the hidden field served on the login page, a user value equal to the USERNAME constant in access.php and a password verified through mc_PassHash; the trigger is isset($_POST['process_banners']) at settings.php:84-87. The unauthenticated objective was pursued and exhaustively ruled out: ajax.php exposes no file-write or command primitive an unauthenticated caller can steer; forging the persistent administrator cookie is infeasible because mc_encrypt(SECRET_KEY . DB_NAME) requires the per-installation database name, which is not remotely discoverable; the only gadget found was PHPMailer's __destruct, not exploitable into a dangerous sink in this codebase, so no object-population chain exists; the login path is not injectable; and every move_uploaded_file call site identified sits under the gated /admin/ backend. No unauthenticated route to the sink is claimed. Impact: arbitrary OS command execution as the web server process, uid=0(root) in the laboratory capture because the test web tier ran as root, typically www-data or apache in production. Full read and write access to store source code, application configuration including the database credentials the application itself reads, uploaded order attachments and the surrounding file system; database access with the application's own privileges, meaning the store's orders, customer records and settings; persistence, since the stored img_<id>.php outlives the administrator session and nothing in the product scans for or removes it; and a pivot into whatever segments the store host can reach. For an insider or credential-holder the upload is audit-invisible: it appears in the interface as an ordinary banner addition. Verification boundary, stated plainly. Verified end to end against a real Maian Cart 3.8 installation: the product's own install wizard run to completion on a Linux laboratory host, creating 62 database tables and populating the settings rows, with MySQL 8.0.46 on a dedicated laboratory schema and account, and the web tier being the PHP built-in server bound to loopback for laboratory safety. That process ran as root for the session in question, which is why uid=0(root) was observed and why the stored 39-byte shell, exactly the length of the one-line payload, was owned by root. Three independent runs reached command execution, the third being a from-scratch re-analysis pass by an independent reviewer who rebuilt the audit and shipped a second, distinctly named shell through the same handler, stored as img_3.php. An adversarial refutation review searched for a global upload sanitizer, a deny rule for the banners directory, FilesMatch restrictions on .php, getimagesize usage on this path, web-server configuration hardening and install-time hardening, found none, and left the finding standing. No vendor-hosted or third-party installation was touched, and no post-exploitation was performed beyond identity and file-system probes. What was not exercised: the production Apache and mod_php stack was not run, so execution under Apache rests on static evidence plus the research record's own assertion that Apache with mod_php is the default production deployment, an assertion not substantiated from a vendor document; a non-root process identity was not demonstrated, so the claim is command execution at whatever identity the web server holds; the updateBanners edit path, the TRADE_THEME_FOLDER branch and the dead-code uploadWebLogo variant (class.system.php:956, same pattern, no call site, excluded from the finding) were not exercised; only 3.8 was tested. One precondition is unresolved and stated rather than smoothed over: the banners directory must already exist on disk. It was absent in the first laboratory iteration, move_uploaded_file failed with a no-such-file-or-directory error, and the directory was created manually before a later iteration succeeded; move_uploaded_file does not create directories and the handler's only filesystem guard tests the destination file, not the directory. No artifact establishes which product component creates it, so no first-upload directory creation is claimed. Related product, scoped out. The research record notes that the sibling Maian Support product exhibits the same CWE-434-class pattern, a content tree without .htaccess protection combined with Apache AllowOverride All. That product is out of scope for this advisory: no exploitation attempt was made there and no identifier is claimed for it. Fix direction, in short: enforce an extension allow-list at the sink reusing the control already in class.products.php; validate uploaded content with getimagesize() or finfo; discard the client-supplied extension and name stored files server-side; make the banner storage non-executable by moving it outside the document root or shipping a deny rule plus documented nginx-equivalent guidance; route every move_uploaded_file call site through one shared validator; force first-login rotation of the installer-provisioned administrator credential; and remove or gate the dead uploadWebLogo code. Full technical analysis and a reproducible proof-of-concept: https://0day-rubbish.com/blog/maian-cart-addbanners-upload-extension-webshell-rce Project archive (ongoing disclosure series): https://github.com/Exploit-Garbage/0day-Rubbish The vendor has been notified through the support mailbox it publishes for its products. No vulnerability identifier has been assigned to this finding yet. -- 0day Rubbish Research Team disclosure () 0day-rubbish com https://0day-rubbish.com _______________________________________________ Sent through the Full Disclosure mailing list https://nmap.org/mailman/listinfo/fulldisclosure Web Archives & RSS: https://seclists.org/fulldisclosure/