From 78a4701b9c384f9adb976c248b92ad3d4d556972 Mon Sep 17 00:00:00 2001 From: garvinhicking Date: Tue, 27 Sep 2005 11:40:14 +0000 Subject: [PATCH] Try to prevent XSRF exploits by passing a form token. --- docs/NEWS | 5 +- include/admin/category.inc.php | 6 +- include/admin/comments.inc.php | 20 ++++--- include/admin/configuration.inc.php | 2 +- include/admin/groups.inc.php | 8 ++- include/admin/images.inc.php | 31 +++++----- include/admin/import.inc.php | 24 ++++++-- include/admin/importers/generic.inc.php | 1 + include/admin/personal.inc.php | 6 +- include/admin/plugins.inc.php | 16 ++--- include/admin/users.inc.php | 8 ++- include/functions_config.inc.php | 60 +++++++++++++++++-- include/functions_entries_admin.inc.php | 2 +- include/functions_images.inc.php | 1 + include/functions_installer.inc.php | 7 ++- .../serendipity_plugin_remoterss.php | 21 ++++++- serendipity_config.inc.php | 10 ++-- 17 files changed, 163 insertions(+), 65 deletions(-) diff --git a/docs/NEWS b/docs/NEWS index d92d479..4d18d19 100644 --- a/docs/NEWS +++ b/docs/NEWS @@ -3,8 +3,6 @@ Version 0.9 () ------------------------------------------------------------------------ - * Try to detect XSRF based on HTTP-Referrer. (garvinhicking) - * Fix not showing thumbnail images in media database when thumbSuffix is empty. Thanks to Brian J. France! @@ -226,7 +224,8 @@ Version 0.8.5 () ------------------------------------------------------------------------ * More Security: When changing the password in your personal preferences, - you need to insert the old password. Thanks to Nenad Jovanovic for + you need to insert the old password. Secure backend forms with extra + token checks to bypass XSRF attacks. Thanks to Nenad Jovanovic for contacting me about this issue! (garvinhicking) * Fix JS errors in admin comment overview for IE6 (garvinhicking) diff --git a/include/admin/category.inc.php b/include/admin/category.inc.php index 6c1c92c..1f26c48 100644 --- a/include/admin/category.inc.php +++ b/include/admin/category.inc.php @@ -13,7 +13,7 @@ if (!serendipity_checkPermission('adminCategories')) { $admin_category = (!serendipity_checkPermission('adminCategoriesMaintainOthers') ? "AND (authorid = 0 OR authorid = " . (int)$serendipity['authorid'] . ")" : ''); /* Add a new category */ -if (isset($_POST['SAVE'])) { +if (isset($_POST['SAVE']) && serendipity_checkFormToken()) { $name = $serendipity['POST']['cat']['name']; $desc = $serendipity['POST']['cat']['description']; @@ -82,7 +82,7 @@ if (isset($_POST['SAVE'])) { } /* Delete a category */ -if ($serendipity['GET']['adminAction'] == 'doDelete') { +if ($serendipity['GET']['adminAction'] == 'doDelete' && serendipity_checkFormToken()) { if ($serendipity['GET']['cid'] != 0) { $remaining_cat = (int)$serendipity['POST']['cat']['remaining_catid']; $category_ranges = serendipity_fetchCategoryRange((int)$serendipity['GET']['cid']); @@ -135,6 +135,7 @@ if ($serendipity['GET']['adminAction'] == 'doDelete') { || (serendipity_checkPermission('adminCategoriesDelete') && serendipity_ACLCheck($serendipity['authorid'], $serendipity['GET']['cid'], 'category', 'write'))) { ?>
+
: @@ -255,6 +258,7 @@ function highlightComment(id, checkvalue) { } else { ?> +
@@ -360,13 +364,13 @@ foreach ($sql as $rs) {
- <?php echo APPROVE ?> + <?php echo APPROVE ?> <?php echo VIEW; ?> - <?php echo EDIT; ?> - ")' title="" class="serendipityIconLink"><?php echo DELETE; ?> + <?php echo EDIT; ?> + ")' title="" class="serendipityIconLink"><?php echo DELETE; ?> diff --git a/include/admin/configuration.inc.php b/include/admin/configuration.inc.php index e34cc99..2dc1864 100644 --- a/include/admin/configuration.inc.php +++ b/include/admin/configuration.inc.php @@ -14,7 +14,7 @@ if (!serendipity_checkPermission('siteConfiguration') && !!serendipity_checkPerm return; } -switch ($_POST['installAction']) { +switch ($_POST['installAction'] && serendipity_checkFormToken()) { case 'check': $oldConfig = $serendipity; $res = serendipity_updateConfiguration(); diff --git a/include/admin/groups.inc.php b/include/admin/groups.inc.php index be1951a..aebf749 100644 --- a/include/admin/groups.inc.php +++ b/include/admin/groups.inc.php @@ -11,14 +11,14 @@ if (!serendipity_checkPermission('adminUsersGroups')) { } /* Delete a group */ -if (isset($_POST['DELETE_YES'])) { +if (isset($_POST['DELETE_YES']) && serendipity_checkFormToken()) { $group = serendipity_fetchGroup($serendipity['POST']['group']); serendipity_deleteGroup($serendipity['POST']['group']); printf('
' . DELETED_GROUP . '
', $serendipity['POST']['group'], $group['name']); } /* Save new group */ -if (isset($_POST['SAVE_NEW'])) { +if (isset($_POST['SAVE_NEW']) && serendipity_checkFormToken()) { $serendipity['POST']['group'] = serendipity_addGroup($serendipity['POST']['name']); $perms = serendipity_getAllPermissionNames(); serendipity_updateGroupConfig($serendipity['POST']['group'], $perms, $serendipity['POST']); @@ -27,7 +27,7 @@ if (isset($_POST['SAVE_NEW'])) { /* Edit a group */ -if (isset($_POST['SAVE_EDIT'])) { +if (isset($_POST['SAVE_EDIT']) && serendipity_checkFormToken()) { $perms = serendipity_getAllPermissionNames(); serendipity_updateGroupConfig($serendipity['POST']['group'], $perms, $serendipity['POST']); printf('
' . MODIFIED_GROUP . '
', $serendipity['POST']['name']); @@ -88,6 +88,7 @@ if ($serendipity['GET']['adminAction'] == 'edit' || isset($_POST['NEW'])) {

+



+ diff --git a/include/admin/images.inc.php b/include/admin/images.inc.php index b7a4852..4918029 100644 --- a/include/admin/images.inc.php +++ b/include/admin/images.inc.php @@ -33,7 +33,7 @@ switch ($serendipity['GET']['adminAction']) { break; case 'DoDelete': - if (!serendipity_checkPermission('adminImagesDelete')) { + if (!serendipity_checkFormToken() || !serendipity_checkPermission('adminImagesDelete')) { break; } @@ -49,7 +49,7 @@ switch ($serendipity['GET']['adminAction']) { } $abortLoc = $serendipity['serendipityHTTPPath'] . 'serendipity_admin.php?serendipity[adminModule]=images'; - $newLoc = $abortLoc . '&serendipity[adminAction]=DoDelete&serendipity[fid]=' . $serendipity['GET']['fid']; + $newLoc = $abortLoc . '&serendipity[adminAction]=DoDelete&serendipity[fid]=' . $serendipity['GET']['fid'] . '&' . serendipity_setFormToken('url'); printf(ABOUT_TO_DELETE_FILE, $file['name'] .'.'. $file['extension']); ?> @@ -67,7 +67,7 @@ switch ($serendipity['GET']['adminAction']) { $file = serendipity_fetchImageFromDatabase($serendipity['GET']['fid']); $serendipity['GET']['newname'] = serendipity_uploadSecure($serendipity['GET']['newname'], true); - if (!serendipity_checkPermission('adminImagesDelete') || (!serendipity_checkPermission('adminImagesMaintainOthers') && $file['authorid'] != '0' && $file['authorid'] != $serendipity['authorid'])) { + if (!serendipity_checkFormToken() || !serendipity_checkPermission('adminImagesDelete') || (!serendipity_checkPermission('adminImagesMaintainOthers') && $file['authorid'] != '0' && $file['authorid'] != $serendipity['authorid'])) { return; } @@ -119,7 +119,7 @@ switch ($serendipity['GET']['adminAction']) { break; case 'add': - if (!serendipity_checkPermission('adminImagesAdd')) { + if (!serendipity_checkFormToken() || !serendipity_checkPermission('adminImagesAdd')) { return; } @@ -249,7 +249,7 @@ switch ($serendipity['GET']['adminAction']) { case 'directoryDoDelete': - if (!serendipity_checkPermission('adminImagesDirectories')) { + if (!serendipity_checkFormToken() || !serendipity_checkPermission('adminImagesDirectories')) { return; } @@ -278,6 +278,7 @@ switch ($serendipity['GET']['adminAction']) {

+ @@ -299,7 +300,7 @@ switch ($serendipity['GET']['adminAction']) { break; case 'directoryDoCreate': - if (!serendipity_checkPermission('adminImagesDirectories')) { + if (!serendipity_checkFormToken() || !serendipity_checkPermission('adminImagesDirectories')) { return; } @@ -327,6 +328,7 @@ switch ($serendipity['GET']['adminAction']) {

+
@@ -540,6 +542,7 @@ switch ($serendipity['GET']['adminAction']) {
+ @@ -639,9 +642,9 @@ switch ($serendipity['GET']['adminAction']) { if (serendipity_rotateImg($serendipity['GET']['fid'], -90)) { ?> - + - + // location.href="?serendipity[adminModule]=images"; - + + " /> @@ -758,7 +762,7 @@ switch ($serendipity['GET']['adminAction']) { @@ -775,4 +779,3 @@ switch ($serendipity['GET']['adminAction']) { break; } /* vim: set sts=4 ts=4 expandtab : */ -?> diff --git a/include/admin/import.inc.php b/include/admin/import.inc.php index d3bf0e3..7bb4f7f 100644 --- a/include/admin/import.inc.php +++ b/include/admin/import.inc.php @@ -31,7 +31,7 @@ class Serendipity_Import { } if (LANG_CHARSET != 'ISO-8859-1') { - $charset['ISO-8859-1'] = 'ISO-8859-1'; + $charsets['ISO-8859-1'] = 'ISO-8859-1'; } if ($utf8_default) { @@ -41,8 +41,22 @@ class Serendipity_Import { return $charsets; } - function decode($string) { - switch($this->data['charset']) { + function &decode($string) { + static $phpCharset = null; + $target = $this->data['charset']; + + if ($phpCharset === null) { + $phpCharset = version_compare(phpversion(), '4.3.11', '>='); + } + + if ($phpCharset == 1) { + // Luckily PHP5 supports + // xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, LANG_CHARSET); + // which means we need no transcoding here. + return $string; + } + + switch($target) { case 'native': return $string; @@ -103,7 +117,7 @@ class Serendipity_Import { } } -if ( isset($serendipity['GET']['importFrom']) ) { +if (isset($serendipity['GET']['importFrom']) && serendipity_checkFormToken()) { /* Include the importer */ $class = @require_once(S9Y_INCLUDE_PATH . 'include/admin/importers/'. basename($serendipity['GET']['importFrom']) .'.inc.php'); @@ -133,6 +147,7 @@ if ( isset($serendipity['GET']['importFrom']) ) { :

+
getInputFields() as $field ) { ?> @@ -183,6 +198,7 @@ if ( isset($serendipity['GET']['importFrom']) ) {
+ :
@@ -105,13 +106,13 @@ function show_plugins($event_only = false) if ($sort_idx == 0) { $moveup = ' '; } else { - $moveup = '' . UP . ''; + $moveup = '' . UP . ''; } if ($sort_idx == (count($plugins)-1)) { $movedown = ' '; } else { - $movedown = ($moveup != '' ? ' ' : '') . ''. DOWN .''; + $movedown = ($moveup != '' ? ' ' : '') . ''. DOWN .''; } ?> @@ -214,7 +215,7 @@ function placement_box($name, $val, $is_plugin_editable = false) return $x . "\n"; } -if (isset($_GET['serendipity']['plugin_to_move']) && isset($_GET['submit'])) { +if (isset($_GET['serendipity']['plugin_to_move']) && isset($_GET['submit']) && serendipity_checkFormToken()) { if (isset($_GET['serendipity']['event_plugin'])) { $plugins = serendipity_plugin_api::enum_plugins('event', false); } else { @@ -270,7 +271,7 @@ if (isset($_GET['serendipity']['plugin_to_conf'])) { $config_names = $bag->get('configuration'); - if (isset($_POST['SAVECONF'])) { + if (isset($_POST['SAVECONF']) && serendipity_checkFormToken()) { /* enum properties and set their values */ $save_errors = array(); @@ -310,6 +311,7 @@ if (isset($_GET['serendipity']['plugin_to_conf'])) { +
 
@@ -625,6 +627,7 @@ if (isset($_GET['serendipity']['plugin_to_conf'])) {
+ @@ -743,7 +746,7 @@ if (isset($_GET['serendipity']['plugin_to_conf'])) { } else { /* show general plugin list */ - if (isset($_POST['SAVE']) && isset($_POST['serendipity']['placement'])) { + if (isset($_POST['SAVE']) && isset($_POST['serendipity']['placement']) && serendipity_checkFormToken()) { foreach ($_POST['serendipity']['placement'] as $plugin_name => $placement) { serendipity_plugin_api::update_plugin_placement( addslashes($plugin_name), @@ -792,7 +795,7 @@ if (isset($_GET['serendipity']['plugin_to_conf'])) { } } - if (isset($_POST['REMOVE'])) { + if (isset($_POST['REMOVE']) && serendipity_checkFormToken()) { if (is_array($_POST['serendipity']['plugin_to_remove'])) { foreach ($_POST['serendipity']['plugin_to_remove'] as $key) { $plugin =& serendipity_plugin_api::load_plugin($key); @@ -824,4 +827,3 @@ if (isset($_GET['serendipity']['plugin_to_conf'])) { diff --git a/include/admin/users.inc.php b/include/admin/users.inc.php index 49b6f37..9e86e91 100644 --- a/include/admin/users.inc.php +++ b/include/admin/users.inc.php @@ -13,7 +13,7 @@ if (!serendipity_checkPermission('adminUsers')) { require_once(S9Y_INCLUDE_PATH . 'include/functions_installer.inc.php'); /* Delete a user */ -if (isset($_POST['DELETE_YES'])) { +if (isset($_POST['DELETE_YES']) && serendipity_checkFormToken()) { $user = serendipity_fetchUsers($serendipity['POST']['user']); if ($user[0]['userlevel'] >= $serendipity['serendipityUserlevel'] || !serendipity_checkPermission('adminUsersDelete')) { echo '
' . CREATE_NOT_AUTHORIZED . '
'; @@ -35,7 +35,7 @@ if (isset($_POST['DELETE_YES'])) { /* Save new user */ -if (isset($_POST['SAVE_NEW'])) { +if (isset($_POST['SAVE_NEW']) && serendipity_checkFormToken()) { if ($_POST['userlevel'] >= $serendipity['serendipityUserlevel'] || !serendipity_checkPermission('adminUsersCreateNew')) { echo '
' . CREATE_NOT_AUTHORIZED . '
'; } else { @@ -87,7 +87,7 @@ if (isset($_POST['SAVE_NEW'])) { /* Edit a user */ -if (isset($_POST['SAVE_EDIT'])) { +if (isset($_POST['SAVE_EDIT']) && serendipity_checkFormToken()) { $user = serendipity_fetchUsers($serendipity['POST']['user']); if (!serendipity_checkPermission('adminUsersMaintainOthers') && $user[0]['userlevel'] >= $serendipity['serendipityUserlevel']) { echo '
' . CREATE_NOT_AUTHORIZED . '
'; @@ -213,6 +213,7 @@ if ( ($serendipity['GET']['adminAction'] == 'edit' && serendipity_checkPermissio

+



+ diff --git a/include/functions_config.inc.php b/include/functions_config.inc.php index 1782168..d8603d9 100644 --- a/include/functions_config.inc.php +++ b/include/functions_config.inc.php @@ -299,6 +299,7 @@ function serendipity_authenticate_author($username = '', $password = '', $is_md5 $row = serendipity_db_query($query, true, 'assoc'); if (is_array($row)) { + serendipity_setCookie('old_session', session_id()); $_SESSION['serendipityUser'] = $serendipity['serendipityUser'] = $username; $_SESSION['serendipityPassword'] = $serendipity['serendipityPassword'] = $password; $_SESSION['serendipityEmail'] = $serendipity['serendipityEmail'] = $row['email']; @@ -1109,14 +1110,14 @@ function serendipity_checkXSRF() { // The referrer was empty. Deny access. if (empty($_SERVER['HTTP_REFERER'])) { - echo serendipity_reportXSRF(1); + echo serendipity_reportXSRF(1, true, true); return false; } // Parse the Referrer host. Abort if not parseable. $hostinfo = @parse_url($_SERVER['HTTP_REFERER']); if (!is_array($hostinfo)) { - echo serendipity_reportXSRF(2); + echo serendipity_reportXSRF(2, true, true); return true; } @@ -1133,19 +1134,66 @@ function serendipity_checkXSRF() { // If the current server is different than the referred server, deny access. if ($hostinfo['host'] != $server) { - echo serendipity_reportXSRF(3); + echo serendipity_reportXSRF(3, true, true); return true; } return false; } -function serendipity_reportXSRF($type = 0) { +function serendipity_reportXSRF($type = 0, $reset = true, $use_config = false) { global $serendipity; + // Set this in your serendipity_config_local.inc.php if you want HTTP Referrer blocking: + // $serendipity['referrerXSRF'] = true; + $string = '
' . ERROR_XSRF . '
'; - $serendipity['GET']['adminModule'] = ''; + if ($reset) { + // Config key "referrerXSRF" can be set to enable blocking based on HTTP Referrer. Recommended for Paranoia. + if (($use_config && isset($serendipity['referrerXSRF']) && $serendipity['referrerXSRF']) || $use_config === false) { + $serendipity['GET']['adminModule'] = ''; + } else { + // Paranoia not enabled. Do not report XSRF. + $string = ''; + } + } return $string; } -/* vim: set sts=4 ts=4 expandtab : */ +function serendipity_checkFormToken() { + global $serendipity; + + $token = ''; + if (!empty($serendipity['POST']['token'])) { + $token = $serendipity['POST']['token']; + } elseif (!empty($serendipity['GET']['token'])) { + $token = $serendipity['GET']['token']; + } + + if (empty($token)) { + echo serendipity_reportXSRF('token', false); + return false; + } + + if ($token != md5(session_id()) && + $token != md5($serendipity['COOKIE']['old_session'])) { + echo serendipity_reportXSRF('token', false); + return false; + } + + return true; +} + +function serendipity_setFormToken($type = 'form') { + global $serendipity; + + if ($type == 'form') { + return ''; + } elseif ($type == 'url') { + return 'serendipity[token]=' . md5(session_id()); + } else { + return md5(session_id()); + } +} + +/* vim: set sts=4 ts=4 expandtab : */ \ No newline at end of file diff --git a/include/functions_entries_admin.inc.php b/include/functions_entries_admin.inc.php index c7e8ee8..2695d18 100644 --- a/include/functions_entries_admin.inc.php +++ b/include/functions_entries_admin.inc.php @@ -99,7 +99,7 @@ function serendipity_printEntryForm($targetURL, $hiddens = array(), $entry = arr $hidden .= ' ' . $n; $hidden .= ' ' . $n; $hidden .= ' '; - + $hidden .= ' ' . serendipity_setFormToken(); if (!empty($errMsg)) { ?>
diff --git a/include/functions_images.inc.php b/include/functions_images.inc.php index 35bcd5e..f2192ea 100644 --- a/include/functions_images.inc.php +++ b/include/functions_images.inc.php @@ -940,6 +940,7 @@ function serendipity_displayImageList($page = 0, $lineBreak = NULL, $manage = fa ?> $g_val) { if ( !is_array($g_val) && $g_key != 'page' ) { echo ''; diff --git a/include/functions_installer.inc.php b/include/functions_installer.inc.php index 49be3df..e48054b 100644 --- a/include/functions_installer.inc.php +++ b/include/functions_installer.inc.php @@ -387,6 +387,7 @@ function showConfigAll(count) {
+
1 && $allowToggle) { ?> @@ -504,7 +505,7 @@ function serendipity_parse_sql_tables($filename) { $line = trim(fgets($fp, 4096)); if ($in_table) { $def .= $line; - if (preg_match('/^\)\s*(type\=\S+)?\s*\;$/i', $line)) { + if (preg_match('/^\)\s*(type\=\S+|\{UTF_8\})?\s*\;$/i', $line)) { $in_table = 0; array_push($queries, $def); } @@ -581,9 +582,9 @@ function serendipity_checkInstallation() { $serendipity['dbType'] = $_POST['dbType']; // Probe database // (do it after the dir stuff, as we need to be able to create the sqlite database) - @include_once($_POST['serendipityPath'] . 'include/db/db.inc.php'); + include_once($_POST['serendipityPath'] . 'include/db/db.inc.php'); // For shared installations, probe the file on include path - @include_once(S9Y_INCLUDE_PATH . 'include/db/db.inc.php'); + include_once(S9Y_INCLUDE_PATH . 'include/db/db.inc.php'); if (S9Y_DB_INCLUDED) { serendipity_db_probe($_POST, $errs); diff --git a/plugins/serendipity_plugin_remoterss/serendipity_plugin_remoterss.php b/plugins/serendipity_plugin_remoterss/serendipity_plugin_remoterss.php index b0b34e6..b3813ed 100644 --- a/plugins/serendipity_plugin_remoterss/serendipity_plugin_remoterss.php +++ b/plugins/serendipity_plugin_remoterss/serendipity_plugin_remoterss.php @@ -254,6 +254,7 @@ class s9y_remoterss_OPML { class serendipity_plugin_remoterss extends serendipity_plugin { var $title = PLUGIN_REMOTERSS_TITLE; + var $encoding = null; function introspect(&$propbag) { $this->title = $this->get_config('sidebartitle', $this->title); @@ -408,6 +409,7 @@ class serendipity_plugin_remoterss extends serendipity_plugin { require_once S9Y_PEAR_PATH . 'Onyx/RSS.php'; $c = &new Onyx_RSS(); $c->parse($rssuri); + $this->encoding = $c->rss['encoding']; $i = 0; $content = ''; @@ -526,8 +528,23 @@ class serendipity_plugin_remoterss extends serendipity_plugin { } } - function decode($string) { - switch($this->get_config('charset', 'native')) { + function &decode($string) { + static $phpCharset = null; + + if ($phpCharset === null) { + $phpCharset = version_compare(phpversion(), '4.3.11', '>='); + } + + if ($phpCharset == 1) { + // Luckily PHP5 supports + // xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, LANG_CHARSET); + // which means we need no transcoding here. + return $string; + } + + $target = $this->get_config('charset', 'native'); + + switch($target) { case 'native': return $string; diff --git a/serendipity_config.inc.php b/serendipity_config.inc.php index 0e48889..04a6bb4 100644 --- a/serendipity_config.inc.php +++ b/serendipity_config.inc.php @@ -111,6 +111,11 @@ $serendipity['calendars'] = array('gregorian' => 'Gregorian', */ include($serendipity['serendipityPath'] . 'include/lang.inc.php'); +$serendipity['charsets'] = array( + 'UTF-8/' => 'UTF-8', + '' => CHARSET_NATIVE +); + @define('PATH_SMARTY_COMPILE', 'templates_c'); // will be placed inside the template directory @define('USERLEVEL_ADMIN', 255); @define('USERLEVEL_CHIEF', 1); @@ -235,11 +240,6 @@ if (defined('DATE_LOCALES')) { } } -$serendipity['charsets'] = array( - 'UTF-8/' => 'UTF-8', - '' => CHARSET_NATIVE -); - /* * Fallback charset, if none is defined in the language files */ -- 2.39.5