Issues (196)

Security Analysis    6 potential vulnerabilities

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 (4)
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 (1)
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.
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/Controller/QuoteController.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\Controller;
6
7
use OpenApi\Annotations as OA;
8
use Symfony\Component\HttpFoundation\JsonResponse;
9
use Symfony\Component\HttpFoundation\RedirectResponse;
10
use Symfony\Component\HttpFoundation\Response;
11
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
12
use Symfony\Component\Routing\Annotation\Route;
13
use Symfony\Component\Routing\Exception\InvalidParameterException;
14
15
/**
16
 * A quick note: This tool is referred to as "bash" in much of the legacy code base.  As such,
17
 * the terms "quote" and "bash" are used interchangeably here, so as to not break many conventions.
18
 *
19
 * This tool is intentionally disabled in the WMF installation.
20
 * @codeCoverageIgnore
21
 */
22
class QuoteController extends XtoolsController
23
{
24
25
    /**
26
     * @inheritDoc
27
     * @codeCoverageIgnore
28
     */
29
    public function getIndexRoute(): string
30
    {
31
        return 'Quote';
32
    }
33
34
    /**
35
     * Method for rendering the Bash Main Form. This method redirects if valid parameters are found,
36
     * making it a valid form endpoint as well.
37
     * @Route("/bash", name="Bash")
38
     * @Route("/quote", name="Quote")
39
     * @Route("/bash/base.php", name="BashBase")
40
     * @return Response
41
     */
42
    public function indexAction(): Response
43
    {
44
        // Check to see if the quote is a param.  If so,
45
        // redirect to the proper route.
46
        if ('' != $this->request->query->get('id')) {
47
            return $this->redirectToRoute(
48
                'QuoteID',
49
                ['id' => $this->request->query->get('id')]
50
            );
51
        }
52
53
        // Otherwise render the form.
54
        return $this->render(
55
            'quote/index.html.twig',
56
            [
57
                'xtPage' => 'Quote',
58
                'xtPageTitle' => 'tool-bash',
59
                'xtSubtitle' => 'tool-bash-desc',
60
            ]
61
        );
62
    }
63
64
    /**
65
     * Method for rendering a random quote. This should redirect to the /quote/{id} path below.
66
     * @Route("/quote/random", name="QuoteRandom")
67
     * @Route("/bash/random", name="BashRandom")
68
     * @return RedirectResponse
69
     */
70
    public function randomQuoteAction(): RedirectResponse
71
    {
72
        // Choose a random quote by ID. If we can't find the quotes, return back to
73
        // the main form with a flash notice.
74
        try {
75
            $id = rand(1, sizeof($this->getParameter('quotes')));
0 ignored issues
show
It seems like $this->getParameter('quotes') can also be of type boolean and double and integer and null and string; however, parameter $value of sizeof() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

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

75
            $id = rand(1, sizeof(/** @scrutinizer ignore-type */ $this->getParameter('quotes')));
Loading history...
76
        } catch (InvalidParameterException $e) {
77
            $this->addFlashMessage('notice', 'noquotes');
78
            return $this->redirectToRoute('Quote');
79
        }
80
81
        return $this->redirectToRoute('QuoteID', ['id' => $id]);
82
    }
83
84
    /**
85
     * Method to show all quotes.
86
     * @Route("/quote/all", name="QuoteAll")
87
     * @Route("/bash/all", name="BashAll")
88
     * @return Response
89
     */
90
    public function quoteAllAction(): Response
91
    {
92
        // Load up an array of all the quotes.
93
        // if we can't find the quotes, return back to  the main form with
94
        // a flash notice.
95
        try {
96
            $quotes = $this->getParameter('quotes');
97
        } catch (InvalidParameterException $e) {
98
            $this->addFlashMessage('notice', 'noquotes');
99
            return $this->redirectToRoute('Quote');
100
        }
101
102
        // Render the page.
103
        return $this->render(
104
            'quote/all.html.twig',
105
            [
106
                'xtPage' => 'Quote',
107
                'quotes' => $quotes,
108
            ]
109
        );
110
    }
111
112
    /**
113
     * Method to render a single quote.
114
     * @param int $id ID of the quote
115
     * @Route("/quote/{id}", name="QuoteID")
116
     * @Route("/bash/{id}", name="BashID")
117
     * @return Response
118
     */
119
    public function quoteAction(int $id): Response
120
    {
121
        // Get the singular quote.
122
        // If we can't find the quotes, return back to  the main form with a flash notice.
123
        try {
124
            if (isset($this->getParameter('quotes')[$id])) {
125
                $text = $this->getParameter('quotes')[$id];
126
            } else {
127
                throw new InvalidParameterException("Quote doesn't exist'");
128
            }
129
        } catch (InvalidParameterException $e) {
130
            $this->addFlashMessage('notice', 'noquotes');
131
            return $this->redirectToRoute('Quote');
132
        }
133
134
        // If the text is undefined, that quote doesn't exist.
135
        // Redirect back to the main form.
136
        if (!isset($text)) {
137
            $this->addFlashMessage('notice', 'noquotes');
138
            return $this->redirectToRoute('Quote');
139
        }
140
141
        // Show the quote.
142
        return $this->render(
143
            'quote/view.html.twig',
144
            [
145
                'xtPage' => 'Quote',
146
                'text' => $text,
147
                'id' => $id,
148
            ]
149
        );
150
    }
151
152
    /************************ API endpoints ************************/
153
154
    /**
155
     * Get random quote.
156
     * @Route("/api/quote/random", name="QuoteApiRandom", methods={"GET"})
157
     * @OA\Tag(name="Quote API")
158
     * @OA\Get(description="Get a random quote. The quotes are sourced from [developer quips](https://w.wiki/6rpo)
159
           and [IRC quotes](https://meta.wikimedia.org/wiki/IRC/Quotes/archives).")
160
     * @OA\Response(
161
     *     response=200,
162
     *     description="Quote keyed by ID.",
163
     *     @OA\JsonContent(
164
     *         @OA\Property(property="<quote-id>", type="string")
165
     *     )
166
     * )
167
     * @return JsonResponse
168
     * @codeCoverageIgnore
169
     */
170
    public function randomQuoteApiAction(): JsonResponse
171
    {
172
        $this->validateIsEnabled();
173
174
        $this->recordApiUsage('quote/random');
175
        $quotes = $this->getParameter('quotes');
176
        $id = array_rand($quotes);
177
178
        return new JsonResponse(
179
            [$id => $quotes[$id]],
180
            Response::HTTP_OK
181
        );
182
    }
183
184
    /**
185
     * Get all quotes.
186
     * @Route("/api/quote/all", name="QuoteApiAll", methods={"GET"})
187
     * @OA\Tag(name="Quote API")
188
     * @OA\Get(description="Get a list of all quotes, sourced from [developer quips](https://w.wiki/6rpo)
189
           and [IRC quotes](https://meta.wikimedia.org/wiki/IRC/Quotes/archives).")
190
     * @OA\Response(
191
     *     response=200,
192
     *     description="All quotes, keyed by ID.",
193
     *     @OA\JsonContent(
194
     *         @OA\Property(property="<quote-id>", type="string")
195
     *     )
196
     * )
197
     * @return Response
198
     * @codeCoverageIgnore
199
     */
200
    public function allQuotesApiAction(): Response
201
    {
202
        $this->validateIsEnabled();
203
204
        $this->recordApiUsage('quote/all');
205
        $quotes = $this->getParameter('quotes');
206
        $numberedQuotes = [];
207
208
        // Number the quotes, since they somehow have significance.
209
        foreach ($quotes as $index => $quote) {
210
            $numberedQuotes[(string)($index + 1)] = $quote;
211
        }
212
213
        return new JsonResponse($numberedQuotes, Response::HTTP_OK);
214
    }
215
216
    /**
217
     * Get the quote with the given ID.
218
     * @Route("/api/quote/{id}", name="QuoteApiQuote", requirements={"id"="\d+"}, methods={"GET"})
219
     * @OA\Tag(name="Quote API")
220
     * @OA\Get(description="Get a quote with the given ID.")
221
     * @OA\Parameter(name="id", in="path", required="true", @OA\Schema(type="integer", minimum=0))
222
     * @OA\Response(
223
     *     response=200,
224
     *     description="Quote keyed by ID.",
225
     *     @OA\JsonContent(
226
     *         @OA\Property(property="<quote-id>", type="string")
227
     *     )
228
     * )
229
     * @param int $id
230
     * @return JsonResponse
231
     * @codeCoverageIgnore
232
     */
233
    public function singleQuotesApiAction(int $id): JsonResponse
234
    {
235
        $this->validateIsEnabled();
236
237
        $this->recordApiUsage('quote/id');
238
        $quotes = $this->getParameter('quotes');
239
240
        if (!isset($quotes[$id])) {
241
            return new JsonResponse(
242
                [
243
                    'error' => [
244
                        'code' => Response::HTTP_NOT_FOUND,
245
                        'message' => 'No quote found with ID '.$id,
246
                    ],
247
                ],
248
                Response::HTTP_NOT_FOUND
249
            );
250
        }
251
252
        return new JsonResponse([
253
            $id => $quotes[$id],
254
        ], Response::HTTP_OK);
255
    }
256
257
    /**
258
     * Validate that the Quote tool is enabled, and throw a 404 if it is not.
259
     * This is normally done by DisabledToolSubscriber but we have special logic here, because for Labs we want to
260
     * show the quote in the footer but not expose the web interface.
261
     * @throws NotFoundHttpException
262
     */
263
    private function validateIsEnabled(): void
264
    {
265
        $isLabs = $this->getParameter('app.is_wmf');
266
        if (!$isLabs && !$this->getParameter('enable.Quote')) {
267
            throw $this->createNotFoundException('This tool is disabled');
268
        }
269
    }
270
}
271