Sunday 17 February 2013

J2EE Container Managed Authentication Security Flaw


Introduction

Web applications also often rely on the web servers for authentication. The container managed authentication is a well-known concept in J2EE, which kicks-off just with a simple configuration of “security-constraints” in the deployment descriptor i.e. web.xml file.

The container is responsible to block unauthenticated access to the internal resources of the application, as configured in the “web.xml” file. But we have discovered that a user can still perform any internal operation of the application without authentication by exploiting a severe flaw of the J2EE based web containers.

Let’s understand the flaw.

Before we dive into the flaw, let’s look at how does the container managed thing work in J2EE applications.

A figure given below explains it in detail.


 

Here, whenever a user requests for an Internal page of the application, the user gets redirected to a login page and only after authentication the requested page is served to the user.

So, what is the catch here? Isn’t it normal to behave that way?
It is perfect to behave that way, but have you ever wondered how does the server redirect the user to the page requested before authentication? Don’t you think the server might be maintaining the copy of the previous request somewhere? We have already shown it to you in the figure above that it does it in the session object.

Bingo! That’s exactly the catch :)

In Tomcat, the container managed authentication is handled by
 “org.apache.catalina.authenticator.AuthenticatorBase” class and it subclasses depending on the type of authentication being configured.
It has 5 subclasses:
  • BasicAuthenticator
  • DigestAuthenticator
  • FormAuthenticator
  • NonLoginAuthenticator
  • SSLAuthenticator
      Out of which “FormAuthenticator” is our interest as it comes into action for Form based authentication, which is widely seen in web applications.

It all starts with the “invoke” method implemented in the “AuthenticatorBase” class. The “security-constraints” configuration of web.xml is considered as a valve by the web container. And every valve has to have an “invoke” method as specified by the “ValveBase” class, super class of “AuthenticatorBase” class.
So, whenever a request is received by the server, and if the container managed authentication is enabled (i.e. “security-constraints” is set) one of the subclasses of “AuthenticatorBase” based on the authentication type is initialized and its “invoke” method is called.
The “invoke” method then calls the “authenticate” method to authenticate the request.

The definition of “authenticate” method of “FormAuthenticator” class is shown below.

Observe that it checks if the incoming request is a login request i.e. whether the request is posted to the URL – “j_security_check”. If no, it saves the request using the “saveRequest” method and displays the login page to the user. That means the user must be authenticated to the site before accessing any of its pages. However, we will see shortly how it can be bypassed.

public boolean authenticate(Request request, Response response, LoginConfig config)throws IOException {
boolean loginAction = requestURI.startsWith(contextPath) && requestURI.endsWith(Constants.FORM_ACTION);
// No -- Save this request and redirect to the form login page
        if (!loginAction) {
               session = request.getSessionInternal(true);
               if (log.isDebugEnabled())
        log.debug("Save request in session '" + session.getIdInternal() +"'");
try {
        saveRequest(request, session);
} catch (IOException ioe) {
        …
}
forwardToLoginPage(request, response, config);
       return (false);

The definition of “saveRequest” method is shown below. Observe that it retrieves all the information of the request and saves it in session. It initializes an object of the class – “org.apache.catalina.authenticator.SavedRequest” for storing the request information.

Note an important point here that in case of a POST request, even the body of the request containing the request parameters is also saved.

protected void saveRequest(Request request, Session session) throws IOException {

// Create and populate a SavedRequest object for this request
            SavedRequest saved = new SavedRequest();
               ...
             if ("POST".equalsIgnoreCase(request.getMethod())) {
                 ByteChunk body = new ByteChunk();           body.setLimit(request.getConnector().getMaxSavePostSize());
                 byte[] buffer = new byte[4096];
                 int bytesRead;
                 InputStream is = request.getInputStream();
                 while ( (bytesRead = is.read(buffer) ) >= 0) {
                     body.append(buffer, 0, bytesRead);
                 }
                 saved.setContentType(request.getContentType());
                 saved.setBody(body);
             }
             saved.setMethod(request.getMethod());
             saved.setQueryString(request.getQueryString());
             saved.setRequestURI(request.getRequestURI());
             // Stash the SavedRequest in our session for later use
             session.setNote(Constants.FORM_REQUEST_NOTE, saved);

Once the user is authenticated the user is then redirected to the previously request saved URL.
When this time the request comes to the server, it checks if the saved URL matches the requested URL using a “matchRequest” method and in case of a match it restores and processes the previously stored request from the session.

How does this flaw manifest?

We already asked you to note an interesting fact before, and that is in case the pre-authentication request is POST, even the request body is stored at the server.
So, here in this case, if an attacker happens to send any POST request for an internal action, for instance, to add a new user in the system, from a victim user’s browser. When the victim user comes and logs into the application using the same browser the internal operation will get carried out without the knowledge of the user.

So, that’s the flaw, the server stored the request information sent before authentication along with all the request parameters, which can be easily exploited using technique shown in the section below.

Exploit Steps

Consider a dummy social networking application called – “Facehook” that has uses container managed authenticated. Observe that it has configured “security-constraint” in “web.xml” and all the pages within the directory - “site” has been declared as protected. That means container’s authentication process will come into play whenever a user tries to access any of those pages. 



Let’s see how we can exploit it.

Create a HTML page – that can send a request to an internal page of the application – i.e. “site/Updation.jsp”. This page is used to allow users to edit their profile information. Craft the page such a way that it can send the profile change request whenever the page loads using a javascript, without the knowledge of the user accessing it. A sample code is shown in the figure below.


The malicious page designed above is displayed on the browser as shown below.



The above HTML page can then we used to victimize users of “Facehook” applications and edit their profiles, without their knowledge.

Let’s see how this would work.


If a victim user opens this page, the profile change request will go to the server. A snapshot of the captured request is shown in the figure below. 


This being an internal request, the server redirects the user to the login page of the application.


Since, our page had misled the user in believing that in order to obtain promotional benefits from “Facehook” application, he must login to the application. The user continues to log into the application, as shown below.


The captured login request is shown in the figure below. Observe that the request is being posted to “j_security_check” that invokes the containers authentication process.



Once, the user is authenticated, the server redirects the user to the profile update page, to which the initial request was sent by the malicious page, without the knowledge of the user.



With this redirection request, the profile change request gets processed for the user and users account thus gets hacked, as shown in the figure below.


Here, the victim user did not intend to send any profile change information, he had just logged-in to the application to receive the new promotional benefits from the “Facehook” application, as indicated by the malicious page. But the user got tricked, because of the container managed authentication flaw.

The server had saved the previous profile change request sent by the malicious page and when the user logged-in to the site, it executed that request. Similarly all the users of applications having container managed authentication can be tricked into performing internal operations without their knowledge. 

Similarity with CSRF

Though the way this container managed flaw can be exploited might be similar to Cross-Site request forgery but the concept is different. Unlike CSRF in this case we don’t need any pre-logged in session of the user, the only requirement is that the user must login after the malicious unintended request has been sent to the server using the same browser. It can also be carried out my local attackers who are situated locally in the same premise as the victim, and who have got access to victim’s browsers. They can forge a request from the victim’s browser, and keep the login page open on the victim’s browser. When the victim logs in, the malicious request will get executed under his login privilege.

Recommended Fix

The solution is to ensure that all actions that result in change or addition of data, transactions etc, within application, should be accompanied with a random token.  All HTTP requests that cause change in data should carry a random and unique token, that should verified by a servlet filter or the processing servlet before making any changes to the application.
This will prevent any attacker from forging a HTTP request and duping legitmate users into executing such a request.
Always use POST request to modify or delete data on the server
Use an token along with every POST HTTP request
  •       Token should unique for each user session
  •       Token should be random making it difficult to guess
  •        Should be always validated at the server

References
Code References
Affected Servers
JBOSS, Tomcat, Glassfish

No comments:

Post a Comment