Thursday, March 19, 2015

SQLMAP Web GUI

It has certainly been a while :)

The last few weeks I have been teaching myself a little PHP to help improve my skills and knowledge. In the process I decided to try and make a Web GUI for SQLMAP. When I originally started I was unaware of the JSON API that they already have available through sqlmapapi.py (available from latest versions in github repo). The API itself is not documented anywhere really so I took it as a small challenge to see what I might be able to slap together. You can find most of the API functionality documented to best of my ability in the SQLMAPClientAPI.class.php file I wrote, hopefully it will be helpful to others in the future that look to expand or write cooler GUI's and apps for the API.

Quick View of the core SQLMAPClientAPI.class.php:
Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. <?php
  2.  
  3.   include("config.php");
  4.  
  5.   /*
  6.      Ghetto Check if file is binary or ascii
  7.      This way we can determine if we can print the contents in textarea or not
  8.      Returns True if file is ascii, False otherwise
  9.   */
  10.   function is_ascii($sourcefile) {
  11.     if (is_file($sourcefile)) {
  12.       $content = str_replace(array("\n", "\r", "\t", "\v", "\b"), '', file_get_contents($sourcefile));
  13.       return ctype_print($content);
  14.     } else {
  15.       return false;
  16.     }
  17.   }
  18.  
  19.   /*
  20.      Check if array is a associated (hash) array vs a simple list array
  21.      Returns True when array is an associative array (key:value pairings)
  22.         Returns False otherwise
  23.  
  24.      Borrowed from stackoverflow discussion:
  25.        http://stackoverflow.com/questions/5996749/determine-whether-an-array-is-associative-hash-or-not
  26.   */
  27.   function is_assoc(array $array) {
  28.     $keys = array_keys($array); // Keys of the array
  29.     return array_keys($keys) !== $keys;
  30.   }
  31.  
  32.  
  33.   // SQLMAP Rest API Client Communicator Class
  34.   class SQLMAPClientAPI {
  35.     private $api = API_URL;                         // REST API Server Address
  36.     public $task_id;                                // Task ID to track against
  37.  
  38.     /*
  39.       Initialize our Class Object
  40.       Set the task id for new instance
  41.     */
  42.     public function __construct() {
  43. // Commented out to avoid spinning up unneccary tasks for admin
  44. // Manually call the generateNewTaskID() function to set...
  45. //      $this->task_id = $this->generateNewTaskID();
  46.     }
  47.  
  48.  
  49.     /*
  50.        Start up a new Task
  51.        Returns the new task id on success, false otherwise
  52.     */
  53.     public function generateNewTaskID() {
  54.       $json = json_decode(file_get_contents($this->api . "task/new"), true);
  55.       if(($json['success'] == "true") && (trim($json['taskid']) != "")) {
  56.         return trim($json['taskid']);
  57.       }
  58.       return NULL;
  59.     }
  60.  
  61.  
  62.     /*
  63.        Delete an Active Task by ID
  64.        Returns true on success, false otherwise
  65.     */
  66.     public function deleteTaskID($id) {
  67.       $json = json_decode(file_get_contents($this->api . "task/" . $id . "/delete"), true);
  68.       if($json['success'] == "true") {
  69.         return true;
  70.       }
  71.       return false;
  72.     }
  73.  
  74.  
  75.     /*
  76.        Lists current tasks with ADMIN ID, NOT task id
  77.        Returns associative array on success, false otherwise
  78.           array(
  79.             'tasks_num' => int,
  80.             'tasks' => array('1eb32c498dd90fb3','e398810ea2603520',...)
  81.           )'
  82.     */
  83.     public function adminListTasks($adminid) {
  84.       $json = json_decode(file_get_contents($this->api . "admin/" . $adminid . "/list"), true);
  85.       if($json['success'] == "true") {
  86.         return array('tasks_num' => $json['tasks_num'], 'tasks' => $json['tasks']);
  87.       }
  88.       return false;
  89.     }
  90.  
  91.  
  92.     /*
  93.        Flush all tasks with ADMIN ID, NOT task id
  94.        Returns true on success, false otherwise
  95.     */
  96.     public function adminFlushTasks($adminid) {
  97.       $json = json_decode(file_get_contents($this->api . "admin/" . $adminid . "/flush"), true);
  98.       if($json['success'] == "true") {
  99.         return true;
  100.       }
  101.       return false;
  102.     }
  103.  
  104.  
  105.     /*
  106.        List the currently set options under particular task ID
  107.        Returns an associative array on success, false otherwise
  108.     {
  109.         "options": {
  110.         "taskid": "e398810ea2603520",
  111.         "agent": null,
  112.         "alert": null,
  113.         "answers": null,
  114.         "api": true,
  115.         "authCred": null,
  116.         "authPrivate": null,
  117.         "authType": null,
  118.         "batch": true,
  119.         "beep": false,
  120.         "binaryFields": null,
  121.         "bulkFile": null,
  122.         "charset": null,
  123.         "checkTor": false,
  124.         "cleanup": false,
  125.         "code": null,
  126.         "col": null,
  127.         "commonColumns": false,
  128.         "commonTables": false,
  129.         "configFile": null,
  130.         "cookie": null,
  131.         "cookieDel": null,
  132.         "cpuThrottle": 5,
  133.         "crawlDepth": null,
  134.         "csrfToken": null,
  135.         "csrfUrl": null,
  136.         "csvDel": ",",
  137.         "data": null,
  138.         "database": "/tmp/sqlmapipc-rA0QdN",
  139.         "db": null,
  140.         "dbms": null,
  141.         "dbmsCred": null,
  142.         "delay": 0,
  143.         "dependencies": false,
  144.         "dFile": null,
  145.         "direct": null,
  146.         "disableColoring": true,
  147.         "dnsName": null,
  148.         "dropSetCookie": false,
  149.         "dummy": false,
  150.         "dumpAll": false,
  151.         "dumpFormat": "CSV",
  152.         "dumpTable": false,
  153.         "dumpWhere": null,
  154.         "eta": false,
  155.         "excludeCol": null,
  156.         "excludeSysDbs": false,
  157.         "extensiveFp": false,
  158.         "evalCode": null,
  159.         "firstChar": null,
  160.         "flushSession": false,
  161.         "forceDns": false,
  162.         "forceSSL": false,
  163.         "forms": false,
  164.         "freshQueries": false,
  165.         "getAll": false,
  166.         "getBanner": false,
  167.         "getColumns": false,
  168.         "getComments": false,
  169.         "getCount": false,
  170.         "getCurrentDb": false,
  171.         "getCurrentUser": false,
  172.         "getDbs": false,
  173.         "getHostname": false,
  174.         "getPasswordHashes": false,
  175.         "getPrivileges": false,
  176.         "getRoles": false,
  177.         "getSchema": false,
  178.         "getTables": false,
  179.         "getUsers": false,
  180.         "googleDork": null,
  181.         "googlePage": 1,
  182.         "headers": null,
  183.         "hexConvert": false,
  184.         "host": null,
  185.         "hpp": false,
  186.         "identifyWaf": false,
  187.         "ignore401": false,
  188.         "ignoreProxy": false,
  189.         "invalidBignum": false,
  190.         "invalidLogical": false,
  191.         "invalidString": false,
  192.         "isDba": false,
  193.         "keepAlive": false,
  194.         "lastChar": null,
  195.         "level": 1,
  196.         "limitStart": null,
  197.         "limitStop": null,
  198.         "liveTest": false,
  199.         "loadCookies": null,
  200.         "logFile": null,
  201.         "method": null,
  202.         "mnemonics": null,
  203.         "mobile": false,
  204.         "msfPath": null,
  205.         "noCast": false,
  206.         "noEscape": false,
  207.         "notString": null,
  208.         "nullConnection": false,
  209.         "optimize": false,
  210.         "outputDir": null,
  211.         "os": null,
  212.         "osBof": false,
  213.         "osCmd": null,
  214.         "osPwn": false,
  215.         "osShell": false,
  216.         "osSmb": false,
  217.         "pageRank": false,
  218.         "paramDel": null,
  219.         "parseErrors": false,
  220.         "pivotColumn": null,
  221.         "predictOutput": false,
  222.         "prefix": null,
  223.         "privEsc": false,
  224.         "profile": false,
  225.         "proxy": null,
  226.         "proxyCred": null,
  227.         "proxyFile": null,
  228.         "purgeOutput": false,
  229.         "query": null,
  230.         "randomAgent": false,
  231.         "referer": null,
  232.         "regexp": null,
  233.         "regAdd": false,
  234.         "regData": null,
  235.         "regDel": false,
  236.         "regKey": null,
  237.         "regRead": false,
  238.         "regType": null,
  239.         "regVal": null,
  240.         "requestFile": null,
  241.         "retries": 3,
  242.         "risk": 1,
  243.         "rFile": null,
  244.         "rParam": null,
  245.         "runCase": null,
  246.         "safUrl": null,
  247.         "saFreq": 0,
  248.         "saveCmdline": false,
  249.         "scope": null,
  250.         "search": false,
  251.         "secondOrder": null,
  252.         "sessionFile": null,
  253.         "shLib": null,
  254.         "sitemapUrl": null,
  255.         "skip": null,
  256.         "skipUrlEncode": false,
  257.         "smart": false,
  258.         "smokeTest": false,
  259.         "sqlFile": null,
  260.         "sqlShell": false,
  261.         "stopFail": false,
  262.         "string": null,
  263.         "suffix": null,
  264.         "tamper": null,
  265.         "tbl": null,
  266.         "tech": "BEUSTQ",
  267.         "testFilter": null,
  268.         "testParameter": null,
  269.         "textOnly": false,
  270.         "threads": 1,
  271.         "timeout": 30,
  272.         "timeSec": 5,
  273.         "titles": false,
  274.         "tmpPath": null,
  275.         "tor": false,
  276.         "torPort": null,
  277.         "torType": "HTTP",
  278.         "trafficFile": null,
  279.         "uChar": null,
  280.         "uCols": null,
  281.         "udfInject": false,  
  282.         "uFrom": null,
  283.         "updateAll": false,
  284.         "url": null,
  285.         "user": null,
  286.         "verbose": 1,
  287.         "wizard": false,
  288.         "wFile": null
  289.         },
  290.         "success": true
  291.     }
  292.     */
  293.     public function listOptions($taskid) {
  294.       $json = json_decode(file_get_contents($this->api . "option/" . $taskid . "/list"), true);
  295.       if($json['success'] == "true") {
  296.         return $json;
  297.       }
  298.       return false;
  299.     }
  300.  
  301.  
  302.     /*
  303.        Get SQLMAP Configuration Option Value under specific task ID
  304.        Returns the option value as string on success, false otherwise
  305.          $taskid = your user level task id to look under
  306.          $optstr = the SQLMAP configuration option string
  307.             NOTE: It's case sensitive, so reference list example above if stuck
  308.     */
  309.     public function getOptionValue($taskid, $optstr) {
  310.       // Sorry, not going to pass through code to be eval'd in setter so not going to bother trying to return value...
  311.       if((strtolower(trim($optstr)) != "evalcode") && (strtolower(trim($optstr)) != "eval")) {
  312.         $opts = array(
  313.           'http'=> array(
  314.             'method'=>"POST",
  315.             'header'=>"Content-Type: application/json\r\n",
  316.             'content' => '{"option":"' . trim($optstr) . '"}',
  317.             'timeout' => 60
  318.           )
  319.         );
  320.         $context = stream_context_create($opts);
  321.         $json = json_decode(file_get_contents($this->api . "option/" . $taskid . "/get", false, $context), true);
  322.         if($json['success'] == "true") {
  323.           return $json[$optstr];
  324.         }
  325.       }
  326.       return false;
  327.     }
  328.  
  329.  
  330.     /*
  331.        Set SQLMAP Configuration Option Value under specific task ID
  332.        Returns true on success, false otherwise
  333.          $taskid = your user level task id to look under
  334.          $optstr = the SQLMAP configuration option we want to set value for (case sensitive)
  335.          $optvalue = the value to set for configuration option above ($optstr)
  336.     */
  337.     public function setOptionValue($taskid, $optstr, $optvalue, $integer=false) {
  338.       // Sorry, not going to pass through code to be eval'd here...
  339.       if((strtolower(trim($optstr)) != "evalcode") && (strtolower(trim($optstr)) != "eval")) {
  340.         if(!$integer) {
  341.           $opts = array(
  342.             'http'=> array(
  343.               'method'=>"POST",
  344.               'header'=>"Content-Type: application/json\r\n",
  345.               'content' => '{"' . trim($optstr) . '":"' . trim($optvalue) . '"}',
  346.               'timeout' => 60
  347.             )
  348.           );
  349.         } else {
  350.           $opts = array(
  351.             'http'=> array(
  352.               'method'=>"POST",
  353.               'header'=>"Content-Type: application/json\r\n",
  354.               'content' => '{"' . trim($optstr) . '":' . trim($optvalue) . '}',
  355.               'timeout' => 60
  356.             )
  357.           );
  358.         }
  359.         $context = stream_context_create($opts);
  360.         $json = json_decode(file_get_contents($this->api . "option/" . $taskid . "/set", false, $context), true);
  361.         if($json['success'] == "true") {
  362.           return true;
  363.         }
  364.       }
  365.       return false;
  366.     }
  367.  
  368.  
  369.     /*
  370.        Start SQLMAP Scan using all configured options under user level task ID
  371.        Returns the scan engine id for tracking status and results on success, false otherwise
  372.          $taskid = your user level task id to track scan under
  373.     */
  374.     public function startScan($taskid) {
  375.       $opts = array(
  376.         'http'=> array(
  377.           'method'=>"POST",
  378.           'header'=>"Content-Type: application/json\r\n",
  379.           'content' => '{ "url":"' . trim($this->getOptionValue($taskid, "url")) . '"}',
  380.           'timeout' => 60
  381.         )
  382.       );
  383.       $context = stream_context_create($opts);
  384.       $json = json_decode(file_get_contents($this->api . "scan/" . $taskid . "/start", false, $context), true);
  385.       if($json['success'] == 1) {
  386.         return $json['engineid'];
  387.       }
  388.       return false;
  389.     }
  390.  
  391.  
  392.     /*
  393.        Gracefully Stop a SQLMAP Scan, identified by user level task ID
  394.        Returns true on success, false otherwise
  395.          $taskid = your user level task id to stop scan for
  396.     */
  397.     public function stopScan($taskid) {
  398.       $json = json_decode(file_get_contents($this->api . "scan/" . $taskid . "/stop"), true);
  399.       if($json['success'] == 1) {
  400.         return true;
  401.       }
  402.       return false;
  403.     }
  404.  
  405.  
  406.     /*
  407.        Forcefully KILL a SQLMAP Scan, identified by user level task ID
  408.        Returns true on success, false otherwise
  409.          $taskid = your user level task id to kill scan for
  410.     */
  411.     public function killScan($taskid) {
  412.       $json = json_decode(file_get_contents($this->api . "scan/" . $taskid . "/kill"), true);
  413.       if($json['success'] == 1) {
  414.         return true;
  415.       }
  416.       return false;
  417.     }
  418.  
  419.  
  420.     /*
  421.        Check Status for a SQLMAP Scan, identified by user level task ID
  422.        Returns associative array on success, false otherwise
  423.            array(
  424.              "status" => "running|terminated|not running",
  425.              "code" => (int) "Process Polling Return Code, Status Percent?"
  426.            );
  427.  
  428.          $taskid = your user level task id to check scan status for
  429.     */
  430.     public function checkScanStatus($taskid) {
  431.       $json = json_decode(file_get_contents($this->api . "scan/" . $taskid . "/status"), true);
  432.       if($json['success'] == 1) {
  433.         return array("status" => $json['status'], "code" => $json['returncode']);
  434.       }
  435.       return false;
  436.     }
  437.  
  438.  
  439.     /*
  440.        Fetch the Scan Data from finished SQLMAP scan, identified by user level task ID
  441.        Returns associative array on success, false otherwise
  442.            array(
  443.              "data"  => array(
  444.                 "status" => "stats",
  445.                 "type" => "content_type",
  446.                 "value" => "some value"
  447.                 ),
  448.              "error" => array("error msg", "error msg2", ...)
  449.            );
  450.  
  451.          $taskid = your user level task id  to get scan data for
  452.     */
  453.     public function getScanData($taskid) {
  454.       $json = json_decode(file_get_contents($this->api . "scan/" . $taskid . "/data"), true);
  455.       if($json['success'] == 1) {
  456.         return array("data" => $json['data'], "error" => $json['error']);
  457.       }
  458.       return false;
  459.     }
  460.  
  461.  
  462.     /*
  463.        Review a subset of the SQLMAP scan log messages
  464.        Message subset is based on start and end points provided by user
  465.        Returns associative array on success, false otherwise
  466.            array(
  467.              "log"  => array(
  468.                array(
  469.                  "message" => "testing connection to the target URL",
  470.                  "level" => "INFO",
  471.                  "time" => "19:44:23"
  472.                ),
  473.                array(
  474.                  "message" => "testing if the target URL is stable. This can take a couple of seconds",
  475.                  "level" => "INFO",
  476.                  "time" => "19:44:24"
  477.                ),
  478.                array(...)
  479.            );
  480.  
  481.          $taskid = your user level task id to get scan log for
  482.           $start = the log entry index to start on
  483.             $end = the log entry index to end on
  484.     */
  485.     public function reviewScanLogPartial($taskid, $start, $end) {
  486.       $json = json_decode(file_get_contents($this->api . "scan/" . $taskid . "/log/" . $start . "/" . $end), true);
  487.       if($json['success'] == 1) {
  488.         return $json['log'];
  489.       }
  490.       return false;
  491.     }
  492.  
  493.  
  494.     /*
  495.        Review the FULL set of SQLMAP scan log messages
  496.        Returns associative array on success, false otherwise
  497.            array(
  498.              "log"  => array(
  499.                array(
  500.                  "message" => "testing connection to the target URL",
  501.                  "level" => "INFO",
  502.                  "time" => "19:44:23"
  503.                ),
  504.                array(
  505.                  "message" => "testing if the target URL is stable. This can take a couple of seconds",
  506.                  "level" => "INFO",
  507.                  "time" => "19:44:24"
  508.                ),
  509.                array(...)
  510.            );
  511.  
  512.          $taskid = your user level task id to get scan log for
  513.     */
  514.     public function reviewScanLogFull($taskid) {
  515.       $json = json_decode(file_get_contents($this->api . "scan/" . $taskid . "/log"), true);
  516.       if($json['success'] == 1) {
  517.         return $json['log'];
  518.       }
  519.       return false;
  520.     }
  521.  
  522.  
  523.     /*
  524.        Download a Scan Result File for a Particular Target under Task ID
  525.        Returns $filename's content as base64 encoded string on success, false otherwise
  526.  
  527.          $taskid = your user level task id to find results under
  528.          $target = ip or domain from scan
  529.             NOTE: This is what SQLMAP will create folder under in output directory on scan initialization
  530.               i.e. 10.10.10.10, domain.com, sub.domain.com or www.domain.com
  531.          $filename = the file you wish to download
  532.             NOTE: filename should include path (relative to sqlmap/output/target/ folder)
  533.               i.e. dump/dbname/tblname.csv
  534.               i.e. dump/ipbf_db/ipbf_members.csv
  535.               i.e. files/filename
  536.               i.e. files/_etc_passwd
  537.     */
  538.     public function downloadTargetFile($taskid, $target, $filename) {
  539.       if((!preg_match("#..|%2e%2e|\x2e\x2e|0x2e0x2e#", $target)) && (!preg_match("#..|%2e%2e|\x2e\x2e|0x2e0x2e#", $filename))) {
  540.         $json = json_decode(file_get_contents($this->api . "download/" . $taskid . "/" . $target . "/" . $filename), true);
  541.         if($json['success'] == "true") {
  542.           return $json['file'];
  543.         }
  544.       }
  545.       return false;
  546.     }
  547.   }
  548.  
  549. ?>



Now once I had that working, I decided to dive on into trying to make a front end. To date, I have only ever really tried breaking web applications, never really building them (I think I learned things backwards and wouldn't advice this path to others). I decided to use Bootstrap since it was easy to pickup and run with and well documented. The look is clean and simple for now, meets my minimum for acceptability test I suppose but leaves lots of room for improvements if you do this on a regular basis. I documented things in the source as best I could, where I could, but nothing too magical with the front end work and as I said plenty of room for further improvements...

A few snapshots to show off the basic view:


I broke the  form up into tabbed areas to make it all a little easier to swallow since SQLMAP has a whole lot of options to configure scans with. The actual scan opens in a new tab so you dont loose all the form data and allows you to continue enumerating target as you build up info (I like it this way :p).

Request modifications:

Detection modifications:


Injection & Technique modifications:


Enumeration & Data Dumping modifications:


System Access & Advanced Exploitations:

You can find the code on Github for your forking, pulling, and pushing delights: https://github.com/Hood3dRob1n/SQLMAP-Web-GUI

How to get things setup:
  •  Install SQLMAP and all necessary dependencies per the standard sqlmap instructions...
  •  Get a basic LAMP setup going per your favorite distro's guide
    •  NOTE: MySQL is not being used for this project at this point in time
  •  Download the Web GUI files from my new github repo I created (https://github.com/Hood3dRob1n/SQLMAP-Web-GUI)
    • Edit the sqlmap/inc/config.php from the GUI files so the correct paths are in place for your box
    • Then copy all the web_gui/sqlmap/ files to the web root directory for your server /var/www/sqlmap/
  •  Start up the API server when you want to use it, otherwise GUI will fail
  •  Surf to your new SQLMAP Web GUI in browser to enjoy

Here is a few quick videos I made to show that almost all of your usual SQLMAP command line functionality is still possible via the Web GUI.

Demo against: Windows 2003 Server, IIS/6.0 + ASP + MS-SQL 2005


Demo against: Linux (CentOS), Apache, MySQL, PHP




It is entirely possible that the API Server runs on one server while the Web GUI Frontend runs on a different server, simply make the proper edits to the config file so they can communicate. There still remain a few obstacles in some advanced functions I want to add due to how the API Server works. I plan to try and work on them as time goes on. I had several friends tell me to put this out there and I feel pretty happy with where things are for now that I decided to share with everyone that might be interested.

Open to suggestions and feedback, hope you guys like it!


My Current ToDo List:
  • Ask SQLMAP team to modify the logger or work with me on how to extract info log while it is running scan
    • Would love to present scan log info while the spinner wheel is running during an active scan so you know what is going on
    • Currently the scan log info seems to be set in a blocking manner so that the active scan needs to finish before logs can be parsed/extracted from API
    •  The admin panel seems to suffer from this blocking behavior as well.
      • I would like to improve this function/feature in future but current blocking behavior makes it too annoying to work on for now
  • Ask SQLMAP team to modify the --answer delimiter value or allow custom one to be set
    • Affects ability to pass in more than one path when using file write options (which takes a csv list of paths, but --answer mistakes them as multiple answers instead)
  • Ask if MSF Advanced Exploit options (--os-pwn, --os-smb, --os-bof, --priv-esc) could be prompted differently
    • Currently assumption is that sqlmap attack box is the box that should also accept MSF payload call backs
    • Should allow new option to be added so user can specify a remote IP and PORT instead of local IP/PORT
    • Current setup causes API to hang in an infinite loop if a remote IP/PORT specified
    • The GUI version of these is disabled until can fix
  • The --os-cmd option doesn't seem to return output to API properly with MySQL (works fine for MS-SQL), more testing needed to report bug if indeed a bug...
  • Ask if SQLMAP team would consider moving away from using Python's pickle method for serializing options passed from API to CLI
    • Also use a different web server that doesn't use the same pickle method
    • pickle.loads() and pickle.dumps() are known to be susceptible to Python Object Injection attacks that can lead to code execution
    • Current use of API Server doesn't call the vulnerable cookie decoder the bottle server has built in, so safe for now...
      • Currently my attempts to find a working exploit seem to break the json which stops it from passing through to execute by sqlmap
      • I'm concerned someone smarter than me can figure it out and find a way to sneak some pickled py code through to achieve rce
      • If you know how, please show or send me a quick POC as I would love to see how it is accomplished in this particular situation
    • Until this is address or confirmed safe by more people, I can't widely suggest or really recommend running this Web GUI on a open web facing server to untrusted users of the interwebs
    • Did my best to secure the few areas I found problems with for trying to get it to be safe web facing
      • Nothing can be done without API server running so secure enough for me to use locally or spin up as needed, you will need to decide your own security...
  • Add options to config.php to allow settings or levels to activate and expose some of the other options not currently available as of right now
    • evalCode, proxy options, tor use, etc
  • Do more testing:
    • May have some issues with PHP < 5.3, not tested and still a PHP n00b so all bets are off...
    • Setups Confirmed Working:
      • Debian 7, PHP 5.4.4-14+deb7u14
      • Debian 7, PHP 5.4.36-0+deb7u3
      • Ubuntu 12, PHP 5.3.10-1ubuntu3.17
      • Kali w/PHP 5.4