Issues (7)

Security Analysis    no request data  

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

  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.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  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.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  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.
  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.
  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.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  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.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  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.
  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.
  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.
  Header Injection
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/Auth/Process/AttributeAddUsersGroups.php (1 issue)

Labels
Severity
1
<?php
2
3
/**
4
 * Does a reverse membership lookup on the logged in user,
5
 * looking for groups it is a member of and adds them to
6
 * a defined attribute, in DN format.
7
 *
8
 * @package simplesamlphp/simplesamlphp-module-ldap
9
 */
10
11
declare(strict_types=1);
12
13
namespace SimpleSAML\Module\ldap\Auth\Process;
14
15
use SimpleSAML\Assert\Assert;
16
use SimpleSAML\Error;
17
use SimpleSAML\Logger;
18
use SimpleSAML\Utils;
19
use Symfony\Component\Ldap\Adapter\ExtLdap\Query;
20
21
class AttributeAddUsersGroups extends BaseFilter
22
{
23
    /** @var string|null */
24
    protected ?string $searchUsername;
25
26
    /** @var string|null */
27
    protected ?string $searchPassword;
28
29
    /** @var string|null */
30
    protected ?string $product;
31
32
33
    /**
34
     * Initialize this filter.
35
     *
36
     * @param array<mixed> $config Configuration information about this filter.
37
     * @param mixed $reserved For future use.
38
     */
39
    public function __construct(array $config, $reserved)
40
    {
41
        parent::__construct($config, $reserved);
42
43
        // Get filter specific config options
44
        $this->searchUsername = $this->config->getOptionalString('search.username', null);
45
        $this->searchPassword = $this->config->getOptionalString('search.password', null);
46
        $this->product = $this->config->getOptionalString('ldap.product', null);
47
    }
48
49
50
    /**
51
     * LDAP search filters to be added to the base filters for this authproc-filter.
52
     * It's an array of key => value pairs that will be translated to (key=value) in the ldap query.
53
     *
54
     * @var array<mixed>
55
     */
56
    protected array $additional_filters;
57
58
59
    /**
60
     * This is run when the filter is processed by SimpleSAML.
61
     * It will attempt to find the current users groups using
62
     * the best method possible for the LDAP product. The groups
63
     * are then added to the request attributes.
64
     *
65
     * @throws \SimpleSAML\Error\Exception
66
     * @param array<mixed> &$state
67
     */
68
    public function process(array &$state): void
69
    {
70
        Assert::keyExists($state, 'Attributes');
71
72
        // Log the process
73
        Logger::debug(sprintf(
74
            '%s : Attempting to get the users groups...',
75
            $this->title,
76
        ));
77
78
        $this->additional_filters = $this->config->getOptionalArray('additional_filters', []);
79
80
        // Reference the attributes, just to make the names shorter
81
        $attributes = &$state['Attributes'];
82
        $map = &$this->attribute_map;
83
84
        // Get the users groups from LDAP
85
        $groups = $this->getGroups($attributes);
86
87
        // If there are none, do not proceed
88
        if (empty($groups)) {
89
            return;
90
        }
91
92
        // Make the array if it is not set already
93
        if (!isset($attributes[$map['groups']])) {
94
            $attributes[$map['groups']] = [];
95
        }
96
97
        // Must be an array, else cannot merge groups
98
        if (!is_array($attributes[$map['groups']])) {
99
            throw new Error\Exception(sprintf(
100
                '%s : The group attribute [%s] is not an array of group DNs. %s',
101
                $this->title,
102
                $map['groups'],
103
                $this->varExport($attributes[$map['groups']]),
104
            ));
105
        }
106
107
        // Add the users group(s)
108
        $group_attribute = &$attributes[$map['groups']];
109
        $group_attribute = array_merge($group_attribute, $groups);
110
        $group_attribute = array_unique($group_attribute);
111
112
        // All done
113
        Logger::debug(sprintf(
114
            '%s : Added users groups to the group attribute[%s]: %s',
115
            $this->title,
116
            $map['groups'],
117
            implode('; ', $groups),
118
        ));
119
    }
120
121
122
    /**
123
     * Will perform a search using the required attribute values from the user to
124
     * get their group membership, recursively.
125
     *
126
     * @throws \SimpleSAML\Error\Exception
127
     * @param array<mixed> $attributes
128
     * @return array<mixed>
129
     */
130
    protected function getGroups(array $attributes): array
131
    {
132
        // Log the request
133
        Logger::debug(sprintf(
134
            '%s : Checking for groups based on the best method for the LDAP product.',
135
            $this->title,
136
        ));
137
138
        $this->connector->bind($this->searchUsername, $this->searchPassword);
139
140
        $options = [
141
            'scope' => $this->config->getOptionalString('search.scope', Query::SCOPE_SUB),
142
            'timeout' => $this->config->getOptionalInteger('timeout', 3),
143
        ];
144
145
        // Reference the map, just to make the name shorter
146
        $map = &$this->attribute_map;
147
148
149
        // All map-properties are guaranteed to exist and have a default value
150
        $dn_attribute = $map['dn'];
151
        $return_attribute = $map['return'];
152
153
        // Based on the directory service, search LDAP for groups
154
        // If any attributes are needed, prepare them before calling search method
155
        switch ($this->product) {
156
            case 'ActiveDirectory':
157
                // Log the AD specific search
158
                Logger::debug(sprintf(
159
                    '%s : Searching LDAP using ActiveDirectory specific method.',
160
                    $this->title,
161
                ));
162
163
                // Make sure the defined DN attribute exists
164
                if (!isset($attributes[$dn_attribute])) {
165
                    Logger::warning(sprintf(
166
                        "%s : The DN attribute [%s] is not defined in the user's Attributes: %s",
167
                        $this->title,
168
                        $dn_attribute,
169
                        implode(', ', array_keys($attributes)),
170
                    ));
171
172
                    return [];
173
                }
174
175
                // Make sure the defined DN attribute has a value
176
                if (!isset($attributes[$dn_attribute][0]) || !$attributes[$dn_attribute][0]) {
177
                    Logger::warning(sprintf(
178
                        '%s : The DN attribute [%s] does not have a [0] value defined. %s',
179
                        $this->title,
180
                        $dn_attribute,
181
                        $this->varExport($attributes[$dn_attribute]),
182
                    ));
183
184
                    return [];
185
                }
186
187
                // Log the search
188
                $arrayUtils = new Utils\Arrays();
189
                Logger::debug(sprintf(
190
                    '%s : Searching ActiveDirectory group membership.'
191
                        . ' DN: %s DN Attribute: %s Member Attribute: %s Type Attribute: %s Type Value: %s Base: %s',
192
                    $this->title,
193
                    $attributes[$dn_attribute][0],
194
                    $dn_attribute,
195
                    $map['member'],
196
                    $map['type'],
197
                    $this->type_map['group'],
198
                    implode('; ', $arrayUtils->arrayize($this->searchBase)),
199
                ));
200
201
                $filter = sprintf(
202
                    "(&(%s=%s)(%s=%s))",
203
                    $map['type'],
204
                    $this->type_map['group'],
205
                    $map['member'] . ':1.2.840.113556.1.4.1941:',
206
                    $this->connector->escapeFilterValue($attributes[$dn_attribute][0], true),
0 ignored issues
show
The method escapeFilterValue() does not exist on SimpleSAML\Module\ldap\ConnectorInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to SimpleSAML\Module\ldap\ConnectorInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

206
                    $this->connector->/** @scrutinizer ignore-call */ 
207
                                      escapeFilterValue($attributes[$dn_attribute][0], true),
Loading history...
207
                );
208
209
                $entries = $this->connector->searchForMultiple(
210
                    $this->searchBase,
211
                    $filter,
212
                    $options,
213
                    true,
214
                );
215
216
                break;
217
            case 'OpenLDAP':
218
                // Log the OpenLDAP specific search
219
                Logger::debug(sprintf(
220
                    '%s : Searching LDAP using OpenLDAP specific method.',
221
                    $this->title,
222
                ));
223
224
                Logger::debug(sprintf(
225
                    '%s : Searching for groups in base [%s] with filter (%s=%s) and attributes %s',
226
                    $this->title,
227
                    implode(', ', $this->searchBase),
228
                    $map['memberOf'],
229
                    $attributes[$map['username']][0],
230
                    $map['member'],
231
                ));
232
233
                $filter = sprintf(
234
                    '(&(%s=%s))',
235
                    $map['memberOf'],
236
                    $attributes[$map['username']][0],
237
                );
238
239
                $entries = $this->connector->searchForMultiple(
240
                    $this->searchBase,
241
                    $filter,
242
                    $options,
243
                    true,
244
                );
245
246
                break;
247
            default:
248
                // Log the generic search
249
                Logger::debug(
250
                    sprintf('%s : Searching LDAP using the generic search method.', $this->title),
251
                );
252
253
                // Make sure the defined memberOf attribute exists
254
                Assert::keyExists(
255
                    $attributes,
256
                    $map['memberOf'],
257
                    sprintf(
258
                        "%s : The memberOf attribute [%s] is not defined in the user's attributes: [%s]",
259
                        $this->title,
260
                        $map['memberOf'],
261
                        implode(', ', array_keys($attributes)),
262
                    ),
263
                    Error\Exception::class,
264
                );
265
266
                // MemberOf must be an array of group DN's
267
                Assert::isArray(
268
                    $attributes[$map['memberOf']],
269
                    sprintf(
270
                        '%s : The memberOf attribute [%s] is not an array of group DNs;  %s',
271
                        $this->title,
272
                        $map['memberOf'],
273
                        $this->varExport($attributes[$map['memberOf']]),
274
                    ),
275
                    Error\Exception::class,
276
                );
277
278
                Logger::debug(sprintf(
279
                    '%s : Checking DNs for groups. DNs: %s Attributes: %s, %s Group Type: %s',
280
                    $this->title,
281
                    implode('; ', $attributes[$map['memberOf']]),
282
                    $map['memberOf'],
283
                    $map['type'],
284
                    $this->type_map['group'],
285
                ));
286
287
                // Search for the users group membership, recursively
288
                $entries = $this->search($attributes[$map['memberOf']], $options);
289
        }
290
291
        $groups = [];
292
        foreach ($entries as $entry) {
293
            if ($entry->hasAttribute($return_attribute)) {
294
                $values = $entry->getAttribute($return_attribute);
295
                $groups[] = array_pop($values);
296
                continue;
297
            } elseif ($entry->hasAttribute(strtolower($return_attribute))) {
298
                // Some backends return lowercase attributes
299
                $values = $entry->getAttribute(strtolower($return_attribute));
300
                $groups[] = array_pop($values);
301
                continue;
302
            }
303
304
            // Could not find return attribute, log and continue
305
            Logger::debug(sprintf(
306
                '%s : The return attribute [%s] could not be found in entry `%s`.',
307
                $this->title,
308
                implode(', ', array_unique([$map['return'], strtolower($map['return'])])),
309
                $entry->getDn(),
310
            ));
311
            Logger::debug(sprintf('%s : Entry was: %s', $this->title, $this->varExport($entry)));
312
        }
313
314
        // All done
315
        Logger::debug(sprintf(
316
            '%s : User found to be a member of the following groups: %s',
317
            $this->title,
318
            empty($groups) ? 'none' : implode('; ', $groups),
319
        ));
320
321
        return $groups;
322
    }
323
324
325
    /**
326
     * Looks for groups from the list of DN's passed. Also
327
     * recursively searches groups for further membership.
328
     * Avoids loops by only searching a DN once. Returns
329
     * the list of groups found.
330
     *
331
     * @param array<mixed> $memberOf
332
     * @param array<mixed> $options
333
     * @return array<mixed>
334
     */
335
    protected function search(array $memberOf, array $options): array
336
    {
337
        // Shorten the variable name
338
        $map = &$this->attribute_map;
339
340
        // Used to determine what DN's have already been searched
341
        static $searched = [];
342
343
        // Init the groups variable
344
        $entries = [];
345
346
        // Set scope to 'base'
347
        $options['scope'] = Query::SCOPE_BASE;
348
349
        // Check each DN of the passed memberOf
350
        foreach ($memberOf as $dn) {
351
            // Avoid infinite loops, only need to check a DN once
352
            if (isset($searched[$dn])) {
353
                continue;
354
            }
355
356
            // Track all DN's that are searched
357
            // Use DN for key as well, isset() is faster than in_array()
358
            $searched[$dn] = $dn;
359
360
            // Query LDAP for the attribute values for the DN
361
            $entry = $this->connector->search(
362
                [$dn],
363
                sprintf("(%s=%s)", $map['type'], $this->type_map['group']),
364
                $options,
365
                true,
366
            );
367
368
            if ($entry === null) {
369
                // Probably the DN does not exist within the given search base
370
                continue;
371
            }
372
373
            // Add to found groups array
374
            $entries[] = $entry;
375
376
            // Recursively search "sub" groups
377
            $subGroups = $entry->getAttribute($map['memberOf']);
378
            if (!empty($subGroups)) {
379
                $entries = array_merge($entries, $this->search($subGroups, $options));
380
            }
381
        }
382
383
        return $entries;
384
    }
385
}
386