首发于安全客:ThinkPHP安全开发规范 - 安全客,安全资讯平台
目前ThinkPHP在国内中小型开发场景非常流行,但由于漏洞频发,主要集中在SQL注入、信息泄露(debug模式打开)、越权等漏洞,使得业务安全性受到不小的挑战。另外由于ThinkPHP版本比较多,实际业务多用3.2.3或5.1,因此下面主要从这两个版本来介绍ThinkPHP开发过程中常见的安全问题。
极少业务出现使用官方默认数据库操作方法引发SQL注入的,通常是业务不用官方I函数或者标准方法,而是自定义了过滤函数,例如下面的recursive()
,由于采用的黑名单方式过滤不完整且没有对过滤结果二次验证,导致通过双写绕过:
function recursive($arr){
foreach ($arr as $k => $v) {
if (is_array($v)) {
$arr[$k]=recursive($v);
} else {
$keyword = 'select|insert|update|delete|union|into|load_file|outfile|sleep| or ';
$arr1 = explode( '|', $keyword );
$v = str_ireplace( $arr1, '', $v );
$arr[$k] = $v;
}
}
return $arr;
}
外网环境开启debug模式调试,导致报错信息泄露,之前有开发认为开启error_report(0)
可以避免信息泄露,然而这个处理方式对ThinkPHP是没用的。
APP_DEBUG => TRUE;
开启该选项后,一旦sql执行出错或者找不到路由,ThinkPHP则将报错路径甚至sql语句全部暴露。
发生越权的情况比较普遍在于操作数据库时没有验证或者合理验证当前用户是否有权限操作,表现为sql操作时没有加上and user_id=session->userid
这样的限制或者使用了and user_id=$_GET("user_id")
这样的查询方式,前者的问题时没有绑定当前用户,后者的问题时没有从session获取用户身份而从用户可控参数引用。
针对以上常见的安全漏洞以及ThinkPHP一年爆几次漏洞的现状,建议按照以下规范合理使用。
public
目录而不是应用根目录,并且不要随意更改入口文件的位置。public目录下面不要放除了入口文件和资源文件以外的其它应用文件。.env
文件)。//DEFAULT_FILTER为空
$filters = isset($filter)?$filter:C('DEFAULT_FILTER');
...
if(is_array($filters)){
foreach($filters as $filter){
if(function_exists($filter)) {
$data = is_array($data) ? array_map_recursive($filter,$data) : $filter($data); // 参数过滤
}else{
$data = filter_var($data,is_int($filter) ? $filter : filter_id($filter));
if(false === $data) {
return isset($default) ? $default : null;
}
}
}
}
}
...
is_array($data) && array_walk_recursive($data,'think_filter');
...
//think_filter过滤也很有限
function think_filter(&$value){
// TODO 其他安全过滤
// 过滤查询特殊字符
if(preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i',$value)){
$value .= ' ';
}
}
prepare
预查询和参数绑定机制,能有效的避免SQL注入的发生。但不代表绝对安全,如果你缺乏良好的代码规范,仍然有可能被利用。//标准方式
$User = M("User"); // 实例化User对象
$data = $User->where('status=1 AND name="thinkphp"')->find();//如果where条件使用拼接参数则仍存在sql注入
//原生方式
$Model = new \Think\Model() // 实例化一个model对象 没有对应任何数据表
$Model->query("select * from think_user where status=1");
例如ThinkPHP5:
//标准方式
Db::table('think_user')->where('id',1)->find();
//原生方式
Db::query("select * from think_user where id=? AND status=?", [8, 1]);
Db::execute("update think_user set name=:name where status=:status", ['name' => 'thinkphp', 'status' => 1]);
//手动绑定
$Model = M('User');
$where['name'] = ':name';
$list = $Model->where($where)->bind(':name',I('name'))->select();
//自动绑定
$Model = M('User');
$Model->name = 'thinkphp';
$Model->email = 'thinkphp@qq.com';
$Model->add();
例如ThinkPHP5:
//手动绑定
Db::query("select * from think_user where id=? AND status=?", [8, 1]);
//自动绑定
Db::table('think_user')
->where('name|title','like','thinkphp%')
->where('create_time&update_time','>',0)
->find();
白名单
的方式而不是黑名单
!validate
方法进行处理。大版本 | 安全建议版本 |
---|---|
3.2 | 3.2.4+ |
5.1 | 5.1.25+ |