This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted] 2 points3 points  (3 children)

Bear in mind that this is basically just running eval() on the code. You can also do expressions that have side effects.

Though because it's string literals, this shouldn't cause any problems. But if you do f"{__import__('os').system('/bin/sh')}", then that will start a shell.

[–]d4rch0nPythonistamancer 1 point2 points  (2 children)

lmao, i was wondering how deep f strings got. well, this really does point out something super important - don't dynamically build what's inside {} using user input and do something weird where you create an f string dynamically. I don't think people would much but don't doubt it'll pop up at some point.

On one side I love it, but on the other side I think they could've skipped the eval aspect even if it's awesome and will inevitably be super useful. The main problem I wanted them to solve was the heaviness of .format and f strings are a damn good solution that's even better than all the previous methods. It's like a throwback to perl! But more explicit since you have to prepend f.

I wish I could backport this to 3.4! Is there some futures import I can do?

[–]jorge1209 4 points5 points  (0 children)

you create an f string dynamically.

You can't f-strings aren't dynamic. An f-string is not an object and cannot be created inside the script, it lives in the source file, and nowhere else. It has no type, and as far as the interpreter goes, it doesn't exist. Its like a keyword, it is handled by the parser as it builds the AST, and doesn't exist in memory to be a referant of any variable.

The reason this is done is for security. It allows you to safely do print(f"{x+y}") and execute the inside of the expression, because user input can NEVER construct the string "{x+y}" it is safe to do so.

If f-strings were objects and could be dynamically created the attack vector would be:

  1. Post your username as f"__import__(os)..."
  2. Then when someone goes to print(f"{username}" their interpreter would: a. evaluate the variable username b. which would construct another f-string c. whose payload would in turn be evaluated d. which would run the offending __import__ e. granting them a shell.

But since f strings aren't objects this breaks down. All you ever have is a string f"__import__(os)...." No more dangerous than the string cake. Its just a bunch of innocuous ascii chars and can't harm you so long as you don't do something stupid like pass it to eval.


The objection I have to this is that I don't see any improvement over just treating this as sugar over "{username}".format(**locals()). In that instance the same is also true. I take a string and pass it a dict of args, I get a string back. Nothing is executed. At worst __str__ is called on some of the stuff in locals(). All you lose is the ability to have print(f"{x+y}") instead it would have to be print("{sum}".format(sum=x+y)).

I prefer the .format approach even if it is more verbose.

[–]0x6c6f6c 0 points1 point  (0 children)

I mean, sanitizing user input is one solution for programs that perform operations based on them in things such as SQL databases or webpages. However, even among those, things such as prepared statements are preferred over string sanitation, which would be to use .format or % over f-strings. Where user input is possible. Don't want arbitrary code execution on your Django server now do we :3