mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-18 11:30:55 +01:00
Allow Phabricator to run with "enable_post_data_reading" disabled
Summary: Ref T13008. Depends on D18701. The overall goal here is to make turning `enable_post_data_reading` off not break things, so we can run rate limiting checks before we read file uploads. The biggest blocker for this is that turning it off stops `$_FILES` from coming into existence. This //appears// to mostly work. Specifically: - Skip the `max_post_size` check when POST is off, since it's meaningless. - Don't read or scrub $_POST at startup when POST is off. - When we rebuild REQUEST and POST before processing requests, do multipart parsing if we need to and rebuild FILES. - Skip the `is_uploaded_file()` check if we built FILES ourselves. This probably breaks a couple of small things, like maybe `__profile__` and other DarkConsole triggers over POST, and probably some other weird stuff. The parsers may also need more work than they've received so far. I also need to verify that this actually works (i.e., lets us run code without reading the request body) but I'll include that in the change where I update the actual rate limiting. Test Plan: - Disabled `enable_post_data_reading`. - Uploaded a file with a vanilla upload form (project profile image). - Uploaded a file with drag and drop. - Used DarkConsole. - Submitted comments. - Created a task. - Browsed around. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13008 Differential Revision: https://secure.phabricator.com/D18702
This commit is contained in:
parent
821c7ac833
commit
9777c66576
3 changed files with 101 additions and 24 deletions
|
@ -15,26 +15,79 @@ class AphrontDefaultApplicationConfiguration
|
||||||
$parser = new PhutilQueryStringParser();
|
$parser = new PhutilQueryStringParser();
|
||||||
$data = array();
|
$data = array();
|
||||||
|
|
||||||
// If the request has "multipart/form-data" content, we can't use
|
|
||||||
// PhutilQueryStringParser to parse it, and the raw data supposedly is not
|
|
||||||
// available anyway (according to the PHP documentation, "php://input" is
|
|
||||||
// not available for "multipart/form-data" requests). However, it is
|
|
||||||
// available at least some of the time (see T3673), so double check that
|
|
||||||
// we aren't trying to parse data we won't be able to parse correctly by
|
|
||||||
// examining the Content-Type header.
|
|
||||||
$content_type = idx($_SERVER, 'CONTENT_TYPE');
|
|
||||||
$is_form_data = preg_match('@^multipart/form-data@i', $content_type);
|
|
||||||
|
|
||||||
$request_method = idx($_SERVER, 'REQUEST_METHOD');
|
$request_method = idx($_SERVER, 'REQUEST_METHOD');
|
||||||
if ($request_method === 'PUT') {
|
if ($request_method === 'PUT') {
|
||||||
// For PUT requests, do nothing: in particular, do NOT read input. This
|
// For PUT requests, do nothing: in particular, do NOT read input. This
|
||||||
// allows us to stream input later and process very large PUT requests,
|
// allows us to stream input later and process very large PUT requests,
|
||||||
// like those coming from Git LFS.
|
// like those coming from Git LFS.
|
||||||
} else {
|
} else {
|
||||||
|
// For POST requests, we're going to read the raw input ourselves here
|
||||||
|
// if we can. Among other things, this corrects variable names with
|
||||||
|
// the "." character in them, which PHP normally converts into "_".
|
||||||
|
|
||||||
|
// There are two major considerations here: whether the
|
||||||
|
// `enable_post_data_reading` option is set, and whether the content
|
||||||
|
// type is "multipart/form-data" or not.
|
||||||
|
|
||||||
|
// If `enable_post_data_reading` is off, we're free to read the entire
|
||||||
|
// raw request body and parse it -- and we must, because $_POST and
|
||||||
|
// $_FILES are not built for us. If `enable_post_data_reading` is on,
|
||||||
|
// which is the default, we may not be able to read the body (the
|
||||||
|
// documentation says we can't, but empirically we can at least some
|
||||||
|
// of the time).
|
||||||
|
|
||||||
|
// If the content type is "multipart/form-data", we need to build both
|
||||||
|
// $_POST and $_FILES, which is involved. The body itself is also more
|
||||||
|
// difficult to parse than other requests.
|
||||||
$raw_input = PhabricatorStartup::getRawInput();
|
$raw_input = PhabricatorStartup::getRawInput();
|
||||||
if (strlen($raw_input) && !$is_form_data) {
|
if (strlen($raw_input)) {
|
||||||
$data += $parser->parseQueryString($raw_input);
|
$content_type = idx($_SERVER, 'CONTENT_TYPE');
|
||||||
|
$is_multipart = preg_match('@^multipart/form-data@i', $content_type);
|
||||||
|
if ($is_multipart && !ini_get('enable_post_data_reading')) {
|
||||||
|
$multipart_parser = id(new AphrontMultipartParser())
|
||||||
|
->setContentType($content_type);
|
||||||
|
|
||||||
|
$multipart_parser->beginParse();
|
||||||
|
$multipart_parser->continueParse($raw_input);
|
||||||
|
$parts = $multipart_parser->endParse();
|
||||||
|
|
||||||
|
$query_string = array();
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
if (!$part->isVariable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $part->getName();
|
||||||
|
$value = $part->getVariableValue();
|
||||||
|
|
||||||
|
$query_string[] = urlencode($name).'='.urlencode($value);
|
||||||
|
}
|
||||||
|
$query_string = implode('&', $query_string);
|
||||||
|
$post = $parser->parseQueryString($query_string);
|
||||||
|
|
||||||
|
$files = array();
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
if ($part->isVariable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$files[$part->getName()] = $part->getPHPFileDictionary();
|
||||||
|
}
|
||||||
|
$_FILES = $files;
|
||||||
|
} else {
|
||||||
|
$post = $parser->parseQueryString($raw_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
$_POST = $post;
|
||||||
|
PhabricatorStartup::rebuildRequest();
|
||||||
|
|
||||||
|
$data += $post;
|
||||||
} else if ($_POST) {
|
} else if ($_POST) {
|
||||||
|
$post = filter_input_array(INPUT_POST, FILTER_UNSAFE_RAW);
|
||||||
|
if (is_array($post)) {
|
||||||
|
$_POST = $post;
|
||||||
|
PhabricatorStartup::rebuildRequest();
|
||||||
|
}
|
||||||
$data += $_POST;
|
$data += $_POST;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,9 +178,17 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
||||||
}
|
}
|
||||||
|
|
||||||
$tmp_name = idx($spec, 'tmp_name');
|
$tmp_name = idx($spec, 'tmp_name');
|
||||||
$is_valid = @is_uploaded_file($tmp_name);
|
|
||||||
if (!$is_valid) {
|
// NOTE: If we parsed the request body ourselves, the files we wrote will
|
||||||
throw new Exception(pht('File is not an uploaded file.'));
|
// not be registered in the `is_uploaded_file()` list. It's fine to skip
|
||||||
|
// this check: it just protects against sloppy code from the long ago era
|
||||||
|
// of "register_globals".
|
||||||
|
|
||||||
|
if (ini_get('enable_post_data_reading')) {
|
||||||
|
$is_valid = @is_uploaded_file($tmp_name);
|
||||||
|
if (!$is_valid) {
|
||||||
|
throw new Exception(pht('File is not an uploaded file.'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$file_data = Filesystem::readFile($tmp_name);
|
$file_data = Filesystem::readFile($tmp_name);
|
||||||
|
|
|
@ -416,9 +416,12 @@ final class PhabricatorStartup {
|
||||||
|
|
||||||
// NOTE: We don't filter INPUT_SERVER because we don't want to overwrite
|
// NOTE: We don't filter INPUT_SERVER because we don't want to overwrite
|
||||||
// changes made in "preamble.php".
|
// changes made in "preamble.php".
|
||||||
|
|
||||||
|
// NOTE: WE don't filter INPUT_POST because we may be constructing it
|
||||||
|
// lazily if "enable_post_data_reading" is disabled.
|
||||||
|
|
||||||
$filter = array(
|
$filter = array(
|
||||||
INPUT_GET,
|
INPUT_GET,
|
||||||
INPUT_POST,
|
|
||||||
INPUT_ENV,
|
INPUT_ENV,
|
||||||
INPUT_COOKIE,
|
INPUT_COOKIE,
|
||||||
);
|
);
|
||||||
|
@ -434,9 +437,6 @@ final class PhabricatorStartup {
|
||||||
case INPUT_COOKIE:
|
case INPUT_COOKIE:
|
||||||
$_COOKIE = array_merge($_COOKIE, $filtered);
|
$_COOKIE = array_merge($_COOKIE, $filtered);
|
||||||
break;
|
break;
|
||||||
case INPUT_POST:
|
|
||||||
$_POST = array_merge($_POST, $filtered);
|
|
||||||
break;
|
|
||||||
case INPUT_ENV;
|
case INPUT_ENV;
|
||||||
$env = array_merge($_ENV, $filtered);
|
$env = array_merge($_ENV, $filtered);
|
||||||
$_ENV = self::filterEnvSuperglobal($env);
|
$_ENV = self::filterEnvSuperglobal($env);
|
||||||
|
@ -444,18 +444,28 @@ final class PhabricatorStartup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// rebuild $_REQUEST, respecting order declared in ini files
|
self::rebuildRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task validation
|
||||||
|
*/
|
||||||
|
public static function rebuildRequest() {
|
||||||
|
// Rebuild $_REQUEST, respecting order declared in ".ini" files.
|
||||||
$order = ini_get('request_order');
|
$order = ini_get('request_order');
|
||||||
|
|
||||||
if (!$order) {
|
if (!$order) {
|
||||||
$order = ini_get('variables_order');
|
$order = ini_get('variables_order');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$order) {
|
if (!$order) {
|
||||||
// $_REQUEST will be empty, leave it alone
|
// $_REQUEST will be empty, so leave it alone.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$_REQUEST = array();
|
$_REQUEST = array();
|
||||||
for ($i = 0; $i < strlen($order); $i++) {
|
for ($ii = 0; $ii < strlen($order); $ii++) {
|
||||||
switch ($order[$i]) {
|
switch ($order[$ii]) {
|
||||||
case 'G':
|
case 'G':
|
||||||
$_REQUEST = array_merge($_REQUEST, $_GET);
|
$_REQUEST = array_merge($_REQUEST, $_GET);
|
||||||
break;
|
break;
|
||||||
|
@ -466,7 +476,7 @@ final class PhabricatorStartup {
|
||||||
$_REQUEST = array_merge($_REQUEST, $_COOKIE);
|
$_REQUEST = array_merge($_REQUEST, $_COOKIE);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// $_ENV and $_SERVER never go into $_REQUEST
|
// $_ENV and $_SERVER never go into $_REQUEST.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -593,6 +603,12 @@ final class PhabricatorStartup {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If "enable_post_data_reading" is off, we won't have $_POST and this
|
||||||
|
// condition is effectively impossible.
|
||||||
|
if (!ini_get('enable_post_data_reading')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If there's POST data, clearly we're in good shape.
|
// If there's POST data, clearly we're in good shape.
|
||||||
if ($_POST) {
|
if ($_POST) {
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Reference in a new issue