OAuth 2.0, autorizando el uso de recursos

OAuth 2.0 tal y como se define en el RFC 6749 es aquél framework de autorización que permite a la aplicación de un tercero obtener un acceso limitado a un cierto servicio http, lo que comúnmente llamamos un API.

El framework de autorización OAuth, establece un conjunto de roles entre los que se deben orquestar los procesos necesarios para establecer si finalmente un cliente tiene los permisos para acceder a un recurso.

Los roles definidos son el cliente que consume un recurso, el servidor donde se halla este recurso, el dueño del recurso que va a ser consumido y aquel servidor que autoriza el consumo del recurso.

Roles en OAuth

En un ejemplo típico, el cliente podría ser aquella web a la que accedo y en lugar de crear una nueva cuenta me permite usar mi credencial de Facebook. El servidor del recurso puede ser Facebook también. El dueño del recurso sería yo mismo.  Y por último el servidor de autorización sería el de facebook.com.

¿Cómo se establece el diálogo entre los distintos actores para finalmente autorizar el uso a un cierto cliente?

La web a la que accedo va a pedir al dueño del recurso, en el ejemplo sería yo mismo, autorización para usar la información. En este paso normalmente se determina el nivel de autorización, si sólo voy a permitir el acceso en mi nombre, o si permito que se acceda a más información de mi perfil. Si concedo permiso a utilizar mis credenciales, al final la aplicación cliente va a presentar un token de acceso al servidor del recurso para que pueda acceder a esta novedosa web.

Es importante notar la presencia de un token de autorización, este token puede ser almacenado por la aplicación cliente y ahorrar al usuario final ( que podría ser una aplicación ) de tener que hacer login cada vez que quiera acceder ( usaría el login de facebook. Simplificamos el proceso de registro, a cambio de hacer concesiones a un tercero.

¿Cómo de privada debe ser mi API?

Tal y como entiendo, los datos públicos (opendata) deberían ser expuestos mediante APIs públicas de forma que estén disponibles para los ciudadanos, y todo aquel que quiera utilizarlos.

En el caso de las empresas, dependerá de su modelo de negocio, de hecho exponer servicios con acceso directo a los objetos de negocio debería hacerse en distintos niveles, que tal y como yo veo podrían ser los siguientes:

  • Por ejemplo, en un cierto nivel sólo aquella información que quiera suministrarse de forma pública, API pública, estaría disponible para la comunidad global de forma que pueda se expanda el conocimiento de productos y servicios de la compañía, u otras informaciones que la compañía deba exponer por algún marco regulatorio. Este API sería de acceso público con libre acceso y registro a la comunidad, o con acceso restringido a una gran comunidad de clientes.
  • Avanzando en cuanto a privacidad, habrá otros modelos del datos de la compañía que estarán restringidos para intercambio con otras empresas (lo que se denomina business to business – b2b ). De esta forma las aplicaciones de terceros que consuman las APIs privadas formarán parte de una o más comunidades privadas a las que deberíamos ofrecer distintas APIs dependiendo del escenario, por ejemplo intercambio de información de pagos y cobros, de pedidos y facturas, movimientos de almacén, etc. Estas son las comúnmente llamadas APIs privadas.
  • Y en el último nivel de restricción, habrá APIs de uso interno, lo cual no quiere decir que sean APIs que se puedan consumir internamente a discreción, si no que su existencia implica también un gobierno sobre su ciclo de vida, notificación a los consumidores de nuevas versiones o cambios, y control del consumo del API y de las condiciones del servicio por parte de los distintos consumidores. Estos consumidores internos pueden perfectamente ser otros sistemas, lo que tradicionalmente se llama integración application to application – A2A. Yo llamo a estas APIs internas, aunque muchas veces son APIs propietarias de los componentes software que la compañía utilice, SCM, CRP, CRM, HR, ERP.

Esta gestión de los diferentes niveles de privacidad de la información, y por tanto de las diferentes APIs que se utilizan, implica todo un proceso de gobierno del ciclo de vida de las distintas APIs, control de los cambios, y adicionalmente la gestión de la Autenticación de los consumidores, el control de la Autorización de uso del recurso que se pretende acceder y por su puesto la Auditoría de las operaciones realizadas lo cual no es muy diferente de los requisitos que pedimos a un API pública.

Protección de APIs frente a Inyección de código

Una de las amenazas a las que cualquier API está expuesta es el ataque de inyección (sql injection, ldap injection ), que no por conocido deja de estar entre la lista de ataques que mejores resultados producen a la hora de obtener acceso a grandes cantidades de información, o la mejor forma de destruir la información existente.

¿De qué forma podemos detectar que al recibir una llamada a un método esta incluye algún contenido malicioso que pueda ocasionar daños al servicio expuesto?

Debemos tener en cuenta que el contenido malicioso puede encontrarse en cualquier cabecera http, en la query string, o en el propio contenido de la petición.

El mecanismo habitual es contrastar con un conjunto de expresiones regulares que definan los diferentes casos de inyección: select * from users, delete from users, ‘ or 1=1–, ‘ or ”=’.

Una vez definido el conjunto de expresiones regulares el objetivo es aplicarlo a las distintas llamadas al API que queremos securizar.

Algún ejemplo de expresiones regulares, para los casos que comento, serían:

delete from users

       (?i)\s*delete\s*\w*\W*\s+from

‘ or ”=’

       (?i)’\s+or(‘|”|\s+)

¿Pero cuál es exactamente la base del problema?

Normalmente cuando accedemos a una base de datos tenemos diversas formas de hacerlo:

a)      Creamos una consulta, le añadimos el parámetro que hemos recibido en la llamada al método del API, y posteriormente la ejecutamos.

String query = “SELECT user FROM users WHERE user_name = ” + request.getParameter(“customerName”);

 

b)      Usamos un prepared statement, al que pasamos como parámetro aquél que hemos recibido en la llamada al método del API, y posteriormente lo ejecutamos.

String cName = request.getParameter(“customerName”);

String query = “SELECT user FROM users WHERE user_name = ? “;

PreparedStatement pstmt = connection.prepareStatement( query );

pstmt.setString( 1, cName);

 

c)       Utilizar procedimientos almacenados en la base de datos, a los que llamamos desde nuestra API.

        String cName = request.getParameter(“customerName”);  CallableStatement cs = connection.prepareCall(“{call sp_getAccountBalance(?)}”);        cs.setString(1, cName);

 

El defecto que plantea utilizar la primera opción, que es la más usada, es que permite que cualquiera que sea el contenido del parámetro “customerName” va a ser usado dentro de la query que se lanza ( select * from xxxx, drop table yyy, … ), y sólo los privilegios que dicho usuario tenga dentro del gestor de base de datos le frenarán de parar el motor de base de datos, o cualquier otra fechoría.

Aunque siempre debe verificarse que los parámetros no contengan ninguna inyección de código, las opciones b y c son más seguras y sólo depende del diseño de la solución el usar una u otra.

¿Cuál es la recomendación? Utilizar una solución de seguridad perimetral que impida que dichas llamadas a los métodos del API crucen siquiera la DMZ, y a su vez detectada la amenaza notificar al SIEM para que sirva de punto de detección de otros patrones.