|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2020 - 2024 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing checks for prohibited methods and functions. |
|
8 """ |
|
9 |
|
10 # |
|
11 # This is a modified version of the one found in the bandit package. |
|
12 # |
|
13 # Original Copyright 2016 Hewlett-Packard Development Company, L.P. |
|
14 # |
|
15 # SPDX-License-Identifier: Apache-2.0 |
|
16 # |
|
17 |
|
18 import ast |
|
19 import fnmatch |
|
20 import sys |
|
21 |
|
22 import AstUtilities |
|
23 |
|
24 _prohibitedCalls = { |
|
25 "S301": ( |
|
26 [ |
|
27 "pickle.loads", |
|
28 "pickle.load", |
|
29 "pickle.Unpickler", |
|
30 "cPickle.loads", |
|
31 "cPickle.load", |
|
32 "cPickle.Unpickler", |
|
33 "dill.loads", |
|
34 "dill.load", |
|
35 "dill.Unpickler", |
|
36 "shelve.open", |
|
37 "shelve.DbfilenameShelf", |
|
38 ], |
|
39 "M", |
|
40 ), |
|
41 "S302": (["marshal.load", "marshal.loads"], "M"), |
|
42 } |
|
43 if sys.version_info >= (3, 9): |
|
44 _prohibitedCalls["S303"] = ( |
|
45 [ |
|
46 "Crypto.Hash.MD2.new", |
|
47 "Crypto.Hash.MD4.new", |
|
48 "Crypto.Hash.MD5.new", |
|
49 "Crypto.Hash.SHA.new", |
|
50 "Cryptodome.Hash.MD2.new", |
|
51 "Cryptodome.Hash.MD4.new", |
|
52 "Cryptodome.Hash.MD5.new", |
|
53 "Cryptodome.Hash.SHA.new", |
|
54 "cryptography.hazmat.primitives.hashes.MD5", |
|
55 "cryptography.hazmat.primitives.hashes.SHA1", |
|
56 ], |
|
57 "M", |
|
58 ) |
|
59 else: |
|
60 _prohibitedCalls["S303"] = ( |
|
61 [ |
|
62 "hashlib.md4", |
|
63 "hashlib.md5", |
|
64 "hashlib.sha", |
|
65 "hashlib.sha1", |
|
66 "Crypto.Hash.MD2.new", |
|
67 "Crypto.Hash.MD4.new", |
|
68 "Crypto.Hash.MD5.new", |
|
69 "Crypto.Hash.SHA.new", |
|
70 "Cryptodome.Hash.MD2.new", |
|
71 "Cryptodome.Hash.MD4.new", |
|
72 "Cryptodome.Hash.MD5.new", |
|
73 "Cryptodome.Hash.SHA.new", |
|
74 "cryptography.hazmat.primitives.hashes.MD5", |
|
75 "cryptography.hazmat.primitives.hashes.SHA1", |
|
76 ], |
|
77 "M", |
|
78 ) |
|
79 |
|
80 _prohibitedCalls.update( |
|
81 { |
|
82 "S304": ( |
|
83 [ |
|
84 "Crypto.Cipher.ARC2.new", |
|
85 "Crypto.Cipher.ARC4.new", |
|
86 "Crypto.Cipher.Blowfish.new", |
|
87 "Crypto.Cipher.DES.new", |
|
88 "Crypto.Cipher.XOR.new", |
|
89 "Cryptodome.Cipher.ARC2.new", |
|
90 "Cryptodome.Cipher.ARC4.new", |
|
91 "Cryptodome.Cipher.Blowfish.new", |
|
92 "Cryptodome.Cipher.DES.new", |
|
93 "Cryptodome.Cipher.XOR.new", |
|
94 "cryptography.hazmat.primitives.ciphers.algorithms.ARC4", |
|
95 "cryptography.hazmat.primitives.ciphers.algorithms.Blowfish", |
|
96 "cryptography.hazmat.primitives.ciphers.algorithms.IDEA", |
|
97 ], |
|
98 "H", |
|
99 ), |
|
100 "S305": (["cryptography.hazmat.primitives.ciphers.modes.ECB"], "M"), |
|
101 "S306": (["tempfile.mktemp"], "M"), |
|
102 "S307": (["eval"], "M"), |
|
103 "S308": (["django.utils.safestring.mark_safe"], "M"), |
|
104 "S309": ( |
|
105 [ |
|
106 "httplib.HTTPSConnection", |
|
107 "http.client.HTTPSConnection", |
|
108 "six.moves.http_client.HTTPSConnection", |
|
109 ], |
|
110 "M", |
|
111 ), |
|
112 "S310": ( |
|
113 [ |
|
114 "urllib.urlopen", |
|
115 "urllib.request.urlopen", |
|
116 "urllib.urlretrieve", |
|
117 "urllib.request.urlretrieve", |
|
118 "urllib.URLopener", |
|
119 "urllib.request.URLopener", |
|
120 "urllib.FancyURLopener", |
|
121 "urllib.request.FancyURLopener", |
|
122 "urllib2.urlopen", |
|
123 "urllib2.Request", |
|
124 "six.moves.urllib.request.urlopen", |
|
125 "six.moves.urllib.request.urlretrieve", |
|
126 "six.moves.urllib.request.URLopener", |
|
127 "six.moves.urllib.request.FancyURLopener", |
|
128 ], |
|
129 "", |
|
130 ), |
|
131 "S311": ( |
|
132 [ |
|
133 "random.random", |
|
134 "random.randrange", |
|
135 "random.randint", |
|
136 "random.choice", |
|
137 "random.choices", |
|
138 "random.uniform", |
|
139 "random.triangular", |
|
140 ], |
|
141 "L", |
|
142 ), |
|
143 "S312": (["telnetlib.*"], "H"), |
|
144 "S313": ( |
|
145 [ |
|
146 "xml.etree.cElementTree.parse", |
|
147 "xml.etree.cElementTree.iterparse", |
|
148 "xml.etree.cElementTree.fromstring", |
|
149 "xml.etree.cElementTree.XMLParser", |
|
150 ], |
|
151 "M", |
|
152 ), |
|
153 "S314": ( |
|
154 [ |
|
155 "xml.etree.ElementTree.parse", |
|
156 "xml.etree.ElementTree.iterparse", |
|
157 "xml.etree.ElementTree.fromstring", |
|
158 "xml.etree.ElementTree.XMLParser", |
|
159 ], |
|
160 "M", |
|
161 ), |
|
162 "S315": (["xml.sax.expatreader.create_parser"], "M"), |
|
163 "S316": ( |
|
164 ["xml.dom.expatbuilder.parse", "xml.dom.expatbuilder.parseString"], |
|
165 "M", |
|
166 ), |
|
167 "S317": (["xml.sax.parse", "xml.sax.parseString", "xml.sax.make_parser"], "M"), |
|
168 "S318": (["xml.dom.minidom.parse", "xml.dom.minidom.parseString"], "M"), |
|
169 "S319": (["xml.dom.pulldom.parse", "xml.dom.pulldom.parseString"], "M"), |
|
170 "S320": ( |
|
171 [ |
|
172 "lxml.etree.parse", |
|
173 "lxml.etree.fromstring", |
|
174 "lxml.etree.RestrictedElement", |
|
175 "lxml.etree.GlobalParserTLS", |
|
176 "lxml.etree.getDefaultParser", |
|
177 "lxml.etree.check_docinfo", |
|
178 ], |
|
179 "M", |
|
180 ), |
|
181 "S321": (["ftplib.*"], "H"), |
|
182 "S322": (["input"], "H"), |
|
183 "S323": (["ssl._create_unverified_context"], "M"), |
|
184 "S324": (["os.tempnam", "os.tmpnam"], "M"), |
|
185 } |
|
186 ) |
|
187 |
|
188 |
|
189 def getChecks(): |
|
190 """ |
|
191 Public method to get a dictionary with checks handled by this module. |
|
192 |
|
193 @return dictionary containing checker lists containing checker function and |
|
194 list of codes |
|
195 @rtype dict |
|
196 """ |
|
197 return { |
|
198 "Call": [ |
|
199 (checkProhibitedCalls, tuple(_prohibitedCalls)), |
|
200 ], |
|
201 } |
|
202 |
|
203 |
|
204 def checkProhibitedCalls(reportError, context, config): # noqa: U100 |
|
205 """ |
|
206 Function to check for prohibited method calls. |
|
207 |
|
208 @param reportError function to be used to report errors |
|
209 @type func |
|
210 @param context security context object |
|
211 @type SecurityContext |
|
212 @param config dictionary with configuration data |
|
213 @type dict |
|
214 """ |
|
215 nodeType = context.node.__class__.__name__ |
|
216 |
|
217 if nodeType == "Call": |
|
218 func = context.node.func |
|
219 if isinstance(func, ast.Name) and func.id == "__import__": |
|
220 if len(context.node.args): |
|
221 if AstUtilities.isString(context.node.args[0]): |
|
222 name = context.node.args[0].s |
|
223 else: |
|
224 name = "UNKNOWN" |
|
225 else: |
|
226 name = "" # handle '__import__()' |
|
227 else: |
|
228 name = context.callFunctionNameQual |
|
229 # In the case the Call is an importlib.import, treat the first |
|
230 # argument name as an actual import module name. |
|
231 # Will produce None if argument is not a literal or identifier. |
|
232 if name in ["importlib.import_module", "importlib.__import__"]: |
|
233 name = context.callArgs[0] |
|
234 |
|
235 for code in _prohibitedCalls: |
|
236 qualnames, severity = _prohibitedCalls[code] |
|
237 for qualname in qualnames: |
|
238 if name and fnmatch.fnmatch(name, qualname): |
|
239 reportError( |
|
240 context.node.lineno - 1, |
|
241 context.node.col_offset, |
|
242 code, |
|
243 severity, |
|
244 "H", |
|
245 name, |
|
246 ) |