Security

This document is referring to a past Scout release. Please click here for the recent version.

Default HTTP Response Headers

All Scout HTTP servlets delegate to a central authority to append HTTP response headers. This is the bean HttpServletControl. It enables developers to control which headers that should be added to the HTTP response for each servlet and request.

The next sections describe the headers that are added to any response by default. Beside these also the following headers may be of interest for an end user application (consider adding them to your application if possible):

Please note that not all headers are supported in all user agents!

X-Frame-Options

The X-Frame-Options HTTP response header [1] can be used to indicate whether a user agent should be allowed to render a page in a <frame>, <iframe> or <object>. Sites can use this to avoid clickjacking [2] attacks, by ensuring that their content is not embedded into other sites. The X-Frame-Options header is described in RFC 7034 [3].

In Scout this header is set to SAMEORIGIN which allows the page to be displayed in a frame on the same origin (scheme, host and port) as the page itself only.

X-XSS-Protection

This header enables the XSS [4] filter built into most recent user agents. It’s usually enabled by default anyway, so the role of this header is to re-enable the filter for the website if it was disabled by the user. The X-XSS-Protection header is described in controlling-the-xss-filter.

In Scout this header is configured to enable XSS protections and instructs the user-agent to block a page from loading if reflected XSS is detected.

Content Security Policy

Content Security Policy is an HTTP response header that helps you reduce XSS risks on modern user agents by declaring what dynamic resources are allowed to load [5]. The CSP header is described in Level 1 and Level 2. There is also a working draft for a Level 3.

Scout makes use of Level 1 (and one directive from Level 2) and sets by default the following settings:

  • JavaScript [6]: Only accepts JavaScript resources from the same origin (same scheme, host and port). Inline JavaScript is allowed and unsafe dynamic code evaluation (like eval(string), setTimeout(string), setInterval(string), new Function(string)) is allowed as well.

  • Stylesheets (CSS) [7]: Only accepts Stylesheet resources from the same origin (same scheme, host and port). Inline style attributes are allowed.

  • Frames [8]: All sources are allowed because the iframes created by the Scout BrowserField run in the sandbox mode and therefore handle the security policy on their own.

  • All other types (Image, WebSocket [9], EventSource [10], AJAX calls [11], fonts, <object> [12], <embed> [13], <applet> [14], <audio> [15] and <video> [16]) only allow resources from the same origin (same scheme, host and port).

If a resource is blocked because it violates the CSP a report is created and logged on server side using level warning. This is done in the class ContentSecurityPolicyReportHandler. This enables admins to monitor the application and to react if a CSP violation is detected.

The UiServlet checks if the session cookie is configured safely. The validation is only performed on first access to the UiServlet. There is no automatic validation on the backend server side or on any custom servlets!

If the validation fails, a corresponding error message is logged to the server and an exception is thrown making the UiServlet inaccessible. Because of security reasons the exception shown to the user includes no details about the error. These can only be seen on the server side log.

HttpOnly

First the existence of the HttpOnly flag is checked. The servlet container will then add this flag to the Set-Cookie HTTP response header. If the user agent supports this flag, the cookie cannot be accessed through a client side script. As a result even if a cross-site scripting (XSS) flaw exists and a user accidentally accesses a link that exploits this flaw, the user agent will not reveal the cookie to a third party. For a list of user agents supporting this feature please refer to OWASP.

It is recommended to always enable this flag.

Since Java Servlet 3.0 specification this property can be set in the configuration in the deployment descriptor WEB-INF/web.xml:

<?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_4_0.xsd"
         version="4.0"
         metadata-complete="true">
  ...
  <session-config>
    ...
    <cookie-config>
      <http-only>true</http-only> (1)
      ...
    </cookie-config>
    ...
  </session-config>
  ...
</web-app>
1 The HttpOnly flag activated

Secure

Second the existence of the Secure flag is checked. The servlet container will then add this flag to the Set-Cookie HTTP response header. The purpose of the secure flag is to prevent cookies from being observed by unauthorized parties due to the transmission of a cookie in clear text. Therefore, setting this flag will prevent the user agent from transmitting the session id over an unencrypted channel.

Since Java Servlet 3.0 specification this property can be set in the configuration in the deployment descriptor WEB-INF/web.xml:

<?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_4_0.xsd"
         version="4.0"
         metadata-complete="true">
  ...
  <session-config>
    ...
    <cookie-config>
      <secure>true</secure> (1)
      ...
    </cookie-config>
    ...
  </session-config>
  ...
</web-app>
1 The Secure flag activated

This of course only makes sense if the application is exposed to the end user using an encrypted channel like HTTPS (which is strongly recommended).

Unfortunately for the UI server it is not possible to detect if an application uses a secured channel. Consider the following example: The servlet container is protected by a reverse proxy. The communication between the user agent and the proxy is encrypted while the channel between the proxy and the servlet container is not. In this scenario the container cannot know that from a user agent point of view the channel is secured.

Because of this the validation assumes that the channel from the user agent to the entering node is secured and by default checks for the Secure flag. In case this assumption is not true and an unencrypted channel must be used this validation step can be disabled by setting the following property in the config.propertis file:

scout.auth.cookieSessionValidateSecure=false

This skips the Secure flag check completely. In this scenario (not using https) it is also required to remove the secure tag from the cookie config in the WEB-INF/web.xml.

Secure Output

This chapter describes how HTML Output can be handled in a secure way.

Scout applications often display potentially dangerous data, e.g. user input or data from other systems. Encoding this input in such a way, that it can not be executed, prevents security vulnerabilities like cross-site scripting.

Encoding by Default

By default, all input in the Scout model is encoded. Examples are values/labels in value fields, cells in tables, message in message box. The reason behind this default choice is that developers do not have to think about output encoding in the standard case and are therefore less likely to forget output encoding and introduce a security vulnerability.

Example: In the following label field, the HTML <b> tag is encoded as &lt;b&gt;bold text&lt;/b&gt;:

encodedField
public class LabelField extends AbstractLabelField {
  @Override
  protected void execInitField() {
    setValue("...<b>Bold text</b>...");
  }

Html Enabled

Sometimes developers may want to use HTML in the Scout model.

Examples are

  • Simple styling of dynamic content, such as addresses or texts in message boxes

  • Text containing application-internal or external links

  • Html or XML content received from other systems, such as e-mails or html pages

Html input should only partially be encoded or not at all.

To disable the encoding of the whole value, the property HtmlEnabled can be used:

public class NoEncodingLabelField extends AbstractLabelField {
  @Override
  protected boolean getConfiguredHtmlEnabled() {
    return true;
  }
@Override
protected void execInitField() {
  setValue("...<b>Bold text</b>...");
}

There are several ways to implement the use cases above. Some typical implementations are described in the following sections.

CSS Class and Other Model Properties

Often using HTML in value fields or table cells is not necessary for styling. Very basic styling can be done for example by setting the CSS class.

HTML Builder

For creating simple HTML files or fragments with encoded user input, the class org.eclipse.scout.rt.platform.html.HTML can be used. It is also easily possible to create application internal and external link with this approach.

Styling in the UI-Layer

For more complex HTML, using IBeanField in the scout model and implementing the styling in the UI-Layer is often the preferred way. Links are possible as well.

Manual Encoding

It is also possible to encode any String manually using StringUtility.htmlEncode(String). org.eclipse.scout.rt.platform.html.HTML uses this method internally for encoding. However, using HTML is recommended, where possible, because it is more concise and leads to less errors.

Using a White-List Filter

If HTML or XML from external sources or more complex HTML are used in the Scout model, using a white-list filter might be the best way to avoid security bugs. Libraries, such as JSoup provide such a white-list filter. Scout currently does not include any services or utilities for using white-list filters, because the configuration and usage is very use-case-specific and would therefore not add much benefit.

Authorization (Granting)

Scout uses the java.security API principles to grant access to a specific resource.

Each user has a set of granted java.security.Permission instances. This set is a java.security.PermissionCollection. A call to PermissionCollection.implies(Permission p) does the access check. The argument p in this call is a new permission instance for which we want to do the access check and which is compared against the granted permissions. Usually, the permission collection implementation iterates through all granted permissions and calls on each Permission.implies(Permission p) until one call returns true.

Scout adds some concepts and helpers to this API:

IPermission

Unlike other permissions, a permission implementing this interface can only be implied by another IPermission with the same name. A permission used together with scouts security API does not have to implement the IPermission interface, but it is recommended.

PermissionLevel

An IPermission, which is part of an IPermissionCollection has always a granted access level assigned (IPermission.getLevel()). If the granted level is PermissionLevel.NONE, any access checks will fail.

IAccessControlService

This service is responsible to provide and manage a users set of granted permissions. A scout application usually extends AbstractAccessControlService and implements #execLoadPermissions.

ACCESS

Provides a set of convenience methods to check access.

Let us assume you require a permission to allow a user to access companies.

public class ReadCompanyPermission extends AbstractPermission {
  private static final long serialVersionUID = 1L;

  public ReadCompanyPermission() {
    super("scoutdoc.ReadCompany");
  }
}

To check access one can use ACCESS.

if (ACCESS.check(new ReadCompanyPermission())) { (1)
  throw new AccessForbiddenException(TEXTS.get("YouAreNotAllowedToReadThisData"));
}

ACCESS.checkAndThrow(new ReadCompanyPermission()); (2)
1 Checks permission against granted permissions of current user.
2 Checks permission and if this check fails, throw an AccessForbiddenException with a default message.

We can define a default access check failed message for a permission.

public class CreateCompanyPermission extends AbstractPermission {
  private static final long serialVersionUID = 1L;

  public CreateCompanyPermission() {
    super("scoutdoc.CreateCompany");
  }

  @Override
  public String getAccessCheckFailedMessage() {
    return TEXTS.get("YouAreNotAllowedToRegisterThisData");
  }
}

ACCESS allows to check multiple permissions at once.

ACCESS.checkAllAndThrow(new ReadCompanyPermission(), new CreateCompanyPermission());

ACCESS.checkAnyAndThrow(new ReadCompanyPermission(), new CreateCompanyPermission());

We have seen some simple permission checks. Now let us assume, that some users may modify a company only if they have registered the company by themselves. For this use case we introduce a new permission level ScoutdocPermissionLevels.OWN. This is the permission level which is granted for those users.

public final class ScoutdocPermissionLevels {

  private ScoutdocPermissionLevels() {
  }

  public static final int LEVEL_NONE = PermissionLevel.LEVEL_NONE;
  public static final int LEVEL_OWN = 10;
  public static final int LEVEL_ALL = PermissionLevel.LEVEL_ALL;

  public static final PermissionLevel NONE = PermissionLevel.NONE;
  public static final PermissionLevel OWN =
      PermissionLevel.register(LEVEL_OWN, "OWN", true, () -> TEXTS.get("Own"));
  public static final PermissionLevel ALL = PermissionLevel.ALL;

  public static void init() {
    // ensures all static initializers have been called
  }
}

In order to check access for this new level we have to override AbstractPermission#evalPermission.

public class UpdateCompanyPermission extends AbstractPermission {
  private static final long serialVersionUID = 1L;

  private final UUID m_companyId;

  public UpdateCompanyPermission() {
    this(null);
  }

  public UpdateCompanyPermission(UUID companyId) {
    super("scoutdoc.UpdateCompany");
    m_companyId = companyId;
  }

  public UUID getCompanyId() {
    return m_companyId;
  }

  @Override
  protected boolean evalPermission(IPermission p) {
    // Precondition: p.getClass() == getClass() && getName().equals(p.getName()) &&
    //               getLevel() != PermissionLevel.NONE
    if (ScoutdocPermissionLevels.OWN == getLevel()) {
      UUID companyId = ((UpdateCompanyPermission) p).getCompanyId();
      return BEANS.get(ICompanyService.class).isOwnCompany(companyId);
    }
    return true; // ScoutdocPermissionLevels.ALL == getLevel()
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = super.hashCode();
    result = prime * result + ((m_companyId == null) ? 0 : m_companyId.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (!super.equals(obj)) {
      return false;
    }
    if (getClass() != obj.getClass()) {
      return false;
    }
    UpdateCompanyPermission other = (UpdateCompanyPermission) obj;
    if (m_companyId == null) {
      if (other.m_companyId != null) {
        return false;
      }
    }
    else if (!m_companyId.equals(other.m_companyId)) {
      return false;
    }
    return true;
  }
}
ACCESS.checkAndThrow(new UpdateCompanyPermission(companyId));

If such a service call would be expensive, one may cache the result of such a permission check. You have to implement this by yourself. A recommended solution is to create a wrapper around IPermissionCollection and this wrapper caches calls to IPermissionCollection#implies if required.