JEE Security: Preventing Clickjacking Attacks

PROBLEM

Clickjacking is an attack that tricks the users to perform unintended actions… see OWASP’s Testing for Clickjacking (OTG-CLIENT-009)

SOLUTION

To prevent clickjacking attacks, the app must set X-FRAME-OPTIONS header with an appropriate value:-

  • DENY: this denies any domain using the page as an iFrame source. This is the best option.
  • SAMEORIGIN: this allows pages within the same domain to use other application pages as iFrame sources.
  • ALLOW-FROM [whitelisted domains]: this declares a list of domains that are allowed to include the pages as iFrame sources.

If set correctly, the HTTPS response should show X-FRAME-OPTIONS header:-

➜  ~ curl -i -k 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
Set-Cookie: JSESSIONID=04ADDAF886A20AA561021E869E980BCC; Path=/; Secure; HttpOnly
Content-Type: text/html;charset=UTF-8
Content-Language: en-US
Content-Length: 631
Date: Thu, 31 Aug 2017 14:56:57 GMT

There are several ways to set this header.

Solution 1: Using a servlet filter

You may create a servlet filter that sets X-FRAME-OPTIONS in the response header.

Here’s an example using web.xml-less Spring Boot:-

@SpringBootApplication
class Application extends SpringBootServletInitializer {
    static void main(String[] args) {
        SpringApplication.run(Application, args)
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(Application)
    }

    @Bean
    FilterRegistrationBean clickjackingPreventionFilter() {
        return new FilterRegistrationBean(
                urlPatterns: ['/**'],
                filter: new Filter() {
                    @Override
                    void init(final FilterConfig filterConfig) throws ServletException {
                    }

                    @Override
                    void doFilter(final ServletRequest servletRequest,
                                  final ServletResponse servletResponse,
                                  final FilterChain filterChain) throws IOException, ServletException {
                        final HttpServletResponse response = (HttpServletResponse) servletResponse
                        response.addHeader('X-FRAME-OPTIONS', 'DENY')
                        filterChain.doFilter(servletRequest, servletResponse)
                    }

                    @Override
                    void destroy() {
                    }
                }
        )
    }
}

Solution 2: Using Spring Security

Spring Security provides a very easy way to set the X-FRAME-OPTIONS header:-

@Configuration
@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http.
                headers().frameOptions().deny(). 
                and().
                authorizeRequests().
                antMatchers('/**').permitAll()
    }
}
Advertisements

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

Maven GPG Plugin: Prevent Signing Prompt or “gpg: signing failed: No such file or directory” Error

PROBLEM

Given the following Maven settings.xml:-

<?xml version="1.0"?>
<settings>
	<profiles>
		<profile>
			<activation>
				<activeByDefault>true</activeByDefault>
			</activation>
			<properties>
				<gpg.executable>/usr/local/bin/gpg</gpg.executable>
				<gpg.passphrase>XXXXXXXXXXXXXXXXXX</gpg.passphrase>
			</properties>
		</profile>
	</profiles>
</settings>

… and the following Maven GPG Plugin configuration in pom.xml:-

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-gpg-plugin</artifactId>
    <version>1.6</version>
    <executions>
        <execution>
            <id>sign-artifacts</id>
            <phase>verify</phase>
            <goals>
                <goal>sign</goal>
            </goals>
        </execution>
    </executions>
</plugin>

When running mvn clean deploy, you either get a prompt for you to enter the GPG passphrase:-

┌────────────────────────────────────────────────────────────────┐
│ Please enter the passphrase to unlock the OpenPGP secret key:  │
│ "Shitty Author <shitty@email.com>"                             │
│ 2048-bit RSA key, ID 9F1A27DFE94D5473,                         │
│ created 2015-05-08.                                            │
│                                                                │
│                                                                │
│ Passphrase: __________________________________________________ │
│                                                                │
│         <OK>                                    <Cancel>       │
└────────────────────────────────────────────────────────────────┘

… or, get the following error:-

gpg: signing failed: No such file or directory

The long story short, Maven GPG Plugin isn’t using the passphrase defined in the Maven settings.xml… AND THIS UPSETS THE HONEY BADGER!

SOLUTION

If you 1) initially had it working in the past, and 2) have tried all sorts of solutions from the web, and still couldn’t get it working, chances are you have unconsciously upgraded GPG version from 2.0 to 2.1.

You can check your GPG version by running the following command:-

✗ gpg --version 
gpg (GnuPG) 2.1.21
libgcrypt 1.7.6
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: /Users/shitty-author/.gnupg
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
        CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2

To fix this, GPG 2.1 requires --pinentry-mode to be set to loopback in order to pick up gpg.passphrase value defined in Maven settings.xml.

So, update Maven GPG Plugin configuration in pom.xml to the following:-

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-gpg-plugin</artifactId>
    <version>1.6</version>
    <executions>
        <execution>
            <id>sign-artifacts</id>
            <phase>verify</phase>
            <goals>
                <goal>sign</goal>
            </goals>
            <configuration>
                <gpgArguments>
                    <arg>--pinentry-mode</arg>
                    <arg>loopback</arg>
                </gpgArguments>
            </configuration>
        </execution>
    </executions>
</plugin>

When re-running mvn clean deploy, it should deploy the artifact properly now.

Groovy: Copying Properties Between Two Beans

PROBLEM

Given two beans…

class A {
    String name
    LocalDateTime localDateTime
}

class B {
    String name
    LocalDateTime localDateTime
}

There are several ways to copy properties from one bean to another:-

  • The most rudimentary way is to “get” each property from one bean and “set” it on another bean, which is VERY verbose and stupid.
  • Another way is to leverage utilities such as BeanUtils provided by either Apache Commons or Spring. While both libraries are called BeanUtils, they behave slightly different from one another.
  • Write home-grown reflection function… and now you have two problems: 1) it may not handle edge cases properly and 2) no one understands your implementation.

SOLUTION

Groovy provides a helper class to solve this problem called InvokerHelper. The advantage of using this is there’s no need to import yet another dependency and it still allows us to keep our code concise.

Scenario 1: Both beans have exact properties

class MySpec extends Specification {
    class A {
        String name
        LocalDateTime localDateTime
    }

    class B {
        String name
        LocalDateTime localDateTime
    }

    def "given a and b with same exact properties, should copy all properties"() {
        given:
        def a = new A(name: 'name',
                      localDateTime: LocalDateTime.now())
        def b = new B()

        when:
        InvokerHelper.setProperties(b, a.properties)

        then:
        b.name == a.name
        b.localDateTime == a.localDateTime
    }
}

Scenario 2: Source bean has additional properties

class MySpec extends Specification {
    class A {
        String name
        LocalDateTime localDateTime
        Integer extra1
        Boolean extra2
    }

    class B {
        String name
        LocalDateTime localDateTime
    }

    def "given a has additional properties than b, should ignore additional properties"() {
        given:
        def a = new A(name: 'name',
                      localDateTime: LocalDateTime.now(),
                      extra1: 1,
                      extra2: true)
        def b = new B()

        when:
        InvokerHelper.setProperties(b, a.properties)

        then:
        b.name == a.name
        b.localDateTime == a.localDateTime
    }
}

Scenario 3: Destination bean has additional properties

class MySpec extends Specification {
    class A {
        String name
        LocalDateTime localDateTime
    }

    class B {
        String name
        LocalDateTime localDateTime
        Integer extra1
        Boolean extra2
    }

    def "given b has additional properties than a, should set additional properties as null"() {
        given:
        def a = new A(name: 'name',
                      localDateTime: LocalDateTime.now())
        def b = new B()

        when:
        InvokerHelper.setProperties(b, a.properties)

        then:
        b.name == a.name
        b.localDateTime == a.localDateTime
        b.extra1 == null
        b.extra2 == null
    }
}

Scenario 4: Same property but different data type from each bean

The short answer is don’t do it. It’s not worth the hassle and confusion.

class MySpec extends Specification {
    class A {
        String number
    }

    class B {
        Integer number
    }

    def "given same property name but different data type, should go bat shit crazy"() {
        given:
        def a = new A(number: '0')
        def b = new B()

        when:
        InvokerHelper.setProperties(b, a.properties)

        then:
        b.number == 48 // ASCII value for character '0'
    }

    def "given same property name but different data type, should go bat shit crazy again"() {
        given:
        def a = new A(number: '10')
        def b = new B()

        when:
        InvokerHelper.setProperties(b, a.properties)

        then:
        thrown ClassCastException // because there's no ASCII value for character '10'
    }
}

Spring MVC: Failed to convert value of type ‘java.lang.String’ to required type ‘java.time.LocalDateTime’

PROBLEM

Given the following controller …

@RestController
@RequestMapping(value = '/controller')
class MyController {

    @RequestMapping(method = RequestMethod.GET)
    ResponseEntity main(@RequestParam(name = 'dateTime') LocalDateTime dateTime) {
        // ...

        return ResponseEntity.noContent().build()
    }
}

When executing …

GET https://localhost:8443/controller?dateTime=2017-06-22T17:38

… the web service call returns 400 Bad Request with the following error in the console log:-

Failed to bind request element: org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: 
Failed to convert value of type 'java.lang.String' to required type 'java.time.LocalDateTime'; nested exception 
is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] 
to type [@org.springframework.web.bind.annotation.RequestParam java.time.LocalDateTime] for value '2017-06-22T17:38'; 
nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [2017-06-22T17:38]

SOLUTION

One solution is to change the data type from java.time.LocalDateTime to java.lang.String before parsing it to java.time.LocalDateTime. However, it is a little more verbose than I like.

A better way is to leverage @DateTimeFormat:-

@RestController
@RequestMapping(value = '/controller')
class MyController {

    @RequestMapping(method = RequestMethod.GET)
    ResponseEntity main(@RequestParam(name = 'dateTime') @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm") LocalDateTime dateTime) {
        // ...

        return ResponseEntity.noContent().build()
    }
}

MS SQL Server + Hibernate 5: Incorrect syntax near ‘@P0’

PROBLEM

When upgrading to Hibernate 5, the following exception is thrown:-

Caused by: java.sql.SQLException: Incorrect syntax near '@P0'.
	at net.sourceforge.jtds.jdbc.SQLDiagnostic.addDiagnostic(SQLDiagnostic.java:372) ~[jtds-1.3.1.jar:1.3.1]
	at net.sourceforge.jtds.jdbc.TdsCore.tdsErrorToken(TdsCore.java:2988) ~[jtds-1.3.1.jar:1.3.1]
	at net.sourceforge.jtds.jdbc.TdsCore.nextToken(TdsCore.java:2421) ~[jtds-1.3.1.jar:1.3.1]

SOLUTION

Change the MS SQL Server dialect from this…

org.hibernate.dialect.SQLServerDialect

… to this …

org.hibernate.dialect.SQLServer2012Dialect

tar: Exiting with failure status due to previous errors

PROBLEM

When creating a compressed archive file:-

tar -zcvf apps.tar.gz apps

… the following error is thrown:-

tar: Exiting with failure status due to previous errors

SOLUTION

This error usually occurs due to permission issues.

However, the error messages are hidden beneath gobs of output activated by the verbose flag.

To fix this, reduce the output by removing the -v flag:-

tar -zcf apps.tar.gz apps

… and now, the error message appears:-

tar: apps/apps.key.enc: Cannot open: Permission denied
tar: Exiting with failure status due to previous errors

Once the problem is fixed, the command will run successfully.