Python 的 f 字符串实际上更安全。使用它们!
当格式字符串依赖于不受信任的数据时,字符串格式可能很危险。因此,在使用 str.format()or %-formatting 时,使用静态格式字符串或在应用 formatter 函数之前清理不受信任的部分非常重要。相比之下,f 字符串实际上并不是纯字符串,而更像是用于连接字符串和表达式的语法糖。因此,f-string 的格式是预先确定的,并且首先不允许动态(可能不受信任)部分。
旧式格式与 str.format()
>>> data_str = 'bob'
>>> format_str = 'hello {name}!'
>>> format_str.format(name=data_str)
'hello bob!'
在这里,您的 Python 解释器不知道数据字符串和格式字符串之间的区别。它只是调用一个函数 ,str.format()该函数在执行时对格式字符串值运行替换算法。因此,可以预期,格式只是一个带有花括号的普通字符串:
>>> import dis
>>> dis.dis("'hello {name}!'")
1 0 LOAD_CONST 0 ('hello {name}!')
2 RETURN_VALUE
使用 f 字符串的新型格式
>>> data_str = 'bob'
>>> f'hello {data_str}!'
'hello bob!'
在这里,f'hello {data_str}!'可能看起来像一个字符串常量,但它不是。解释器不会将之间{...}的部分解析为稍后可能扩展的字符串的一部分,而是作为单独的表达式:
>>> 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
因此,将 ."hi {sys.argv[1]}"视为(大约)语法糖"hi " + sys.argv[1]。在运行时,解释器甚至并不真正知道或关心您使用了 f 字符串。它只看到从常量"hi "和格式化值构建字符串的指令sys.argv[1]。
易受攻击的例子
这是一个str.format()以易受攻击的方式使用的示例 Web 应用程序:
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
攻击
构建字符串时res,它会self.path用作格式字符串的一部分。由于self.path是用户控制的,我们可以使用它来更改格式字符串,例如泄露全局变量secret:
$ curl -g 'http://localhost:8888/XXX{site.do_GET.__globals__[secret]}'
<title>/XXXabc123</title>
welcome to funtimes