Issues (182)

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.

SamlSPBundle/Bridge/AssertionConsumer.php (4 issues)

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
namespace AerialShip\SamlSPBundle\Bridge;
4
5
use AerialShip\LightSaml\Bindings;
6
use AerialShip\LightSaml\Model\Assertion\Assertion;
7
use AerialShip\LightSaml\Model\Protocol\Response;
8
use AerialShip\LightSaml\Model\XmlDSig\SignatureXmlValidator;
9
use AerialShip\LightSaml\Security\KeyHelper;
10
use AerialShip\SamlSPBundle\Config\ServiceInfo;
11
use AerialShip\SamlSPBundle\Config\ServiceInfoCollection;
12
use AerialShip\SamlSPBundle\RelyingParty\RelyingPartyInterface;
13
use AerialShip\SamlSPBundle\State\Request\RequestStateStoreInterface;
14
use AerialShip\SamlSPBundle\State\SSO\SSOStateStoreInterface;
15
use Symfony\Component\HttpFoundation\Request;
16
use Symfony\Component\Security\Core\Exception\AuthenticationException;
17
18
class AssertionConsumer implements RelyingPartyInterface
19
{
20
    /** @var BindingManager  */
21
    protected $bindingManager;
22
23
    /** @var  ServiceInfoCollection */
24
    protected $serviceInfoCollection;
25
26
    /** @var  RequestStateStoreInterface */
27
    protected $requestStore;
28
29
    /** @var SSOStateStoreInterface  */
30
    protected $ssoStore;
31
32
33
34
35
    public function __construct(
36
        BindingManager $bindingManager,
37
        ServiceInfoCollection $serviceInfoCollection,
38
        RequestStateStoreInterface $requestStore,
39
        SSOStateStoreInterface $ssoStore
40
    ) {
41
        $this->bindingManager = $bindingManager;
42
        $this->serviceInfoCollection = $serviceInfoCollection;
43
        $this->requestStore = $requestStore;
44
        $this->ssoStore = $ssoStore;
45
    }
46
47
48
49
    /**
50
     * @param \Symfony\Component\HttpFoundation\Request $request
51
     * @return bool
52
     */
53
    public function supports(Request $request)
54
    {
55
        $result = $request->attributes->get('check_path') == $request->getPathInfo();
56
        return $result;
57
    }
58
59
    /**
60
     * @param \Symfony\Component\HttpFoundation\Request $request
61
     * @throws \RuntimeException
62
     * @throws \Symfony\Component\Security\Core\Exception\AuthenticationException
63
     * @throws \InvalidArgumentException if cannot manage the Request
64
     * @return \Symfony\Component\HttpFoundation\RedirectResponse|SamlSpInfo
65
     */
66
    public function manage(Request $request)
67
    {
68
        if (!$this->supports($request)) {
69
            throw new \InvalidArgumentException();
70
        }
71
72
        $response = $this->getSamlResponse($request);
73
        $serviceInfo = $this->serviceInfoCollection->findByIDPEntityID($response->getIssuer());
74
        
75
        if (!$serviceInfo) {
76
            throw new \RuntimeException('Could not find ServiceProvider with entity id: '.$response->getIssuer());
77
        }
78
79
        $serviceInfo->getSpProvider()->setRequest($request);
80
        $this->validateResponse($serviceInfo, $response);
81
82
        $assertion = $this->getSingleAssertion($response);
83
84
        $this->createSSOState($serviceInfo, $assertion);
0 ignored issues
show
It seems like $assertion defined by $this->getSingleAssertion($response) on line 82 can be null; however, AerialShip\SamlSPBundle\...sumer::createSSOState() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
85
86
        return new SamlSpInfo(
87
            $serviceInfo->getAuthenticationService(),
88
            $assertion->getSubject()->getNameID(),
89
            $assertion->getAllAttributes(),
90
            $assertion->getAuthnStatement()
91
        );
92
    }
93
94
95
    protected function getSamlResponse(Request $request)
96
    {
97
        $bindingType = null;
98
        /** @var Response $response */
99
        $response = $this->bindingManager->receive($request, $bindingType);
100
        if ($bindingType == Bindings::SAML2_HTTP_REDIRECT) {
101
            throw new \RuntimeException('SAML protocol response cannot be sent via binding HTTP REDIRECT');
102
        }
103
        if (!$response instanceof Response) {
104
            throw new \RuntimeException('Expected Protocol/Response type but got '.($response ? get_class($response) : 'nothing'));
105
        }
106
107
        return $response;
108
    }
109
110
    /**
111
     * @param Response $response
112
     * @return Assertion
113
     * @throws \RuntimeException
114
     */
115
    protected function getSingleAssertion(Response $response)
116
    {
117
        $arr = $response->getAllAssertions();
118
        if (empty($arr)) {
119
            throw new \RuntimeException('No assertion received');
120
        }
121
        $assertion = array_pop($arr);
122
123
        return $assertion;
124
    }
125
126
127
    protected function createSSOState(ServiceInfo $serviceInfo, Assertion $assertion)
128
    {
129
        $ssoState = $this->ssoStore->create();
130
        $ssoState->setNameID($assertion->getSubject()->getNameID()->getValue());
131
        $ssoState->setNameIDFormat($assertion->getSubject()->getNameID()->getFormat() ?: '');
132
        $ssoState->setAuthenticationServiceName($serviceInfo->getAuthenticationService());
133
        $ssoState->setProviderID($serviceInfo->getProviderID());
134
        $ssoState->setSessionIndex($assertion->getAuthnStatement()->getSessionIndex());
135
        $this->ssoStore->set($ssoState);
136
137
        return $ssoState;
138
    }
139
140
141
    protected function validateResponse(ServiceInfo $metaProvider, Response $response)
142
    {
143
        if (!$metaProvider) {
144
            throw new \RuntimeException('Unknown issuer '.$response->getIssuer());
145
        }
146
        $this->validateState($response);
147
        $this->validateStatus($response);
148
        $this->validateResponseSignature($metaProvider, $response);
149
        foreach ($response->getAllAssertions() as $assertion) {
150
            $this->validateAssertion($metaProvider, $assertion);
151
        }
152
    }
153
154
    protected function validateState(Response $response)
155
    {
156
        if ($response->getInResponseTo()) {
157
            $requestState = $this->requestStore->get($response->getInResponseTo());
158
            if (!$requestState) {
159
                throw new \RuntimeException('Got response to a request that was not made');
160
            }
161
            if ($requestState->getDestination() != $response->getIssuer()) {
162
                throw new \RuntimeException('Got response from different issuer');
163
            }
164
            $this->requestStore->remove($requestState);
165
        }
166
    }
167
168
    protected function validateStatus(Response $response)
169
    {
170
        if (!$response->getStatus()->isSuccess()) {
171
            $status = $response->getStatus()->getStatusCode()->getValue();
172
            $status .= "\n".$response->getStatus()->getMessage();
173
            if ($response->getStatus()->getStatusCode()->getChild()) {
174
                $status .= "\n".$response->getStatus()->getStatusCode()->getChild()->getValue();
175
            }
176
            throw new AuthenticationException('Unsuccessful SAML response: '.$status);
177
        }
178
    }
179
180
    protected function validateResponseSignature(ServiceInfo $serviceInfo, Response $response)
181
    {
182
        /** @var  $signature SignatureXmlValidator */
183
        if ($signature = $response->getSignature()) {
184
            $keys = $this->getAllKeys($serviceInfo);
185
            $signature->validateMulti($keys);
186
        }
187
    }
188
189
    protected function validateAssertion(ServiceInfo $serviceInfo, Assertion $assertion)
190
    {
191
        $this->validateAssertionSignature($assertion, $serviceInfo);
192
        $this->validateAssertionTime($assertion);
193
        $this->validateAssertionSubjectTime($assertion);
194
        $this->validateSubjectConfirmationRecipient($assertion, $serviceInfo);
195
    }
196
197
    protected function validateAssertionSignature(Assertion $assertion, ServiceInfo $serviceInfo)
198
    {
199
        /** @var  $signature SignatureXmlValidator */
200
        if ($signature = $assertion->getSignature()) {
201
            $keys = $this->getAllKeys($serviceInfo);
202
            $signature->validateMulti($keys);
203
        } else {
204
            throw new AuthenticationException('Assertion must be signed');
205
        }
206
    }
207
208
209
    /**
210
     * @param Assertion $assertion
211
     * @throws \Symfony\Component\Security\Core\Exception\AuthenticationException
212
     */
213
    protected function validateAssertionTime(Assertion $assertion)
214
    {
215
        if ($assertion->getNotBefore() && $assertion->getNotBefore() > time() + 60) {
216
            throw new AuthenticationException('Received an assertion that is valid in the future. Check clock synchronization on IdP and SP');
217
        }
218
        if ($assertion->getNotOnOrAfter() && $assertion->getNotOnOrAfter() <= time() - 60) {
219
            throw new AuthenticationException('Received an assertion that has expired. Check clock synchronization on IdP and SP');
220
        }
221
    }
222
223
    /**
224
     * @param Assertion $assertion
225
     * @throws \Symfony\Component\Security\Core\Exception\AuthenticationException
226
     */
227
    protected function validateAssertionSubjectTime(Assertion $assertion)
228
    {
229
        $arrSubjectConfirmations = $assertion->getSubject()->getSubjectConfirmations();
230
        if ($arrSubjectConfirmations) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $arrSubjectConfirmations of type AerialShip\LightSaml\Mod...n\SubjectConfirmation[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
231
            foreach ($arrSubjectConfirmations as $subjectConfirmation) {
232
                if ($data = $subjectConfirmation->getData()) {
233
                    if ($data->getNotBefore() && $data->getNotBefore() > time() + 60) {
234
                        throw new AuthenticationException('Received an assertion with a session valid in future. Check clock synchronization on IdP and SP');
235
                    }
236
                    if ($data->getNotOnOrAfter() && $data->getNotOnOrAfter() <= time() - 60) {
237
                        throw new AuthenticationException('Received an assertion with a session that has expired. Check clock synchronization on IdP and SP');
238
                    }
239
                }
240
            }
241
        }
242
    }
243
244
245
    protected function validateSubjectConfirmationRecipient(Assertion $assertion, ServiceInfo $serviceInfo)
246
    {
247
        $arrACS = $serviceInfo->getSpProvider()
248
                ->getEntityDescriptor()
249
                ->getFirstSpSsoDescriptor()
250
                ->findAssertionConsumerServices();
251
        foreach ($assertion->getSubject()->getSubjectConfirmations() as $subjectConfirmation) {
252
            $ok = false;
253
            foreach ($arrACS as $acs) {
254
                if ($acs->getLocation() == $subjectConfirmation->getData()->getRecipient()) {
255
                    $ok = true;
256
                    break;
257
                }
258
            }
259
            if (!$ok) {
260
                throw new AuthenticationException(
261
                    sprintf(
262
                        'Invalid Assertion SubjectConfirmation Recipient %s',
263
                        $subjectConfirmation->getData()->getRecipient()
264
                    )
265
                );
266
            }
267
        }
268
    }
269
270
    /**
271
     * @param ServiceInfo $metaProvider
272
     * @return \XMLSecurityKey[]
273
     */
274
    protected function getAllKeys(ServiceInfo $metaProvider)
275
    {
276
        $result = array();
277
        $edIDP = $metaProvider->getIdpProvider()->getEntityDescriptor();
278
        if ($edIDP) {
279
            $arr = $edIDP->getAllIdpSsoDescriptors();
280
            if ($arr) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $arr of type array<AerialShip\LightSa...a\LoadFromXmlInterface> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
281
                $idp = $arr[0];
282
                $keyDescriptors = $idp->getKeyDescriptors();
283
                foreach ($keyDescriptors as $keyDescriptor) {
284
                    $certificate = $keyDescriptor->getCertificate();
285
                    $result[] = KeyHelper::createPublicKey($certificate);
286
                }
287
            }
288
        }
289
290
        return $result;
291
    }
292
293
    /**
294
     * @param \AerialShip\SamlSPBundle\Config\ServiceInfo $metaProvider
295
     * @return null|\XMLSecurityKey
296
     */
297
    protected function getSigningKey(ServiceInfo $metaProvider)
298
    {
299
        $result = null;
300
        $edIDP = $metaProvider->getIdpProvider()->getEntityDescriptor();
301
        if ($edIDP) {
302
            $arr = $edIDP->getAllIdpSsoDescriptors();
303
            if ($arr) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $arr of type array<AerialShip\LightSa...a\LoadFromXmlInterface> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
304
                $idp = $arr[0];
305
                $arr = $idp->findKeyDescriptors('signing');
306
                if ($arr) {
307
                    $keyDescriptor = $arr[0];
308
                    $certificate = $keyDescriptor->getCertificate();
309
                    $result = KeyHelper::createPublicKey($certificate);
310
                }
311
            }
312
        }
313
        
314
        return $result;
315
    }
316
}
317