No le des poder al cliente (Parte 1)

Iniciado por NERV0, Mayo 28, 2018, 12:38:41 AM

Tema anterior - Siguiente tema

0 Miembros y 1 Visitante están viendo este tema.

Mayo 28, 2018, 12:38:41 AM Ultima modificación: Mayo 28, 2018, 01:03:26 AM por NERV0

Hace una semana, me encontré con una aplicación para Android (La cual no mencionaré y evitaré mostrar el logo de la misma) que tiene una idea muy buena y sinceramente me gustó... Pero, como siempre pica el gusanito de la curiosidad, me tenté a ver si podía jugar con poquito con el código.

Y así empieza este hermoso post de "No le des poder al cliente (Parte 1)", por
NERV0.

ANTES QUE NADA SE DEBE ACLARAR QUE EN NINGÚN MOMENTO PERJUDIQUÉ A LOS DEMÁS USUARIOS NI A LOS ADMINISTRADORES. Y DE LA MISMA BUENA INTENCIÓN, SE LES HA ENVIADO TODOS LOS EXPLOITS QUE SE ENCONTRARON A LO LARGO DE LA INVESTIGACIÓN.



I - Etapa de adaptación y reconocimento:

Apenas instalé la aplicación, apareció el logo de carga, y noté que estaba desplazado un poco mas hacia la derecha (Si, aunque suene a chiste). Cuando vi esto dije: "Si el logo de carga está desplazado, entonces la aplicación debe estar... Chingada" (Por decirlo de una manera). Me sorprendió que era mejor de lo que me esperaba.

Noté que tenía bastantes lagazos comparada con otras aplicaciones mucho mas pesadas como por ejemplo: Facebook y Facebook Messenger. Así que me dije: "¿Si tiene tanto lag, será porque están corriendo anuncios en segundo plano y no los veo?" (Siempre pensando lo malo primero), "¿O será que está usando algún motor especial?". Efectivamente no duré ni 15 minutos y tuve que sacarme la duda.

Como corro Android en mi celular, instalé una aplicación llamada "APK Editor" (Pueden buscarla en la playstore) y lo primero que hice, fue echar un ojo dentro de los archivos.












¿Les suena a algo? Es mas que obvio, "Cordova"... "Apache Cordova" sistema multiplataforma que corre aplicaciones web! Eso significa dos cosas:

1 - Tiene que estar en la APK el index.html.
2 - De lo contrario, corre mitad servidor mitad celular.

Ahora ya sabemos porqué se tilda tanto...
Obviamente como se ve en la imágen, encontré el index.html y todo el codigo del cliente :)
Listo, ¿que mas puedo pedir? Servido en bandeja...












II - Etapa de desensamblado y "desofuscado":

Nos instalamos en el celular alguna aplicación para hacer backup de la APK, y copiamos la misma a la PC.


(En mi caso la aplicación para backups es la que figura en la imágen)




Nos bajamos el "Apktool":

No tienes permitido ver los links. Registrarse o Entrar a mi cuenta (Archivo .jar)
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta (Y nos bajamos el archivo .bat)

Una vez que tenemos estos dos archivos, los ponemos en una carpeta que nosotros querramos y copiamos la APK a la misma carpeta, así:






Abrimos la consola en la carpeta del decompilador, y ejecutamos el comando:
Código: php
apktool d Nombre de Aplicación.apk



(Ahora con W10 hay que abrir la cmd, dirigirse a la carpeta con cd y recién ahí ejecutar el comando)


Esto nos va a crear una carpeta con el mismo nombre que la APK, y ahí vamos a tener todos los datos necesarios.



Ahora, vamos a poder ver en nuestra PC el código ;)



Nos adentramos en "assets" y encontramos nuevamente nuestro preciado index... ¿Lo ejecutamos?:






Pero no funciona... ¿Porqué?
Simple, estamos corriendo un index que tiene soporte para dispositivo movil, no para navegador/PC.
Como sabemos que usa Cordova, vamos a instalar Node, NPM y Cordova!

Primero nos instalamos Node y NPM, porque sin ellos no existiría Cordova:

Node + NPM:
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Y ahora si podemos instalarlo:

Código: php
npm install -g cordova


Una vez instalados los paquetes, nos vamos a una carpeta que nos sea cómoda para trabajar y comenzamos creando un proyecto de Cordova. ¿Porque? No hay porqué. No, bueno, si, para poder correr una aplicación de Cordova, necesitamos si o si convertir la plataforma "móvil" a navegador o aplicación Windows... (Se recomienda aplicación de navegador porque es mas cómoda)

Nos vamos a la consola, nos posicionamos sobre la carpeta deseada, y ponemos el siguiente comando:

Código: php
cordova create Nombre_De_La_App


(Puede que tarde, no se preocupen... Si ven que no se mueve nada, presionen "Enter" en la consola, capaz necesitan confirmar algo y no figura, lo digo porque me ha pasado)


Ahora tenemos el nucleo principal de Cordova... Tenemos que agregar el soporte para navegador. Nos vamos adentro del proyecto creado, y en la consola escribimos:

Código: php
cordova platform add browser


Plataforma de Android por si tienen el Android SDK instalado y quieren compilar la APP desde ahí:
Código: php
cordova platform add android


Una vez que tenemos la plataforma "browser", copiamos todos los archivos de la carpeta "www" a la carpeta "www" del proyecto:



Y una vez copiados, ejecutamos el código:

Código: php
cordova run browser




Ahora que Apache Cordova modifica los datos de las carpetas cordova.js, cordova_plugins.js, y service-worker.js, podemos correrla en navegador:



Durante mi tiempo en la aplicación en el celular, tuve tiempo de analizar en detalle las funciones del cliente, funciones de los usuarios, sistemas de niveles, y la parte interesante, compras... Así que antes que nada, procedemos a "desofuscar" los archivos ".js":



Como pueden ver, no están muy "ofuscados" que digamos, está todo juntito, así que mejor para nosotros, nos vamos a nuestro divino "JS Beautifier"...

Copiamos todo el contenido del ".js", lo pegamos en el input gigante, y presionamos el botón:




El resultado, es el siguiente:



Hermosa diferencia :) . Lo copiamos, y lo reemplazamos en el ".js" ! Así uno por uno...











III - Etapa "Mandale mecha":


Yo ya llevo un tiempo con la APP, por lo tanto tuve la chance y el tiempo de conocerla a fondo, pero hubo algo que me tentó, y fue esto:



Como pueden ver, si compramos el objeto "Whistle" (Silbato en inglés), podemos ganar unas monedas mas. Osea que, si yo gano, se me devuelven las monedas gastadas y ¿me quedo con un extra? Vamos a usar el objeto...



Si inspeccionamos los elementos de la prueba, sabemos que:



1 - La imagen "sailor.png" cargó gracias a un archivo ".js".
2 - Sabemos que aplicaciones de este estilo cargan mediante un ".js" principal que llama a otros.

Así que es totalmente una perdida de tiempo analizar un ".js" de un tamaño considerable como el polyfills.js y vendor.js. Si lo pensamos así, es preferible buscar una palabra clave entre los archivos potenciales...

Comenzando la búsqueda, podemos ver que encontramos la llamada en el archivo "0.js":



Y si bajamos... BANG:



Vamos a analizar el código:


Código: javascript


t.prototype.takeQuiz = function() {
                var t = this;
                if (void 0 != this.quizSailor.id) { //Arranca con la prueba
                    this.zone.run(function() {
                        t.isShowQuiz = !0
                    }), setTimeout(function() { // Y nos tira un timer...
                        t.StartTimer() // Que arranca en la función start timer
                    }, 2e3), this.infoRef.child("quiz_taken").transaction(function(t) {
                        return t + 1
                    });
                    var e = parseInt(this.userinfo.myInfo.nb_quiz) + 1;
                    this.myRef.update((n = {
                        isWhistle: !1,
                        nb_quiz: e
                    }, n["doneQuiz/" + this.quizSailor.id + "/taken"] = !0, n)), this.ga.trackEvent("Quiz", "Quiz taken", "Quiz taken", 1);
                    var n
                }
            }, t.prototype.doneTimer = function() { //Si el timer termina
                var t = this;
                void 0 == this.pickedAnswer && (this.displayToast("fr" == this.userinfo.myInfo.language ? '<div><img width="30" src="assets/img/sailor.png"></div>Temps écoulé, trop lent moussaillon !' : '<div><img width="30" src="assets/img/sailor.png"></div>Too slow matey!', "whiteError", 3e3), this.showCorrect(), this.takeSailorGold(), this.zone.run(function() {
                    t.getPercent = 1, t.isDoneQuiz = !0
                })) // Me muestra la correcta y perdí
            }, t.prototype.takeSailorGold = function() {
                this.infoRef.child("sailor_gold_taken").transaction(function(t) {
                    return t + 2
                })
            }, t.prototype.StartTimer = function() { //Arranca el timer
                var t = this;
                this.isDoneTimer || this.isDoneQuiz || (this.timer = setTimeout(function(e) {
                    t.maxTime -= 1, t.maxTime > 0 ? (t.getPercent = Math.round(t.maxTime / t.initMaxTime * 100), t.isDoneTimer = !1, t.StartTimer()) : (t.doneTimer(), t.isDoneTimer = !0)
                }, 1e3))
//Como se puede ver, arriba tenemos un setTimeout (un contador) y tenemos la variable t.maxTime -= 1, nos damos cuenta que va descontando 1 segundo del contador... A tener en cuenta

            }, t.prototype.displayToast = function(t, e, n) {
                var r = this.toastCtrl.create({
                    message: t,
                    duration: n,
                    showCloseButton: !0,
                    cssClass: "center raleway whiteToast " + e + " bold lighter57",
                    position: "bottom"
                });
                r.onDidDismiss(function() {}), r.present()
            }, t.prototype.showCorrect = function() { //Nos muestra la respuesta correcta
                var t = "one";
                1 == this.quizSailor.correct_answer ? t = "one" : 2 == this.quizSailor.correct_answer ? t = "two" : 3 == this.quizSailor.correct_answer ? t = "three" : 4 == this.quizSailor.correct_answer && (t = "four"); // Y como podemos ver, ahora sabemos que la respuesta correcta es "this.quizSailor.correct_answer"
                document.querySelector("." + t).classList.add("correct")
            }, t.prototype.pickAnswer = function(t) {
                var e = this;
                if (!this.isDoneQuiz) {
                    this.zone.run(function() {
                        e.pickedAnswer = t, e.isDoneQuiz = !0, e.isDoneTimer = !0
                    });
                    var n = "one";
                    if (1 == t ? n = "one" : 2 == t ? n = "two" : 3 == t ? n = "three" : 4 == t && (n = "four"), t != this.quizSailor.correct_answer) {
                        document.querySelector("." + n).classList.add("wrong"), this.showCorrect(), this.takeSailorGold(), this.displayToast("fr" == this.userinfo.myInfo.language ? '<div><img width="30" src="assets/img/sailor.png"></div>Pas de chance !' : '<div><img width="30" src="assets/img/sailor.png"></div>Bad luck!', "whiteError", 3e3), this.ga.trackEvent("Quiz", "Wrong answer", "Wrong answer", 1) //Si yo elegí mal la respuesta, me marca como mal y me muestra la correcta
                    } else { //De lo contrario, me suma monedas a mi usuario y puntos de reputación...
                        var r = parseInt(this.userinfo.myInfo.gold) + parseInt(this.quizSailor.reward); //Acá me suma la ganancia...
                        void 0 == this.userinfo.myInfo.good_answer && (this.userinfo.myInfo.good_answer = 0);
                        var i = parseInt(this.userinfo.myInfo.good_answer) + 1;
                        this.myRef.update({
                            gold: r,
                            good_answer: i
                        });
                        var s = this;
                        this.infoRef.child("sailor_gold_given").transaction(function(t) {
                            return t + parseInt(s.quizSailor.reward)
                        });
                        document.querySelector("." + n).classList.add("correct"), this.displayToast("fr" == this.userinfo.myInfo.language ? '<div><img width="30" src="assets/img/sailor.png"></div>Bien joué ! Voilà pour toi<br>Pièces d\'or +' + this.quizSailor.reward : '<div><img width="30" src="assets/img/sailor.png"></div>Well played! Gold coins +' + this.quizSailor.reward, "whiteSuccess", 3e3), this.ga.trackEvent("Quiz", "Correct answer", "Correct answer", 1) // Y me dice que respondí bien...
                    }
                }



Tenemos el funcionamiento completo de las preguntas en el lado del cliente. Esto es algo que JAMÁS se debe hacer. Es vital para una aplicación web hacer que cualquier movimiento se realice a través del servidor y no en nuestra computadora.

Ahora, si modificamos el código:

Código: javascript


t.prototype.takeQuiz = function() {
                var t = this;
                if (void 0 != this.quizSailor.id) {
                    this.zone.run(function() {
                        t.isShowQuiz = !0
                    }), setTimeout(function() { //Ahora cuando arranca el tiempo
                        t.doneTimer() //Directamente se termina el tiempo
                    }, 2e3), this.infoRef.child("quiz_taken").transaction(function(t) {
                        return t + 1
                    });
                    var e = parseInt(this.userinfo.myInfo.nb_quiz) + 1;
                    this.myRef.update((n = {
                        isWhistle: !1,
                        nb_quiz: e
                    }, n["doneQuiz/" + this.quizSailor.id + "/taken"] = !0, n)), this.ga.trackEvent("Quiz", "Quiz taken", "Quiz taken", 1);
                    var n
                }
            }, t.prototype.doneTimer = function() {
               
                var t = this.quizSailor.correct_answer; //Marcamos t como la respuesta correcta

                /*
var t = this;
                void 0 == this.pickedAnswer && (this.displayToast("fr" == this.userinfo.myInfo.language ? '<div><img width="30" src="assets/img/sailor.png"></div>Temps écoulé, trop lent moussaillon !' : '<div><img width="30" src="assets/img/sailor.png"></div>Too slow matey!', "whiteError", 3e3), this.showCorrect(), this.takeSailorGold(), this.zone.run(function() {
                    t.getPercent = 1, t.isDoneQuiz = !0
                }))*/

                var r = parseInt(this.userinfo.myInfo.gold) + parseInt(this.quizSailor.reward) * 9999; // Y nos agrega las monedas que queremos
                void 0 == this.userinfo.myInfo.good_answer && (this.userinfo.myInfo.good_answer = 0);
                var i = parseInt(this.userinfo.myInfo.good_answer) + 1;
                this.myRef.update({
                    gold: r,
                    good_answer: i
                });
                var s = this.quizSailor.correct_answer;
                this.infoRef.child("sailor_gold_given").transaction(function(t) {
                    return t + parseInt(s.quizSailor.reward)
                });

            }, t.prototype.takeSailorGold = function() {
                this.infoRef.child("sailor_gold_taken").transaction(function(t) {
                    return t + 2
                })
            }, t.prototype.StartTimer = function() {
                var t = this;
                this.isDoneTimer || this.isDoneQuiz || (this.timer = setTimeout(function(e) {
                    t.maxTime -= 1, t.maxTime > 0 ? (t.getPercent = Math.round(t.maxTime / t.initMaxTime * 100), t.isDoneTimer = !1, t.StartTimer()) : (t.doneTimer(), t.isDoneTimer = !0)
                }, 1e3))
            }, t.prototype.displayToast = function(t, e, n) {
                var r = this.toastCtrl.create({
                    message: t,
                    duration: n,
                    showCloseButton: !0,
                    cssClass: "center raleway whiteToast " + e + " bold lighter57",
                    position: "bottom"
                });
                r.onDidDismiss(function() {}), r.present()
            }, t.prototype.showCorrect = function() {
                var t = "one";
                1 == this.quizSailor.correct_answer ? t = "one" : 2 == this.quizSailor.correct_answer ? t = "two" : 3 == this.quizSailor.correct_answer ? t = "three" : 4 == this.quizSailor.correct_answer && (t = "four");
                document.querySelector("." + t).classList.add("correct")
            }, t.prototype.pickAnswer = function(t) {
                var e = this;
                if (!this.isDoneQuiz) {
                    this.zone.run(function() {
                        e.pickedAnswer = t, e.isDoneQuiz = !0, e.isDoneTimer = !0
                    });
                    var n = "one";
                    if (1 == t ? n = "one" : 2 == t ? n = "two" : 3 == t ? n = "three" : 4 == t && (n = "four"), t != this.quizSailor.correct_answer) {
                        document.querySelector("." + n).classList.add("wrong"), this.showCorrect(), this.takeSailorGold(), this.displayToast("fr" == this.userinfo.myInfo.language ? '<div><img width="30" src="assets/img/sailor.png"></div>Pas de chance !' : '<div><img width="30" src="assets/img/sailor.png"></div>Bad luck!', "whiteError", 3e3), this.ga.trackEvent("Quiz", "Wrong answer", "Wrong answer", 1)
                    } else {
                        var r = parseInt(this.userinfo.myInfo.gold) + parseInt(this.quizSailor.reward);
                        void 0 == this.userinfo.myInfo.good_answer && (this.userinfo.myInfo.good_answer = 0);
                        var i = parseInt(this.userinfo.myInfo.good_answer) + 1;
                        this.myRef.update({
                            gold: r,
                            good_answer: i
                        });
                        var s = this;
                        this.infoRef.child("sailor_gold_given").transaction(function(t) {
                            return t + parseInt(s.quizSailor.reward)
                        });
                        document.querySelector("." + n).classList.add("correct"), this.displayToast("fr" == this.userinfo.myInfo.language ? '<div><img width="30" src="assets/img/sailor.png"></div>Bien joué ! Voilà pour toi<br>Pièces d\'or +' + this.quizSailor.reward : '<div><img width="30" src="assets/img/sailor.png"></div>Well played! Gold coins +' + this.quizSailor.reward, "whiteSuccess", 3e3), this.ga.trackEvent("Quiz", "Correct answer", "Correct answer", 1)
                    }
                }
            }



Como terminé después de agregarme 9999 monedas?



Pero si lo pensamos bien... Si casi todo está del lado del cliente, eso significa que por mas baneado que esté en el servidor, yo puedo "desbanearme" quitando los bloqueos en el cliente... ¿No? Vamos a ver... Usamos el mismo metodo que con el "sailor.png" y... Acá está:





Y si bajamos, nos encontramos con linea de código que chequea si yo hice trampa... Se supone que al desactivarla no me debería aparecer el cartel... ¿Verdad?



No hay mas ban:



Y estoy lleno de monedas:







Ahora podemos seguir usando la APP sin problemas :)

Saquen sus propias conclusiones. Me gustaría leerlas en los comentarios para que la gente que es mas entendida en el tema nos explique como evitar meter la pata en cosas como estas al momento de programar una app web.


Proximamente se viene una parte 2, porque a como pueden imaginar, esto recién es la punta del iceberg!

Quiero aclarar una cosa antes de terminar el post:

NO BUSQUEN LA APLICACIÓN
(Ya se repararon la mayoría de los exploits, por eso mismo recién ahora publico una parte)
Y lo aclaro porque yo mismo me aseguré de avisarle a los administradores sobre todos los errores que tenían. Y a medida que los vayan reparando y yo vaya teniendo el tiempo de publicarlos, voy a seguir sacando posts de lo que encontré.

Si alguno de ustedes la conoce, le pido que no intente replicar lo que se ve en el post. Los administradores tienen la IP, codigo MAC del dispositivo con el cual usaste la APP, cuentas vinculadas a tu dispositivo y obviamente tu cuenta de Google Play. Así que eviten romper los términos y condiciones.

Un saludo, NERV0




"Ciertos programas informáticos son el reflejo del ego académico del pelotudo que los desarrolla"

Me lleva a pensar que si exise esa aplicacion , deben existir muchas con el mismo problema, como programador me enseñaron que todo se hacia en el server,utilizando digamos MVC, separar esto es muy importante ya que se pueden hacer este tipo de cosas, que buen post!!

 
         @No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Leo tu post  con mucho agrado.

Dando inicio con el despertar de la curiosidad que te provocó la aplicación -auténtica llave de la investigación-  hasta finalizar con el aviso a los navegantes.

Una desarrollo lógico, ordenado, prolijo, ilustrado y descriptivo que atrapa en cada paso.

Felicitaciones por la investigación y análisis, por esa búsqueda del qué, por qué y para qué; claves del conocimiento y de la significación del saber.

Esperamos la segunda parte.

+1
Saludos
Gabriela
Tú te enamoraste de mi valentía, yo me enamoré de tu oscuridad; tú aprendiste a vencer tus miedos, yo aprendí a no perderme en tu abismo.

Es un poco interesante tu publicación ya que el impacto no es grave y no afecta comercialmente a la compañia desarrolladora de la aplicación según pude entender de la lectura (...). Quizás eso se exceptue en tu segunda parte.

Ahora déjame recomendarte mis consejos como conocedores de in-seguridad informática que somos:

Primero:


Citar

Quiero aclarar una cosa antes de terminar el post:

NO BUSQUEN LA APLICACIÓN
(Ya se repararon la mayoría de los exploits, por eso mismo recién ahora publico una parte)
Y lo aclaro porque yo mismo me aseguré de avisarle a los administradores sobre todos los errores que tenían. Y a medida que los vayan reparando y yo vaya teniendo el tiempo de publicarlos, voy a seguir sacando posts de lo que encontré.


El término EXPLOIT, es demasiado ambiguo ponerlo aquí, porque Exploit tecnicamente significa a mi entender que se trata de un code snippet (o fragmento de programa), que se utiliza o se escribe para aprovechar un agujero, vulnerabilidad informática.

Aquí lo correcto a poner en tu conclusión es Misconfigurationya que estas hablando de un servidor web.

Y no es segun yo, lo dice OWASP: No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Segundo:


Citar

Si alguno de ustedes la conoce, le pido que no intente replicar lo que se ve en el post. Los administradores tienen la IP, codigo MAC del dispositivo con el cual usaste la APP, cuentas vinculadas a tu dispositivoy obviamente tu cuenta de Google Play. Así que eviten romper los términos y condiciones.


¿Qué quiere decir esto?, que se está violando la privacidad o compartiendo los datos sin consentimiento de cada usuario que otorga sus permisos a la supuesta app descargada de Google Play (Esto se interpreta como una filtración de información sensible) ¿?. O acaso el desarrollador de la app es un usuario de Underc0de ¿?. Quién decida hacer maldades o no en la APK es problema suyo total el impacto es bastante bajo y solo se trata de ganar monedas demás.

Yo no sé como interpretar tus conclusiones...

#Saludos

Gracias, @No tienes permitido ver los links. Registrarse o Entrar a mi cuenta ! "deben existir muchas con el mismo problema" y es verdad. No si se debe a la credulidad en que los usuarios son "tontos" o por simple falta de conocimiento. Pero si, se debe trabajar mas desde el lado del servidor, sinó se le da al usuario todo en bandeja!

Gracias, @No tienes permitido ver los links. Registrarse o Entrar a mi cuenta ! Me pone muy contento que te haya gustado el post. Para mi, sin la curiosidad no sabríamos los "por qués" del universo. Se que en algunas partes el lenguaje no fue el adecuado, prometo pasar para la próxima de coloquial a técnico  :) ! Gracias de nuevo por el +1, me pone muy contento saber que mi aporte es un buen material para la comunidad.

Gracias @No tienes permitido ver los links. Registrarse o Entrar a mi cuenta por tomarte el tiempo de hablar sobre el impacto económico y sobre los terminos y condiciones para que un error a abusar en el código se convierta en un "exploit".

  • Aunque para mi, no es una vulnerabilidad del servidor, es una vulnerabilidad en el cliente, lo que lleva a convertirlo en un exploit aunque sea del tamaño de un alfiler. Calculo que cuando veas la parte 2, vas a verlo de otra manera "[...] Quizás eso se exceptue en tu segunda parte. [...]"

  • Cuando uno acepta los terminos y condiciones (TyC) de una aplicación (ya sea los mismos TyC de Google, que nadie lee), dice claramente que en el caso que sea necesario se extraerá información privada del usuario. Obviamente esta aplicación en sus TyC, al momento de romper las reglas, extrae esos datos privados que mencioné anteriormente.






Un saludo enorme a los tres, y los espero en la parte 2 ! NERV0
"Ciertos programas informáticos son el reflejo del ego académico del pelotudo que los desarrolla"