Está en la página 1de 12

Introducción

La definición e implementación de autorizaciones es una de las medidas de


protección importantes de una aplicación. Se definen en la fase de creación
del proyecto e, incluso si se encuentran problemas de autorización cuando la
aplicación se lanza inicialmente y se somete a una auditoría de seguridad
antes de comenzar a funcionar, el número más significativo de problemas
relacionados con la autorización se produjo en la vida útil de mantenimiento
de la aplicación.

Esta situación a menudo se explica por el hecho de que las características se


agregan / modifican y no se realizó ninguna revisión de las autorizaciones en
la aplicación antes de la publicación de la nueva versión, por razones de costo
o tiempo.

Contexto
Para tratar de abordar esta situación, puede ser interesante automatizar la
evaluación de la definición e implementación de autorizaciones en la
aplicación. Esto, para garantizar constantemente que la implementación de las
autorizaciones en la aplicación sea coherente con la definición de
autorizaciones.

Una autorización a menudo se compone de 2 elementos (también


denominadas dimensiones): la función y el rol lógico que pueden acceder a
ella (en algún momento se agrega una tercera dimensión
denominada datos para definir un acceso que incluya un filtrado a nivel de
datos comerciales).

La representación de las diferentes combinaciones de estas 2 dimensiones a


menudo se denomina matriz de autorización y a menudo se formaliza en un
archivo de Microsoft Excel.

Durante una prueba de una autorización, un rol lógico también se


denomina punto de vista .

Objetivo
Este artículo describe una propuesta de implementación para automatizar las
pruebas de una matriz de autorización .

Este artículo utiliza el supuesto de que 2 dimensiones se utilizan para


representar una autorización para la propuesta técnica descrita y tomar como
ejemplo una aplicación que expone los servicios REST.

El objetivo es proporcionar ideas / sugerencias iniciales para crear una forma


personalizada de probar la matriz de autorización para la aplicación de destino.
Proposición
Para lograr la automatización completa de la evaluación de la matriz de
autorización , se han realizado las siguientes acciones:

1. Formalice la matriz de autorización en un archivo de formato dinámico que


permita:

i. El procesamiento por un programa de una manera fácil.


ii. Para ser leída y actualizada por un humano para el seguimiento de las
combinaciones de autorización.
iii. Jerarquía en la información para materializar fácilmente las diferentes
combinaciones.
iv. El máximo posible de independencia de la tecnología y el diseño
utilizados para implementar la aplicación exponiendo las características.
2. Cree un conjunto de pruebas de integración que utilicen completamente el
archivo pivote de matriz de autorización como fuente de entrada para evaluar
las diferentes combinaciones con:
i. El mínimo posible de mantenimiento cuando se actualiza el archivo pivote
de matriz de autorización.
ii. Una indicación clara, en caso de prueba fallida, de la combinación de
autorización de origen que no respeta la matriz de autorización.

Archivo pivote de matriz de autorización


El formato XML se ha utilizado para formalizar la matriz de autorización.

La estructura XML contiene 3 secciones principales:

 Roles de nodo : este nodo describe los posibles roles lógicos utilizados en
el sistema, se utiliza para proporcionar una lista y la explicación de los
diferentes roles (nivel de autorización).
 Servicios de nodo : este nodo enumera y describe los servicios
disponibles expuestos por el sistema y los roles lógicos asociados que
pueden llamarlos.
 Prueba de servicios de nodo : este nodo proporciona una carga útil de
prueba para cada servicio si el servicio utiliza datos de entrada distintos de
los que provienen de url o ruta.

Este es un ejemplo del XML utilizado para representar la autorización:


Los marcadores de posición (valores entre {}) se utilizan para marcar la
ubicación donde las pruebas de integración deben colocar el valor de prueba
si es necesario
<?xml version="1.0" encoding="UTF-8"?>
<!--
This file materialize the authorization matrix for the different
services exposed by the system.
It will be used by the tests as a input sources for the different tests
cases:
1) Evaluate legitimate access and is correct implementation
2) Identify not legitimate access (authorization definition issue
on service implementation)

The "name" attribute is used for identify uniquely a SERVICE or a ROLE.


-->
<authorization-matrix>

<!-- Describe the possible logical roles used in the system, is used
here to
provide a list+explanation
of the different roles (authorization level) -->
<roles>
<role name="ANONYMOUS"
description="Indicate that no authorization is needed"/>
<role name="BASIC"
description="Role affected to a standard user (lowest access right
just above anonymous)"/>
<role name="ADMIN"
description="Role affected to a administrator user (highest access
right)"/>
</roles>

<!-- List and describe the available services exposed by the system
and the associated
logical role(s) that can call them -->
<services>
<service name="ReadSingleMessage" uri="/{messageId}" http-
method="GET"
http-response-code-for-access-allowed="200" http-response-code-
for-access-denied="403">
<role name="ANONYMOUS"/>
<role name="BASIC"/>
<role name="ADMIN"/>
</service>
<service name="ReadAllMessages" uri="/" http-method="GET"
http-response-code-for-access-allowed="200" http-response-code-
for-access-denied="403">
<role name="ANONYMOUS"/>
<role name="BASIC"/>
<role name="ADMIN"/>
</service>
<service name="CreateMessage" uri="/" http-method="PUT"
http-response-code-for-access-allowed="200" http-response-code-
for-access-denied="403">
<role name="BASIC"/>
<role name="ADMIN"/>
</service>
<service name="DeleteMessage" uri="/{messageId}" http-
method="DELETE"
http-response-code-for-access-allowed="200" http-response-code-
for-access-denied="403">
<role name="ADMIN"/>
</service>
</services>

<!-- Provide a test payload for each service if needed -->


<services-testing>
<service name="ReadSingleMessage">
<payload/>
</service>
<service name="ReadAllMessages">
<payload/>
</service>
<service name="CreateMessage">
<payload content-type="application/json">
{"content":"test"}
</payload>
</service>
<service name="DeleteMessage">
<payload/>
</service>
</services-testing>

</authorization-matrix>

Pruebas de integración
Las pruebas de integración se implementan utilizando un máximo de código
factorizado y se ha creado un caso de prueba por Punto de vista (POV) para
agrupar las verificaciones por perfil de nivel de acceso (rol lógico) y facilitar la
representación / identificación de los errores.

El análisis, el mapeo de objetos y el acceso a la información de la matriz de


autorización se han implementado utilizando las funciones integradas de
clasificación / descomposición XML proporcionadas por la tecnología utilizada
para implementar las pruebas (aquí JAXB) con el fin de limitar el código al
encargado de realizar el pruebas

Esta es la implementación de la clase de caso de pruebas de integración:


import org.owasp.pocauthztesting.enumeration.SecurityRole;
import org.owasp.pocauthztesting.service.AuthService;
import org.owasp.pocauthztesting.vo.AuthorizationMatrix;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.xml.sax.InputSource;
import javax.xml.bind.JAXBContext;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
* Integration Test cases in charge of validate the correct implementation
of the authorization matrix.
* Create on test case by logical role that will test access on all services
exposed by the system.
* Implements here focus on readability
*/
public class AuthorizationMatrixIT {

/**
* Object representation of the authorization matrix
*/
private static AuthorizationMatrix AUTHZ_MATRIX;

private static final String BASE_URL = "http://localhost:8080";

/**
* Load the authorization matrix in objects tree
*
* @throws Exception If any error occurs
*/
@BeforeClass
public static void globalInit() throws Exception {
try (FileInputStream fis = new FileInputStream(new
File("authorization-matrix.xml"))) {
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://xml.org/sax/features/external-general-
entities", false);
spf.setFeature("http://xml.org/sax/features/external-
parameter-entities", false);

spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-
dtd", false);
Source xmlSource = new
SAXSource(spf.newSAXParser().getXMLReader(), new InputSource(fis));
JAXBContext jc =
JAXBContext.newInstance(AuthorizationMatrix.class);
AUTHZ_MATRIX = (AuthorizationMatrix)
jc.createUnmarshaller().unmarshal(xmlSource);
}
}

/**
* Test access to the services from a anonymous user.
*
* @throws Exception
*/
@Test
public void testAccessUsingAnonymousUserPointOfView() throws Exception
{
//Run the tests - No access token here
List<String> errors =
executeTestWithPointOfView(SecurityRole.ANONYMOUS, null);
//Verify the test results
Assert.assertEquals("Access issues detected using the ANONYMOUS
USER point of view:\n" + formatErrorsList(errors), 0, errors.size());
}

/**
* Test access to the services from a basic user.
*
* @throws Exception
*/
@Test
public void testAccessUsingBasicUserPointOfView() throws Exception {
//Get access token representing the authorization for the
associated point of view
String accessToken = generateTestCaseAccessToken("basic",
SecurityRole.BASIC);
//Run the tests
List<String> errors =
executeTestWithPointOfView(SecurityRole.BASIC, accessToken);
//Verify the test results
Assert.assertEquals("Access issues detected using the BASIC USER
point of view:\n " + formatErrorsList(errors), 0, errors.size());
}

/**
* Test access to the services from a administrator user.
*
* @throws Exception
*/
@Test
public void testAccessUsingAdministratorUserPointOfView() throws
Exception {
//Get access token representing the authorization for the
associated point of view
String accessToken = generateTestCaseAccessToken("admin",
SecurityRole.ADMIN);
//Run the tests
List<String> errors =
executeTestWithPointOfView(SecurityRole.ADMIN, accessToken);
//Verify the test results
Assert.assertEquals("Access issues detected using the ADMIN USER
point of view:\n" + formatErrorsList(errors), 0, errors.size());
}

/**
* Evaluate the access to all service using the point of view (POV)
specified.
*
* @param pointOfView Point of view to use
* @param accessToken Access token that is linked to the point of view
in terms of authorization.
* @return List of errors detected
* @throws Exception If any error occurs
*/
private List<String> executeTestWithPointOfView(SecurityRole
pointOfView, String accessToken) throws Exception {
List<String> errors = new ArrayList<>();
String errorMessageTplForUnexpectedReturnCode = "The service '%s'
when called with POV '%s' return a response code %s that is not the expected
one in allowed or denied case.";
String errorMessageTplForIncorrectReturnCode = "The service '%s'
when called with POV '%s' return a response code %s that is not the expected
one (%s expected).";
String fatalErrorMessageTpl = "The service '%s' when called with
POV %s meet the error: %s";

//Get the list of services to call


List<AuthorizationMatrix.Services.Service> services =
AUTHZ_MATRIX.getServices().getService();

//Get the list of services test payload to use


List<AuthorizationMatrix.ServicesTesting.Service>
servicesTestPayload = AUTHZ_MATRIX.getServicesTesting().getService();

//Call all services sequentially (no special focus on performance


here)
services.forEach(service -> {
//Get the service test payload for the current service
String payload = null;
String payloadContentType = null;
Optional<AuthorizationMatrix.ServicesTesting.Service>
serviceTesting = servicesTestPayload.stream().filter(srvPld ->
srvPld.getName().equals(service.getName())).findFirst();
if (serviceTesting.isPresent()) {
payload = serviceTesting.get().getPayload().getValue();
payloadContentType =
serviceTesting.get().getPayload().getContentType();
}
//Call the service and verify if the response is consistent
try {
//Call the service
int serviceResponseCode = callService(service.getUri(),
payload, payloadContentType, service.getHttpMethod(), accessToken);
//Check if the role represented by the specified point of
view is defined for the current service
Optional<AuthorizationMatrix.Services.Service.Role> role
= service.getRole().stream().filter(r ->
r.getName().equals(pointOfView.name())).findFirst();
boolean accessIsGrantedInAuthorizationMatrix =
role.isPresent();
//Verify behavior consistency according to the response
code returned and the authorization configured in the matrix
if (serviceResponseCode ==
service.getHttpResponseCodeForAccessAllowed()) {
//Roles is not in the list of role allowed to access
to the service so it's an error
if (!accessIsGrantedInAuthorizationMatrix) {

errors.add(String.format(errorMessageTplForIncorrectReturnCode,
service.getName(), pointOfView.name(), serviceResponseCode,
service.getHttpResponseCodeForAccessDenied()));
}
} else if (serviceResponseCode ==
service.getHttpResponseCodeForAccessDenied()) {
//Roles is in the list of role allowed to access to
the service so it's an error
if (accessIsGrantedInAuthorizationMatrix) {
errors.add(String.format(errorMessageTplForIncorrectReturnCode,
service.getName(), pointOfView.name(), serviceResponseCode,
service.getHttpResponseCodeForAccessAllowed()));
}
} else {

errors.add(String.format(errorMessageTplForUnexpectedReturnCode,
service.getName(), pointOfView.name(), serviceResponseCode));
}
} catch (Exception e) {
errors.add(String.format(fatalErrorMessageTpl,
service.getName(), pointOfView.name(), e.getMessage()));
}

});

return errors;
}

/**
* Call a service with a specific payload and return the HTTP response
code received.
* Delegate this step in order to made the test cases more easy to
maintain.
*
* @param uri URI of the target service
* @param payloadContentType Content type of the payload to send
* @param payload Payload to send
* @param httpMethod HTTP method to use
* @param accessToken Access token to specify to represent the
identity of the caller
* @return The HTTP response code received
* @throws Exception If any error occurs
*/
private int callService(String uri, String payload, String
payloadContentType, String httpMethod, String accessToken) throws Exception
{
int rc;

//Build the request - Use Apache HTTP Client in order to be more


flexible in the combination
HttpRequestBase request;
String url = (BASE_URL + uri).replaceAll("\\{messageId\\}", "1");
switch (httpMethod) {
case "GET":
request = new HttpGet(url);
break;
case "DELETE":
request = new HttpDelete(url);
break;
case "PUT":
request = new HttpPut(url);
if (payload != null) {
request.setHeader("Content-Type",
payloadContentType);
((HttpPut) request).setEntity(new
StringEntity(payload.trim()));
}
break;
default:
throw new UnsupportedOperationException(httpMethod + " not
supported !");
}
request.setHeader("Authorization", (accessToken != null) ?
accessToken : "");

//Send the request and get the HTTP response code


try (CloseableHttpClient httpClient = HttpClients.createDefault())
{
try (CloseableHttpResponse httpResponse =
httpClient.execute(request)) {
//Don't care here about the response content...
rc = httpResponse.getStatusLine().getStatusCode();
}
}

return rc;
}

/**
* Generate a JWT token the user and role specified.
*
* @param login User login
* @param role Authorization logical role
* @return The JWT token
* @throws Exception If any error occurs during the creation
*/
private String generateTestCaseAccessToken(String login, SecurityRole
role) throws Exception {
return new AuthService().issueAccessToken(login, role);
}

/**
* Format a list of errors to a printable string
*
* @param errors Error list
* @return Printable string
*/
private String formatErrorsList(List<String> errors) {
StringBuilder buffer = new StringBuilder();
errors.forEach(e -> buffer.append(e).append("\n"));
return buffer.toString();
}
}
En caso de detección de un problema de autorización, el resultado es el
siguiente:
testAccessUsingAnonymousUserPointOfView(org.owasp.pocauthztesting.Authoriza
tionMatrixIT)
Time elapsed: 1.009 s ### FAILURE
java.lang.AssertionError:
Access issues detected using the ANONYMOUS USER point of view:
The service 'DeleteMessage' when called with POV 'ANONYMOUS' return
a response code 200 that is not the expected one (403 expected).
The service 'CreateMessage' when called with POV 'ANONYMOUS' return
a response code 200 that is not the expected one (403 expected).

testAccessUsingBasicUserPointOfView(org.owasp.pocauthztesting.Authorization
MatrixIT)
Time elapsed: 0.05 s ### FAILURE!
java.lang.AssertionError:
Access issues detected using the BASIC USER point of view:
The service 'DeleteMessage' when called with POV 'BASIC' return
a response code 200 that is not the expected one (403 expected).

Representación de la matriz de
autorización para auditoría / revisión.
Incluso si la matriz de autorización se almacena en un formato legible por
humanos (XML), puede ser interesante proporcionar una representación de
representación sobre la marcha del archivo XML para facilitar la revisión,
auditoría y discusión sobre la matriz de autorización para para detectar
posibles inconsistencias.

Se puede utilizar la siguiente hoja de estilo XSL:


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<html>
<head>
<title>Authorization Matrix</title>
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-
alpha.6/css/bootstrap.min.css"
integrity="sha384-
rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ"
crossorigin="anonymous" />
</head>
<body>
<h3>Roles</h3>
<ul>
<xsl:for-each select="authorization-matrix/roles/role">
<xsl:choose>
<xsl:when test="@name = 'ADMIN'">
<div class="alert alert-warning" role="alert">
<strong>
<xsl:value-of select="@name" />
</strong>
:
<xsl:value-of select="@description" />
</div>
</xsl:when>
<xsl:when test="@name = 'BASIC'">
<div class="alert alert-info" role="alert">
<strong>
<xsl:value-of select="@name" />
</strong>
:
<xsl:value-of select="@description" />
</div>
</xsl:when>
<xsl:otherwise>
<div class="alert alert-danger" role="alert">
<strong>
<xsl:value-of select="@name" />
</strong>
:
<xsl:value-of select="@description" />
</div>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</ul>
<h3>Authorizations</h3>
<table class="table table-hover table-sm">
<thead class="thead-inverse">
<tr>
<th>Service</th>
<th>URI</th>
<th>Method</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<xsl:for-each select="authorization-matrix/services/service">
<xsl:variable name="service-name" select="@name" />
<xsl:variable name="service-uri" select="@uri" />
<xsl:variable name="service-method" select="@http-method" />
<xsl:for-each select="role">
<tr>
<td scope="row">
<xsl:value-of select="$service-name" />
</td>
<td>
<xsl:value-of select="$service-uri" />
</td>
<td>
<xsl:value-of select="$service-method" />
</td>
<td>
<xsl:variable name="service-role-name" select="@name" />
<xsl:choose>
<xsl:when test="@name = 'ADMIN'">
<div class="alert alert-warning" role="alert">
<xsl:value-of select="@name" />
</div>
</xsl:when>
<xsl:when test="@name = 'BASIC'">
<div class="alert alert-info" role="alert">
<xsl:value-of select="@name" />
</div>
</xsl:when>
<xsl:otherwise>
<div class="alert alert-danger" role="alert">
<xsl:value-of select="@name" />
</div>
</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</xsl:for-each>
</xsl:for-each>
</tbody>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Ejemplo de renderizado:

Fuentes del prototipo


Repositorio de Github

También podría gustarte