Skip to content

Commit 3a4f5a6

Browse files
authored
Merge pull request #24 from queueit/Fix-for-QueueItToken-url-vulnerability
Improve exception handling when validating queueit token
2 parents 7aa77ab + 7e4cc76 commit 3a4f5a6

File tree

4 files changed

+108
-82
lines changed

4 files changed

+108
-82
lines changed

KnownUser.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
class KnownUser
1212
{
13+
const QueueItTokenKey = "queueittoken";
1314
const QueueITAjaxHeaderKey = "x-queueit-ajaxpageurl";
1415

1516
//used for unittest

QueueITHelpers.php

Lines changed: 75 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class Utils
66
{
77
public static function isNullOrEmptyString($value)
88
{
9-
return (!isset($value) || trim($value) === '');
9+
return (!isset($value) || !is_string($value) || trim($value) === '');
1010
}
1111

1212
public static function boolToString($value)
@@ -17,6 +17,19 @@ public static function boolToString($value)
1717

1818
return $value ? "true" : "false";
1919
}
20+
21+
public static function getParameterByName($url, $name)
22+
{
23+
$name = preg_quote($name, '/');
24+
$pattern = '/[?&]' . $name . '(=([^&#]*)|&|#|$)/';
25+
if (preg_match($pattern, $url, $matches)) {
26+
if (!isset($matches[2])) return "";
27+
$response = urldecode(str_replace('+', ' ', $matches[2]));
28+
return $response;
29+
}
30+
31+
return null;
32+
}
2033
}
2134

2235
class QueueUrlParams
@@ -47,65 +60,72 @@ public static function extractQueueParams($queueitToken)
4760
return null;
4861
}
4962

50-
$result = new QueueUrlParams();
51-
$result->queueITToken = $queueitToken;
52-
$paramsNameValueList = explode(QueueUrlParams::KeyValueSeparatorGroupChar, $result->queueITToken);
53-
54-
foreach ($paramsNameValueList as $pNameValue) {
55-
$paramNameValueArr = explode(QueueUrlParams::KeyValueSeparatorChar, $pNameValue);
56-
57-
if (count($paramNameValueArr) != 2) {
58-
continue;
59-
}
60-
61-
switch ($paramNameValueArr[0]) {
62-
case QueueUrlParams::TimeStampKey: {
63-
if (is_numeric($paramNameValueArr[1])) {
64-
$result->timeStamp = intval($paramNameValueArr[1]);
65-
} else {
66-
$result->timeStamp = 0;
63+
try{
64+
65+
$result = new QueueUrlParams();
66+
$result->queueITToken = $queueitToken;
67+
$paramsNameValueList = explode(QueueUrlParams::KeyValueSeparatorGroupChar, $result->queueITToken);
68+
69+
foreach ($paramsNameValueList as $pNameValue) {
70+
$paramNameValueArr = explode(QueueUrlParams::KeyValueSeparatorChar, $pNameValue);
71+
72+
if (count($paramNameValueArr) != 2) {
73+
continue;
74+
}
75+
76+
switch ($paramNameValueArr[0]) {
77+
case QueueUrlParams::TimeStampKey: {
78+
if (is_numeric($paramNameValueArr[1])) {
79+
$result->timeStamp = intval($paramNameValueArr[1]);
80+
} else {
81+
$result->timeStamp = 0;
82+
}
83+
break;
84+
}
85+
case QueueUrlParams::CookieValidityMinutesKey: {
86+
if (is_numeric($paramNameValueArr[1])) {
87+
$result->cookieValidityMinutes = intval($paramNameValueArr[1]);
88+
}
89+
break;
6790
}
68-
break;
69-
}
70-
case QueueUrlParams::CookieValidityMinutesKey: {
71-
if (is_numeric($paramNameValueArr[1])) {
72-
$result->cookieValidityMinutes = intval($paramNameValueArr[1]);
91+
case QueueUrlParams::EventIdKey: {
92+
$result->eventId = $paramNameValueArr[1];
93+
break;
7394
}
74-
break;
75-
}
76-
case QueueUrlParams::EventIdKey: {
77-
$result->eventId = $paramNameValueArr[1];
78-
break;
79-
}
80-
case QueueUrlParams::ExtendableCookieKey: {
81-
$result->extendableCookie = $paramNameValueArr[1] === 'True' || $paramNameValueArr[1] === 'true';
82-
break;
83-
}
84-
case QueueUrlParams::HashKey: {
85-
$result->hashCode = $paramNameValueArr[1];
86-
break;
87-
}
88-
case QueueUrlParams::QueueIdKey: {
89-
$result->queueId = $paramNameValueArr[1];
90-
break;
91-
}
92-
case QueueUrlParams::RedirectTypeKey: {
93-
$result->redirectType = $paramNameValueArr[1];
94-
break;
95-
}
95+
case QueueUrlParams::ExtendableCookieKey: {
96+
$result->extendableCookie = $paramNameValueArr[1] === 'True' || $paramNameValueArr[1] === 'true';
97+
break;
98+
}
99+
case QueueUrlParams::HashKey: {
100+
$result->hashCode = $paramNameValueArr[1];
101+
break;
102+
}
103+
case QueueUrlParams::QueueIdKey: {
104+
$result->queueId = $paramNameValueArr[1];
105+
break;
106+
}
107+
case QueueUrlParams::RedirectTypeKey: {
108+
$result->redirectType = $paramNameValueArr[1];
109+
break;
110+
}
111+
}
96112
}
97-
}
98113

99-
$result->queueITTokenWithoutHash = str_replace(
100-
QueueUrlParams::KeyValueSeparatorGroupChar
101-
. QueueUrlParams::HashKey
102-
. QueueUrlParams::KeyValueSeparatorChar
103-
. $result->hashCode,
104-
"",
105-
$result->queueITToken
106-
);
114+
$result->queueITTokenWithoutHash = str_replace(
115+
QueueUrlParams::KeyValueSeparatorGroupChar
116+
. QueueUrlParams::HashKey
117+
. QueueUrlParams::KeyValueSeparatorChar
118+
. $result->hashCode,
119+
"",
120+
$result->queueITToken
121+
);
107122

108-
return $result;
123+
return $result;
124+
}
125+
catch (\Exception $e)
126+
{
127+
return null;
128+
}
109129
}
110130
}
111131

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,21 @@ the following method is all that is needed to validate that a user has been thro
1515
```php
1616
require_once( __DIR__ .'Models.php');
1717
require_once( __DIR__ .'KnownUser.php');
18+
require_once( __DIR__ .'Utils.php');
1819

1920
$configText = file_get_contents('integrationconfig.json');
2021
$customerID = ""; //Your Queue-it customer ID
2122
$secretKey = ""; //Your 72 char secret key as specified in Go Queue-it self-service platform
2223

23-
$queueittoken = isset( $_GET["queueittoken"] )? $_GET["queueittoken"] :'';
24-
2524
try
2625
{
2726
$fullUrl = getFullRequestUri();
27+
$queueittoken = Utils::getParameterByName($fullUrl, KnownUser::QueueItTokenKey);
2828
$currentUrlWithoutQueueitToken = preg_replace("/([\\?&])("."queueittoken"."=[^&]*)/i", "", $fullUrl);
2929

3030
//Verify if the user has been through the queue
31-
$result = QueueIT\KnownUserV3\SDK\KnownUser::validateRequestByIntegrationConfig(
32-
$currentUrlWithoutQueueitToken, $queueittoken, $configText, $customerID, $secretKey);
31+
$result = QueueIT\KnownUserV3\SDK\KnownUser::validateRequestByIntegrationConfig($currentUrlWithoutQueueitToken,
32+
$queueittoken, $configText, $customerID, $secretKey);
3333

3434
if($result->doRedirect())
3535
{
@@ -98,6 +98,7 @@ The following is an example of how to specify the configuration in code:
9898
```php
9999
require_once( __DIR__ .'Models.php');
100100
require_once( __DIR__ .'KnownUser.php');
101+
require_once( __DIR__ .'Utils.php');
101102

102103
$customerID = ""; //Your Queue-it customer ID
103104
$secretKey = ""; //Your 72 char secret key as specified in Go Queue-it self-service platform
@@ -111,16 +112,15 @@ $eventConfig->extendCookieValidity = true; //Should the Queue-it session cookie
111112
//$eventConfig->culture = "da-DK"; //Optional - Culture of the queue layout in the format specified here: https://msdn.microsoft.com/en-us/library/ee825488(v=cs.20).aspx. If unspecified then settings from Event will be used.
112113
// $eventConfig->layoutName = "NameOfYourCustomLayout"; //Optional - Name of the queue layout. If unspecified then settings from Event will be used.
113114

114-
$queueittoken = isset( $_GET["queueittoken"] )? $_GET["queueittoken"] :'';
115-
116115
try
117-
{
116+
{
118117
$fullUrl = getFullRequestUri();
118+
$queueittoken = Utils::getParameterByName($fullUrl, KnownUser::QueueItTokenKey);
119119
$currentUrlWithoutQueueitToken = preg_replace("/([\\?&])("."queueittoken"."=[^&]*)/i", "", $fullUrl);
120120

121121
//Verify if the user has been through the queue
122-
$result = QueueIT\KnownUserV3\SDK\KnownUser::resolveQueueRequestByLocalConfig(
123-
$currentUrlWithoutQueueitToken, $queueittoken, $eventConfig, $customerID, $secretKey);
122+
$result = QueueIT\KnownUserV3\SDK\KnownUser::validateRequestByLocalEventConfig($currentUrlWithoutQueueitToken,
123+
$queueittoken, $eventConfig, $customerID, $secretKey);
124124

125125
if($result->doRedirect())
126126
{

UserInQueueService.php

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class UserInQueueService implements IUserInQueueService
4141
{
4242
public static function getSDKVersion()
4343
{
44-
return "v3-php-" . "3.7.1";
44+
return "v3-php-" . "3.7.2";
4545
}
4646

4747
private $userInQueueStateRepository;
@@ -90,17 +90,18 @@ public function validateQueueRequest(
9090
$requestValidationResult = null;
9191
$isTokenValid = false;
9292

93-
if ($queueParams != null) {
93+
if($queueParams == null){
94+
$requestValidationResult = $this->getQueueResult($targetUrl, $config, $customerId);
95+
}
96+
else{
9497
$tokenValidationResult = $this->validateToken($config, $queueParams, $secretKey);
95-
$isTokenValid = $tokenValidationResult->isValid;
96-
97-
if ($isTokenValid) {
98+
if($tokenValidationResult == null){
99+
$requestValidationResult = $this->getQueueResult($targetUrl, $config, $customerId);
100+
} elseif ($tokenValidationResult->isValid) {
98101
$requestValidationResult = $this->getValidTokenResult($config, $queueParams, $secretKey);
99102
} else {
100103
$requestValidationResult = $this->getErrorResult($customerId, $targetUrl, $config, $queueParams, $tokenValidationResult->errorCode);
101104
}
102-
} else {
103-
$requestValidationResult = $this->getQueueResult($targetUrl, $config, $customerId);
104105
}
105106

106107
if ($state->isFound && !$isTokenValid)
@@ -297,21 +298,25 @@ private function validateToken(
297298
QueueUrlParams $queueParams,
298299
$secretKey
299300
) {
300-
$calculatedHash = hash_hmac('sha256', $queueParams->queueITTokenWithoutHash, $secretKey);
301+
try{
302+
$calculatedHash = hash_hmac('sha256', $queueParams->queueITTokenWithoutHash, $secretKey);
301303

302-
if (strtoupper($calculatedHash) != strtoupper($queueParams->hashCode)) {
303-
return new TokenValidationResult(false, "hash");
304-
}
304+
if (strtoupper($calculatedHash) != strtoupper($queueParams->hashCode)) {
305+
return new TokenValidationResult(false, "hash");
306+
}
305307

306-
if (strtoupper($queueParams->eventId) != strtoupper($config->eventId)) {
307-
return new TokenValidationResult(false, "eventid");
308-
}
308+
if (strtoupper($queueParams->eventId) != strtoupper($config->eventId)) {
309+
return new TokenValidationResult(false, "eventid");
310+
}
309311

310-
if ($queueParams->timeStamp < time()) {
311-
return new TokenValidationResult(false, "timestamp");
312-
}
312+
if ($queueParams->timeStamp < time()) {
313+
return new TokenValidationResult(false, "timestamp");
314+
}
313315

314-
return new TokenValidationResult(true, null);
316+
return new TokenValidationResult(true, null);
317+
}catch(\Exception $e) {
318+
null;
319+
}
315320
}
316321
}
317322

0 commit comments

Comments
 (0)