Apache 漏洞 PHP 文件 上传

Tagged Under :

标题党一下,其实这个不应该算是 Apache 的漏洞。

简述漏洞情况:

有的网站有文件上传功能,尤其是可以上传 rar 文件的。如果文件名是 abc.php.rar,而且这个文件被放在了 http://www.leakon.com/uploads/abc.php.rar,你在地址栏输入这个地址,Apache 就会调用 PHP 模块去解析 abc.php.rar。

为什么会解析呢?

因为 Apache 按照文件扩展名,寻找 handler,比如,如果扩展名是 php,就交给 PHP 模块,如果是 jpg,就会输出 image/jpg 类型 的 http 头。

Apache 有一些已知的文件类型,在 Apache 目录的 conf/mime.types 文件中可以查看。

但这里没有 rar 的 handler,这时 Apache 会把 php 当作 abc.php.rar 这个文件的扩展名!

这个可能是 Apache 的一个解析规则吧,反正在 2.2.4 版本测试是这样的。

最简单的测试方法,写一个文件,内容是:

<?php
phpinfo();
?>

保存为 abc.php.rar,然后上传到你的服务器,看能不能执行吧。

前面我也说过,可能这不应该算是漏洞,只是一个规则,如果把这个规则去掉,可能相当大的一部分网站会因此受到影响。不可否认的,也是具有讽刺意味的是:我们的程序能够正常运行,有时是因为它处在一个有“漏洞”的环境中……

避免问题的方法有很多,比如在 mime 文件中加上 rar 类型的定义,再给 rar 文件加个 handler,再限制 upload 目录的执行权限等等。

我觉得比较好的方法是在服务端检查文件扩展名,如果应用程序需要上传 rar 等类型的文件,建议把文件在服务端改名,写成没有扩展名的字母和数字的组合,然后把原始文件名保存在数据库里,当需要下载的时候,用 http 头声明文件名。

单点处理,可以避免很多后续的相互依赖的安全工作。

PHP clone 复制对象 优化对象创建速度

Tagged Under : ,

用 clone 结构来复制一个对象。

PHP 的对象赋值,默认是引用传递。

如果需要一个对象的副本,只能用 clone。

从性能上考虑,当在一个循环中需要反复创建一个新对象的时候,可以考虑在外层创建,在循环内层用 clone 来复制对象。这样性能将会得到很大提高。

这里有实地测试的代码和测试结果可以参考:

<<PHP 对象克隆 性能优化示例>>

引用一下结论:

仅仅是这么简单的一个对象,创建与克隆,性能差距就达到5至6倍,时间消耗已经不在一个数量级上了。

PHP MySQL localhost 127.0.0.1

Tagged Under : ,

今天刚发现一个问题,PHP连接MySQL的时候,不同环境的localhost会有不同的结果。

我的服务器装了2套PHP,其中有一个是用源码编译的,另一个是xampp集成包。

编译的php用于生产环境,xampp用来建立测试环境。

把线上的代码放到测试环境下,居然报告无法连接MySQL!

刚开始以为是端口的问题,可是我在命令行下怎么连接都没问题。最后抱着试试看的想法,把localhost改成了127.0.0.1,这回居然成功了。

我不能理解,线上代码运行的好好的,配置文件就是localhost呀,怎么到测试环境就必须改成127.0.0.1才行?

两套环境,主要的区别就是PHP,虽然版本都一样,不过xampp是编译好的,我估计问题出在这里。

然后立刻写了一个测试程序:

$connA = mysql_connect(’127.0.0.1:3306′, ‘leakon’, ‘pass’);
$connB = mysql_connect(’localhost:3306′, ‘leakon’, ‘pass’);
var_dump($connA);
var_dump($connB); 

在命令行下分别用编译和xampp的php执行上述代码,果然发现两项结果不一样。

后来分析了一下,按照这种方式理解:编译PHP的时候需要指定MySQL的安装路径,这个时候localhost就指向对应的MySQL。与编译版的PHP不一样,xampp指向的是随包附带的二进制版MySQL,因此他发现这个MySQL的root密码不对,拒绝连接。

但用127.0.0.1作为主机地址时,PHP就不会按照编译的localhost找MySQL服务器,而是根据端口来找,这回就没问题了。

同时也发现了一个问题,当用localhost:port作为主机地址时,PHP会忽略端口号!

不信你试试上面的代码,那个port写成什么都无所谓,只要是localhost,就会链接特定的MySQL。

不知道为什么,就当经验,记住这个事实吧!

PHP 安全设置 %5c magic_quotes_gpc GBK

Tagged Under : , ,

安全问题。

今天看到一个朋友的blog上,写到PHP的安全设置,应该开启magic_quotes_gpc,避免SQL注入等。

可是,我在用Symfony开发时,文档上说应该关闭php.ini的magic_quotes_gpc,以前我不知道为什么,今天突然有些明白了。

这个设置,只能起到非常有限的安全作用,却有可能带来更严重的问题。

PHP的放置SQL注入,很多网站或教程上,都写着用addslashes或mysql_escape_string来过滤,或者用自动的magic_quote。这些函数的作用,就是查询字符串中的单引号、双引号和斜线”\”,在这些字符前面插入一个斜线”\”。

如果遇到了”頫運鳿黒靄錦鳿黒靄錦”这样的字符,而你的数据库、网页又是GBK编码的话,上面的3种过滤函数,都会给你带来麻烦,因为这些字符的第二个字节的编码,是%5c,恰好是”\”对应的编码。

原本是2个字节的汉字,被这些函数变成了3个字节,最后的2个字节是连续的”\”,而这第3个”\”会把后续的引号转义,因此SQL语句就会出错,导致更新无法执行。

安全建议:

  1. 设置php.ini,默认关闭magic_quotes_gpc
  2. 使用mysql_real_escape_string对字符串变量进行转义,注意,转义前判断magic_quotes_gpc是否已开启
  3. 使用utf-8编码,可以有效避免此类问题

PHP 文件下载 IE 无法打开页面

Tagged Under : , ,

IE 又有一个弱得不行的问题让我发现!

有个项目,要限制文件的下载权限,只有注册用户才可以下载,用户登录后,点击下载链接,弹出保存附件的提示。

我用 PHP 写了一个下载类,支持断点续传的。

今天发现一个问题,在 IE 7 下,点击链接,可以弹出对话框,提示 “打开”、”保存” 和 “取消”,点击打开没问题,点击保存,却马上弹出错误提示 “该页面无法打开”!

这时可以注意到一个细节,弹出保存对话框的时候,正常情况下窗口左边会根据文件类型显示图标,而此时却是一个没有类型的默认图标。

我怀疑是 PHP 在设置 Http Header 的时候有问题,仔细检查了每一项,逐项注释,问题依旧。

可是同样的链接,在 Firefox、Opera 和 Safari 下都没问题,打开或保存都正常。

后来去网上搜了好多文件下载的 header 设置,发现我少了 2 个属性:

Expires 和 Cache-Control,我想起来我以前写过一个 Case,说要加 Cache-Control,否则用IE 打开文件会提示 “文档已损坏”:

http://www.leakon.com/archives/76

这提醒了我,我应该加上 Cache-Control 的。

果然,加上下面这 2 行,问题就完全解决:
header(’Expires: 0′);
header(’Cache-Control: must-revalidate, post-check=0, pre-check=0′);

全部代码请见我的 GoogleCode:

http://leakon.googlecode.com/svn/trunk/leakon/php/iplimit/smart_download.php

这是支持短点续传的哦,很好用。

使用很简单,用文件绝对路径 new 一个对象,然后调用 $obj->download() 就可以啦。

当然,还可以通过参数,配置文件名和文件类型。

大伙儿看看吧,这是我给互联网的贡献~~

虚拟主机 SSH

问题由来:

我买了美国的虚拟主机,机器性能很好,空间巨大(150G) ,唯一的也是最大的问题,就是访问速度慢。

其实服务器本身至少能保证3M的带宽,我用其他服务器,单线程wget美国主机的文件,都可以稳定地保持在300K/s以上。

但用浏览器访问,由于需要发起多次tcp连接,而每次连接只传很小的几k文件就立即断掉,所以很慢。

最让我抓狂的就是,我上传一个软件包,总大小也就4、5M,但文件数量特别多,至少有几百甚至上千,传这么一个文件夹,没有2小时根本完不了。

我就想,如果虚拟主机有命令行,可以执行压缩或解压命令就好了。

传单个文件,再慢,也能保证每秒50K,像这样几M的文件,几分钟就可以搞定。

但如何解压呢?

答案就是:web版的命令行工具。

最简单的,就是system或exec函数,可以像SSH客户端一样,执行我们想要的命令。

注意,有些虚拟主机限制执行system和exec这两个函数,但我做了测试,证明是有其他方法的,一会儿再说这个方法是什么。

请您看到这个方法后,不要大肆宣传,或利用这个方法做一些危害主机安全的操作。如果这个方法也被禁用了,那以后就再也没有类似的方法了。

有了web的ssh,我们该怎么用呢?

1、首先要有清晰的unix文件的路径知识,知道什么是绝对路径,什么是相对路径,如何引用一个路径,等等。因为web版ssh只能方便地在当前一个目录下操作,稍有不慎,就可能造成无法挽回的后果。

2、学会使用ls、df、du、cp、mv、tar、zip等常用命令。使用web版ssh的出发点,就是希望以后在上传或下载文件时,可以预先打包,然后只传一个文件,这样可以大大减少传输时间的浪费。因此,列出目录、复制、移动、压缩和解压,就是必备的命令工具。

3、web版ssh还有一个功能,就是可以执行命令行的MySQL!!!最近我刚试着迁移discuz论坛,俗称论坛搬家,就是把论坛从A服务器迁移(搬家)到B服务器,重要的过程就是dump数据库,再import。而传统的工具,只有phpmyadmin,导出sql文件到还容易,但导入到另一个服务器,尤其是导入到另一个虚拟主机的时候,会受B主机的上传文件大小限制,大文件没法导入。还有,就是我遇到的乱码问题,由于B主机的大小限制只有2M,我的sql有5M,没办法,只能先压缩。import的时候,没有出错,但是导完发现都是乱码。A服务器是utf8,B是gbk。import的时候,本来import时选择了utf8编码的,但貌似对zip压缩的sql文件无效,最终是按gbk编码导入utf8的sql文件,这必然是乱码啊,结果就是论坛变成“蝌蚪文”。逼得我没办法了,只能开发一个web版ssh工具,最后用 mysql –default-character-set=utf8 -uleakon -pleakon leakon < leakon.sql 这个命令行才成功导入。这回,导入过程快多了,瞬间完成,不必再等着phpmyadmin上传本地sql文件。这一切,多亏了我的ssh工具,也就是本为的主角:web_shell。

按说这不叫ssh,只是一个web的命令行转发函数,但为了大家搜索虚拟主机ssh的时候能方便一些,就故意写了好多ssh。

大家可以看看国外的虚拟主机,大部分都支持ssh,而且……

唉,我都不想重复这些了,国外主机的优势,真不是国内idc服务商们可以比的。国内用最烂的服务、最烂的技术、最烂的界面来提供的虚拟主机,价格却是国外的好几倍甚至几十倍。价格我真不想再说了,反正最贵的都比国内最便宜的便宜好多好多,而且服务好得更多。

跑题了,回来说我的web_shell。

这是我简单开发的一个辅助工具,专门解决我上文提到的各种问题,加了一个简单安全验证,文件放在服务上,别人无法使用,只有你自己能用。

源码在我的GoogleCode里可以找到,地址是:

http://leakon.googlecode.com/svn/trunk/leakon/php/web_shell/web_zip.php

现在充其量是alpha 0.0.0.1版,里面还有一大堆debug的注释,本来还想加入一些新功能,但没那么多时间,先解决眼前的问题吧。

使用的时候,需要自己写一个web_inc.php,里面只要定义一个AUTH_KEY就可以了,这是你的密钥,只有知道密钥的人才可以使用这个web_shell。

默认的密钥,我是用一个字符串加当天日期的md5写的,如何快速计算一个字符串的md5呢?我早就写了一个工具,也许大家都没注意过,我就再发一次吧:

http://code.leakon.com/php/tools/

可以做一些简单的编码、解码计算,很好用哦。

源码也在googlecode里,大家自己找吧。

盼望得到您的指点或回复,谢谢!

另,php本身还有一个popen,也可以执行命令,一般的虚拟主机都没禁用,还是那句话,请慎用,要是用烂了,以后也就没得用了。

PHP 函数调用的开销

处理大量数据,每个关键词有5000条数据,一共有50万个关键词。

要对每个关键词的每条数据进行加权处理。

写了一个加权函数,作为一个类的静态方法。

遍历这50万个关键词的数据,结果非常慢。

考虑问题原因,尝试把加权函数的逻辑拆出来,放到大循环中。

写了测试代码,结果性能提升非常明显。

调用类的静态方法,程序性能是 156 次/秒,而拆出逻辑,直接运行,性能是 625 次/秒!

速度是原来的 4  倍多!

addslashes() Versus mysql_real_escape_string()

关于 addslashes() 和 mysql_real_escape_string() 两个函数,已经有过很多争论了。

在 PHP 过滤 SQL 注入时,通常都会使用  addslashes() 函数,但这并不保险,尤其是数据库编码是 GBK 时,类似于 %5c(\) 和 %27(’) 等字符时,不能得到预期的正确过滤。

我在使用  mysql_real_escape_string() ,数据库是 UTF-8 编码时,也是不能正常过滤,以 %5c 结尾的汉字,在做完转义后,效果仍然和 addslashes() 的结果一样。但同样的代码,拿到使用 GBK 编码编译的 MySQL 环境下就没问题。

我还没有搞明白到底问题出在哪里,最近会一直 focus 在这上面。

今天发现一个老外写的 Blog,分析得比较深入,尽管已经是 2 年前发表的了。

http://shiflett.org/blog/2006/jan/addslashes-versus-mysql-real-escape-string

分析得比较透彻,有很多人写了回复,仔细看看每个人的反馈,也能受益匪浅。

PHP 性能优化 二进制转换 pack()

今天搞一个数据存储程序,需要把数据转换成二进制存储。

在转换过程中,发现效率很低,不能满足需要。

经过反复测试和修改,总结了一些经验。

写在我的 Wiki 里了,Wiki 里贴代码比较方便,也顺便在这里推广一下我的Wiki

<<Leakon’s Wiki>>

性能优化的Wiki地址:http://wiki.leakon.com/PerformancePack

编码检查 UTF-8 浏览器编码

编码问题一直是做网站开发的工程师们很头疼的事,如果你希望自己的网站能够被更多不同语言环境的人浏览和使用,那就一定要解决好编码的问题。

我的经验是,从HTML页面的编码,到PHP程序文件的编码,到数据库的设计以及与PHP之间连接的编码,全部使用UTF-8,这样就能保证你的页面不会出现乱码。

不要总是觉得,你的网站是给中国人用的,不给其他国家的人使用。如果你的中国用户希望保存一些日文、韩文等亚洲文字, 你也没有理由拒绝吧?

还是在一开始就解决好编码的问题为好,做网站,免不了这一步,今天不做,以后迟早要还的。

如果你认为我说的毫无道理,那我给大家引用一篇W3C组织的官方说明吧:

http://www.w3.org/International/questions/qa-forms-utf-8.en.php

注意回答部分的第一句:The best way to deal with encoding issues in (X)HTML forms is to serve all your pages in UTF-8.

用UTF-8吧,肯定没错的,你可以避免很多很多问题。

不过,就算页面使用了UTF-8,也还是会遇到一个问题:

浏览器接收编码的转换问题。

就这个问题的说明,我做了一个测试页面,可以在我的Google SVN下载:

http://leakon.googlecode.com/svn/trunk/leakon/php/detect_utf8/

在浏览器里打开test.php页面即可。

这个页面是UTF-8编码的,如果你不是用SVN软件CheckOut而是直接复制的源码,请记得要把test.php这个文件保存成UTF-8格式。

这个页面有一个Form表单,你可以在输入框内输入中文,然后看地址栏里word字段的值,一个汉字对应3个%,因为UTF-8是变长编码的,针对不同的文字,%的数量是1-4个。

如果你熟悉GBK编码的页面,应该注意到每个汉字对应2个%,因为汉字都是双字节编码的。

这个时候,如果你足够细心,应该可以发现一个冲突的问题。

如果,我在浏览器的地址栏的word字段后面直接输入汉字,得到的是什么结果呢?给大家一个提示,这里最好使用Firefox浏览器,如果你在地址栏输入中文,Firefox会按照GBK的编码方式,按双字节编码,也就是一个汉字对应2个%。

可是你的页面默认是接收UTF-8编码的字符的,给你一个GBK编码,你会解析成乱码。

很多网站存在这个问题,包括Google和Yahoo。 他们只对各自的中文网站就解决了这个问题,英文的和日文的都没有处理。下面我们逐一测试一下:

我们给一个测试用例,中文:百度;GBK:%B0%D9%B6%C8;UTF-8:%E7%99%BE%E5%BA%A6。

看到了吧,GBK是4个%,UTF-8是6个%。

  1. http://www.google.cn/search?hl=zh-CN&q=%E7%99%BE%E5%BA%A6&btnG=Google+%E6%90%9C%E7%B4%A2&meta=
  2. http://www.google.cn/search?hl=zh-CN&q=%B0%D9%B6%C8&btnG=Google+%E6%90%9C%E7%B4%A2&meta=
  3. http://yahoo.cn/s?p=%E7%99%BE%E5%BA%A6&v=web
  4. http://yahoo.cn/s?p=%B0%D9%B6%C8&v=web
  5. http://www.google.com/search?hl=en&q=%E7%99%BE%E5%BA%A6&btnG=Google+Search
  6. http://www.google.com/search?hl=en&q=%B0%D9%B6%C8&btnG=Google+Search
  7. http://search.yahoo.com/search?p=%E7%99%BE%E5%BA%A6&fr=yfp-t-501&toggle=1&cop=mss&ei=UTF-8&vc=&fp_ip=CN
  8. http://search.yahoo.com/search?p=%B0%D9%B6%C8&fr=yfp-t-501&toggle=1&cop=mss&ei=UTF-8&vc=&fp_ip=CN
  9. http://www.google.co.jp/search?hl=ja&newwindow=1&q=%E7%99%BE%E5%BA%A6&btnG=%E6%A4%9C%E7%B4%A2&lr=
  10. http://www.google.co.jp/search?hl=ja&newwindow=1&q=%B0%D9%B6%C8&btnG=%E6%A4%9C%E7%B4%A2&lr=
  11. http://search.yahoo.co.jp/search?p=%E7%99%BE%E5%BA%A6&ei=UTF-8&fr=sfp_as&x=wrt
  12. http://search.yahoo.co.jp/search?ei=UTF-8&fr=sfp_as&p=%B0%D9%B6%C8&meta=vc%3D

以上是对Google和Yahoo两大搜索引擎的中文、英文、日文网站进行UTF-8、GBK编码的访问。

可以看到,2家公司只对中文网站做了编码检查,发现不是UTF-8就对关键词进行编码转换,得到了正确的结果,英文和日文都没有处理,给GBK编码时得到的是乱码。

他们就忽略了英文和日文用户搜索中文的需求。

其实,这个问题不是不可以解决的,中文网站都做到了,其他语言的怎么就不行呢?

这个不讨论了,还是说说怎么检查UTF-8编码吧。

其实就是一段正则表达式:

$regex    = '/^('
.
'[\x09\x0A\x0D\x20-\x7E]|'        # ASCII
.
'[\xC2-\xDF][\x80-\xBF]|'        # non-overlong 2-byte
.
'\xE0[\xA0-\xBF][\x80-\xBF]|'        # excluding overlongs
.
'[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|'    # straight 3-byte
.
'\xED[\x80-\x9F][\x80-\xBF]|'        # excluding surrogates
.
'\xF0[\x90-\xBF][\x80-\xBF]{2}|'    # planes 1-3
.
'[\xF1-\xF3][\x80-\xBF]{3}|'        # planes 4-15
.
'\xF4[\x80-\x8F][\x80-\xBF]{2}'    # plane 16
.
')*\z/x';

大家从SVN里可以下载源码。

我封装了一个类,DetectUT8,用于检测和转换编码。

过程是这样的:如果检查到不是UTF-8编码,就进行GBK -> UTF-8的转换。因为在地址栏输入的非ASCII字符都会按照GBK编码。

转换函数会检查系统是否启用了 mbstring 函数库,如果没有,则改用 iconv 转换。

这里要注意一点,不要把UTF-8写成utf8,也不要写成其他格式,iconv 的检查比较严格。

如果内置了 mbstring 函数,还是用 mb_detect_encoding 比较保险,但经测试表明,用上面这段正则,编码检测的成功率和 mb_detect_encoding 没有差别。

还要重申一个问题,编码检测的可靠性不是 100%,但应用这种方法已经极大地改善了用户体验,建议大家都采用这种方式。

Google

Google
LAMP-Linux-redhat LAMP-Apache LAMP-MySQL LAMP-Php Leakon-Wiki Leakon-BBS XueBaoBao Xyoyou