Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Psalm's taint analysis behaves unexpectedly for different accesses to _POST #10919

Open
talarcin opened this issue Apr 25, 2024 · 3 comments
Open

Comments

@talarcin
Copy link

Hey,

I am currently testing out Psalm's taint analysis feature. While testing it on some WordPress Plugins with known vulnerabilities I've seen that depending on the way the _POST array is accessed and values from it are passed to a custom taint sink, taint warnings are not reported consistently. The following code example should visualize the inconsistency.

<?php

/**
 * @psalm-taint-sink html $value
 *
 * Updates the value of an option that was already added.
 *
 * You do not need to serialize values. If the value needs to be serialized,
 * then it will be serialized before it is inserted into the database.
 * Remember, resources cannot be serialized or added as an option.
 *
 * If the option does not exist, it will be created.
 * This function is designed to work with or without a logged-in user. In terms of security,
 * plugin developers should check the current user's capabilities before updating any options.
 *
 * @param string $option Name of the option to update. Expected to not be SQL-escaped.
 * @param mixed $value Option value. Must be serializable if non-scalar. Expected to not be SQL-escaped.
 * @param string|bool $autoload Optional. Whether to load the option when WordPress starts up. For existing options,
 *                              `$autoload` can only be updated using `update_option()` if `$value` is also changed.
 *                              Accepts 'yes'|true to enable or 'no'|false to disable.
 *                              Autoloading too many options can lead to performance problems, especially if the
 *                              options are not frequently used. For options which are accessed across several places
 *                              in the frontend, it is recommended to autoload them, by using 'yes'|true.
 *                              For options which are accessed only on few specific URLs, it is recommended
 *                              to not autoload them, by using 'no'|false. For non-existent options, the default value
 *                              is 'yes'. Default null.
 * @return bool True if the value was updated, false otherwise.
 * @since 4.2.0 The `$autoload` parameter was added.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 1.0.0
 */
function update_option($option, $value, $autoload = null)
{
}


$_POST['value'] = 'tainted_input';

$recognized_value = $_POST;
$new_nested_array_recognized = array($_POST);

$merged_array_unrecognized = array_merge(array(), $_POST);
$new_array_unrecognized = array('value' => $_POST['value']);
$unrecognized_value = $_POST['value'];


update_option('key', $recognized_value); // recognized
update_option('key', $new_nested_array_recognized); // recognized
update_option('key', $new_array_unrecognized); // unrecognized
update_option('key', $unrecognized_value); // unrecognized
update_option('key', $merged_array_unrecognized); // unrecognized

The update_option function is from WordPress and is writing options settings to the database. Psalm only detects the taint for $recognized_value and $new_nested_array_recognized. However, I would expect it to detect it for all five cases, since tainted HTML would be written to the database in each of them.

This is Psalm's output after running psalm --taint-analysis:

ERROR: TaintedHtml
at ~/GitHub/psalm-test/under-test/target/taint-two.php:49:22
Detected tainted HTML (see https://psalm.dev/245)
  $_POST
    <no known location>

  $recognized_value - under-test/target/taint-two.php:41:1
$recognized_value = $_POST;

  call to update_option - under-test/target/taint-two.php:49:22
update_option('key', $recognized_value); // recognized



ERROR: TaintedHtml
at ~/GitHub/psalm-test/under-test/target/taint-two.php:50:22
Detected tainted HTML (see https://psalm.dev/245)
  $_POST
    <no known location>

  array['0'] - under-test/target/taint-two.php:42:38
$new_nested_array_recognized = array($_POST);

  $new_nested_array_recognized - under-test/target/taint-two.php:42:1
$new_nested_array_recognized = array($_POST);

  call to update_option - under-test/target/taint-two.php:50:22
update_option('key', $new_nested_array_recognized); // recognized



------------------------------
2 errors found
------------------------------

Is this defined behavior or is there something not correct or incomplete with how Psalm handles the taint flow in this example?

Best Regards,
Tuncay

Copy link

Hey @talarcin, can you reproduce the issue on https://psalm.dev? These will be used as phpunit tests when implementing the feature or fixing this bug.

@talarcin talarcin reopened this Apr 29, 2024
@orklah
Copy link
Collaborator

orklah commented Apr 29, 2024

reproducer:
https://psalm.dev/r/887f950099

Note that I commented the value you assigned into $_POST because otherwise Psalm can tell it's not tainted.

It does leave 2 unrecognized cases that are definitely bugs

Copy link

I found these snippets:

https://psalm.dev/r/887f950099
<?php //--taint-analysis

/**
 * @psalm-taint-sink html $value
 *
 * Updates the value of an option that was already added.
 *
 * You do not need to serialize values. If the value needs to be serialized,
 * then it will be serialized before it is inserted into the database.
 * Remember, resources cannot be serialized or added as an option.
 *
 * If the option does not exist, it will be created.
 * This function is designed to work with or without a logged-in user. In terms of security,
 * plugin developers should check the current user's capabilities before updating any options.
 *
 * @param string $option Name of the option to update. Expected to not be SQL-escaped.
 * @param mixed $value Option value. Must be serializable if non-scalar. Expected to not be SQL-escaped.
 * @param string|bool $autoload Optional. Whether to load the option when WordPress starts up. For existing options,
 *                              `$autoload` can only be updated using `update_option()` if `$value` is also changed.
 *                              Accepts 'yes'|true to enable or 'no'|false to disable.
 *                              Autoloading too many options can lead to performance problems, especially if the
 *                              options are not frequently used. For options which are accessed across several places
 *                              in the frontend, it is recommended to autoload them, by using 'yes'|true.
 *                              For options which are accessed only on few specific URLs, it is recommended
 *                              to not autoload them, by using 'no'|false. For non-existent options, the default value
 *                              is 'yes'. Default null.
 * @return bool True if the value was updated, false otherwise.
 * @since 4.2.0 The `$autoload` parameter was added.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 1.0.0
 */
function update_option($option, $value, $autoload = null)
{
}


//$_POST['value'] = 'tainted_input';

$recognized_value = $_POST;
$new_nested_array_recognized = array($_POST);

$merged_array_unrecognized = array_merge(array(), $_POST);
$new_array_unrecognized = array('value' => $_POST['value']);
$unrecognized_value = $_POST['value'];


update_option('key', $recognized_value); // recognized
update_option('key', $new_nested_array_recognized); // recognized
update_option('key', $new_array_unrecognized); // unrecognized
update_option('key', $unrecognized_value); // unrecognized
update_option('key', $merged_array_unrecognized); // unrecognized
Psalm output (using commit 08afc45):

ERROR: TaintedHtml - 34:33 - Detected tainted HTML

ERROR: TaintedHtml - 34:33 - Detected tainted HTML

ERROR: TaintedHtml - 34:33 - Detected tainted HTML

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants