Skip to content

Commit 4511197

Browse files
Fix lab format-strings (#666)
* Fix lab format-strings Add "hints:" (without the field name, it has no contents). Remove an example of a password in source code. That is a vulnerability all by itself, we do NOT want to show how to write *vulnerable* code. Signed-off-by: David A. Wheeler <[email protected]> * 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]> --------- Signed-off-by: David A. Wheeler <[email protected]>
1 parent fd01531 commit 4511197

File tree

2 files changed

+75
-60
lines changed

2 files changed

+75
-60
lines changed

docs/labs/format-strings.html

Lines changed: 46 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -9,43 +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
---
24-
- absent: "user_input"
25-
text: Make sure the user_input is included in the replacement fields passed to the format function
26-
examples:
27-
- - "'some format'.format(user_input=user_input)"
28-
- - "'some format'.format(user_input)"
32+
hints:
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.
2942
- absent: "event"
30-
text: Make sure the event is included in the replacement fields passed to the format function
31-
examples:
32-
- - "'some format'.format(user_input=user_input, event=event)"
33-
- - "'some format'.format(user_input, event)"
34-
- absent: \{(0|format_string)\}
35-
text: Make sure the braces for the format_string replacement field are in the format string
36-
examples:
37-
- - "'{event}'.format(format_string=user_input)"
38-
- - "'{0}.format(user_input)"
39-
- absent: \{(1|event)\}
40-
text: Make sure the braces for the event replacement field are in the format string
41-
examples:
42-
- - "'{format_string}, {event.level}'.format(format_string=user_input, event=event)"
43-
- - "'{0}, {1.level}'.format(user_input, event)"
44-
- absent: \{(1\.level|event\.level)\}
45-
text: Make sure the level attribute of the event replacement field is in the format string
46-
examples:
47-
- - "'{format_string}, {event.level}'.format(format_string=user_input, event=event)"
48-
- - "'{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.
4951
# debug: true
5052
</script>
5153
</head>
@@ -61,36 +63,33 @@ <h1>Lab Exercise Format Strings and Templates</h1>
6163
<p>
6264
<h2>Task</h2>
6365
<p>
64-
<b>Practice using string templates in a secure way.</b>
66+
<b>Practice eliminating string formatting vulnerabilities in Python.</b>
6567

6668
<p>
6769
<h2>Background</h2>
6870
<p>
6971
In this exercise, we'll adjust our string formatting so that it doesn't allow a user to control
7072
the <a href="https://docs.python.org/3/tutorial/inputoutput.html#the-string-format-method"><tt>
71-
format string</tt></a>. If a user can control the <tt>format string</tt> they can access
72-
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
7377
as output, it could lead to information disclosure beyond what was intended by the developer.
7478

7579
<p>
7680
<h2>Task Information</h2>
7781
<p>
82+
Please change the code below so the string formatting cannot disclose arbitrary
83+
program values.
7884

7985
<p>
80-
Please change the code below so the string formatting cannot disclose arbitrary
81-
program values. The server-side program is written in Python and allows a user to specify a
82-
<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.
8387

8488
<p>
85-
You could adjust the program so that it only formats the event, and does not include any user input,
86-
However it is considered safe to include user input in the output as long as they cannot control
87-
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.
8890

8991
<p>
90-
Adjust the value returned by the <tt>format_event</tt> function so the the user controlled
91-
<tt>user_input</tt> variable is only used as a
92-
<a href="https://docs.python.org/3/library/string.html#format-string-syntax"><tt>replacement field</tt></a>
93-
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.
9493

9594
<p>
9695
Use the “hint” and “give up” buttons if necessary.
@@ -99,24 +98,10 @@ <h2>Task Information</h2>
9998
<h2>Interactive Lab (<span id="grade"></span>)</h2>
10099
<p>
101100
<form id="lab">
102-
<pre><code
103-
> # Application configuration which should be kept secret from a user
104-
CONFIG = {
105-
'SECRET_KEY': 'super secret key'
106-
}
107-
108-
# A event object with a single attribute used by the malicious format string to gain access to the
109-
# secret application configuration below
110-
class Event(object):
111-
def __init__(self, level):
112-
self.level = level
113-
114-
def format_event(user_input, event):
115-
<input id="attempt0" type="text" size="60" spellcheck="false" value=" return user_input.format(event=event)">
116-
117-
event = Event('level')
118-
format_event('{event.__init__.__globals__[CONFIG][SECRET_KEY]}', event)
119-
</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>
120105
<button type="button" class="hintButton">Hint</button>
121106
<button type="button" class="resetButton">Reset</button>
122107
<button type="button" class="giveUpButton">Give up</button>
@@ -125,7 +110,8 @@ <h2>Interactive Lab (<span id="grade"></span>)</h2>
125110
<i>This lab was developed by Jason Shepherd at
126111
<a href="https://access.redhat.com"
127112
>Red Hat</a>.</i> with an modified version of the example code from Armin Ronacher's
128-
<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.
129115
<br><br>
130116
<p id="correctStamp" class="small">
131117
<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)