Skip to content

Commit 93b296e

Browse files
committed
few bug fixes (NTLM credential parsing was wrong), some switch reordering (few Misc to General), implemented --check-waf switch (irony is that this will also be called highly experimental/unstable while other things will be called "major/turbo/super bug fix/implementation")
1 parent b8ffcf9 commit 93b296e

12 files changed

Lines changed: 146 additions & 61 deletions

File tree

lib/controller/checks.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
from lib.core.settings import UNKNOWN_DBMS_VERSION
5858
from lib.core.settings import LOWER_RATIO_BOUND
5959
from lib.core.settings import UPPER_RATIO_BOUND
60+
from lib.core.settings import IDS_WAF_CHECK_PAYLOAD
6061
from lib.core.threads import getCurrentThreadData
6162
from lib.request.connect import Connect as Request
6263
from lib.request.inject import checkBooleanExpression
@@ -832,6 +833,60 @@ def checkRegexp():
832833

833834
return True
834835

836+
def checkWaf():
837+
"""
838+
Reference: http://seclists.org/nmap-dev/2011/q2/att-1005/http-waf-detect.nse
839+
"""
840+
841+
if not conf.checkWaf:
842+
return False
843+
844+
infoMsg = "testing if the target is protected by "
845+
infoMsg += "some kind of WAF/IPS/IDS"
846+
logger.info(infoMsg)
847+
848+
retVal = False
849+
850+
backup = dict(conf.parameters)
851+
852+
conf.parameters = dict(backup)
853+
conf.parameters[PLACE.GET] = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + "&"
854+
conf.parameters[PLACE.GET] += "%s=%d %s" % (randomStr(), randomInt(), IDS_WAF_CHECK_PAYLOAD)
855+
856+
kb.matchRatio = None
857+
_ = Request.queryPage()
858+
859+
if kb.errorIsNone and kb.matchRatio is None:
860+
kb.matchRatio = LOWER_RATIO_BOUND
861+
862+
conf.parameters = dict(backup)
863+
conf.parameters[PLACE.GET] = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + "&"
864+
conf.parameters[PLACE.GET] += "%s=%d" % (randomStr(), randomInt())
865+
866+
trueResult = Request.queryPage()
867+
868+
if trueResult:
869+
conf.parameters = dict(backup)
870+
conf.parameters[PLACE.GET] = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + "&"
871+
conf.parameters[PLACE.GET] += "%s=%d %s" % (randomStr(), randomInt(), IDS_WAF_CHECK_PAYLOAD)
872+
873+
falseResult = Request.queryPage()
874+
875+
if not falseResult:
876+
retVal = True
877+
878+
conf.parameters = dict(backup)
879+
880+
if retVal:
881+
warnMsg = "it appears that the target is protected. "
882+
warnMsg += "please consider usage of tampering scripts"
883+
logger.warn(warnMsg)
884+
else:
885+
infoMsg = "it appears that the target is not protected"
886+
logger.info(infoMsg)
887+
888+
return retVal
889+
835890
def checkNullConnection():
836891
"""
837892
Reference: http://www.wisec.it/sectou.php?id=472f952d79293

lib/controller/controller.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from lib.controller.checks import checkRegexp
1919
from lib.controller.checks import checkConnection
2020
from lib.controller.checks import checkNullConnection
21+
from lib.controller.checks import checkWaf
2122
from lib.controller.checks import heuristicCheckSqlInjection
2223
from lib.controller.checks import simpletonCheckSqlInjection
2324
from lib.core.agent import agent
@@ -320,6 +321,9 @@ def start():
320321
if not checkConnection(suppressOutput=conf.forms) or not checkString() or not checkRegexp():
321322
continue
322323

324+
if conf.checkWaf:
325+
checkWaf()
326+
323327
if conf.nullConnection:
324328
checkNullConnection()
325329

lib/core/common.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"""
99

1010
import codecs
11+
import copy
1112
import ctypes
1213
import inspect
1314
import logging
@@ -1924,7 +1925,7 @@ def pushValue(value):
19241925
Push value to the stack (thread dependent)
19251926
"""
19261927

1927-
getCurrentThreadData().valueStack.append(value)
1928+
getCurrentThreadData().valueStack.append(copy.deepcopy(value))
19281929

19291930
def popValue():
19301931
"""

lib/core/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class MOBILES:
8585
NOKIA = "Nokia N97;Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/10.0.012; Profile/MIDP-2.1 Configuration/CLDC-1.1; en-us) AppleWebKit/525 (KHTML, like Gecko) WicKed/7.1.12344"
8686

8787
class HTTPHEADER:
88+
ACCEPT = "Accept"
8889
ACCEPT_ENCODING = "Accept-Encoding"
8990
AUTHORIZATION = "Authorization"
9091
CONNECTION = "Connection"

lib/core/option.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -981,8 +981,8 @@ def __setPrefixSuffix():
981981
else:
982982
boundary.ptype = 1
983983

984-
# Prepend user's provided boundaries to all others boundaries
985-
conf.boundaries.insert(0, boundary)
984+
# user who knows for --prefix/--suffix doesn't want other combinations
985+
conf.boundaries = [boundary]
986986

987987
def __setHTTPAuthentication():
988988
"""
@@ -1021,7 +1021,7 @@ def __setHTTPAuthentication():
10211021
errMsg = "HTTP %s authentication credentials " % aTypeLower
10221022
errMsg += "value must be in format username:password"
10231023
elif aTypeLower == "ntlm":
1024-
regExp = "^(.*?)\\\(.*?):(.*?)$"
1024+
regExp = "^(.*\\\\.*):(.*?)$"
10251025
errMsg = "HTTP NTLM authentication credentials value must "
10261026
errMsg += "be in format DOMAIN\username:password"
10271027

lib/core/optiondict.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,26 +156,25 @@
156156
"trafficFile": "string",
157157
"batch": "boolean",
158158
"charset": "string",
159+
"crawlDepth": "integer",
159160
"eta": "boolean",
160161
"flushSession": "boolean",
161162
"forms": "boolean",
162163
"freshQueries": "boolean",
163-
"updateAll": "boolean"
164+
"parseErrors": "boolean",
165+
"replicate": "boolean",
166+
"updateAll": "boolean",
167+
"tor": "boolean"
164168
},
165169

166170
"Miscellaneous": {
167171
"beep": "boolean",
168172
"checkPayload": "boolean",
169173
"cleanup": "boolean",
170-
"crawlDepth": "integer",
171174
"dependencies": "boolean",
172-
"forms": "boolean",
173175
"googlePage": "integer",
174176
"mobile": "boolean",
175177
"pageRank": "boolean",
176-
"parseErrors": "boolean",
177-
"replicate": "boolean",
178-
"tor": "boolean",
179178
"wizard": "boolean",
180179
"verbose": "integer"
181180
},

lib/core/settings.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@
8080
# length of queue for candidates for time delay adjustment
8181
TIME_DELAY_CANDIDATES = 3
8282

83+
# standard value for HTTP Accept header
84+
HTTP_ACCEPT_HEADER_VALUE = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
85+
8386
# HTTP timeout in silent mode
8487
HTTP_SILENT_TIMEOUT = 3
8588

@@ -370,3 +373,6 @@
370373

371374
# Template used for common column existence check
372375
BRUTE_COLUMN_EXISTS_TEMPLATE = "EXISTS(SELECT %s FROM %s)"
376+
377+
# Payload used for checking of existence of IDS/WAF (dummier the better)
378+
IDS_WAF_CHECK_PAYLOAD = "AND 1=1 UNION ALL SELECT 1,2,3,table_name FROM information_schema.tables"

lib/parse/cmdline.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,9 @@ def cmdLineParser():
471471
general.add_option("--charset", dest="charset",
472472
help="Force character encoding used for data retrieval")
473473

474+
general.add_option("--crawl", dest="crawlDepth", type="int",
475+
help="Crawl the website starting from the target url")
476+
474477
general.add_option("--eta", dest="eta",
475478
action="store_true",
476479
help="Display for each output the "
@@ -480,14 +483,30 @@ def cmdLineParser():
480483
action="store_true",
481484
help="Flush session file for current target")
482485

486+
general.add_option("--forms", dest="forms",
487+
action="store_true",
488+
help="Parse and test forms on target url")
489+
483490
general.add_option("--fresh-queries", dest="freshQueries",
484491
action="store_true",
485492
help="Ignores query results stored in session file")
486493

494+
general.add_option("--parse-errors", dest="parseErrors",
495+
action="store_true",
496+
help="Parse and display DBMS error messages from responses")
497+
498+
general.add_option("--replicate", dest="replicate",
499+
action="store_true",
500+
help="Replicate dumped data into a sqlite3 database")
501+
487502
general.add_option("--save", dest="saveCmdline",
488503
action="store_true",
489504
help="Save options on a configuration INI file")
490505

506+
general.add_option("--tor", dest="tor",
507+
action="store_true",
508+
help="Use default Tor (Vidalia/Privoxy/Polipo) proxy address")
509+
491510
general.add_option("--update", dest="updateAll",
492511
action="store_true",
493512
help="Update sqlmap")
@@ -504,24 +523,21 @@ def cmdLineParser():
504523

505524
miscellaneous.add_option("--check-payload", dest="checkPayload",
506525
action="store_true",
507-
help="IDS detection testing of injection payloads")
526+
help="Offline WAF/IPS/IDS payload detection testing")
527+
528+
miscellaneous.add_option("--check-waf", dest="checkWaf",
529+
action="store_true",
530+
help="Check for existence of WAF/IPS/IDS protection")
508531

509532
miscellaneous.add_option("--cleanup", dest="cleanup",
510533
action="store_true",
511534
help="Clean up the DBMS by sqlmap specific "
512535
"UDF and tables")
513536

514-
miscellaneous.add_option("--crawl", dest="crawlDepth", type="int",
515-
help="Crawl the website starting from the target url")
516-
517537
miscellaneous.add_option("--dependencies", dest="dependencies",
518538
action="store_true",
519539
help="Check for missing sqlmap dependencies")
520540

521-
miscellaneous.add_option("--forms", dest="forms",
522-
action="store_true",
523-
help="Parse and test forms on target url")
524-
525541
miscellaneous.add_option("--gpage", dest="googlePage", type="int",
526542
help="Use Google dork results from specified page number")
527543

@@ -533,18 +549,6 @@ def cmdLineParser():
533549
action="store_true",
534550
help="Display page rank (PR) for Google dork results")
535551

536-
miscellaneous.add_option("--parse-errors", dest="parseErrors",
537-
action="store_true",
538-
help="Parse and display DBMS error messages from responses")
539-
540-
miscellaneous.add_option("--replicate", dest="replicate",
541-
action="store_true",
542-
help="Replicate dumped data into a sqlite3 database")
543-
544-
miscellaneous.add_option("--tor", dest="tor",
545-
action="store_true",
546-
help="Use default Tor (Vidalia/Privoxy/Polipo) proxy address")
547-
548552
miscellaneous.add_option("--wizard", dest="wizard",
549553
action="store_true",
550554
help="Simple wizard interface for beginner users")

lib/request/basic.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from lib.core.data import conf
2828
from lib.core.data import kb
2929
from lib.core.data import logger
30+
from lib.core.exception import sqlmapDataException
3031
from lib.core.settings import ML
3132
from lib.core.settings import META_CHARSET_REGEX
3233
from lib.core.settings import UNICODE_ENCODING
@@ -172,7 +173,12 @@ def decodePage(page, contentEncoding, contentType):
172173
else:
173174
data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(page))
174175

175-
page = data.read()
176+
try:
177+
page = data.read()
178+
except Exception, msg:
179+
errMsg = "detected invalid data for declared content "
180+
errMsg += "encoding '%s' ('%s')" % (contentEncoding, msg)
181+
singleTimeLogMessage(errMsg, logging.ERROR)
176182

177183
if not conf.charset:
178184
httpCharset, metaCharset = None, None

lib/request/connect.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
from lib.core.enums import PLACE
4646
from lib.core.exception import sqlmapConnectionException
4747
from lib.core.exception import sqlmapSyntaxException
48+
from lib.core.settings import HTTP_ACCEPT_HEADER_VALUE
4849
from lib.core.settings import HTTP_SILENT_TIMEOUT
4950
from lib.core.settings import META_REFRESH_REGEX
5051
from lib.core.settings import IS_WIN
@@ -224,6 +225,8 @@ def getPage(**kwargs):
224225
if kb.proxyAuthHeader:
225226
headers[HTTPHEADER.PROXY_AUTHORIZATION] = kb.proxyAuthHeader
226227

228+
headers[HTTPHEADER.ACCEPT] = HTTP_ACCEPT_HEADER_VALUE
229+
227230
headers[HTTPHEADER.HOST] = urlparse.urlparse(url).netloc
228231

229232
if any(map(lambda x: headers[HTTPHEADER.HOST].endswith(':%d' % x), [80, 443])):
@@ -498,10 +501,11 @@ def queryPage(value=None, place=None, content=False, getRatioValue=False, silent
498501
page = None
499502
pageLength = None
500503
uri = None
501-
raise404 = place != PLACE.URI if raise404 is None else raise404
502504

503505
if not place:
504-
place = kb.injection.place
506+
place = kb.injection.place or PLACE.GET
507+
508+
raise404 = place != PLACE.URI if raise404 is None else raise404
505509

506510
payload = agent.extractPayload(value)
507511
threadData = getCurrentThreadData()

0 commit comments

Comments
 (0)