tiki-admin_security.php 15.2 KB
Newer Older
1
<?php
2
// (c) Copyright 2002-2011 by authors of the Tiki Wiki CMS Groupware Project
3
// 
4 5
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
changi67's avatar
changi67 committed
6
// $Id$
7

8 9
require_once ('tiki-setup.php');
// do we need it?
changi67's avatar
changi67 committed
10
require_once ('lib/admin/adminlib.php');
11 12
$access->check_permission('tiki_p_admin');

changi67's avatar
changi67 committed
13 14
// get all dangerous php settings and check them
$phpsettings = array();
15
// register globals
changi67's avatar
changi67 committed
16
$s = ini_get('register_globals');
17
if ($s) {
18 19 20 21 22
	$phpsettings['register_globals'] = array(
		'risk' => tra('unsafe') ,
		'setting' => $s,
		'message' => tra('register_globals should be off by default. See the php manual for details.')
	);
23
} else {
24 25 26 27
	$phpsettings['register_globals'] = array(
		'risk' => tra('safe') ,
		'setting' => $s
	);
28
}
changi67's avatar
changi67 committed
29
$fcts = array(
30 31 32 33 34 35 36 37 38
	'exec',
	'passthru',
	'shell_exec',
	'system',
	'proc_open',
	'curl_exec',
	'curl_multi_exec',
	'parse_ini_file',
	'show_source'
changi67's avatar
changi67 committed
39 40
);
foreach($fcts as $fct) {
41 42
	if (function_exists($fct)) {
		$phpfunctions[$fct] = array(
43
			'setting' => tr('Enabled') ,
44 45 46 47
			'risk' => tra('risky')
		);
	} else {
		$phpfunctions[$fct] = array(
48
			'setting' => tr('Disabled') ,
49 50 51
			'risk' => tra('safe')
		);
	}
52 53
}
$smarty->assign_by_ref('phpfunctions', $phpfunctions);
54
// trans_sid
changi67's avatar
changi67 committed
55
$s = ini_get('session.use_trans_sid');
56
if ($s) {
57 58 59 60 61
	$phpsettings['session.use_trans_sid'] = array(
		'risk' => tra('unsafe') ,
		'setting' => $s,
		'message' => tra('session.use_trans_sid should be off by default. See the php manual for details.')
	);
62
} else {
63 64 65 66
	$phpsettings['session.use_trans_sid'] = array(
		'risk' => tra('safe') ,
		'setting' => $s
	);
67 68
}
// check file upload dir and compare it to tiki root dir
changi67's avatar
changi67 committed
69 70
$s = ini_get('upload_tmp_dir');
$sn = substr($_SERVER['SCRIPT_NAME'], 0, -23);
changi67's avatar
changi67 committed
71
if ( $s != "" && strpos($sn, $s) !== FALSE) {
72 73 74
	$phpsettings['upload_tmp_dir'] = array(
		'risk' => tra('unsafe') ,
		'setting' => $s,
changi67's avatar
changi67 committed
75
		'message' => tra('upload_tmp_dir is probably within your Tiki directory. There is a risk that someone can upload any file to this directory and access them via web browser')
76
	);
77
} else {
78 79 80 81 82
	$phpsettings['upload_tmp_dir'] = array(
		'risk' => tra('unknown') ,
		'setting' => $s,
		'message' => tra('cannot check if the upload_tmp_dir is accessible via web browser. To be sure you should check your webserver config.')
	);
83
}
changi67's avatar
changi67 committed
84 85
$s = ini_get('xbithack');
if ($s == 1) {
86 87 88 89 90
	$phpsettings['xbithack'] = array(
		'risk' => tra('unsafe') ,
		'setting' => $s,
		'message' => tra('setting the xbithack option is unsafe. Depending on the file handling of your webserver and your tiki settings, it may be possible that a attacker can upload scripts to file gallery and execute them')
	);
91
} else {
92 93 94 95
	$phpsettings['xbithack'] = array(
		'risk' => tra('safe') ,
		'setting' => $s
	);
96
}
changi67's avatar
changi67 committed
97 98
$s = ini_get('allow_url_fopen');
if ($s == 1) {
99 100 101 102 103
	$phpsettings['allow_url_fopen'] = array(
		'risk' => tra('risky') ,
		'setting' => $s,
		'message' => tra('allow_url_fopen may potentially be used to upload remote data or scripts. If you dont use the blog feature, you can switch it off.')
	);
104
} else {
105 106 107 108
	$phpsettings['allow_url_fopen'] = array(
		'risk' => tra('safe') ,
		'setting' => $s
	);
109
}
110
ksort($phpsettings);
changi67's avatar
changi67 committed
111
$smarty->assign_by_ref('phpsettings', $phpsettings);
112
// tikiwiki preferences check
changi67's avatar
changi67 committed
113 114 115
// do we need to get the preferences or are they already loaded?
$tikisettings = array();
if ($prefs['feature_file_galleries'] == 'y' && !empty($prefs['fgal_use_dir']) && substr($prefs['fgal_use_dir'], 0, 1) != '/') { // todo: check if absolute path is in tiki root
116 117 118 119 120
	$tikisettings['fgal_use_dir'] = array(
		'risk' => tra('unsafe') ,
		'setting' => $prefs['fgal_use_dir'],
		'message' => tra('The Path to store files in the filegallery should be outside the tiki root directory')
	);
121
}
changi67's avatar
changi67 committed
122
if ($prefs['feature_galleries'] == 'y' && !empty($prefs['gal_use_dir']) && substr($prefs['gal_use_dir'], 0, 1) != '/') {
123 124 125 126 127
	$tikisettings['gal_use_dir'] = array(
		'risk' => tra('unsafe') ,
		'setting' => $prefs['gal_use_dir'],
		'message' => tra('The Path to store files in the imagegallery should be outside the tiki root directory')
	);
128
}
changi67's avatar
changi67 committed
129
if ($prefs['feature_edit_templates'] == 'y') {
130 131
	$tikisettings['edit_templates'] = array(
		'risk' => tra('unsafe') ,
132
		'setting' => tra('Enabled') ,
133 134
		'message' => tra('The feature "Edit Templates" is switched on. Do not allow anyone you cannot trust to use this feature. It can easily be used to inject php code.')
	);
135
}
changi67's avatar
changi67 committed
136
if ($prefs['wikiplugin_snarf'] == 'y') {
137 138
	$tikisettings['wikiplugin_snarf'] = array(
		'risk' => tra('unsafe') ,
139
		'setting' => tra('Enabled') ,
140 141
		'message' => tra('The "Snarf Wikiplugin" is activated. It can be used by wiki editors to include pages from the local network and via regex replacement create any html.')
	);
142
}
changi67's avatar
changi67 committed
143
if ($prefs['wikiplugin_regex'] == 'y') {
144 145
	$tikisettings['wikiplugin_regex'] = array(
		'risk' => tra('unsafe') ,
146
		'setting' => tra('Enabled') ,
147 148
		'message' => tra('The "Regex Wikiplugin" is activated. It can be used by wiki editors to create any html via regex replacement.')
	);
149
}
changi67's avatar
changi67 committed
150
if ($prefs['wikiplugin_lsdir'] == 'y') {
151 152
	$tikisettings['wikiplugin_lsdir'] = array(
		'risk' => tra('unsafe') ,
153
		'setting' => tra('Enabled') ,
154 155
		'message' => tra('The "Lsdir Wikiplugin" is activated. It can be used by wiki editors to view the contents of any directory.')
	);
156
}
changi67's avatar
changi67 committed
157
if ($prefs['wikiplugin_bloglist'] == 'y') {
158 159
	$tikisettings['wikiplugin_bloglist'] = array(
		'risk' => tra('unsafe') ,
160
		'setting' => tra('Enabled') ,
161 162
		'message' => tra('The "Bloglist Wikiplugin" is activated. It can be used by wiki editors to disclose private blog posts.')
	);
163
}
changi67's avatar
changi67 committed
164
if ($prefs['wikiplugin_iframe'] == 'y') {
165 166
	$tikisettings['wikiplugin_iframe'] = array(
		'risk' => tra('unsafe') ,
167
		'setting' => tra('Enabled') ,
168 169
		'message' => tra('The "iframe Wikiplugin" is activated. It can be used by wiki editors for cross site scripting attacks.')
	);
170
}
changi67's avatar
changi67 committed
171
if ($prefs['wikiplugin_js'] == 'y') {
172 173
	$tikisettings['wikiplugin_js'] = array(
		'risk' => tra('unsafe') ,
174
		'setting' => tra('Enabled') ,
175 176
		'message' => tra('The "js Wikiplugin" is activated. It can be used by wiki editors to use Javascript, which can be used to do all kind of nasty things like cross site scripting attacks, etc.')
	);
177
}
changi67's avatar
changi67 committed
178
if ($prefs['wikiplugin_jq'] == 'y') {
179 180
	$tikisettings['wikiplugin_jq'] = array(
		'risk' => tra('unsafe') ,
181
		'setting' => tra('Enabled') ,
182 183
		'message' => tra('The "JQ Wikiplugin" is activated. It can be used by wiki editors to use Javascript, which can be used to do all kind of nasty things like cross site scripting attacks, etc.')
	);
184
}
changi67's avatar
changi67 committed
185
if ($prefs['wikiplugin_redirect'] == 'y') {
186 187
	$tikisettings['wikiplugin_redirect'] = array(
		'risk' => tra('unsafe') ,
188
		'setting' => tra('Enabled') ,
189 190
		'message' => tra('The "Redirect Wikiplugin" is activated. It can be used by wiki editors for cross site scripting attacks.')
	);
191
}
changi67's avatar
changi67 committed
192
if ($prefs['wikiplugin_module'] == 'y') {
193 194
	$tikisettings['wikiplugin_module'] = array(
		'risk' => tra('unsafe') ,
195
		'setting' => tra('Enabled') ,
196 197
		'message' => tra('The "Module Wikiplugin" is activated. It can be used by wiki editors to add modules which permit to access information (see module list).')
	);
changi67's avatar
changi67 committed
198 199
}
if ($prefs['wikiplugin_userlist'] == 'y') {
200 201
	$tikisettings['wikiplugin_userlist'] = array(
		'risk' => tra('unsafe') ,
202
		'setting' => tra('Enabled') ,
203 204
		'message' => tra('The "Userlist Wikiplugin" is activated. It can be used by wiki editors to display the list of users.')
	);
205
}
changi67's avatar
changi67 committed
206
if ($prefs['wikiplugin_usercount'] == 'y') {
207 208
	$tikisettings['wikiplugin_usercount'] = array(
		'risk' => tra('unsafe') ,
209
		'setting' => tra('Enabled') ,
210 211
		'message' => tra('The "Usercount Wikiplugin" is activated. It can be used by wiki editors to display a count of the number of users.')
	);
212
}
changi67's avatar
changi67 committed
213
if ($prefs['wikiplugin_sql'] == 'y') {
214 215
	$tikisettings['wikiplugin_sql'] = array(
		'risk' => tra('unsafe') ,
216
		'setting' => tra('Enabled') ,
217 218
		'message' => tra('The "SQL Wikiplugin" is activated. It can be used by wiki editors to execute SQL commands.')
	);
219
}
changi67's avatar
changi67 committed
220
if ($prefs['feature_clear_passwords'] == 'y') {
221 222
	$tikisettings['feature_clear_passwords'] = array(
		'risk' => tra('unsafe') ,
223
		'setting' => tra('Enabled') ,
224 225
		'message' => tra('Store passwords in plain text is activated. You should never set this unless you know what you are doing.')
	);
226
}
227
ksort($tikisettings);
changi67's avatar
changi67 committed
228
$smarty->assign_by_ref('tikisettings', $tikisettings);
229 230
// array for severity in tiki_secdb table. This can go into a extra table if
// the array grows to much.
changi67's avatar
changi67 committed
231
$secdb_severity = array(
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
	//1000 Path disclosure
	1000 => tra('Path disclosure') ,
	1001 => tra('Path disclosure through error message') ,
	//2000 SQL injection
	2000 => tra('SQL injection') ,
	2001 => tra('SQL injection by authenticated user') ,
	2002 => tra('SQL injection by authenticated user with special privileges') ,
	2003 => tra('SQL injection without authentication') ,
	//3000 command injection
	3000 => tra('PHP command injection') ,
	3001 => tra('PHP command injection by authenticated user') ,
	3002 => tra('PHP command injection by authenticated user with special privileges') ,
	3003 => tra('PHP command injection without authentication') ,
	//4000 File upload
	4000 => tra('File upload')
changi67's avatar
changi67 committed
247
);
248
// dir walk & check functions
changi67's avatar
changi67 committed
249
function md5_check_dir($dir, &$result) { // save all suspicious files in $result
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
	global $tikilib;
	global $tiki_versions;
	$c_tiki_versions = count($tiki_versions);
	$query = "select * from `tiki_secdb` where `filename`=?";
	$d = dir($dir);
	while (false !== ($e = $d->read())) {
		$entry = $dir . '/' . $e;
		if (is_dir($entry)) {
			if ($e != '..' && $e != '.' && $entry != './templates_c') { // do not descend and no checking of templates_c since the file based md5 database would grow to big
				md5_check_dir($entry, $result);
			}
		} else if (substr($e, -4, 4) == ".php") {
			if (!is_readable($entry)) {
				$result[$entry] = tra('File is not readable. Unable to check.');
			} else {
				$md5val = md5_file($entry);
				$dbresult = $tikilib->query($query, array(
					$entry
				));
				$is_tikifile = false;
				$is_tikiver = array();
				$valid_tikiver = array();
				$severity = 0;
				// we could avoid the following with a second sql, but i think, this is faster.
				while ($res = $dbresult->FetchRow()) {
					$is_tikifile = true; // we know the filename ... probably modified
					if ($res['md5_value'] == $md5val) {
						$is_tikiver[] = $res['tiki_version']; // found
						$severity = $res['severity'];
					}
					$k = array_search($res['tiki_version'], $tiki_versions);
					if ($k > 0) {
						//record the valid versions in this array
						if ($res['md5_value'] == $md5val) {
							$valid_tikiver[$k] = true;
						} else {
							$valid_tikiver[$k] = false;
						}
					}
				}
				if ($is_tikifile == false) {
changi67's avatar
changi67 committed
291
					$result[$entry] = tra('This is not a Tiki file. Check if this file was uploaded and if it is dangerous.');
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
				} else if ($is_tikifile == true && count($is_tikiver) == 0) {
					$result[$entry] = tra('This is a modified File. Cannot check version. Check if it is dangerous.');
				} else {
					// check if we have a most recent valid version
					$most_recent = false;
					for ($i = $c_tiki_versions; $i > 0; $i--) { // search $valid_tikiver top to down to find the most recent version
						if (isset($valid_tikiver[$i])) {
							if ($valid_tikiver[$i] == false) {
								//$most_recent stays false. we break
								break;
							} else {
								$most_recent = true; // in this case we have found the most recent version. good
								break;
							}
						}
					}
					// use result of most_recent to decide
					if ($most_recent == false) {
changi67's avatar
changi67 committed
310
						$result[$entry] = tra('This file is from another Tiki version: ') . implode(' ' . tra('or') . ' ', $is_tikiver);
311 312 313 314 315 316
					}
				}
			}
		}
	}
	$d->close();
changi67's avatar
changi67 committed
317
}
318 319
// if check installation is pressed, walk through all files and compute md5 sums
if (isset($_REQUEST['check_files'])) {
320 321 322 323 324 325 326 327
	global $tiki_versions;
	require_once ('lib/setup/twversion.class.php');
	$version = new TWVersion();
	$tiki_versions = $version->tikiVersions();
	$result = array();
	md5_check_dir(".", $result);
	$smarty->assign('filecheck', true);
	$smarty->assign_by_ref('tikifiles', $result);
328
}
changi67's avatar
changi67 committed
329 330 331 332 333 334 335 336 337 338 339 340
define('S_ISUID', '2048');
define('S_ISGID', '1024');
define('S_ISVTX', '512');
define('S_IRUSR', '256');
define('S_IWUSR', '128');
define('S_IXUSR', '64');
define('S_IRGRP', '32');
define('S_IWGRP', '16');
define('S_IXGRP', '8');
define('S_IROTH', '4');
define('S_IWOTH', '2');
define('S_IXOTH', '1');
341
// Function to check Filesystem permissions
changi67's avatar
changi67 committed
342
function check_dir_perms($dir, &$result) {
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
	static $depth = 0;
	$depth++;
	$d = dir($dir);
	while (false !== ($e = $d->read())) {
		$entry = $dir . '/' . $e;
		if ($e != '..' && ($e != '.' || $depth == 1)) {
			$result[$entry]['w'] = is_writable($entry);
			$result[$entry]['r'] = is_readable($entry);
			$result[$entry]['t'] = filetype($entry);
			$s = stat($entry);
			if (function_exists('posix_getpwuid')) {
				$t = posix_getpwuid($s['uid']);
				$result[$entry]['u'] = $t['name'];
				$t = posix_getgrgid($s['gid']);
				$result[$entry]['g'] = $t['name'];
			} else {
				$result[$entry]['u'] = $s['uid'];
				$result[$entry]['g'] = $s['gid'];
			}
			$m = (int)$s['mode'];
			if ($m >= 32768) $m-= 32768; // clear file type indicators
			if ($m >= 16384) $m-= 16384;
			if ($m >= 8192) $m-= 8192;
			if ($m >= 4096) $m-= 4096;
			$result[$entry]['p'] = $m;
			$result[$entry]['suid'] = ($m >= S_ISUID && ($m-= S_ISUID) >= 0);
			$result[$entry]['sgid'] = ($m >= S_ISGID && ($m-= S_ISGID) >= 0);
			$result[$entry]['sticky'] = ($m >= S_ISVTX && ($m-= S_ISVTX) >= 0);
			$result[$entry]['ur'] = ($m >= S_IRUSR && ($m-= S_IRUSR) >= 0);
			$result[$entry]['uw'] = ($m >= S_IWUSR && ($m-= S_IWUSR) >= 0);
			$result[$entry]['ux'] = ($m >= S_IXUSR && ($m-= S_IXUSR) >= 0);
			$result[$entry]['gr'] = ($m >= S_IRGRP && ($m-= S_IRGRP) >= 0);
			$result[$entry]['gw'] = ($m >= S_IWGRP && ($m-= S_IWGRP) >= 0);
			$result[$entry]['gx'] = ($m >= S_IXGRP && ($m-= S_IXGRP) >= 0);
			$result[$entry]['or'] = ($m >= S_IROTH && ($m-= S_IROTH) >= 0);
			$result[$entry]['ow'] = ($m >= S_IWOTH && ($m-= S_IWOTH) >= 0);
			$result[$entry]['ox'] = ($m >= S_IXOTH && ($m-= S_IXOTH) >= 0);
			if ($result[$entry]['t'] == 'dir' && $e != '.') {
				check_dir_perms($entry, $result);
			}
		}
	}
	$depth--;
386 387
}
if (isset($_REQUEST['check_file_permissions'])) {
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
	$fileperms = array();
	check_dir_perms('.', $fileperms);
	// walk throug array to find problematic entries
	$worldwritable = array();
	$suid = array();
	$executable = array();
	$strangeinode = array();
	$apachewritable = array();
	foreach($fileperms as $fname => $fperms) {
		if ($fperms['suid']) {
			$suid[$fname] = & $fileperms[$fname];
		}
		if ($fperms['ow']) {
			$worldwritable[$fname] = & $fileperms[$fname];
		}
		if ($fperms['t'] != 'dir' && ($fperms['ux'] || $fperms['gx'] || $fperms['ox'])) {
			$executable[$fname] = & $fileperms[$fname];
		}
		if ($fperms['t'] != 'dir' && $fperms['t'] != 'file' && $fperms['t'] != 'link') {
			$strangeinode[$fname] = & $fileperms[$fname];
		}
		if ($fperms['w']) {
			$apachewritable[$fname] = & $fileperms[$fname];
		}
	}
	$smarty->assign_by_ref('worldwritable', $worldwritable);
	$smarty->assign_by_ref('suid', $suid);
	$smarty->assign_by_ref('executable', $executable);
	$smarty->assign_by_ref('strangeinode', $strangeinode);
	$smarty->assign_by_ref('apachewritable', $apachewritable);
	$smarty->assign('permcheck', TRUE);
419
}
420 421
// disallow robots to index page:
$smarty->assign('metatag_robots', 'NOINDEX, NOFOLLOW');
422 423
$smarty->assign('mid', 'tiki-admin_security.tpl');
$smarty->display("tiki.tpl");