Select box and radio button selection have protection against forgery of value of input selection. However for checkboxes, the form still submits the fake value of checkbox selection, which expected to be failed in validation and should return a message:
"An illegal choice has been detected. Please contact the site administrator."

Same with disabled input elements, making the "#disabled" property of any element to TRUE should prohibit the user from submitting the form but by editing the HTML code to remove the disabled="disabled" property of any input element and submit this form, Drupal does not validate this kind of forgery and still accept the submitted disabled input form.

I'm using D7 CVS updated.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

arpeggio’s picture

Priority: Normal » Critical

This is a security issue, I think this is critical.

Heine’s picture

To reproduce:

function scratch_form(&$form_state) {
	$form = array();

	$options = array(
		'one' => 'One',
		'two' => 'Two',
	);

	$form['secissue'] = array(
		'#type' => 'checkboxes',
		'#options' => $options,
	);

	$form['submit'] = array(
		'#type' => 'submit',
		'#value' => t('Submit'),
	);
	return $form;
}

function scratch_form_submit($form, &$form_state) {
	drupal_set_message('<pre>'. check_plain(print_r($form_state['values'], TRUE)) .'</pre>');
}

When checking the checkboxes, results in (only relevant key given):

[secissue] => Array
        (
            [one] => one
            [two] => two
        )

Whe modifying the value of $_POSTed secissue[two] to FOO:

[secissue] => Array
        (
            [one] => one
            [two] => FOO
        )

Only the value is affected; when you try to add another key, the "Illegal option" error message is given. This is not an issue on Drupal 6.

Heine’s picture

arpeggio’s picture

Thank you for providing how to reproduce the issue. BTW, yesterday I've already sent the full details to security@drupal.org about this issue because Nathaniel suggested me that'll help you to track this. Thanks.

arpeggio’s picture

I would like to suggest the following patch (for includes/form.inc) to solve the checkboxes input selection value forgery issue:

--- form.inc   19 May 2010 19:22:24 -0000   1.464
+++ form.inc   23 May 2010 12:51:48 -0000
@@ -941,7 +941,7 @@
           $options = $elements['#options'];
         }
         if (is_array($elements['#value'])) {
-          $value = $elements['#type'] == 'checkboxes' ? array_keys(array_filter($elements['#value'])) : $elements['#value'];
+          $value = $elements['#type'] == 'checkboxes' ? array_filter($elements['#value']) : $elements['#value'];               
           foreach ($value as $v) {
             if (!isset($options[$v])) {
               form_error($elements, $t('An illegal choice has been detected. Please contact the site administrator.'));

I removed the array_keys() because the suspected fake value/s is/are not in the array key/s but in the array value/s. Thanks.

Heine’s picture

I've traced this back to #414424: Introduce Form API #type 'text_format'.

In Drupal 6, checkboxes behaviour results in:

array(
  'key' => 'key', // For checked boxes
  'key' => 0,     // For unchecked boxes
);

regardless of the 'value' posted for the element.

What happens in D6 is that the "input" from the checkboxes element is added to form_state, and later overwritten by the child checkboxes; they always return the correct return_value. The following check that was added by the referenced issue prevents this overwriting by the child elements:

3f587416 (webchick 2010-03-07 23:14:20 +0000 1561)   // Set the element's value in $form_state['values'], but only, if its key
3f587416 (webchick 2010-03-07 23:14:20 +0000 1562)   // does not exist yet (a #value_callback may have already populated it).
3f587416 (webchick 2010-03-07 23:14:20 +0000 1563)   $values = $form_state['values'];
3f587416 (webchick 2010-03-07 23:14:20 +0000 1564)   foreach ($element['#parents'] as $key) {
3f587416 (webchick 2010-03-07 23:14:20 +0000 1565)     $values = (isset($values[$key]) ? $values[$key] : NULL);
3f587416 (webchick 2010-03-07 23:14:20 +0000 1566)   }
3f587416 (webchick 2010-03-07 23:14:20 +0000 1567)   if (!isset($values)) {
3f587416 (webchick 2010-03-07 23:14:20 +0000 1568)     form_set_value($element, $element['#value'], $form_state);
3f587416 (webchick 2010-03-07 23:14:20 +0000 1569)   }

This _could_ be handled by the checkboxes value callback, or process handler, but I doubt that it is the correct approach; the behaviour of the processed element should depend on the children, and should not be coded entirely in the parent process / value callbacks.

Heine’s picture

effulgentsia’s picture

Status: Active » Needs review
FileSize
3.45 KB

Thanks. This fixes form_type_checkboxes_value() to be consistent with form_type_select_value().

effulgentsia’s picture

This also fixes drupal_map_assoc() to handle empty arrays.

rfay’s picture

subscribe

effulgentsia’s picture

@arpeggio: In the original issue description, you write:

Same with disabled input elements, making the "#disabled" property of any element to TRUE should prohibit the user from submitting the form but by editing the HTML code to remove the disabled="disabled" property of any input element and submit this form, Drupal does not validate this kind of forgery and still accept the submitted disabled input form.

This is true for D6, but was fixed for D7: #426056: Server-side enforcement of #disabled is inconsistent. If you can find a use-case where that bug remains, please post the steps to reproduce it. Thanks. Note, for D6, since #disabled isn't enforced server-side, if your module needs such enforcement, you need to set #value. See the linked issue.

arpeggio’s picture

FileSize
835 bytes

Actually I have sent the detailed steps to security@drupal.org. Anyway here are the steps I sent and the attachment is the module I created:

I have created a simple module that can aid the security team to simulate the issue. Here are the steps:
1. Enable the devel and issue modules.
2. Go to http://localhost/admin/config/development/issue
3. The submit button is disabled and it means the user is prohibited to submit this form. We will try to hack the form so we can submit this disabled form. Edit the HTML code (its easier if using Chrome browser, just right click the element and select "Inspect element" from the pop-up menu and editing the DOM is a breeze) to remove the disabled="disabled" property of submit button.
4. Then we will try to hack one of the value of checkboxes, again by editing the HTML code and change the value of "anonymous user" which is value="1" to value="hacked".
5. Click the "Apply" button.
We can see that we have successfully submitted this disabled form without error and in the devel debug message box we clearly see that the value of "anonymous user" ($form_state['values]['access_roles_checkbox'][1]) changed to "hacked".

Heine’s picture

Can we please discuss the disabled issue elsewhere? edit; Maybe reopen #426056: Server-side enforcement of #disabled is inconsistent ?

Heine’s picture

As of #9; I find it odd that an element type processed into simpler types has no access to values of that type, but has to reimplement such functionality on raw $_POST data.

I would expect the following flow of information:

- type checkboxes -> processed into multiple checkbox elements
- value of all checkbox elements is set _individually_ in form_type_checkbox_value
- form_type_checkboxes_value runs later & receives these values, then returns the appropriate value for the checkboxes element.
- this value lands in form_state['values']

Unfortunately, it seems to late to do this in D7.

arpeggio’s picture

@Heine: I reopened your suggested thread about disabled issue.

effulgentsia’s picture

Thanks for #12. #736298: Refactor file.module to use proper button click detection, enabling FAPI to improve button click detection security fixes that problem (see comment #46 on that issue): I'll try to roll in a test into that issue's patch with your use-case. So as per #13, this issue is now strictly about checkbox value forgery.

Re #14:

Unfortunately, it seems too late to do this in D7.

I agree that it is too late for that for D7. _form_builder_handle_input_element() runs in pre-order traversal (i.e., form_builder() calls it before recursing). This has been the case since FAPI was first introduced, and changing it to post-order traversal will have many side effects that are way out of scope for D7.

moshe weitzman’s picture

anyone available to slay this critical?

effulgentsia’s picture

#9 slays this as far as I'm concerned. Comments after it have been answered or relegated to other issues. Anyone available to RTBC this issue?

catch’s picture

Status: Needs review » Reviewed & tested by the community

Patch is only a couple of lines and the test looks good, it would be nice to see another FAPI maintainer or Heine take a look at this before commit, maybe marking RTBC will encourage that second look.

Heine’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
4.14 KB

Patch itself is RTBC would it not for the options checker allowing the insertion of 0 keys:

$value = $elements['#type'] == 'checkboxes' ? array_keys($elements['#value']) : $elements['#value'];
effulgentsia’s picture

I think I agree with the change in #20, but I'm not sure it belongs in this issue. #654796: Identify fix bugs with checkbox element is still a critical issue for D7, and if the line in #20 has unwanted side-effects, that would be the issue to catch it. @Heine: what's your thinking for including it here?

Dries’s picture

Status: Needs review » Fixed

I reviewed this patch -- it fixes the problem. Committed to CVS HEAD. Thanks Heine and effulgentsia -- great job.

David_Rothstein’s picture

The change introduced in #20 did indeed have an unwanted side effect: It prevents drupal_form_submit() from programmatically turning off a checkbox by submitting a NULL value for it.

I posted a patch which fixes it here (not a simple rollback, since for everything but checkboxes I think #20 is probably correct):

#827430: drupal_form_submit() no longer works with checkboxes

It could be pulled into #654796: Identify fix bugs with checkbox element instead, but I think probably not, since this really is just about NULL values rather than other things which also evaluate to empty.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.