Solving OWASP Liferay Vulnerabilities And Security Exploits For Version 6.0.x

Big clients ask about Liferay vulnerabilities and security exploits. In this post I will explain how to identify and fix these security issues in Liferay 6.0.x. Please note that some of these Liferay exploits and their fixes might apply to newer versions of the Liferay Portal like 6.1.x to even 7.0.

I have worked with Liferay for over 5 years, delivering solutions on the Liferay platform to clients of all sizes. Medium to large clients, multinational companies especially, have to conform to lots of rules and regulations when it comes to their IT infrastructure. Therefore, when we were approached by this type of clients we were always asked about Liferay security vulnerabilities. In this post I will explain how we went about identifying and fixing these security issues in Liferay.

security-audit

Liferay Portal Security Audit

Security audits will check if your software goes by the OWASP guidelines.

We employed a third party to conduct a security audit on our Liferay Portal 6.0.6 platform at the time (we found the the findings also apply to Liferay 6.0.5 and maybe even Liferay Portal 7.0.x). These kind of audits are quite expensive. Most software companies are not really willing to spend those amounts unless they really have to. And if you have a potential big client that asks it, then you kinda have to.

One of our banking customers agreed to pay part of the security audit. If you can’t afford paying for a third party security audit, I recommend running internal security audits using the tools available online.

The one I’ve used the most is OWASP Zap Proxy which is free to download and very easy to install and use.

The results of the security audit revealed OWASP vulnerabilities in Liferay 6.0.6.

Here’s the list of OWASP vulnerabilities found:

X­Frame­Options Header Not Set

X-Frame-Options header is not included in the HTTP response to protect against ‘ClickJacking’ attacks. Most modern Web browsers support the X-Frame-Options HTTP header. The OWASP guidelines says that you mush ensure it’s set on all web pages returned by your site (if you expect the page to be framed only by pages on your server (e.g. it’s part of a FRAMESET) then you’ll want to use SAMEORIGIN, otherwise if you never expect the page to be framed, you should use DENY.  ALLOW-FROM allows specific websites to frame the web page in supported web browsers).

Cookie No HttpOnly Flag

A cookie has been set without the HttpOnly flag, which means that the cookie can be accessed by JavaScript. If a malicious script can be run on this page then the cookie will be accessible and can be transmitted to another site. If this is a session cookie then session hijacking may be possible. Ensure that the HttpOnly flag is set for all cookies to conform with OWASP guidelines.

Cookie without Secure Flag

A cookie has been set without the secure flag, which means that the cookie can be accessed via unencrypted connections. Whenever a cookie contains sensitive information or is a session token, then it should always be passed using an encrypted channel. Ensure that the secure flag is set for cookies containing such sensitive information.

Web Browser XSS Protection Not Enabled

Web Browser XSS Protection is not enabled, or is disabled by the configuration of the ‘X-XSS-Protection’ HTTP response header on the web server. Ensure that the web browser’s XSS filter is enabled, by setting the X-XSS-Protection HTTP response header to ‘1’.

X­Content­Type­Options Header Missing

The Anti-MIME-Sniffing header X-Content-Type-Options was not set to ‘nosniff’. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.

Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to ‘nosniff’ for all web pages.
If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.

Fixing Liferay Vulnerabilities And Liferay Exploits

Fixing the security vulnerabilities found in Liferay 6.0.6

We actually looked at each and every security vulnerability that the audit revealed. The report contained the location the of the Liferay vulnerability and what triggered the warning.

Also, from the list of issues found only the missing x-frame-options header was marked as medium severity, the rest are considered low severity. It’s always a good approach to discuss with your client the level of acceptance for known security vulnerabilities. With our client we decided to eliminate the medium level issue and try to limit as much as possible the other issues. And that’s what we did.

 

OWASP X-Frame Options And Other Missing Headers

Most of the issues relate to missing headers in the server’s response. To fix this we chose to create an filter that would add the missing headers. We named this filter GlobalSecurityFilter and based on the examples of other Liferay filters we made the class extend BasePortalFilter.

To add this filter to the stack of existing Liferay filters we had to modify the liferay-dir/tomcat/webapps/ROOT/WEB-INF/web.xml and add the following entries:

 <filter>
 <filter-name>Global Security Filter</filter-name>
 <filter-class>com.liferay.security.GlobalSecurityFilter</filter-class>
 </filter>
 <filter-mapping>
 <filter-name>Global Security Filter</filter-name>
 <url-pattern>/*</url-pattern>
 </filter-mapping>

Also, we had to create a jar file containing the GlobalSecurityFilter class and place the jar file under liferay-dir/tomcat/webapps/ROOT/WEB-INF/lib.

What about the content of the class? Well, we made an override of the processFilter method such that all response get the extra headers like this:

...

 public static final String X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options";
 public static final String X_FRAME_OPTIONS = "X-Frame-Options";
 public static final String X_XSS_PROTECTION = "X-XSS-Protection";

@Override
 protected void processFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
 throws Exception {

 response.setHeader(X_FRAME_OPTIONS, "SAMEORIGIN");
 response.setHeader(X_CONTENT_TYPE_OPTIONS, "nosniff");
 response.setHeader(X_XSS_PROTECTION, "1; mode=block");
 
 filterChain.doFilter(request, response);
 }

...

All the warnings were fixed regarding the OWASP X-Frame-Options header for example, X-Content-Type-Options header and the XSS protection related headers.

I say almost because though we had implemented this as a filter and set it on all requests, some requests do not pass through this filter. For example the requests for Liferay theme related resources. However, this is not a real Liferay security exploit since those resources are not critical to the system.

 

Cookie Secure Flag And HttpOnly Cookie

One other item on the security audit report was the fact that the cookies handled by the Liferay server did not have the HttpOnly flag and the Cookie Secure flag. The latter was needed since we ran our apps on HTTPS.

There were multiple cookies that needed those flags: JSESSIONID, GUEST_LANGUAGE_ID, COOKIE_SUPPORT, etc.

One thing to note is that JSESSIONID is not actually a Liferay cookie, but it is set by Tomcat. To add the HttpOnly flag we made the following modification in the Tomcat configuration: liferay-dir/tomcat/conf/context.xml

<Context useHttpOnly="true">

For the other cookies we have to make use of the GlobalSecurityFilter again. We have to add some flags to the cookies used by Lifera, but these cookies are javax.servlet. Cookie classes from the 2.5 specification and that means that they don’t have the HttpOnly or Cookie Secure flag in the implementation. To go around that we have to use a trick. We have to first use an HttpServletResponseWrapper to wrap the original response that we catch in the filter and inside that wrapper we have to modify all the cookies coming in by tampering with the Set-Cookie header.

Here’s the implementation of the HttpServletResponseWrapper, I called it SecuredResponseWrapper and made it an internal class to GlobalSecurityFilter:

...

protected class SecuredResponseWrapper extends HttpServletResponseWrapper {

 public static final String SECURE_FLAG = "; Secure";
 public static final String HTTPONLY_FLAG = "; HttpOnly";
 
 private static SimpleDateFormat cookieFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss zzz");
 public SecuredResponseWrapper(HttpServletResponse response) {
  super(response);
 }

 @Override
 public void addCookie(Cookie cookie) {
 StringBuffer header = new StringBuffer();
 if ((cookie.getName() != null) && (!cookie.getName().equals(""))) {
 header.append(cookie.getName());
 }
 if (cookie.getValue() != null) {
 // Empty values allowed for deleting cookie
 header.append("=" + cookie.getValue());
 }

 if (cookie.getVersion() == 1) {
  header.append(";Version=1");
  if (cookie.getComment() != null) {
   header.append(";Comment=\"" + cookie.getComment() + "\"");
  }
  if (cookie.getMaxAge() > -1) {
   header.append(";Max-Age=" + cookie.getMaxAge());
  }
 } else {
  if (cookie.getMaxAge() > -1) {
   Date now = new Date();
   now.setTime(now.getTime() + (1000L * cookie.getMaxAge()));
   header.append(";Expires=" + cookieFormat.format(now));
  }
 }

 if (cookie.getDomain() != null) {
 header.append(";Domain=" + cookie.getDomain());
 }
 if (cookie.getPath() != null) {
 header.append(";Path=" + cookie.getPath());
 }

 header.append(HTTPONLY_FLAG);

 if (cookie.getSecure()) {
  header.append(SECURE_FLAG);
 }

 addHeader("Set-Cookie", header.toString());
 }
}

..

Also, to make use of this wrapper class we have to slightly change the process filter method like this:

...

@Override
 protected void processFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
 throws Exception {

 response.setHeader(X_FRAME_OPTIONS, "SAMEORIGIN");
 response.setHeader(X_CONTENT_TYPE_OPTIONS, "nosniff");
 response.setHeader(X_XSS_PROTECTION, "1; mode=block");
 
 filterChain.doFilter(request, new SecuredResponseWrapper(response));
 }

...

In order to test that everything is working as expected we can either re-run the ZAP OWASP tool, or easier we can use Chrome to inspect the headers and the cookies by hitting F12 and then go to the Network tab and the Resources tab > Cookies. Here’s how the headers should look after the modifications:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Set-Cookie: JSESSIONID=D782BDBB2ECD97B3EE86CF57BADFA4A3; Path=/; HttpOnly
Content-Encoding: gzip
Set-Cookie: GUEST_LANGUAGE_ID=nl_NL;Expires=Fri, 14 Jul 2017 07:59:03 GMT;Path=/; HttpOnly
Set-Cookie: COOKIE_SUPPORT=true;Expires=Fri, 14 Jul 2017 07:59:03 GMT;Path=/; HttpOnly
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 14 Jul 2016 07:59:04 GMT

And here’s the cookies view

liferay cookies httponly

 

John Negoita

View posts by John Negoita
I'm a Java programmer, been into programming since 1999 and having tons of fun with it.

3 Comments

  1. HazmirulMarch 20, 2018

    Hi

    I am new with liferay. I want to ask details step on adding missing headers. How to create the filter class? I want to set x frame options to “DENY”. I am currently using liferay ce6.1.2

    Thank you

    Reply
    1. John NegoitaMay 12, 2018

      Hi

      simply create a class that extends com.liferay.portal.servlet.filters.header.HeaderFilter then override processFilter to do what you want. Export the class to a jar file and follow the instructions in the article to install it in your Liferay install.

      Reply
  2. […] I’ve been working intensely with Java for more than 12 years. J2EE applications and customization of the Liferay platform (eg.: Solving Liferay OWASP vulnerabilities). […]

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top