Write for OTN
Earn money and promote your technical skills by writing a technical article for Oracle Technology Network.
Learn more
Stay Connected
OTN Architect Community
OTN ArchBeat Blog Facebook Twitter YouTube Podcast Icon

Protecting IDPs from Malformed SAML Requests

Steffo Weber

Using Oracle API Gateway as an XML firewall to protect Oracle Identity Federation from receiving malformed SAML requests

July 2013

Downloads
download-icon13-1Oracle API Gateway
download-icon13-1Oracle Identity Federation

Introduction

Recent research papers, including On Breaking SAML: Be Whoever You Want to Be by Juraj Somorovsky et al, show that certain SAML (Security Assertion Markup Language) implementations are vulnerable to XML attacks. While SAML is a well-established protocol, it is also known for its complexity. This can lead to flaws in implementations with which every real-world deployment must deal. The title of Somorovsky's paper may therefore be somewhat misleading as the paper is not about breaking SAML itself, but about breaking certain SAML implementations (including those of Salesforce, IBM, and Shibboleth).

The goal of this article is to show how Oracle API Gateway (OAG) can be used as an XML firewall to protect Oracle Identity Federation from receiving malformed SAML requests. OAG has built-in functionality to check SAML assertions (e.g. received in SOAP message) but no out-of-the-box integration with an IdP receiving SAML requests (which can also contain malformed XML messages). This article assumes little familiarity with the concepts of SAML, IdP, and SP.

Deployment Architecture: Components

Oracle's Enterprise Deployment Guide suggests placingOracle HTTP Server (OHS, an adopted version of Apache HTTP server) as a reverse proxy in front of OIF. OIF is a web-application running on top of a JEE application server. While OHS disrupts the traffic and prevents direct end-user access OIF , it does not examine the traffic for malicious content. This is were OAG comes into play. OAG is designed as an XML firewall protecting SOAP and REST based web-services. As such is it placed as the first line of defense after the enterprise network firewall. We configure OAG to work as a reverse proxy. OAG listens on some TCP port (e.g. 8080) and accepts only GET requests (this can be configured when defining the path in OAG).

weber-oif-oag-fig01
Figure 1: Overview of deployment architecture

The following is an outline of the steps to configure OAG in order to protect OIF.

  • Extract the SAML Request from the HTTP Request.
  • Transform the SAML Request into an XML-text document.
  • Let OAG's out-of-the-box filters analyze the XML object.

Defining an Oracle Indentity Federation protection policy

Extracting the SAML Request

SAML Requests are normally transmitted via an HTTP GET statement with the request being sent as an URL parameter: http://idp.com/fed?SAMLRequest=<encoded-stuff>. The request is typically zipped (via zlib) and then Base64 encoded. Example 1 shows a sample SAML Request.

GET /fed/idp/samlv20?SAMLRequest=<base64string> HTTP/1.1
Host: oag.example.com:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8;
rv:14.0) Gecko/20100101 Firefox/14.0.1
Accept: text/html,application/xhtml+xml,application/
xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.7,de;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://localhost:8080/fedletsample/
Pragma: no-cache
Cache-Control: no-cache
Example 1: A simple SAML Request

The first step is to extract the value of the URL parameter and decode it. This can be done by a simple filter: HTTP Header Attribute. This filter can also retrieve attributes from URL params. We'll take the value from SAMLRequest and copy it over to saml.request (Figure 2).

weber-oif-oag-fig02
Figure 2: Fetching the value of SAMLRequest URL parameter

Decode SAMLRequest and prepare content.body

The next step is to decode saml.request and prepare it for an input to OAG's XML filter. Most XML Content filters work on the content.body entity.

We'll use a Java lib to decode saml.request. Simply compile the attached Java class and put the JAR (SAMLLibrary.jar) in ext/lib. Note that it goes under apigateway/groups/topologylinks/Group1/instance-1/ext/lib/ and ./oagpolicystudio/jre/lib/ext/. Restart policystudio and gateway (not sure if this is absolutely necessary but it works).

We will use Scripting Filter to decode saml.request. Select Groovy as a language (Example 2); content.body must be in a DOM representation in order to work with the OAG content filter. At this point the SAML Request is in a form that can be analyzed by the OAG content filter.

import com.vordel.mime.XMLBody;
import com.vordel.mime.HeaderSet;
import com.vordel.mime.ContentType;
import com.vordel.mime.ContentType.Authority;
import java.lang.String;
import com.vordel.common.base64.Decoder;
import com.oracle.identity.SamlRequest;
import groovy.xml.DOMBuilder;
import groovy.xml.dom.DOMCategory;
def invoke(msg)
{
def saml = msg.get("saml.request");
def reader = new StringReader(SamlRequest.decode(saml));
def samlRequest = DOMBuilder.parse(reader);
def samlMimeBody = new XMLBody(new HeaderSet(),new
ContentType(ContentType.Authority.MIME,"text/xml"), samlRequest);
msg.put("content.body",samlMimeBody);
return true;
}
Example 2: Generating content.body as DOM

Using Oracle API Gateway content filtering

Now we'll apply a filter which checks if the SAML Request complies with the SAML protocol specification. First, we need to obtain the XSD (available here: http://docs.oasis-open.org/security/saml/v2.0/saml-schema-protocol-2.0.xsd). Add the XSD to OAG's schema cache via "XML Schemas > Add schema", as illustrated in Figure 3.

weber-oif-oag-fig03
Figure 3: Loading SAML schema.

You can now drag a Schema Validation filter onto the canvas and configure it to check against the SAML protocol specification. In our example, we first applied the Threatening Content filter, which runs a series of regular expressions to identify attack signatures (see: OAG Documentation, 2013). We also apply the XML-Complexity Filter. The configuration of this filter allows you to specify the maximal number of nodes, children per node, and attributes per node of the XML document. This filter provides valuable protection against DoS attacks

weber-oif-oag-fig04
Figure 4: Content filters

When content checking is complete the request can be passed to the identity provider (IdP), in this case, Oracle Identity Federation.

Protecting Oracle Identity Federation

Configuring Oracle API Gateway as a proxy

To configure OAG as a proxy, OAG must be able to receive a request on a certain port and forward this request (after content checking) to the actual service. Let's first look at how OAG can receive messages.

By default OAG comes with Default Service enabled (typically listening on :8080). You can now add a path to a service. (A path roughly corresponds to part of a URL, for example, as specified by Apache's mod_proxy ProxyPassReverse directive. The path is the URL called by the client browser. In this case, we define a path as /fed/idp. A path is also assigned an HTTP method by which it can be accessed. In this case, we know that the client uses the HTTP GET method to transmit the SAML request. We assign this "GET" as the method. This means that if a client accesses <apigateway:8080/fed/idp> with a POST method, the gateway will reject the request (typically with an "Access Denied" message). If you configure the ports and path accordingly, you can assign the policy developed above (using the "Policies" tab as shown in Figure 5).
weber-oif-oag-fig05
Figure 5: Defining ports and paths in OAG.

The next step is to augment the policy with filters that forward the request to the IdP (the term "filters" is somewhat irritating as these are just modules that transform the message but do not perform real filtering, as compared, for example, to . the content filters). We will use the following filters:

  • Static router (this is where we specify the host to which the request will be forwarded).
  • Rewrite URL
  • Connection (here you can specify whether you want to modify the host header, HTTP authentication, and lot more). This filter sends the request to the specified host and passes the response back to the browser.

The full policy for the path /idp/fed is illustrated in Figure 6.

weber-oif-oag-fig06
Figure 6: OIF protection policy in OAG.

For real-world deployments, however, additional steps are necessary:

  • Authentication Form. The IdP will redirect the browser to a login page. In the case of OIF with LDAP authentication engine, the page is at /fed/user - a URI for which the above configuration has no path. However, it is easy to add such a path by using the filters Static Router and Connect To URL. This will forward the posting of the credential without content inspection. You might want to add some filters to check whether the credential posting contains only the username and password (plus a product-specific refId). This is fairly easy and left to the reader.
  • Single Logout. The above policy assumes that every access to /fed/idp contains a SAML request. However, this may not always be true. SAML-based logout may also use /fed/idp. In this case the above policy must be augmented to account for logout. This, also, is easily done.

Using Oracle API Gateway to mitigate risks

Flexible attack response

A great benefit of having an XML firewall such as OAG is that you can easily react if a product you have in place suddenly becomes vulnerable. This is the case, for example, when someone figures out that certain service can be compromised by sending malicious messages. If the attack pattern is known, you can check for this pattern using OAG's content filters, scripting operations (Groovy, Jython, JavaScript), or even by deploying your own Java classes (as we did with the decoder).

A sophisticated attack pattern might be too complex for you to quickly write an OAG-filter to protect OIF from malformed SAML requests. The solution here is to authenticate the user before the request is passed on to OIF. In order to prevent the user from entering his password twice (once for sending the SAML request via OAG to OIF and once for authenticate at OIF) you can use a web-SSO tool such as Oracle Access Manager, which integrates nicely with OAG. OIF can be configured to use Access Manager as an authentication engine (this is an out-of-the-box configuration). This OAG policy for /fed/idp needs only slight modifications. The message flow is as follows:

  1. User (browser) sends SAMLReq via HTTP GET.
  2. OAG intercepts the request and forces user to authenticate at Access Manager.
  3. User authenticates at Access Manager and OAG passes SAMLReq to OIF.
  4. OIF checks if user is authenticated at Access Manager (which is the case here) and replies to SAMLReq.

Note that you need this alternative setting only if attacks on your IdP implementation emerge (e.g. via CERT news). This is plan B. Reconfiguring OIF to use Access Manager as an authentication engine takes only a few minutes. Redeploying the augmented policy, which checks for an authenticated user, also takes only few minutes.

An alternative would be to place the reverse proxy (OHS) with an optional webgate (to be enabled as part of plan B) in front of OAG (on the same box) and also letting SSL traffic terminate here (Figure 7).

weber-oif-oag-fig07
Figure 7: Alternative setup for diversified security

This would allow you to account for two things:

  • flaws in OAG's SSL implementation, and
  • flaws in the IdP's SAML service implementation.

This would require (as part of plan B):

  • restarting the reverse proxy with an SSL module and a webgate,
  • disabling SSL at the API gateway.

Again, this means that you can react within a couple of minutes to such an attack. Not too bad, right?

References

Appendix

Unzipping and Base64 decoding the SAMLRequest

package com.oracle.identity;

// Shamelessly copied from https://forums.oracle.com/thread/977401. 
// Kudos go to an unknown coder.


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import java.util.zip.ZipException;
import org.apache.commons.codec.binary.Base64;
public class SamlRequest {

   /**
    * * @param args
    */

public static String decode(String encSAMLRequest){
    String ret = null;
     
    SamlRequest samlRequest = null; //the xml is compressed (deflate) and encoded (base64)
    byte[] decodedBytes = null;
    try { 
        decodedBytes = new Base64().decode(encSAMLRequest.getBytes("UTF-8"));
    } catch (UnsupportedEncodingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    try {
        //try DEFLATE (rfc 1951) -- according to SAML spec
        ret = new String(inflate(decodedBytes, true));
        //return new SamlRequest(new String(inflate(decodedBytes, true)));
    } catch (Exception ze) {
        //try zlib (rfc 1950) -- initial impl didn't realize java docs are wrong
        try {
            System.out.println(new String(inflate(decodedBytes, false)));
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //return new SamlRequest(new String(inflate(decodedBytes, false)));
    }
    return ret;
}


private static byte[] inflate(byte[] bytes, boolean nowrap) throws Exception {

    Inflater decompressor = null;
    InflaterInputStream decompressorStream = null;
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    try {
        decompressor = new Inflater(nowrap);
        decompressorStream = new InflaterInputStream(new ByteArrayInputStream(bytes), 
          decompressor);
        byte[] buf = new byte[1024];
        int count;
        while ((count = decompressorStream.read(buf)) != -1) {
            out.write(buf, 0, count);
        }
        return out.toByteArray();
    } finally {
        if (decompressor != null) {
            decompressor.end();
        }
        try {
            if (decompressorStream != null) {
                decompressorStream.close();
            }
         } catch (IOException ioe) {
             /*ignore*/
         }
         try {
             if (out != null) {
                 out.close();
             }
         } catch (IOException ioe) {
             /*ignore*/
         }
      }
   }
}

About the Author

Steffo Weber is an identity management architect for Oracle. Before joining Oracle he worked 10 years for Sun Microsystems on highly scalable web and identity systems.