前言

twitter老哥还是nb啊

分析

首先了解下open_basedir的判断方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
PHPAPI int php_check_open_basedir_ex(const char *path, int warn)
{
	/* Only check when open_basedir is available */
	if (PG(open_basedir) && *PG(open_basedir)) {
		char *pathbuf;
		char *ptr;
		char *end;

		/* Check if the path is too long so we can give a more useful error
		* message. */
		if (strlen(path) > (MAXPATHLEN - 1)) {
			php_error_docref(NULL, E_WARNING, "File name is longer than the maximum allowed path length on this platform (%d): %s", MAXPATHLEN, path);
			errno = EINVAL;
			return -1;
		}

		pathbuf = estrdup(PG(open_basedir));

		ptr = pathbuf;

		while (ptr && *ptr) {
			end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
			if (end != NULL) {
				*end = '\0';
				end++;
			}

			if (php_check_specific_open_basedir(ptr, path) == 0) {
				efree(pathbuf);
				return 0;
			}

			ptr = end;
		}
		if (warn) {
			php_error_docref(NULL, E_WARNING, "open_basedir restriction in effect. File(%s) is not within the allowed path(s): (%s)", path, PG(open_basedir));
		}
		efree(pathbuf);
		errno = EPERM; /* we deny permission to open it */
		return -1;
	}

	/* Nothing to check... */
	return 0;
}

获取open_basedir的设定值,与需要判断的路径一起传进php_check_specific_open_basedir 函数有点长,就讲个大概流程php_check_specific_open_basedir() basedir的值复制进local_open_basedir 将需要判断的path传入expand_filepath,如果path是绝对路径,则直接赋值给resolved_name,如果是相对路径,则与当前工作目录拼接处理后,赋值给resolved_name 将当前的open_basedir值传入expand_filepath函数,处理方法与上面一样 (expand_filepath的处理过程就不详细分析了,可以自行查看expand_filepath_with_mode()) 最后将resolved_basedirresolved_name进行比较,如果前resolved_name_len一致,则通过open_basedir的检查 会到twitter老哥的poc,首先是使用chdir切到当前目录的子目录,假设当前open_basedir=/var/www/html,使用chdir('img'),因为是子目录,所以通过了open_basedir的检查,所以当前工作目录就是/var/www/html/img。接着就是ini_set("open_basedir", ".."),先看下ini_set的流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
PHP_FUNCTION(ini_set)
{
	zend_string *varname;
	zend_string *new_value;
	char *old_value;

	ZEND_PARSE_PARAMETERS_START(2, 2)
		Z_PARAM_STR(varname)
		Z_PARAM_STR(new_value)
	ZEND_PARSE_PARAMETERS_END();

	old_value = zend_ini_string(ZSTR_VAL(varname), (int)ZSTR_LEN(varname), 0);

	/* copy to return here, because alter might free it! */
	if (old_value) {
		size_t len = strlen(old_value);

		if (len == 0) {
			RETVAL_EMPTY_STRING();
		} else if (len == 1) {
			RETVAL_INTERNED_STR(ZSTR_CHAR((zend_uchar)old_value[0]));
		} else {
			RETVAL_STRINGL(old_value, len);
		}
	} else {
		RETVAL_FALSE;
	}

#define _CHECK_PATH(var, var_len, ini) php_ini_check_path(var, (int)var_len, ini, sizeof(ini))
	/* open basedir check */
	if (PG(open_basedir)) {
		if (_CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "error_log") ||
			_CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "java.class.path") ||
			_CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "java.home") ||
			_CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "mail.log") ||
			_CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "java.library.path") ||
			_CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "vpopmail.directory")) {
			if (php_check_open_basedir(ZSTR_VAL(new_value))) {
				zval_dtor(return_value);
				RETURN_FALSE;
			}
		}
	}

	if (zend_alter_ini_entry_ex(varname, new_value, PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0) == FAILURE) {
		zval_dtor(return_value);
		RETURN_FALSE;
	}
}

因为设置的name是open_basedir,所以那些检查跟他没关系,跟进zend_alter_ini_entry_ex zend_alter_ini_entry_ex() 进入in_modify进行对open_basedir的修改,使用gdb调试发现是进入了ZEND_INI_MH(OnUpdateBaseDir) ZEND_INI_MH(OnUpdateBaseDir) 发现对值进行了check,通过后才会进行更新 所以根据上面check的流程,resolved_name就为当前工作区(/var/www/html/img)+..,最终为/var/www/htmlresolved_basedir就为/var/www/html因为是绝对路径。所以reolved_name==resolved_basedir,通过check,于是open_basedir的值就更新为..

接下来是chdir("..")resolved_name就为当前工作区(/var/www/html/img)+..,最终为/var/www/htmlresolved_basedir就为当前工作区(/var/www/html/img)+..,最终为/var/www/html,通过check,工作区更改为/var/www/html

以此类推,慢慢的最终工作区被更改到根目录,resolved_basedir就为"/" + ".." = "/"了,无论怎样的路径都能通过check,open_basedir失去了作用

结语

如果有错误,请指出,一定改正