39
39
class SecurityComponent extends Component
40
40
{
41
41
42
+ /**
43
+ * Default message used for exceptions thrown
44
+ */
45
+ const DEFAULT_EXCEPTION_MESSAGE = 'The request has been black-holed ' ;
46
+
42
47
/**
43
48
* Default config
44
49
*
@@ -119,11 +124,10 @@ public function startup(Event $event)
119
124
}
120
125
121
126
if (!in_array ($ this ->_action , (array )$ this ->_config ['unlockedActions ' ]) &&
122
- $ hasData && $ isNotRequestAction
123
- ) {
124
- if ( $ this ->_config ['validatePost ' ]) {
127
+ $ hasData &&
128
+ $ isNotRequestAction &&
129
+ $ this ->_config ['validatePost ' ]) {
125
130
$ this ->_validatePost ($ controller );
126
- }
127
131
}
128
132
} catch (SecurityException $ se ) {
129
133
$ this ->blackHole ($ controller , $ se ->getType (), $ se );
@@ -203,15 +207,14 @@ public function blackHole(Controller $controller, $error = '', SecurityException
203
207
*/
204
208
protected function _throwException ($ exception = null )
205
209
{
206
- $ defaultMessage = 'The request has been black-holed ' ;
207
210
if ($ exception !== null ) {
208
- if (!Configure::read ('debug ' )) {
211
+ if (!Configure::read ('debug ' ) && $ exception instanceof SecurityException ) {
209
212
$ exception ->setReason ($ exception ->getMessage ());
210
- $ exception ->setMessage ($ defaultMessage );
213
+ $ exception ->setMessage (self :: DEFAULT_EXCEPTION_MESSAGE );
211
214
}
212
215
throw $ exception ;
213
216
}
214
- throw new BadRequestException ($ defaultMessage );
217
+ throw new BadRequestException (self :: DEFAULT_EXCEPTION_MESSAGE );
215
218
}
216
219
217
220
/**
@@ -270,7 +273,7 @@ protected function _authRequired(Controller $controller)
270
273
271
274
if (in_array ($ this ->request ->params ['action ' ], $ requireAuth ) || $ requireAuth == ['* ' ]) {
272
275
if (!isset ($ controller ->request ->data ['_Token ' ])) {
273
- throw new AuthSecurityException (sprintf ( '\'%s \' was not found in request data. ' , ' _Token ' ) );
276
+ throw new AuthSecurityException ('\'_Token \' was not found in request data. ' );
274
277
}
275
278
276
279
if ($ this ->session ->check ('_Token ' )) {
@@ -299,7 +302,7 @@ protected function _authRequired(Controller $controller)
299
302
);
300
303
}
301
304
} else {
302
- throw new AuthSecurityException (sprintf ( '\'%s \' was not found in session. ' , ' _Token ' ) );
305
+ throw new AuthSecurityException ('\'_Token \' was not found in session. ' );
303
306
}
304
307
}
305
308
}
@@ -326,12 +329,12 @@ protected function _validatePost(Controller $controller)
326
329
return true ;
327
330
}
328
331
332
+ $ msg = self ::DEFAULT_EXCEPTION_MESSAGE ;
329
333
if (Configure::read ('debug ' )) {
330
334
$ msg = $ this ->_debugPostTokenNotMatching ($ controller , $ hashParts );
331
- throw new SecurityException ($ msg );
332
335
}
333
336
334
- return false ;
337
+ throw new SecurityException ( $ msg ) ;
335
338
}
336
339
337
340
/**
@@ -358,10 +361,13 @@ protected function _validToken(Controller $controller)
358
361
if (Configure::read ('debug ' ) && !isset ($ check ['_Token ' ]['debug ' ])) {
359
362
throw new SecurityException (sprintf ($ message , '_Token.debug ' ));
360
363
}
364
+ if (!Configure::read ('debug ' ) && isset ($ check ['_Token ' ]['debug ' ])) {
365
+ throw new SecurityException ('Unexpected \'_Token.debug \' found in request data ' );
366
+ }
361
367
362
368
$ token = urldecode ($ check ['_Token ' ]['fields ' ]);
363
369
if (strpos ($ token , ': ' )) {
364
- list ($ token , $ locked ) = explode (': ' , $ token , 2 );
370
+ list ($ token , ) = explode (': ' , $ token , 2 );
365
371
}
366
372
return $ token ;
367
373
}
@@ -375,7 +381,8 @@ protected function _validToken(Controller $controller)
375
381
protected function _hashParts (Controller $ controller )
376
382
{
377
383
$ fieldList = $ this ->_fieldsList ($ controller ->request ->data );
378
- $ unlocked = $ this ->_unlocked ($ controller ->request ->data );
384
+ $ unlocked = $ this ->_sortedUnlocked ($ controller ->request ->data );
385
+
379
386
return [
380
387
$ controller ->request ->here (),
381
388
serialize ($ fieldList ),
@@ -456,20 +463,34 @@ protected function _fieldsList(array $check)
456
463
/**
457
464
* Get the unlocked string
458
465
*
459
- * @param array $check Data array
466
+ * @param array $data Data array
467
+ * @return string
468
+ */
469
+ protected function _unlocked (array $ data )
470
+ {
471
+ return urldecode ($ data ['_Token ' ]['unlocked ' ]);
472
+ }
473
+
474
+ /**
475
+ * Get the sorted unlocked string
476
+ *
477
+ * @param array $data Data array
460
478
* @return string
461
479
*/
462
- protected function _unlocked ( array $ check )
480
+ protected function _sortedUnlocked ( $ data )
463
481
{
464
- return urldecode ($ check ['_Token ' ]['unlocked ' ]);
482
+ $ unlocked = $ this ->_unlocked ($ data );
483
+ $ unlocked = explode ('| ' , $ unlocked );
484
+ sort ($ unlocked , SORT_STRING );
485
+ return implode ('| ' , $ unlocked );
465
486
}
466
487
467
488
/**
468
489
* Create a message for humans to understand why Security token is not matching
469
490
*
470
491
* @param \Cake\Controller\Controller $controller Instantiating controller
471
492
* @param array $hashParts Elements used to generate the Token hash
472
- * @return array Messages to explain why token is not matching
493
+ * @return string Message explaining why the tokens are not matching
473
494
*/
474
495
protected function _debugPostTokenNotMatching (Controller $ controller , $ hashParts )
475
496
{
@@ -478,11 +499,16 @@ protected function _debugPostTokenNotMatching(Controller $controller, $hashParts
478
499
if (!is_array ($ expectedParts ) || count ($ expectedParts ) !== 3 ) {
479
500
return 'Invalid security debug token. ' ;
480
501
}
481
- if ($ hashParts [0 ] !== $ expectedParts [0 ]) {
482
- $ messages [] = sprintf ('URL mismatch in POST data (expected \'%s \' but found \'%s \') ' , $ expectedParts [0 ], $ hashParts [0 ]);
502
+ $ expectedUrl = Hash::get ($ expectedParts , 0 );
503
+ $ url = Hash::get ($ hashParts , 0 );
504
+ if ($ expectedUrl !== $ url ) {
505
+ $ messages [] = sprintf ('URL mismatch in POST data (expected \'%s \' but found \'%s \') ' , $ expectedUrl , $ url );
506
+ }
507
+ $ expectedFields = Hash::get ($ expectedParts , 1 );
508
+ $ dataFields = Hash::get ($ hashParts , 1 );
509
+ if ($ dataFields ) {
510
+ $ dataFields = unserialize ($ dataFields );
483
511
}
484
- $ expectedFields = $ expectedParts [1 ];
485
- $ dataFields = unserialize ($ hashParts [1 ]);
486
512
$ fieldsMessages = $ this ->_debugCheckFields (
487
513
$ dataFields ,
488
514
$ expectedFields ,
@@ -519,24 +545,10 @@ protected function _debugPostTokenNotMatching(Controller $controller, $hashParts
519
545
*/
520
546
protected function _debugCheckFields ($ dataFields , $ expectedFields = [], $ intKeyMessage = '' , $ stringKeyMessage = '' , $ missingMessage = '' )
521
547
{
522
- $ messages = [];
523
- foreach ($ dataFields as $ key => $ value ) {
524
- if (is_int ($ key )) {
525
- $ foundKey = array_search ($ value , (array )$ expectedFields );
526
- if ($ foundKey === false ) {
527
- $ messages [] = sprintf ($ intKeyMessage , $ value );
528
- } else {
529
- unset($ expectedFields [$ foundKey ]);
530
- }
531
- } elseif (is_string ($ key )) {
532
- if (isset ($ expectedFields [$ key ]) && $ value !== $ expectedFields [$ key ]) {
533
- $ messages [] = sprintf ($ stringKeyMessage , $ key , $ expectedFields [$ key ], $ value );
534
- }
535
- unset($ expectedFields [$ key ]);
536
- }
537
- }
538
- if (count ($ expectedFields ) > 0 ) {
539
- $ messages [] = sprintf ($ missingMessage , implode (', ' , $ expectedFields ));
548
+ $ messages = $ this ->_matchExistingFields ($ dataFields , $ expectedFields , $ intKeyMessage , $ stringKeyMessage );
549
+ $ expectedFieldsMessage = $ this ->_debugExpectedFields ($ expectedFields , $ missingMessage );
550
+ if ($ expectedFieldsMessage !== null ) {
551
+ $ messages [] = $ expectedFieldsMessage ;
540
552
}
541
553
return $ messages ;
542
554
}
@@ -585,4 +597,60 @@ protected function _callback(Controller $controller, $method, $params = [])
585
597
}
586
598
return call_user_func_array ([&$ controller , $ method ], empty ($ params ) ? null : $ params );
587
599
}
600
+
601
+ /**
602
+ * Generate array of messages for the existing fields in POST data, matching dataFields in $expectedFields
603
+ * will be unset
604
+ *
605
+ * @param array $dataFields Fields array, containing the POST data fields
606
+ * @param array $expectedFields Fields array, containing the expected fields we should have in POST
607
+ * @param string $intKeyMessage Message string if unexpected found in data fields indexed by int (not protected)
608
+ * @param string $stringKeyMessage Message string if tampered found in data fields indexed by string (protected)
609
+ * @return array Error messages
610
+ */
611
+ protected function _matchExistingFields ($ dataFields , &$ expectedFields , $ intKeyMessage , $ stringKeyMessage )
612
+ {
613
+ $ messages = [];
614
+ foreach ((array )$ dataFields as $ key => $ value ) {
615
+ if (is_int ($ key )) {
616
+ $ foundKey = array_search ($ value , (array )$ expectedFields );
617
+ if ($ foundKey === false ) {
618
+ $ messages [] = sprintf ($ intKeyMessage , $ value );
619
+ } else {
620
+ unset($ expectedFields [$ foundKey ]);
621
+ }
622
+ } elseif (is_string ($ key )) {
623
+ if (isset ($ expectedFields [$ key ]) && $ value !== $ expectedFields [$ key ]) {
624
+ $ messages [] = sprintf ($ stringKeyMessage , $ key , $ expectedFields [$ key ], $ value );
625
+ }
626
+ unset($ expectedFields [$ key ]);
627
+ }
628
+ }
629
+
630
+ return $ messages ;
631
+ }
632
+
633
+ /**
634
+ * Generate debug message for the expected fields
635
+ *
636
+ * @param array $expectedFields Expected fields
637
+ * @param string $missingMessage Message template
638
+ * @return string Error message about expected fields
639
+ */
640
+ protected function _debugExpectedFields ($ expectedFields = [], $ missingMessage = '' )
641
+ {
642
+ if (count ($ expectedFields ) === 0 ) {
643
+ return null ;
644
+ }
645
+
646
+ $ expectedFieldNames = [];
647
+ foreach ((array )$ expectedFields as $ key => $ expectedField ) {
648
+ if (is_int ($ key )) {
649
+ $ expectedFieldNames [] = $ expectedField ;
650
+ } else {
651
+ $ expectedFieldNames [] = $ key ;
652
+ }
653
+ }
654
+ return sprintf ($ missingMessage , implode (', ' , $ expectedFieldNames ));
655
+ }
588
656
}
0 commit comments