SecurityCheckerTest::testCheckAll()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 5
rs 10
1
<?php
2
3
namespace Signify\SecurityChecker\Tests;
4
5
use InvalidArgumentException;
6
use PHPUnit\Framework\TestCase;
7
use Signify\SecurityChecker\SecurityChecker;
8
9
final class SecurityCheckerTest extends TestCase
10
{
11
    private $securityChecker;
12
13
    public function testStaleAfterConfig()
14
    {
15
        $checker = $this->getDefaultSecurityChecker();
16
        // Get the timestamp from the first instantiation.
17
        $filePath = $checker->getOption('advisories-dir') . '/timestamp.txt';
18
        $timestamp1 = (int)file_get_contents($filePath);
19
20
        // Wait for a second to be certain the next instantiation isn't done on the same second.
21
        sleep(1);
22
        // Use default (24 hours) stale time.
23
        new SecurityChecker();
24
        $timestamp2 = (int)file_get_contents($filePath);
25
        $this->assertEquals($timestamp1, $timestamp2);
26
27
        // Wait for a second to be certain the next instantiation isn't done on the same second.
28
        sleep(1);
29
        // Use a custom stale timeout that will not have expired.
30
        new SecurityChecker(['advisories-stale-after' => 3600]);
31
        $timestamp3 = (int)file_get_contents($filePath);
32
        $this->assertEquals($timestamp1, $timestamp3);
33
34
        // Wait for a second to be certain the next instantiation isn't done on the same second.
35
        sleep(1);
36
        // Force the contents to be stale.
37
        new SecurityChecker(['advisories-stale-after' => 0]);
38
        $timestamp4 = (int)file_get_contents($filePath);
39
        $this->assertGreaterThan($timestamp1, $timestamp4);
40
    }
41
42
    public function testUnwritableDirectory()
43
    {
44
        $this->expectException(InvalidArgumentException::class);
45
        $unwritableDir = sys_get_temp_dir() . '/security-checker-unwritable';
46
        mkdir($unwritableDir, 0);
47
        try {
48
            new SecurityChecker(['advisories-dir' => $unwritableDir]);
49
        } catch (InvalidArgumentException $e) {
50
            rmdir($unwritableDir);
51
            throw $e;
52
        }
53
    }
54
55
    public function testWritableDirectory()
56
    {
57
        $dir = $this->getWritableDir();
58
        $beforeSetupTimestamp = time();
59
        new SecurityChecker([
60
            'advisories-dir' => $dir,
61
            'advisories-stale-after' => 0
62
        ]);
63
        // Confirm that there is a timestamp file, and it was created as a part of this test.
64
        $filePath = $dir . '/timestamp.txt';
65
        $this->assertTrue(is_file($filePath));
66
        $newTimestamp = (int)file_get_contents($filePath);
67
        $this->assertGreaterThanOrEqual($beforeSetupTimestamp, $newTimestamp);
68
    }
69
70
    public function testGetOptions()
71
    {
72
        $defaults = [
73
            'advisories-dir' => sys_get_temp_dir() . '/signify-nz-security/advisories',
74
            'advisories-stale-after' => 86400,
75
            'guzzle-options' => [],
76
        ];
77
        $defaultChecker = $this->getDefaultSecurityChecker();
78
        $this->assertSame($defaults, $defaultChecker->getOptions());
79
        foreach ($defaults as $key => $value) {
80
            $this->assertSame($value, $defaultChecker->getOption($key));
81
        }
82
83
        $testOptions = [
84
            'advisories-dir' => $this->getWritableDir(),
85
            'advisories-stale-after' => 3600,
86
            'guzzle-options' => ['timeout' => 0],
87
        ];
88
        $testChecker = new SecurityChecker($testOptions);
89
        $this->assertSame($testOptions, $testChecker->getOptions());
90
        foreach ($testOptions as $key => $value) {
91
            $this->assertSame($value, $testChecker->getOption($key));
92
        }
93
    }
94
95
    public function testCheckNoFileFail()
96
    {
97
        $this->expectException(InvalidArgumentException::class);
98
        $checker = $this->getDefaultSecurityChecker();
99
        $checker->check(dirname(__FILE__) . '/no-such-file');
100
    }
101
102
    public function testCheckInvalidJsonFail()
103
    {
104
        $this->expectException(InvalidArgumentException::class);
105
        $checker = $this->getDefaultSecurityChecker();
106
        $checker->check(json_decode('true'));
107
    }
108
109
    /**
110
     * NOTE: If this test fails, confirm if there have been more advisories added for the installed packages.
111
     */
112
    public function testCheckAll()
113
    {
114
        $checker = $this->getDefaultSecurityChecker();
115
        $vulnerabilities = $checker->check($this->getLockPath());
116
        $this->assertEqualsCanonicalizing($this->getTestVulnerabilities(), $vulnerabilities);
117
    }
118
119
    /**
120
     * NOTE: If this test fails, confirm if there have been more advisories added for the installed packages.
121
     */
122
    public function testCheckNoDev()
123
    {
124
        $checker = $this->getDefaultSecurityChecker();
125
        $vulnerabilities = $checker->check($this->getLockPath(), false);
126
        $this->assertEqualsCanonicalizing($this->getTestVulnerabilities(false), $vulnerabilities);
127
    }
128
129
    /**
130
     * NOTE: If this test fails, confirm if there have been more advisories added for the installed packages.
131
     */
132
    public function testCheckAlreadyParsed()
133
    {
134
        $checker = $this->getDefaultSecurityChecker();
135
        $json = json_decode(file_get_contents($this->getLockPath()), true);
136
        $vulnerabilities = $checker->check($json);
137
        $this->assertEqualsCanonicalizing($this->getTestVulnerabilities(), $vulnerabilities);
138
    }
139
140
    /**
141
     * NOTE: If this test fails, confirm if there have been more advisories added for the referenced package.
142
     */
143
    public function testPrereleaseVersion()
144
    {
145
        $checker = $this->getDefaultSecurityChecker();
146
        $json = [
147
            'packages' => [
148
                [
149
                    'name' => 'symbiote/silverstripe-queuedjobs',
150
                    'version' => '4.6.0-rc1',
151
                ],
152
            ],
153
        ];
154
        $vulnerabilities = $checker->check($json);
155
        $actual = [
156
            'symbiote/silverstripe-queuedjobs' => [
157
                'version' => '4.6.0-rc1',
158
                'advisories' => [
159
                    [
160
                        'title' => 'CVE-2021-27938: XSS in CreateQueuedJobTask',
161
                        'link' => 'https://www.silverstripe.org/download/security-releases/cve-2021-27938',
162
                        'cve' => 'CVE-2021-27938',
163
                    ],
164
                ],
165
            ],
166
        ];
167
        $this->assertEqualsCanonicalizing($actual, $vulnerabilities);
168
    }
169
170
    private function getDefaultSecurityChecker()
171
    {
172
        if (!$this->securityChecker) {
173
            $this->securityChecker = new SecurityChecker();
174
        }
175
        return $this->securityChecker;
176
    }
177
178
    private function getWritableDir()
179
    {
180
        return sys_get_temp_dir() . '/security-checker-test';
181
    }
182
183
    private function getLockPath()
184
    {
185
        return dirname(__FILE__) . '/composer.lock.test';
186
    }
187
188
     /**
189
      * Last updated 2021-12-16
190
      *
191
      * silverstripe/framework 4.0.0 as a test of direct dependency with its own dependencies with known
192
      *     vulnerabilities.
193
      * symbiote/silverstripe-queuedjobs 4.0.x-dev with a specific hash given as a test of dev stabilities
194
      *     with known vulnerabilities.
195
      * twig/twig 1.x-dev as a test of a branch with known vulnerabilities, but at a commit after the
196
      *     vulnerability should no longer apply.
197
      * phpunit/phpunit 5.0.10 as a test that dev dependencies can be skipped.
198
      */
199
    private function getTestVulnerabilities($withDev = true)
200
    {
201
        $vulnerabilities = [
202
            'league/flysystem' => [
203
                'version' => '1.0.70',
204
                'advisories' => [
205
                    [
206
                        'title' => 'TOCTOU Race Condition enabling remote code execution',
207
                        'link' => 'https://github.com/thephpleague/flysystem/security/advisories/GHSA-9f46-5r25-5wfm',
208
                        'cve' => 'CVE-2021-32708',
209
                    ],
210
                ],
211
            ],
212
            'silverstripe/admin' => [
213
                'version' => '1.4.5',
214
                'advisories' => [
215
                    [
216
                        'title' => 'CVE-2021-36150 - Insert from files link text - Reflective (self) Cross Site '
217
                            . 'Scripting',
218
                        'link' => 'https://www.silverstripe.org/download/security-releases/CVE-2021-36150',
219
                        'cve' => 'CVE-2021-36150',
220
                    ],
221
                ],
222
            ],
223
            'silverstripe/assets' => [
224
                'version' => '1.1.0',
225
                'advisories' => [
226
                    [
227
                        'title' => 'CVE-2019-12245: Incorrect access control vulnerability in files uploaded to '
228
                            . 'protected folders',
229
                        'link' => 'https://www.silverstripe.org/download/security-releases/cve-2019-12245/',
230
                        'cve' => 'CVE-2019-12245',
231
                    ],
232
                    [
233
                        'title' => 'CVE-2020-9280: Folders migrated from 3.x may be unsafe to upload to',
234
                        'link' => 'https://www.silverstripe.org/download/security-releases/cve-2020-9280/',
235
                        'cve' => 'CVE-2020-9280',
236
                    ],
237
                ],
238
            ],
239
            'silverstripe/framework' => [
240
                'version' => '4.0.0',
241
                'advisories' => [
242
                    [
243
                        'title' => 'CVE-2019-12203: Session fixation in "change password" form',
244
                        'link' => 'https://www.silverstripe.org/download/security-releases/cve-2019-12203/',
245
                        'cve' => 'CVE-2019-12203',
246
                    ],
247
                    [
248
                        'title' => 'CVE-2019-12246: Denial of Service on flush and development URL tools',
249
                        'link' => 'https://www.silverstripe.org/download/security-releases/cve-2019-12246',
250
                        'cve' => 'CVE-2019-12246',
251
                    ],
252
                    [
253
                        'title' => 'CVE-2019-14272: XSS in file titles managed through the CMS',
254
                        'link' => 'https://www.silverstripe.org/download/security-releases/cve-2019-14272/',
255
                        'cve' => 'CVE-2019-14272',
256
                    ],
257
                    [
258
                        'title' => 'CVE-2019-14273: Broken Access control on files',
259
                        'link' => 'https://www.silverstripe.org/download/security-releases/cve-2019-14273/',
260
                        'cve' => 'CVE-2019-14273',
261
                    ],
262
                    [
263
                        'title' => 'CVE-2019-16409: Secureassets and versionedfiles modules can expose versions of '
264
                            . 'protected files',
265
                        'link' => 'https://www.silverstripe.org/download/security-releases/cve-2019-16409/',
266
                        'cve' => 'CVE-2019-16409',
267
                    ],
268
                    [
269
                        'title' => 'CVE-2019-19325: XSS through non-scalar FormField attributes',
270
                        'link' => 'https://www.silverstripe.org/download/security-releases/cve-2019-19325/',
271
                        'cve' => 'CVE-2019-19325',
272
                    ],
273
                    [
274
                        'title' => 'CVE-2019-19326: Web Cache Poisoning through HTTPRequestBuilder',
275
                        'link' => 'https://www.silverstripe.org/download/security-releases/cve-2019-19326/',
276
                        'cve' => 'CVE-2019-19326',
277
                    ],
278
                    [
279
                        'title' => 'CVE-2019-5715: Reflected SQL Injection through Form and DataObject',
280
                        'link' => 'https://www.silverstripe.org/download/security-releases/ss-2018-021',
281
                        'cve' => 'CVE-2019-5715',
282
                    ],
283
                    [
284
                        'title' => 'CVE-2020-26138 FormField: with square brackets in field name skips validation',
285
                        'link' => 'https://www.silverstripe.org/download/security-releases/cve-2020-26138',
286
                        'cve' => 'CVE-2020-26138',
287
                    ],
288
                    [
289
                        'title' => 'CVE-2020-6164: Information disclosure on /interactive URL path',
290
                        'link' => 'https://www.silverstripe.org/download/security-releases/cve-2020-6164/',
291
                        'cve' => 'CVE-2020-6164',
292
                    ],
293
                    [
294
                        'title' => 'CVE-2021-25817 XXE: Vulnerability in CSSContentParser',
295
                        'link' => 'https://www.silverstripe.org/download/security-releases/cve-2021-25817',
296
                        'cve' => 'CVE-2021-25817',
297
                    ],
298
                    [
299
                        'title' => 'SS-2017-007: CSV Excel Macro Injection',
300
                        'link' => 'https://www.silverstripe.org/download/security-releases/ss-2017-007/',
301
                        'cve' => null,
302
                    ],
303
                    [
304
                        'title' => 'SS-2017-008: SQL injection in full text search of SilverStripe 4',
305
                        'link' => 'https://www.silverstripe.org/download/security-releases/ss-2017-008/',
306
                        'cve' => null,
307
                    ],
308
                    [
309
                        'title' => 'SS-2017-009: Users inadvertently passing sensitive data to LoginAttempt',
310
                        'link' => 'https://www.silverstripe.org/download/security-releases/ss-2017-009/',
311
                        'cve' => null,
312
                    ],
313
                    [
314
                        'title' => 'SS-2017-010: install.php discloses sensitive data by pre-populating DB credential '
315
                            . 'forms',
316
                        'link' => 'https://www.silverstripe.org/download/security-releases/ss-2017-010/',
317
                        'cve' => null,
318
                    ],
319
                    [
320
                        'title' => 'SS-2018-001: Privilege Escalation Risk in Member Edit form',
321
                        'link' => 'https://www.silverstripe.org/download/security-releases/ss-2018-001/',
322
                        'cve' => null,
323
                    ],
324
                    [
325
                        'title' => 'SS-2018-005: isDev and isTest unguarded',
326
                        'link' => 'https://www.silverstripe.org/download/security-releases/ss-2018-005/',
327
                        'cve' => null,
328
                    ],
329
                    [
330
                        'title' => 'SS-2018-008: BackURL validation bypass with malformed URLs',
331
                        'link' => 'https://www.silverstripe.org/download/security-releases/ss-2018-008/',
332
                        'cve' => null,
333
                    ],
334
                    [
335
                        'title' => 'SS-2018-010: Member disclosure in login form',
336
                        'link' => 'https://www.silverstripe.org/download/security-releases/ss-2018-010/',
337
                        'cve' => null,
338
                    ],
339
                    [
340
                        'title' => 'SS-2018-012: Uploaded PHP script execution in assets',
341
                        'link' => 'https://www.silverstripe.org/download/security-releases/ss-2018-012/',
342
                        'cve' => null,
343
                    ],
344
                    [
345
                        'title' => 'SS-2018-018: Database credentials disclosure during connection failure',
346
                        'link' => 'https://www.silverstripe.org/download/security-releases/ss-2018-018/',
347
                        'cve' => null,
348
                    ],
349
                    [
350
                        'title' => 'SS-2018-019: Possible denial of service attack vector when flushing',
351
                        'link' => 'https://www.silverstripe.org/download/security-releases/ss-2018-019/',
352
                        'cve' => null,
353
                    ],
354
                    [
355
                        'title' => 'SS-2018-020: Potential SQL vulnerability in PostgreSQL database connector',
356
                        'link' => 'https://www.silverstripe.org/download/security-releases/ss-2018-020/',
357
                        'cve' => null,
358
                    ],
359
                ],
360
            ],
361
            'symbiote/silverstripe-queuedjobs' => [
362
                'version' => '4.0.x-dev',
363
                'advisories' => [
364
                    [
365
                        'title' => 'CVE-2021-27938: XSS in CreateQueuedJobTask',
366
                        'link' => 'https://www.silverstripe.org/download/security-releases/cve-2021-27938',
367
                        'cve' => 'CVE-2021-27938',
368
                    ],
369
                ],
370
            ]
371
        ];
372
373
        if ($withDev) {
374
            $vulnerabilities = array_merge($vulnerabilities, [
375
                'phpunit/phpunit' => [
376
                    'version' => '5.0.10',
377
                    'advisories' => [
378
                        [
379
                            'title' => 'RCE vulnerability in phpunit',
380
                            'link' => 'https://nvd.nist.gov/vuln/detail/CVE-2017-9841',
381
                            'cve' => 'CVE-2017-9841',
382
                        ],
383
                    ],
384
                ],
385
            ]);
386
        }
387
        return $vulnerabilities;
388
    }
389
}
390