Skip to content

Commit

Permalink
Limit the number of filters
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jvoisin committed Nov 4, 2024
1 parent fa15ac5 commit 5f697d1
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 0 deletions.
2 changes: 2 additions & 0 deletions main/php_streams.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ struct _php_stream_wrapper {

#define PHP_STREAM_FLAG_WAS_WRITTEN 0x80000000

#define PHP_STREAM_MAX_FILTERS 5

struct _php_stream {
const php_stream_ops *ops;
void *abstract; /* convenience pointer for abstraction */
Expand Down
15 changes: 15 additions & 0 deletions main/streams/filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,12 @@ PHPAPI void php_stream_filter_free(php_stream_filter *filter)

PHPAPI int php_stream_filter_prepend_ex(php_stream_filter_chain *chain, php_stream_filter *filter)
{
if (chain->nb_filters >= PHP_STREAM_MAX_FILTERS) {
php_error_docref(NULL, E_ERROR, "Unable to apply filter, maximum number (%d) reached", PHP_STREAM_MAX_FILTERS);
return FAILURE;
}
chain->nb_filters++;

filter->next = chain->head;
filter->prev = NULL;

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

if (chain->nb_filters >= PHP_STREAM_MAX_FILTERS) {
php_error_docref(NULL, E_ERROR, "Unable to apply filter, maximum number (%d) reached", PHP_STREAM_MAX_FILTERS);
return FAILURE;
}
chain->nb_filters++;

filter->prev = chain->tail;
filter->next = NULL;
if (chain->tail) {
Expand Down Expand Up @@ -435,6 +447,8 @@ PHPAPI int _php_stream_filter_flush(php_stream_filter *filter, int finish)
flags = PSFS_FLAG_NORMAL;
}

chain->nb_filters = 0;

/* Last filter returned data via PSFS_PASS_ON
Do something with it */

Expand Down Expand Up @@ -492,6 +506,7 @@ PHPAPI php_stream_filter *php_stream_filter_remove(php_stream_filter *filter, in
} else {
filter->chain->tail = filter->prev;
}
filter->chain->nb_filters--;

if (filter->res) {
zend_list_delete(filter->res);
Expand Down
1 change: 1 addition & 0 deletions main/streams/php_stream_filter_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ typedef struct _php_stream_filter_ops {

typedef struct _php_stream_filter_chain {
php_stream_filter *head, *tail;
size_t nb_filters;

/* Owning stream */
php_stream *stream;
Expand Down
12 changes: 12 additions & 0 deletions tests/security/bug10453.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Bug #10453 (using a high amount of filters for nefarious purposes)
--FILE--
<?php
$fp = fopen('php://output', 'w');
for($i=0; $i<10; $i++)
stream_filter_append($fp, 'string.rot13');
fwrite($fp, "This is a test.\n");
?>
--EXPECTF--
Fatal error: stream_filter_append(): Unable to apply filter, maximum number (5) reached in %s

10 changes: 10 additions & 0 deletions tests/security/bug10453_fullchain.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
Bug #10453 (using a high amount of filters for nefarious purposes via a full chain)
--FILE--
<?php
$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');
fwrite($fp, "This is a test.\n");
?>
--EXPECTF--
Fatal error: fopen(): Unable to apply filter, maximum number (5) reached in %s

0 comments on commit 5f697d1

Please sign in to comment.