Skip to content

Commit 5f697d1

Browse files
committed
Limit the number of filters
Chaining filters is becoming an increasingly popular primitive to exploit PHP applications. Limiting the usage of only a few of them at the time should, if not close entirely, make it significantly less attractive. This should close #10453
1 parent fa15ac5 commit 5f697d1

File tree

5 files changed

+40
-0
lines changed

5 files changed

+40
-0
lines changed

main/php_streams.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ struct _php_stream_wrapper {
193193

194194
#define PHP_STREAM_FLAG_WAS_WRITTEN 0x80000000
195195

196+
#define PHP_STREAM_MAX_FILTERS 5
197+
196198
struct _php_stream {
197199
const php_stream_ops *ops;
198200
void *abstract; /* convenience pointer for abstraction */

main/streams/filter.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,12 @@ PHPAPI void php_stream_filter_free(php_stream_filter *filter)
284284

285285
PHPAPI int php_stream_filter_prepend_ex(php_stream_filter_chain *chain, php_stream_filter *filter)
286286
{
287+
if (chain->nb_filters >= PHP_STREAM_MAX_FILTERS) {
288+
php_error_docref(NULL, E_ERROR, "Unable to apply filter, maximum number (%d) reached", PHP_STREAM_MAX_FILTERS);
289+
return FAILURE;
290+
}
291+
chain->nb_filters++;
292+
287293
filter->next = chain->head;
288294
filter->prev = NULL;
289295

@@ -307,6 +313,12 @@ PHPAPI int php_stream_filter_append_ex(php_stream_filter_chain *chain, php_strea
307313
{
308314
php_stream *stream = chain->stream;
309315

316+
if (chain->nb_filters >= PHP_STREAM_MAX_FILTERS) {
317+
php_error_docref(NULL, E_ERROR, "Unable to apply filter, maximum number (%d) reached", PHP_STREAM_MAX_FILTERS);
318+
return FAILURE;
319+
}
320+
chain->nb_filters++;
321+
310322
filter->prev = chain->tail;
311323
filter->next = NULL;
312324
if (chain->tail) {
@@ -435,6 +447,8 @@ PHPAPI int _php_stream_filter_flush(php_stream_filter *filter, int finish)
435447
flags = PSFS_FLAG_NORMAL;
436448
}
437449

450+
chain->nb_filters = 0;
451+
438452
/* Last filter returned data via PSFS_PASS_ON
439453
Do something with it */
440454

@@ -492,6 +506,7 @@ PHPAPI php_stream_filter *php_stream_filter_remove(php_stream_filter *filter, in
492506
} else {
493507
filter->chain->tail = filter->prev;
494508
}
509+
filter->chain->nb_filters--;
495510

496511
if (filter->res) {
497512
zend_list_delete(filter->res);

main/streams/php_stream_filter_api.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ typedef struct _php_stream_filter_ops {
9696

9797
typedef struct _php_stream_filter_chain {
9898
php_stream_filter *head, *tail;
99+
size_t nb_filters;
99100

100101
/* Owning stream */
101102
php_stream *stream;

tests/security/bug10453.phpt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Bug #10453 (using a high amount of filters for nefarious purposes)
3+
--FILE--
4+
<?php
5+
$fp = fopen('php://output', 'w');
6+
for($i=0; $i<10; $i++)
7+
stream_filter_append($fp, 'string.rot13');
8+
fwrite($fp, "This is a test.\n");
9+
?>
10+
--EXPECTF--
11+
Fatal error: stream_filter_append(): Unable to apply filter, maximum number (5) reached in %s
12+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Bug #10453 (using a high amount of filters for nefarious purposes via a full chain)
3+
--FILE--
4+
<?php
5+
$fp = fopen('php://filter/write=string.rot13|string.rot13|string.rot13|string.rot13|string.rot13|string.rot13|string.rot13|string.rot13|string.rot13|string.rot13|string.rot13|string.rot13|string.rot13|string.rot13/resource=php://output', 'w');
6+
fwrite($fp, "This is a test.\n");
7+
?>
8+
--EXPECTF--
9+
Fatal error: fopen(): Unable to apply filter, maximum number (5) reached in %s
10+

0 commit comments

Comments
 (0)