WebViews: Carga de URI no segura

Categoría de OWASP: MASVS-CODE: Calidad de código

Descripción general

La carga de URI no segura ocurre cuando una aplicación para Android no puede evaluar correctamente la validez de un URI antes de cargarlo en un WebView.

El motivo subyacente de este tipo de vulnerabilidad es que un URI consta de varias partes, de las cuales, como mínimo, el esquema y el host (de la parte de la autoridad) se deben verificar (p. ej., se incluyen en la lista de entidades permitidas) antes de que el URI se cargue en un WebView o que la aplicación lo use de manera interna.

Estos son los errores más comunes:

  • Verificar el host, pero no el esquema, lo que permite que un atacante use esquemas como http://, content:// o javascript:// con un host autenticado
  • No analizar el URI correctamente, en especial cuando el URI se recibe como una cadena
  • Validar el esquema, pero no el host (validación de host insuficiente)

En cuanto al último caso, esto suele ocurrir cuando la aplicación necesita permitir subdominios arbitrarios de un dominio principal. Por lo tanto, incluso si el nombre de host se extrajo correctamente, la app usa métodos como startsWith, endsWith, o contains de la clase java.lang.String para validar la presencia de un dominio principal en la sección de cadena extraída. Si se usan de forma incorrecta, estos métodos pueden generar resultados defectuosos y forzar a la aplicación a confiar de manera inadecuada en un host potencialmente malicioso.

Impacto

El impacto puede variar según el contexto en el que se usa el host. En los casos en los que cargar un URI malicioso (es decir, uno que omitió el filtrado o la lista de entidades permitidas) en un WebView podría causar la apropiación de cuentas (p. ej., con la suplantación de identidad), la ejecución de código (p. ej., la carga de JavaScript malicioso) o una vulneración del dispositivo (código de exploit que se envía con un hipervínculo).

Mitigaciones

Cuando se manejan URIs de cadena, es importante analizar la cadena como un URI y validar el esquema y el host:

Kotlin

fun isUriTrusted(incomingUri: String, trustedHostName: String): Boolean {
    try {
        val uri = Uri.parse(incomingUri)
        return uri.scheme == "https" && uri.host == trustedHostName
    } catch (e: NullPointerException) {
        throw NullPointerException("incomingUri is null or not well-formed")
    }
}

Java

public static boolean isUriTrusted(String incomingUri, String trustedHostName)
    throws NullPointerException {
        try {
            Uri uri = Uri.parse(incomingUri);
            return uri.getScheme().equals("https") &&
            uri.getHost().equals(trustedHostName);
        } catch (NullPointerException e) {
            throw new NullPointerException(
                "incomingUri is null or not well-formed");
        }
    }

Para la validación del host, después de aislar la parte del URI correspondiente, es importante validarlo por completo (en lugar de hacerlo de forma parcial) para identificar con precisión si el host es de confianza o no. Cuando no se pueden evitar métodos como startsWith o endsWith, es importante utilizar la sintaxis correcta y no pasar por alto los caracteres o símbolos necesarios (por ejemplo, endsWith requiere el carácter de punto "." antes del nombre de dominio para una coincidencia precisa). Si no usas estos caracteres, es posible que se generen coincidencias imprecisas y se ponga en riesgo la seguridad. Dado que los subdominios se pueden anidar infinitamente, la coincidencia de expresiones regulares no es una estrategia recomendada para validar nombres de host.

Colaboradores: Dimitrios Valsamaras y Michael Peck de Microsoft Threat Intelligence

Recursos