Skip to content

Commit 226d10c

Browse files
tvdijenahacker1-securesamlpmeulen
authored
Merge commit from fork
* Prevent duplicate parameters from being provided * Use strict see what (is/will) be signed verification for $message * Fix parseQuery() Exception namspaces and add tests for them * parseQuery may now throw an Exception * Tie the results from parseQuery to those extracted from the SignedQuery string This way both methods of arriving at the signed message must agree and complement each other * Use same (===) instead of Eq (==) * Fix coding style --------- Co-authored-by: ahacker1 <alex@securesaml.com> Co-authored-by: Pieter van der Meulen <pieter.vandermeulen@surf.nl>
1 parent 5dff6cf commit 226d10c

File tree

2 files changed

+74
-6
lines changed

2 files changed

+74
-6
lines changed

src/SAML2/HTTPRedirect.php

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public function send(Message $message) : void
9494
/**
9595
* Receive a SAML 2 message sent using the HTTP-Redirect binding.
9696
*
97-
* Throws an exception if it is unable receive the message.
97+
* Throws an exception if it is unable to receive the message.
9898
*
9999
* @throws \Exception
100100
* @return \SAML2\Message The received message.
@@ -104,10 +104,36 @@ public function send(Message $message) : void
104104
public function receive(): Message
105105
{
106106
$data = self::parseQuery();
107-
if (array_key_exists('SAMLRequest', $data)) {
108-
$message = $data['SAMLRequest'];
109-
} elseif (array_key_exists('SAMLResponse', $data)) {
110-
$message = $data['SAMLResponse'];
107+
$signedQuery = $data['SignedQuery'];
108+
109+
/**
110+
* Get the SAMLRequest/SAMLResponse from the exact same signed data that will be verified later in
111+
* validateSignature into $res using the actual SignedQuery
112+
*/
113+
$res = [];
114+
foreach (explode('&', $signedQuery) as $e) {
115+
$tmp = explode('=', $e, 2);
116+
$name = $tmp[0];
117+
if (count($tmp) === 2) {
118+
$value = $tmp[1];
119+
} else {
120+
/* No value for this parameter. */
121+
$value = '';
122+
}
123+
$name = urldecode($name);
124+
$res[$name] = urldecode($value);
125+
}
126+
127+
/**
128+
* Put the SAMLRequest/SAMLResponse from the actual query string into $message,
129+
* and assert that the result from parseQuery() in $data and the parsing of the SignedQuery in $res agree
130+
*/
131+
if (array_key_exists('SAMLRequest', $res)) {
132+
Assert::same($res['SAMLRequest'], $data['SAMLRequest'], 'Parse failure.');
133+
$message = $res['SAMLRequest'];
134+
} elseif (array_key_exists('SAMLResponse', $res)) {
135+
Assert::same($res['SAMLResponse'], $data['SAMLResponse'], 'Parse failure.');
136+
$message = $res['SAMLResponse'];
111137
} else {
112138
throw new \Exception('Missing SAMLRequest or SAMLResponse parameter.');
113139
}
@@ -157,7 +183,7 @@ public function receive(): Message
157183
$signData = [
158184
'Signature' => $data['Signature'],
159185
'SigAlg' => $data['SigAlg'],
160-
'Query' => $data['SignedQuery'],
186+
'Query' => $signedQuery,
161187
];
162188

163189
$message->addValidator([get_class($this), 'validateSignature'], $signData);
@@ -174,6 +200,7 @@ public function receive(): Message
174200
* signed.
175201
*
176202
* @return array The query data that is signed.
203+
* @throws \Exception
177204
*/
178205
private static function parseQuery() : array
179206
{
@@ -195,7 +222,12 @@ private static function parseQuery() : array
195222
/* No value for this parameter. */
196223
$value = '';
197224
}
225+
198226
$name = urldecode($name);
227+
// Prevent keys from being set more than once
228+
if (array_key_exists($name, $data)) {
229+
throw new \Exception('Duplicate parameter.');
230+
}
199231
$data[$name] = urldecode($value);
200232

201233
switch ($name) {
@@ -211,6 +243,9 @@ private static function parseQuery() : array
211243
break;
212244
}
213245
}
246+
if (array_key_exists('SAMLRequest', $data) && array_key_exists('SAMLResponse', $data)) {
247+
throw new \Exception('Both SAMLRequest and SAMLResponse provided.');
248+
}
214249

215250
$data['SignedQuery'] = $sigQuery.$relayState.$sigAlg;
216251

tests/SAML2/HTTPRedirectTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,4 +253,37 @@ public function testSendAuthnResponseBespokeDestination() : void
253253
$hr->setDestination('gopher://myurl');
254254
$hr->send($response);
255255
}
256+
257+
258+
/**
259+
* Test that multiple query parameters with the same name are not allowed.
260+
*
261+
*/
262+
public function testDuplicateQueryParameters() : void
263+
{
264+
// SAMLRequest appears twice
265+
$qs = 'SAMLRequest=first&RelayState=somestate&SAMLRequest=second&Signature=sig&&SigAlg=alg';
266+
$_SERVER['QUERY_STRING'] = $qs;
267+
268+
$this->expectException(\Exception::class);
269+
$this->expectExceptionMessage('Duplicate parameter.');
270+
$hr = new HTTPRedirect();
271+
$hr->receive();
272+
}
273+
274+
/**
275+
* Test that providing both SAMLRequest and SAMLResponse is not allowed
276+
*/
277+
public function testSAMLResponseAndSAMLRequestConfusion() : void
278+
{
279+
// Both SAMLRequest and SAML Response appear
280+
$qs = 'SAMLRequest=first&RelayState=somestate&SAMLResponse=second&Signature=sig&&SigAlg=alg';
281+
$_SERVER['QUERY_STRING'] = $qs;
282+
283+
$this->expectException(\Exception::class);
284+
$this->expectExceptionMessage('Both SAMLRequest and SAMLResponse provided.');
285+
$hr = new HTTPRedirect();
286+
$hr->receive();
287+
}
288+
256289
}

0 commit comments

Comments
 (0)