Skip to content

Commit f7d7b8e

Browse files
authored
Merge pull request #785 from taus-semmle/python-unsafe-use-of-mktemp
Python: Add query for unsafe use of `tempfile.mktemp`.
2 parents 999e0c8 + e47b391 commit f7d7b8e

File tree

11 files changed

+135
-0
lines changed

11 files changed

+135
-0
lines changed

change-notes/1.20/analysis-python.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ The API has been improved to declutter the global namespace and improve discover
1919
| Default version of SSL/TLS may be insecure (`py/insecure-default-protocol`) | security, external/cwe/cwe-327 | Finds instances where an insecure default protocol may be used. Results are shown on LGTM by default. |
2020
| Incomplete regular expression for hostnames (`py/incomplete-hostname-regexp`) | security, external/cwe/cwe-020 | Finds instances where a hostname is incompletely sanitized because a regular expression contains an unescaped character. Results are shown on LGTM by default. |
2121
| Incomplete URL substring sanitization (`py/incomplete-url-substring-sanitization`) | security, external/cwe/cwe-020 | Finds instances where a URL is incompletely sanitized due to insufficient checks. Results are shown on LGTM by default. |
22+
| Insecure temporary file (`py/insecure-temporary-file`) | security, external/cwe/cwe-377 | Finds uses of the insecure and deprecated `tempfile.mktemp`, `os.tempnam`, and `os.tmpnam` functions. Results are shown on LGTM by default. |
2223
| Overly permissive file permissions (`py/overly-permissive-file`) | security, external/cwe/cwe-732 | Finds instances where a file is created with overly permissive permissions. Results are not shown on LGTM by default. |
2324
| Use of insecure SSL/TLS version (`py/insecure-protocol`) | security, external/cwe/cwe-327 | Finds instances where a known insecure protocol has been specified. Results are shown on LGTM by default. |
2425

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from tempfile import mktemp
2+
3+
def write_results(results):
4+
filename = mktemp()
5+
with open(filename, "w+") as f:
6+
f.write(results)
7+
print("Results written to", filename)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
4+
<overview>
5+
<p>
6+
Functions that create temporary file names (such as <code>tempfile.mktemp</code>
7+
and <code>os.tempnam</code>) are fundamentally insecure, as they do not
8+
ensure exclusive access to a file with the temporary name they return.
9+
The file name returned by these functions is guaranteed to be unique on
10+
creation but the file must be opened in a separate operation. There is no
11+
guarantee that the creation and open operations will happen atomically. This
12+
provides an opportunity for an attacker to interfere with the file before it is
13+
opened.
14+
</p>
15+
<p>
16+
Note that <code>mktemp</code> has been deprecated since Python 2.3.
17+
</p>
18+
</overview>
19+
20+
<recommendation>
21+
<p>
22+
Replace the use of <code>mktemp</code> with some of the more secure functions
23+
in the <code>tempfile</code> module, such as <code>TemporaryFile</code>. If the
24+
file is intended to be accessed from other processes, consider using the
25+
<code>NamedTemporaryFile</code> function.
26+
</p>
27+
</recommendation>
28+
29+
<example>
30+
<p>
31+
The following piece of code opens a temporary file and writes a set of results
32+
to it. Because the file name is created using <code>mktemp</code>, another
33+
process may access this file before it is opened using <code>open</code>.
34+
</p>
35+
<sample src="InsecureTemporaryFile.py" />
36+
37+
<p>
38+
By changing the code to use <code>NamedTemporaryFile</code> instead, the file is
39+
opened immediately.
40+
</p>
41+
<sample src="SecureTemporaryFile.py" />
42+
43+
</example>
44+
45+
<references>
46+
47+
<li>
48+
Python Standard Library: <a href="https://docs.python.org/3/library/tempfile.html#tempfile.mktemp">tempfile.mktemp</a>.
49+
</li>
50+
</references>
51+
52+
</qhelp>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* @name Insecure temporary file
3+
* @description Creating a temporary file using this method may be insecure.
4+
* @id py/insecure-temporary-file
5+
* @problem.severity error
6+
* @sub-severity high
7+
* @precision high
8+
* @tags external/cwe/cwe-377
9+
* security
10+
*/
11+
12+
import python
13+
14+
FunctionObject temporary_name_function(string mod, string function) {
15+
(
16+
mod = "tempfile" and function = "mktemp"
17+
or
18+
mod = "os" and
19+
(
20+
function = "tmpnam"
21+
or
22+
function = "tempnam"
23+
)
24+
) and
25+
result = any(ModuleObject m | m.getName() = mod).getAttribute(function)
26+
}
27+
28+
from Call c, string mod, string function
29+
where
30+
temporary_name_function(mod, function).getACall().getNode() = c
31+
select c, "Call to deprecated function " + mod + "." + function + " may be insecure."
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from tempfile import NamedTemporaryFile
2+
3+
def write_results(results):
4+
with NamedTemporaryFile(mode="w+", delete=False) as f:
5+
f.write(results)
6+
print("Results written to", f.name)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
| InsecureTemporaryFile.py:5:16:5:23 | mktemp() | Call to deprecated function tempfile.mktemp may be insecure. |
2+
| InsecureTemporaryFile.py:11:16:11:27 | Attribute() | Call to deprecated function os.tempnam may be insecure. |
3+
| InsecureTemporaryFile.py:17:16:17:26 | Attribute() | Call to deprecated function os.tmpnam may be insecure. |
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from tempfile import mktemp
2+
import os
3+
4+
def write_results1(results):
5+
filename = mktemp()
6+
with open(filename, "w+") as f:
7+
f.write(results)
8+
print("Results written to", filename)
9+
10+
def write_results2(results):
11+
filename = os.tempnam()
12+
with open(filename, "w+") as f:
13+
f.write(results)
14+
print("Results written to", filename)
15+
16+
def write_results3(results):
17+
filename = os.tmpnam()
18+
with open(filename, "w+") as f:
19+
f.write(results)
20+
print("Results written to", filename)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE-377/InsecureTemporaryFile.ql
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from tempfile import NamedTemporaryFile
2+
3+
def write_results(results):
4+
with NamedTemporaryFile(mode="w+", delete=False) as f:
5+
f.write(results)
6+
print("Results written to", f.name)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
semmle-extractor-options: -p ../lib/ --max-import-depth=3
2+
optimize: true

0 commit comments

Comments
 (0)