Calificaciones CTFZone 2019: Pollo

A pesar del aplazamiento de la conferencia OFFZONE 2020 , ¡ habrá una final de CTFZone ! Este año se llevará a cabo por primera vez en línea y se transmitirá activamente en las redes sociales.

Anunciaremos los detalles más adelante, pero por ahora, sugerimos explorar el inicio de la tarea web desde la etapa de calificación. El análisis de la solución nos lo envió Devand MacLean de Canadá. Especialmente para Habr, hemos traducido el texto de la startup local y lo invitamos a descubrir qué cadena de vulnerabilidades encontraron los participantes y qué tiene que ver el pollo con ella.



Información general


Autor de la tarea: Pavel Sorokin
Puntos: 470
Número de equipos que resolvieron la tarea: 2

Enlaces útiles:




, - :


  • (/Home/Index);
  • (/Home/Hens);
  • (/Home/Contact);
  • (/Auth/Login).

,   , :


  • ;
  •  API ,   .


La página / Inicio / Gallinas contiene enlaces para descargar "pasaportes" de pollo.


Después de decodificar Base64, encontramos el nombre de archivo del parámetro: 1.txt. Usando CyberChef, codificamos / etc / passwd en Base64 y seguimos el enlace:

http://web-chicken.ctfz.one/File/Download?filename=L2V0Yy9wYXNzd2Q=

Los contenidos del archivo / etc / passwd en el servidor se abren en el navegador , lo que significa Tenemos derecho a leer archivos arbitrarios en el sistema.

Cuando intenta encontrar otros archivos en el sistema, puede observar un error que aparece cada vez que solicita un archivo inexistente (o un archivo que no se puede leer con los derechos de usuario de la aplicación):


Habiendo visto que el error se refiere a la variable de entorno ASPNETCORE_ENVIRONMENT , comenzamos a estudiar los diseños de las aplicaciones web ASP.NET Core. Y vemos lo siguiente:


Para convertir las rutas y los nombres de archivo necesarios y extraerlos a Base64, escribimos un pequeño script de Python ( fetch.py ):


Usando este conocimiento sobre la estructura de la aplicación web MVC creada en ASP.NET Core, usando fetch.py ​​obtenemos el código fuente de los siguientes archivos:


  • ../Views/Shared/_Layout.cshtml
  • ../Views/Home/Index.cshtml
  • ../Views/Home/Contact.cshtml
  • ../Views/Home/Hens.cshtml
  • ../Views/Auth/Login.cshtml

Una vez hecho esto y estudiando el código fuente de cada página, encontramos un detalle interesante en Hens.cshtml


La línea 1 contiene la declaración de inclusión @ usando StackHenneryMVCAppProject, que en ASP.NET significa un enlace a un archivo DLL.

Abrimos ../StackHenneryMVCAppProject.dll, pero no a través de la utilidad Python en Kali Linux, sino en el navegador de Windows, porque vamos a descompilar este archivo en Windows. A través de la utilización de tales la URL:

http://web-chicken.ctfz.one/File/Download?filename=Li4vU3RhY2tIZW5uZXJ5TVZDQXBwUHJvamVjdC5kbGw=

de apertura de archivo DLL en dnSpy (.NET decompilador), ver inmediatamente que en el método Initialize () de clase Config. La configuración , que se ejecuta cuando se inicia la aplicación web, lee el contenido del archivo chicken_domains_internal.txt . Usando un script de Python, extraemos el contenido de este archivo:

web-chicken-flag
web-chicken-auth

El método Initialize () se conecta a  web-chicken-flag a través del puerto 4321 y recibe varios parámetros: secret_token , RSA_keyflag .

Desafortunadamente, no es posible iniciar una conexión a web-chicken-flag. Pero, cuando configuramos el registro de hosts DNS locales para traducir web-chicken-auth a la dirección IP externa del servidor ( 34.89.232.240 ), logramos abrir http: // web-chicken-auth / con la interfaz Swagger UI que se usa para describir y ejecutar comandos a través de la API REST.


Al excavar en el archivo DLL descompilado, puede observar que el controlador AuthController no solo tenía el método Login () , sino también el método Change_Password_Test () :


Cuando vamos a  / Auth / Change_Password_Test vemos este formulario:


Al estudiar el método Change_Password_Test () , entendemos que procesa cuatro parámetros de tipo String a través de una solicitud HTTP POST:


  • nombre de usuario;
  • nueva contraseña;
  • Contraseña anterior;
  • base_url.


Un poco más abajo en el método Change_Password_Test () vemos: sin recibir un valor para base_url , el método toma el valor conf.auth_server , que es web-chicken-auth , y luego agrega / auth / login al final del valor base_url , asignando la cadena resultante a la variable requestUri :


En la siguiente parte importante del código, los valores de cadena del nombre de usuario y los  parámetros de contraseña antigua de la solicitud HTTP POST, y con ellos el  secret_token de la configuración, se insertan en la cadena JSON. Luego, se crea un objeto JSON StringContent a partir de estos datos , que se envía como datos HTTP POST a la variable requestURI creada en el paso anterior.


Ahora el servidor espera a que la solicitud HTTP POST solicite a  Uri que devuelva el objeto JSON con el parámetro de código igual a 0. Si esto no sucede, llegamos a la rama de código que comienza desde la línea 6 y devuelve la vista / Vistas / Autenticación / Inicio de sesión con el error Ingreso no válido / contraseña .

Si la solicitud HTTP POST aún devuelve un objeto JSON con el parámetro de código igual a 0, vamos a la rama de código desde la línea 11. Luego, la variable requestUri2 obtiene el valor http: // web-chicken-auth , se crea otro objeto JSON StringContentcon los parámetros de la solicitud HTTP POST original, como en la parte anterior, y estos datos se envían a la dirección requestUri2  en la solicitud HTTP PUT. Finalmente, el mensaje de cambio de contraseña se devuelve al cliente .


En pocas palabras: todo el proceso comienza con una solicitud HTTP POST a  / Auth / Change_Password_Test , que espera un cierto número de parámetros. Toma estos valores y crea un objeto JSON para enviarlo a  <base_url> / auth / login a través de HTTP POST. Si en respuesta recibe JSON con el parámetro de código igual a 0, crea otro objeto JSON y envía una solicitud HTTP PUT a  http: // web-chicken-auth / auth / change_password .

Al aprender la API REST en Swagger UI, vemos que en el archivo DLL descompilado no solo había las rutas / auth / login y  / auth / change_password , sino también la ruta para / auth / password_recovery .


Esta ruta API esperaba un objeto JSON con dos parámetros: correo electrónicosecret_token . Si lo recibió, devolvió un objeto JSON con una propiedad de código de 0 y cadenas de mensajestokens .


Resulta que si de alguna manera forzamos al método Check_Password_Test () a solicitar inmediatamente la ruta / auth / password_recovery en lugar de / auth / login , para ingresar la rama de cambio de contraseña será suficiente ingresar una dirección de correo electrónico válida.

Nuevamente estudiamos la parte del código en el que el método Check_Password_Test () asigna el valor requestUri en la solicitud HTTP POST original, y entendemos que necesitamos enviar el valor http: // web-chicken-auth / auth / password_recovery # como el parámetro base_url, y requestUri en el final el resultado será http: // web-chicken-auth / auth / password_recovery # / auth / login .

El carácter # en la URL HTTP indica que parte de la dirección de solicitud ha finalizado. Por lo tanto, el servidor que recibe la solicitud simplemente ignorará la parte / auth / login .


Esto, por supuesto, es bueno, pero solo tenemos que descubrir cómo el punto final REST, donde ahora redirigimos la solicitud, recibirá el valor del correo electrónico . Sin este valor, la solicitud fallará y no iremos más allá.

Observamos cuidadosamente cómo el método Check_Password_Test () crea datos JSON, y vemos que los datos de entrada no se borran en absoluto, lo que significa que puede implementar fácilmente sus parámetros. Es suficiente enviar los siguientes parámetros en la solicitud HTTP POST:

username = admin
old_password = pwned "," email ":" admin@chicken.ctf.zone "," lol ":"
new_password = p0tat0
base_url = http: // web-chicken-auth / auth / password_recovery #

El objeto JSON resultante se envía a  / auth / password_recovery :


Con estos datos JSON, cumplimos con los requisitos de que / auth / password_recovery debería devolver una respuesta, después de lo cual el servidor ingresará la rama de cambio de contraseña (el objeto JSON contiene la propiedad de correo electrónico ). Ahora ya no necesitamos el valor de old_password . En esta rama de código, proporcionamos todos los valores necesarios para cambiar la contraseña (la propiedad de nombre de usuario es admin y la propiedad de contraseña es p0tat0 ). La solicitud se completa y recibimos un cambio de contraseña en respuesta.


¡Solo queda ingresar un nuevo nombre de usuario y contraseña en la página de inicio de sesión!


Y aquí está nuestra bandera:


All Articles