From 1930a64e9c7e0d287b4ad9e57c6ee03b11b9cbc4 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sun, 1 May 2022 02:46:57 +0200 Subject: [PATCH] fix: authentication bypass and denial of service (DoS) vulnerabilities in Apple Game Center auth adapter (GHSA-qf8x-vqjv-92gr) (#7963) --- spec/AuthenticationAdapters.spec.js | 49 ++++++++++++++++------------- src/Adapters/Auth/gcenter.js | 33 +++++++++++++------ 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index 87679d02..850039c5 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -1657,11 +1657,7 @@ describe('Apple Game Center Auth adapter', () => { bundleId: 'cloud.xtralife.gamecenterauth', }; - try { - await gcenter.validateAuthData(authData); - } catch (e) { - fail(); - } + await gcenter.validateAuthData(authData); }); it('validateAuthData invalid signature id', async () => { @@ -1682,22 +1678,33 @@ describe('Apple Game Center Auth adapter', () => { } }); - it('validateAuthData invalid public key url', async () => { - const authData = { - id: 'G:1965586982', - publicKeyUrl: 'invalid.com', - timestamp: 1565257031287, - signature: '1234', - salt: 'DzqqrQ==', - bundleId: 'cloud.xtralife.gamecenterauth', - }; - - try { - await gcenter.validateAuthData(authData); - fail(); - } catch (e) { - expect(e.message).toBe('Apple Game Center - invalid publicKeyUrl: invalid.com'); - } + it('validateAuthData invalid public key http url', async () => { + const publicKeyUrls = [ + 'example.com', + 'http://static.gc.apple.com/public-key/gc-prod-4.cer', + 'https://developer.apple.com/assets/elements/badges/download-on-the-app-store.svg', + 'https://example.com/ \\.apple.com/public_key.cer', + 'https://example.com/ &.apple.com/public_key.cer', + ]; + await Promise.all( + publicKeyUrls.map(publicKeyUrl => + expectAsync( + gcenter.validateAuthData({ + id: 'G:1965586982', + timestamp: 1565257031287, + publicKeyUrl, + signature: '1234', + salt: 'DzqqrQ==', + bundleId: 'com.example.com', + }) + ).toBeRejectedWith( + new Parse.Error( + Parse.Error.SCRIPT_FAILED, + `Apple Game Center - invalid publicKeyUrl: ${publicKeyUrl}` + ) + ) + ) + ); }); }); diff --git a/src/Adapters/Auth/gcenter.js b/src/Adapters/Auth/gcenter.js index 090b9fab..5cd8e8af 100644 --- a/src/Adapters/Auth/gcenter.js +++ b/src/Adapters/Auth/gcenter.js @@ -14,20 +14,16 @@ const authData = { const { Parse } = require('parse/node'); const crypto = require('crypto'); const https = require('https'); -const url = require('url'); const cache = {}; // (publicKey -> cert) cache function verifyPublicKeyUrl(publicKeyUrl) { - const parsedUrl = url.parse(publicKeyUrl); - if (parsedUrl.protocol !== 'https:') { + try { + const regex = /^https:\/\/(?:[-_A-Za-z0-9]+\.){0,}apple\.com\/.*\.cer$/; + return regex.test(publicKeyUrl); + } catch (error) { return false; } - const hostnameParts = parsedUrl.hostname.split('.'); - const length = hostnameParts.length; - const domainParts = hostnameParts.slice(length - 2, length); - const domain = domainParts.join('.'); - return domain === 'apple.com'; } function convertX509CertToPEM(X509Cert) { @@ -40,7 +36,7 @@ function convertX509CertToPEM(X509Cert) { return pemPreFix + certBody + pemPostFix; } -function getAppleCertificate(publicKeyUrl) { +async function getAppleCertificate(publicKeyUrl) { if (!verifyPublicKeyUrl(publicKeyUrl)) { throw new Parse.Error( Parse.Error.OBJECT_NOT_FOUND, @@ -50,6 +46,25 @@ function getAppleCertificate(publicKeyUrl) { if (cache[publicKeyUrl]) { return cache[publicKeyUrl]; } + const url = new URL(publicKeyUrl); + const headOptions = { + hostname: url.hostname, + path: url.pathname, + method: 'HEAD', + }; + const headers = await new Promise((resolve, reject) => + https.get(headOptions, res => resolve(res.headers)).on('error', reject) + ); + if ( + headers['content-type'] !== 'application/pkix-cert' || + headers['content-length'] == null || + headers['content-length'] > 10000 + ) { + throw new Parse.Error( + Parse.Error.OBJECT_NOT_FOUND, + `Apple Game Center - invalid publicKeyUrl: ${publicKeyUrl}` + ); + } return new Promise((resolve, reject) => { https .get(publicKeyUrl, res => {