Issues (63)

Security Analysis    not enabled

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.

Command/ConfigCommand.php (1 issue)

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
 * This file is part of the LdapToolsBundle package.
4
 *
5
 * (c) Chad Sikorra <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace LdapTools\Bundle\LdapToolsBundle\Command;
12
13
use LdapTools\Bundle\LdapToolsBundle\Factory\LdapFactory;
14
use LdapTools\Connection\LdapConnection;
15
use LdapTools\Connection\LdapServerPool;
16
use LdapTools\DomainConfiguration;
17
use LdapTools\Exception\LdapConnectionException;
18
use LdapTools\Operation\AuthenticationOperation;
19
use LdapTools\Operation\AuthenticationResponse;
20
use LdapTools\Utilities\LdapUtilities;
21
use Symfony\Component\Console\Command\Command;
22
use Symfony\Component\Console\Helper\QuestionHelper;
23
use Symfony\Component\Console\Input\InputInterface;
24
use Symfony\Component\Console\Input\InputOption;
25
use Symfony\Component\Console\Output\OutputInterface;
26
use Symfony\Component\Console\Question\ChoiceQuestion;
27
use Symfony\Component\Console\Question\ConfirmationQuestion;
28
use Symfony\Component\Console\Question\Question;
29
use Symfony\Component\Yaml\Yaml;
30
31
/**
32
 * Assists in generating the LdapTools configuration for the bundle.
33
 *
34
 * @author Chad Sikorra <[email protected]>
35
 */
36
class ConfigCommand extends Command
37
{
38
    /**
39
     * @var OutputInterface
40
     */
41
    protected $output;
42
43
    /**
44
     * @var InputInterface
45
     */
46
    protected $input;
47
48
    /**
49
     * @var QuestionHelper
50
     */
51
    protected $helper;
52
53
    /**
54
     * @var bool
55
     */
56
    protected $silent = false;
57
58
    /**
59
     * @var bool
60
     */
61
    protected $interactive = true;
62
63
    /**
64
     * @var LdapServerPool
65
     */
66
    protected $serverPool;
67
68
    /**
69
     * @var LdapFactory
70
     */
71
    protected $factory;
72
73
    /**
74
     * @param null|string $name
75
     */
76
    public function __construct($name = null)
77
    {
78
        $this->factory = new LdapFactory();
79
        parent::__construct($name);
80
    }
81
82
    /**
83
     * @param LdapServerPool $serverPool
84
     */
85
    public function setLdapServerPool(LdapServerPool $serverPool)
86
    {
87
        $this->serverPool = $serverPool;
88
    }
89
90
    /**
91
     * @param LdapFactory $factory
92
     */
93
    public function setConnectionFactory(LdapFactory $factory)
94
    {
95
        $this->factory = $factory;
96
    }
97
98
    /**
99
     * {@inheritDoc}
100
     */
101
    protected function configure()
102
    {
103
        $this
104
            ->setName('ldaptools:generate:config')
105
            ->addOption('domain', null, InputOption::VALUE_OPTIONAL, 'The LDAP domain name (ie. domain.local).')
106
            ->addOption('username', null, InputOption::VALUE_OPTIONAL, 'The LDAP username.')
107
            ->addOption('password', null, InputOption::VALUE_OPTIONAL, 'The LDAP password.')
108
            ->addOption('server', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The LDAP server to use.')
109
            ->addOption('port', null, InputOption::VALUE_OPTIONAL, 'The LDAP server port to use.', 389)
110
            ->addOption('use-tls', null, InputOption::VALUE_NONE, 'Whether or not TLS should be used for the connection.')
111
            ->addOption('use-ssl', null, InputOption::VALUE_NONE, 'Whether or not to use SSL (TLS over port 636).')
112
            ->addOption('silent', null, InputOption::VALUE_NONE, 'Only the YAML config will be displayed.')
113
            ->addOption('non-interactive', null, InputOption::VALUE_NONE, 'No prompts or questions. Requires: server/domain, username, password')
114
            ->addOption('show-config', null, InputOption::VALUE_NONE, 'Show the config at the end without prompting.')
115
            ->setDescription('Assists in generating the base LDAP YAML configuration needed for the bundle.');
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121
    public function execute(InputInterface $input, OutputInterface $output)
122
    {
123
        $config = null;
124
        $connection = null;
125
126
        $this->input = $input;
127
        $this->output = $output;
128
        $this->helper = $this->getHelper('question');
129
        $this->silent = $input->getOption('silent');
130
        $this->interactive = !$input->getOption('non-interactive');
131
        $this->serverPool = $this->serverPool ?: new LdapServerPool(new DomainConfiguration(''));
132
133
        $domain = trim($input->getOption('domain'));
134
        $server = $input->getOption('server');
135
        $port = (int) $input->getOption('port');
136
        $username = trim($input->getOption('username'));
137
        $password = $input->getOption('password');
138
        $useTls = $input->getOption('use-tls');
139
        $useSsl = $input->getOption('use-ssl');
140
        $this->validateOptions($server, $domain, $username, $password, $useTls, $useSsl);
141
142
        while (!$connection) {
143
            $config = $this->getConfigForDomain($server, $domain, $port, $useTls, $useSsl);
144
            if (!$config && (!$this->interactive || !$this->confirm('<question>Try again? [Y/n]: </question>'))) {
145
                return 1;
146
            } elseif (!$config) {
147
                continue;
148
            }
149
            $connection = $this->getLdapConnection($config);
150
            if (!$connection && (!$this->interactive || !$this->confirm('<question>Try again? [Y/n]: </question>'))) {
151
                return 1;
152
            }
153
        }
154
        if (!$useTls && !$useSsl && $this->interactive) {
155
            while (!$this->chooseEncryption($connection)) {
156
                if ($this->confirm('<question>Continue without encryption? [Y/n]: </question>')) {
157
                    break;
158
                }
159
            }
160
        }
161
162
        while (!$this->verifyCredentials($connection, $username, $password)) {
163
            if (!$this->confirm('<question>Try a different username/password? [Y/n]: </question>')) {
164
                return 1;
165
            }
166
            $username = null;
167
            $password = null;
168
        }
169
170
        $this->askAndShowConfig($config, $input, $output);
171
172
        return 0;
173
    }
174
175
    /**
176
     * @param string $server
177
     * @param string $domain
178
     * @param string $username
179
     * @param string $password
180
     * @param bool $useTls
181
     * @param bool $useSsl
182
     */
183
    protected function validateOptions($server, $domain, $username, $password, $useTls, $useSsl)
184
    {
185
        if ($useTls && $useSsl) {
186
            throw new \LogicException('You cannot use both the ssl and tls option. Generally you want --use-tls.');
187
        }
188
        if (!$this->interactive && !$server && !$domain) {
189
            throw new \LogicException('You must enter a server or domain when not in interactive mode.');
190
        }
191
        if (!$this->interactive && (!$username || !$password)) {
192
            throw new \LogicException('You must enter a username and password when not in interactive mode.');
193
        }
194
    }
195
196
    /**
197
     * @param string|null $server
198
     * @param string|null $domain
199
     * @param int|null $port
200
     * @param bool $useTls
201
     * @param bool $useSsl
202
     * @return DomainConfiguration|null
203
     */
204
    protected function getConfigForDomain($server, $domain, $port, $useTls, $useSsl)
205
    {
206
        $config = $this->factory->getConfig($domain);
207
        $config->setLazyBind(true)
208
            ->setUseTls($useTls)
209
            ->setUseSsl($useSsl);
210
        $config = $this->setServerOrDomain($config, $server, $domain);
211
        if ($port) {
212
            $config->setPort($port);
213
        }
214
        $this->serverPool->setConfig($config);
215
216
        try {
217
            $config->setServers([$this->serverPool->getServer()]);
218
        } catch (LdapConnectionException $e) {
219
            if (!empty($config->getServers())) {
220
                $this->writeln(sprintf('<error>Cannot connect to LDAP server %s on port %s.</error>', $config->getServers()[0], $config->getPort()));
221
            } else {
222
                $this->writeln(sprintf('<error>Cannot find any LDAP severs for domain: %s</error>', $domain));
223
            }
224
225
            return null;
226
        }
227
        $this->writeln(sprintf('<info>Server %s is responding on port %s.</info>', $config->getServers()[0], $config->getPort()));
228
229
        return $config;
230
    }
231
232
    /**
233
     * @param DomainConfiguration $config
234
     * @param string|null $server
235
     * @param string|null $domain
236
     * @return DomainConfiguration|null
237
     */
238
    protected function setServerOrDomain(DomainConfiguration $config, $server, $domain)
239
    {
240
        while (!$domain && !$server) {
241
            if (!$server && $this->interactive) {
242
                $defaultServer = $this->getDefaultServerName();
243
                $server = $this->promptForResponse(
244
                    'Enter a server name '
245
                    . ($defaultServer ? "[$defaultServer]" : '(Leave empty to attempt lookup via domain name)') . ': ',
246
                    $defaultServer
247
                );
248
            }
249
            if (!$server && !$domain && $this->interactive) {
250
                $defaultDomain = $this->getDefaultDomainName();
251
                $domain = $this->promptForResponse('Enter a domain name' . ($defaultDomain ? " [$defaultDomain]" : '') . ': ', $defaultDomain);
252
            }
253
            if (!$domain && !$server) {
254
                $this->writeln('<error>You must enter a domain name or server name.</error>');
255
                if (!$this->interactive) {
256
                    return null;
257
                }
258
            }
259
        }
260
261
        if ($server) {
262
            $config->setServers(is_array($server) ? $server : [$server]);
263
        }
264
        if ($domain) {
265
            $config->setDomainName($domain);
266
        }
267
268
        return $config;
269
    }
270
271
    /**
272
     * @param DomainConfiguration $config
273
     * @return LdapConnection|null
274
     */
275
    protected function getLdapConnection(DomainConfiguration $config)
276
    {
277
        $connection = $this->factory->getConnection($config);
278
279
        try {
280
            $rootDse = $connection->getRootDse();
281
        } catch (\Exception $e) {
282
            $this->writeln(sprintf(
283
                '<error>Unable to query the RootDSE. %s</error>',
284
                $e->getMessage()
285
            ));
286
287
            return null;
288
        }
289
290
        $baseDn = null;
0 ignored issues
show
$baseDn is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
291
        if ($rootDse->has('defaultNamingContext')) {
292
            $baseDn = $rootDse->get('defaultNamingContext');
293
        } else {
294
            $contexts = $rootDse->get('namingContexts');
295
            $baseDn = is_array($contexts) ? $contexts[0] : $contexts;
296
        }
297
        $config->setBaseDn($baseDn)->setDomainName(implode('.', LdapUtilities::explodeDn($baseDn)));
298
        $this->writeln(sprintf('<info>Successfully connected to: %s</info>', $config->getDomainName()));
299
300
        return $connection;
301
    }
302
303
    /**
304
     * @param LdapConnection $connection
305
     * @param string|null $username
306
     * @param string|null $password
307
     * @return bool
308
     */
309
    protected function verifyCredentials(LdapConnection $connection, $username, $password)
310
    {
311
        $username = $this->getConnectionUsername($username);
312
        $password = $this->getConnectionPassword($username, $password);
313
314
        /** @var AuthenticationResponse $response */
315
        $response = $connection->execute(new AuthenticationOperation($username, $password));
316
        if ($response->isAuthenticated()) {
317
            $connection->getConfig()->setUsername($username)->setPassword($password);
318
            $this->writeln(sprintf('<info>Successfully authenticated with user: %s</info>', $username));
319
        } else {
320
            $this->writeln(sprintf('<error>Unable to authenticate to LDAP: %s</error>', $response->getErrorMessage()));
321
        }
322
323
        return $response->isAuthenticated();
324
    }
325
326
    /**
327
     * @param $username
328
     * @return string
329
     */
330
    protected function getConnectionUsername($username)
331
    {
332
        if (!$username && $this->interactive) {
333
            $defaultUser = $this->getDefaultUsername();
334
            $username = $this->promptForResponse('Enter the LDAP username' . ($defaultUser ? " [$defaultUser]" : '') . ': ', $defaultUser, false, function ($value) {
335
                if (trim($value) === '') {
336
                    throw new \Exception('The username cannot be empty.');
337
                }
338
339
                return $value;
340
            });
341
        }
342
343
        return $username;
344
    }
345
346
    /**
347
     * @param $username
348
     * @param $password
349
     * @return string
350
     */
351
    protected function getConnectionPassword($username, $password)
352
    {
353
        $validatePassword = function ($value) {
354
            if (empty($value)) {
355
                throw new \InvalidArgumentException('The password cannot be empty.');
356
            }
357
358
            return $value;
359
        };
360
361
        while (!$password && $this->interactive) {
362
            $original = $this->promptForResponse(sprintf('Enter the LDAP password for %s: ', $username), null, true, $validatePassword);
363
            $verified = $this->promptForResponse('Enter the password again to confirm: ', null, true, $validatePassword);
364
            if ($original !== $verified) {
365
                $this->writeln('<error>Passwords do not match. Please enter them again.</error>');
366
            } else {
367
                $password = $verified;
368
            }
369
        }
370
371
        return $password;
372
    }
373
374
    /**
375
     * @param LdapConnection $connection
376
     * @return bool
377
     */
378
    protected function chooseEncryption(LdapConnection $connection)
379
    {
380
        $question = new ChoiceQuestion(
381
            'Encryption is currently not enabled for this connection. Please make a selection [TLS]: ',
382
            ['TLS', 'SSL', 'None'],
383
            '0'
384
        );
385
        $answer = $this->helper->ask($this->input, $this->output, $question);
386
387
        if ($answer === 'None') {
388
            return true;
389
        }
390
        $useSsl = $connection->getConfig()->getUseSsl();
391
        $useTls = $connection->getConfig()->getUseTls();
392
393
        if ($answer === 'TLS') {
394
            $connection->getConfig()->setUseTls(true);
395
        } else {
396
            $connection->getConfig()->setUseSsl(true);
397
        }
398
399
        $success = false;
400
        try {
401
            // RootDSE is cached in the connection initially. Make sure to use a new connection...
402
            $success = (bool) $this->factory->getConnection($connection->getConfig())->getRootDse();
403
            $this->writeln(sprintf('<info>Connected to LDAP via %s.</info>', $answer));
404
        } catch (\Exception $e) {
405
            $this->writeln(sprintf('<error>Error connecting via %s. %s</error>', $answer, $e->getMessage()));
406
        } finally {
407
            if (!$success) {
408
                $connection->getConfig()->setUseSsl($useSsl);
409
                $connection->getConfig()->setUseTls($useTls);
410
            }
411
        }
412
413
        return $success;
414
    }
415
416
    protected function askAndShowConfig(DomainConfiguration $config, InputInterface $input, OutputInterface $output)
417
    {
418
        if ($input->getOption('show-config') || $this->confirm('<question>Show the generated config (includes password)? [Y/n]: </question>')) {
419
            $this->writeln('');
420
            $output->writeln(Yaml::dump($this->getYamlArrayFromConfig($config), 4));
421
        }
422
    }
423
424
    /**
425
     * @param DomainConfiguration $config
426
     * @return array
427
     */
428
    protected function getAllLdapServersForDomain(DomainConfiguration $config)
429
    {
430
        $server = strtolower($config->getServers()[0]);
431
        // Slice the array to 5, as it's possible for a large amount of LDAP servers...
432
        $servers = array_map('strtolower', array_slice(
433
            LdapUtilities::getLdapServersForDomain($config->getDomainName()),
434
            0,
435
            5
436
        ));
437
438
        // We want to make sure to pop the tested server to the front...
439
        $pos = array_search($server, $servers);
440
        if ($pos !== false) {
441
            unset($servers[$pos]);
442
        }
443
        // But if we have a FQDN version, prefer that...
444
        $pos = array_search($server.'.'.strtolower($config->getDomainName()), $servers);
445
        if ($pos !== false) {
446
            unset($servers[$pos]);
447
            $server .= '.'.$config->getDomainName();
448
        }
449
        array_unshift($servers, $server);
450
451
        return $servers;
452
    }
453
454
    /**
455
     * @return string|null
456
     */
457
    protected function getDefaultDomainName()
458
    {
459
        return isset($_SERVER['USERDNSDOMAIN']) ? $_SERVER['USERDNSDOMAIN'] : null;
460
    }
461
462
    /**
463
     * @return string|null
464
     */
465
    protected function getDefaultServerName()
466
    {
467
        return isset($_SERVER['LOGONSERVER']) ? ltrim($_SERVER['LOGONSERVER'], '\\') : null;
468
    }
469
470
    /**
471
     * @return string|null
472
     */
473
    protected function getDefaultUsername()
474
    {
475
        $user = null;
476
477
        if (isset($_SERVER['USERNAME'])) {
478
            $user = $_SERVER['USERNAME'];
479
        } elseif ($_SERVER['USER']) {
480
            $user = $_SERVER['USER'];
481
        }
482
483
        return $user;
484
    }
485
486
    /**
487
     * @param DomainConfiguration $config
488
     * @return array
489
     */
490
    protected function getYamlArrayFromConfig(DomainConfiguration $config)
491
    {
492
        $domainCfg = [
493
            'domain_name' => $config->getDomainName(),
494
            'base_dn' => $config->getBaseDn(),
495
            'username' => $config->getUsername(),
496
            'password' => $config->getPassword(),
497
            'servers' => $this->getAllLdapServersForDomain($config),
498
        ];
499
        if ($config->getPort() !== 389) {
500
            $domainCfg['port'] = $config->getPort();
501
        }
502
        if ($config->getUseTls()) {
503
            $domainCfg['use_tls'] = true;
504
        }
505
        if ($config->getUseSsl()) {
506
            $domainCfg['use_ssl'] = true;
507
        }
508
509
        return [
510
            'ldap_tools' => [
511
                'domains' => [
512
                    $config->getDomainName() => $domainCfg
513
                ]
514
            ]
515
        ];
516
    }
517
518
    /**
519
     * @param string $message
520
     * @param bool $default
521
     * @return bool
522
     */
523
    protected function confirm($message, $default = true)
524
    {
525
        if (!$this->interactive) {
526
            return false;
527
        }
528
529
        return $this->helper->ask($this->input, $this->output, new ConfirmationQuestion($message, $default));
530
    }
531
532
    /**
533
     * @param string $message
534
     * @param null|string $default
535
     * @param bool $hide
536
     * @param null|callable $validator
537
     * @return string
538
     */
539
    protected function promptForResponse($message, $default = null, $hide = false, $validator = null)
540
    {
541
        $question =  (new Question($message, $default))->setHidden($hide);
542
        if ($validator) {
543
            $question->setValidator($validator);
544
        }
545
546
        return $this->helper->ask($this->input, $this->output, $question);
547
    }
548
549
    /**
550
     * @param $message
551
     */
552
    protected function writeln($message)
553
    {
554
        if (!$this->silent) {
555
            $this->output->writeln($message);
556
        }
557
    }
558
}
559