This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Mtolhuys\LaravelEnvScanner; |
||
4 | |||
5 | use RecursiveDirectoryIterator; |
||
6 | use RecursiveIteratorIterator; |
||
7 | use RegexIterator; |
||
8 | |||
9 | class LaravelEnvScanner |
||
10 | { |
||
11 | /** |
||
12 | * The results of performed scan |
||
13 | * |
||
14 | * @var array |
||
15 | */ |
||
16 | public $results = [ |
||
17 | 'locations' => 0, |
||
18 | 'defined' => 0, |
||
19 | 'undefined' => 0, |
||
20 | 'depending_on_default' => 0, |
||
21 | 'rows' => [], |
||
22 | ]; |
||
23 | |||
24 | /** |
||
25 | * Stores processed file and var names |
||
26 | * |
||
27 | * @var array |
||
28 | */ |
||
29 | private $processed = [ |
||
30 | 'variables' => [], |
||
31 | ]; |
||
32 | |||
33 | /** |
||
34 | * Stores undefined var names |
||
35 | * |
||
36 | * @var array |
||
37 | */ |
||
38 | public $undefined = []; |
||
39 | |||
40 | /** |
||
41 | * Stores warnings for vars not passing validation |
||
42 | * |
||
43 | * @var array |
||
44 | */ |
||
45 | public $warnings = []; |
||
46 | |||
47 | /** |
||
48 | * Current file being processed |
||
49 | * |
||
50 | * @var string |
||
51 | */ |
||
52 | private $file; |
||
53 | |||
54 | /** |
||
55 | * Current location a found invocation |
||
56 | * |
||
57 | * @var string |
||
58 | */ |
||
59 | private $location; |
||
60 | |||
61 | /** |
||
62 | * Current invocation being processed |
||
63 | * |
||
64 | * @var string |
||
65 | */ |
||
66 | private $invocation; |
||
67 | |||
68 | /** |
||
69 | * Current parameters being processed |
||
70 | * |
||
71 | * @var object |
||
72 | */ |
||
73 | private $parameters; |
||
74 | |||
75 | /** |
||
76 | * Root directory to start recursive search for env()'s from |
||
77 | * Defaults to config_path() |
||
78 | * |
||
79 | * @var string $dir |
||
80 | */ |
||
81 | public $dir; |
||
82 | |||
83 | public function __construct(string $dir = null) |
||
84 | { |
||
85 | $this->dir = basename($dir ?? config_path()); |
||
86 | } |
||
87 | |||
88 | /** |
||
89 | * Run the scan |
||
90 | * |
||
91 | * @return mixed |
||
92 | * @throws \Exception |
||
93 | */ |
||
94 | public function scan() |
||
95 | { |
||
96 | foreach ($this->getFiles() as $file) { |
||
97 | $lines = explode(PHP_EOL, file_get_contents($file)); |
||
98 | |||
99 | $this->file = $file; |
||
100 | |||
101 | foreach ($lines as $index => $line) { |
||
102 | |||
103 | if (preg_match('# env\(| getenv\(#', $line)) { |
||
104 | if (! $this->setInvocationDetails($lines, $line, $index)) { |
||
105 | continue; |
||
106 | } |
||
107 | |||
108 | $this->storeResult(); |
||
109 | } |
||
110 | } |
||
111 | } |
||
112 | |||
113 | return $this; |
||
114 | } |
||
115 | |||
116 | /** |
||
117 | * Search for possible matches and make something usable out of it |
||
118 | * |
||
119 | * @param array $lines |
||
120 | * @param string $line |
||
121 | * @param int $index |
||
122 | * @return bool |
||
123 | */ |
||
124 | private function setInvocationDetails(array $lines, string $line, int $index): bool |
||
125 | { |
||
126 | $matches = $this->search($lines, $line, $index); |
||
127 | |||
128 | if (empty(array_filter($matches))) { |
||
129 | return false; |
||
130 | } |
||
131 | |||
132 | $this->setInvocation($matches); |
||
0 ignored issues
–
show
|
|||
133 | $this->setParameters($matches); |
||
0 ignored issues
–
show
It seems like
$matches defined by $this->search($lines, $line, $index) on line 126 can also be of type null ; however, Mtolhuys\LaravelEnvScann...canner::setParameters() does only seem to accept array , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
134 | $this->setLocation($index + 1); |
||
135 | |||
136 | if ($this->needsWarning()) { |
||
137 | return false; |
||
138 | } |
||
139 | |||
140 | if ($this->alreadyProcessed()) { |
||
141 | return false; |
||
142 | } |
||
143 | |||
144 | $this->processed['variables'][] = $this->parameters->variable; |
||
145 | |||
146 | return true; |
||
147 | } |
||
148 | |||
149 | /** |
||
150 | * Search for single and multi-lined env and getenv invocations |
||
151 | * |
||
152 | * @param array $lines |
||
153 | * @param string $line |
||
154 | * @param int $number |
||
155 | * @return mixed |
||
156 | */ |
||
157 | private function search(array $lines, string $line, int $number) |
||
158 | { |
||
159 | preg_match_all( |
||
160 | '# env\((.*?)\)| getenv\((.*?)\)#', |
||
161 | $line, |
||
162 | $matches |
||
163 | ); |
||
164 | |||
165 | $line = str_replace(' ', '', $line); |
||
166 | |||
167 | if ($line === 'env(' || $line === 'getenv(') { |
||
168 | $matches = $this->searchMultiLine($lines, $number); |
||
169 | } |
||
170 | |||
171 | return $matches; |
||
172 | } |
||
173 | |||
174 | /** |
||
175 | * For multi-line invocation f.e. |
||
176 | * env( |
||
177 | * 'MULTI', |
||
178 | * 'lined' |
||
179 | * ); |
||
180 | * |
||
181 | * @param array $lines |
||
182 | * @param int $number |
||
183 | * @return mixed |
||
184 | */ |
||
185 | private function searchMultiLine(array $lines, int $number) |
||
186 | { |
||
187 | $search = $lines[$number]; |
||
188 | $search .= $lines[$number + 1]; |
||
189 | $search .= $lines[$number + 2]; |
||
190 | $search .= $lines[$number + 3]; |
||
191 | |||
192 | preg_match_all( |
||
193 | '# env\((.*?)\)| getenv\((.*?)\)#', |
||
194 | $search, |
||
195 | $matches |
||
196 | ); |
||
197 | |||
198 | return $matches; |
||
199 | } |
||
200 | |||
201 | /** |
||
202 | * Set invocation based on first index in preg_match_all result |
||
203 | * |
||
204 | * @param array $matches |
||
205 | */ |
||
206 | private function setInvocation(array $matches) |
||
207 | { |
||
208 | $this->invocation = str_replace(' ', '', str_replace( |
||
209 | ' ', '', $matches[0] |
||
210 | )[0]); |
||
211 | } |
||
212 | |||
213 | /** |
||
214 | * Sets parameters based on comma exploding |
||
215 | * 1 of last indexes in preg_match_all result |
||
216 | * |
||
217 | * @param array $matches |
||
218 | */ |
||
219 | private function setParameters(array $matches) |
||
220 | { |
||
221 | $parameters = empty($matches[1][0]) ? $matches[2][0] : $matches[1][0]; |
||
222 | $parameters = explode(',', str_replace(["'", '"', ' ',], '', $parameters)); |
||
223 | |||
224 | $this->parameters = (object)[ |
||
225 | 'variable' => $parameters[0], |
||
226 | 'default' => $parameters[1] ?? null, |
||
227 | ]; |
||
228 | } |
||
229 | |||
230 | /** |
||
231 | * Sets location as filename + linenumber |
||
232 | * |
||
233 | * @param int $linenumber |
||
234 | */ |
||
235 | private function setLocation(int $linenumber) |
||
236 | { |
||
237 | $this->location = "{$this->file}:$linenumber"; |
||
238 | } |
||
239 | |||
240 | /** |
||
241 | * Only warn about risky and unreadable invocations |
||
242 | * |
||
243 | * @return bool |
||
244 | */ |
||
245 | private function needsWarning(): bool |
||
246 | { |
||
247 | |||
248 | if (!preg_match('/^\w+$/', $this->parameters->variable)) { |
||
249 | $this->warnings[] = (object)[ |
||
250 | 'invocation' => $this->invocation, |
||
251 | 'location' => $this->location, |
||
252 | ]; |
||
253 | |||
254 | return true; |
||
255 | } |
||
256 | |||
257 | return false; |
||
258 | } |
||
259 | |||
260 | /** |
||
261 | * @return bool |
||
262 | */ |
||
263 | private function alreadyProcessed(): bool |
||
264 | { |
||
265 | return in_array($this->parameters->variable, $this->processed['variables'], true); |
||
266 | } |
||
267 | |||
268 | private function storeResult() |
||
269 | { |
||
270 | $resultData = [ |
||
271 | 'location' => $this->location, |
||
272 | 'defined' => '-', |
||
273 | 'depending_on_default' => '-', |
||
274 | 'undefined' => '-', |
||
275 | ]; |
||
276 | |||
277 | $this->results['locations']++; |
||
278 | |||
279 | if (env($this->parameters->variable) !== null) { |
||
280 | $resultData['defined'] = $this->parameters->variable; |
||
281 | $this->results['defined']++; |
||
282 | } else if ($this->parameters->default) { |
||
283 | $resultData['depending_on_default'] = $this->parameters->variable; |
||
284 | $this->results['depending_on_default']++; |
||
285 | } else { |
||
286 | $resultData['undefined'] = $this->parameters->variable; |
||
287 | $this->results['undefined']++; |
||
288 | $this->undefined[] = [ |
||
289 | 'filename' => $this->location, |
||
290 | 'variable' => $this->parameters->variable, |
||
291 | ]; |
||
292 | } |
||
293 | |||
294 | $this->results['rows'][] = $resultData; |
||
295 | } |
||
296 | |||
297 | private function getFiles(): array |
||
298 | { |
||
299 | if (!file_exists($this->dir)) { |
||
300 | return []; |
||
301 | } |
||
302 | |||
303 | $files = new RegexIterator( |
||
304 | new RecursiveIteratorIterator( |
||
305 | new RecursiveDirectoryIterator($this->dir) |
||
306 | ), |
||
307 | '/.*?.php/', RegexIterator::GET_MATCH |
||
308 | ); |
||
309 | |||
310 | $list = [[]]; |
||
311 | |||
312 | foreach ($files as $file) { |
||
313 | $list[] = $file; |
||
314 | } |
||
315 | |||
316 | $list = array_merge(...$list); |
||
317 | |||
318 | return $list; |
||
319 | } |
||
320 | } |
||
321 |
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.