16 |
16 |
17 class SecurityChecker: |
17 class SecurityChecker: |
18 """ |
18 """ |
19 Class implementing a checker for security issues. |
19 Class implementing a checker for security issues. |
20 """ |
20 """ |
|
21 |
21 Codes = [ |
22 Codes = [ |
22 # assert used |
23 # assert used |
23 "S101", |
24 "S101", |
24 |
|
25 # exec used |
25 # exec used |
26 "S102", |
26 "S102", |
27 |
|
28 # bad file permissions |
27 # bad file permissions |
29 "S103", |
28 "S103", |
30 |
|
31 # bind to all interfaces |
29 # bind to all interfaces |
32 "S104", |
30 "S104", |
33 |
|
34 # hardcoded passwords |
31 # hardcoded passwords |
35 "S105", "S106", "S107" |
32 "S105", |
36 |
33 "S106", |
|
34 "S107" |
37 # hardcoded tmp directory |
35 # hardcoded tmp directory |
38 "S108", |
36 "S108", |
39 |
|
40 # try-except |
37 # try-except |
41 "S110", "S112", |
38 "S110", |
42 |
39 "S112", |
43 # flask app |
40 # flask app |
44 "S201", |
41 "S201", |
45 |
|
46 # insecure function calls (blacklisted) |
42 # insecure function calls (blacklisted) |
47 "S301", "S302", "S303", "S304", "S305", "S306", "S307", "S308", "S309", |
43 "S301", |
48 "S310", "S311", "S312", "S313", "S314", "S315", "S316", "S317", "S318", |
44 "S302", |
49 "S319", "S320", "S321", "S322", "S323", "S324", |
45 "S303", |
50 |
46 "S304", |
|
47 "S305", |
|
48 "S306", |
|
49 "S307", |
|
50 "S308", |
|
51 "S309", |
|
52 "S310", |
|
53 "S311", |
|
54 "S312", |
|
55 "S313", |
|
56 "S314", |
|
57 "S315", |
|
58 "S316", |
|
59 "S317", |
|
60 "S318", |
|
61 "S319", |
|
62 "S320", |
|
63 "S321", |
|
64 "S322", |
|
65 "S323", |
|
66 "S324", |
51 # hashlib.new |
67 # hashlib.new |
52 "S331", |
68 "S331", |
53 |
|
54 # insecure imports (blacklisted) |
69 # insecure imports (blacklisted) |
55 "S401", "S402", "S403", "S404", "S405", "S406", "S407", "S408", "S409", |
70 "S401", |
56 "S410", "S411", "S412", "S413", |
71 "S402", |
57 |
72 "S403", |
|
73 "S404", |
|
74 "S405", |
|
75 "S406", |
|
76 "S407", |
|
77 "S408", |
|
78 "S409", |
|
79 "S410", |
|
80 "S411", |
|
81 "S412", |
|
82 "S413", |
58 # insecure certificate usage |
83 # insecure certificate usage |
59 "S501", |
84 "S501", |
60 |
|
61 # insecure SSL/TLS protocol version |
85 # insecure SSL/TLS protocol version |
62 "S502", "S503", "S504", |
86 "S502", |
63 |
87 "S503", |
|
88 "S504", |
64 # weak cryptographic keys |
89 # weak cryptographic keys |
65 "S505", |
90 "S505", |
66 |
|
67 # YAML load |
91 # YAML load |
68 "S506", |
92 "S506", |
69 |
|
70 # SSH host key verification |
93 # SSH host key verification |
71 "S507", |
94 "S507", |
72 |
|
73 # Shell injection |
95 # Shell injection |
74 "S601", "S602", "S603", "S604", "S605", "S606", "S607", |
96 "S601", |
75 |
97 "S602", |
|
98 "S603", |
|
99 "S604", |
|
100 "S605", |
|
101 "S606", |
|
102 "S607", |
76 # SQL injection |
103 # SQL injection |
77 "S608", |
104 "S608", |
78 |
|
79 # Wildcard injection |
105 # Wildcard injection |
80 "S609", |
106 "S609", |
81 |
|
82 # Django SQL injection |
107 # Django SQL injection |
83 "S610", "S611", |
108 "S610", |
84 |
109 "S611", |
85 # Jinja2 templates |
110 # Jinja2 templates |
86 "S701", |
111 "S701", |
87 |
|
88 # Mako templates |
112 # Mako templates |
89 "S702", |
113 "S702", |
90 |
|
91 # Django XSS vulnerability |
114 # Django XSS vulnerability |
92 "S703", |
115 "S703", |
93 |
|
94 # hardcoded AWS passwords |
116 # hardcoded AWS passwords |
95 "S801", "S802", |
117 "S801", |
|
118 "S802", |
96 ] |
119 ] |
97 |
120 |
98 def __init__(self, source, filename, tree, select, ignore, expected, |
121 def __init__(self, source, filename, tree, select, ignore, expected, repeat, args): |
99 repeat, args): |
|
100 """ |
122 """ |
101 Constructor |
123 Constructor |
102 |
124 |
103 @param source source code to be checked |
125 @param source source code to be checked |
104 @type list of str |
126 @type list of str |
105 @param filename name of the source file |
127 @param filename name of the source file |
106 @type str |
128 @type str |
107 @param tree AST tree of the source code |
129 @param tree AST tree of the source code |
116 @type bool |
138 @type bool |
117 @param args dictionary of arguments for the security checks |
139 @param args dictionary of arguments for the security checks |
118 @type dict |
140 @type dict |
119 """ |
141 """ |
120 self.__select = tuple(select) |
142 self.__select = tuple(select) |
121 self.__ignore = ('',) if select else tuple(ignore) |
143 self.__ignore = ("",) if select else tuple(ignore) |
122 self.__expected = expected[:] |
144 self.__expected = expected[:] |
123 self.__repeat = repeat |
145 self.__repeat = repeat |
124 self.__filename = filename |
146 self.__filename = filename |
125 self.__source = source[:] |
147 self.__source = source[:] |
126 self.__tree = copy.deepcopy(tree) |
148 self.__tree = copy.deepcopy(tree) |
127 self.__args = args |
149 self.__args = args |
128 |
150 |
129 # statistics counters |
151 # statistics counters |
130 self.counters = {} |
152 self.counters = {} |
131 |
153 |
132 # collection of detected errors |
154 # collection of detected errors |
133 self.errors = [] |
155 self.errors = [] |
134 |
156 |
135 checkersWithCodes = Checks.generateCheckersDict() |
157 checkersWithCodes = Checks.generateCheckersDict() |
136 |
158 |
137 self.__checkers = collections.defaultdict(list) |
159 self.__checkers = collections.defaultdict(list) |
138 for checkType, checkersList in checkersWithCodes.items(): |
160 for checkType, checkersList in checkersWithCodes.items(): |
139 for checker, codes in checkersList: |
161 for checker, codes in checkersList: |
140 if any(not (code and self.__ignoreCode(code)) |
162 if any(not (code and self.__ignoreCode(code)) for code in codes): |
141 for code in codes): |
|
142 self.__checkers[checkType].append(checker) |
163 self.__checkers[checkType].append(checker) |
143 |
164 |
144 def __ignoreCode(self, code): |
165 def __ignoreCode(self, code): |
145 """ |
166 """ |
146 Private method to check if the message code should be ignored. |
167 Private method to check if the message code should be ignored. |
147 |
168 |
148 @param code message code to check for |
169 @param code message code to check for |
149 @type str |
170 @type str |
150 @return flag indicating to ignore the given code |
171 @return flag indicating to ignore the given code |
151 @rtype bool |
172 @rtype bool |
152 """ |
173 """ |
153 return (code.startswith(self.__ignore) and |
174 return code.startswith(self.__ignore) and not code.startswith(self.__select) |
154 not code.startswith(self.__select)) |
175 |
155 |
176 def reportError(self, lineNumber, offset, code, severity, confidence, *args): |
156 def reportError(self, lineNumber, offset, code, severity, confidence, |
|
157 *args): |
|
158 """ |
177 """ |
159 Public method to record an issue. |
178 Public method to record an issue. |
160 |
179 |
161 @param lineNumber line number of the issue |
180 @param lineNumber line number of the issue |
162 @type int |
181 @type int |
163 @param offset position within line of the issue |
182 @param offset position within line of the issue |
164 @type int |
183 @type int |
165 @param code message code |
184 @param code message code |
173 @param args arguments for the message |
192 @param args arguments for the message |
174 @type list |
193 @type list |
175 """ |
194 """ |
176 if self.__ignoreCode(code): |
195 if self.__ignoreCode(code): |
177 return |
196 return |
178 |
197 |
179 if code in self.counters: |
198 if code in self.counters: |
180 self.counters[code] += 1 |
199 self.counters[code] += 1 |
181 else: |
200 else: |
182 self.counters[code] = 1 |
201 self.counters[code] = 1 |
183 |
202 |
184 # Don't care about expected codes |
203 # Don't care about expected codes |
185 if code in self.__expected: |
204 if code in self.__expected: |
186 return |
205 return |
187 |
206 |
188 if code and (self.counters[code] == 1 or self.__repeat): |
207 if code and (self.counters[code] == 1 or self.__repeat): |
189 # record the issue with one based line number |
208 # record the issue with one based line number |
190 self.errors.append({ |
209 self.errors.append( |
191 "file": self.__filename, |
210 { |
192 "line": lineNumber + 1, |
211 "file": self.__filename, |
193 "offset": offset, |
212 "line": lineNumber + 1, |
194 "code": code, |
213 "offset": offset, |
195 "args": args, |
214 "code": code, |
196 "severity": severity, |
215 "args": args, |
197 "confidence": confidence, |
216 "severity": severity, |
198 }) |
217 "confidence": confidence, |
199 |
218 } |
|
219 ) |
|
220 |
200 def getConfig(self): |
221 def getConfig(self): |
201 """ |
222 """ |
202 Public method to get the configuration dictionary. |
223 Public method to get the configuration dictionary. |
203 |
224 |
204 @return dictionary containing the configuration |
225 @return dictionary containing the configuration |
205 @rtype dict |
226 @rtype dict |
206 """ |
227 """ |
207 return self.__args |
228 return self.__args |
208 |
229 |
209 def run(self): |
230 def run(self): |
210 """ |
231 """ |
211 Public method to check the given source against security related |
232 Public method to check the given source against security related |
212 conditions. |
233 conditions. |
213 """ |
234 """ |
214 if not self.__filename: |
235 if not self.__filename: |
215 # don't do anything, if essential data is missing |
236 # don't do anything, if essential data is missing |
216 return |
237 return |
217 |
238 |
218 if not self.__checkers: |
239 if not self.__checkers: |
219 # don't do anything, if no codes were selected |
240 # don't do anything, if no codes were selected |
220 return |
241 return |
221 |
242 |
222 securityNodeVisitor = SecurityNodeVisitor( |
243 securityNodeVisitor = SecurityNodeVisitor( |
223 self, self.__checkers, self.__filename) |
244 self, self.__checkers, self.__filename |
|
245 ) |
224 securityNodeVisitor.generic_visit(self.__tree) |
246 securityNodeVisitor.generic_visit(self.__tree) |