在我们日常的开发中,经常需要用到判断图片是否存在,存在则显示,不存在则显示默认图片,那么我们用到的判断有哪些呢?今天我们就来看下几个常用的方法:
1、getimagesize()函数
getimagesize
函数并不属于 GD 扩展的部分,标准安装的 PHP 都可以使用这个函数。可以先看看这个函数的文档描述:http://php.net/manual/zh/function.getimagesize.php
如果指定的文件如果不是有效的图像,会返回 false,返回数据中也有表示文档类型的字段。如果不用来获取文件的大小而是使用它来判断上传文件是否是图片文件,看起来似乎是个很不错的方案,当然这需要屏蔽掉可能产生的警告,比如代码这样写:
<?php $filesize = @getimagesize('/path/to/image.png'); if ($filesize) { do_upload(); } # 另外需要注意的是,你不可以像下面这样写: # if ($filesize[2] == 0) # 因为 $filesize[2] 可能是 1 到 16 之间的整数,但却绝对不对是0。
但是如果你仅仅是做了这样的验证,那么很不幸,你成功的在代码里种下了一个 webshell 的隐患。
要分析这个问题,我们先来看一下这个函数的原型:
static void php_getimagesize_from_stream(php_stream *stream, zval **info, INTERNAL_FUNCTION_PARAMETERS)
{
...
itype = php_getimagetype(stream, NULL TSRMLS_CC);
switch( itype) {
...
}
...
}
static void php_getimagesize_from_any(INTERNAL_FUNCTION_PARAMETERS, int mode) {
...
php_getimagesize_from_stream(stream, info, INTERNAL_FUNCTION_PARAM_PASSTHRU);
php_stream_close(stream);
}
PHP_FUNCTION(getimagesize)
{
php_getimagesize_from_any(INTERNAL_FUNCTION_PARAM_PASSTHRU, FROM_PATH);
}
限于篇幅上面隐藏了一些细节,现在从上面的代码中我们知道两件事情就够了:
-
最终处理的函数是
php_getimagesize_from_stream
-
负责判断文件类型的函数是
php_getimagetype
接下来看一下 php_getimagetype
的实现:
PHPAPI int php_getimagetype(php_stream * stream, char *filetype TSRMLS_DC)
{
...
if (!memcmp(filetype, php_sig_gif, 3)) {
return IMAGE_FILETYPE_GIF;
} else if (!memcmp(filetype, php_sig_jpg, 3)) {
return IMAGE_FILETYPE_JPEG;
} else if (!memcmp(filetype, php_sig_png, 3)) {
...
}
}
去掉了一些细节,php_sig_gif
php_sig_png
等是在文件头部定义的:
PHPAPI const char php_sig_gif[3] = {'G', 'I', 'F'};
...
PHPAPI const char php_sig_png[8] = {(char) 0x89, (char) 0x50, (char) 0x4e, (char) 0x47,
(char) 0x0d, (char) 0x0a, (char) 0x1a, (char) 0x0a};
可以看出来 image type 是根据文件流的前几个字节(文件头)来判断的。那么既然如此,我们可不可以构造一个特殊的 PHP 文件来绕过这个判断呢?不如来尝试一下。
找一个十六进制编辑器来写一个的 PHP 语句,比如:
<?php phpinfo(); ?>
这几个字符的十六进制编码(UTF-8)是这样的:
3C3F 7068 7020 7068 7069 6E66 6F28 293B 203F 3E
我们构造一下,把 PNG 文件的头字节加在前面变成这样的:
8950 4E47 0D0A 1A0A 3C3F 7068 7020 7068 7069 6E66 6F28 293B 203F 3E
最后保存成 .php
后缀的文件(注意上面是文件的十六进制值),比如 test.php。执行一下 php test.php
你会发现完全可以执行成功。那么能用 getimagesize
读取它的文件信息吗?新建一个文件写入代码试一下:
<?php
print_r(getimagesize('test.php'));
执行结果:
Array
(
[0] => 1885957734
[1] => 1864902971
[2] => 3
[3] => width="1885957734" height="1864902971"
[bits] => 32
[mime] => image/png
)
成功读取出来,并且文件也被正常识别为 PNG 文件,虽然宽和高的值都大的有点离谱。
现在你应该明白为什么上文说这里留下了一个 webshell 的隐患的吧。如果这里只有这样的上传判断,而且上传之后的文件是可以访问的,就可以通过这个入口注入任意代码执行了。
那么为什么上面的文件可以 PHP 是可以正常执行的呢?用 token_get_all 函数来看一下这个文件:
<?php
print_r(token_get_all(file_get_contents('test.php')));
如果显示正常的话你能看到输出数组的第一个元素的解析器代号是 312,通过 token_name 获取到的名称会是 T_INLINE_HTML,也就是说文件头部的信息被当成正常的内嵌的 HTML 代码被忽略掉了。
至于为什么会有一个大的离谱的宽和高,看一下 php_handle_png
函数的实现就能知道,这些信息也是通过读取特定的文件头的位来获取的。
所以,对于正常的图片文件,getimagesize 完全可以胜任,但是对于一些有心构造的文件结构却不行。
在处理用户上传的文件时,先简单粗暴的判断文件扩展名并对文件名做一下处理,保证在服务器上不是 php 文件都不能直接执行也是一种有效的方式。然后可以使用 getimagesize 做一些辅助处理。
2、file_exists()函数
file_exists() 函数检查文件或目录是否存在。
如果指定的文件或目录存在则返回 true,否则返回 false。
eg: file_exists(path);其中的参数path必须是路径,不能是url不然会一直返回false;
注意:
1、文件的任何上级目录,只有写权限时报文件不存在;
2、文件的任何上级目录,只有读权限时也报文件不存在;
3、而当所有上级目录都有执行权限的时候,报文件是存在的,一切都正常。
说明file_exists()在判断文件是否存在的时候是递归判断每个目录是不是有执行权限。
3、file_get_contents()函数
file_get_contents — 将整个文件读入一个字符串
如果失败,file_get_contents() 将返回 FALSE
。
果要打开有特殊字符的 URL (比如说有空格),就需要使用 urlencode() 进行 URL 编码。
但是此函数如果请求比较多,文件比较大,那么可能会超时未响应,导致服务器挂掉
要设置file_get_contents函数的超时时间,可以用resource $context的timeout参数,代码如下:
$opts = array( 'http'=>array( 'method'=>"GET", 'timeout'=>10, ) ); $context = stream_context_create($opts); $html =file_get_contents('http://www.example.com', false, $context); echo $html;
4、curl方法
实现的功能:
1、实现远程获取和采集内容
2、实现PHP 网页版的FTP上传下载
3、实现模拟登陆:去一个邮件系统,curl可以模拟cookies
4、实现接口对接(API),数据传输等:通过一个平台发送短信啊,抓取和传递所传输的信息。
5、实现模拟Cookie等:登陆的状态下才可以操作一些属性。
如何使用CURL功能:
默认情况加PHP是不支持CURL的,需要在php.ini中开启该功能
;extension=php_curl.dll前面的分号去掉
1 整个操作过程中第一步是用cur_init()函数进行初始化
2.用curl_setopt()函数进行设置选项。
3.设置后,进行执行事务 curl_exec($curl);
4 最后关闭curl_close();
兼容get和post方法的curl;
function curl($url, $type = 'get', $post_data = null, $second = 30) { $ch = curl_init(); //设置超时 curl_setopt($ch, CURLOPT_TIMEOUT, $second); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // //设置header curl_setopt($ch, CURLOPT_HEADER, false); //要求结果为字符串且输出到屏幕上 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); if ('post' == $type) { curl_setopt($ch, CURLOPT_POST, 1); //开启POST curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); //POST数据 } $output = curl_exec($ch); curl_close($ch); return $output; //返回或者显示结果 }