Issues (17)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Antibot/Domain/Antibot.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * antibot
5
 *
6
 * @category   Jkphl
7
 * @package    Jkphl\Antibot
8
 * @subpackage Jkphl\Antibot\Domain
9
 * @author     Joschi Kuphal <[email protected]> / @jkphl
10
 * @copyright  Copyright © 2020 Joschi Kuphal <[email protected]> / @jkphl
11
 * @license    http://opensource.org/licenses/MIT The MIT License (MIT)
12
 */
13
14
/***********************************************************************************
15
 *  The MIT License (MIT)
16
 *
17
 *  Copyright © 2020 Joschi Kuphal <[email protected]>
18
 *
19
 *  Permission is hereby granted, free of charge, to any person obtaining a copy of
20
 *  this software and associated documentation files (the "Software"), to deal in
21
 *  the Software without restriction, including without limitation the rights to
22
 *  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
23
 *  the Software, and to permit persons to whom the Software is furnished to do so,
24
 *  subject to the following conditions:
25
 *
26
 *  The above copyright notice and this permission notice shall be included in all
27
 *  copies or substantial portions of the Software.
28
 *
29
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
31
 *  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
32
 *  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
33
 *  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
34
 *  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35
 ***********************************************************************************/
36
37
namespace Jkphl\Antibot\Domain;
38
39
use Jkphl\Antibot\Domain\Contract\ValidationResultInterface;
40
use Jkphl\Antibot\Domain\Contract\ValidatorInterface;
41
use Jkphl\Antibot\Domain\Exceptions\BlacklistValidationException;
42
use Jkphl\Antibot\Domain\Exceptions\ErrorException;
43
use Jkphl\Antibot\Domain\Exceptions\InvalidArgumentException;
44
use Jkphl\Antibot\Domain\Exceptions\RuntimeException;
45
use Jkphl\Antibot\Domain\Exceptions\SkippedValidationException;
46
use Jkphl\Antibot\Domain\Exceptions\WhitelistValidationException;
47
use Jkphl\Antibot\Infrastructure\Model\InputElement;
48
use Psr\Http\Message\ServerRequestInterface;
49
use Psr\Log\LoggerAwareInterface;
50
use Psr\Log\LoggerInterface;
51
use Psr\Log\NullLogger;
52
53
/**
54
 * Antibot core
55
 *
56
 * @package    Jkphl\Antibot
57
 * @subpackage Jkphl\Antibot\Domain
58
 */
59
class Antibot implements LoggerAwareInterface
60
{
61
    /**
62
     * Session persistent, unique token
63
     *
64
     * @var string
65
     */
66
    protected $unique;
67
    /**
68
     * Antibot prefix
69
     *
70
     * @var string
71
     */
72
    protected $prefix;
73
    /**
74
     * Parameter scope nodes
75
     *
76
     * @var array
77
     */
78
    protected $scope = [];
79
    /**
80
     * Unique signature
81
     *
82
     * @var string
83
     */
84
    protected $signature;
85
    /**
86
     * Parameter prefix
87
     *
88
     * @var string
89
     */
90
    protected $parameterPrefix;
91
    /**
92
     * GET & POST data
93
     *
94
     * @var null|array
95
     */
96
    protected $data = null;
97
    /**
98
     * Validators
99
     *
100
     * @var ValidatorInterface[]
101
     */
102
    protected $validators = [];
103
    /**
104
     * Immutable instance
105
     *
106
     * @var bool
107
     */
108
    protected $immutable = false;
109
    /**
110
     * Logger
111
     *
112
     * @var LoggerInterface
113
     */
114
    protected $logger = null;
115
    /**
116
     * Default antibot prefix
117
     *
118
     * @var string
119
     */
120
    const DEFAULT_PREFIX = 'antibot';
121
122
    /**
123
     * Antibot constructor
124
     *
125
     * @param string $unique Session-persistent, unique key
126
     * @param string $prefix Prefix
127
     */
128 20
    public function __construct(string $unique, string $prefix = self::DEFAULT_PREFIX)
129
    {
130 20
        $this->unique = $unique;
131 20
        $this->prefix = $prefix;
132 20
        $this->logger = new NullLogger();
133 20
    }
134
135
    /**
136
     * Return the session persistent, unique token
137
     *
138
     * @return string Session persistent, unique token
139
     */
140 5
    public function getUnique(): string
141
    {
142 5
        return $this->unique;
143
    }
144
145
    /**
146
     * Return the prefix
147
     *
148
     * @return string Prefix
149
     */
150 2
    public function getPrefix(): string
151
    {
152 2
        return $this->prefix;
153
    }
154
155
    /**
156
     * Return the submitted Antibot data
157
     *
158
     * @return string[] Antibot data
159
     */
160 6
    public function getData(): ?array
161
    {
162 6
        $this->checkInitialized();
163
164 5
        return $this->data;
165
    }
166
167
    /**
168
     * Return the parameter prefix
169
     *
170
     * @return string Parameter prefix
171
     * @throws RuntimeException If Antibot needs to be initialized
172
     */
173 14
    public function getParameterPrefix(): string
174
    {
175 14
        $this->checkInitialized();
176
177 13
        return $this->parameterPrefix;
178
    }
179
180
    /**
181
     * Add a validator
182
     *
183
     * @param ValidatorInterface $validator Validator
184
     */
185 12
    public function addValidator(ValidatorInterface $validator): void
186
    {
187 12
        $this->checkImmutable();
188 12
        $this->validators[] = $validator;
189 12
    }
190
191
    /**
192
     * Validate a request
193
     *
194
     * @param ServerRequestInterface $request   Request
195
     * @param ValidationResultInterface $result Validation result
196
     *
197
     * @return ValidationResultInterface Validation result
198
     * @internal
199
     */
200 12
    public function validateRequest(
201
        ServerRequestInterface $request,
202
        ValidationResultInterface $result
203
    ): ValidationResultInterface {
204 12
        $this->logger->info('Start validation');
205 12
        $this->initialize($request);
206
207
        // Run through all validators (in order)
208
        /** @var ValidatorInterface $validator */
209 12
        foreach ($this->validators as $validator) {
210
            try {
211 11
                if (!$validator->validate($request, $this)) {
212 6
                    $result->setValid(false);
213
                }
214
215
                // If the validator skipped validation
216 9
            } catch (SkippedValidationException $e) {
217 3
                $result->addSkip($e->getMessage());
218
219
                // If the request failed a blacklist test
220 7
            } catch (BlacklistValidationException $e) {
221 1
                $result->addBlacklist($e->getMessage());
222 1
                $result->setValid(false);
223
224
                // If the request passed a whitelist test
225 6
            } catch (WhitelistValidationException $e) {
226 2
                $result->addWhitelist($e->getMessage());
227 2
                break;
228
229
                // If an error occured
230 4
            } catch (ErrorException $e) {
231 4
                $result->addError($e);
232 4
                $result->setValid(false);
233
            }
234
        }
235
236 12
        $this->logger->info('Finished validation');
237
238 12
        return $result;
239
    }
240
241
    /**
242
     * Create and return the raw armor input elements
243
     *
244
     * @param ServerRequestInterface $request Request
245
     *
246
     * @return InputElement[] Armor input elements
247
     */
248 5
    public function armorInputs(ServerRequestInterface $request): array
249
    {
250 5
        $this->initialize($request);
251 5
        $armor = [];
252
253
        // Run through all validators (in order)
254
        /** @var ValidatorInterface $validator */
255 5
        foreach ($this->validators as $validator) {
256 5
            $validatorArmor = $validator->armor($request, $this);
257 5
            if (!empty($validatorArmor)) {
258 5
                $armor = array_merge($armor, $validatorArmor);
259
            }
260
        }
261
262 5
        return $armor;
263
    }
264
265
    /**
266
     * Compare and sort validators
267
     *
268
     * @param ValidatorInterface $validator1 Validator 1
269
     * @param ValidatorInterface $validator2 Validator 2
270
     *
271
     * @return int Sorting
272
     */
273
    protected function sortValidators(ValidatorInterface $validator1, ValidatorInterface $validator2): int
274
    {
275
        $validatorPos1 = $validator1->getPosition();
276
        $validatorPos2 = $validator2->getPosition();
277
        if ($validatorPos1 == $validatorPos2) {
278
            return 0;
279
        }
280
281
        return ($validatorPos1 > $validatorPos2) ? 1 : -1;
282
    }
283
284
    /**
285
     * Pre-validation initialization
286
     *
287
     * @param ServerRequestInterface $request Request
288
     */
289 13
    protected function initialize(ServerRequestInterface $request): void
290
    {
291 13
        if (!$this->immutable) {
292 13
            $this->immutable = true;
293 13
            usort($this->validators, [$this, 'sortValidators']);
294 13
            $this->signature       = $this->calculateSignature();
295 13
            $this->parameterPrefix = $this->prefix.'_'.$this->signature;
296
        }
297 13
        $this->extractData($request);
298 13
    }
299
300
    /**
301
     * Calculate the unique signature
302
     *
303
     * @return string Signature
304
     */
305 13
    protected function calculateSignature(): string
306
    {
307 13
        $params = [$this->prefix, $this->validators];
308
309 13
        return sha1($this->unique.serialize($params));
310
    }
311
312
    /**
313
     * Extract the antibot data from GET and POST parameters
314
     *
315
     * @param ServerRequestInterface $request Request
316
     */
317 13
    protected function extractData(ServerRequestInterface $request): void
318
    {
319 13
        $get        = $this->extractScopedData($request->getQueryParams() ?? []);
320 13
        $post       = $this->extractScopedData($request->getParsedBody() ?? []);
0 ignored issues
show
It seems like $request->getParsedBody() ?? array() can also be of type object; however, Jkphl\Antibot\Domain\Antibot::extractScopedData() 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...
321 13
        $this->data = (($get !== null) || ($post !== null)) ? array_merge((array)$get, (array)$post) : null;
322 13
    }
323
324
    /**
325
     * Extract scoped data
326
     *
327
     * @param array $data Source data
328
     *
329
     * @return array|null Scoped data
330
     */
331 13
    protected function extractScopedData(array $data): ?array
332
    {
333
        // Run through all scope nodes
334 13
        foreach (array_merge($this->scope, [$this->getParameterPrefix()]) as $node) {
335 13
            if (!isset($data[$node])) {
336 13
                return null;
337
            }
338
339 4
            $data = $data[$node];
340
        }
341
342 4
        return $data;
343
    }
344
345
    /**
346
     * Check whether this Antibot instance is immutable
347
     *
348
     * @throws RuntimeException If the Antibot instance is immutable
349
     */
350 12
    protected function checkImmutable(): void
351
    {
352 12
        if ($this->immutable) {
353
            throw new RuntimeException(
354
                RuntimeException::ANTIBOT_IMMUTABLE_STR,
355
                RuntimeException::ANTIBOT_IMMUTABLE
356
            );
357
        }
358 12
    }
359
360
    /**
361
     * Check whether this Antibot instance is already initialized
362
     *
363
     * @throws RuntimeException If the Antibot instance still needs to be initialized
364
     */
365 15
    protected function checkInitialized(): void
366
    {
367
        // If the Antibot instance still needs to be initialized
368 15
        if (!$this->immutable) {
369 2
            throw new RuntimeException(
370 2
                RuntimeException::ANTIBOT_INITIALIZE_STR,
371 2
                RuntimeException::ANTIBOT_INITIALIZE
372
            );
373
        }
374 13
    }
375
376
    /**
377
     * Return the logger
378
     *
379
     * @return LoggerInterface Logger
380
     */
381 6
    public function getLogger(): LoggerInterface
382
    {
383 6
        return $this->logger;
384
    }
385
386
    /**
387
     * Sets a logger instance on the object
388
     *
389
     * @param LoggerInterface $logger Logger
390
     *
391
     * @return void
392
     */
393 12
    public function setLogger(LoggerInterface $logger): void
394
    {
395 12
        $this->logger = $logger;
396 12
    }
397
398
    /**
399
     * Set the parameter scope
400
     *
401
     * @param string[] ...$scope Parameter scope
402
     */
403 1
    public function setParameterScope(...$scope): void
404
    {
405
        // Run through all scope nodes
406 1
        foreach ($scope as $node) {
407 1
            if (!is_string($node) || empty($node)) {
408 1
                throw new InvalidArgumentException(
409 1
                    sprintf(InvalidArgumentException::INVALID_SCOPE_NODE_STR, $node),
410 1
                    InvalidArgumentException::INVALID_SCOPE_NODE
411
                );
412
            }
413
414 1
            $this->scope[] = $node;
415
        }
416 1
    }
417
418
    /**
419
     * Scope a set of parameters
420
     *
421
     * @param array $params Parameters
422
     *
423
     * @return array Scoped parameters
424
     */
425 1
    public function getScopedParameters(array $params): array
426
    {
427 1
        $params = [$this->getParameterPrefix() => $params];
428 1
        $scope  = $this->scope;
429 1
        while ($node = array_pop($scope)) {
430 1
            $params = [$node => $params];
431
        }
432
433 1
        return $params;
434
    }
435
}
436