diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..28c16af --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +## ChangeLog + +--- develop --- + +* security: Escape WMI account, query, and tool output on render (html_escape/__esc) to close stored and reflected XSS +* security: Bind the remaining interpolated SQL as prepared statements in functions.php, poller_wmi.php, and script/wmi-script.php +* security: Remove wmi_accounts.php and wmi_tools.php from the Template Editor auth augment so credential management and the live query tool stay behind the WMI Management realm +* security: Escape the wmic hostname and namespace before exec so a device-supplied address cannot inject a shell command (issue#5) +* security: Quote the wmic delimiter so exec() no longer splits the command into a shell pipeline (issue#5) +* security: Restrict decode() unserialize with allowed_classes to block PHP object injection (issue#5) diff --git a/functions.php b/functions.php index 884d7d4..c632925 100644 --- a/functions.php +++ b/functions.php @@ -58,7 +58,7 @@ function plugin_wmi_query_exists($query) { foreach($tokens as $token) { if ($next_ic) { - $exists = db_fetch_cell("SELECT COUNT(*) FROM wmi_wql_queries WHERE query RLIKE '^FROM\s$token$+'"); + $exists = db_fetch_cell_prepared('SELECT COUNT(*) FROM wmi_wql_queries WHERE query RLIKE ?', array('^FROM\s' . $token . '$+')); } if (strtolower($token) == 'from') { @@ -71,7 +71,7 @@ function plugin_wmi_create_dataquery_xml($id) { global $config; include_once($config['base_path'] . '/lib/export.php'); - $wmic = db_fetch_row("SELECT * FROM wmi_wql_queries WHERE id = $id"); + $wmic = db_fetch_row_prepared('SELECT * FROM wmi_wql_queries WHERE id = ?', array($id)); $data = ''; if (isset($wmic['id'])) { $data = "\n"; @@ -174,7 +174,7 @@ function plugin_wmi_create_dataquery_xml($id) { } $data .= "\t\t\n"; - $data_input_data = db_fetch_assoc("SELECT * FROM data_input_fields WHERE data_input_fields.data_input_id=$input AND input_output = 'in' ORDER BY id DESC"); + $data_input_data = db_fetch_assoc_prepared("SELECT * FROM data_input_fields WHERE data_input_fields.data_input_id = ? AND input_output = 'in' ORDER BY id DESC", array($input)); $data .= "\t\t\n"; $i = 0; if (cacti_sizeof($data_input_data) > 0) { @@ -278,7 +278,7 @@ function plugin_wmi_create_dataquery_xml($id) { } function plugin_wmi_create_resource_xml($id) { - $wmic = db_fetch_row("SELECT * FROM wmi_wql_queries WHERE id = $id"); + $wmic = db_fetch_row_prepared('SELECT * FROM wmi_wql_queries WHERE id = ?', array($id)); $data = ''; if (isset($wmic['id'])) { $data = "\n"; diff --git a/poller_wmi.php b/poller_wmi.php index 7d19aa1..42d525c 100644 --- a/poller_wmi.php +++ b/poller_wmi.php @@ -191,7 +191,7 @@ function process_all_devices() { /* put a placeholder in place to prevent overloads on slow systems */ $key = rand(); - db_execute("INSERT INTO wmi_processes (pid, taskid, started) VALUES ($key, $seed, NOW())"); + db_execute_prepared('INSERT INTO wmi_processes (pid, taskid, started) VALUES (?, ?, NOW())', array($key, $seed)); print "NOTE: Launching WMI Collector For: '" . $device['description'] . '[' . $device['hostname'] . "]'" . PHP_EOL; @@ -210,7 +210,7 @@ function process_all_devices() { /* wait for all processes to end or max run time */ while (true) { - $processes_left = db_fetch_cell("SELECT COUNT(*) FROM wmi_processes WHERE taskid = $seed"); + $processes_left = db_fetch_cell_prepared('SELECT COUNT(*) FROM wmi_processes WHERE taskid = ?', array($seed)); $pl = db_fetch_cell('SELECT COUNT(*) FROM wmi_processes'); if ($processes_left == 0) { @@ -296,8 +296,8 @@ function process_device($host_id) { array($host_id)); /* remove the key process and insert the set a process lock */ - db_execute('REPLACE INTO wmi_processes (pid, taskid) VALUES (' . getmypid() . ", $seed)"); - db_execute("DELETE FROM wmi_processes WHERE pid = $key AND taskid = $seed"); + db_execute_prepared('REPLACE INTO wmi_processes (pid, taskid) VALUES (?, ?)', array(getmypid(), $seed)); + db_execute_prepared('DELETE FROM wmi_processes WHERE pid = ? AND taskid = ?', array($key, $seed)); $qstart = date('Y-m-d H:i:s'); @@ -355,7 +355,7 @@ function process_device($host_id) { } /* remove the process lock */ - db_execute('DELETE FROM wmi_processes WHERE pid=' . getmypid()); + db_execute_prepared('DELETE FROM wmi_processes WHERE pid = ?', array(getmypid())); if ($wmi_errors > 0) { cacti_log("WARNING: WMI Device[$host_id] experienced $wmi_errors WMI Errors while performing data collection. Increase logging to HIGH for this device to see the errors.", false, 'WMI'); diff --git a/script/wmi-script.php b/script/wmi-script.php index 437608c..47980f0 100644 --- a/script/wmi-script.php +++ b/script/wmi-script.php @@ -42,7 +42,7 @@ function wmi_script($hostname, $host_id, $wmiquery, $cmd = '', $arg1 = '', $arg2 $wmi->binary = $config['base_path'] . '/plugins/wmi/wmic'; /* Fetch the info for this WMI query from the database, exit if not found */ - $wmiinfo = db_fetch_row("SELECT * FROM plugin_wmi_queries WHERE queryname = '$wmiquery'", FALSE); + $wmiinfo = db_fetch_row_prepared('SELECT * FROM plugin_wmi_queries WHERE queryname = ?', array($wmiquery)); if (!isset($wmiinfo['queryclass'])) { return ''; } diff --git a/setup.php b/setup.php index d5aa4c4..59ed773 100644 --- a/setup.php +++ b/setup.php @@ -304,7 +304,11 @@ function wmi_config_arrays() { $menu[__('Data Collection')]['plugins/wmi/wmi_queries.php'] = __('WMI Queries', 'wmi'); if (function_exists('auth_augment_roles')) { - auth_augment_roles(__('Template Editor'), array('wmi_accounts.php', 'wmi_queries.php', 'wmi_tools.php')); + // Template Editors define WMI queries as part of template work. Credential + // management (wmi_accounts.php) and the live query tool (wmi_tools.php, + // which reaches Linux_WMI::exec) stay behind the dedicated WMI Management + // realm rather than being handed to every Template Editor. + auth_augment_roles(__('Template Editor'), array('wmi_queries.php')); } } diff --git a/wmi_accounts.php b/wmi_accounts.php index 8fa131a..c8c062e 100644 --- a/wmi_accounts.php +++ b/wmi_accounts.php @@ -220,7 +220,7 @@ function edit_accounts() { $account = db_fetch_row_prepared('SELECT * FROM wmi_user_accounts WHERE id = ?', array(get_request_var('id'))); $account['password'] = ''; - $header_label = __('Account [edit: %s]', $account['name'], 'wmi'); + $header_label = __esc('Account [edit: %s]', $account['name'], 'wmi'); }else{ $header_label = __('Account [new]', 'wmi'); } @@ -416,7 +416,7 @@ function show_accounts() { form_alternate_row('line' . $row['id'], false); form_selectable_cell(filter_value($row['name'], get_request_var('filter'), 'wmi_accounts.php?&action=edit&id=' . $row['id']), $row['id']); - form_selectable_cell($row['username'], $row['id']); + form_selectable_cell(html_escape($row['username']), $row['id']); form_selectable_cell(number_format_i18n($count), $row['id'], '', 'right'); form_checkbox_cell($row['name'], $row['id']); form_end_row(); diff --git a/wmi_queries.php b/wmi_queries.php index 99ef09c..efa984d 100644 --- a/wmi_queries.php +++ b/wmi_queries.php @@ -239,7 +239,7 @@ function edit_queries() { WHERE id= ?', array(get_filter_request_var('id'))); - $header_label = __('Query [edit: %s]', $query['name'], 'wmi'); + $header_label = __esc('Query [edit: %s]', $query['name'], 'wmi'); }else{ $header_label = __('Query [new]', 'wmi'); } @@ -452,12 +452,12 @@ function show_queries() { if (!empty($queries)) { foreach ($queries as $row) { form_alternate_row('line' . $row['id'], true); - form_selectable_cell('' . $row['name'] . '', $row['id']); + form_selectable_cell('' . html_escape($row['name']) . '', $row['id']); form_selectable_cell($row['id'], $row['id'], '', 'right'); form_selectable_cell($row['frequency'], $row['id'], '', 'right'); - form_selectable_cell($row['namespace'], $row['id']); - form_selectable_cell($row['query'], $row['id']); - form_selectable_cell($row['primary_key'], $row['id']); + form_selectable_ecell($row['namespace'], $row['id']); + form_selectable_ecell($row['query'], $row['id']); + form_selectable_ecell($row['primary_key'], $row['id']); form_checkbox_cell($row['name'], $row['id']); form_end_row(); } diff --git a/wmi_tools.php b/wmi_tools.php index 86f4a4b..b6ee45f 100644 --- a/wmi_tools.php +++ b/wmi_tools.php @@ -549,7 +549,7 @@ function walk_host() { $class = $wmi->fetch_class(); $data = $wmi->fetch_data(); - print "

" . __('WMI Query Results for Device: %s, Class: %s, Columns: %s, Rows: %s', $host, $class, sizeof($indexes), sizeof($data), 'wmi') . "

"; + print "

" . __esc('WMI Query Results for Device: %s, Class: %s, Columns: %s, Rows: %s', $host, $class, sizeof($indexes), sizeof($data), 'wmi') . "

"; print "

" . __('Showing columns and first one or two rows of data.', 'wmi') . "

"; @@ -563,10 +563,10 @@ function walk_host() { foreach($data[0] as $index => $r) { form_alternate_row('line' . $index, true); - print "" . $indexes[$index] . "" . $r . ""; + print "" . html_escape($indexes[$index]) . "" . html_escape($r) . ""; if (isset($data[1][$index])) { - print "" . $indexes[$index] . "" . $data[1][$index] . ""; + print "" . html_escape($indexes[$index]) . "" . html_escape($data[1][$index]) . ""; } form_end_row(); @@ -578,14 +578,14 @@ function walk_host() { if (cacti_sizeof($indexes)) { print ""; foreach($indexes as $col) { - print "" . $col . ""; + print "" . html_escape($col) . ""; } print ""; } print ""; foreach($row as $data) { - print "" . $data . ""; + print "" . html_escape($data) . ""; } print ""; } @@ -614,7 +614,7 @@ function walk_host() { print ""; + print ""; if (cacti_sizeof($odata1)) { - print ""; + print ""; } form_end_row();
"; - print "

" . __('WMI Query Results for Device: %s, Class: %s, Columns: %s, Rows: %s', $host, $namespace, sizeof($indexes), sizeof($data), 'wmi') . "

"; + print "

" . __esc('WMI Query Results for Device: %s, Class: %s, Columns: %s, Rows: %s', $host, $namespace, sizeof($indexes), sizeof($data), 'wmi') . "

"; print "

" . __('Showing columns and first one or two rows of data.', 'wmi') . "

"; @@ -625,10 +625,10 @@ function walk_host() { foreach($odata as $index => $r) { form_alternate_row('line' . $index, true); - print "
" . $indexes[$index] . "" . $r . "" . html_escape($indexes[$index]) . "" . html_escape($r) . "" . $indexes[$index] . "" . $odata1[$index] . "" . html_escape($indexes[$index]) . "" . html_escape($odata1[$index]) . "