Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
tircnf opened this issue Nov 10, 2020 · 0 comments

Comments

@tircnf
Copy link

tircnf commented Nov 10, 2020

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant