You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by bu...@apache.org on 2002/02/06 18:21:42 UTC

DO NOT REPLY [Bug 6279] New: - Resubmit to j_security_check mistakenly fetches a page of that name

DO NOT REPLY TO THIS EMAIL, BUT PLEASE POST YOUR BUG 
RELATED COMMENTS THROUGH THE WEB INTERFACE AVAILABLE AT
<http://nagoya.apache.org/bugzilla/show_bug.cgi?id=6279>.
ANY REPLY MADE TO THIS MESSAGE WILL NOT BE COLLECTED AND 
INSERTED IN THE BUG DATABASE.

http://nagoya.apache.org/bugzilla/show_bug.cgi?id=6279

Resubmit to j_security_check mistakenly fetches a page of that name

           Summary: Resubmit to j_security_check mistakenly fetches a page
                    of that name
           Product: Tomcat 4
           Version: 4.0.1 Final
          Platform: All
        OS/Version: All
            Status: NEW
          Severity: Normal
          Priority: Other
         Component: HTTP/1.1 Connector
        AssignedTo: tomcat-dev@jakarta.apache.org
        ReportedBy: Brian.Ewins@btinternet.com


This ought to be easy to reproduce...if you go to a page secured by form-based 
authentication in a webapp under catalina, you are displayed the login form. 
Log in, you see the page you asked for. Now click 'back' in the browser. You 
get the login form again. Resubmit the form.

You will end up at a tomcat 404 error page, explaining that the 
resource /some/path/to/j_security_check does not exist.

Its not that unusual a thing for users to do, it seems; three out of four 
testers spotted it in our site without being told to look for it.

The problem appears to be in 
org.apache.catalina.authenticator.FormAuthenticator.java . This /does/ get 
invoked if the request is for j_security_check, by this code in 
AuthenticatorBase:

 if (requestURI.startsWith(contextPath) &&
            requestURI.endsWith(Constants.FORM_ACTION)) {
            if (!authenticate(hrequest, hresponse, config)) {
 ...etc

However as soon as the code enters FormAuthenticator.authenticate it checks for 
a non-null principal, and if it finds one, it returns immediately. Because 
there is no check here that the uri requested matches j_security_check, the 
user is taken directly to j_security_check, instead of reauthenticating or 
sending the user to the URL saved in the session.

The spec is silent on this situation. I would guess that if someone resubmits 
they want to re-authenticate, potentially as someone else. The code could be 
fixed at the test in AuthenticatorBase, but this may have side effects if 
j_security_check is requested when using other forms of authenticator. It seems 
best to patch FormAuthenticator.java, like so (changes marked 'BE'):

    public boolean authenticate(HttpRequest request,
                                HttpResponse response,
                                LoginConfig config)
        throws IOException {

        // References to objects we will need later
        HttpServletRequest hreq =
          (HttpServletRequest) request.getRequest();
        HttpServletResponse hres =
          (HttpServletResponse) response.getResponse();
        Session session = null;

        // BE: moved three declarations and their comments to top

        // Acquire references to objects we will need to evaluate
        String contextPath = hreq.getContextPath();
        String requestURI = hreq.getRequestURI();

        // Is this the action request from the login page?
        boolean loginAction =
            requestURI.startsWith(contextPath) &&
            requestURI.endsWith(Constants.FORM_ACTION);

        // Have we already authenticated someone?
        Principal principal = hreq.getUserPrincipal();

        // BE: added check for loginAction
        if (principal != null && !loginAction) {
            if (debug >= 1)
                log("Already authenticated '" +
                    principal.getName() + "'");
            String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
            if (ssoId != null)
                associate(ssoId, getSession(request, true));
            return (true);
        }

        // Have we authenticated this user before but have caching disabled?
        // BE: added check for loginAction - would have had same problem
        if (!cache && !loginAction) {
            session = getSession(request, true);
            if (debug >= 1)
                log("Checking for reauthenticate in session " + session);
            String username =
                (String) session.getNote(Constants.SESS_USERNAME_NOTE);
            String password =
                (String) session.getNote(Constants.SESS_PASSWORD_NOTE);
            if ((username != null) && (password != null)) {
                if (debug >= 1)
                    log("Reauthenticating username '" + username + "'");
                principal =
                    context.getRealm().authenticate(username, password);
                if (principal != null) {
                    session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal);
                    register(request, response, principal,
                             Constants.FORM_METHOD,
                             username, password);
                    return (true);
                }
                if (debug >= 1)
                    log("Reauthentication failed, proceed normally");
            }
        }

        // Is this the re-submit of the original request URI after successful
        // authentication?  If so, forward the *original* request instead.
        // BE: will still have a problem here if someone directly requested the
        // j_security_check page! Should really send an error response for
        // that case.
        if (matchRequest(request)) {
            session = getSession(request, true);
            if (debug >= 1)
                log("Restore request from session '" + session.getId() + "'");
            principal = (Principal)
                session.getNote(Constants.FORM_PRINCIPAL_NOTE);
            register(request, response, principal, Constants.FORM_METHOD,
                     (String) session.getNote(Constants.SESS_USERNAME_NOTE),
                     (String) session.getNote(Constants.SESS_PASSWORD_NOTE));
            String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
            if (ssoId != null)
                associate(ssoId, session);
            if (restoreRequest(request, session)) {
                if (debug >= 1)
                    log("Proceed to restored request");
                return (true);
            } else {
                if (debug >= 1)
                    log("Restore of original request failed");
                hres.sendError(HttpServletResponse.SC_BAD_REQUEST);
                return (false);
            }
        }

        // Acquire references to objects we will need to evaluate
        // BE: moved two declarations to start of method
        response.setContext(request.getContext());

        // Is this a request for the login page itself?  Test here to avoid
        // displaying it twice (from the user's perspective) -- once because
        // of the "save and redirect" and once because of the "restore and
        // redirect" performed below.
        String loginURI = contextPath + config.getLoginPage();
        if (requestURI.equals(loginURI)) {
            if (debug >= 1)
                log("Requesting login page normally");
            return (true);      // Display the login page in the usual manner
        }

        // Is this a request for the error page itself?  Test here to avoid
        // an endless loop (back to the login page) if the error page is
        // within the protected area of our security constraint
        String errorURI = contextPath + config.getErrorPage();
        if (requestURI.equals(errorURI)) {
            if (debug >= 1)
                log("Requesting error page normally");
            return (true);      // Display the error page in the usual manner
        }

        // BE - declaration of loginAction moved to start of method
        // Is this the action request from the login page?
        // No -- Save this request and redirect to the form login page
        if (!loginAction) {
            session = getSession(request, true);
            if (debug >= 1)
                log("Save request in session '" + session.getId() + "'");
            saveRequest(request, session);
            if (debug >= 1)
                log("Redirect to login page '" + loginURI + "'");
            hres.sendRedirect(hres.encodeRedirectURL(loginURI));
            return (false);
        }

        // Yes -- Validate the specified credentials and redirect
        // to the error page if they are not correct
        Realm realm = context.getRealm();
        String username = hreq.getParameter(Constants.FORM_USERNAME);
        String password = hreq.getParameter(Constants.FORM_PASSWORD);
        if (debug >= 1)
            log("Authenticating username '" + username + "'");
        principal = realm.authenticate(username, password);
        if (principal == null) {
            if (debug >= 1)
                log("Redirect to error page '" + errorURI + "'");
            hres.sendRedirect(hres.encodeRedirectURL(errorURI));
            return (false);
        }

        // Save the authenticated Principal in our session
        if (debug >= 1)
            log("Authentication of '" + username + "' was successful");
        if (session == null)
            session = getSession(request, true);
        session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal);

        // If we are not caching, save the username and password as well
        if (!cache) {
            session.setNote(Constants.SESS_USERNAME_NOTE, username);
            session.setNote(Constants.SESS_PASSWORD_NOTE, password);
        }

        // Redirect the user to the original request URI (which will cause
        // the original request to be restored)
        requestURI = savedRequestURL(session);
        if (debug >= 1)
            log("Redirecting to original '" + requestURI + "'");
        if (requestURI == null)
            hres.sendError(HttpServletResponse.SC_BAD_REQUEST,
                           sm.getString("authenticator.formlogin"));
        else
            hres.sendRedirect(hres.encodeRedirectURL(requestURI));
        return (false);

    }

Looks like this could be tidied up considerably to decrease the number of 
loginAction checks, but hopefully the changes above will resolve the problem.

--
To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>