Seguridad en aplicaciones SPA

seg

Ayer tuvimos una interesante mesa redonda, done expusimos nuestras experiencias en el desarrollo y planificación de aplicaciones web siguiendo la filosofía SPA «Single Page Application».

En este debate expuse una duda sobre la seguridad de este tipo de aplicaciones, porque creo que no está bien resuelta. Pero por el comentario de Alfredo y de su compañero de Plain Concepts creo que no quedo del todo claro mi punto de vista.

Primero de todo quiero aclarar que no dije o eso espero :), que la seguridad de una aplicación web dependa de que si un usuario pueda o no ver tu código. Por eso me gustaría hacer un pequeño resume a ver si de esa manera puedo exponer mejor mi duda y podamos debatir sobre el tema.

Seguridad de los servicios y datos

Esta parte bajo mi punto de vista está bien resuelta con la utilización de oauth y los tokens tanto para la autentificación como para la autorización al acceso de recursos.

Seguridad de la interfaz de usuario

Esta parte es la que me chirria si nos basamos en la filosofía SPA,  tener las vistas completamente en el cliente y gestionar todo desde JavaScript.

La parte de autentificación no me preocupa porque podemos utilizar el mismo sistema que para los servicios. Pero si me preocupa la autorización de la partes privadas de la interfaz. Cualquier aplicación profesional te pedirán que dependiendo del rol del usuario pueda acceder a acciones del menú o a vistas que otro usuario no tendría que tener acceso.

Por la misma naturaleza de JavaScript si gestionamos esto desde el cliente personalmente me parece poco seguro «es fácilmente accesible y modificable» y vuelvo a repetir que mis servicios si que están protegidos.

Mi solución

La opción que he utilizado para evitar este problema desde angularJS , es al definir el enrutamiento de los controladores y las vista, utilizar RequireJs para cargarlas vistas desde el servidor de forma perezosa. De esta manera puedo utilizar la seguridad de ASP.NET y la localización de los recursos y solo cargar en el cliente el HTML validado desde el servidor sin perder la funcionalidad SPA.

Con este mezcla de cliente-servidor evito dar una responsabilidad innecesaria al cliente y evitar futuros problemas de seguridad.

Debate

Ahora me gustaría saber vuestra opinión sobre el tema  y si habéis  encontrado otra alternativa  para solucionar este problema. Como he comentado antes para mi no se puede resolver de una forma natural con una aplicación SPA  gestionando las vistas completamente desde el cliente.

Espero vuestros comentarios ;-)

https://plus.google.com/u/0/events/cufio59kfjdrd74dp5iha9lr7l0?authkey=CNC-4OOugMmNtAE

21 comentarios en “Seguridad en aplicaciones SPA

  1. Hola Marc,

    Te ha explicado perfectamente y además me parece genial abrir un post para debatir sobre este tema ;-)

    Yo que conste que no tengo gran experiencia en SPA, así que seguro cualquiera de los que estuvisteis ayer tendréis una más acertada respuesta, pero en mi opinión, no me preocupa (al menos por ahora) que un usuario malintencionado puede manipular el código JS para terminar viendo una pantalla «sin datos» que no le toca según su autorización. Es decir, podría llegar a ver una pantalla pero siempre sin datos, porque los datos cuando los solicitara se le negarían según su rol.

    Claro, si hablamos de un escenario 100% offline este problema es mayor, ya hemos descargado a cliente tanto estructura (HTML) como datos y aquí no se que decir, pido el comodín del público.

    Un saludo.

  2. Sin utilizar Require, lo hago exactamente igual que tu. Es decir no descargo al cliente ni una linea de html para un usuario que no tenga permisos, si eso se hace y se deja la responsabilidad en el cliente no me parece serio.

    Pero voy a ir un poquito más lejos imagina que el usuario x tiene permisos para una determinada vista a las 10 de la mañana y por una decisión del administrador decide quitarle esos permisos a las 11 horas si este usuario entro a esa vista a las 9:00 como bien sabes está en la cache de angular y por tanto puede ver esa vista.

    Eso como lo tienes solucionado?

    Yo en un principio pensé en esto http://www.html5rocks.com/en/tutorials/notifications/quick/ pero claro con la iglesia hemos topado http://caniuse.com/#feat=notifications con lo cual al final en algunos casos he optado por un websocket o bien por hacer un rountrip al servidor cada vez que se produce esto.

    $rootScope.$on(«$routeChangeStart», function (event, next, current) {……}

    • Buenas tardes, aquí Juan Luna, no se si utilizáis Nodejs en el backend, yo lo hago y hay unas cabeceras http (independientes de Nodejs) que impiden que el usuario utilice la cache del navegador, de manera que cuando intenta acceder utilizando esa caché, el servidor lo actualiza, devolviéndole una versión nueva y actualizada.
      Esto era una brecha de seguridad grave ya que tu puedes actualizar tus módulos en el servidor pero un ‘hacker-maléfico’ podía utilizar, versiones anteriores usando la cache.
      De echo un módulo npm de nodejs llamado helmet se encarga de gestionar las cabeceras de las peticiones http, en la parte de seguridad, para evitar otras cosas como ataques CRSS, y otras cositas, echarle un vistazo, aunque no desarrolleis backend, esto os puede ayudar en vuestros desarrollos, para no hacer dos veces lo mismo, no se si estais desarrollando en solitario, (ese es mi caso) o pertenecéis a un equipo, siendo así comentarles al grupo de backend vuestras inquietudes y que ellos opinen y aporten soluciones.
      De todas maneras hay un sitio buenísimo que explica cuales son las cuestiones fundamentales de seguridad que hay que tener en cuenta para desarrollar con Nodejs-express y por supuesto esto involucra a Angular, osea en el satck MEAN.
      http://expressjs.com/es/advanced/best-practice-security.html
      Una cosita la url de mi web esta en desarrollo, no ha sido subida al hosting todavía.

      • Por otro lado en el front end con angular tengo rutas que implementan una pequeña cuestion de seguridad
        //los profiles
        .when(URL_USER_PROFILE, {// URL_USER_PROFILE // #/user/profile
        controller: «profileUserCtrl»,
        controllerAs: «pu_sc»,
        templateUrl: «app_gt3w/ConceptBlocks/profiles/user_profile.html»,
        resolve: {
        userAuthentication: userIsAuthenticated
        }//resolve
        })

        //los profiles
        .when(URL_ADMIN_PROFILE, { // URL_ADMIN_PROFILE, {// ‘#/admin/profile’
        controller: «profileAdminCtrl»,
        controllerAs: «admin_sc»,
        templateUrl: «app_gt3w/ConceptBlocks/profiles/admin_profile.html»
        ,resolve: {
        userAuthentication: userIsAdmin
        }//resolve
        })

        userIsAuthenticated y userIsAdmin son dos fucniones que hacen una comprobacion via ajax usando una promesa de angular

        function userIsAuthenticated(authService, $location, $rootScope) {//, $q, $http, $location
        //console.log(«In userIsAuthenticated»);
        if ($rootScope.user){
        var promise = authService.userIsAuthenticated();
        promise.then(function (data) {
        if (data.checkUserAuth === «authenticated») {
        //console.log(«Bienvenido»);
        return true;
        } else {
        //console.log(«acceso denegado»);
        $location.path(‘/’);
        }
        });
        }else {
        $location.path(‘/’);
        }
        }
        a parte de otras medidas de seguridad como utilizar un interceptador de envios de peticiones http de angular para comprobar que los tokens enviados son correptos

        $httpProvider.interceptors.push([‘$q’, ‘$location’, ‘$localStorage’, function ($q, $location, $localStorage) {
        return {
        ‘request’: function (config) {
        config.headers = config.headers || {};

        if ($localStorage[NAME_TOKEN_AUTH]) {
        config.headers.Authorization = ‘Bearer ‘ + $localStorage[NAME_TOKEN_AUTH];
        } else {
        config.headers.Authorization = NAME_TOKEN_PAGE;
        }
        return config;
        },
        ‘responseError’: function (response) {
        if (response.status === 401 || response.status === 403) {
        $location.path(‘/’);
        }
        return $q.reject(response);
        }
        };
        }]);

        }]//$routeProvider
        )//.config

        con esta accion el servidor debe analizar todas las peticiones que requieran de seguridad y analizar el token que va en esa cabecera para dar una respuesta desde este tal como esta response.senStatus(401) || response.sendStatus(403) or res.json({data:data})

  3. Marc Rubiño dijo:

    @Sergio como bien dice @Pedro no me parece serio dejar estas responsabilidades en el cliente y esto que no le das importancia mañana puede ser un agujero de seguridad. Aunque tristemente seguimos haciendo webs sensibles a taques XSS y SQL injection.

    Por otra parte @Pedro también me he encontrado con el problema del cache y por el momento no he encontrado una solución elegante :(

  4. Para mi lo único que es elegante a fecha de hoy es utilizar «Notification Api». Pero claro donde radica el problema, pues sencillo.

    Si de una vez y para siempre se exigiese a los fabricantes de browser o como a ellos les gusta decir «user agent» :) que cumplieran con la norma a rajatabla y que tuviesen un tiempo limite para actualizar otro gallo nos cantaría.

    Pero eso es soñar con los ojos abiertos:)

  5. Lo cierto es que parece una buena idea no permitir a un usuario ver una pantalla que no le toca (aunque sea vacía). En esto estoy de acuerdo, la verdad es que es irrefutable. Podría haber matices, pero esta claro que es mejor que no vea pantallas a las que no podría acceder según su nivel de autorización. Pero ¿Cómo podría favorecer eso a la proliferación de ataques XSS o SQL injection? Yo creo que si no estás protegido contra estos ataques de forma «global», mucho no importa si ven una u otra pantalla, no?

  6. @sergio Creo que nadie a dicho o por lo menos yo no lo he entendido así que eso sea un motivo de ataque XSS y por supuesto mucho menos Sql Inyection. El problema es que no es serio como ya comente.

    Imagina que hago una auditoria de tu web y detecto eso, tu crees que aún sabiendo que no es preocupante no lo voy a citar en un informe.

    Pues esa es la seriedad a la que nos referimos, pero como ya hemos comentado es bastante difícil controlarlo en un SPA.

  7. Marc Rubiño dijo:

    Exactamente eso, dejar todo en cliente no es motivo para un ataque XSS pero es probable que te dejes cosas que faciliten las cosas a un ataque malintencionado.

  8. Vale, me parece que ahora estoy más en vuestra línea, entiendo lo que queréis decir… Me ha costado, i’m sorry. Ciertamente, parece razonable no bajar todo a cholón… o mejor dicho, que cuando el navegador pida HTML (vistas) se validen también contra la autorización del usuario, de la misma forma que se hace con los datos. Vosotros lo habéis dicho, parece más «serio» y «profesional».

    Pues siento deciros entonces que si no viene nadie más a animar el cotarro, estamos los 3 de acuerdo :)

  9. jmhdez dijo:

    Hola a todos,

    Llego un poco tarde a la fiesta y tampoco vi la mesa redonda de ayer, así que a lo mejor lo que digo ya se ha discutido, pero por muy de acuerdo que hayáis acabado los tres, yo no acabo de ver el problema de bajar el html de páginas no autorizadas.

    Está claro que lo de seguridad por oscuridad no es una buena idea, como dice Marc en el post, por lo que no bajar las páginas no incrementa la seguridad. Es verdad que cuanto más código, más superficie de ataque hay para XSS y amigos, pero todas las páginas de la aplicación son visibles para alguien (aunque no sea el usuario actual) por lo que ya deberían estar convenientemente protegidas frente a eso.

    Entonces, ¿cuál es el problema de que un usuario se baje un html que, en principio no está visible y que, en el peor de los casos, si manipula el código javascript de la aplicación puede llegar a verlo pero sin datos porque el servidor no se los servirá?

    Tendría sentido evitarlo por un tema de optimización de recursos, pero ese es un escenario distinto en el que hay que tener en cuenta más factores y que no tiene nada que ver con seguridad.

    De hecho, hace muchos, muchos años, usábamos unas aplicaciones que se instalaban en los PCs (no sé si os acordáis) y se conectaban a servidores. Arquitectura cliente/servidor lo llamaban. Cuando te instalabas la aplicación, tenías en local todos los formularios de la aplicación, y era resposabilidad de ella (y del servidor) asegurarse de que no accedías a nada que no debieras. Si lo que os preocupa es se baja el código fuente de javascript, no veo mucha diferencia entre bajase javascript minificado y tener una aplicación en C#, Java o Visual Basic que son sumamente fáciles de descompilar.

    Autorizar la descarga de recursos implica una complejidad adicional (como los require de Marc) y plantea problemas (como el invalidado de cache de Pedro), por lo que no creo que sea algo que merezca la pena hacer siempre (y menos por motivos de «seguridad»).

    En definitiva, que el único argumento que veo (y en el que coincidís los tres) es que no se debe hacer porque «es poco serio», pero, sinceramente, eso sí que me parece un argumento «poco serio» ;-P

    Un saludo,

    Juanma.

  10. Marc Rubiño dijo:

    Me parece una buena explicación, pero igualmente el problema del cache lo tendrás te bajes el HTML o no, porque ya no vuelve a hacer la petición al servidor y ese usuario seguirá logado hasta que el navegador quiera.

    Creo que son cosas que ahora parecen triviales pero seguro que darán de que hablar en algún tiempo.

    Si no que se lo digan a Chema Alonso todo lo que puede hacer con el fichero de paginas ocultas con el robot.txt o con los documentos del servidor tipo Excel, PDF, etc que creemos no importan a nadie.

  11. Me parece que estoy podría ser un empate técnico porque al final es un tema de «percepción» y que entendemos cada uno de nosotros sobre seguridad, rendimiento, optimización, etc.
    Además, depende del escenario donde vaya a desplegarse la SPA, la opinión podría variar. Si la aplicación es offline no hay discusión, simplemente tiene que bajarse todo. Si es híbrida, cada uno decidirá el punto medio donde quedarse.
    En cualquier caso, sano y enriquecedor debate!

  12. Juanma (@gulnor) dijo:

    El problema de la cache sólo es un problema en el caso de que te preocupe que el usuario pueda acceder a una vista a la que (ya) no tiene acceso.

    Si te da igual que acceda a ese html porque el servidor no devuelve información cacheada sino que aplica *siempre* las reglas de autorización pertinentes (tal y como explicas tú en el post al hablar de servicios), que el usuario acceda a un html cacheado da igual.

    No me malinterpretes, estoy de acuerdo contigo en que la seguridad es algo muy importante y que hay que cuidar todos los detalles, simplemente creo que en este caso descargar o no el html no influye en la seguridad (o al menos no acabo de ver cómo puede influir).

  13. Mi ultimo centavo:)

    Os voy a poner un caso de la vida real.

    Imaginemos por un momento una lista de facturas donde tenemos dos funcionalidades.

    1. Imprimir factura.
    2. Aprobar factura y cobros.

    El usuario A tiene permiso para imprimir factura, pero con el razonamiento de no acometer complejidades el usuario B que no tiene permisos para imprimir, cuando entre a esa pantalla como puede entrar según lo que comentáis, le da a imprimir y le sale en blanco. La verdad es que queda muy profesional:) O más bien cuando le de imprimir y vamos al servidor le decimos que no tiene permisos, también es profesional :) Veo una pantalla y luego no puedo hacer nada.

    Segundo supuesto, ahora cambiamos el orden de los usuarios. B puede aprobar facturas y A no. ¿Cuando le decimos al usuario que no puede hacer eso?.

    Cuando leemos los datos de la factura al navegar a «Aprobar factura» o cuando pulsa «Aceptar». Porque si en la primera opción, lo veo medio aceptable, pero claro imaginemos que lo hacemos cuando acepta los datos introducidos y en determinados casos puede ser un campo o muchos. Nada le dejamos rellenar todos los campos para decirle que no tiene usted permisos:)

    Vamos yo no soy un experto en usabilidad ni mucho menos y es más cada día empiezo a creer que soy experto de nada.

    Pero yo haría un software donde el usuario A no pudiese ver la opción de «Aprobar facturas» y el usuario B no pudiese ver la opción de «Imprimir factura». Y me da lo mismo si en online,offline(mayor complejidad)

    1. Debería de mostrar los link según tu puedas o no verlos.

    -Sí solo aquellos donde tu tengas permisos no todos.

    2. Si eres tan listo y te acuerdas del link (url amigables,algunas veces no se para que?)

    -La solución es tan sencilla como que tu app controle mediante un sistema de routes que tu no tienes permisos para ver esa pagina y automáticamente te redirija a una pagina que te diga.

    «Listillo yo creo que te estás pasando» xDDDDD.

    Así que @gulnor a eso es a lo que yo le llamo poca seriedad.

    Respecto a lo de hace unos años donde tenemos un app de escritorio, mira que llevo años en esto y no he visto a un pavo(usuario) accediendo a un formulario donde no tenía permisos, porque entre otras cosas y por mucho que descompilara no era capaz de volver a compilar su idea.

    Pero actualmente afortunadamente o desfortunadamente solo un listillo de turno tiene que pulsar F12 y cambiar un true por un false, con lo cual si descargas y el false lo convierto en true este escenario lo tienes todos los días.

    ObjectXXX.Hola = function(){return true;}

    Ahora eso está en requisitos o en calidad o en yo no quiero App así. Todo dependerá evidentemente de que es lo que me piden.

    Pero mi propuesta ante una cosa así no es otra que mucho le falta a las SPA´s para que esto sea factible.

    Madre que pesao… Yo me sigo quedando con la opción «Me parece poco serio» :P

    Algún día, que mi contrato «NDA» no me lo permite como dirían algunos xDDDD os contare como con tres lineas solucionamos esto, pero de momento no puedo, para mí seguridad es sinónimo de simplicidad y por tanto no descargar y no mostrar es más simple que mostrar y pensar en protegerme.

    Otra cosa bien distinta es que tu estés pensando en un móvil y no puedes descargar todo desde un servidor puesto que al final es igual que una app de escritorio aunque este hecha en JavaScript/Html y es hay donde lo tienes complicado, puesto que las vistas están en local.

    Ahora si la librería que utilizo no es capaz de controlar esto tarde o temprano alguien podrá usar una vulnerabilidad de tu código Html/JavaScript para hacer cosas que no se pueden.

    Ahora me pregunto si realmente entornos como PhoneGap son seguros. Imagino que bastante menos que hacer una app nativa y es por eso por lo que estoy en desacuerdo con este tipo de aplicaciones en según que tipo de escenarios.

    Para terminar.

    Basta con que tu app tenga millones de usuarios, para que sea sensible a ataques. Estoy cansado como bien ha dicho Marc de leer articulos de Chema Alonso donde descuartiza a whatsApp y otras muchas simplemente viendo un texto.

    Con lo cual oculta y evitarás problemas, simplemente eso.

  14. Juanma (@gulnor) dijo:

    Pedro, estoy de acuerdo en todo lo que comentas sobre usabilidad, pero sigo sin ver por qué no puedo proporcionar la misma experiencia de usuario descargando todo el HTML y usando Javascript para mostrar, ocultar o deshabilitar controles.

    Lo de mostrar páginas en blanco se refería a que, incluso en el caso de que un cracker modificase el código para darse acceso a una opción «oculta», el servidor no le enviaría datos por lo que no sería preocupante. Incluso la parte cliente podría detectar el error 403 y redirigir a donde considere oportuno.

    En cuanto a no dar información para evitar ataques, es verdad que ayuda, pero no me parece el factor más importante a la hora de securizar la aplicación y se me ocurren bastantes escenarios en los que el HTML que tanto miedo te da bajar al cliente es fácilmente accesible por otros medios.

    Yo no basaría la seguridad de mi aplicación en que el usuario pueda ver o no el HTML, igual que no la basaría en que el usuario pueda ver o no el código fuente.

    • Marc Rubiño dijo:

      La verdad es que sigo sin entender porque seguis insistiendo que decimos que queremos basar nuestra aplicacion en mostrar el Html, creo que he xplicado desde el principio que es una parte mas de la seguridad y por supuesto no basaria mi seguridad solo en eso.

  15. Jimmy Sáenz dijo:

    Hola Marc, muy interesante el debate y tu punto de vista, estoy trabajando en mi 1er SPA a nivel empresarial y por tanto pregunto, sería mucho pedir que nos compartás código de ejemplo con tu mecanismo? o algún post en donde lo detallen? Gracias de antemano.

  16. Juver dijo:

    Estoy completamente de acuerdo al final sin apoyo de los servicios del servidor, no hay posibilidad de brindar un mínimo de seguridad, para mi angular nació también bajo el lema, -Ya tenemos la solución!!- ahora nos falta el problema, casarse con Angular puede no tener posibilidad de un divorcio sin traumas.

Replica a Sergio León Cancelar la respuesta