CVE-2021-3957: Cross-Site Request Forgery (CSRF) (CWE-352)
value: kimai2 is vulnerable to Cross-Site Request Forgery (CSRF)
CWE description: The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
CVSS v3 base score: 4.3
Published: 2021-11-19T12:15Z
Repository: https://github.com/kevinpapst/kimai2
Language: PHP
Files changed: src/Controller/DoctorController.php, templates/doctor/index.html.twig
Commit date: 2021-11-14 20:39:22 +0100
Commit message: prevent csrf to flush logs (#2930)


Diff: diff --git a/src/Controller/DoctorController.php b/src/Controller/DoctorController.php
index fca4d3e5..7e1b5625 100644
--- a/src/Controller/DoctorController.php
+++ b/src/Controller/DoctorController.php
@@ -13,10 +13,12 @@ use App\Utils\FileHelper;
 use Composer\InstalledVersions;
 use PackageVersions\Versions;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Annotation\Route;
+use Symfony\Component\Security\Csrf\CsrfToken;
+use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
 
 /**
  * @Route(path="/doctor")
  * @Security("is_granted('system_information')")
  */
@@ -54,15 +56,23 @@ class DoctorController extends AbstractController
         $this->environment = $kernelEnvironment;
         $this->fileHelper = $fileHelper;
     }
 
     /**
-     * @Route(path="/flush-log", name="doctor_flush_log", methods={"GET"})
+     * @Route(path="/flush-log/{token}", name="doctor_flush_log", methods={"GET"})
      * @Security("is_granted('system_configuration')")
      */
-    public function deleteLogfileAction(): Response
+    public function deleteLogfileAction(string $token, CsrfTokenManagerInterface $csrfTokenManager): Response
     {
+        if (!$csrfTokenManager->isTokenValid(new CsrfToken('doctor.flush_log', $token))) {
+            $this->flashError('action.delete.error');
+
+            return $this->redirectToRoute('doctor');
+        }
+
+        $csrfTokenManager->refreshToken($token);
+
         $logfile = $this->getLogFilename();
 
         if (file_exists($logfile)) {
             if (!is_writable($logfile)) {
                 $this->flashError('action.delete.error', ['%reason%' => 'Logfile cannot be written']);
diff --git a/templates/doctor/index.html.twig b/templates/doctor/index.html.twig
index 08e1d392..baab28d9 100644
--- a/templates/doctor/index.html.twig
+++ b/templates/doctor/index.html.twig
@@ -78,11 +78,11 @@
     {% endif %}
     {% embed '@AdminLTE/Widgets/box-widget.html.twig' with logOpts %}
         {% block box_title %}Logfile (max. {{ logLines }} last lines){% endblock %}
         {% block box_tools %}
             {% if log_delete %}
-                <a class="btn-box-tool confirmation-link" href="{{ path('doctor_flush_log') }}" data-question="confirm.delete"><i class="{{ 'delete'|icon }}"></i></a>
+                <a class="btn-box-tool confirmation-link" href="{{ path('doctor_flush_log', {'token': csrf_token('doctor.flush_log')}) }}" data-question="confirm.delete"><i class="{{ 'delete'|icon }}"></i></a>
             {% endif %}
         {% endblock %}
         {% block box_body %}
             {% if logSize > 52428800 %}
                 <p>Your logfile is larger than 50 MB, consider deleting it (it will be re-created automatically).</p>

Vulnerable code:      * @Route(path="/flush-log", name="doctor_flush_log", methods={"GET"})
    public function deleteLogfileAction(): Response
                <a class="btn-box-tool confirmation-link" href="{{ path('doctor_flush_log') }}" data-question="confirm.delete"><i class="{{ 'delete'|icon }}"></i></a>
Fixed code: use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
     * @Route(path="/flush-log/{token}", name="doctor_flush_log", methods={"GET"})
    public function deleteLogfileAction(string $token, CsrfTokenManagerInterface $csrfTokenManager): Response
        if (!$csrfTokenManager->isTokenValid(new CsrfToken('doctor.flush_log', $token))) {
            $this->flashError('action.delete.error');

            return $this->redirectToRoute('doctor');
        }

        $csrfTokenManager->refreshToken($token);

                <a class="btn-box-tool confirmation-link" href="{{ path('doctor_flush_log', {'token': csrf_token('doctor.flush_log')}) }}" data-question="confirm.delete"><i class="{{ 'delete'|icon }}"></i></a>