Skip to content

HttpFoundation: Deprecate Request::get #40984

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

Closed
flack opened this issue Apr 29, 2021 · 13 comments · Fixed by #42392
Closed

HttpFoundation: Deprecate Request::get #40984

flack opened this issue Apr 29, 2021 · 13 comments · Fixed by #42392

Comments

@flack
Copy link
Contributor

flack commented Apr 29, 2021

Symfony version(s) affected: all I think

Description

Request::get looks at the following data:

  • $this->attributes
  • $this->query
  • $this->request

in that order. So it checks $_GET before it checks $_POST. I haven't checked, but I think it might have been implemented this way because of a misunderstanding about how the ini setting request_order works: The default there is GP, but

Registration is done from left to right, newer values override older values.

so P (POST) takes precedence over G (GET)

and if you think about it, that is the only logical order, because the way Symfony does it, allows for trivial attacks by sending malicious links to users which completely override whatever they enter in a (POST) form.

How to reproduce

If the backend code uses (directly or indirectly) $request->get() and you have a POST form in the frontend, anyone can make a malicious link like /order?selectedproduct=ultra-deluxe-edition which completely overrides anything the user enters in a form, without them knowing. So for POST forms, $request->get() is outright dangerous.

If your frontend code uses a GET form, then your backend might as well just use $request->query->get(), because where is the POST data going to come from? The only way to get POST data in this scenario would be if the backend code adds it itself programmatically. But if the backend code does that, it might as well just add its stuff to $request->attributes or modify $request->query directly.

Possible Solution

There seems to be no scenario where Request::get can be used safely and does something useful. One way to fix this would be to invert the order, i.e. look at POST before looking at GET, but that would be a very hard to detect BC break. So it might be best to just get rid of the function completely

Additional context

See discussion in #40974

@derrabus
Copy link
Member

On the one hand: Using Request::get() is almost always a bad idea, so deprecating it will help me maintain my sanity as a code reviewer in the future.

On the other hand: Migrating a mature codebase away from that method is a huge PITA. Been there, done that, won't recommend. 🙈

@ro0NL
Copy link
Contributor

ro0NL commented Apr 29, 2021

what i like about this deprecation is it moves decision taking to the user's side (aka the "use instead" part)

A) i'll use the correct input source explicitly, B) i'll inline get() into a one-liner using nested $default args

Both acceptable to me, preferring A most likely :) overall i think it's worth forcing users to make the intentions more expressive.

@flack
Copy link
Contributor Author

flack commented Apr 29, 2021

maybe an interim step could also be to deprecate accessing POST in that function. i.e. add a deprecation notice here:

return $this->request->all()[$key];

Because like I said, I don't see how you could use this safely for POST forms

@nicolas-grekas
Copy link
Member

The method starts with the following disclaimer:

 * This method is mainly useful for libraries that want to provide some flexibility. If you don't need the
 * flexibility in controllers, it is better to explicitly get request parameters from the appropriate
 * public property instead (attributes, query, request).

And looking at the codebase, this method is exactly used for that: fetching a value either in the route, query or body.

Before considering deprecating, we should ensure that these use cases can be updated and that the change would make sense.

@flack
Copy link
Contributor Author

flack commented Apr 30, 2021

@nicolas-grekas Well the thing is that the disclaimer is wrong IMO: It doesn't provide flexibility, it just introduces uncertainty. Because making POST data overrideable by GET just opens you to all sorts of trouble from external parties

@nicolas-grekas
Copy link
Member

Please have a look at the codebase. The places where get() is used look aligned with the disclaimer to me.

@flack
Copy link
Contributor Author

flack commented Apr 30, 2021

I don't doubt that it is used correctly in this repo, I'm more worried about external code of varying quality. Who's to say that every dev using this function read and fully understood the disclaimer? The point is that it's just very easy to get it wrong and introduce vulnerabilities, so to me at least the function seems badly designed. If you want to keep it for use in this repo, maybe tag it as @internal, otherwise I think the disclaimer needs to be a lot stronger than it is now. E.g. something like this could be added:

 * Do not use this function to read data from POST forms! Doing so allows third parties 
 * to override the users' choices in the form by sending them specially crafted URLs.

It's weird that I have to keep repeating it, but to me this function seems like a security incident waiting to happen. Is nobody else worried about the abuse potential here? Am I missing something?

@nicolas-grekas
Copy link
Member

I would welcome a PR that removes using the method from the codebase. That could be a nice exercise to practice the proposal and get some more food for thoughts.

@flack
Copy link
Contributor Author

flack commented Apr 30, 2021

out of curiosity, I added xdebug_print_function_stack to the top of the get function and ran the test suite. It showed the following call sites:

src/Symfony/Component/HttpFoundation/Request.php:464
src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php:129
src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php:130
src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationSuccessHandler.php:105
src/Symfony/Component/Security/Http/ParameterBagUtils.php:75
src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php:89
src/Symfony/Component/Security/Http/Firewall/AbstractListener.php:26
src/Symfony/Component/Security/Http/HttpUtils.php:101
src/Symfony/Component/Security/Http/ParameterBagUtils.php:80

(take that list with a grain of salt though: I get tons of errors & failures when running the test suite)

Side question: does anyone know a static anaylzer that can find all call sites for a function? I'm looking for this for some other project, and it might come in handy here, too

@derrabus
Copy link
Member

Side question: does anyone know a static anaylzer that can find all call sites for a function?

PhpStorm? 🙃

@ro0NL
Copy link
Contributor

ro0NL commented Apr 30, 2021

Please consider different naming otherwise. We have +- 60 calls, and im totally not amused about it :D

@gggeek
Copy link

gggeek commented May 2, 2021

Plus one for deprecating that function. I agree that conflating get/post params can lead to subtle bugs, often with security implications, and should not be something so easily available to end developers. What's more, I always found it name confusing, as in my mind get() is too close to $_GET

@flack
Copy link
Contributor Author

flack commented May 2, 2021

aside from being overly long, I think a good name for this function would be getAttributeOrQueryOrRequest: It tells you exactly what it does and also kind of conveys why it's usually a bad idea to use it.

@fabpot fabpot closed this as completed Aug 6, 2021
fabpot added a commit that referenced this issue Aug 6, 2021
This PR was merged into the 5.4 branch.

Discussion
----------

[HttpFoundation] Mark Request::get() internal

| Q             | A
| ------------- | ---
| Branch?       | 5.4
| Bug fix?      | no
| New feature?  | no
| Deprecations? | yes
| Tickets       | Fix #40984
| License       | MIT
| Doc PR        | symfony/symfony-docs#... <!-- required for new features -->

we should not forget about this :)

Commits
-------

e84efc4 [HttpFoundation] Mark Request::get() internal
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants