Saltar al contenido

¿Cómo interceptar una RequestRejectedException en Spring?

Nuestro grupo de especialistas pasados muchos días de investigación y de juntar de información, encontramos la solución, esperamos que todo este artículo sea de utilidad para tu trabajo.

Solución:

También se puede manejar con un filtro simple, que dará lugar a una respuesta de error 404

@Component
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class LogAndSuppressRequestRejectedExceptionFilter extends GenericFilterBean 

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException 
        try 
            chain.doFilter(req, res);
         catch (RequestRejectedException e) 
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;

            log
                .warn(
                        "request_rejected: remote=, user_agent=, request_url=",
                        request.getRemoteHost(),  
                        request.getHeader(HttpHeaders.USER_AGENT),
                        request.getRequestURL(), 
                        e
                );

            response.sendError(HttpServletResponse.SC_NOT_FOUND);
        
    

Implementé una subclase de StrictHttpFirewall que registra la solicitud de información a la consola y lanza una nueva excepción con un seguimiento de pila suprimido. Esto resuelve parcialmente mi problema (al menos ahora puedo ver las solicitudes incorrectas).

Si solo desea ver las solicitudes rechazadas sin el seguimiento de la pila, esta es la respuesta que está buscando.

Si desea manejar estas excepciones en un controlador, consulte la respuesta aceptada para obtener una solución completa (pero un poco más compleja).


RegistroHttpFirewall.java

Esta clase extiende StrictHttpFirewall para atrapar RequestRejectedException y lanza una nueva excepción con metadatos de la solicitud y un seguimiento de pila suprimido.

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.web.firewall.FirewalledRequest;
import org.springframework.security.web.firewall.RequestRejectedException;
import org.springframework.security.web.firewall.StrictHttpFirewall;

/**
 * Overrides the StrictHttpFirewall to log some useful information about blocked requests.
 */
public final class LoggingHttpFirewall extends StrictHttpFirewall

    /**
     * Logger.
     */
    private static final Logger LOGGER = Logger.getLogger(LoggingHttpFirewall.class.getName());

    /**
     * Default constructor.
     */
    public LoggingHttpFirewall()
    
        super();
        return;
    

    /**
     * Provides the request object which will be passed through the filter chain.
     *
     * @returns A FirewalledRequest (required by the HttpFirewall interface) which
     *          inconveniently breaks the general contract of ServletFilter because
     *          we can't upcast this to an HttpServletRequest. This prevents us
     *          from re-wrapping this using an HttpServletRequestWrapper.
     * @throws RequestRejectedException if the request should be rejected immediately.
     */
    @Override
    public FirewalledRequest getFirewalledRequest(final HttpServletRequest request) throws RequestRejectedException
    
        try
        
            return super.getFirewalledRequest(request);
         catch (RequestRejectedException ex) 
            if (LOGGER.isLoggable(Level.WARNING))
            
                LOGGER.log(Level.WARNING, "Intercepted RequestBlockedException: Remote Host: " + request.getRemoteHost() + " User Agent: " + request.getHeader("User-Agent") + " Request URL: " + request.getRequestURL().toString());
            

            // Wrap in a new RequestRejectedException with request metadata and a shallower stack trace.
            throw new RequestRejectedException(ex.getMessage() + ".n Remote Host: " + request.getRemoteHost() + "n User Agent: " + request.getHeader("User-Agent") + "n Request URL: " + request.getRequestURL().toString())
            
                private static final long serialVersionUID = 1L;

                @Override
                public synchronized Throwable fillInStackTrace()
                
                    return this; // suppress the stack trace.
                
            ;
        
    

    /**
     * Provides the response which will be passed through the filter chain.
     * This method isn't extensible because the request may already be committed.
     * Furthermore, this is only invoked for requests that were not blocked, so we can't
     * control the status or response for blocked requests here.
     *
     * @param response The original HttpServletResponse.
     * @return the original response or a replacement/wrapper.
     */
    @Override
    public HttpServletResponse getFirewalledResponse(final HttpServletResponse response)
    
        // Note: The FirewalledResponse class is not accessible outside the package.
        return super.getFirewalledResponse(response);
    


WebSecurityConfig.java

En WebSecurityConfig, configure el cortafuegos HTTP en el LoggingHttpFirewall.

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter

    /**
     * Default constructor.
     */
    public WebSecurityConfig()
    
        super();
        return;
    

    @Override
    public final void configure(final WebSecurity web) throws Exception
    
        super.configure(web);
        web.httpFirewall(new LoggingHttpFirewall()); // Set the custom firewall.
        return;
    


Resultados

Después de implementar esta solución en producción, descubrí rápidamente que el comportamiento predeterminado de StrictHttpFirewall estaba bloqueando a Google para que no indexara mi sitio.

Aug 13, 2018 1:48:56 PM com.mycompany.spring.security.AnnotatingHttpFirewall getFirewalledRequest
WARNING: Intercepted RequestBlockedException: Remote Host: 66.249.64.223 User Agent: Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) Request URL: https://www.mycompany.com/10.1601/tx.3784;jsessionid=692804549F9AB55F45DBD0AFE2A97FFD

Tan pronto como descubrí esto, implementé rápidamente una nueva versión (incluida en mi otra respuesta) que busca ;jsessionid= y permite que estas solicitudes pasen. Es posible que haya otras solicitudes que también deberían pasar, y ahora tengo una forma de detectarlas.

Resulta que aunque HttpFirewall y StrictHttpFirewall contienen varios errores de diseño (documentados en el código a continuación), es apenas posible escapar de Spring Security Un verdadero cortafuegos y tunelizar el HttpFirewall información a través de un atributo de solicitud a un HandlerInterceptor que puede pasar estas solicitudes marcadas a un verdadero firewall (persistente) sin sacrificar la lógica comercial original que los marcó en primer lugar. El método documentado aquí debe estar bastante preparado para el futuro, ya que se ajusta a un simple contrato del HttpFirewall interfaz, y el resto es simplemente Spring Framework y Java Servlet API.

Esta es esencialmente una alternativa más complicada pero más completa a mi respuesta anterior. En esta respuesta, implementé una nueva subclase de StrictHttpFirewall que intercepta y registra las solicitudes rechazadas en un nivel de registro específico, pero también agrega un atributo a la solicitud HTTP que lo marca para que lo manejen los filtros (o controladores) descendentes. También esto AnnotatingHttpFirewall proporciona un inspect() método que permite a las subclases agregar reglas personalizadas para bloquear solicitudes.

Esta solución se divide en dos partes: (1) Seguridad de primavera y 2) Spring Framework (núcleo), porque esa es la división que causó este problema en primer lugar, y esto muestra cómo salvarlo.

Como referencia, esto se prueba en Spring 4.3.17 y Spring Security 4.2.6. Puede haber cambios significativos cuando se lance Spring 5.1.


Parte 1: Seguridad de primavera

Esta es la mitad de la solución que realiza el registro y marcado dentro de Spring Security.


AnotandoHttpFirewall.java

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.web.firewall.FirewalledRequest;
import org.springframework.security.web.firewall.RequestRejectedException;
import org.springframework.security.web.firewall.StrictHttpFirewall;

/**
 * Overrides the StrictHttpFirewall to log some useful information about blocked requests.
 */
public class AnnotatingHttpFirewall extends StrictHttpFirewall

    /**
     * The name of the HTTP header representing a request that has been rejected by this firewall.
     */
    public static final String HTTP_HEADER_REQUEST_REJECTED_FLAG = "X-HttpFirewall-RequestRejectedFlag";

    /**
     * The name of the HTTP header representing the reason a request has been rejected by this firewall.
     */
    public static final String HTTP_HEADER_REQUEST_REJECTED_REASON = "X-HttpFirewall-RequestRejectedReason";

    /**
     * Logger.
     */
    private static final Logger LOGGER = Logger.getLogger(AnnotatingHttpFirewall.class.getName());

    /**
     * Default constructor.
     */
    public AnnotatingHttpFirewall()
    
        super();
        return;
    

    /**
     * Provides the request object which will be passed through the filter chain.
     *
     * @param request The original HttpServletRequest.
     * @returns A FirewalledRequest (required by the HttpFirewall interface) which
     *          inconveniently breaks the general contract of ServletFilter because
     *          we can't upcast this to an HttpServletRequest. This prevents us
     *          from re-wrapping this using an HttpServletRequestWrapper.
     */
    @Override
    public FirewalledRequest getFirewalledRequest(final HttpServletRequest request)
    
        try
        
            this.inspect(request); // Perform any additional checks that the naive "StrictHttpFirewall" misses.
            return super.getFirewalledRequest(request);
         catch (RequestRejectedException ex) 
            final String requestUrl = request.getRequestURL().toString();

            // Override some of the default behavior because some requests are
            // legitimate.
            if (requestUrl.contains(";jsessionid="))
            
                // Do not block non-cookie serialized sessions. Google's crawler does this often.
             else 
                // Log anything that is blocked so we can find these in the catalina.out log.
                // This will give us any information we need to make
                // adjustments to these special cases and see potentially
                // malicious activity.
                if (LOGGER.isLoggable(Level.WARNING))
                
                    LOGGER.log(Level.WARNING, "Intercepted RequestBlockedException: Remote Host: " + request.getRemoteHost() + " User Agent: " + request.getHeader("User-Agent") + " Request URL: " + request.getRequestURL().toString());
                

                // Mark this request as rejected.
                request.setAttribute(HTTP_HEADER_REQUEST_REJECTED, Boolean.TRUE);
                request.setAttribute(HTTP_HEADER_REQUEST_REJECTED_REASON, ex.getMessage());
            

            // Suppress the RequestBlockedException and pass the request through
            // with the additional attribute.
            return new FirewalledRequest(request)
            
                @Override
                public void reset()
                
                    return;
                
            ;
        
    

    /**
     * Provides the response which will be passed through the filter chain.
     * This method isn't extensible because the request may already be committed.
     * Furthermore, this is only invoked for requests that were not blocked, so we can't
     * control the status or response for blocked requests here.
     *
     * @param response The original HttpServletResponse.
     * @return the original response or a replacement/wrapper.
     */
    @Override
    public HttpServletResponse getFirewalledResponse(final HttpServletResponse response)
    
        // Note: The FirewalledResponse class is not accessible outside the package.
        return super.getFirewalledResponse(response);
    

    /**
     * Perform any custom checks on the request.
     * This method may be overridden by a subclass in order to supplement or replace these tests.
     *
     * @param request The original HttpServletRequest.
     * @throws RequestRejectedException if the request should be rejected immediately.
     */
    public void inspect(final HttpServletRequest request) throws RequestRejectedException
    
        final String requestUri = request.getRequestURI(); // path without parameters
//        final String requestUrl = request.getRequestURL().toString(); // full path with parameters

        if (requestUri.endsWith("/wp-login.php"))
        
            throw new RequestRejectedException("The request was rejected because it is a vulnerability scan.");
        

        if (requestUri.endsWith(".php"))
        
            throw new RequestRejectedException("The request was rejected because it is a likely vulnerability scan.");
        

        return; // The request passed all custom tests.
    


WebSecurityConfig.java

En WebSecurityConfig, configure el cortafuegos HTTP en el AnnotatingHttpFirewall.

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter

    /**
     * Default constructor.
     */
    public WebSecurityConfig()
    
        super();
        return;
    

    @Override
    public final void configure(final WebSecurity web) throws Exception
    
        super.configure(web);
        web.httpFirewall(new AnnotatingHttpFirewall()); // Set the custom firewall.
        return;
    


Parte 2: Spring Framework

La segunda parte de esta solución posiblemente podría implementarse como un ServletFilter o HandlerInterceptor. Voy por el camino de un HandlerInterceptor porque parece ofrecer la mayor flexibilidad y funciona directamente dentro de Spring Framework.


RequestBlockedException.java

Esta excepción personalizada puede ser manejada por un controlador de errores. Esto puede ampliarse para agregar encabezados de solicitud, parámetros o propiedades disponibles de la solicitud sin procesar (incluso la solicitud completa en sí misma) que puedan ser pertinentes para la lógica comercial de la aplicación (por ejemplo, un firewall persistente).

/**
 * A custom exception for situations where a request is blocked or rejected.
 */
public class RequestBlockedException extends RuntimeException

    private static final long serialVersionUID = 1L;

    /**
     * The requested URL.
     */
    private String requestUrl;

    /**
     * The remote address of the client making the request.
     */
    private String remoteAddress;

    /**
     * A message or reason for blocking the request.
     */
    private String reason;

    /**
     * The user agent supplied by the client the request.
     */
    private String userAgent;

    /**
     * Creates a new Request Blocked Exception.
     *
     * @param reqUrl The requested URL.
     * @param remoteAddr The remote address of the client making the request.
     * @param userAgent The user agent supplied by the client making the request.
     * @param message A message or reason for blocking the request.
     */
    public RequestBlockedException(final String reqUrl, final String remoteAddr, final String userAgent, final String message)
    
        this.requestUrl = reqUrl;
        this.remoteAddress = remoteAddr;
        this.userAgent = userAgent;
        this.reason = message;
        return;
    

    /**
     * Gets the requested URL.
     *
     * @return A URL.
     */
    public String getRequestUrl()
    
        return this.requestUrl;
    

    /**
     * Gets the remote address of the client making the request.
     *
     * @return A remote address.
     */
    public String getRemoteAddress()
    
        return this.remoteAddress;
    

    /**
     * Gets the user agent supplied by the client making the request.
     *
     * @return  A user agent string.
     */
    public String getUserAgent()
    
        return this.userAgent;
    

    /**
     * Gets the reason for blocking the request.
     *
     * @return  A message or reason for blocking the request.
     */
    public String getReason()
    
        return this.reason;
    


FirewallInterceptor.java

Este interceptor se invoca después de que se hayan ejecutado los filtros de Spring Security (es decir, después AnnotatingHttpFirewall ha marcado solicitudes que deben rechazarse. Este interceptor detecta esos indicadores (atributos) en la solicitud y genera una excepción personalizada que nuestro controlador de errores puede manejar.

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

/**
 * Intercepts requests that were flagged as rejected by the firewall.
 */
public final class FirewallInterceptor implements HandlerInterceptor

    /**
     * Default constructor.
     */
    public FirewallInterceptor()
    
        return;
    

    @Override
    public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception
    
        if (Boolean.TRUE.equals(request.getAttribute(AnnotatingHttpFirewall.HTTP_HEADER_REQUEST_REJECTED)))
        
            // Throw a custom exception that can be handled by a custom error controller.
            final String reason = (String) request.getAttribute(AnnotatingHttpFirewall.HTTP_HEADER_REQUEST_REJECTED_REASON);
            throw new RequestRejectedByFirewallException(request.getRequestURL().toString(), request.getRemoteAddr(), request.getHeader(HttpHeaders.USER_AGENT), reason);
        

        return true; // Allow the request to proceed normally.
    

    @Override
    public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final ModelAndView modelAndView) throws Exception
    
        return;
    

    @Override
    public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final Exception ex) throws Exception
    
        return;
    


WebConfig.java

En WebConfig, añade el FirewallInterceptor al registro.

@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter

    /**
     * Among your other methods in this class, make sure you register
     * your Interceptor.
     */
    @Override
    public void addInterceptors(final InterceptorRegistry registry)
    
        // Register firewall interceptor for all URLs in webapp.
        registry.addInterceptor(new FirewallInterceptor()).addPathPatterns("/**");
        return;
    


ErrorController.java

Esto maneja específicamente la excepción personalizada anterior y produce una página de error limpia para el cliente mientras registra toda la información relevante e invoca cualquier lógica comercial especial para un firewall de aplicación personalizado.

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.springframework.web.servlet.NoHandlerFoundException;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

import RequestBlockedException;

@ControllerAdvice
public final class ErrorController

    /**
     * Logger.
     */
    private static final Logger LOGGER = Logger.getLogger(ErrorController.class.getName());

    /**
     * Generates an Error page by intercepting exceptions generated from AnnotatingHttpFirewall.
     *
     * @param request The original HTTP request.
     * @param ex A RequestBlockedException exception.
     * @return The tile definition name for the page.
     */
    @ExceptionHandler(RequestBlockedException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String handleRequestBlockedException(final RequestBlockedException ex)
    
        if (LOGGER.isLoggable(Level.WARNING))
        
            LOGGER.log(Level.WARNING, "Rejected request from " + ex.getRemoteAddress() + " for [" + ex.getRequestUrl() + "]. Reason: " + ex.getReason());
        

        // Note: Perform any additional business logic or logging here.

        return "errorPage"; // Returns a nice error page with the specified status code.
    

    /**
     * Generates a Page Not Found page.
     *
     * @param ex A NoHandlerFound exception.
     * @return The tile definition name for the page.
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public String handleException(final NoHandlerFoundException ex)
    
        return "notFoundPage";
    


FirewallController.java

Un controlador con un mapeo predeterminado que arroja un NoHandlerFoundException. Esto elude la estrategia del huevo y la gallina en DispatcherServlet.noHandlerFound, permitiendo que ese método siempre encontrar un mapeo para que FirewallInterceptor.preHandle siempre se invoca. Esto da RequestRejectedByFirewallException prioridad sobre NoHandlerFoundException.

Por qué esto es necesario:

Como se menciona aquí, cuando un NoHandlerFoundException es arrojado desde DispatcherServlet (es decir, cuando una URL solicitada no tiene la asignación correspondiente), no hay forma de manejar las excepciones generadas desde el firewall anterior (NoHandlerFoundException se lanza antes de invocar preHandle ()), por lo que esas solicitudes pasarán a su vista 404 (que no es el comportamiento deseado en mi caso; verá una gran cantidad de “No se encontró mapeo para la solicitud HTTP con URI …” mensajes). Esto podría solucionarse moviendo el cheque del encabezado especial al noHandlerFound método. Desafortunadamente, no hay forma de hacer esto sin escribir un nuevo Dispatcher Servlet desde cero, y luego puede desechar todo el Spring Framework. Es imposible extender DispatcherServlet debido a la combinación de métodos protegidos, privados y finales, y al hecho de que sus propiedades son inaccesibles (ni getters ni setters). También es imposible envolver la clase porque no existe una interfaz común que se pueda implementar. El mapeo predeterminado en esta clase proporciona una forma elegante de eludir toda esa lógica.

Advertencia importante: El RequestMapping a continuación evitará la resolución de recursos estáticos porque tiene prioridad sobre todos los ResourceHandlers registrados. Todavía estoy buscando una solución para esto, pero una posibilidad podría ser probar uno de los métodos para manejar recursos estáticos sugeridos en esta respuesta.

import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.NoHandlerFoundException;

@Controller
public final class FirewallController

    /**
     * The name of the model attribute (or request parameter for advertisement click tracking) that contains the request URL.
     */
    protected static final String REQUEST_URL = "requestUrl";

    /**
     * The name of the model attribute that contains the request method.
     */
    protected static final String REQUEST_METHOD = "requestMethod";

    /**
     * The name of the model attribute that contains all HTTP headers.
     */
    protected static final String REQUEST_HEADERS = "requestHeaders";

    /**
     * Default constructor.
     */
    public FirewallController()
    
        return;
    

    /**
     * Populates the request URL model attribute from the HTTP request.
     *
     * @param request The HTTP request.
     * @return The request URL.
     */
    @ModelAttribute(REQUEST_URL)
    public final String getRequestURL(final HttpServletRequest request)
    
        return request.getRequestURL().toString();
    

    /**
     * Populates the request method from the HTTP request.
     *
     * @param request The HTTP request.
     * @return The request method (GET, POST, HEAD, etc.).
     */
    @ModelAttribute(REQUEST_METHOD)
    public final String getRequestMethod(final HttpServletRequest request)
    
        return request.getMethod();
    

    /**
     * Gets all headers from the HTTP request.
     *
     * @param request The HTTP request.
     * @return The request headers.
     */
    @ModelAttribute(REQUEST_HEADERS)
    public final HttpHeaders getRequestHeaders(final HttpServletRequest request)
    
        return FirewallController.headers(request);
    

    /**
     * A catch-all default mapping that throws a NoHandlerFoundException.
     * This will be intercepted by the ErrorController, which allows preHandle to work normally.
     *
     * @param requestMethod The request method.
     * @param requestUrl The request URL.
     * @param requestHeaders The request headers.
     * @throws NoHandlerFoundException every time this method is invoked.
     */
    @RequestMapping(value = "/**") // NOTE: This prevents resolution of static resources. Still looking for a workaround for this.
    public void getNotFoundPage(@ModelAttribute(REQUEST_METHOD) final String requestMethod, @ModelAttribute(REQUEST_URL) final String requestUrl, @ModelAttribute(REQUEST_HEADERS) final HttpHeaders requestHeaders) throws NoHandlerFoundException
    
        throw new NoHandlerFoundException(requestMethod, requestUrl, requestHeaders);
    

    /**
     * Gets all headers from a HTTP request.
     *
     * @param request The HTTP request.
     * @return The request headers.
     */
    public static HttpHeaders headers(final HttpServletRequest request)
    
        final HttpHeaders headers = new HttpHeaders();

        for (Enumeration names = request.getHeaderNames(); names.hasMoreElements();)
        
            final String headerName = (String) names.nextElement();

            for (Enumeration headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
            
                headers.add(headerName, (String) headerValues.nextElement());
            
        

        return headers;
    


Resultados

Cuando ambas partes de esto estén funcionando, verá las siguientes dos advertencias registradas (la primera está en Spring Security, la segunda es Spring Framework (Core) ErrorController). Ahora tiene control total sobre el registro y un firewall de aplicaciones extensible que puede ajustar como lo necesite.

Sep 12, 2018 10:24:37 AM com.mycompany.spring.security.AnnotatingHttpFirewall getFirewalledRequest
WARNING: Intercepted org.springframework.security.web.firewall.RequestRejectedException: Remote Host: 0:0:0:0:0:0:0:1 User Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0 Request URL: http://localhost:8080/webapp-www-mycompany-com/login.php
Sep 12, 2018 10:24:37 AM com.mycompany.spring.controller.ErrorController handleException
WARNING: Rejected request from 0:0:0:0:0:0:0:1 for [http://localhost:8080/webapp-www-mycompany-com/login.php]. Reason: The request was rejected because it is a likely vulnerability scan.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *