Python gems to look out for
Appsec / June 29, 2022 • 2 min read
Tags: python
A few weeks ago I was looking into Python specific code patterns that would lead to vulnerabilities. I was surprised when I found a few patterns that I hadn’t really thought about, most likely because I never write Python code like the examples I found. Nevertheless, I learned something new and thought I share it here.
Example One
Passing an untrusted string to an f-string
while passing a dict as an argument to the logger, may give the attacker the possibility to read keys in the dict that should not be readable.
1import logging
2
3logging.basicConfig(level=logging.INFO)
4logger = logging.getLogger(__name__)
5
6untrusted_string = "%(secret)s"
7some_dict = {"foo": "bar", "secret": "mysecret"}
8logger.info(f"Case one: '{untrusted_string}' and '%(foo)s'", some_dict)
9
10""" Outputs:
11INFO:__main__:look: 'mysecret' and 'bar'
12"""
Of course it assumes the attacker knows what keys are in the dict
. Note that this example does not work with normal print()
statements.
Example Two
This is an interesting one. Using %(var)s
method to reference variables in a string will parse \n
as newlines. If used in a logging context, you poison the log by “injecting” new log items. If normal f-strings
or .format()
were used, it wouldn’t work.
1import logging
2
3logging.basicConfig(level=logging.INFO)
4logger = logging.getLogger(__name__)
5
6
7context = {"user": "admin", "msg": 'hello everybody.\nINFO:__main__:user \'alice\' commented: \'I like pineapple pizza\'', "secret": "1337"}
8logger.info("Case Two: user '%(user)s' commented: '%(msg)s'.", context)
9
10""" Outputs
11INFO:__main__:user 'admin' commented: 'hello everybody.
12INFO:__main__:user 'alice' commented: 'I like pineapple pizza''.
13"""
Example Three
This one is pretty clever, though I have never seen it in the wild nor written anything like it myself. But if the format string itself is controlled by the user, you can of course reference variables within __globals__
and read their values.
1THESECRET = "gibson"
2
3class Author(object):
4 def __init__(self) -> None:
5 self.name = "Ellis"
6
7def format_me(format_string: str, a):
8 return format_string.format(a)
9
10
11a = Author()
12# Outputs: <class '__main__.Author'>
13print("Current class is: " + format_me("{0.__class__}", a))
14# prints: gibson
15print("The secret is: " + format_me("{0.__init__.__globals__[THESECRET]}", a))
I haven’t confirmed it yet, but I have a suspicion that remote code execution could be achieved in this specific scenario. Maybe an exercise for the reader? :)
Credits
Thank you Arie Bovenberg for teaching me about these fine python gems :)