Live data from Hacker News

Be Careful with Python's New-Style String Format

lucumr.pocoo.org

71–80 of 155 posts

Re: Be Careful with Python's New-Style String Format

#71
post #9

No, Rust does not have the ability to access any variable in the program via a format string. Rust has this: 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.

> It should have used something with a limited list of named arguments, or maybe a dict.

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.

Re: Be Careful with Python's New-Style String Format

#72

Doesn't this apply to any language that has string interpolation, Ruby, Python, Javascript Perl, etc? And doesn't it not really matter, because it's not realistic to use a dynamic string template in a program?

Ruby format strings are like old-style Python format strings. They only map to names or positional arguments. Perl also just maps to positional arguments, like printf. The most you can do is mess up the string by moving around positional args.

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 :|

Re: Be Careful with Python's New-Style String Format

#73
post #18

Earlier quoted context omitted.

Shouldn't that be based not on the built-in string capabilities, but on a template engine, that must also take care for much more narrow cases of abuse (e.g. SQL injection, or access to only of a restricted number of helper functions within the template)?

The author discovered this exploit in the template engine he wrote (Jinja2).

I'll consider switching to a different engine.

Re: Be Careful with Python's New-Style String Format

#74

Earlier quoted context omitted.

What does Ruby have to do with any of this?

Like PHP and JS's template literals, it has full on string interpolation.

These are only in literals, however. You cannot have a string value with interpolation exprs inside it (both in JS and Ruby)

Re: Be Careful with Python's New-Style String Format

#75
Yeah, I never liked Python's "new-style" format because of this. It didn't occur to me that you could use field access to access globals (never done enough Python metaprogramming to mess with the reflection stuff), but I was afraid of arbitrary getters being invoked.

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.

Re: Be Careful with Python's New-Style String Format

#76
So is the issue that it's a problem to let the format string be controlled arbitrarily? It's good to warn users around it because some may not know the dangers, but in general you don't trust user input, so I don't really see this as something you need to build something custom around, rather just be careful and follow good practices. It should be understood not to pass user data directly to internal code without sanitizing it. Proposing some custom code that uses undocumented internal features is overkill and also dangerous since things that aren't documented/internal can change suddenly.

Re: Be Careful with Python's New-Style String Format

#77

Earlier quoted context omitted.

You can do the same thing in Java with reflection. Don't let users run untrusted code. Full stop. If you need templating use a sandbox like jinja2.

The discovered vulnerability manifested itself in jinja2. Edit: release notes https://www.palletsprojects.com/blog/jinja-281-released/

Exactly it's already fixed. It's in the article.

Re: Be Careful with Python's New-Style String Format

#78
post #11
post #4

Err, why would you allow for the user to enter arbitrary format strings in the first place? Might as well write "be careful about eval of arbitrary user provided strings".

Internationalization, usually. Word order differs between languages, and this kind of format allows reordering the inserted values.

A lot of folks do use lang format strings for i18n, but they really shouldn't. They won't be able to handle pluralization and other oddities.

Re: Be Careful with Python's New-Style String Format

#79

Sane Lisp approach: provide easily analyzable target syntax. 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 ca…

If you're LISP has first class environments, then this becomes much easier, and safer.

    (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.

Re: Be Careful with Python's New-Style String Format

#80
post #35

Earlier quoted context omitted.

Neither does python. 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)…

> Neither does python. I would argue that being able to access any global value is a sufficient enough concern.

The security issue here isn't with f strings, which oddly enough, don't have this flaw (because they are literals only). This has to do with new style string formatting, which does not have access to globals.
Post reply on HN