php安全编码规范

本文涉及的产品
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
云原生内存数据库 Tair,内存型 2GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介: php安全编码规范

php安全编码规范

1.配置

php.ini基本安全配置

1.1应启用“cgi.force_redirect”

cgi.force_redirectphp.ini中修改,默认是开启的,它可以防止当PHP运行的CGI脚本未经验证的访问。在IISOmniHTTPDXitami上是禁用的,但在所有其他情况下它应该打开。

; php.ini

cgi.force_redirect=1 ;

1.2应禁用“enable_dl”

该指令仅对Apache模块版本的PHP有效。你可以针对每个虚拟机或每个目录开启或关闭dl()动态加载PHP模块。关闭动态加载的主要原因是为了安全。通过动态加载,有可能忽略所有open_basedir限制。默认允许动态加载,除了使用安全模式。在安全模式,总是无法使用dl()

; php.ini

enable_dl=0 ;

1.3应禁用“file_uploads”

file_uploads默认是开启的,允许将文件上传到您的站点。因为来自陌生人的文件本质上是不可信甚至危险的,除非您的网站绝对需要,否则应禁用此功能。如果开启请进行相应的限制,参考upload_max_filesize, upload_tmp_dir,post_max_size

; php.ini

file_uploads = 0 ;

1.4通过“open_basedir”限制文件访问权限

open_basedir默认是打开所有文件,它将 PHP 所能打开的文件限制在指定的目录树,包括文件本身。本指令不受安全模式打开或者关闭的影响。当一个脚本试图用例如 fopen() ,include或者 gzopen() 打开一个文件时,该文件的位置将被检查。当文件在指定的目录树之外时 PHP 将拒绝打开它。所有的符号连接都会被解析,所以不可能通过符号连接来避开此限制。

open_basedir应该配置一个目录,然后可以递归访问。但是,应该避免使用. (当前目录)作为open_basedir值,因为它在脚本执行期间动态解析特殊值 . 指明脚本的工作目录将被作为基准目录,但这有些危险,因为脚本的工作目录可以轻易被 chdir() 而改变。

在 httpd.conf 文件中,open_basedir 可以像其它任何配置选项一样用“php_admin_value open_basedir none”的方法关闭(例如某些虚拟主机中)。在 Windows 中,用分号分隔目录。在任何其它系统中用冒号分隔目录。作为 Apache 模块时,父目录中的 open_basedir 路径自动被继承。

用 open_basedir 指定的限制实际上是前缀,不是目录名。也就是说“open_basedir = /dir/incl”也会允许访问“/dir/include”“/dir/incls”,如果它们存在的话。如果要将访问限制在仅为指定的目录,用斜线结束路径名。例如:“open_basedir = /dir/incl/”

; php.ini

open_basedir="${USER}/scripts/data" ;

1.5应禁用“session.use_trans_sid”

默认为 0(禁用)。当禁用cookie时,如果它开启,PHP会自动将用户的会话ID附加到URL。基于 URL 的会话管理比基于 cookie 的会话管理有更多安全风险,从表面上看,这似乎是让那些禁用cookie的用户正常使用您的网站的好方法。实际上,它使那些用户容易被任何人劫持他们的会话。例如用户有可能通过 email 将一个包含有效的会话 ID 的 URL 发给他的朋友,或者用户总是有可能在收藏夹中存有一个包含会话 ID 的 URL 来以同样的会话 ID 去访问站点。也可以从浏览器历史记录和服务器日志中检索URL获取会话ID

; php.ini

session.use_trans_sid = 0 ;

1.6会话管理cookie不能是持久的

没有固定生命周期或到期日期的Cookie被称为非持久性或会话”cookie,这意味着它们只会持续与浏览器会话一样长,并且在浏览器关闭时会消失。具有到期日期的Cookie叫做持久性”Cookie,他们将被存储/保留到这些生存日期。

管理网站上的登录会话应用非持久性cookie。要使cookie非持久化,只需省略该 expires属性即可。也可以使用session.cookie_lifetime实现。

1.7应禁用"allow_url_fopen""allow_url_include"

allow_url_fopenallow_url_include默认是开启的,他们允许代码从URL中读入脚本。从站点外部吸入可执行代码的能力,加上不完美的输入清理可能会使站点裸露给攻击者。即使该站点的输入过滤在今天是完美的,但不能保证以后也是。

; php.ini

allow_url_fopen = 0

allow_url_include = 0

2.编码

php安全编码建议

2.1慎用sleep()函数

sleep()有时用于通过限制响应率来防止拒绝服务(DoS)攻击。但是因为它占用了一个线程,每个请求需要更长的时间来服务,这会使应用程序更容易受到DoS攻击,而不是减少风险。

if (is_bad_ip($requester)) {

 sleep(5);  // 不合规的用法

}

2.2禁止代码动态注入和执行

eval()函数是一种在运行时运行任意代码的方法。 函数eval()语言结构是非常危险的,因为它允许执行任意 PHP 代码。因此不鼓励使用它。如果您仔细的确认过,除了使用此结构以外别无方法,请多加注意,不要允许传入任何由用户提供的、未经完整验证过的数据。

eval($code_to_be_dynamically_executed)  // 不合规的用法

2.3禁止凭据硬编码

因为从编译的应用程序中提取字符串很容易,所以永远不应对凭证进行硬编码。对于分发的应用程序尤其如此。 凭据应存储在受强保护的加密配置文件或数据库中的代码之外。

// 合规的用法

$uname = getEncryptedUser();

$password = getEncryptedPass();

connect($uname, $password);

 

 

// 不合规的用法

$uname = "steve";

$password = "blue";

connect($uname, $password);

2.4禁止危险函数

有时候,我们不希望执行包括system()等在那的能够执行命令的php函数,或者能够查看phpinfo信息的

phpinfo()等函数,那么我们就可以禁止它们:

disable_functions = system,passthru,exec,shell_exec,popen,phpinfo

如果你要禁止任何文件和目录的操作,那么可以关闭很多文件操作

disable_functions = chdir,chroot,dir,getcwd,opendir,readdir,scandir,fopen,unlink,delete,copy,mkdir,   rmdir,rename,file,file_get_contents,fputs,fwrite,chgrp,chmod,chown

以上只是部分常用的文件处理函数,你也可以把上面执行命令函数和这个函数结合,应该就能够抵制大部分的phpshell了。

3、常见漏洞安全编码

3.1 低版本框架、库漏洞

安全方法: 此类安全问题应使用稳定的高版本框架、库进行开发。

3.2 Command Injection

安全方法: 无法规避此类功能时,应使用白名单控制:

if(isset($_POST["target"]))

{

   $target = $_POST["target"];

   switch ($target) {

   case "www.protect.domain": echo "" . shell_exec("nslookup  www.protect.domain") . "";

       break;

   case "web.protect.domain": echo "" . shell_exec("nslookup  web.protect.domain") . "";

       break;

   ...

   default: echo "" . shell_exec("nslookup  www.protect.domain") . "";

   }          

}

3.3 SQL Injection

安全方法:
注意: 不论项目是否使用了框架,涉及到的表名、字段名用户可控时,均应先使用白名单对此类数据进行处理:

switch ($order){

   case "id": $order="id";

       break;

   case "name":$order="name";

       break;

   default :$order="id";  

}

a、未使用框架时,可使用提供的安全方法:

//查询

$result=$this->db->select()->from("table_name")->where("id","=",$id)->execute()->fetchAll();  

//删除

$result=$this->db->delete()->from("table_name")->where("name","like","%".$name."%")->execute();    

//插入

$result=$this->db->insert(array("name","age"))->into("table_name")->values(array($name,$age))->execute();      

//更新

$result=$this->db->update(array("name" => $name))->table("table_name")->where("id", "=", $id)->execute();      

b、使用YiilaravelCodeIgniter框架时,可使用框架自带的数据库访问方法。Yii 2.0

//查询

Users::find()->where(["id"=>$id])->orderBy("name")->select("id,name")->one();

//插入

$user=new Users();

$user->age=$age;

$user->name=$name;

$user->save();

//单条更新

$use=Users::find()->where(["id"=>$id])->orderBy("name")->select("id,name")->one();

$use->name=$name;

$use->save();

//多条更新

$result=Users::find()->where([">","id",$id])->orderBy("name")->select("id,name")->all();

foreach ($result as $item){

   $item->name=$name;

   $item->save();

}

//删除

Users::deleteAll(["id"=>$id]);

Laravel

//查询

DB::table("table_name")->where("id",$id)->orderBy("id")->get();

//插入                      

DB::table("table_name")->insert(["name" => $name, "age" => $age]);

//更新                    

DB::table("table_name")->where("id", $id)->update(["name" => $name]);  

//删除                    

DB::table("table_name")->where("age", $age)->delete();                                

CodeIgniter

//查询

$result = $this->db->select("*")->from("table_name")->where("userid", $userid)->order_by("id")->get();    

//插入

$this->db->insert("table_name", array("title" => $title, "userid" => $userid));  

//更新                              

$this->db->where("id",$id)->update("table_name", array("name" => $title, "age" => $age));            

//删除

$this->db->where("id", $id)->delete("table_name");              

3.4 XSS

安全方法:a、用户可控数据不需存储直接响应的,应编码输出。

// html实体编码输出

$this->securityUtil->encodeForHTML($data);  

// javascript编码输出

$this->securityUtil->encodeForJavaScript($data)

b、用户可控数据需存储展示或用于其他系统展示的,应过滤危险字符。

$this->securityUtil->purifier($_GET["data"]);

注意: 输入过滤会改变用户输入。应结合具体业务场景搭配使用输入过滤、输出编码。

3.5 CSRF

安全方法:
前端应从cookie中获取在认证通过后植入的csrf_token,并以POST方式提交包含csrf_token值的请求,前端代码如下:

function getCookie() {

   var value = "; " + document.cookie;

   var parts = value.split("; csrf_token=");

   if (parts.length == 2)

       return parts.pop().split(";").shift();

}

 

$.ajax({

   type: "post",

   url: "/xxxx",

   data: {csrf_token:getCookie()},

   dataType: "json",

   success: function (data) {

       if (data.ec == 200) {

        //do something

       }

   }

});

后端应从POST请求体中提取csrf_token参数值,进行校验,代码如下:

if(!$this->securityUtil->verifyCSRFToken()){

   return ;   //csrf token 校验失败

}

// 开始处理业务逻辑

注意:csrf_token生成方式影响,当存在XSS时,会导致全局CSRF防护措施失效。

3.6 URL Redirect

安全方法: 此类安全问题服务端应根据具体的业务需求防止不安全的重定向:a、如果跳转后的链接比较少且比较固定,那么可以在服务端对参数进行白名单限制,非白名单里面的URL禁止跳转。

$index=intval($_GET["index"]);

switch($index){

   case 1:  $url="https://www.protect.domain/";

       break;  

   case 2:  $url="https://web.protect.domain/";

       break;

   ...

   default: $url="https://web.protect.domain/";

}

header("Location:".$url);

当链接比较多时,可根据索引从数据库检索。b、如仅希望在当前域跳转,或因业务需要,跳转的链接经常变化且比较多,应做个二次确认页,对非当前域的链接,提示用户将跳转到其他网站:

$white=[".protect.domain"];

//校验是否为信任域

if(!$this->securityUtil->verifyRedirectUrl($url,$white)){

   // 非信任域名,提供二次确认页

}

// 开始处理业务逻辑

3.7 路径可控

安全方法: 无法规避外界指定路径名时,应使用白名单处理:

$directory = $_GET["directory"];

switch ($directory) {

     // $directory重新赋值

     case "./image":$directory="./image";  

            break;

     case "./page":$directory="./page";

            break;

     ...

     default:$directory="./image";

}

while($line = readdir($directory))

{  

    //do something

}

3.8 Code Injection

安全方法: 无法规避此类功能时,应精确匹配用户提交数据:

$name=strval($_POST["name"]);

$regex="/^[a-zA-Z0-9]{3,20}$/";

if(preg_match($regex,$name,$matches) && $matches[0]===$name)

{

   eval ("echo '" . $name . "';");

}

3.9 Xpath Injection

安全方法: 此类安全问题应精确匹配用户输入:

if(isset($_POST["login"]) && $_POST["login"]){

       if(isset($_POST["password"]) && $_POST["password"]){

              $login = strval($_POST["login"]);

              $password = strval($_POST["password"]);

              $xml = simplexml_load_file("./heroes.xml");

              $regex="/^[a-zA-Z0-9]{3,20}$/";

              if(preg_match($regex,$login,$match_login) && $match_login[0]===$login){

                  if(preg_match($regex,$password,$match_password) && $match_password[0]===$password){

                      $result = $xml->xpath("/heroes/hero[login= '" . $login . "' and password= '" . $password . "' ]");

                      //业务逻辑

                  }

              }

       }

}

3.10 资源泄露

安全方法: 此类安全问题应在相关操作完成后释放资源:

$file = fopen("file.txt", "w") or die("Unable to open file!");

...

//关闭由fopen()函数打开的文件

fclose($file);

3.11 XXE

安全方法: 此类安全问题应在解析XML数据时显式禁止加载外部实体:

//禁止加载外部实体

libxml_disable_entity_loader(true);

//解析xml数据

$xml = simplexml_load_string($data);

3.12 SSRF

安全方法: 应校验传入ip地址是否为内部ip

if (!$this->securityUtil->verifySSRFURL($url)) {

   return ; //内部ip

}

//开始处理业务逻辑

3.13 敏感信息泄露

安全方法: 此类安全问题应在代码上线之前删除注释信息(特别是敏感信息),合理设置忽略文件:
agitlab/github项目根目录新建文档 .gitignore,内容可参考.gitignore
bSVN新建文档.svnignore,内容可参考.gitignore,执行:

svn propset svn:ignore -R -F .svnignore .

注意: 当使用add的时候,禁止使用:

svn add *

这样会把忽略中的文件也添加到仓库。应使如下命令:

svn add --force .

3.14 越权漏洞

安全方法: 涉及到用户数据的增删改查,应校验数据归属:

$articleId=$_GET["articleId"];

//用户登录状态下,从session取出用户唯一标识userid

$userId = session("userId");

...

//关联用户信息执行数据库操作

$stmt = $db->prepare("UPDATE articles SET del_flag=1 WHERE articleId=? ANS userId=?");

$stmt->bind_param("ss",$articleId, $userid);

$stmt->execute();

3.15 MongoDB Injection

安全方法: 涉及到MongoDB的操作,应禁用execute方法,校验数据类型:

$appId  = $this->request->post("appId");

$num = $this->request->post("num");

...

//校验数据类型

if(!is_array($appId)&&!is_array($num))

{

   $criteria = ["appId" => $appId, "num" => $num];

   $ret = $this->mongodb->findOne($criteria);

}

预期数据类型明确的(如商品数量),应在接收用户提交数据时直接强制数据类型转换,避免因校验不严谨可能带来的其他问题。

3.16 弱类型漏洞

安全方法:
注意:a、涉及多字符串拼接进行md5运算时,应在各拼接字符串之间添加分隔符:

//避免md5("1234"."14"."234")===md5("1234"."1"."4234")

md5($secret . "|" . $uid . "|" . $code);  

b、涉及到函数返回结果既可能是布尔值(FALSE),也可能是等同于布尔值(FALSE)的非布尔值且返回结果用于判断时,需显式判断执行结果
以内置函数strpos为例:

int strpos ( string $haystack , mixed $needle [, int $offset = 0 ] )

该函数:1)返回$needle$haystack中首次出现的数字位置;2)未找到时返回布尔值FALSE3)当$needle$haystack起始位置出现时,会返回0.
则安全编码应为:

$domain="https://www.protect.domain";

//禁止 if(!strpos($domain,"https")) if(strpos($domain,"https")!=false)的方式

if(strpos($domain,"https")!==false){  

   //处理业务逻辑

}

此类安全问题应使用=== 代替 == , !== 代替 !=。

if (isset($_GET["id"]) && isset($_GET["userId"])) {

   $id = strval($_GET["id"]);

   $userId = strval($_GET["userId"]);

   //防止出现 md5("240610708")==md5("QNKCDZO")

   if (md5($id) !== md5($userId)) {

      return ;

   }

   //业务逻辑

}

3.17 任意文件上传

安全方法: 此类安全问题应校验上传文件大小、后缀、类型等是否符合要求:

$config=array('limit'=>5 * 1024 * 1024, //允许上传的文件最大大小

   'type'=>array(                      //允许的上传文件后缀及MIME

        "gif"=>"image/gif",

        "jpg"=>"image/jpeg",

        "png"=>"image/png")

);

 

$file = $_FILES["file"];

$data=$this->securityUtil->verifyUploadFile($file, $config);

if($data['flag']!==true){

   return; //上传失败

}

//生成新的文件名拼接$data['ext']上传到文件服务器

3.18 本地文件包含

安全方法: 无法规避外界指定文件名时,应使用白名单处理:

$filename =$_GET["filename"];

switch ($filename) {

     case "lfi.txt":include("./lfi.txt");

            break;

     ...

     default:include("./notexists.txt");

}

3.19 并发问题

安全方法:php+mysql(InnoDBREPEATABLE-READ)
此类安全问题在已使用事务的前提下,应使用悲观锁或乐观锁解决:
悲观锁:

mysqli_query($conn, "BEGIN");

$rs = mysqli_query($conn, "SELECT num FROM oversold WHERE id = 1 FOR UPDATE ");  //for UPDATE

$row = mysqli_fetch_array($rs);

$num = $row[0];

if($num>0)

{

   //do something

   mysqli_query($conn, "UPDATE oversold SET num = num - 1 WHERE id = 1");

}

if(mysqli_errno($conn)) {

   mysqli_query($conn, "ROLLBACK");

} else {

   mysqli_query($conn, "COMMIT");

}

mysqli_close($conn);

SELECT ... FOR UPDATE加悲观锁,保证总是获取最新的数据,适合写入频繁的场景.
乐观锁: 需使用一个新的字段version保存版本号:

mysqli_query($conn, "BEGIN");

$result = mysqli_query($conn, "SELECT num,version FROM oversold WHERE id = 1 ");

$row = mysqli_fetch_array($result);

$num = $row[0];

$version=$row[1];

if($num>0)

{

   //do something

   $stmt=$conn->prepare("UPDATE oversold SET num = num - 1,version=version+1 WHERE id = 1 AND version= ? ");

   $stmt->bind_param("i",$version);

   $stmt->execute();

}

if(mysqli_errno($conn)) {

   mysqli_query($conn, "ROLLBACK");

} else {

   mysqli_query($conn, "COMMIT");

}

mysqli_close($conn);

 

乐观锁比较适合数据修改比较少,读取比较频繁的场景。

注意: 悲观锁会带来比较大的性能开销,而乐观锁可能会读取到脏数据,具体采用哪种加锁方式可根据具体业务场景确定。

php+redis

当redis版本不小于2.6.12时,应使用set指令限流避免并发问题。set指定用法如下:

 

redis > SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]

 ● EX seconds - 设置指定的过期时间,单位为秒。

 ● PX milliseconds - 设置指定的过期时间,单位为毫秒。

 ● NX - 仅当KEY不存在时,设置KEY的值为VALUE

 ● XX - 仅当KEY存在时,设置KEY的值为VALUE

代码示例:

$res=$redis->set($key, $value, ["nx", "px"=>$ps]);

if(!$res){

   throw new Exception("操作太快了,请稍后再试!");

}

//开始处理业务逻辑

以上代码,在$ps毫秒内,对指定的$key,仅允许执行一次业务逻辑。
注意: 涉及到的key应结合具体业务场景注意key过期时间的设置,防止key的膨胀。

3.20 WebSocket跨站劫持

安全方法: 此类安全问题服务端应校验Origin头:

$origin="https://www.protect.domain/";

...

function CheckOrigin(){

   if (array_key_exists("Origin", $_SERVER)) {

       $value = $_SERVER["Origin"];

       if($origin===$value){  

           return ture;

       }

   }

   return false;  

}

 

 

相关文章
|
2月前
|
SQL 存储 安全
PHP 与现代 Web 应用的安全挑战与解决方案
随着 Web 应用的发展,PHP 作为一种广泛使用的服务器端脚本语言,面临着越来越复杂的安全挑战。本文探讨了当前 PHP 开发中常见的安全问题,并提供了相应的解决方案,帮助开发者构建更安全可靠的 Web 应用。 【7月更文挑战第8天】
56 1
|
1月前
|
Ubuntu 应用服务中间件 Linux
如何在Ubuntu 14.04上使用Nginx和Php-fpm安全地托管多个网站
如何在Ubuntu 14.04上使用Nginx和Php-fpm安全地托管多个网站
13 0
|
3月前
|
自然语言处理 安全 PHP
PHP 之道笔记整理:最佳实践与安全指南
这篇文章讨论了PHP开发中的最佳实践,包括使用最新稳定版(PHP 8.3)以提升性能和安全,利用`DateTime`类及Carbon库处理日期时间,确保使用UTF-8编码并用`mb_*`函数处理字符串,以及通过密码哈希和数据过滤来加强Web应用安全。文章提醒开发者始终保持对新技术和安全实践的关注。
68 2
|
3月前
|
存储 安全 PHP
安全开发-PHP应用&文件管理模块&显示上传&黑白名单类型过滤&访问控制&文件管理模块&包含&上传&遍历&写入&删除&下载&安全
安全开发-PHP应用&文件管理模块&显示上传&黑白名单类型过滤&访问控制&文件管理模块&包含&上传&遍历&写入&删除&下载&安全
|
2月前
|
安全 数据安全/隐私保护
屏蔽修改wp-login.php登录入口确保WordPress网站后台安全
WordPress程序默认的后台地址wp-login.php,虽然我们的密码设置比较复杂,但是如果被软件一直扫后台入口,一来影响网站的速度增加服务器的负担,二来万一被扫到密码,那就处于不安全的境地。所以,我们最好将后台地址入口隐藏屏蔽起来,我们可以通过下面的命令实现隐蔽wp-login.php入口。
68 0
|
4月前
|
安全 前端开发 PHP
采用PHP开发的医院安全(不良)事件系统源码 医院不良事件有哪些?又该怎样分类呢?也许这篇文章能给予你答案。
医疗安全不容忽视! 医疗不良事件有哪些?又该怎样分类呢?也许这篇文章能给予你答案。
50 1
采用PHP开发的医院安全(不良)事件系统源码 医院不良事件有哪些?又该怎样分类呢?也许这篇文章能给予你答案。
|
3月前
|
安全 前端开发 测试技术
安全开发-PHP应用&模版引用&Smarty渲染&MVC模型&数据联动&RCE安全&TP框架&路由访问&对象操作&内置过滤绕过&核心漏洞
安全开发-PHP应用&模版引用&Smarty渲染&MVC模型&数据联动&RCE安全&TP框架&路由访问&对象操作&内置过滤绕过&核心漏洞
|
3月前
|
存储 安全 关系型数据库
安全开发-PHP应用&留言板功能&超全局变量&数据库操作&第三方插件引用&后台模块&Session&Cookie&Token&身份验证&唯一性
安全开发-PHP应用&留言板功能&超全局变量&数据库操作&第三方插件引用&后台模块&Session&Cookie&Token&身份验证&唯一性
|
4月前
|
存储 安全 前端开发
PHP医院安全不良事件管理系统源码(AEMS)前端vue2+element+后端laravel8不良事件上报与闭环管理
医院不良事件上报与管理系统结合现代医院管理思路,遵照PDCA全面质量循环管理方法而设计,并在多家大型三甲医院成熟运用。系统从事件上报、基于人、机、料、法 、环的RCA分析、事件整改、效果评估实现了结构化、标准化、智能化的管理和分析,满足医院可追溯化、全流程闭环管理要求,满足等级医院评审细则要求,大力提高医院不良事件上报的效率,保障事件分析的准确性,促进医疗安全的提高,避免同类事件再次发生,改善整个医院医疗安全,从而实现医院安全医疗的目标。
66 3
|
4月前
|
人工智能 安全 前端开发
PHP医疗不良事件上报系统源码 AEMS 医院安全(不良)事件报告系统源码 可提供演示
根据报告系统主体和适用范围;根据所报告事件强制性要求 分为自愿报告系统和强制报告系统。强制报告系统主要定义为严重的、可预防的护理差错和可以确定的不良事件,几乎所有医院的护理主管部门都制订了不良事件上报制度,以便有效地分析事件原因。自愿报告系统是强制报告系统的补充,要求和鼓励护理主管部门或个人自愿上报不 良事件,更有助于发现组织系统的安全 隐患,加强护理安全管理。
50 4