CVE-2019-1020002
Description
Pterodactyl before 0.7.14 with 2FA allows credential sniffing.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Pterodactyl before 0.7.14 exposed the 2FA secret during setup, allowing network sniffing to bypass two-factor authentication.
Vulnerability
In Pterodactyl versions before 0.7.14, the two-factor authentication (2FA) setup process transmitted the TOTP secret in a way that could be intercepted. The previous implementation used a client-side library (PragmaRX\Google2FAQRCode\Google2FA) to generate the QR code, potentially exposing the secret in the response. The fix in commit [1] moved the secret generation to the server and used an external QR code API to avoid leaking the secret.
Exploitation
An attacker with network access (e.g., on the same network or via man-in-the-middle) could sniff the 2FA setup request/response. The attacker would need to capture the traffic during the initial 2FA enrollment, as the secret was transmitted in a manner that allowed credential sniffing. No authentication is required beyond being able to intercept the network traffic.
Impact
Successful exploitation allows the attacker to obtain the user's 2FA TOTP secret, enabling them to generate valid one-time passwords. This bypasses the second factor of authentication, leading to full account takeover and unauthorized access to the Pterodactyl panel.
Mitigation
The vulnerability is fixed in Pterodactyl version 0.7.14, released on July 24, 2019 [4]. Users should upgrade to this version or later. No known workarounds exist for earlier versions.
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
pterodactyl/panelPackagist | < 0.7.14 | 0.7.14 |
Affected products
2- Pterodactyl/Pterodactyl Panelv5Range: < 0.7.14
Patches
1092e7e79fff8Change 2FA service to generate the secret on our own and use an external QR service to display the image
5 files changed · +44 −186
app/Http/Controllers/Base/SecurityController.php+3 −1 modified@@ -90,8 +90,10 @@ public function index(Request $request) */ public function generateTotp(Request $request) { + $totpData = $this->twoFactorSetupService->handle($request->user()); + return response()->json([ - 'qrImage' => $this->twoFactorSetupService->handle($request->user()), + 'qrImage' => 'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=' . $totpData, ]); }
app/Services/Users/TwoFactorSetupService.php+22 −20 modified@@ -1,22 +1,18 @@ <?php -/** - * Pterodactyl - Panel - * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Users; +use Exception; +use RuntimeException; use Pterodactyl\Models\User; -use PragmaRX\Google2FAQRCode\Google2FA; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Illuminate\Contracts\Config\Repository as ConfigRepository; class TwoFactorSetupService { + const VALID_BASE32_CHARACTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; + /** * @var \Illuminate\Contracts\Config\Repository */ @@ -27,11 +23,6 @@ class TwoFactorSetupService */ private $encrypter; - /** - * @var PragmaRX\Google2FAQRCode\Google2FA - */ - private $google2FA; - /** * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface */ @@ -42,24 +33,22 @@ class TwoFactorSetupService * * @param \Illuminate\Contracts\Config\Repository $config * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter - * @param PragmaRX\Google2FAQRCode\Google2FA $google2FA * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository */ public function __construct( ConfigRepository $config, Encrypter $encrypter, - Google2FA $google2FA, UserRepositoryInterface $repository ) { $this->config = $config; $this->encrypter = $encrypter; - $this->google2FA = $google2FA; $this->repository = $repository; } /** * Generate a 2FA token and store it in the database before returning the - * QR code image. + * QR code URL. This URL will need to be attached to a QR generating service in + * order to function. * * @param \Pterodactyl\Models\User $user * @return string @@ -69,13 +58,26 @@ public function __construct( */ public function handle(User $user): string { - $secret = $this->google2FA->generateSecretKey($this->config->get('pterodactyl.auth.2fa.bytes')); - $image = $this->google2FA->getQRCodeInline($this->config->get('app.name'), $user->email, $secret); + $secret = ''; + try { + for ($i = 0; $i < $this->config->get('pterodactyl.auth.2fa.bytes', 16); $i++) { + $secret .= substr(self::VALID_BASE32_CHARACTERS, random_int(0, 31), 1); + } + } catch (Exception $exception) { + throw new RuntimeException($exception->getMessage(), 0, $exception); + } $this->repository->withoutFreshModel()->update($user->id, [ 'totp_secret' => $this->encrypter->encrypt($secret), ]); - return $image; + $company = $this->config->get('app.name'); + + return sprintf( + 'otpauth://totp/%1$s:%2$s?secret=%3$s&issuer=%1$s', + rawurlencode($company), + rawurlencode($user->email), + rawurlencode($secret) + ); } }
composer.json+0 −1 modified@@ -30,7 +30,6 @@ "matriphe/iso-639": "^1.2", "nesbot/carbon": "^1.22", "pragmarx/google2fa": "^5.0", - "pragmarx/google2fa-qrcode": "^1.0.3", "predis/predis": "^1.1", "prologue/alerts": "^0.4", "ramsey/uuid": "^3.7",
composer.lock+1 −150 modified@@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9c03519785d01a8f710a0f7e65f602e8", + "content-hash": "8a99f4996405b8080a0dcabb2609c39d", "packages": [ { "name": "appstract/laravel-blade-directives", @@ -142,55 +142,6 @@ ], "time": "2018-11-21T19:18:43+00:00" }, - { - "name": "bacon/bacon-qr-code", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/Bacon/BaconQrCode.git", - "reference": "eaac909da3ccc32b748a65b127acd8918f58d9b0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/eaac909da3ccc32b748a65b127acd8918f58d9b0", - "reference": "eaac909da3ccc32b748a65b127acd8918f58d9b0", - "shasum": "" - }, - "require": { - "dasprid/enum": "^1.0", - "ext-iconv": "*", - "php": "^7.1" - }, - "require-dev": { - "phly/keep-a-changelog": "^1.4", - "phpunit/phpunit": "^6.4", - "squizlabs/php_codesniffer": "^3.1" - }, - "suggest": { - "ext-imagick": "to generate QR code images" - }, - "type": "library", - "autoload": { - "psr-4": { - "BaconQrCode\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Ben Scholzen 'DASPRiD'", - "email": "mail@dasprids.de", - "homepage": "http://www.dasprids.de", - "role": "Developer" - } - ], - "description": "BaconQrCode is a QR code generator for PHP.", - "homepage": "https://github.com/Bacon/BaconQrCode", - "time": "2018-04-25T17:53:56+00:00" - }, { "name": "cakephp/chronos", "version": "1.2.3", @@ -248,48 +199,6 @@ ], "time": "2018-10-18T22:02:21+00:00" }, - { - "name": "dasprid/enum", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/DASPRiD/Enum.git", - "reference": "631ef6e638e9494b0310837fa531bedd908fc22b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/631ef6e638e9494b0310837fa531bedd908fc22b", - "reference": "631ef6e638e9494b0310837fa531bedd908fc22b", - "shasum": "" - }, - "require-dev": { - "phpunit/phpunit": "^6.4", - "squizlabs/php_codesniffer": "^3.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "DASPRiD\\Enum\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Ben Scholzen 'DASPRiD'", - "email": "mail@dasprids.de", - "homepage": "https://dasprids.de/" - } - ], - "description": "PHP 7.1 enum implementation", - "keywords": [ - "enum", - "map" - ], - "time": "2017-10-25T22:45:27+00:00" - }, { "name": "dnoegel/php-xdg-base-dir", "version": "0.1", @@ -2253,64 +2162,6 @@ ], "time": "2019-03-19T22:44:16+00:00" }, - { - "name": "pragmarx/google2fa-qrcode", - "version": "v1.0.3", - "source": { - "type": "git", - "url": "https://github.com/antonioribeiro/google2fa-qrcode.git", - "reference": "fd5ff0531a48b193a659309cc5fb882c14dbd03f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/antonioribeiro/google2fa-qrcode/zipball/fd5ff0531a48b193a659309cc5fb882c14dbd03f", - "reference": "fd5ff0531a48b193a659309cc5fb882c14dbd03f", - "shasum": "" - }, - "require": { - "bacon/bacon-qr-code": "~1.0|~2.0", - "php": ">=5.4", - "pragmarx/google2fa": ">=4.0" - }, - "require-dev": { - "khanamiryan/qrcode-detector-decoder": "^1.0", - "phpunit/phpunit": "~4|~5|~6|~7" - }, - "type": "library", - "extra": { - "component": "package", - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "PragmaRX\\Google2FAQRCode\\": "src/", - "PragmaRX\\Google2FAQRCode\\Tests\\": "tests/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Antonio Carlos Ribeiro", - "email": "acr@antoniocarlosribeiro.com", - "role": "Creator & Designer" - } - ], - "description": "QR Code package for Google2FA", - "keywords": [ - "2fa", - "Authentication", - "Two Factor Authentication", - "google2fa", - "qr code", - "qrcode" - ], - "time": "2019-03-20T16:42:58+00:00" - }, { "name": "predis/predis", "version": "v1.1.1",
tests/Unit/Services/Users/TwoFactorSetupServiceTest.php+18 −14 modified@@ -5,7 +5,6 @@ use Mockery as m; use Tests\TestCase; use Pterodactyl\Models\User; -use PragmaRX\Google2FAQRCode\Google2FA; use Illuminate\Contracts\Config\Repository; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Services\Users\TwoFactorSetupService; @@ -23,11 +22,6 @@ class TwoFactorSetupServiceTest extends TestCase */ private $encrypter; - /** - * @var PragmaRX\Google2FAQRCode\Google2FA|\Mockery\Mock - */ - private $google2FA; - /** * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface|\Mockery\Mock */ @@ -42,7 +36,6 @@ public function setUp() $this->config = m::mock(Repository::class); $this->encrypter = m::mock(Encrypter::class); - $this->google2FA = m::mock(Google2FA::class); $this->repository = m::mock(UserRepositoryInterface::class); } @@ -53,16 +46,27 @@ public function testSecretAndImageAreReturned() { $model = factory(User::class)->make(); - $this->config->shouldReceive('get')->with('pterodactyl.auth.2fa.bytes')->once()->andReturn(32); - $this->google2FA->shouldReceive('generateSecretKey')->with(32)->once()->andReturn('secretKey'); - $this->config->shouldReceive('get')->with('app.name')->once()->andReturn('CompanyName'); - $this->google2FA->shouldReceive('getQRCodeInline')->with('CompanyName', $model->email, 'secretKey')->once()->andReturn('http://url.com'); - $this->encrypter->shouldReceive('encrypt')->with('secretKey')->once()->andReturn('encryptedSecret'); + $this->config->shouldReceive('get')->with('pterodactyl.auth.2fa.bytes', 16)->andReturn(32); + $this->config->shouldReceive('get')->with('app.name')->andReturn('Company Name'); + $this->encrypter->shouldReceive('encrypt') + ->with(m::on(function ($value) { + return preg_match('/([A-Z234567]{32})/', $value) !== false; + })) + ->once() + ->andReturn('encryptedSecret'); + $this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, ['totp_secret' => 'encryptedSecret'])->once()->andReturnNull(); $response = $this->getService()->handle($model); $this->assertNotEmpty($response); - $this->assertSame('http://url.com', $response); + + $companyName = preg_quote(rawurlencode('Company Name')); + $email = preg_quote(rawurlencode($model->email)); + + $this->assertRegExp( + '/otpauth:\/\/totp\/' . $companyName . ':' . $email . '\?secret=([A-Z234567]{32})&issuer=' . $companyName . '/', + $response + ); } /** @@ -72,6 +76,6 @@ public function testSecretAndImageAreReturned() */ private function getService(): TwoFactorSetupService { - return new TwoFactorSetupService($this->config, $this->encrypter, $this->google2FA, $this->repository); + return new TwoFactorSetupService($this->config, $this->encrypter, $this->repository); } }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-fg52-xjfc-9rh8ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-1020002ghsaADVISORY
- github.com/pterodactyl/panel/commit/092e7e79fff858ee026608c7dbccab165a67526fghsaWEB
- github.com/pterodactyl/panel/releases/tag/v0.7.14ghsaWEB
- github.com/pterodactyl/panel/security/advisories/GHSA-vcm9-hx3q-qwj8ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.