slimphp / Slim

Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs.
http://slimframework.com
MIT License
11.94k stars 1.95k forks source link

PHP Object Injection Vulnerability in SessionCookie.php #1034

Closed sarciszewski closed 9 years ago

sarciszewski commented 9 years ago

https://github.com/slimphp/Slim/blob/master/Slim/Middleware/SessionCookie.php#L127

Generally, it's a bad idea to blindly unserialize() user-controllable input.

https://www.owasp.org/index.php/PHP_Object_Injection

EDIT - for people who don't want to read the whole thread:

The SessionCookie class is not used by default, you have to actually write your application to use it. So this means that the unserialize() -> RCE possibility is only for the select few apps that explicitly use this feature. The default is the native session driver, which is of course not vulnerable.

4nd commented 9 years ago

Could be worth changing this class to store data in JSON format?

sarciszewski commented 9 years ago

Why are you storing session state in the hand of the client in the first place?

I'd hate to be self-referential, but see similar work I've done for Kohana and CodeIgniter: https://scott.arciszewski.me/research/view/php-framework-timing-attacks-object-injection

They had a similar problem, except they were using hash functions (poorly) to detect tampering. It's still a bad idea. Use server-side storage:

Cookies are doom.

4nd commented 9 years ago

Totally agree, but some people may need them for some reason. They're good for long-term client-side configuration storage, no need for objects though, a key/value pair should be enough. Definitely not for use with secure data.

sarciszewski commented 9 years ago

Totally agree, but some people may need them for some reason.

Okay. Who needs them and what are they doing that requires such a poorly designed feature? PHP supports file storage out of the box.

People are going to use sessions for secure data. ['user_id' => 92, 'is_admin' => false] Hmm, gee, I wonder how I could exploit that?

mattsah commented 9 years ago

@sarciszewski

How do you feel about storing such data in a cookie as an encrypted JSON Web Token?

sarciszewski commented 9 years ago

NO

https://github.com/slimphp/Slim/issues/1035 - I do not trust the framework authors to implement encryption properly after reading Slim\Http\Util

codeguy commented 9 years ago

@sarciszewski But how do you really feel? I agree earlier versions should not persist session data client side. This will be changed in 3.x. You can also review the latest encryption strategies in \Slim\Crypt in the develop branch.

sarciszewski commented 9 years ago

https://github.com/slimphp/Slim/blob/develop/Slim/Crypt.php#L128

http://php.net/manual/en/function.hash-equals.php

codeguy commented 9 years ago

@sarciszewski Please review the develop branch \Slim\Crypt class and let me know if you have any feedback. I want it peer reviewed as much as possible. It's already had a lot of eyes on it, but one more won't hurt. Thanks!

joepie91 commented 9 years ago

@4nd

Totally agree, but some people may need them for some reason.

Like what reasons, for example?

They're good for long-term client-side configuration storage, no need for objects though, a key/value pair should be enough.

That's what non-session cookies and local storage are for. This does not belong in a session, end of story.

mattsah commented 9 years ago

@sarciszewski

I wasn't necessarily referring to the context of slim. https://github.com/firebase/php-jwt/blob/master/Authentication/JWT.php uses PHP's built in hmac and openssl stuff. Why wouldn't one just use a third party library? It becomes as simple as, pass it data to JWT encode, pass it a key for encryption.

Not sure I follow your concern. You seem to suggest they use someone else's php encryption library to solve that case. Wouldn't that be fine for JWT?

joepie91 commented 9 years ago

@mattsah Why are you trying to store session data client-side to begin with?

EDIT: Sorry, mistook you for @codeguy - you are not a contributor, I guess?

mattsah commented 9 years ago

@joepie91

As I believe was already covered, it does make things more scalable. That is, I think I'd agree with the principle as described in REST, and the client should contain the necessary state to succeed in a given request. Sessions blur this, and if the session is removed, the client state is affected irreversibly.

This is true for memcache as well. It scales horizontally, but the client application state is still dependent on the state of the server... clear memcache, and the client is suddenly logged out.

EDIT: Even if you mistook me, I agree that "session data" such as a login name can and should be stored client side. It's part of the client state. I agree, this means we need a means to do so securely, but the answer to that is not "move the client state to the server."

joepie91 commented 9 years ago

I don't really see how this makes it more "scalable". You may as well run a Redis cluster to keep track of sessions, if you're at the scale where you need multiple backend servers - statefulness doesn't change anything here.

And frankly, if you're running at that kind of scale, you don't need to rely on the default framework configuration, and can easily roll your own session handler - they are pluggable, after all. Hell, even if you can't, there's plenty other solutions like sticky sessions.

Right now, Slim is shipping in an insecure default for the majority of users, in order to solve an effectively non-existent problem that is an edge case at best. People are not going to read that caveat about secure storage, and cookie encryption is almost certainly going to cause issues later down the line. Not to mention the size limits that are going to bite people.

TL;DR: It makes absolutely no sense to implement this in a framework like this.

mattsah commented 9 years ago

@joepie91

It makes it more scalable for the same reasons redis/memcache/session handler does in one sense, but also makes it more scalable in the sense that client state can be provided to other systems or services which may not be tied into the back end state. Sure, it's easy to say, "just tie them into the backend state," but that doesn't seem to really address the issue.

What you're saying is, X service now has to rely on some additional backend component in order to verify client state. Not only might that be completely unnecessary for some services (you're only making it necessary by mixing server/client state), it obviously increases the requisite resources to make that service usable.

When the client state is stored client side and passed in the request, any service, no matter how disconnected, has everything it needs to complete the request. It's really that simple.

mattsah commented 9 years ago

@joepie91

I should probably note, I'm not necessarily disagreeing with you with respect to slim. I'm more making the point on a broader architectural level.

I would agree that at that scale you're going to be doing custom stuff anyway, and this may not be necessary out of the box, and as implemented, causes all sorts of security concerns for the 90%.

joepie91 commented 9 years ago

but also makes it more scalable in the sense that client state can be provided to other systems or services which may not be tied into the back end state.

Cookies are hostname- and port-specific, so that will never be directly possible. Data that you want to share between systems should be explicitly shared (even when going through the client) - you can use things like local storage to keep track of this.

There's a fundamental distinction between 'trusted data' and 'shared data', and the latter simply doesn't belong in session data. Non-session cookies or local storage are a much better choice for that.

mattsah commented 9 years ago

Cookies are hostname- and port-specific, so that will never be directly possible.

This is irrelevant with respect to some hierarchical architectures or even more advanced proxies.

There's a fundamental distinction between 'trusted data' and 'shared data', and the latter simply doesn't belong in session data.

I think you meant the former simply doesn't belong in client data. But that's an obviously absurd notion for every single application architecture aside from web applications. The only reasons it's accepted in web applications is because of arguments like the one you're making, namely that there's just no way to do it securely.

All I'm saying is, the "how do we do this securely?" is a separate question from "why/when/should someone do this?"

There are cases for the latter, which means we need solutions for the former. You seem to be saying that there are no solutions for the former, thus, we should do whatever it takes to avoid the latter. You've got your problem solving hat on backwards.

All I was initially asking in this thread, was whether or not @sarciszewski felt JWT was a relatively secure method to store session data client side (regardless of mechanism, cookie, local storage, etc). His answer was NO, but it seemed specific to separate concerns in slim, which didn't really answer the question.

sarciszewski commented 9 years ago

I said "NO" because you shouldn't be storing session state that the server processes and depends on, on client machines. An identifier to fetch it from the backend? Sure. Even with JWT, you're going to run head-first into the 4KB limit (which is down to 3 KB with base64 encoding, and even less with the HMAC).

You're literally cutting yourself off at the knees with this strategy.

Want to make everyone insecure because a few morons think storing session state on the client is okay? Do what you want; I look forward to seeing your spools on Full Disclosure.

joepie91 commented 9 years ago

I think you meant the former simply doesn't belong in client data.

No, I meant it doesn't belong in session data, exactly as stated. You can keep state beyond session data, including on the client.

All I'm saying is, the "how do we do this securely?" is a separate question from "why/when/should someone do this?"

I still have to see them. Can you name one for me?

mattsah commented 9 years ago

@sarciszewski

I said "NO" because you shouldn't be storing session state that the server processes and depends on, on client machines.

This sounds more like a lack of proper deliniation between server state vs. client state. Based on the source you cited in OP, I'd agree. I was moreso referring to...

An identifier to fetch it from the backend? Sure.

So it sounds like you're OK with this, if implemented in a secure fashion.

Even with JWT, you're going to run head-first into the 4KB limit (which is down to 3 KB with base64 encoding, and even less with the HMAC).

In this specific case, I'd think that would depend on how much you're storing in session, which again, may be an issue of not properly dilineating.

Want to make everyone insecure because a few morons think storing session state on the client is okay?

No. But I agree that is what this particular example does.

sarciszewski commented 9 years ago

So it sounds like you're OK with this, if implemented in a secure fashion.

Yes, like PHPSESSID over HTTPS (assuming TLS v1.2). That's a secure implementation.

mattsah commented 9 years ago

@joepie91

No, I meant it doesn't belong in session data, exactly as stated.

Then (as with my previous comment to @sarciszewski), this sounds more like a deliniation issue.

I still have to see them. Can you name one for me?

I have no idea what this means in the context of the quote you quoted. Can I name one what? Are you asking if I can show you a specific example of an application running at that level of scale which benefits from client state actually being stored in the client? No. I don't know of a single one that's open source. Do they exist? Obviously, there were plenty of examples when I worked in Motorola.

joepie91 commented 9 years ago

@mattsah Apologies, mis-quoted. Quote should have included:

There are cases for the latter, which means we need solutions for the former.

I'd like to see one of those cases.

mattsah commented 9 years ago

@sarciszewski

Yes, like PHPSESSID over HTTPS (assuming TLS v1.2). That's a secure implementation.

This is an avoidance of the issue, not an addressing of it. Again, no clear delineation between client state and server state. They're on one extreme (all server state is client state) and you're on the other (all client state is server state).

Specifically, I'm talking about storing a user token with information as to which user the client is authenticated (or even whether they are at all).

mattsah commented 9 years ago

@joepie91

I believe I responded to your statement as if that's what you were responding to. So, no, I have no publicly visible examples. They may exist, but I don't know every enterprise level web application that is open source. Maybe Bacula?

sarciszewski commented 9 years ago

Specifically, I'm talking about storing a user token with information as to which user the client is authenticated (or even whether they are at all).

Congratulations, you just reinvented PHP's session storage.

mattsah commented 9 years ago

Congratulations, you just reinvented PHP's session storage.

Sessions are stored serverside.

sarciszewski commented 9 years ago

Yes, the data is stored serverside, but the identifier is stored in a cookie.

mattsah commented 9 years ago

Yes, the data is stored serverside, but the identifier is stored in a cookie.

The identifier is useless to a system which doesn't have access to the serverside information. I'm not sure what you don't understand about this principle. It's called client state for a reason. Perhaps you disagree that which user a client is authenticated as or whether they're authenticated at all is not part of client state?

Can you explain what use the server has for it without the client?

sarciszewski commented 9 years ago

The identifier is useless to a system which doesn't have access to the serverside information.

Yeah, that's kind of the point. Do I need to draw you a diagram of the OSI model?

joepie91 commented 9 years ago

I believe I responded to your statement as if that's what you were responding to. So, no, I have no publicly visible examples. They may exist, but I don't know every enterprise level web application that is open source. Maybe Bacula?

Okay, what about Bacula makes it so that a Redis server for keeping track of session state is unsuitable?

If you do not have an example, then I wonder how you could've come to this kind of conclusion. What data points exactly are you using to determine that client-stored session state is a necessity for scaling?

padraic commented 9 years ago

Before the debate goes crazy... The value drawn from the cookie should be validated. That's even more basic than not passing user values to unserialize() or having all security related functionality audited. You can't just rely on one defense and ignore all the others, they should be layered in shells to afford maximum protection, i.e. Defense In Depth.

mattsah commented 9 years ago

Okay, what about Bacula makes it so that a Redis server for keeping track of session state is unsuitable?

Well, for starters I said maybe bacula. Bacula is not a web (HTTP) based application. I think the vast majority of examples you would find are not in web based applications for reasons already cited. That said, HTTP clients generally don't have a good mechanism for this, but that's a flaw in HTTP clients rather than a flaw in the design principle.

I'm not familiar enough with what Bacula does on subsequent requests, but initially, client authorization is stored client side in the configuration for the client. I'd be somewhat similar to your password for a website being stored by your browser and just sent to it, rather than some negotiation phase.

But you don't need to look at enterprise software to see this principle in action. A simple IM client stores your password locally and is sent to the server. Whether they generate a token from that for subsequent requests is irrelevant, so long as the token is all that's needed to verify the user regardless of server state, or if it's not the token can be renegotiated based on the stored password.

Again, you're talking about a problem that is essentially non-existent and is assumed solved in every other application architecture other than web applications.

The reason I bring up bacula is that there are multiple components, all, which in theory can be hosted independently.

If you do not have an example, then I wonder how you could've come to this kind of conclusion.

I do have examples (with respect to the web), as I already mentioned, the examples that I'm immediately or intimately familiar with are not open source. I came to this conclusion based on understanding the principles outlined in Martin Fowler's work and real world experience working with large enterprises which provide hierarchical or horizontally scaled services to common clients.

What data points exactly are you using to determine that client-stored session state is a necessity for scaling?

Necessary? I don't think I ever claimed it was necessary for scaling. You can almost always scale in other ways. What this particular principle of REST ensures is that your scalability is not limited by other things like network architecture or amounts of hardware.

If your client token always has to be sent to the same central service (note, not server), then that becomes your bottle neck to scalability. You can't add services to handle additional tasks without scaling your central authentication service as well. If you're getting 5000 requests a minute that need to be authenticated, and you wish to add a service which is used roughly in parallel to your to your primary service which also requires authentication, you've now got 10,000 requests a minute to your service providing authentication.

Can you do this? Of course. Can you just beef up the service that provides authentication? Sure. Can you horizontally scale that service using redis or memcache? Sure.

Should you have to scale that service at all in order to scale using other services? NO

That's the point of scalability with respect to this particular REST principle.

I'm really not sure why you find this so difficult to understand.

mattsah commented 9 years ago

I should note, you can also bypass the service directly and go to redis/memcache, which would avoid 5000 requests additional on the service, however, still results in 5000 additional requests per minute to your redis/memcache server, you just move the problem, albeit to one that may be more likely able to handle it than something wrapping redis/memcache with PHP.

mattsah commented 9 years ago

Also, you need to refer to it as "client stored session state" is rather strange. "Session state" in most software architectures is necessarily client side. The idea of a server side session is meaningless without the client. The fact that PHP has and most PHP developers continue to store client state in server-side sessions doesn't make the phrase more valid.

Which user a client is authenticated as or can authenticate as is client state.

Same question to you @joepie91, can you explain to me what use the server has for this information independent of the client?

ircmaxell commented 9 years ago

Storing session state on the client is 100% fine. In fact, for many apps it's not necessary to store anything non-trivial on the server side.

The issue here is using serialize, not where the data is stored...

ffflabs commented 9 years ago

I agree with @ircmaxell. We wouldn't be discussing any of this if SessionCookie middleware used json_decode to retrieve the key/value pairs of the encrypted cookie.

The cookie is encrypted to avoid someone tampering with its values, but it isn't meant to hold anything besides your logged status and user_id.

Sending this hash on each request, and having the server unencrypt it to know who you are is just as secure as sending an OAuth token to an API backend.

Sure, you can tamper with an OAuth token just as easily as you can tamper with a cookie. Hell, you might send whatever token you want and if you succeed in forging a token that amounts for another person you can impersonate them. Because that's how it works, actually.

sarciszewski commented 9 years ago

The cookie is encrypted to avoid someone tampering with its values

I still see a problem here. Encryption is not authentication. Since the session state is in the hands of the client, they get unlimited tries. It takes an average of 128 tries (worst case: 256) per byte to break CBC and forge the contents. Encrypt-Then-MAC or doom.

Sending this hash on each request,

Wait, is it encrypted or hashed?

@ircmaxell I see your point. If they can implement JWT competently (my history with other frameworks reinvention the session wheel has not made me optimistic about this) this issue can be considered closed.

mattsah commented 9 years ago

@ircmaxell

Yes. That's a good assessment, given that an actual compromise using unserialize() could result in arbitrary code execution on __wakeup() if I'm not mistaken.

sarciszewski commented 9 years ago

an actual compromise using unserialize() could result in arbitrary code execution on __wakeup() if I'm not mistaken.

Yeah, that's the crux of this issue.

mattsah commented 9 years ago

@sarciszewski just a point to note and for the slim team, JWT still requires separate encryption, initially it's just a hash with signature if I'm not mistaken. The data itself still needs to be encrypted.

sarciszewski commented 9 years ago

@mattsah If you use Firebase's implementation rather than rolling your own, then I won't complain. (Instead, I'll go review that.)

EDIT: Their library looks good at a glance.

mattsah commented 9 years ago

@sarciszewski I'm not on the slim team. If you'd like to review Firebase's implemention, go for it, but please take note of the above with respect to encryption. Firebase, so far as I know is just supplying stock JWT which gets around the unserialize()/__wakeup() issue and offers some data integrity. Encryption is still needed.

sarciszewski commented 9 years ago

Well, the develop branch has adopted Zend\Crypt so it should be fine. I haven't reviewed that library at all but others have.

ffflabs commented 9 years ago

I still see a problem here. Encryption is not authentication. Since the session state is in the hands of the client, they get unlimited tries. It takes an average of 128 tries (worst case: 256) per byte to break CBC | and forge the contents. Encrypt-Then-MAC or doom.

@sarciszewski so basically the problems are

I agree on those too, but that has no relation whatsoever with the pros or cons of using user provided data (cookies, headers, query string or POST params) to provide a stateful app.

sarciszewski commented 9 years ago
4096
- 64 (HMAC-SHA-256, hex-encoded)
4032
* 3/4 (base64)
3024
- 2 ('a=' for example)

3022

If you're fine only being able to store up to 3022 bytes of information in your session state of information per user ever, then by all means keep at it.

I'd recommend adopting a more modular approach. Let people choose their session backend, don't lock them into the cookie-based one. If they're fine storing it the default PHP way, it should be trivial to do so.

EDIT: Also, please don't use compression to try to get around this. https://www.isecpartners.com/blog/2012/september/details-on-the-crime-attack.aspx

ffflabs commented 9 years ago

But Slim does by no means force you to use SessionCookie middleware. The default implementation uses native session storage.

sarciszewski commented 9 years ago

The default implementation uses native session storage.

That wasn't obvious to me when I was looking at the code.

ffflabs commented 9 years ago

This is the part of the documentation that describes it.