Issues (37)

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/Storageless/Http/SessionMiddleware.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
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license.
17
 */
18
19
declare(strict_types=1);
20
21
namespace PSR7Sessions\Storageless\Http;
22
23
use BadMethodCallException;
24
use Dflydev\FigCookies\FigResponseCookies;
25
use Dflydev\FigCookies\Modifier\SameSite;
26
use Dflydev\FigCookies\SetCookie;
27
use InvalidArgumentException;
28
use Lcobucci\Clock\Clock;
29
use Lcobucci\Clock\SystemClock;
30
use Lcobucci\JWT\Builder;
31
use Lcobucci\JWT\Parser;
32
use Lcobucci\JWT\Signer;
33
use Lcobucci\JWT\Token;
34
use Lcobucci\JWT\ValidationData;
35
use OutOfBoundsException;
36
use Psr\Http\Message\ResponseInterface as Response;
37
use Psr\Http\Message\ServerRequestInterface as Request;
38
use Psr\Http\Server\MiddlewareInterface;
39
use Psr\Http\Server\RequestHandlerInterface;
40
use PSR7Sessions\Storageless\Session\DefaultSessionData;
41
use PSR7Sessions\Storageless\Session\LazySession;
42
use PSR7Sessions\Storageless\Session\SessionInterface;
43
use stdClass;
44
45
final class SessionMiddleware implements MiddlewareInterface
46
{
47
    public const ISSUED_AT_CLAIM      = 'iat';
48
    public const SESSION_CLAIM        = 'session-data';
49
    public const SESSION_ATTRIBUTE    = 'session';
50
    public const DEFAULT_COOKIE       = 'slsession';
51
    public const DEFAULT_REFRESH_TIME = 60;
52
53
    private Signer $signer;
0 ignored issues
show
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_STRING, expecting T_FUNCTION or T_CONST
Loading history...
54
55
    private string $signatureKey;
56
57
    private string $verificationKey;
58
59
    private int $expirationTime;
60
61
    private int $refreshTime;
62
63
    private Parser $tokenParser;
64
65
    private SetCookie $defaultCookie;
66
67
    private Clock $clock;
68
69 13
    public function __construct(
70
        Signer $signer,
71
        string $signatureKey,
72
        string $verificationKey,
73
        SetCookie $defaultCookie,
74
        Parser $tokenParser,
75
        int $expirationTime,
76
        Clock $clock,
77
        int $refreshTime = self::DEFAULT_REFRESH_TIME
78
    ) {
79 13
        $this->signer          = $signer;
80 13
        $this->signatureKey    = $signatureKey;
81 13
        $this->verificationKey = $verificationKey;
82 13
        $this->tokenParser     = $tokenParser;
83 13
        $this->defaultCookie   = clone $defaultCookie;
84 13
        $this->expirationTime  = $expirationTime;
85 13
        $this->clock           = $clock;
86 13
        $this->refreshTime     = $refreshTime;
87 13
    }
88
89
    /**
90
     * This constructor simplifies instantiation when using HTTPS (REQUIRED!) and symmetric key encryption
91
     */
92 3
    public static function fromSymmetricKeyDefaults(string $symmetricKey, int $expirationTime) : self
93
    {
94 3
        return new self(
95 3
            new Signer\Hmac\Sha256(),
96
            $symmetricKey,
97
            $symmetricKey,
98 3
            SetCookie::create(self::DEFAULT_COOKIE)
99 3
                ->withSecure(true)
100 3
                ->withHttpOnly(true)
101 3
                ->withSameSite(SameSite::lax())
102 3
                ->withPath('/'),
103 3
            new Parser(),
104
            $expirationTime,
105 3
            new SystemClock()
106
        );
107
    }
108
109
    /**
110
     * This constructor simplifies instantiation when using HTTPS (REQUIRED!) and asymmetric key encryption
111
     * based on RSA keys
112
     */
113 3
    public static function fromAsymmetricKeyDefaults(
114
        string $privateRsaKey,
115
        string $publicRsaKey,
116
        int $expirationTime
117
    ) : self {
118 3
        return new self(
119 3
            new Signer\Rsa\Sha256(),
120
            $privateRsaKey,
121
            $publicRsaKey,
122 3
            SetCookie::create(self::DEFAULT_COOKIE)
123 3
                ->withSecure(true)
124 3
                ->withHttpOnly(true)
125 3
                ->withSameSite(SameSite::lax())
126 3
                ->withPath('/'),
127 3
            new Parser(),
128
            $expirationTime,
129 3
            new SystemClock()
130
        );
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     *
136
     * @throws BadMethodCallException
137
     * @throws InvalidArgumentException
138
     */
139 49
    public function process(Request $request, RequestHandlerInterface $delegate) : Response
140
    {
141 49
        $token            = $this->parseToken($request);
142
        $sessionContainer = LazySession::fromContainerBuildingCallback(function () use ($token) : SessionInterface {
143 44
            return $this->extractSessionContainer($token);
144 49
        });
145
146 49
        return $this->appendToken(
147 49
            $sessionContainer,
148 49
            $delegate->handle($request->withAttribute(self::SESSION_ATTRIBUTE, $sessionContainer)),
149
            $token
150
        );
151
    }
152
153
    /**
154
     * Extract the token from the given request object
155
     */
156 49
    private function parseToken(Request $request) : ?Token
157
    {
158 49
        $cookies    = $request->getCookieParams();
159 49
        $cookieName = $this->defaultCookie->getName();
160
161 49
        if (! isset($cookies[$cookieName])) {
162 27
            return null;
163
        }
164
165
        try {
166 32
            $token = $this->tokenParser->parse($cookies[$cookieName]);
167 3
        } catch (InvalidArgumentException $invalidToken) {
168 3
            return null;
169
        }
170
171 29
        if (! $token->validate(new ValidationData())) {
172 6
            return null;
173
        }
174
175 23
        return $token;
176
    }
177
178
    /**
179
     * @throws OutOfBoundsException
180
     */
181 44
    private function extractSessionContainer(?Token $token) : SessionInterface
182
    {
183 44
        if (! $token) {
184 36
            return DefaultSessionData::newEmptySession();
185
        }
186
187
        try {
188 18
            if (! $token->verify($this->signer, $this->verificationKey)) {
189 1
                return DefaultSessionData::newEmptySession();
190
            }
191
192 14
            return DefaultSessionData::fromDecodedTokenData(
193 14
                (object) $token->getClaim(self::SESSION_CLAIM, new stdClass())
194
            );
195 3
        } catch (BadMethodCallException $invalidToken) {
196 3
            return DefaultSessionData::newEmptySession();
197
        }
198
    }
199
200
    /**
201
     * @throws BadMethodCallException
202
     * @throws InvalidArgumentException
203
     */
204 49
    private function appendToken(SessionInterface $sessionContainer, Response $response, ?Token $token) : Response
205
    {
206 49
        $sessionContainerChanged = $sessionContainer->hasChanged();
207
208 49
        if ($sessionContainerChanged && $sessionContainer->isEmpty()) {
209 3
            return FigResponseCookies::set($response, $this->getExpirationCookie());
210
        }
211
212 49
        if ($sessionContainerChanged || ($this->shouldTokenBeRefreshed($token) && ! $sessionContainer->isEmpty())) {
213 26
            return FigResponseCookies::set($response, $this->getTokenCookie($sessionContainer));
214
        }
215
216 27
        return $response;
217
    }
218
219 32
    private function shouldTokenBeRefreshed(?Token $token) : bool
220
    {
221 32
        if (! ($token && $token->hasClaim(self::ISSUED_AT_CLAIM))) {
222 18
            return false;
223
        }
224
225 14
        return $this->timestamp() >= $token->getClaim(self::ISSUED_AT_CLAIM) + $this->refreshTime;
226
    }
227
228
    /**
229
     * @throws BadMethodCallException
230
     */
231 26
    private function getTokenCookie(SessionInterface $sessionContainer) : SetCookie
232
    {
233 26
        $timestamp = $this->timestamp();
234
235
        return $this
236 26
            ->defaultCookie
237 26
            ->withValue(
238 26
                (new Builder())
239 26
                    ->setIssuedAt($timestamp)
240 26
                    ->setExpiration($timestamp + $this->expirationTime)
241 26
                    ->set(self::SESSION_CLAIM, $sessionContainer)
242 26
                    ->sign($this->signer, $this->signatureKey)
243 26
                    ->getToken()
244 26
                    ->__toString()
245
            )
246 26
            ->withExpires($timestamp + $this->expirationTime);
247
    }
248
249 3
    private function getExpirationCookie() : SetCookie
250
    {
251 3
        $expirationDate = $this->clock->now()->modify('-30 days');
252
253
        return $this
254 3
            ->defaultCookie
255 3
            ->withValue(null)
256 3
            ->withExpires($expirationDate->getTimestamp());
257
    }
258
259 31
    private function timestamp() : int
260
    {
261 31
        return $this->clock->now()->getTimestamp();
262
    }
263
}
264