FleaPHP默认上传类的一个隐患
随着各种开发框架的盛行,程序员也不愿意做那么多重复的事情了,开发,速度最重要.现在开发什么东西都讲究一个效率.
目前国内似乎还没有多少人对框架的安全性有多少研究.毕竟如果一个底层的编程框架出了问题,很多程序都将受到威胁.
我下了个国内现在很流行的php框架中一个:FleaPHP 1.0.70 beta.翻了翻他的FLEA/FLEA/Helper/FileUploader.php,这个是此框架默认的文件上传类.它有一个检查上传文件是否合法的函数:
function check($allowExts = null, $maxSize = null)
{
if (!$this->isSuccessed()) { return false; }
//允许上传的扩展名
if ($allowExts) {
if (strpos($allowExts, ',')) {
$exts = explode(',', $allowExts);
} elseif (strpos($allowExts, '/')) {
$exts = explode('/', $allowExts);
} elseif (strpos($allowExts, '|')) {
$exts = explode('|', $allowExts);
} else {
$exts = array($allowExts);
}
$fileExt = strtolower($this->getExt());//获取扩展名
$passed = false;
$exts = array_filter(array_map('trim', $exts), 'trim');
foreach ($exts as $ext) {
if (substr($ext, 0, 1) == '.') {
$ext = substr($ext, 1);
}
if ($fileExt == strtolower($ext)) {
$passed = true;
break;
}
}
if (!$passed) {
return false;
}
}
if ($maxSize && $this->getSize() > $maxSize) {
return false;
}
return true;
}
{
if (!$this->isSuccessed()) { return false; }
//允许上传的扩展名
if ($allowExts) {
if (strpos($allowExts, ',')) {
$exts = explode(',', $allowExts);
} elseif (strpos($allowExts, '/')) {
$exts = explode('/', $allowExts);
} elseif (strpos($allowExts, '|')) {
$exts = explode('|', $allowExts);
} else {
$exts = array($allowExts);
}
$fileExt = strtolower($this->getExt());//获取扩展名
$passed = false;
$exts = array_filter(array_map('trim', $exts), 'trim');
foreach ($exts as $ext) {
if (substr($ext, 0, 1) == '.') {
$ext = substr($ext, 1);
}
if ($fileExt == strtolower($ext)) {
$passed = true;
break;
}
}
if (!$passed) {
return false;
}
}
if ($maxSize && $this->getSize() > $maxSize) {
return false;
}
return true;
}
再看getExt函数:
function getExt()
{
if ($this->isMoved()) {
return pathinfo($this->getNewPath(), PATHINFO_EXTENSION);
} else {
return pathinfo($this->getFilename(), PATHINFO_EXTENSION);
}
}
{
if ($this->isMoved()) {
return pathinfo($this->getNewPath(), PATHINFO_EXTENSION);
} else {
return pathinfo($this->getFilename(), PATHINFO_EXTENSION);
}
}
继续跟踪php的pathinfo函数:
/**//* {{{ proto array pathinfo(string path[, int options])
Returns information about a certain string */
PHP_FUNCTION(pathinfo)
{
zval *tmp;
char *path, *ret = NULL;
int path_len, have_basename;
size_t ret_len;
long opt = PHP_PATHINFO_ALL;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &path, &path_len, &opt) == FAILURE) {
return;
}
have_basename = ((opt & PHP_PATHINFO_BASENAME) == PHP_PATHINFO_BASENAME);
MAKE_STD_ZVAL(tmp);
array_init(tmp);
......
......
if ((opt & PHP_PATHINFO_EXTENSION) == PHP_PATHINFO_EXTENSION) {
char *p;
int idx;
if (!have_basename) {
php_basename(path, path_len, NULL, 0, &ret, &ret_len TSRMLS_CC);
}
p = zend_memrchr(ret, '.', ret_len);
if (p) {
idx = p - ret;
add_assoc_stringl(tmp, "extension", ret + idx + 1, ret_len - idx - 1, 1);
}
}
......
......
zval_ptr_dtor(&tmp);
}
/**//* }}} */
Returns information about a certain string */
PHP_FUNCTION(pathinfo)
{
zval *tmp;
char *path, *ret = NULL;
int path_len, have_basename;
size_t ret_len;
long opt = PHP_PATHINFO_ALL;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &path, &path_len, &opt) == FAILURE) {
return;
}
have_basename = ((opt & PHP_PATHINFO_BASENAME) == PHP_PATHINFO_BASENAME);
MAKE_STD_ZVAL(tmp);
array_init(tmp);
......
......
if ((opt & PHP_PATHINFO_EXTENSION) == PHP_PATHINFO_EXTENSION) {
char *p;
int idx;
if (!have_basename) {
php_basename(path, path_len, NULL, 0, &ret, &ret_len TSRMLS_CC);
}
p = zend_memrchr(ret, '.', ret_len);
if (p) {
idx = p - ret;
add_assoc_stringl(tmp, "extension", ret + idx + 1, ret_len - idx - 1, 1);
}
}
......
......
zval_ptr_dtor(&tmp);
}
/**//* }}} */
到这里明白了,原来都只看文件名最后一个 "." 之后的部分作为文件的扩展名.那么如果根据apache的一个特性,我们可以使用多扩展名的方式上传php文件而绕过验证.(比如允许的扩展名里有rar,pdf等apache不认识但常见的类型,我们就可以上传shell.php.rar并得以执行)
当然这个只是个安全隐患而已.并不是所有用FleaPHP的程序都有这个问题.
就像superhei说的那样,关键在于看开发者如何去使用框架,不能太过依赖于框架提供的函数.而必须自己做些必要的前提验证.就能避免漏洞