1 | 文章首发在 https://www.anquanke.com/post/id/153232 |
前言
暑假不学习,和咸鱼并无区别
今天刚好在发掘一下默认配置可能存在问题和一些容易触发漏洞的php函数
这里做一个总结
in_array()函数
相关知识
查阅PHP手册:
(PHP 4, PHP 5, PHP 7)
in_array() — 检查数组中是否存在某个值
大体用法为:1
bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
而官方的解释也很有意思:
大海捞针,在大海(haystack)中搜索针(needle),如果没有设置 strict 则使用宽松的比较。
漏洞问题
我们注意到1
bool $strict = FALSE
宽松比较如果不设置,默认是FALSE,那么这就会引来安全问题
如果设置$strict = True
:则 in_array() 函数还会检查 needle 的类型是否和 haystack 中的相同。
那么不难得知,如果不设置,那么就会产生弱类型的问题
例如:1
2
3
4
5
$whitelist = range(1, 24);
$filename='sky';
var_dump(in_array($filename, $whitelist));
此时运行结果为false
但是如果我们将filename改为1sky
成功利用弱比较,而绕过了这里的检测
典型案例
上面的实例已说明了问题,其实这个问题是存在于上次文件的检查的
在php-security-calendar-2017-Wish List中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class Challenge {
const UPLOAD_DIRECTORY = './solutions/';
private $file;
private $whitelist;
public function __construct($file) {
$this->file = $file;
$this->whitelist = range(1, 24);
}
public function __destruct() {
if (in_array($this->file['name'], $this->whitelist)) {
move_uploaded_file(
$this->file['tmp_name'],
self::UPLOAD_DIRECTORY . $this->file['name']
);
}
}
}
$challenge = new Challenge($_FILES['solution']);
我们不难看出,代码的意图上是想让我们只传数字名称的文件的
而我们却可以用1skyevil.php
这样的名称去bypass
由于没有修改in_array的默认设置,而导致了安全问题
可能这比较鸡肋,但在后续对文件的处理中,前一步产生了非预期,可能会直接影响后一步的操作
漏洞修复
将宽松比较设为true即可
可以看到,搜索的时候,直接要求前两个参数均为array
此时已经不存在弱比较问题
filter_var()函数
相关知识
(PHP 5 >= 5.2.0, PHP 7)
filter_var — 使用特定的过滤器过滤一个变量1
mixed filter_var ( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] )
虽然官方说这是过滤器,但是如果用这个函数进行过滤,并且相信他的结果,是非常愚蠢的
漏洞问题
比较常用的当属FILTER_VALIDATE_URL了吧,但是它存在非常多的过滤bypass
本应该用于check url是否合法的函数,就这样放过了可能导致SSRF的url
类似的bypass还有:1
20://evil.com:80$skysec.top:80/
0://evil.com:80;skysec.top:80/
详细SSRF漏洞触发可参考这篇文章:
http://skysec.top/2018/03/15/Some%20trick%20in%20ssrf%20and%20unserialize()/
除此之外,还能触发xss1
javascript://comment%0Aalert(1)
典型案例
1 | // composer require "twig/twig" |
这里不难看出是有模板渲染的,而模板渲染则有可能触发xss
那么寻找可控点,不难发现1
2
3
4
5
6public function render() {
echo $this->twig->render(
'index.html',
['link' => $this->getNexSlideUrl()]
);
}
这里的Link是使用了getNexSlideUrl()
的结果
我们跟进这个函数1
2
3
4public function getNexSlideUrl() {
$nextSlide = $_GET['nextSlide'];
return filter_var($nextSlide, FILTER_VALIDATE_URL);
}
这里的nextSlide
使用就充分相信了filter_var()的过滤结果
所以导致了XSS:1
?nextSlide=javascript://comment%250aalert(1)
漏洞修复
不要轻易的相信filter_var(),它只能当做初步验证函数,结果不能当做是否进入if的后续程序的条件
class_exists()函数
相关知识
(PHP 4, PHP 5, PHP 7)
class_exists — 检查类是否已定义1
bool class_exists ( string $class_name [, bool $autoload = true ] )
检查指定的类是否已定义。
漏洞问题
上述操作表面上看起来似乎没有什么问题,和函数名一样,检查指定的类是否已定义
但是关键点就在于选项上,可以选择调用或不调用__autoload
更值得思考的是,该函数默认调用了__autoload
什么是__autoload
?
PHP手册是这样描述的:
在编写面向对象(OOP) 程序时,很多开发者为每个类新建一个 PHP 文件。 这会带来一个烦恼:每个脚本的开头,都需要包含(include)一个长长的列表(每个类都有个文件)。
在 PHP 5 中,已经不再需要这样了。 spl_autoload_register() 函数可以注册任意数量的自动加载器,当使用尚未被定义的类(class)和接口(interface)时自动去加载。通过注册自动加载器,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。
那么自动调用__autoload
会产生什么问题呢?
我们从下面的案例来看
典型案例
1 | function __autoload($className) { |
案例同样来自php-security-calendar-2017
乍一看,这样的代码并不存在什么高危的问题,但实际上因为class_exists()
的check自动调用了__autoload()
所以我们可以调用php的内置类实现攻击,例如SimpleXMLElement
正常来说,应该是可以这样触发render():1
http://localhost/xxe.php?c=HomeController&d[t]=sky&d[v][new]=skrskr
可以得到回显1
controller rendering new response
但此时我们可以使用SimpleXMLElement
或是simplexml_load_string
对象触发盲打xxe,进行任意文件读取
构造:1
simplexml_load_file($filename,'SimpleXMLElement')
即1
c=simplexml_load_file&d[t]=filename&d[v]=SimpleXMLElement
即可
而这里的$filename使用最常见的盲打XXE的payload即可
这就不再赘述,详细可参看1
https://blog.csdn.net/u011721501/article/details/43775691
漏洞修复
对于特点情况,可关闭自动调用1
bool $autoload = false
htmlentities()函数
相关知识
(PHP 4, PHP 5, PHP 7)
htmlentities — 将字符转换为 HTML 转义字符1
string htmlentities ( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = ini_get("default_charset") [, bool $double_encode = true ]]] )
本函数各方面都和 htmlspecialchars() 一样, 除了 htmlentities() 会转换所有具有 HTML 实体的字符。
如果要解码(反向操作),可以使用 html_entity_decode()。
漏洞问题
从上述知识来看,该函数应该是用来预防XSS,进行转义的了
但是不幸的是
该函数默认使用的是ENT_COMPAT
即不会转义单引号,那么就可能产生非常严重的问题,例如如下案例
典型案例
1 | $sanitized = []; |
由于不会转义单引号
我们可以随意闭合1
<a href='/images/size.php?htmlentities($query)'>link</a>
此时我们替换htmlentities($query)
为1
' onclick=alert(1) //
这样原语句就变成了1
<a href='/images/size.php?' onclick=alert(1) //'>link</a>
这样就成功的引起了xss
故此最终的payload为1
/?a'onclick%3dalert(1)%2f%2f=c
漏洞修复
必要的时候加上ENT_QUOTES
选项
openssl_verify()函数
相关知识
(PHP 4 >= 4.0.4, PHP 5, PHP 7)
openssl_verify — 验证签名1
int openssl_verify ( string $data , string $signature , mixed $pub_key_id [, mixed $signature_alg = OPENSSL_ALGO_SHA1 ] )
openssl_verify() 使用与pub_key_id关联的公钥验证指定数据data的签名signature是否正确。这必须是与用于签名的私钥相对应的公钥。
漏洞问题
这个函数看起来是用于验证签名正确性的,怎么会产生漏洞呢?
我们注意到它的返回值情况
其中,内部发送错误会返回-1
我们知道if判断中,-1和1同样都可以被当做true
那么假设存在这样的情况if(openssl_verify())
那么它出现错误的时候,则同样可以经过check进入后续程序
如何触发错误呢?
实际上只要使用另一个与当前公钥不匹配的算法生成的签名,即可触发错误
典型案例
1 | class JWT { |
此时我们只需要使用一个不同于当前算法的公钥算法,生成一个有效签名,然后传入参数
即可导致openssl_verify()发生内部错误,返回-1,顺利通过验证,达成签名无效依然可以通过的目的
漏洞修复
if判断中使用1
if(openssl_verify()===1)