targetScanner.py 43.9 KB
Newer Older
1 2 3
#
# This file is part of fimap.
#
4
# Copyright(c) 2009-2012 Iman Karim(ikarim2s@smail.inf.fh-brs.de).
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
# http://fimap.googlecode.com
#
# This file may be licensed under the terms of of the
# GNU General Public License Version 2 (the ``GPL'').
#
# Software distributed under the License is distributed
# on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
# express or implied. See the GPL for the specific language
# governing rights and limitations.
#
# You should have received a copy of the GPL along with this
# program. If not, go to http://www.gnu.org/licenses/gpl.html
# or write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#

from config import settings
22 23
from copy import copy, deepcopy
import pickle
24
import shutil
25 26 27
import baseClass
from report import report
import re,os
28
import os.path
29
import posixpath
30
import ntpath
31 32
import difflib
import time
33 34 35 36 37 38 39

__author__="Iman Karim(ikarim2s@smail.inf.fh-brs.de)"
__date__ ="$30.08.2009 19:59:44$"

class targetScanner (baseClass.baseClass):

    def _load(self):
40
        self.MonkeyTechnique = False
41
        self._log("TargetScanner loaded.", self.LOG_DEVEL)
42
        self.params = {}
43
        self.postparams = {}
44
        self.header = {}
45

46 47
    def prepareTarget(self, url):
        self.Target_URL = url
48

49
        self._log("Inspecting URL '%s'..."%(self.Target_URL), self.LOG_ALWAYS)
50
        self._log("Analyzing provided GET params...", self.LOG_DEBUG);
51
        if (self.Target_URL.count("?") == 0):
52
            self._log("Target URL doesn't have any GET params.", self.LOG_DEBUG);
53
        else:
54 55 56 57 58 59
            data = self.Target_URL.split("?")[1]
            if (data.find("&") == -1):
                self.__addToken(self.params, data)
            else:
                for ln in data.split("&"):
                    self.__addToken(self.params, ln)
60

61
        self._log("Analyzing provided POST params...", self.LOG_DEBUG);
62 63 64 65 66 67 68
        post = self.config["p_post"]
        if (post != ""):
            if (post.find("&") == -1):
                self.__addToken(self.postparams, post)
            else:
                for ln in post.split("&"):
                    self.__addToken(self.postparams, ln)
69 70 71 72
        else:
            self._log("No POST params provided.", self.LOG_DEBUG);
            
        self._log("Analyzing provided headers...", self.LOG_DEBUG);
73 74 75
        header = self.config["header"]
        if (len(header) > 0):
            for key, headerString in header.items():
76 77
                self.header[key] = {}
                if (headerString.find(";") == -1):
78 79
                    self.__addToken(self.header[key], headerString)
                else:
80
                    for ln in headerString.split(";"):
81
                        self.__addToken(self.header[key], ln)
82 83
        else:
            self._log("No headers provided.", self.LOG_DEBUG);
84 85

        return(len(self.params)>0 or len(self.postparams)>0 or len(self.header)>0)
86

87
    def analyzeURL(self, result, k, v, post=None, haxMode=0, header=None, headerKey=None):
88 89
        tmpurl = self.Target_URL
        tmppost = post
90
        headDict = header
91
        
92
        rndStr = self.getRandomStr()
93
        if (haxMode == 0):
94
            tmpurl = tmpurl.replace("%s=%s"%(k,v), "%s=%s"%(k, rndStr))
95
        elif (haxMode == 1):
96
            tmppost = tmppost.replace("%s=%s"%(k,v), "%s=%s"%(k, rndStr))
97 98 99 100 101
        elif (haxMode == 2):
            tmphead = headDict[headerKey]
            tmphead = tmphead.replace("%s=%s"%(k,v), "%s=%s"%(k, rndStr))
            headDict[headerKey] = tmphead
        
102 103
        code = None
        if (post==None):
104
            self._log("Requesting: '%s'..." %(tmpurl), self.LOG_DEBUG)
105
            code = self.doGetRequest(tmpurl, additionalHeaders=headDict)
106
        else:
107
            self._log("Requesting: '%s' with POST('%s')..." %(tmpurl, tmppost), self.LOG_DEBUG)
108
            code = self.doPostRequest(tmpurl, tmppost, additionalHeaders=headDict)
109

110
        if (len(headDict)>0):
111 112
            for ck,vv in headDict.items():
                self._log("  Header: '%s' -> %s"%(ck, vv), self.LOG_DEBUG)
113

114 115 116
        xml2config = self.config["XML2CONFIG"]
        READFILE_ERR_MSG = xml2config.getAllReadfileRegex()

117 118
        if (code != None):
            disclosure_found = False
119
            for lang, ex in READFILE_ERR_MSG:
120 121 122
                RE_SUCCESS_MSG = re.compile(ex%(rndStr), re.DOTALL)
                m = RE_SUCCESS_MSG.search(code)
                if (m != None):
123
                    if (haxMode == 0):
124
                        self._log("Possible local file disclosure found! -> '%s' with Parameter '%s'. (%s)"%(tmpurl, k, lang), self.LOG_ALWAYS)
125
                    elif (haxMode == 1):
126
                        self._log("Possible local file disclosure found! -> '%s' with POST-Parameter '%s'. (%s)"%(tmpurl, k, lang), self.LOG_ALWAYS)
127 128
                    elif (haxMode == 2):
                        self._log("Possible local file disclosure found! -> '%s' with Header(%s)-Parameter '%s'. (%s)"%(tmpurl, k, headerKey, lang), self.LOG_ALWAYS)
129 130 131 132 133 134
                    #self.identifyReadFile(URL, Params, VulnParam)
                    self._writeToLog("READ ; %s ; %s"%(tmpurl, k))
                    disclosure_found = True
                    break

            if (not disclosure_found):
135 136 137 138 139 140 141
                sniper_regex = xml2config.getAllSniperRegex()
                for lang, sniper in sniper_regex:
                    RE_SUCCESS_MSG = re.compile(sniper%(rndStr), re.DOTALL)
                    m = RE_SUCCESS_MSG.search(code)
                    if (m != None):
                        rep = None
                        self._writeToLog("POSSIBLE ; %s ; %s"%(self.Target_URL, k))
142
                        if (haxMode == 0):
143
                            self._log("[%s] Possible file inclusion found! -> '%s' with Parameter '%s'." %(lang, tmpurl, k), self.LOG_ALWAYS)
144
                            rep = self.identifyVuln(self.Target_URL, self.params, k, post, lang, haxMode, None, None, headerKey, headerDict = headDict)
145
                        elif (haxMode == 1):
146
                            self._log("[%s] Possible file inclusion found! -> '%s' with POST-Parameter '%s'." %(lang, tmpurl, k), self.LOG_ALWAYS)
147 148
                            rep = self.identifyVuln(self.Target_URL, self.postparams, k, post, lang, haxMode, None, None, headerKey, headerDict = headDict)
                        elif (haxMode == 2):
149
                            self._log("[%s] Possible file inclusion found! -> '%s' with Header(%s)-Parameter '%s'." %(lang, tmpurl, headerKey, k), self.LOG_ALWAYS)
150
                            rep = self.identifyVuln(self.Target_URL, self.header, k, post, lang, haxMode, None, None, headerKey, headerDict = headDict)
151 152 153 154 155
                        
                        if (rep != None):
                            rep.setVulnKeyVal(v)
                            rep.setLanguage(lang)
                            result.append((rep, self.readFiles(rep)))
156
        return(result)
157

158
    def analyzeURLblindly(self, i, testfile, k, v, find, goBackSymbols, post=None, haxMode=0, isUnix=True, header=None, headerKey=None):
159 160 161 162
        tmpurl = self.Target_URL
        tmppost = post
        rep = None
        doBreak = False
163
        headDict = deepcopy(header)
164
        
165
        if (haxMode == 0):
166
            tmpurl = tmpurl.replace("%s=%s"%(k,v), "%s=%s"%(k, testfile))
167
        elif (haxMode == 1):
168
            tmppost = tmppost.replace("%s=%s"%(k,v), "%s=%s"%(k, testfile))
169 170 171 172 173
        elif (haxMode == 2):
            tmphead = headDict[headerKey]
            tmphead = tmphead.replace("%s=%s"%(k,v), "%s=%s"%(k, testfile))
            headDict[headerKey] = tmphead
            
174 175 176 177
        if (post != None and post != ""):
            self._log("Requesting: '%s'..." %(tmpurl), self.LOG_DEBUG)
        else:
            self._log("Requesting: '%s' with POST('%s')..." %(tmpurl, tmppost), self.LOG_DEBUG)
178 179
        
        code = self.doPostRequest(tmpurl, tmppost, additionalHeaders=headDict)
180
        if (code != None):
181
            if (code.find(find) != -1):
182
                if (haxMode == 0):
183
                    self._log("Possible file inclusion found blindly! -> '%s' with Parameter '%s'." %(tmpurl, k), self.LOG_ALWAYS)
184
                    rep = self.identifyVuln(self.Target_URL, self.params, k, post, None, haxMode, (goBackSymbols * i, False), isUnix, headerDict = headDict)
185
                    
186
                elif (haxMode == 1):
187
                    self._log("Possible file inclusion found blindly! -> '%s' with POST-Parameter '%s'." %(tmpurl, k), self.LOG_ALWAYS)
188
                    rep = self.identifyVuln(self.Target_URL, self.postparams, k, post, None, haxMode, (goBackSymbols * i, False), isUnix, headerDict = headDict)
189
                    
190
                elif (haxMode == 2):
191
                    self._log("Possible file inclusion found blindly! -> '%s' with Header(%s)-Parameter '%s'." %(tmpurl, headerKey, k), self.LOG_ALWAYS)
192
                    rep = self.identifyVuln(self.Target_URL, self.header, k, post, None, haxMode, (goBackSymbols * i, False), isUnix, headerKey, headerDict = headDict)
193 194 195
                
                doBreak = True

196 197 198 199
            else:
                tmpurl = self.Target_URL
                tmpfile = testfile + "%00"
                postdata = post
200
                headDict = deepcopy(header)
201
                if (haxMode == 0):
202
                    tmpurl = tmpurl.replace("%s=%s"%(k,v), "%s=%s"%(k, tmpfile))
203
                elif (haxMode == 1):
204
                    postdata = postdata.replace("%s=%s"%(k,v), "%s=%s"%(k, tmpfile))
205 206
                elif (haxMode == 2):
                    tmphead = headDict[headerKey]
207
                    tmphead = tmphead.replace("%s=%s"%(k,v), "%s=%s"%(k, tmpfile))
208
                    headDict[headerKey] = tmphead
209 210 211 212 213 214
                
                if (post != None and post != ""):
                    self._log("Requesting: '%s'..." %(tmpurl), self.LOG_DEBUG)
                else:
                    self._log("Requesting: '%s' with POST('%s')..." %(tmpurl, postdata), self.LOG_DEBUG)
                
215
                code = self.doPostRequest(tmpurl, postdata, additionalHeaders=headDict)
fimap.dev@gmail.com's avatar
fimap.dev@gmail.com committed
216 217 218
                
                if (code == None):
                    self._log("Code == None. Skipping testing of the URL.", self.LOG_DEBUG)
219 220 221 222 223
	            if (self.config["p_skiponerror"] == True): # User decided to skip blind check if server returned an error.
			self._log("You decided to cancel blind checks when the server returned an error.", self.LOG_ALWAYS)
                	self._log("Code == None. Skipping testing of the URL.", self.LOG_DEBUG)

                    	doBreak = True
fimap.dev@gmail.com's avatar
fimap.dev@gmail.com committed
224 225
                else:
                    if (code.find(find) != -1):
226
                        if (haxMode == 0):
fimap.dev@gmail.com's avatar
fimap.dev@gmail.com committed
227
                            self._log("Possible file inclusion found blindly! -> '%s' with Parameter '%s'." %(tmpurl, k), self.LOG_ALWAYS)
228
                            doBreak = True
229
                        elif (haxMode == 1):
fimap.dev@gmail.com's avatar
fimap.dev@gmail.com committed
230
                            self._log("Possible file inclusion found blindly! -> '%s' with POST-Parameter '%s'." %(tmpurl, k), self.LOG_ALWAYS)
231
                            doBreak = True
232
                        elif (haxMode == 2):
233
                            self._log("Possible file inclusion found blindly! -> '%s' with Header(%s)-Parameter '%s'." %(tmpurl, headerKey, k), self.LOG_ALWAYS)
234
                            doBreak = True
235
                        rep = self.identifyVuln(self.Target_URL, self.params, k, post, None, haxMode, (goBackSymbols * i, True), isUnix, headerKey, headerDict = headDict)
236
                        
237 238 239 240 241
        else:
            # Previous result was none. Assuming that we can break here.
            doBreak = True
        return(rep, doBreak)

242 243 244
    def testTargetVuln(self):
        ret = []

fimap.dev's avatar
fimap.dev committed
245 246
        xml2config = self.config["XML2CONFIG"]

247 248
        self._log("Fiddling around with URL...", self.LOG_INFO)

249
        # Scan Get
250
        for k,v in self.params.items():
251 252
            self.analyzeURL(ret, k, v, self.config["p_post"], 0, self.config["header"])
        # Scan Post
253
        for k,v in self.postparams.items():
254 255 256 257
            self.analyzeURL(ret, k, v, self.config["p_post"], 1, self.config["header"])
        # Scan Headers
        for key,params in self.header.items():
            for k,v in params.items():
258
                self.analyzeURL(ret, k, v, self.config["p_post"], 2, deepcopy(self.config["header"]), key)
259
                
260 261

        if (len(ret) == 0 and self.MonkeyTechnique):
262
            self._log("Sniper failed. Going blind...", self.LOG_INFO)
fimap.dev's avatar
fimap.dev committed
263
            files = xml2config.getBlindFiles()
264 265 266
            
            os_restriction = self.config["force-os"]
            
fimap.dev's avatar
fimap.dev committed
267 268 269 270
            for fileobj in files:
                post = fileobj.getPostData()
                v    = fileobj.getFindStr()
                f    = fileobj.getFilepath()
271
                
272 273 274 275 276 277 278
                if (os_restriction != None):
                    if (fileobj.isWindows() and os_restriction != "windows"):
                        continue
                    
                    if (fileobj.isUnix() and os_restriction != "linux"):
                        continue
                
279 280
                backSyms = (fileobj.getBackSymbols(), fileobj.getBackSymbols(False))
                
281 282 283 284
                get_done  = False
                post_done = False
                head_done = {}
                
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
                for backSym in backSyms:
                    # URL Special Char Multiplier
                    if (self.config["p_multiply_term"] > 1):
                        multi = self.config["p_multiply_term"]
                        backSym  = backSym.replace("..", ".." * multi)
                        backSym  = backSym.replace(fileobj.getBackSymbol(), fileobj.getBackSymbol() * multi)
                        
                    for i in range(xml2config.getBlindMin(), xml2config.getBlindMax()):
                        doBreak = False
                        testfile = f
                        if (i > 0):
                            tmpf = f
                            if (fileobj.isWindows()):
                                tmpf = f[f.find(":")+1:]
                            testfile = backSym * i + tmpf
                        
                        rep = None
302 303 304 305 306 307 308 309 310 311 312 313 314 315
                        if not get_done:
                            for k,V in self.params.items():
                                rep, doBreak = self.analyzeURLblindly(i, testfile, k, V, v, backSym, self.config["p_post"], 0, fileobj.isUnix(), deepcopy(self.config["header"]))
                                if (rep != None):
                                    rep.setVulnKeyVal(V)
                                    rep.setPostData(self.config["p_post"])
                                    rep.setPost(0)
                                    rep.setHeader(deepcopy(self.config["header"]))
                                    ret.append((rep, self.readFiles(rep)))
                                    get_done = True
                        
                        if not post_done:
                            for k,V in self.postparams.items():
                                rep, doBreak = self.analyzeURLblindly(i, testfile, k, V, v, backSym, self.config["p_post"], 1, fileobj.isUnix(), deepcopy(self.config["header"]))
316
                                if (rep != None):
317
                                    rep.setVulnKeyVal(V)
318
                                    rep.setPostData(self.config["p_post"])
319
                                    rep.setPost(1)
320
                                    rep.setHeader(deepcopy(self.config["header"]))
321
                                    ret.append((rep, self.readFiles(rep)))
322 323 324 325 326 327
                                    post_done = True
                        
                    
                        for key,params in self.header.items():
                            if (not head_done.has_key(key)):
                                head_done[key] = False
328
                            
329 330 331 332
                            if (not head_done[key]):
                                for k,val in params.items():
                                    rep, doBreak = self.analyzeURLblindly(i, testfile, k, val, v, backSym, self.config["p_post"], 2, fileobj.isUnix(), deepcopy(self.config["header"]), key)
                                    if (rep != None):
333
                                        rep.setVulnKeyVal(val)
334 335 336 337 338 339 340
                                        rep.setVulnHeaderKey(key)
                                        rep.setPostData(self.config["p_post"])
                                        rep.setPost(2)
                                        rep.setHeader(deepcopy(self.config["header"]))
                                        ret.append((rep, self.readFiles(rep)))
                                        head_done[key] = True

fimap.dev@gmail.com's avatar
fimap.dev@gmail.com committed
341 342
                        if (doBreak): 
                            return(ret) # <-- Return if we found one blindly readable file.
343 344 345 346 347 348
                          
                        # When this is a remote file inclusion test done blindly, we do not want to bruteforce
                        # subdirectories with ../../http://www.... nonsense.
                        if ("R" in fileobj.getFlags()):
                            break
                            
349
                    
350 351 352
        return(ret)


353

354
    def identifyVuln(self, URL, Params, VulnParam, PostData, Language, haxMode=0, blindmode=None, isUnix=None, headerKey=None, headerDict=None):
355 356
        xml2config = self.config["XML2CONFIG"]
        
357
        if (blindmode == None):
358
            r = report(URL, Params, VulnParam)
359 360 361
            script = None
            scriptpath = None
            pre = None
362 363 364
            
            langClass = xml2config.getAllLangSets()[Language]
            
365
            if (haxMode == 0):
366
                self._log("[%s] Identifying Vulnerability '%s' with Parameter '%s'..."%(Language, URL, VulnParam), self.LOG_ALWAYS)
367
            elif (haxMode == 1):
368
                self._log("[%s] Identifying Vulnerability '%s' with POST-Parameter '%s'..."%(Language, URL, VulnParam), self.LOG_ALWAYS)
369 370
            elif (haxMode == 2):
                self._log("[%s] Identifying Vulnerability '%s' with Header(%s)-Parameter '%s'..."%(Language, URL, headerKey, VulnParam), self.LOG_ALWAYS)
371

372
            tmpurl = URL
373
            PostHax = PostData
374
            rndStr = self.getRandomStr()
375

376
            if (haxMode == 0):
377
                tmpurl = tmpurl.replace("%s=%s"%(VulnParam,Params[VulnParam]), "%s=%s"%(VulnParam, rndStr))
378
            elif (haxMode == 1):
379
                PostHax = PostHax.replace("%s=%s"%(VulnParam,Params[VulnParam]), "%s=%s"%(VulnParam, rndStr))
380
            elif (haxMode == 2):
381 382 383
                tmphead = deepcopy(self.config["header"][headerKey])
                tmphead = tmphead.replace("%s=%s"%(VulnParam,Params[headerKey][VulnParam]), "%s=%s"%(VulnParam, rndStr))
                headerDict[headerKey] = tmphead
384
                r.setVulnHeaderKey(headerKey)
385

386
            RE_SUCCESS_MSG = re.compile(langClass.getSniper()%(rndStr), re.DOTALL)
387

388
            code = self.doPostRequest(tmpurl, PostHax, additionalHeaders=headerDict)
389
            if (code == None):
390
                self._log("Identification of vulnerability failed. (code == None)", self.LOG_ERROR)
fimap.dev@gmail.com's avatar
fimap.dev@gmail.com committed
391 392
                return None
                
393 394
            m = RE_SUCCESS_MSG.search(code)
            if (m == None):
395
                self._log("Identification of vulnerability failed. (m == None)", self.LOG_ERROR)
396 397 398
                return None


399
            
400
            r.setPost(haxMode)
401
            r.setPostData(PostData)
402
            r.setHeader(deepcopy(self.config["header"]))
403
            
404
            for sp_err_msg in langClass.getIncludeDetectors():
405
                RE_SCRIPT_PATH = re.compile(sp_err_msg, re.S)
406 407 408
                s = RE_SCRIPT_PATH.search(code)
                if (s != None): break
            if (s == None):
409
                self._log("Failed to retrieve script path.", self.LOG_WARN)
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424

                print "[MINOR BUG FOUND]"
                print "------------------------------------------------------"
                print "It's possible that fimap was unable to retrieve the scriptpath"
                print "because the regex for this kind of error message is missing."
                a = raw_input("Do you want to help me and send the URL of the site? [y = Print Info/N = Discard]")
                if (a=="y" or a=="Y"):
                    print "-----------SEND THIS TO 'fimap.dev@gmail.com'-----------"
                    print "SUBJECT: fimap Regex"
                    print "ERROR  : Failed to retrieve script path."
                    print "URL    : " + URL
                    print "-----------------------------------------------------------"
                    raw_input("Copy it and press enter to proceed with scanning...")
                else:
                    print "No problem! I'll continue with your scan..."
425

426 427
                return(None)
            else:
428
                script = s.group('script')
429 430 431 432 433 434
                if (script != None and script[1] == ":"): # Windows detection quick hack
                    scriptpath = script[:script.rfind("\\")]
                    r.setWindows()
                elif (script != None and script.startswith("\\\\")):
                    scriptpath = script[:script.rfind("\\")]
                    r.setWindows()
435
                else:
436
                    scriptpath = os.path.dirname(script)
437 438 439 440
                    if (scriptpath == None or scriptpath == ""):
                        self._log("Scriptpath is empty! Assuming that we are on toplevel.", self.LOG_WARN)
                        scriptpath = "/"
                        script = "/" + script
441

442 443
                # Check if scriptpath was received correctly.
                if(scriptpath!=""):
444
                    self._log("Scriptpath received: '%s'" %(scriptpath), self.LOG_INFO)
445 446
                    r.setServerPath(scriptpath)
                    r.setServerScript(script)
447

448 449

            if (r.isWindows()):
450 451 452
                self._log("Operating System is 'Windows'.", self.LOG_INFO)
            else:
                self._log("Operating System is 'Unix-Like'.", self.LOG_INFO)
453

454

455
            errmsg = m.group("incname")
456

457 458 459 460 461 462 463 464 465 466 467 468 469
            if (errmsg == rndStr):
                r.setPrefix("")
                r.setSurfix("")
            else:
                tokens = errmsg.split(rndStr)
                pre = tokens[0]
                addSlash = False
                if (pre == ""):
                    pre = "/"
                #else:
                #    if pre[-1] != "/":
                #       addSlash = True

470 471 472

                rootdir = None
                
473
                if (pre[0] != "/"):
474 475 476 477
                    if (r.isUnix()):
                        pre = posixpath.join(r.getServerPath(), pre)
                        pre = posixpath.normpath(pre)
                        rootdir = "/"
478
                        pre = self.relpath_unix(rootdir, pre)
479 480 481 482 483
                    else:
                        pre = ntpath.join(r.getServerPath(), pre)
                        pre = ntpath.normpath(pre)
                        if (pre[1] == ":"):
                            rootdir = pre[0:3]
484 485 486 487 488
                        elif (pre[0:1] == "\\"):
                            self._log("The inclusion points to a network path! Skipping vulnerability.", self.LOG_WARN)
                            return(None)
                    
                        
489
                        pre = self.relpath_win(rootdir, pre)
490
                else:
491
                    pre = self.relpath_unix("/", pre)
492
                if addSlash: pre = rootdir + pre
493 494
                
                #Quick fix for increasing success :P
fimap.dev's avatar
fimap.dev committed
495 496
                if (pre != "."):
                    pre = "/" + pre
497
                
498 499 500 501 502
                sur = tokens[1]
                if (pre == "."): pre = ""
                r.setPrefix(pre)
                r.setSurfix(sur)

503

504
                if (sur != ""):
505
                    self._log("Trying NULL-Byte Poisoning to get rid of the suffix...", self.LOG_INFO)
506
                    tmpurl = URL
507
                    PostHax = PostData
508
                    head = deepcopy(self.config["header"])
509
                    
510
                    if (haxMode == 0):
511
                        tmpurl = tmpurl.replace("%s=%s"%(VulnParam,Params[VulnParam]), "%s=%s%%00"%(VulnParam, rndStr))
512
                    elif (haxMode == 1):
513
                        PostHax = PostData.replace("%s=%s"%(VulnParam,Params[VulnParam]), "%s=%s%%00"%(VulnParam, rndStr))
514
                    elif (haxMode == 2):
515
                        tmphead = deepcopy(self.config["header"][headerKey])
516 517
                        tmphead = tmphead.replace("%s=%s"%(VulnParam,Params[headerKey][VulnParam]), "%s=%s%%00"%(VulnParam, rndStr))
                        head[headerKey] = tmphead
518
                        r.setVulnHeaderKey(headerKey)
519
                        
520
                    code = self.doPostRequest(tmpurl, PostHax, additionalHeaders = head)
521
                    if (code == None):
522
                        self._log("NULL-Byte testing failed.", self.LOG_WARN)
523
                        r.setSuffixBreakable(False)
524
                    elif (code.find("%s\\0%s"%(rndStr, sur)) != -1 or code.find("%s%s"%(rndStr, sur)) != -1):
525
                        self._log("NULL-Byte Poisoning not possible.", self.LOG_INFO)
526
                        r.setSuffixBreakable(False)
527
                    else:
528
                        self._log("NULL-Byte Poisoning successfull!", self.LOG_INFO)
529
                        r.setSurfix("%00")
530 531
                        r.setSuffixBreakable(True)
                        r.setSuffixBreakTechName("Null-Byte")
532

533
                if (sur != "" and not r.isSuffixBreakable() and self.config["p_doDotTruncation"]):
fimap.dev@gmail.com's avatar
fimap.dev@gmail.com committed
534
                    if (r.isUnix() and self.config["p_dot_trunc_only_win"]):
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
                        self._log("Not trying dot-truncation because it's a unix server and you have not enabled it.", self.LOG_INFO)
                    else:
                        self._log("Trying Dot Truncation to get rid of the suffix...", self.LOG_INFO)
                        dot_trunc_start = self.config["p_dot_trunc_min"] 
                        dot_trunc_end   = self.config["p_dot_trunc_max"]
                        dot_trunc_step  = self.config["p_dot_trunc_step"]
                        max_diff        = self.config["p_dot_trunc_ratio"]
    
                        self._log("Preparing Dot Truncation...", self.LOG_DEBUG)
                        self._log("Start: %d"%(dot_trunc_start), self.LOG_DEVEL)
                        self._log("Stop : %d"%(dot_trunc_end), self.LOG_DEVEL)
                        self._log("Step : %d"%(dot_trunc_step), self.LOG_DEVEL)
                        self._log("Ratio: %f"%(max_diff), self.LOG_DEVEL)
                        desturl = URL
                        PostHax = PostData
550 551 552 553
                        
                        head = deepcopy(self.config["header"])
                        
                        code1 = self.doPostRequest(URL, PostData, additionalHeaders=head)
554
       
555 556 557 558 559 560
                        vulnParamBlock = None
                        
                        if (haxMode in (0,1)):
                            vulnParamBlock = "%s=%s%s"%(VulnParam, Params[VulnParam], r.getAppendix())
                        else:
                            vulnParamBlock = "%s=%s%s"%(VulnParam, Params[headerKey][VulnParam], r.getAppendix())
561
                        
562
                        if (haxMode == 0):
563
                            desturl = desturl.replace("%s=%s"%(VulnParam,Params[VulnParam]), vulnParamBlock)
564
                        elif (haxMode == 1):
565
                            PostHax = PostHax.replace("%s=%s"%(VulnParam,Params[VulnParam]), vulnParamBlock)
566
                        elif (haxMode == 2):
567 568 569 570
                            tmphead = deepcopy(self.config["header"][headerKey])
                            tmphead = tmphead.replace("%s=%s"%(VulnParam,Params[headerKey][VulnParam]), vulnParamBlock)
                            headerDict[headerKey] = tmphead
                            r.setVulnHeaderKey(headerKey)
571 572 573 574 575 576 577 578 579 580
                        
                        self._log("Test URL will be: " + desturl, self.LOG_DEBUG)
                        
                        success = False
                        
                        seqmatcher = difflib.SequenceMatcher()
                        
                        for i in range (dot_trunc_start, dot_trunc_end, dot_trunc_step):
                            tmpurl = desturl
                            tmppost = PostHax
581
                            tmphead = deepcopy(headerDict)
582
                            if (haxMode == 0):
583
                                tmpurl = tmpurl.replace(vulnParamBlock, "%s%s"%(vulnParamBlock, "." * i))
584
                            elif (haxMode == 1):
585
                                tmppost = tmppost.replace(vulnParamBlock, "%s%s"%(vulnParamBlock, "." * i))
586
                            elif (haxMode == 2):
587
                                tmp = tmphead[headerKey]
588
                                tmp = tmp.replace(vulnParamBlock, "%s%s"%(vulnParamBlock, "." * i))
589 590 591
                                tmphead[headerKey] = tmp
                                
                            content = self.doPostRequest(tmpurl, tmppost, additionalHeaders=tmphead)
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609
                            if (content == None):
                                self._log("Dot Truncation testing failed :(", self.LOG_WARN)
                                break
                            
                            seqmatcher.set_seqs(code1, content)
                            ratio = seqmatcher.ratio()
                            if (1-max_diff <= ratio <= 1):
                                self._log("Dot Truncation successfull with: %d dots ; %f ratio!" %(i, ratio), self.LOG_INFO)
                                r.setSurfix("." * i)
                                r.setSuffixBreakable(True)
                                r.setSuffixBreakTechName("Dot-Truncation")
                                success = True
                                break
                            else:
                                self._log("No luck with (%s)..." %(i), self.LOG_DEBUG)
                        if (not success):
                            self._log("Dot Truncation not possible :(", self.LOG_INFO)
                        
610 611 612
            if (scriptpath == ""):
                # Failed to get scriptpath with easy method :(
                if (pre != ""):
613
                    self._log("Failed to retrieve path but we are forced to go relative!", self.LOG_WARN)
fimap.dev's avatar
fimap.dev committed
614
                    self._log("Go and try it to scan with --enable-blind.", self.LOG_WARN)
615 616
                    return(None)
                else:
617
                    self._log("Failed to retrieve path! It's an absolute injection so I'll fake it to '/'...", self.LOG_WARN)
618 619 620
                    scriptpath = "/"
                    r.setServerPath(scriptpath)
                    r.setServerScript(script)
621

622 623 624 625 626 627 628
            return(r)
        
        
        else:
            # Blindmode
            prefix = blindmode[0]
            isNull = blindmode[1]
629
            self._log("Identifying Vulnerability '%s' with Parameter '%s' blindly..."%(URL, VulnParam), self.LOG_ALWAYS)
630 631 632
            r = report(URL, Params, VulnParam)
            r.setBlindDiscovered(True)
            r.setSurfix("")
633 634
            r.setHeader(deepcopy(self.config["header"]))
            r.setVulnHeaderKey(headerKey)
635
            if isNull: r.setSurfix("%00")
636 637
            r.setSuffixBreakable(isNull)
            r.setSuffixBreakTechName("Null-Byte")
638 639 640 641 642
            if (prefix.strip() == ""):
                r.setServerPath("/noop")
            else:
                r.setServerPath(prefix.replace("..", "a"))
            r.setServerScript("noop")
643 644 645 646 647 648 649 650
            
            slash = ""
            if (isUnix):
                slash = "/"
            else:
                slash = "\\"
            
            r.setPrefix(prefix + slash) # <-- Increase success
651 652
            if (not isUnix):
                r.setWindows()
653
            return(r)
654 655 656


    def readFiles(self, rep):
657
        xml2config = self.config["XML2CONFIG"]
658 659 660 661 662 663
        langClass = None
        if rep.isLanguageSet():
            langClass = xml2config.getAllLangSets()[rep.getLanguage()]
        else:
            if (self.config["p_autolang"]):
                self._log("Unknown language - Autodetecting...", self.LOG_WARN)
fimap.dev's avatar
fimap.dev committed
664
                if (rep.autoDetectLanguageByExtention(xml2config.getAllLangSets())):
665
                    self._log("Autodetect thinks this could be a %s-Script..."%(rep.getLanguage()), self.LOG_INFO)
fimap.dev's avatar
fimap.dev committed
666
                    self._log("If you think this is wrong start fimap with --no-auto-detect", self.LOG_INFO)
667 668 669
                    langClass = xml2config.getAllLangSets()[rep.getLanguage()]
                else:
                    self._log("Autodetect failed!", self.LOG_ERROR)
670
                    self._log("Start fimap with --no-auto-detect if you know which language it is.", self.LOG_ERROR)
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701
                    return([])
            else:
                self._log("Unknown language! You have told me to let you choose - here we go.", self.LOG_WARN)
                boxheader = "Choose language for URL: %s" %(rep.getURL())
                boxarr = []
                choose = []
                idx = 0
                for Name, langClass in xml2config.getAllLangSets().items():
                    boxarr.append("[%d] %s"%(idx+1, Name))
                    choose.append(Name)
                    idx += 1
                boxarr.append("[q] Quit")
                self.drawBox(boxheader, boxarr)
                inp = ""
                while (1==1):
                    inp = raw_input("Script number: ")
                    if (inp == "q" or inp == "Q"):
                        return([])
                    else:
                        try:
                            idx = int(inp)
                            if (idx < 1 or idx > len(choose)):
                                print "Choose out of range..."
                            else:
                                rep.setLanguage(choose[idx-1])
                                langClass = xml2config.getAllLangSets()[rep.getLanguage()]
                                break
                        except:
                            print "Invalid Number!"
        
        
702 703 704 705 706
        
        files     = xml2config.getRelativeFiles(rep.getLanguage())
        abs_files = xml2config.getAbsoluteFiles(rep.getLanguage())
        rmt_files = xml2config.getRemoteFiles(rep.getLanguage())
        log_files = xml2config.getLogFiles(rep.getLanguage())
707 708 709
        rfi_mode = settings["dynamic_rfi"]["mode"]

        ret = []
710
        self._log("Testing default files...", self.LOG_DEBUG)
711

712 713 714 715 716
        for fileobj in files:
            post = fileobj.getPostData()
            p    = fileobj.getFindStr()
            f    = fileobj.getFilepath()
            type = fileobj.getFlags()
717
            quiz = answer = None
718
            if (post != None and post != ""):
719 720 721
                quiz, answer = langClass.generateQuiz()
                post = post.replace("__QUIZ__", quiz)
                p = p.replace("__ANSWER__", answer)
722
                
723
            if ((rep.getSurfix() == "" or rep.isSuffixBreakable() or f.endswith(rep.getSurfix()))):
fimap.dev's avatar
fimap.dev committed
724 725 726 727 728 729
                if (rep.isUnix() and fileobj.isUnix() or rep.isWindows() and fileobj.isWindows()):
                    if (self.readFile(rep, f, p, POST=post)):
                        ret.append(f)
                        self.addXMLLog(rep, type, f)
                    else:
                        pass
730
                else:
fimap.dev's avatar
fimap.dev committed
731
                    self._log("Skipping file '%s' because it's not suitable for our OS."%f, self.LOG_DEBUG)
732
            else:
733
                self._log("Skipping file '%s'."%f, self.LOG_INFO)
734

735
        self._log("Testing absolute files...", self.LOG_DEBUG)
736 737 738 739 740
        for fileobj in abs_files:
            post = fileobj.getPostData()
            p    = fileobj.getFindStr()
            f    = fileobj.getFilepath()
            type = fileobj.getFlags()
fimap.dev's avatar
fimap.dev committed
741 742
            canbreak = fileobj.isBreakable()
            
743 744
            quiz = answer = None
            if (post != None):
745 746 747
                quiz, answer = langClass.generateQuiz()
                post = post.replace("__QUIZ__", quiz)
                p = p.replace("__ANSWER__", answer)
748
            if (rep.getPrefix() == "" and(rep.getSurfix() == "" or rep.isSuffixBreakable() or f.endswith(rep.getSurfix()) or canbreak)):
fimap.dev's avatar
fimap.dev committed
749 750 751 752
                if canbreak:
                    #SUPERDUPER URL HAX!
                    rep.setSurfix("&")
                
fimap.dev's avatar
fimap.dev committed
753 754 755 756 757 758
                if (rep.isUnix() and fileobj.isUnix() or rep.isWindows() and fileobj.isWindows()):
                    if (self.readFile(rep, f, p, True, POST=post)):
                        ret.append(f)
                        self.addXMLLog(rep, type, f)
                    else:
                        pass
759
                else:
fimap.dev's avatar
fimap.dev committed
760
                    self._log("Skipping absolute file '%s' because it's not suitable for our OS."%f, self.LOG_DEBUG)
761
            else:
762
                self._log("Skipping absolute file '%s'."%f, self.LOG_INFO)
763

764
        self._log("Testing log files...", self.LOG_DEBUG)
765 766 767 768 769
        for fileobj in log_files:
            post = fileobj.getPostData()
            p    = fileobj.getFindStr()
            f    = fileobj.getFilepath()
            type = fileobj.getFlags()
fimap.dev's avatar
fimap.dev committed
770
            
771
            if ((rep.getSurfix() == "" or rep.isSuffixBreakable() or f.endswith(rep.getSurfix()))):
fimap.dev's avatar
fimap.dev committed
772 773 774 775 776 777
                if (rep.isUnix() and fileobj.isUnix() or rep.isWindows() and fileobj.isWindows()):
                    if (self.readFile(rep, f, p)):
                        ret.append(f)
                        self.addXMLLog(rep, type, f)
                    else:
                        pass
778
                else:
fimap.dev's avatar
fimap.dev committed
779
                   self._log("Skipping log file '%s' because it's not suitable for our OS."%f, self.LOG_DEBUG) 
780
            else:
781
                self._log("Skipping log file '%s'."%f, self.LOG_INFO)
782 783

        if (rfi_mode in ("ftp", "local")):
784 785
            if (rfi_mode == "ftp"): self._log("Testing remote inclusion dynamicly with FTP...", self.LOG_INFO)
            if (rfi_mode == "local"): self._log("Testing remote inclusion dynamicly with local server...", self.LOG_INFO)
786 787
            if (rep.getPrefix() == ""):
                fl = up = None
fimap.dev@gmail.com's avatar
fimap.dev@gmail.com committed
788
                quiz, answer = langClass.generateQuiz()
789 790
                if (rfi_mode == "ftp"):
                    fl = settings["dynamic_rfi"]["ftp"]["ftp_path"] + rep.getAppendix()
791 792
                    
                    up = self.FTPuploadFile(quiz, rep.getSurfix())
793
                    # Discard the suffix if there is a forced directory structure.
794
                    if (up["http"].endswith(rep.getAppendix())):
795
                        rep.setSurfix("")
796 797 798
                        up["http"] = up["http"][:len(up["http"]) - len(rep.getAppendix())] 
                    
                    
799
                    
800
                elif(rfi_mode == "local"):
fimap.dev@gmail.com's avatar
fimap.dev@gmail.com committed
801
                    up = self.putLocalPayload(quiz, rep.getAppendix())
802 803
                    if (not up["http"].endswith(rep.getAppendix())):
                        rep.setSurfix("")
fimap.dev@gmail.com's avatar
fimap.dev@gmail.com committed
804
                if (self.readFile(rep, up["http"], answer, True)):
805 806 807 808
                    ret.append(up["http"])
                    rep.setRemoteInjectable(True)
                    self.addXMLLog(rep, "rxR", up["http"])

809 810 811 812 813 814 815
                if (rfi_mode == "ftp"): 
                    if up["dirstruct"]:
                        self.FTPdeleteDirectory(up["ftp"])
                    else:
                        self.FTPdeleteFile(up["ftp"])
                if (rfi_mode == "local"): 
                    self.deleteLocalPayload(up["local"])
816
        else:
817
            self._log("Testing remote inclusion...", self.LOG_DEBUG)
818 819 820 821 822
            for fileobj in rmt_files:
                post = fileobj.getPostData()
                p    = fileobj.getFindStr()
                f    = fileobj.getFilepath()
                type = fileobj.getFlags()
fimap.dev's avatar
fimap.dev committed
823 824
                canbreak = fileobj.isBreakable()
                
825 826
                if (rep.getPrefix() == "" and(rep.getSurfix() == "" or rep.isSuffixBreakable() or f.endswith(rep.getSurfix()) or canbreak)):
                    if ((not rep.isSuffixBreakable() and not rep.getSurfix() == "") and f.endswith(rep.getSurfix())):
827 828
                        f = f[:-len(rep.getSurfix())]
                        rep.setSurfix("")
fimap.dev's avatar
fimap.dev committed
829 830 831
                    elif (canbreak):
                        #SUPERDUPER URL HAX!
                        rep.setSurfix("&")
fimap.dev's avatar
fimap.dev committed
832 833 834 835 836 837 838 839
                    
                    if (rep.isUnix() and fileobj.isUnix() or rep.isWindows() and fileobj.isWindows()):
                        if (self.readFile(rep, f, p, True)):
                            ret.append(f)
                            rep.setRemoteInjectable(True)
                            self.addXMLLog(rep, type, f)
                        else:
                            pass
840
                    else:
fimap.dev's avatar
fimap.dev committed
841
                        self._log("Skipping remote file '%s' because it's not suitable for our OS."%f, self.LOG_DEBUG)
842
                else:
843
                    self._log("Skipping remote file '%s'."%f, self.LOG_INFO)
844

845 846


847 848 849 850
        self.saveXML()
        return(ret)


851
    def readFile(self, report, filepath, filepattern, isAbs=False, POST=None, HEADER=None):
852
        self._log("Testing file '%s'..." %filepath, self.LOG_INFO)
853 854 855 856
        
        xml2config = self.config["XML2CONFIG"]
        langClass = xml2config.getAllLangSets()[report.getLanguage()]
        
857 858 859 860 861
        tmpurl = report.getURL()
        prefix = report.getPrefix()
        surfix = report.getSurfix()
        vuln   = report.getVulnKey()
        params = report.getParams()
862 863
        isunix = report.isUnix()
        
864
        scriptpath = report.getServerPath()
865
        
866 867 868 869 870 871
        postdata    = report.getPostData()
        header      = deepcopy(report.getHeader())
        vulnHeader  = report.getVulnHeader()
        haxMode = report.isPost
        
        
872 873 874 875 876 877 878
        filepatha = ""
        if (prefix != None and prefix != "" and prefix[-1] == "/"):
            prefix = prefix[:-1]
            report.setPrefix(prefix)

        if (filepath[0] == "/"):
            filepatha = prefix + filepath
879 880
        if (report.isWindows() and len(prefix.strip()) > 0 and not isAbs):
            filepatha = prefix + filepath[3:]
881
        elif len(prefix.strip()) > 0 and not isAbs:
882
            filepatha = prefix + "/" + filepath
883 884 885
        else:
            filepatha = filepath

886

887
        if (scriptpath[-1] != "/" and filepatha[0] != "/" and not isAbs and report.isUnix()):
888 889 890
            filepatha = "/" + filepatha

        payload = "%s%s"%(filepatha, surfix)
891 892 893 894
        if (payload.endswith(report.getAppendix())):
            payload = payload[:len(payload) - len(report.getAppendix())]
        
                    
895
        if (haxMode == 0):
896
            tmpurl = tmpurl.replace("%s=%s" %(vuln, params[vuln]), "%s=%s"%(vuln, payload))
897
        elif (haxMode == 1):
898
            postdata = postdata.replace("%s=%s" %(vuln, params[vuln]), "%s=%s"%(vuln, payload))
899 900 901 902
        elif (haxMode == 2):
            tmphead = header[vulnHeader]
            tmphead = tmphead.replace("%s=%s"%(vuln, self.header[vulnHeader][vuln]), "%s=%s"%(vuln, payload))
            header[vulnHeader] = tmphead
903

904
        self._log("Testing URL: " + tmpurl, self.LOG_DEBUG)
905

906
        RE_SUCCESS_MSG = re.compile(langClass.getSniper()%(filepath), re.DOTALL)
907
        code = None
908
        if (POST != None and POST != "" or postdata != None and postdata != ""):
909 910 911 912 913
            if (postdata != None):
                if (POST == None):
                    POST = postdata
                else:
                    POST = "%s&%s"%(postdata, POST)
914
            code = self.doPostRequest(tmpurl, POST, additionalHeaders = header)
915
        else:
916
            code = self.doGetRequest(tmpurl, additionalHeaders = header)
917 918 919 920 921 922 923 924 925 926

        if (code == None):
            return(False)

        m = RE_SUCCESS_MSG.search(code)
        if (m == None):
            if (filepattern == None or code.find(filepattern) != -1):
                #self._writeToLog("VULN;%s;%s;%s;%s"%(tmpurl, vuln, payload, filepath))
                return(True)

927
    def __addToken(self, arr, token):
928
        if (token.find("=") == -1):
929
            arr[token] = ""
930
            self._log("Token found: [%s] = none" %(token), self.LOG_DEBUG)
931 932 933
        else:
            k = token.split("=")[0]
            v = token.split("=")[1]
934
            arr[k] = v
935
            self._log("Token found: [%s] = [%s]" %(k,v), self.LOG_DEBUG)