Skip to content

Commit 396b744

Browse files
Simplify format string example
We don't need to create lots of code, and in fact, it's often better to switch to a constant string if we can. Let's show doing that, instead of delving into complications that are often unnecessary. Signed-off-by: David A. Wheeler <[email protected]>
1 parent 11cb3dc commit 396b744

File tree

2 files changed

+74
-57
lines changed

2 files changed

+74
-57
lines changed

docs/labs/format-strings.html

Lines changed: 45 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,45 @@
99
<script src="checker.js"></script>
1010
<link rel="license" href="https://creativecommons.org/licenses/by/4.0/">
1111

12-
1312
<script id="expected0" type="plain/text">
14-
return '{format_string}, {event.level}'.format(format_string=user_input, event=event)
13+
def format_event(new_event):
14+
return '{event.level},{event.message}'.format(event=new_event)
1515
</script>
16+
<!--
17+
return '{event.level},{event.message}'.format(event=new_event)
18+
-->
1619

1720
<!-- Full pattern of correct answer -->
18-
<script id="correct0" type="plain/text">
19-
\s*return\s'\{format_string\},\s\{event\.level\}'\.format\(format_string=user_input,\sevent=event\)\s*
21+
<script id="correct0" type="plain/text"
22+
>(\r?\n)*def\x20+format_event\x20*\( new_event \)\x20*:(\r?\n)\x20+return\x20+'{event\.level},{event\.message}'\x20*\.\x20*format\x20*\( event = new_event \)
23+
\s*
2024
</script>
25+
<!--
26+
\x20+return\x20+'{event\.level},{event\.message}'\.format\( event = new_event \)
27+
\s*
28+
-->
2129

2230
<script id="info" type="application/yaml">
2331
---
2432
hints:
25-
- absent: "user_input"
26-
text: Make sure the user_input is included in the replacement fields passed to the format function
27-
examples:
28-
- - "'some format'.format(user_input=user_input)"
29-
- - "'some format'.format(user_input)"
33+
- present: |
34+
def format_event \( user_input
35+
text: The `user_format` should no longer be used, so we should
36+
remove it from the list of parameters being passed into the function
37+
being defined by `def`.
38+
The first line should read `def format_event(new_event):`
39+
- present: "user_input"
40+
text: Do not support a user-provided format at all.
41+
In this case there is no need for it.
3042
- absent: "event"
31-
text: Make sure the event is included in the replacement fields passed to the format function
32-
examples:
33-
- - "'some format'.format(user_input=user_input, event=event)"
34-
- - "'some format'.format(user_input, event)"
35-
- absent: \{(0|format_string)\}
36-
text: Make sure the braces for the format_string replacement field are in the format string
37-
examples:
38-
- - "'{event}'.format(format_string=user_input)"
39-
- - "'{0}.format(user_input)"
40-
- absent: \{(1|event)\}
41-
text: Make sure the braces for the event replacement field are in the format string
42-
examples:
43-
- - "'{format_string}, {event.level}'.format(format_string=user_input, event=event)"
44-
- - "'{0}, {1.level}'.format(user_input, event)"
45-
- absent: \{(1\.level|event\.level)\}
46-
text: Make sure the level attribute of the event replacement field is in the format string
47-
examples:
48-
- - "'{format_string}, {event.level}'.format(format_string=user_input, event=event)"
49-
- - "'{0}, {1.level}'.format(user_input, event)"
43+
text: We want to see `event`, e.g.,
44+
return '{event.level},...'.format(event=new_event)
45+
- present: '\{0'
46+
text: For our purposes we want to use named parameters, so do not
47+
use `{0}` or similar.
48+
- absent: |
49+
\'\{event.level\},\{event.message\}\'
50+
text: The constant text `'{event.level},{event.message}'` should be present.
5051
# debug: true
5152
</script>
5253
</head>
@@ -62,36 +63,33 @@ <h1>Lab Exercise Format Strings and Templates</h1>
6263
<p>
6364
<h2>Task</h2>
6465
<p>
65-
<b>Practice using string templates in a secure way.</b>
66+
<b>Practice eliminating string formatting vulnerabilities in Python.</b>
6667

6768
<p>
6869
<h2>Background</h2>
6970
<p>
7071
In this exercise, we'll adjust our string formatting so that it doesn't allow a user to control
7172
the <a href="https://docs.python.org/3/tutorial/inputoutput.html#the-string-format-method"><tt>
72-
format string</tt></a>. If a user can control the <tt>format string</tt> they can access
73-
variables which they shouldn't. Particularly if those variable's values can be returned to the user
73+
format string</tt></a>.
74+
75+
<p>If a user can control the <tt>format string</tt> in Python they can access
76+
value which they shouldn't. Particularly if those variable's values can be returned to the user
7477
as output, it could lead to information disclosure beyond what was intended by the developer.
7578

7679
<p>
7780
<h2>Task Information</h2>
7881
<p>
82+
Please change the code below so the string formatting cannot disclose arbitrary
83+
program values.
7984

8085
<p>
81-
Please change the code below so the string formatting cannot disclose arbitrary
82-
program values. The server-side program is written in Python and allows a user to specify a
83-
<tt>format string</tt> to control the output format of an event.
86+
The server-side program is written in Python and allows a user to specify a <tt>format string</tt> to control the output format of an event, shown here as <tt>user_format</tt>. The developer probably expected the user to provide a format string like <tt>'{event.level}'</tt> to control what is shown and where.
8487

8588
<p>
86-
You could adjust the program so that it only formats the event, and does not include any user input,
87-
However it is considered safe to include user input in the output as long as they cannot control
88-
the <tt>format string</tt> itself.
89+
However, in many programming languages, allowing an untrusted user to control a format sting is a vulnerability. Format strings are miniature programming languages; running code provided by an untrusted user is dangerous. In the case of Python, an attacker might be able to provide a sneaky format string value like <tt>'{event.__init__.__globals__[CONFIG][SECRET_KEY]}'</tt> and reveal a secret value like a password or secret key.
8990

9091
<p>
91-
Adjust the value returned by the <tt>format_event</tt> function so the the user controlled
92-
<tt>user_input</tt> variable is only used as a
93-
<a href="https://docs.python.org/3/library/string.html#format-string-syntax"><tt>replacement field</tt></a>
94-
and is not used as the <tt>format string</tt>.
92+
In this case, as in many, there is no need for an untrusted user to control the format string at all. Where we can, we should use a constant format that cannot be controlled by a potential attacker. For purposes of this lab, instead of letting the user control the formatting string, set the format to the fixed value <tt>'{event.level},{event.message}'</tt> and don't forget to remove the no-longer-needed format parameter.
9593

9694
<p>
9795
Use the “hint” and “give up” buttons if necessary.
@@ -100,21 +98,10 @@ <h2>Task Information</h2>
10098
<h2>Interactive Lab (<span id="grade"></span>)</h2>
10199
<p>
102100
<form id="lab">
103-
<pre><code
104-
> # CONFIG includes a secret key that must not be revealed
105-
106-
# A event object with a single attribute used by the malicious format string to gain access to the
107-
# secret application configuration below
108-
class Event(object):
109-
def __init__(self, level):
110-
self.level = level
111-
112-
def format_event(user_input, event):
113-
<input id="attempt0" type="text" size="60" spellcheck="false" value=" return user_input.format(event=event)">
114-
115-
event = Event('level')
116-
format_event('{event.__init__.__globals__[CONFIG][SECRET_KEY]}', event)
117-
</code></pre>
101+
<pre><code><textarea id="attempt0" rows="3" cols="60" spellcheck="false"
102+
>def format_event(user_format, new_event):
103+
return user_format.format(event=new_event)</textarea
104+
></code></pre>
118105
<button type="button" class="hintButton">Hint</button>
119106
<button type="button" class="resetButton">Reset</button>
120107
<button type="button" class="giveUpButton">Give up</button>
@@ -123,7 +110,8 @@ <h2>Interactive Lab (<span id="grade"></span>)</h2>
123110
<i>This lab was developed by Jason Shepherd at
124111
<a href="https://access.redhat.com"
125112
>Red Hat</a>.</i> with an modified version of the example code from Armin Ronacher's
126-
<a href="https://lucumr.pocoo.org/2016/12/29/careful-with-str-format/">Be Careful with Python's New-Style String Format</a> article.
113+
<a href="https://lucumr.pocoo.org/2016/12/29/careful-with-str-format/">Be Careful with Python's New-Style String Format</a> article, and
114+
modified by David A. Wheeler.
127115
<br><br>
128116
<p id="correctStamp" class="small">
129117
<textarea id="debugData" class="displayNone" rows="20" cols="65" readonly>

docs/labs/src/format-strings.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env python3
2+
3+
# Here is a demo of how an attack can happen, though we don't need
4+
# all this detail to be displayed to the learner.
5+
6+
# CONFIG includes a secret key that must not be revealed
7+
CONFIG = {'SECRET_KEY': 'secret_password'}
8+
9+
# A event object with a single attribute used by the malicious format string to gain access to the
10+
# secret application configuration below
11+
class Event(object):
12+
def __init__(self, level, message):
13+
self.level = level
14+
self.message = message
15+
16+
def format_event(user_input, new_event):
17+
return user_input.format(event=new_event)
18+
19+
my_event = Event('high', 'CPU burning')
20+
print(format_event('{event.level}, {event.message}', my_event))
21+
print(format_event('{event.__init__.__globals__[CONFIG][SECRET_KEY]}', my_event))
22+
23+
# Let's redefine format_event
24+
print("Redefining")
25+
26+
def format_event(new_event):
27+
return '{event.level},{event.message}'.format(event=new_event)
28+
29+
print(format_event(my_event))

0 commit comments

Comments
 (0)