grails-spring-security-core icon indicating copy to clipboard operation
grails-spring-security-core copied to clipboard

SecurityObject getting pinned in memory, allowing future requests to run without needing any credentials.

Open tircnf opened this issue 4 years ago • 0 comments

If someone makes the mistake of trying to access springSecurityService.principal during grails bootstrap (which will be null), when a user finally logs in their security object gets pinned into memory.

The next 5-10 requests will use the previous users credentials, even if no bearer token or JSESSIONID is provided.

Here is the output of a script showing the problem. I access a protected resource and get rejected (correct behaviour), then I log in, and try and get the resource again, but don't send a bearer token. The system lets me in and thinks I am the logged in user.

https://github.com/tircnf/springSecurityRest

export PORT=8085

## correctly rejects.
$ curl "http://localhost:$PORT/app/whoami"  | jq .
{
  "timestamp": 1604984735418,
  "status": 401,
  "error": "Unauthorized",
  "message": "No message available",
  "path": "/app/whoami"
}

## log in
$ curl "http://localhost:$PORT/api/login" -H 'Content-Type: application/json'  --data '{"username":"admin","password":"pass"}'  | jq .
{
  "username": "admin",
  "roles": [
    "admin"
  ],
  "token_type": "Bearer",
  "access_token": "eyJhbGciOiJIUzI1NiJ9.eyJwcmluY2lwYWwiOiJINHNJQUFBQUFBQUFBSlZTTVVzY1FSUitlN2xENFNDZUFRTVdwb25wWkE5TWVWVVVEY2hpQXBkckZBSnp1ODkxZEhabW5Kazk5eHE1S2lrc0xpUVJBdmtMXC9wT2t5UThRTGRKYXB3dDVzM3J1Slkxa3F1RzliNzd2ZTkrYjgydG9XQVBQVThPNHNLRVdlY3BsYUxYaE1yVVk1NGE3WVpoYk5BbTZFdkd5QlBhb0FqY25xRUVRUVkwbkRoNUZCMnpBMm9MSnRQMnFmNEN4NnhRR1ZwVkpieG4zRE12d1dKbkQ4STQ3VmdiXC9FcWlvZzk4MW1ObUJlUmJIS3BkdVc4bU5Rbk9EeVE2MHFscWs0a05mV29pcGc5SnhKdXcwZEFZbDZ3dE1JbWl5M08wclV1Vm9IY3pkbU0wZEYrMHV1azRFczVwWlMrNyttYVRydkhYZjl6WWxUWEFFSjFBdmRFQ0hzbnZtb2FIbkNkZVZFRFExVjlJdTkyU21FcjdIdlRqeGo1WStcL2hoXC9IZlZxQUpUSnl2MXZxdnJpR295K3ZmMzFwQXc2aUIwOG5ySmV3VHFGSmpmekZmTWJnMTc1NHN2clQyZlg3M2Nma0xKSGJQN1wvUHBaZjNDWTNYRmVaWm9ZNU5iVWpvajJ1K3p1UnI5MVBQdG5DTU96eVRBdWtIeVVkSm5jU0ZUR05XemRLVFBKMjBHQkp4bVdoZmFGRmFnXC9MWWYyV3dralJqazVcL2Z2ZytmbnBKTDdlZ01XQWlSOHE2VllHMjg2eVA1dDM1MlZMejg5VnA2WHp5aVwvOEFKdnM4VlFrREFBQT0iLCJzdWIiOiJhZG1pbiIsInJvbGVzIjpbImFkbWluIl0sImlzcyI6IlNwcmluZyBTZWN1cml0eSBSRVNUIEdyYWlscyBQbHVnaW4iLCJleHAiOjE2MDQ5ODgzMzYsImlhdCI6MTYwNDk4NDczNn0.Hac1jvsvhDnPpqPZJgtGJqkMkYiK4LGV3AcmIbn6DPc",
  "expires_in": 3600,
  "refresh_token": "eyJhbGciOiJIUzI1NiJ9.eyJwcmluY2lwYWwiOiJINHNJQUFBQUFBQUFBSlZTTVVzY1FSUitlN2xENFNDZUFRTVdwb25wWkE5TWVWVVVEY2hpQXBkckZBSnp1ODkxZEhabW5Kazk5eHE1S2lrc0xpUVJBdmtMXC9wT2t5UThRTGRKYXB3dDVzM3J1Slkxa3F1RzliNzd2ZTkrYjgydG9XQVBQVThPNHNLRVdlY3BsYUxYaE1yVVk1NGE3WVpoYk5BbTZFdkd5QlBhb0FqY25xRUVRUVkwbkRoNUZCMnpBMm9MSnRQMnFmNEN4NnhRR1ZwVkpieG4zRE12d1dKbkQ4STQ3VmdiXC9FcWlvZzk4MW1ObUJlUmJIS3BkdVc4bU5Rbk9EeVE2MHFscWs0a05mV29pcGc5SnhKdXcwZEFZbDZ3dE1JbWl5M08wclV1Vm9IY3pkbU0wZEYrMHV1azRFczVwWlMrNyttYVRydkhYZjl6WWxUWEFFSjFBdmRFQ0hzbnZtb2FIbkNkZVZFRFExVjlJdTkyU21FcjdIdlRqeGo1WStcL2hoXC9IZlZxQUpUSnl2MXZxdnJpR295K3ZmMzFwQXc2aUIwOG5ySmV3VHFGSmpmekZmTWJnMTc1NHN2clQyZlg3M2Nma0xKSGJQN1wvUHBaZjNDWTNYRmVaWm9ZNU5iVWpvajJ1K3p1UnI5MVBQdG5DTU96eVRBdWtIeVVkSm5jU0ZUR05XemRLVFBKMjBHQkp4bVdoZmFGRmFnXC9MWWYyV3dralJqazVcL2Z2ZytmbnBKTDdlZ01XQWlSOHE2VllHMjg2eVA1dDM1MlZMejg5VnA2WHp5aVwvOEFKdnM4VlFrREFBQT0iLCJzdWIiOiJhZG1pbiIsInJvbGVzIjpbImFkbWluIl0sImlzcyI6IlNwcmluZyBTZWN1cml0eSBSRVNUIEdyYWlscyBQbHVnaW4iLCJyZWZyZXNoX29ubHkiOnRydWUsImlhdCI6MTYwNDk4NDczNn0.XU20LpoVHaHImVJKN9Mj7aWTNVuvUZMi5lnHFT319ek"
}

## incorrectly accepts, and thinks I am ADMIN, even though no token.
$ curl "http://localhost:$PORT/app/whoami"  | jq .
{
  "accountNonExpired": true,
  "accountNonLocked": true,
  "authorities": [
    {
      "authority": "admin"
    }
  ],
  "credentialsNonExpired": true,
  "enabled": true,
  "id": 1,
  "password": null,
  "username": "admin"
}

## incorrectly accepts, and thinks I am ADMIN,even though no token.
$ curl "http://localhost:$PORT/app/whoami"  | jq .
{
  "accountNonExpired": true,
  "accountNonLocked": true,
  "authorities": [
    {
      "authority": "admin"
    }
  ],
  "credentialsNonExpired": true,
  "enabled": true,
  "id": 1,
  "password": null,
  "username": "admin"
}

## incorrectly accepts, and thinks I am ADMIN, even though no token.
$ curl "http://localhost:$PORT/app/whoami"  | jq .
{
  "accountNonExpired": true,
  "accountNonLocked": true,
  "authorities": [
    {
      "authority": "admin"
    }
  ],
  "credentialsNonExpired": true,
  "enabled": true,
  "id": 1,
  "password": null,
  "username": "admin"
}


## finally the old user info is gone, and I get the correct 401.
$ curl "http://localhost:$PORT/app/whoami"  | jq .
{
  "timestamp": 1604984738334,
  "status": 401,
  "error": "Unauthorized",
  "message": "No message available",
  "path": "/app/whoami"
}


I've included the source code to the app on github: https://github.com/tircnf/springSecurityRest

It has a single secured controller exposing a "whoami" endpoint.

The "BUG" only happens if Bootstrap tries to get the principal object. I can't determine exactly when the credentials are finally removed. I think it is when the thread that processed the login finally comes around and processes another request.

Here are (I think) the relevant lines from application.groovy.


                //Stateless chain
		[
				pattern: '/**',
				filters: 'JOINED_FILTERS,-anonymousAuthenticationFilter,-exceptionTranslationFilter,-authenticationProcessingFilter,-securityContextPersistenceFilter,-rememberMeAuthenticationFilter'
		],

and Urlmappings.groovy "/app/$action?/$id?(.$format)?"(controller: "app")

and the Controller code.

@Secured('isAuthenticated()')
class AppController {
    @SuppressWarnings('unused')
    static responseFormats = ['json', 'xml']

    SpringSecurityService springSecurityService

    def whoami() {
        def principal = springSecurityService.getPrincipal() ?: [message: "No principal defined"]
        render principal as JSON
    }

}

This code is all brand new to me, but the issue might be around line 114 of RestAuthenticationFilter.groovy. It stores the access token in a Static Holder. I think that trying to access the principal object from Bootstrap might be interacting with that context in a weird way.

tircnf avatar Nov 10 '20 05:11 tircnf