Category Archives: Security

JEE Security: Disabling HTTP OPTIONS method

PROBLEM

HTTP OPTIONS method is used to provide a list of methods that are supported by the web server.

For example, the following shows both GET and HEAD are allowed on the given link:-

➜  ~ curl -i -k -X OPTIONS https://localhost:8443/
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
X-Application-Context: application:local:8443
Allow: GET,HEAD
Content-Length: 0
Date: Thu, 31 Aug 2017 14:07:21 GMT

Enabling OPTIONS may increase the risk of cross-site tracing (XST)… see OWASP’s Test HTTP Methods (OTG-CONFIG-006).

SOLUTION

There are several ways to disable OPTIONS method.

Solution 1: Using web.xml

If your app has web.xml, you may add the following snippet:-

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
		 http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         metadata-complete="true" version="3.1">

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>restricted methods</web-resource-name>
            <url-pattern>/*</url-pattern>
            <http-method>OPTIONS</http-method>
        </web-resource-collection>
        <auth-constraint/>
    </security-constraint>
	
    <!-- Other configurations -->
</web-app>

Solution 2: Using Spring Boot

If you are using Spring Boot, there isn’t any option to mimic the above configuration programmatically.

However, you still can use web.xml in conjunction with Spring Boot by setting metadata-complete to false and use servlet version 3.0 or higher:-

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
		 http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         metadata-complete="false" version="3.1">

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>restricted methods</web-resource-name>
            <url-pattern>/*</url-pattern>
            <http-method>OPTIONS</http-method>
        </web-resource-collection>
        <auth-constraint/>
    </security-constraint>
</web-app>

Solution 3: Using Spring Security

If you don’t want to use web.xml, you may configure Spring Security to disable OPTIONS method on all URIs:-

@Configuration
@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http.authorizeRequests().
                antMatchers(HttpMethod.OPTIONS, '/**').denyAll().
                antMatchers('/**').permitAll()
    }
}

Now, when trying to hit the same link with OPTIONS method, the app will return 403 Forbidden:-

➜  ~ curl -i -k -X OPTIONS https://localhost:8443/
HTTP/1.1 403
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 31 Aug 2017 14:26:51 GMT
Advertisements

Java + HTTPS: Unable to Find Valid Certification Path to Requested Target

PROBLEM

When invoking a HTTPS URL from Java, for example…

final RestTemplate restTemplate = new RestTemplate();
restTemplate.execute("https://www.google.com/some-api", HttpMethod.GET, null, ...);

…the following exception is thrown…

org.springframework.web.client.ResourceAccessException: I/O error on GET 
request for "https://www.google.com":sun.security.validator.ValidatorException: 
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target; nested exception 
is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: 
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:607)
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:557)

SOLUTION 1: Disable SSL Validation – NOT RECOMMENDED

One way is to simply disable the SSL validation by configuring SSLContext to trust all X.509 certificates before invoking the intended HTTPS URL.

@Configuration
class MyAppConfig {
    @Bean
    public Boolean disableSSLValidation() throws Exception {
        final SSLContext sslContext = SSLContext.getInstance("TLS");

        sslContext.init(null, new TrustManager[]{new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        }}, null);

        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });

        return true;
    }
}

Unless you are writing test cases or implementing non-production code, this approach is highly discouraged because it doesn’t prevent the man-in-the-middle attacks.

SOLUTION 2: Import Certificate into Java Keystore – RECOMMENDED

The reason we see this exception is because the certificate used by the HTTPS URL doesn’t exist in the Java Keystore. Hence, the SSL validation fails.

To fix this, we can download the website’s certificate using a browser to be added into Java Keystore.

Access the website (in this example, I use https://www.google.com) and click on the pad lock.

Find the button that allows you to view and export the certificate as a PEM file. In this example, I named that file google.cer.

The content of the downloaded PEM file should look like this:-

-----BEGIN CERTIFICATE-----
MIIEgDCCA2igAwIBAgIITq9JKLrGf5EwDQYJKoZIhvcNAQELBQAwSTELMAkGA1UE
BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl
eoV23mHmV6/0mOwocVYt/Th96WNGGmhANkFW//HCphRWnhaOqIG6yFRQ/jxArTvZ
QJEGI5AiYHzQn7LdUM8mH1o3ifR+lX+QiAwyeU9oegdlRslI2KMoPOuOFj329NFx
Bw+XVQXMsRJITPg8pnegPmLCOjpz8y7pBxbxGnfaI66I8X4dArsaXX4r5mkfhk2e
mm7fxQ8qUaW9mKoW0XvwGxU0AwKI8OopuXHoD97vr2GSK0QNZ19A96mtTWnQ2cu2
i9qjGw==
-----END CERTIFICATE-----

Go to the Terminal.

If you have multiple versions of java, make sure you choose the correct version first:-

export JAVA_HOME="`/usr/libexec/java_home -v '1.7*'`"

Import the certificate into Java Keystore:-

sudo keytool -import -trustcacerts -file /path/to/google.cer -alias google -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit

Now, you should be able to invoke the HTTPS URL without any exception.

If for some reason you need to remove the imported certificate from Java Keystore, you can refer to that certificate using the alias and run this command:-

sudo keytool -delete -alias google -keystore $JAVA_HOME/jre/lib/security/cacerts

Spring Security: Invalid CSRF Token ‘null’ was found on the request parameter ‘_csrf’ or header ‘X-CSRF-TOKEN’

PROBLEM

With Spring Security 4.x, the CSRF protection is enabled by default. You may disable it, but to be more aligned with OWASP and the industry security standard, it’s best to leave this setting the way it is. Learn more about CSRF attack…

To prevent this attack, Spring Security 4.x requires you to attach a server-side generated CSRF token on any POST, PUT or DELETE calls… basically, actions that may modify the request state. Their argument for not attaching this token on GET is to prevent this token value from leaking out.

Further, you will require to call POST /login and POST /logout now. In the past, you can call GET /j_spring_security_logout without problem.

If you invoke POST, PUT or DELETE without this CSRF token, you will get a 403 error with this message: "Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.".

SOLUTION

To obtain this CSRF token, add this Spring Security custom tag to the JSP file:-

<!DOCTYPE html>
<html>
	<head>
	    <sec:csrfMetaTags/>
	</head>
	<body>
	</body>
</html>

The rendered HTML looks like this:-

<!DOCTYPE html>
<html class="no-js">
	<head>
	    <meta name="_csrf_parameter" content="_csrf" />
	    <meta name="_csrf_header" content="X-CSRF-TOKEN" />
	    <meta name="_csrf" content="e62835df-f1a0-49ea-bce7-bf96f998119c" />
	</head>
	<body>
	</body>
</html>

Finally, set the request header before making the AJAX call:-

var header = $("meta[name='_csrf_header']").attr("content");
var token = $("meta[name='_csrf']").attr("content");

$.ajax({
    url: '/test',
    type: 'POST',
    beforeSend: function(xhr){
        xhr.setRequestHeader(header, token);
    },
    success: function(data) {
        console.log(data);
    },
    error: function (xhr, ajaxOptions, thrownError) {
        console.log(xhr.status + ": " + thrownError);
    }
});

Spring Security: Forcing URLs to use HTTPS

PROBLEM

Your web application supports both HTTP and HTTPS. You want to force all URLs to use HTTPS.

SOLUTION

Spring Security has a simple configuration that allows us to redirect all HTTP-based URLs to HTTPS. All we have to do is to set requires-channel="https" on <security:intercept-url/> tag.

For example:-

<security:http auto-config="true">
	<security:form-login .../>
	<security:logout .../>
	
	<security:intercept-url pattern="/reports" access="ROLE_ADMIN" requires-channel="https"/>
	<security:intercept-url pattern="/login" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https"/>
	<security:intercept-url pattern="/**" access="ROLE_USER" requires-channel="https"/>
</security:http>

With this configuration, when the user hits http://server/app, it will be redirected to https://server/app.

If we are seeing this “redirect loop” error…

… and the server log went bananas…

"GET /app HTTP/1.1" 302 0 
"GET /app/ HTTP/1.1" 302 0 
"GET /app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/app/app/app/app/ HTTP/1.1" 302 0 

… then, chances are we are not using the default HTTP port (80) and HTTPS port (443). To fix this, we have to specify the custom port mappings in the Spring Security configuration:-

<security:http auto-config="true">
	<security:form-login .../>
	<security:logout .../>
	
	<security:intercept-url pattern="/reports" access="ROLE_ADMIN" requires-channel="https"/>
	<security:intercept-url pattern="/login" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https"/>
	<security:intercept-url pattern="/**" access="ROLE_USER" requires-channel="https"/>
	
	<security:port-mappings>
		<!-- Default ports -->
		<security:port-mapping http="80" https="443"/>
		<!-- Websphere default ports -->
		<security:port-mapping http="9080" https="9443"/>
		<!-- Tomcat default ports -->
		<security:port-mapping http="8080" https="8443"/>
		<!-- Jetty custom ports -->
		<security:port-mapping http="7777" https="7443"/>
	</security:port-mappings>
</security:http>

Now, when the user hits http://localhost:7777/app, it will be redirected to https://localhost:7443/app.

Java + HTTPS: Unable to Find Valid Certification Path to Requested Target

PROBLEM

When trying to invoke HTTPS-based web service (ex: https://server:1234/myapp/web-service), the following exceptions occurred:-

Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: 
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target

Caused by: sun.security.validator.ValidatorException: PKIX path building failed: 
sun.security.provider.certpath.SunCertPathBuilderException: unable to find 
valid certification path to requested target

Caused by: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target

SOLUTION

I do not want to take any credits from @mkyong, but this author wrote a great solution to this problem at http://www.mkyong.com/webservices/jax-ws/suncertpathbuilderexception-unable-to-find-valid-certification-path-to-requested-target/ . Kudos to him.

The only thing I want to point out here is the link to InstallCert.java in his blog is dead, but you can download it from https://code.google.com/p/java-use-examples/source/browse/trunk/src/com/aw/ad/util/InstallCert.java .

The long story short here is to run java InstallCert server:1234 to generate a file called jssecacerts. Then, drop this file in ${JAVA_HOME}/lib/security directory.

Maven + Jetty: Enabling SSL on Jetty Maven Plugin

PROBLEM

In order to run our web application using HTTPS, we need to enable SSL first on Jetty.

SOLUTION #1: Generating Certificate on the Fly

One clean solution by @PascalThivent is to recreate the certificate whenever Jetty starts. Not to steal any credits from him, but here’s a slightly modified configuration using more recent plugin versions:-

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>keytool-maven-plugin</artifactId>
    <version>1.5</version>
    <executions>
        <execution>
            <phase>generate-resources</phase>
            <id>clean</id>
            <goals>
                <goal>clean</goal>
            </goals>
        </execution>
        <execution>
            <phase>generate-resources</phase>
            <id>generateKeyPair</id>
            <goals>
                <goal>generateKeyPair</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <keystore>${project.build.directory}/jetty-ssl.keystore</keystore>
        <dname>cn=localhost</dname>
        <!--Both `keypass` and `storepass` must be at least 6 characters long. -->
        <keypass>jetty8</keypass>
        <storepass>jetty8</storepass>
        <alias>jetty8</alias>
        <keyalg>RSA</keyalg>
    </configuration>
</plugin>

<plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>8.1.14.v20131031</version>
    ...
    <configuration>
        <connectors>
            <connector implementation="org.eclipse.jetty.server.bio.SocketConnector">
                <port>7777</port>
            </connector>
            <connector implementation="org.eclipse.jetty.server.ssl.SslSocketConnector">
                <port>7443</port>
                <keystore>${project.build.directory}/jetty-ssl.keystore</keystore>
                <keyPassword>jetty8</keyPassword>
                <password>jetty8</password>
            </connector>
        </connectors>
    </configuration>
</plugin>

In this case, the generated certificate is stored under the project’s build directory (“target” directory) every time we run mvn clean jetty:run. This is a great solution because it allows me to easily pass my project to my peers without the need to worry about the generated certificate.

However, a downside to this approach is because the certificate is regenerated again and again, my browser keeps prompting me to add an exception whenever I refresh the web link.

… and after adding the exception for like 5 times, it begins to get very annoying.

SOLUTION #2: Generating Certificate Once and Reuse it

Instead of regenerating the certificate again and again, I decided to generate the certificate that will last for 99999 days and store it under src/main/config directory.

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>keytool-maven-plugin</artifactId>
    <version>1.5</version>
    <configuration>
        <keystore>${project.basedir}/src/main/config/jetty-ssl.keystore</keystore>
        <dname>cn=localhost</dname>
        <!--Both `keypass` and `storepass` must be at least 6 characters long. -->
        <keypass>jetty8</keypass>
        <storepass>jetty8</storepass>
        <alias>jetty8</alias>
        <keyalg>RSA</keyalg>
        <validity>99999</validity>
    </configuration>
</plugin>

<plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>8.1.14.v20131031</version>
    ...
    <configuration>
        <connectors>
            <connector implementation="org.eclipse.jetty.server.bio.SocketConnector">
                <port>7777</port>
            </connector>
            <connector implementation="org.eclipse.jetty.server.ssl.SslSocketConnector">
                <port>7443</port>
                <keystore>${project.basedir}/src/main/config/jetty-ssl.keystore</keystore>
                <keyPassword>jetty8</keyPassword>
                <password>jetty8</password>
            </connector>
        </connectors>
    </configuration>
</plugin>

To generate (or regenerate) the certificate, run mvn keytool:clean keytool:generateKeyPair.

With this approach, I just need to add an exception ONCE in my browser regardless of the number of times I restart Jetty… or when I’m still actively working on this project after 99999 days.