Good instinct. Yes, an attacker being able to supply arbitrary format string is a vulnerability under python.

  • The denial of service is probably the most simple to address. In this case, limiting the size of the string or the number of operators within the string will mitigate this issue. There should be a setting where no reasonable user will need to generate a string with more variables than X, and this amount of computation isn't at risk of being exploited in a DoS attack.
  • Being able to access attributes within an object could be dangerous. However, I don't think that the Object parent class has any useful information. The object supplied to the format would have to contain something sensitive. In any case, this type of notation can limited with a regular expression.
  • If the format strings are user supplied then a user might need to know the error message for debugging. However, error mesages can contain senstive information such as local paths or class names. Make sure to limit the information that an attacker can obtain.

Look over the python format string specification and forbid functionality you don't want the user to have with a regex.

Answer from rook on Stack Overflow
Top answer
1 of 3
9

Good instinct. Yes, an attacker being able to supply arbitrary format string is a vulnerability under python.

  • The denial of service is probably the most simple to address. In this case, limiting the size of the string or the number of operators within the string will mitigate this issue. There should be a setting where no reasonable user will need to generate a string with more variables than X, and this amount of computation isn't at risk of being exploited in a DoS attack.
  • Being able to access attributes within an object could be dangerous. However, I don't think that the Object parent class has any useful information. The object supplied to the format would have to contain something sensitive. In any case, this type of notation can limited with a regular expression.
  • If the format strings are user supplied then a user might need to know the error message for debugging. However, error mesages can contain senstive information such as local paths or class names. Make sure to limit the information that an attacker can obtain.

Look over the python format string specification and forbid functionality you don't want the user to have with a regex.

2 of 3
4

This simple Formatter override blocks users from accessing attributes. It still allows formatting and conversion of types.

from string import Formatter

class SafeFormatter(Formatter):
    def get_field(self, field_name, args, kwargs):
        if not str.isidentifier(field_name):
            raise ValueError(
                f"Invalid format string, {field_name!r} is not a valid identifier"
            )
        return super().get_field(field_name, args, kwargs)

Usage example:

>>> safe_formatter = SafeFormatter()
>>> safe_formatter.format("Number {num} is id={id}", num=1, id="hello")
'Number 1 is hello'
>>> safe_formatter.format("Number {num} is {id.__class__}", num=1, id="hello")
Traceback (most recent call last):
  File "<python-input-3>", line 1, in <module>
    safe_formatter.format("Number {num} is {id.__class__}", num=1, id="hello")
    ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[...omitted...]
  File "<python-input-0>", line 6, in get_field
    raise ValueError(
        f"Invalid format string, {field_name!r} is not a valid identifier"
    )
ValueError: Invalid format string, 'id.__class__' is not a valid identifier
>>> 
🌐
Armin Ronacher
lucumr.pocoo.org › 2016 › 12 › 29 › careful-with-str-format
Be Careful with Python’s New-Style String Format | Armin Ronacher's Thoughts and Writings
December 29, 2016 - For as long as only C interpreter objects are passed to the format string you are somewhat safe because the worst you can discover is some internal reprs like the fact that something is an integer class above. However tricky it becomes once Python objects are passed in.
🌐
GeeksforGeeks
geeksforgeeks.org › vulnerability-in-str-format-in-python
Vulnerability in str.format() in Python | GeeksforGeeks
January 6, 2025 - In Python, the %s format specifier is used to represent a placeholder for a string in a string formatting operation. It allows us to insert values dynamically into a string, making our code more flexible and readable.
🌐
Python
docs.python.org › 3 › library › string.html
Common string operations — Python 3.14.6 documentation
While other exceptions may still occur, this method is called “safe” because it always tries to return a usable string instead of raising an exception. In another sense, safe_substitute() may be anything other than safe, since it will silently ignore malformed templates containing dangling delimiters, unmatched braces, or placeholders that are not valid Python identifiers.
🌐
Dpex77
dpex77.github.io › website › format-string.html
Format String Vulnerability in Python
user_input = input("Enter your name: ") print(f"Hello {user_input}") # Safe print("Hello {}".format(user_input)) # Safe Avoid eval() on untrusted input—this is the only Python operation that can execute user-provided code. Python’s string formatting is inherently safe when used properly.
Find elsewhere
🌐
Python
peps.python.org › pep-3101
PEP 3101 – Advanced String Formatting | peps.python.org
The new, global built-in function ... format(value, format_spec): return value.__format__(format_spec) It is safe to call this function with a value of “None” (because the “None” value in Python is an object and can have ...
🌐
Real Python
realpython.com › python-string-formatting
Python String Formatting: Available Tools and Their Features – Real Python
December 2, 2024 - In this tutorial, you'll learn about the main tools for string formatting in Python, as well as their strengths and weaknesses. These tools include f-strings, the .format() method, and the modulo operator.
🌐
Medium
mehedi-khan.medium.com › python-string-formatting-best-practices-4765104bc243
Python string Formatting Best Practices 🧑🏽‍💻💻 | by Mehedi Khan | Medium
February 15, 2025 - F-strings are concise and offer better readability. Use them whenever possible, especially in Python 3.6 and above. When using str.format(), consider using named placeholders for improved readability.
Top answer
1 of 1
4

No, it is not safe in general to use str.format with user-provided format strings.

Format strings are capable to executing a limited form of Python code. This limited code is just powerful enough to pose significant denial of service (DOS) and data breach risks.

The main factors that make using untrusted format strings risky are:

  1. There's no telling how large the formatted string will be. Even short format strings can lead to massive expansions, posing DOS risks.
  2. Python's string formatting syntax is actually quite rich, and notably supports arbitrary indexing and attribute access on the format arguments. This means that you have to worry not only about the objects that you are passing to str.format, but also about every object that is indirectly accessible from those objects via indexing and attribute lookups. It turns out that the space of indirectly accessible objects is very large.
  3. Performing indexing or attribute access operations can trigger a larger number of different special methods. These triggered methods can have unintended side effects or raise unexpected exceptions.

Data Breach Risks

Off the top of my head, I came up with this toy example of how str.format with a carefully constructed format string can easily leak sensitive information from your server. This attack only relies on the attacker knowing (or guessing) the of types of the arguments being supplied to str.format.

Running this code will print all environment variables defined on the server:

import os

class Foo:
    def foo(self):
        pass

user_format = "{f.foo.__globals__[os].environ}"
print(user_format.format(f=Foo()))

Specific to Django, this format string will print your entire settings module, complete with your DB passwords, signing keys, and all sorts of other bits of exploitable information:

user_format = "{f.foo.__globals__[sys].modules[myapp.settings].__dict__}"

Denial of Service Risks

Simple format strings like "{:10000000000}" can lead to massive memory consumption. This can grind things to a halt due to constant page swapping, or even crash the server process with a MemoryError exception or OOM kill.

Additionally, due to the sheer number of different objects that a format string can interact with, and the variety of special methods that can be called on those objects, there's no telling what sort of other weird and unexpected exceptions could be raised or what resource intensive operations that may be performed. You can imagine a scenario where an ORM manager class has a property that triggers a database transaction when accessed. Such a property could be repeatedly accessed in the format string to stall the server with database interactions.

Top answer
1 of 4
22

Python's f-strings are actually safer. Use them!


String formatting may be dangerous when a format string depends on untrusted data. So, when using str.format() or %-formatting, it's important to use static format strings, or to sanitize untrusted parts before applying the formatter function. In contrast, f-strings aren't actually plain strings, but more like syntactic sugar for concatenating strings and expressions. As such, an f-string's format is predetermined and doesn't allow dynamic (potentially untrusted) parts in the first place.

Old-style formatting with str.format()

>>> data_str = 'bob'
>>> format_str = 'hello {name}!'
>>> format_str.format(name=data_str)
'hello bob!'

Here, your Python interpreter doesn't know the difference between a data string and a format string. It just calls a function, str.format(), which runs a replacement algorithm on the format string value at the moment of execution. So, expectedly, the format is just a plain string with curly braces in it:

>>> import dis
>>> dis.dis("'hello {name}!'")
  1           0 LOAD_CONST               0 ('hello {name}!')
              2 RETURN_VALUE

New-style formatting with f-strings

>>> data_str = 'bob'
>>> f'hello {data_str}!'
'hello bob!'

Here, f'hello {data_str}!' may look like a string constant, but it's not. The interpreter doesn't parse the part between {...} as part of the string that may be expanded later, but as a separate expression:

>>> dis.dis("f'hello {name}!'")
  1           0 LOAD_CONST               0 ('hello ')
              2 LOAD_NAME                0 (name)
              4 FORMAT_VALUE             0
              6 LOAD_CONST               1 ('!')
              8 BUILD_STRING             3
             10 RETURN_VALUE

So, think of "hi {sys.argv[1]}" as (approximately) syntactical sugar for "hi " + sys.argv[1]. At run time, the interpreter doesn't even really know or care that you used an f-string. It just sees instructions to build a string from a the constant "hi " and the formatted value of sys.argv[1].

Vulnerable example

Here is a sample web app which uses str.format() in a vulnerable way:

from http.server import HTTPServer, BaseHTTPRequestHandler

secret = 'abc123'

class Handler(BaseHTTPRequestHandler):
    name = 'funtimes'
    msg = 'welcome to {site.name}'
    def do_GET(self):
        res = ('<title>' + self.path + '</title>\n' + self.msg).format(site=self)
        self.send_response(200)
        self.send_header('content-type', 'text/html')
        self.end_headers()
        self.wfile.write(res.encode())

HTTPServer(('localhost', 8888), Handler).serve_forever()
$ python3 example.py

$ curl 'http://localhost:8888/test'
<title>/test</title>
welcome to funtimes

Attack

When the res string is built, it usesself.path as part of the format string. Since self.path is user-controlled, we can use it to alter the format string and e.g. exfiltrate the global variable secret:

$ curl -g 'http://localhost:8888/XXX{site.do_GET.__globals__[secret]}'
<title>/XXXabc123</title>
welcome to funtimes
2 of 4
4

If this basic language feature were this flawed, it probably wouldn't be a feature at all. As long as the contents of the format string are controlled by the programmer at development time, there is nothing that a user can do to abuse them.

The contents of the curly braces are evaluated, but the result of that evaluation is not evaluated again (i.e. sys.argv[1] is evaluated to "1+1", but is not evaluated again, like you have seen).

The problem arises when a user is able to to inject data into a string before it is formatted; see this challenge example. While this is not for an f-string, it is a good demonstration of the attacks that are possible if the user is allowed to control the formatting.

🌐
PyFormat
pyformat.info
PyFormat: Using % and .format() for great good!
With this site we try to show you the most common use-cases covered by the old and new style string formatting API with practical examples. All examples on this page work out of the box with with Python 2.7, 3.2, 3.3, 3.4, and 3.5 without requiring any additional libraries.
🌐
Stack Exchange
security.stackexchange.com › questions › 269949 › is-format-string-still-an-issue-in-python
Is format string still an issue in Python? - Information Security Stack Exchange
format string vulnerability is about using a format string (in your case Job ID: {}) which includes untrusted user input. Your example does not do this, i.e. your format string is a static string hard coded in the program. This is explained in more detail in Are there any Security Concerns to using Python F Strings with User Input
🌐
Towards Data Science
towardsdatascience.com › home › latest › python template string formatting method
Python Template String Formatting Method | Towards Data Science
January 21, 2025 - It has a syntax somewhat similar to .format() when done with keywords, but instead of curly braces to define the placeholder, it utilises a dollar sign ($). ${} is also valid and should be in place when a valid string comes after the placeholder. See the syntax for various situations. I’ll begin explaining safe_substitute and the use with regex.
🌐
ActiveState
code.activestate.com › recipes › 534139-enhanced-safe-string-formatting
Enhanced safe string formatting « Python recipes « ActiveState Code
October 29, 2007 - "%(())s", "%(()())s", "%((()))s", but Python's regex engine cannot match nested constructs. This quirky behaviour is not preserved: "%s %(abc)s" % {"abc":10} ==> "{'abc': 10} 10". The trailing "b" in the function name safmtb() means 'base' implementation, with the expectation that the safmt() wrapper will be preferred. ... Some ideas for enhancement: 1) Optimise if args or kw empty: search only for positional or named 2) Check for huge field widths, and limit for safety 3) Return information about how many pos+named args were used, or missing 4) Optimise if only kw given, by constructing a regex to only find given names 5) Take a callback function(stanza, num_args|name), implement PEP3101 using % syntax 6) Allow parameterised named formatting e.g.
🌐
Python
docs.python.org › 3.4 › library › string.html
6.1. string — Common string operations — Python 3.4.10 documentation
Like substitute(), except that if placeholders are missing from mapping and kwds, instead of raising a KeyError exception, the original placeholder will appear in the resulting string intact. Also, unlike with substitute(), any other appearances of the $ will simply return $ instead of raising ValueError. While other exceptions may still occur, this method is called “safe” because substitutions always tries to return a usable string instead of raising an exception.