9.图像 10.PDF 11.XML
这三章用到的时候再看
12.安全
1.过滤输入
所有非程序生成的数据都可能是恶意的,都需要过滤
过滤时要判断输入数据大小长度是否合适,是否在规定的有效数据集当中
先使用静态条件过滤,再使用数据库过滤
示例:
//颜色值必须在规定的颜色集当中 if (!in_array($color, $color_set)) { return false; } //中文名长度必须在3到10之间 $len = mb_strlen($cn_name); if ($len >= 10 || $len <= 3) { return false; } //英文名只允许字母、空格、单引号、连字符 if (!preg_match(‘/[^A-Za-z \‘\-]/‘), $en_name) { return false; }
2.跨站脚本攻击XSS
XSS是最常见的网页程序安全漏洞,它允许恶意web用户将恶意代码植入到提供给其它用户的页面中,恶意代码可以在其它页面上执行窃取信息等操作。
为了预防XSS,需要从输出中转义上下文:
$html = array( ‘username‘ => htmlentities($_POST[‘username‘], ENT_QUOTES, ‘UTF-8‘), ); echo $html[‘username‘];
应该过滤输入来提供冗余保护,但不能依赖过滤,因为它不是问题的根源(根源在输出,所以输出要转义才行)。
关于html实体:http://www.w3school.com.cn/html/html_entities.asp
有些符号想要显示在页面上,比如<,在html文件中不能写<,会被浏览器当做html标签处理,正确的方法是使用<对应的html实体符号<。
3.SQL注入
(1)示例:
$username = $_POST[‘username‘]; $hash = hash($_POST[‘password‘]); $sql = "SELECT count(*) FROM users WHERE username = ‘{$username}‘ and password = ‘{$hash}‘ ";
这里的问题是没有转义用户名,它的值可以篡改SQL查询的格式,鉴于此漏洞很常见,很多攻击者会使用如下用户名:
chris‘ --
这样就可以不用密码就可以用chris访问账户
篡改后的SQL查询是:
SELECT count(*) FROM users WHERE username = ‘chris‘ --‘ AND password = ‘xxx
两个破折号(--)表示sql注释,这个查询等同于:
SELECT * FROM users WHERE username = ‘chris‘
(2)SQL注入出问题的是plain sql,使用参数绑定param bind sql可以很好解决这个问题
bind param sql防SQL注入原理:数据库服务器会把完整参数当做sql语句的参数来执行sql语句,参数的任何部分都不会被执行,这样就不会出现SQL注入的问题了
所以,所有根据用户输入数据进行sql查询的地方理论上都应该使用param bind sql
(3)防止针对plain sql的SQL注入,可以在拼接参数之前对参数过滤和转义。示例如下:
if (preg_match(...), $username) { return false; } $username = mysql_real_escape_string($username);
实际上,为了安全-不只是防止SQL注入的目的,所有用户输入数据都应该过滤
4.文件名攻击
可以通过传入特殊的文件名进行攻击
(1)使用相对路径访问特殊文件
比如想要访问用户资料文件,文件放在/usr/local/目录下,以用户名作文件名,例如:
include("/usr/local/{$username}");
如果传入的$username值为../../etc/passwd作为用户名,就可能输出/etc/passwd的内容
(2)使用远程文件
默认情况下,php可以用打开本地文件的函数打开远程文件,fopen、include、require等函数可以通过传递URL做文件名打开远程文件,例如:
chdir(‘/usr/local/‘); $fp = fopen($username, ‘r‘);
如果$username的值为http://www.example.com/myfile,打开的就是一个远程文件,而不是本地的
如果允许用户告诉你要include哪个文件,情况会更糟糕,例如:
$file = $_REQUEST[‘theme‘]; include($file);
如果用户传递了http://www.example.com/badcode.inc作为theme字段的值,并且variables_order配置包括GET或POST,php脚本将会加载远程代码执行
有多种方法可以检查文件名限制此类攻击:禁用远程文件访问、用realpath和basename检查真正的文件名是否和传入参数一致、配置open_basedir选项限制超出网站目录的文件系统访问
例子:
$filename = $_POST[‘username‘]; $vetted = basename(realpath($filename)); if ($filename !== $vetted) { die("{$filename} is not a good username."); } include("/usr/local/{$filename}");
5.会话攻击
攻击者嵌入会话标识符的链接:
<a href="http://host/login.php?PHPSESSID=1234">Log In</a>
受害者单击了链接以会话标识符1234继续访问,如果受害人进行了登录,攻击者可以劫持受害者的会话来提升权限
还有一些这种攻击的变体,比如用cookie达到同样的目的
防护很简单,当权限等级改变时,比如用户登录,用session_generate_id()重新生成会话标识符:
if (check_auth($_POST[‘username‘], $_POST[‘password‘])) { $_SESSION[‘auth‘] = TRUE; session_regerate_id(TRUE); }
6.文件上传攻击
(1)不要相信浏览器提供的文件名
浏览器可能会提供诸如/etc/passwd、/home/ramus/.forward的文件名
可以在与用户交互中使用浏览器提供的名字,但是要自己生成唯一的名字用作实际调用的文件
例子:
$browserName = $_FILES[‘image‘][‘name‘]; $tempName = $_FILES[‘image‘][‘tmp_name‘]; echo "Thanks for sending me {$browserName}"; $counter++; $filename = "image_{$counter}"; if (is_uploaded_file[$tempName]) { //使用is_uploaded_file确保是上传的文件 move_upload_file($tempName, "/web/images/{$filename}"); } else { die("There was a problem processing the file."); }
(2)堤防文件系统填充
攻击者可能会上传很大的文件制造拒绝服务攻击
在php.ini中post_max_size选项设置请求最大尺寸,同时需要在相应的Apache/Nginx设置文件尺寸
(3)限制访问特定目录
php.ini中的open_basedir选项可以限制php只能操作该目录和它的子目录
(4)尽量不使用文件,而使用数据库
由于一个机器上php脚本都使用同一用户(www/nobody)运行,所以不同用户的文件、同一机器上不同网站的文件能被其它用户或网站访问到,比如存储在/tmp/sess_id的用户会话文件就可能被其它用户篡改或添加
(5)隐藏PHP代码库
如果网站根目录时/home/httpd/html,所有该目录下的文件都可以通过URL下载到,代码库、配置文件、日志文件和其它数据应该放在该目录外面,比如放进/home/httpd/myapp。同时应该在Web服务器配置,不可以下载.php等文件。
7.特殊函数
eval、exec、system等执行脚本的函数要谨慎使用,因为很容易被攻击者利用,如果用不到的话应该在php.ini禁用掉
13.应用技术
(1)代码库
利用常用的代码构建代码库,把涉及到的函数放到一个php文件里。例如一个辅助创建HTML表单元素的库文件:其中一个函数创建textarea,一个函数创建设置日期时间的弹出表单
(2)模板系统
模板系统分离php代码和页面布局,使设计者专注于页面设计,程序员专注于数据逻辑。模板系统最基本的思想是网页包含可被动态内容替换的特殊标记。
Smarty是一个高效的模板系统(需要研究下)
(3)输出缓冲
默认php使用echo或类似的命令在每个命令执行完后将结果发送到浏览器,可以用php的输出缓冲函数来收集想要发送到浏览器的数据,这样可以获取内容长度、调整内容、批量延迟输出、清空内容、压缩内容等。
ob_start([callback]) //callback是后处理函数,缓冲区刷新(flush)后会向函数传递收集到的输出,并且返回一个将要发送到浏览器的字符串。
ps:输出缓冲用来拦截输出,也包括var_dump等内置函数的输出
(4)错误报告
正常的,当php脚本发生错误时,错误信息会被插入到脚本的输出中,如果错误时致命的,脚本将会停止执行
有三种错误等级:提示notice、警告warning、错误error,error包括解析错误parse error,除了parse error外都是运行时错误
默认,除了notice都会捕获并且展示给用户,但可以在php.ini中使用error_reporting选项指定输出级别,或在脚本中使用error_reporting()函数来指定
所有错误error:E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR
除notice外的所有错误:E_ALL & ~E_NOTICE
使用@符号可以禁止语句输出错误信息(只能抑制输出错误信息,不能抑制错误造成的结果,致命错误仍然会导致程序停止运行)
(5)错误处理和记录
可以使用set_error_handler()函数注册错误处理器,当错误发生时,错误处理器会被触发。同时可以使用error_log()函数,把错误记录到管理员放置日志的地方。
例子:
//日志分流错误处理器 function log_roller($error, $errorString) { $file = ‘/var/log/php_errors.log‘; if (filesize($filesize) > 1024) { //保证日志不会大于1k rename($file, $file . (string)time()); clearstatcache(); } error_log($errorString, 3, $file); } set_error_handler(‘log_roller‘); for ($i = 0; $i < 5000; $i++) { trigger_error(time() . ‘:Just an error\n‘); }
php.ini中配置不显示错误,而是把错误输出到日志文件中
display_errors=off log_errors=on error_log=/tmp/errors.log
(6)性能调优
开始不要太关注优化,先让代码工作起来,然后找到慢的部分去优化。
优化代码的目标是缩短代码运行时间和内存占用,优化的代码可读性可能会差一些。
基准测试benchmark
可以用Apache基准测试工具ab来做性能测试
./ab -c 10 -n 1000 http://localhost/info.php //10个并发请求执行1000次
查看代码运行时间
$start = microtime(); phpinfo(); $end = microtime(); echo $end - $start; //执行时间
问题:tcp rpc相对于http rpc的优点是什么?