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

Monday, January 13, 2014

Searchsploit-rb - Exploit-DB Search Tool gets an upgrade?

Exploit-DB is pretty famous for their collection of exploits and papers and if you are not familiar with them then then you should use some Google-fu to check up on them. They should be a bookmark found in pretty much any hackers handbook. In the past they had made their full archive available for download over HTTP, which was shaky at best and unless you scheduled it with cron or something you never knew if you had the latest and greatest and updating from a scripted manner was not always reliable (for me anyways). I had previously coded this tool in the past for the old archive methods, but today I noticed on Reddit they have moved to Github - w00t! This makes things much easier for everyone, well most people anyway.

Now they have had their archive collection for some time and the searchsploit bash script works just fine, however it can be too simplistic at times and not yield the results we want, unless you match your search syntax to its janky search method. It also lacks color and output logging. I had some time this morning so I decided to update my old script to take advantage of the simplicity of Github to allow easy fetching of new copies and/or updating existing ones. I also added a bit of color to the presentation of the results. It's very helpful for me so thought I would share with the rest of you...

To download exploit-db archives on your own from command line using normal git client:
COMMAND: git clone https://github.com/offensive-security/exploit-database.git

Then to update from command line when you want, you simply pull:
COMMAND: git pull

This can all be done from within my tool so now you can update and search from one place :) Here is a quick overview and a link to my Github page where you can find it.

Help Menu:



If it can't find the archive setup, it will offer to download via git for you:

Search by a range of options with option to log results to file:


NOTE: this can be handy when you get a lot of results (SQL Injection searches mostly...)

Easy peazy updating now that things can leverage Github:



You can find things on my Github page:
git clone https://github.com/Hood3dRob1n/Exploit-DB-Local-Archive-Search-Tool.git

Should only need to install the 'colorize' gem to get things started with ruby:
sudo gem install colorize





Special thanks to everyone on the Offesive Security
 team that helps to make exploit-db and all of their other awesome projects possible!

Until next time, enjoy!

Wednesday, January 8, 2014

Shodan Search Tool w/My Ruby API Class

Today I just wanted to share a little something I made for Shodan. If you don't know what Shodan is, then I highly recommend you check them out and do some quick googling to see what others have done with its help. I initially tried using their published ruby gem and published API documentation but it failed miserably (likely could just be me, but seems their code is outdated with how their site provides output now, idk). I really like Shodan though so I decided to create my own version of their API so I could get started on making a cool search assistant I can run from the command line with some basic logging for analysis after. Once I finished redoing the API class, I made a little CLI based search tool to make quick Shodan research a snap and am now sharing with the rest of the world, hope its helpful for others.

Prerequisites:
sudo gem install colorize curb json nokogiri

NOTE: curb uses libcurl under the hood so you might need to install this if not already included on your OS

Basic Help Menu:


You can run a basic Shodan search and display the results, which are also logged to the results folder.


The logged results are overwritten on each search so you need to rename it or move it if you want to use it later and plan to run multiple searches.

I also made option for quick search which runs a Shodan search and returns the list of IP addresses from results, skipping all the details. I typically run a normal search, then a follow up quick search on same keywords to pass of lists to other tools in a speedy fashion while manual review is more involved with the full search results...

Shodan also offers up a nice search feature to search for exploits which leverages multiple exploit databases. I currently have the Exploit-db and Metasploit search engines available and fully working. This means you can easily search for known exploits with variety of keywords and get matching results displayed and logged for you.

You can even download the exploit/poc code from search results by referencing the ID number from results.

ToDo List:
Include options to search tool for premium search options (somewhat built into my API Class already but not in tool). Include a Gemfile for easy installs for bundler lovers. Also I have not uploaded things to Github yet as I fried my old box and lost a lot of stuff, working on recovery still but should have it updated soon. Until then you can find things on Pastebin, available for a long while...

My Shodan API Standalone Class:

Direct link: http://pastebin.com/q6LZJqcD


My Shodan API Search Tool, Source Code:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. #!/usr/bin/env ruby
  2. #
  3. # Shodan API Search Assistant
  4. # By: Hood3dRob1n
  5. #
  6.  
  7. ########### ENTER API KEY HERE  ###########
  8. APIKEY='YOURAPIKEYNEEDS2GORIGHTINHEREYO!' #
  9. ###########################################
  10.  
  11. ##### STD GEMS #######
  12. require 'fileutils'  #
  13. require 'optparse'   #
  14. require 'resolv'     #
  15. #### NON-STD GEMS ####
  16. require 'rubygems'   #
  17. require 'colorize'   #
  18. require 'curb'       #
  19. require 'json'       #
  20. require 'nokogiri'   #
  21. ######################
  22.  
  23. HOME=File.expand_path(File.dirname(__FILE__))
  24. RESULTS = HOME + '/results/'
  25.  
  26. # Banner
  27. def banner
  28.   puts
  29.   puts "Shodan API Search Assistant".light_green
  30.   puts "By".light_green + ": Hood3dRob1n".white
  31. end
  32.  
  33. # Clear Terminal
  34. def cls
  35.   if RUBY_PLATFORM =~ /win32|win64|\.NET|windows|cygwin|mingw32/i
  36.     system('cls')
  37.   else
  38.     system('clear')
  39.   end
  40. end
  41.  
  42. # Custom ShodanAPI Class :)
  43. # The pre-built option is broken and doesn't work in several places....
  44. # So we re-wrote it!
  45. class ShodanAPI
  46.   # Initialize ShodanAPI via passed API Key
  47.   def initialize(apikey)
  48.     @url="http://www.shodanhq.com/api/"
  49.     if shodan_connect(apikey)
  50.         @key=apikey
  51.     end
  52.   end
  53.  
  54.   # Check API Key against API Info Query
  55.   # Return True on success, False on Error or Failure
  56.   def shodan_connect(apikey)
  57.     url = @url + "info?key=#{apikey}"
  58.     begin
  59.       c = Curl::Easy.perform(url)
  60.       if c.body_str =~ /"unlocked_left": \d+, "telnet": .+, "plan": ".+", "https": .+, "unlocked": .+/i
  61.         results = JSON.parse(c.body_str)
  62.         @plan = results['plan']
  63.         @unlocked = results['unlocked']
  64.         @unlocks = results['unlocked_left']
  65.         @https = results['https']
  66.         @telnet = results['telnet']
  67.         return true
  68.       elsif c.body_str =~ /"error": "API access denied"/i
  69.         puts "Access Denied using API Key '#{apikey}'".light_red + "!".white
  70.         puts "Check Key & Try Again".light_red + "....".white
  71.         return false
  72.       else
  73.         puts "Unknown Problem with Connection to Shodan API".light_green + "!".white
  74.         return false
  75.       end
  76.     rescue => e
  77.       puts "Problem with Connection to Shodan API".light_red + "!".white
  78.       puts "\t=> #{e}"
  79.       return false
  80.     end
  81.   end
  82.  
  83.   # Just checks our key is working (re-using shodan_connect so updates @unlocks)
  84.   # Returns True or False
  85.   def connected?
  86.     if shodan_connect(@key)
  87.       return true
  88.     else
  89.       return  false
  90.     end
  91.   end
  92.  
  93.   # Return the number of unlocks remaining
  94.   def unlocks
  95.     if shodan_connect(@key)
  96.       return @unlocks.to_i
  97.     else
  98.       return nil
  99.     end
  100.   end
  101.  
  102.   # Check if HTTPS is Enabled
  103.   def https?
  104.     if shodan_connect(@key)
  105.       if @https
  106.         return true
  107.       else
  108.         return false
  109.       end
  110.     else
  111.       return false
  112.     end
  113.   end
  114.  
  115.   # Check if Telnet is Enabled
  116.   def telnet?
  117.     if shodan_connect(@key)
  118.       if @telnet
  119.         return true
  120.       else
  121.         return false
  122.       end
  123.     else
  124.       return false
  125.     end
  126.   end
  127.  
  128.   # Actually display Basic Info for current API Key
  129.   def info
  130.     url = @url + 'info?key=' + @key
  131.     begin
  132.       c = Curl::Easy.perform(url)
  133.       results = JSON.parse(c.body_str)
  134.       puts
  135.       puts "Shodan API Key Confirmed".light_green + "!".white
  136.       puts "API Key".light_green + ": #{@key}".white
  137.       puts "Plan Type".light_green + ": #{results['plan']}".white
  138.       puts "Unlocked".light_green + ": #{results['unlocked']}".white
  139.       puts "Unlocks Remaining".light_green + ": #{results['unlocked_left']}".white
  140.       puts "HTTPS Enabled".light_green + ": #{results['https']}".white
  141.       puts "Telnet Enabled".light_green + ": #{results['telnet']}".white
  142.       return true
  143.     rescue => e
  144.       puts "Problem with Connection to Shodan API".light_red + "!".white
  145.       puts "\t=> #{e}".white
  146.       return false
  147.     end
  148.   end
  149.  
  150.   # Lookup all available information for a specific IP address
  151.   # Returns results hash or nil
  152.   def host(ip)
  153.     url = @url + 'host?ip=' + ip + '&key=' + @key
  154.     begin
  155.       c = Curl::Easy.perform(url)
  156.       results = JSON.parse(c.body_str)
  157.       return results
  158.     rescue => e
  159.       puts "Problem running Host Search".light_red + "!".white
  160.       puts "\t=> #{e}".white
  161.       return nil
  162.     end
  163.   end
  164.  
  165.   # Returns the number of devices that a search query found
  166.   # Unrestricted usage of all advanced filters
  167.   # Return results count or nil on failure
  168.   def count(string)
  169.     url = @url + 'count?q=' + string + '&key=' + @key
  170.     begin
  171.       c = Curl::Easy.perform(url)
  172.       results = JSON.parse(c.body_str)
  173.       return results['total']
  174.     rescue => e
  175.       puts "Problem grabbing results count".light_red + "!".white
  176.       puts "\t=> #{e}".white
  177.       return nil
  178.     end
  179.   end
  180.  
  181.   # Search Shodan for devices using a search query
  182.   # Returns results hash or nil
  183.   def search(string, filters={})
  184.     prem_filters =  [ 'city', 'country', 'geo', 'net', 'before', 'after', 'org', 'isp', 'title', 'html' ]
  185.     cheap_filters = [ 'hostname', 'os', 'port' ]
  186.     url = @url + 'search?q=' + string
  187.     if not filters.empty?
  188.       filters.each do |k, v|
  189.         if cheap_filters.include?(k)
  190.           url += ' ' + k + ":\"#{v}\""
  191.         end
  192.         if prem_filters.include?(k)
  193.           if @unlocks.to_i > 1
  194.             url += ' ' + k + ":\"#{v}\""
  195.             @unlocks = @unlocks.to_i - 1 # Remove an unlock for use of filter
  196.           else
  197.             puts "Not Enough Unlocks Left to run Premium Filter Search".light_red + "!".white
  198.             puts "Try removing '#{k}' filter and trying again".light_red + "....".white
  199.             return nil
  200.           end
  201.         end
  202.       end
  203.     end
  204.     url += '&key=' + @key
  205.     begin
  206.       c = Curl::Easy.perform(url)
  207.       results = JSON.parse(c.body_str)
  208.       return results
  209.     rescue => e
  210.       puts "Problem running Shodan Search".light_red + "!".white
  211.       puts "\t=> #{e}".white
  212.       return nil
  213.     end
  214.   end
  215.  
  216.   # Quick Search Shodan for devices using a search query
  217.   # Results are limited to only the IP addresses
  218.   # Returns results array or nil
  219.   def quick_search(string, filters={})
  220.     prem_filters =  [ 'city', 'country', 'geo', 'net', 'before', 'after', 'org', 'isp', 'title', 'html' ]
  221.     cheap_filters = [ 'hostname', 'os', 'port' ]
  222.     url = @url + 'search?q=' + string
  223.     if not filters.empty?
  224.       filters.each do |k, v|
  225.         if cheap_filters.include?(k)
  226.           url += ' ' + k + ":\"#{v}\""
  227.         end
  228.         if prem_filters.include?(k)
  229.           if @unlocks.to_i > 1
  230.             url += ' ' + k + ":\"#{v}\""
  231.             @unlocks = @unlocks.to_i - 1
  232.           else
  233.             puts "Not Enough Unlocks Left to run Premium Filter Search".light_red + "!".white
  234.             puts "Try removing '#{k}' filter and trying again".light_red + "....".white
  235.             return nil
  236.           end
  237.         end
  238.       end
  239.     end
  240.     url += '&key=' + @key
  241.     begin
  242.       ips=[]
  243.       c = Curl::Easy.perform(url)
  244.       results = JSON.parse(c.body_str)
  245.       results['matches'].each do |host|
  246.        ips << host['ip']
  247.       end
  248.       return ips
  249.     rescue => e
  250.       puts "Problem running Shodan Quick Search".light_red + "!".white
  251.       puts "\t=> #{e}".white
  252.       return nil
  253.     end
  254.   end
  255.  
  256.   # Perform Shodan Exploit Search as done on Web
  257.   # Provide Search String and source
  258.   # Source can be: metasploit, exploitdb, or cve
  259.   # Returns results hash array on success: { downloadID => { link => description } }
  260.   # Returns nil on failure
  261.   def sploit_search(string, source)
  262.     sources = [ "metasploit", "exploitdb", "cve" ]
  263.     if sources.include?(source.downcase)
  264.       sploits = 'https://exploits.shodan.io/?q=' + string + ' source:"' + source.downcase + '"'
  265.       begin
  266.         results={}
  267.         c = Curl::Easy.perform(sploits)
  268.         page = Nokogiri::HTML(c.body_str) # Parsable doc object now
  269.         # Enumerate target section, parse out link & description
  270.         page.css('div[class="search-result well"]').each do |linematch|
  271.           if linematch.to_s =~ /<div class="search-result well">\s+<a href="(.+)"\s/
  272.             link=$1
  273.           end
  274.           if linematch.to_s =~ /class="title">(.+)\s+<\/a>/
  275.             desc=$1.gsub('<em>', '').gsub('</em>', '')
  276.           end
  277.           case source.downcase
  278.           when 'cve'
  279.             dl_id = 'N/A for CVE Search'
  280.           when 'exploitdb'
  281.             dl_id = link.split('/')[-1] unless link.nil?
  282.           when 'metasploit'
  283.             dl_id = link.sub('http://www.metasploit.com/', '').sub(/\/$/, '') unless link.nil?
  284.           end
  285.           results.store(dl_id, { link => desc}) unless (link.nil? or link == '') or (desc.nil? or desc == '') or (dl_id.nil? or dl_id == 'N/A for CVE Search')
  286.         end
  287.         return results
  288.       rescue Curl::Err::ConnectionFailedError => e
  289.         puts "Shitty connection yo".light_red + ".....".white
  290.         return nil
  291.       rescue => e
  292.         puts "Unknown connection problem".light_red + ".....".white
  293.         puts "\t=> #{e}".white
  294.         return nil
  295.       end
  296.     else
  297.       puts "Invalid Search Source Requested".light_red + "!".white
  298.       return nil
  299.     end
  300.   end
  301.  
  302.   # Download Exploit Code from Exploit-DB or MSF Github Page
  303.   # By passing in the Download ID (which can be seen in sploit_search() results)
  304.   # Return { 'Download' => dl_link, 'Viewing' => v_link, 'Exploit' => c.body_str }
  305.   # or nil on failure
  306.   def sploit_download(id, source)
  307.     sources = [ "metasploit", "exploitdb" ]
  308.     if sources.include?(source.downcase)
  309.       case source.downcase
  310.       when 'exploitdb'
  311.         dl_link = "http://www.exploit-db.com/download/#{id}/"
  312.         v_link = "http://www.exploit-db.com/exploits/#{id}/"
  313.       when 'metasploit'
  314.         dl_link = "https://raw.github.com/rapid7/metasploit-framework/master/#{id.sub('/exploit/', '/exploits/')}.rb"
  315.         v_link = "http://www.rapid7.com/db/#{id}/"
  316.       end
  317.       begin
  318.         c = Curl::Easy.perform(dl_link)
  319.         page = Nokogiri::HTML(c.body_str) # Parsable doc object now
  320.         results = { 'Download' => dl_link, 'Viewing' => v_link, 'Exploit' => c.body_str }
  321.         return results
  322.       rescue Curl::Err::ConnectionFailedError => e
  323.         puts "Shitty connection yo".light_red + ".....".white
  324.         return false
  325.       rescue => e
  326.         puts "Unknown connection problem".light_red + ".....".white
  327.         puts "#{e}".light_red
  328.         return false
  329.       end
  330.     else
  331.       puts "Invalid Download Source Requested".light_red + "!".white
  332.       return false
  333.     end
  334.   end
  335. end
  336.  
  337. ### MAIN ###
  338. options = {}
  339. optparse = OptionParser.new do |opts|
  340.   opts.banner = "Usage:".light_green + "#{$0} ".white + "[".light_green + "OPTIONS".white + "]".light_green
  341.   opts.separator ""
  342.   opts.separator "EX:".light_green + " #{$0} -s cisco-ios".white
  343.   opts.separator "EX:".light_green + " #{$0} -h 217.140.75.46".white
  344.   opts.separator "EX:".light_green + " #{$0} --quick-search IIS/5.1".white
  345.   opts.separator "EX:".light_green + " #{$0} -S exploitdb -x udev".white
  346.   opts.separator "EX:".light_green + " #{$0} -d 8678 -S exploitdb".white
  347.   opts.separator "EX:".light_green + " #{$0} --source metasploit --exploit-search udev".white
  348.   opts.separator "EX:".light_green + " #{$0} -S metasploit -d modules/exploit/linux/local/udev_netlink".white
  349.   opts.separator ""
  350.   opts.separator "Options: ".light_green
  351.   opts.on('-q', '--quick-search STRING', "\n\tShodan Quick Search".white) do |search_str|
  352.     options[:method] = 3 # 1=> Normal, 2=> IP, 3=> Quick, 4=>Exploit Search, 5=>Exploit Download
  353.     options[:search] = search_str.chomp
  354.   end
  355.   opts.on('-s', '--shodan-search STRING', "\n\tShodan Search".white) do |search_str|
  356.     options[:method] = 1 # 1=> Normal, 2=> IP, 3=> Quick, 4=>Exploit Search, 5=>Exploit Download
  357.     options[:search] = search_str.chomp
  358.   end
  359.   opts.on('-h', '--host-search HOST', "\n\tShodan Host Search against IP".white) do |search_str|
  360.     options[:method] = 2 # 1=> Normal, 2=> IP, 3=> Quick, 4=>Exploit Search, 5=>Exploit Download
  361.     if search_str.chomp =~ /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/
  362.       options[:search] = search_str.chomp
  363.     else
  364.       begin
  365.         ip = Resolv.getaddress(search_str.chomp) # Resolve Host Domain to IP
  366.         options[:search] = ip
  367.       rescue Resolv::ResolvError => e
  368.         cls
  369.         banner
  370.         puts
  371.         puts "Unable to Resolve Host to IP".light_red + "!".white
  372.     puts
  373.         puts opts
  374.         puts
  375.         exit 69;
  376.       end
  377.     end
  378.   end
  379.   opts.on('-S', '--source SOURCE', "\n\tSet Exploit Source: exploitdb or metasploit".white) do |source|
  380.     sources=["metasploit", "exploitdb"]
  381.     if sources.include?(source.downcase.chomp)
  382.       options[:source] = source.downcase.chomp
  383.     else
  384.       cls
  385.       banner
  386.       puts
  387.       puts "Invalid Search Source Requested".light_red + "!".white
  388.       puts "\t=> #{source}".light_red
  389.       puts
  390.       puts opts
  391.       puts
  392.       exit 69;
  393.     end
  394.   end
  395.   opts.on('-x', '--exploit-search STRING', "\n\tShodan Exploit Search for String (requires -S)".white) do |search_str|
  396.     options[:method] = 4 # 1=> Normal, 2=> IP, 3=> Quick, 4=>Exploit Search, 5=>Exploit Download
  397.     options[:search] = search_str.chomp
  398.   end
  399.   opts.on('-d', '--download-id ID', "\n\tDownload Exploit by Exploit ID (requires -S)".white) do |search_str|
  400.     options[:method] = 5 # 1=> Normal, 2=> IP, 3=> Quick, 4=>Exploit-DB Search, 5=>Exploit-DB Download
  401.     options[:search] = search_str.chomp
  402.   end
  403.   opts.on('-H', '--help', "\n\tHelp Menu".white) do
  404.     cls
  405.     banner
  406.     puts
  407.     puts opts
  408.     puts
  409.     exit 69;
  410.   end
  411. end
  412. begin
  413.   foo = ARGV[0] || ARGV[0] = "-H"
  414.   optparse.parse!
  415.   mandatory = [:method,:search]
  416.   missing = mandatory.select{ |param| options[param].nil? }
  417.   if not missing.empty?
  418.     cls
  419.     banner
  420.     puts
  421.     puts "Missing options: ".red + " #{missing.join(', ')}".white  
  422.     puts optparse
  423.     exit 666;
  424.   end
  425. rescue OptionParser::InvalidOption, OptionParser::MissingArgument, OptionParser::AmbiguousOption
  426.   cls
  427.   banner
  428.   puts
  429.   puts $!.to_s.red
  430.   puts
  431.   puts optparse
  432.   puts
  433.   exit 666;  
  434. end
  435.  
  436. banner
  437. shodan = ShodanAPI.new(APIKEY)
  438. if shodan.connected?
  439.   # Display Basic API Key Info
  440.   shodan.info
  441.   puts
  442.  
  443.   # Create Results Dir if it doesnt exist
  444.   Dir.mkdir(RESULTS) unless File.exists?(RESULTS) and File.directory?(RESULTS)
  445.  
  446.   # Now run as requested....
  447.   case options[:method].to_i
  448.   when 1
  449.     results = shodan.search(options[:search].to_s)
  450.     if not results.nil?
  451.       puts "Shodan Search".light_green + ": #{options[:search].to_s}".white
  452.       f=File.open(RESULTS + "shodan_search_results.txt", 'w+')
  453.       f.puts "Shodan Search: #{options[:search].to_s}"
  454.       puts "Total Results Found".light_green + ": #{results['total']}".white
  455.       f.puts "Total Results Found: #{results['total']}"
  456.       results['countries'].each do |country|
  457.         puts "  #{country['name']}".light_green + ": #{country['count']}".white
  458.         f.puts "  #{country['name']}: #{country['count']}"
  459.       end
  460.       puts
  461.       f.puts
  462.       results['matches'].each do |host|
  463.         puts "Host IP".light_green + ": #{host['ip']}".white
  464.         f.puts "Host IP: #{host['ip']}"
  465.         puts "#{host['data']}".white
  466.         f.puts host['data']
  467.       end
  468.       f.puts
  469.       f.close
  470.     else
  471.       puts "No Results Found for ".light_red + "#{string}".white + " via Shodan Search".light_red + "!".white
  472.     end
  473.     puts
  474.   when 2
  475.     # Check Host Results
  476.     results = shodan.host(options[:search].to_s)
  477.     if not results.nil?
  478.       f=File.open(RESULTS + "shodan_host_search_results.txt", 'w+')
  479.       puts "Host IP".light_green + ": #{results['ip']}".white unless results['ip'].nil?
  480.       f.puts "Host IP: #{results['ip']}" unless results['ip'].nil?
  481.       puts "ISP".light_green + ": #{results['data'][0]['isp']}".white unless results['data'][0]['isp'].nil?
  482.       f.puts "ISP: #{results['data'][0]['isp']}" unless results['data'][0]['isp'].nil?
  483.       puts "Hostname(s)".light_green + ": #{results['hostnames'].join(',')}".white unless results['hostnames'].empty?
  484.       f.puts "Hostname(s): #{results['hostnames'].join(',')}" unless results['hostnames'].empty?
  485.       puts "Host OS".light_green + ": #{results['os']}".white unless results['os'].nil?
  486.       f.puts "Host OS: #{results['os']}" unless results['os'].nil?
  487.       puts "Country".light_green + ": #{results['country_name']}".white unless results['country_name'].nil?
  488.       f.puts "Country: #{results['country_name']}" unless results['country_name'].nil?
  489.       puts "City".light_green + ": #{results['city']}".white unless results['city'].nil?
  490.       f.puts "City: #{results['city']}" unless results['city'].nil?
  491.       puts "Longitude".light_green + ": #{results['longitude']}".white unless results['longitude'].nil? or results['longitude'].nil?
  492.       f.puts "Longitude: #{results['longitude']}" unless results['longitude'].nil? or results['longitude'].nil?
  493.       puts "Latitude".light_green + ": #{results['latitude']}".white unless results['longitude'].nil? or results['longitude'].nil?
  494.       f.puts "Latitude: #{results['latitude']}" unless results['longitude'].nil? or results['longitude'].nil?
  495.       f.puts
  496.       puts
  497.       # We need to split and re-pair up the ports & banners as ports comes after banners in results iteration
  498.       ban=nil
  499.       port_banners={}
  500.       results['data'][0].each do |k, v|
  501.         if k == 'port'
  502.           port=v
  503.           if not ban.nil?
  504.             port_banners.store(port, ban) # store them in hash so we pair them up properly
  505.             ban=nil
  506.           end
  507.         elsif k == 'banner'
  508.           ban=v
  509.         end
  510.       end
  511.       # Now we can display them in proper pairs
  512.       port_banners.each do |port, ban|
  513.         puts "Port".light_green + ": #{port}".white
  514.         f.puts "Port: #{port}"
  515.         puts "Banner".light_green + ": \n#{ban}".white
  516.         f.puts "Banner: \n#{ban}"
  517.       end
  518.       f.puts
  519.       f.close
  520.     else
  521.       puts "No results found for host".light_red + "!".white
  522.     end
  523.     puts
  524.   when 3
  525.     # Perform Quick Shodan Search
  526.     string = options[:search].to_s
  527.     ips = shodan.quick_search(string)
  528.     if not ips.nil?
  529.       puts "Shodan Search".light_green + ": #{string}".white
  530.       puts "Total Results".light_green + ": #{ips.size}".white
  531.       puts "IP Addresses Returned".light_green + ": ".white
  532.       f=File.open(RESULTS + 'quick_search-ips.lst', 'w+')
  533.       ips.each {|x| puts "  #{x}".white; f.puts x }
  534.       f.close
  535.     else
  536.       puts "No Results Found for ".light_red + "#{string}".white + " via Shodan Quick Search".light_red + "!".white
  537.     end
  538.     puts
  539.   when 4
  540.     # Search for Exploits
  541.     string = options[:search].to_s
  542.     source = options[:source].to_s
  543.     results = shodan.sploit_search(string, source)
  544.     if not results.nil?
  545.       f=File.open(RESULTS + "shodan_#{source}_search_results.txt", 'w+')
  546.       puts "Shodan Exploit Search".light_green + ": #{string}".white
  547.       f.puts "Shodan Exploit Search: #{string}"
  548.       results.each do |id, stuff|
  549.         puts "ID".light_green + ": #{id}".white unless id.nil?
  550.         f.puts "ID: #{id}" unless id.nil?
  551.         stuff.each do |link, desc|
  552.           puts "View".light_green + ": #{link.sub('http://www.metasploit.com/', 'http://www.rapid7.com/db/')}".white unless link.nil?
  553.           f.puts "View: #{link.sub('http://www.metasploit.com/', 'http://www.rapid7.com/db/')}" unless link.nil?
  554.           if not link.nil? and source.downcase == 'metasploit'
  555.             puts "Github Link".light_green + ": https://raw.github.com/rapid7/metasploit-framework/master/#{link.sub('http://www.metasploit.com/', '').sub('/exploit/', '/exploits/').sub(/\/$/, '')}.rb".white
  556.             f.puts "Github Link: https://raw.github.com/rapid7/metasploit-framework/master/#{link.sub('http://www.metasploit.com/', '').sub('/exploit/', '/exploits/').sub(/\/$/, '')}.rb"
  557.           end
  558.           puts "Exploit Description".light_green + ": \n#{desc}".white unless desc.nil?
  559.           f.puts "Exploit Description: \n#{desc}" unless desc.nil?
  560.           f.puts
  561.           puts
  562.         end
  563.       end
  564.       f.close
  565.     else
  566.       puts "No Results Found for ".light_red + "#{string}".white + " via Shodan Exploit Search".light_red + "!".white
  567.     end
  568.     puts
  569.   when 5
  570.     # Now download one of the exploits you found....
  571.     id=options[:search].to_s
  572.     source = options[:source].to_s
  573.     results = shodan.sploit_download(id, source)
  574.     if not results.nil?
  575.       downloads = RESULTS + 'downloads/'
  576.       Dir.mkdir(downloads) unless File.exists?(downloads) and File.directory?(downloads)
  577.       f=File.open(downloads + "#{source}-#{id}.code", 'w+')
  578.       results.each do |k, v|
  579.         if k == 'Exploit'
  580.           puts "Saved to".light_green + ": #{downloads}#{source}-#{id}.code".white
  581.           puts "#{k}".light_green + ": \n#{v}".white
  582.           f.puts v
  583.         else
  584.           puts "#{k}".light_green + ": #{v}".white
  585.         end
  586.       end
  587.       f.close
  588.     else
  589.       puts "No Download Results Found for ID".light_red + "#: #{id}".white
  590.     end
  591.   end
  592. else
  593.   exit 666;
  594. end
  595. #EOF
Direct Link: http://pastebin.com/B0SdmmrX

Helpful for me, hope it is for you too!

Until next time, Enjoy!

Tuesday, December 31, 2013

XMAS Gifts from ifixit.com for Bug Bounty

I just wanted to give a quick shout out to the nice folks over at ifixit.com. I recently submitted a few small bugs to them and they were hands down the nicest folks I have ever communicated with when it comes to reporting of bugs! They were very responsive in all communications and in patching of the site. They added me to their responsible disclosure page and even sent me some nice swag as a added thanks. The gear arrived to me on Christmas morning which made my day even better! Thanks ifixit.com

T-Shirt, Handy Mini Toolset & Stickers:

Friday, November 8, 2013

Yet Another SMB PSEXEC (Y.A.S.P) Tool

I was working on my own version of an updated standalone PSEXEC tool in ruby, leveraging the MSF standalone as a base along with some of the newer modules that have been released. Unfortunately SMBEXEC 2.0 was recently released which pretty much does the same thing functionality wise but has threading so its probably a bit cooler but thought I would still post mine out there for anyone who cares to take it for a spin. It is single target focused and a little different in the look and feel in comparison to some of the others available so who knows. It works for me, hope it works for someone else too....

I first started off trying to do things on my own by writing classes to wrap the smbclient tool which now supports the pass-the-hash option or can be fairly easily patched to address this. This Samba suite also includes the rpcclient tool which I originally planned to leverage to make some magic happen. Well I got the wrappers working, but was not able to get things fully working with just these two classes. I do recommend playing around with rpcclient as it is an interesting tool and can lead to a lot of insight against a remote target but that's another story. So after giving up on the rpcclient option i did some checking on the net and found some great references from Mubix and Chris Gates on the MSF standalone tool and some ways to play with it. As Chris and Rob point out the librex library is available outside of MSF as a standalone gem which gives you tons of power to do all kinds of neat things on your own. Being that the hard work was already done and available as reference in MSF i decided to borrow what i could from there and merge with my own wrappers and code to get what I wanted. The end result is an smbclient with all the psexec fun and then some.

It can do some basic recon without creds, which I am working on improving but its main focus is on re-using valid credentials. Once authenticated it's capable of running single commands using the PSEXEC technique or jumping into a pseudo shell to execute multiple commands.


Download registry hives for offline pillaging


Leverage MSFVENOM to generate shellcode and then run payloads using PowerShell (my favorite):

NOTE: You need to specify the hostname for Vista+ targets or connection will fail. You can use raw netbios requests or tool like nbtscan to find this pretty quickly and without any pain. On older targets you can omit this field for connection configuration.

The full list of available options once authenticated:


And here are a few demo videos I made to show off how you can use it....

Y.A.S.P. vs Standalone 2k3 Server:



Y.A.S.P. + PowerShell Payload vs Windows 7:
NOTE: It does leverage MSFVENOM currently to generate shellcode which gets converted over to PowerShell acceptable format and then executed via PowerShell command




Y.A.S.P. vs 2k3 Domain Controller + Active Directory Dumping 101:



You can find all the source code along with all the tools used or referenced in the above videos on my github page here: SOURCE + TOOLS

You can keep an eye on Github as I will be working on this one over time to smooth out a few things and add a few more things to it which I wasn't comfortable with rolling out just yet but again just sharing to share and inspire more coders to code cool shit. Until next time, Enjoy!