对付用户通过 GET, POST, COOKIE, REQUEST等输入的数据以及框架供应的数据来源,即通信协议中从客户端传过来的统统变量,无论是用户手动填写的数据或是客户端浏览器或操作系统自动填写的数据,都可能产生安全问题,须要进行严格的安全性检讨。

间接地输入数据:

从数据库、文件、网络、内部API获取的数据等,即一些不直接来源于用户,但是又不是程序中定义好的常量数据。
比如用户的输入经由层层转化输出到数据库或文件,后面又再次利用的时候,这时得到的数据依然是不可信的,同样须要进行严格的安全性检讨。

php上传安全PHP平安编码规范弗成疏忽 Docker

1.2 不依赖运行环境的安全配置

不能寄希望于配置文件的安全选项,必须将程序置身于最不屈安的配置下进行考虑。

1.3 安全掌握方法落实在末了实行阶段

每个安全问题都有其产生的缘故原由,例如SQL注入的缘故原由是SQL语句参数拼接。
因此对SQL注入问题的戒备,须要在SQL语句实行前对参数进行安全处理,由于此时才能确定预期的参数数据类型、数据范围等。

1.4 最小化

最小化原则适用于所有与安全干系的领域,在代码安全方面紧张表现为:

1、用户输入最小化。
尽可能少地利用用户的输入。

2、用户输入范围最小化。
过滤参数时应利用白名单策略,对付可明确定义范围的参数检讨参数的有效性,譬如Email,卡号,身份证号等。

3、返复书息最小化。
程序缺点信息应对用户屏蔽,不要将原始缺点信息直接返回到用户侧。

1.5 失落败终止

对用户提交的数据进行安全性检讨的时候,如果创造数据不符合哀求应终止业务的实行,不要试图改动和转换用户提交的参数连续向下实行。

二、 常见漏洞安全编码2.1 低版本框架、库漏洞

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

2.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") . "";}}2.3 SQL Injection

安全方法:

把稳: 不论项目是否利用了框架,涉及到的表名、字段名用户可控时,均应先利用白名单对此类数据进行处理:

switch ($order){case "id": $order="id";break;case "name":$order="name";break;default :$order="id";}

a、未利用框架时,可利用安全SDK项目供应的安全方法:

//查询$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、利用Yii、laravel、CodeIgniter框架时,可利用框架自带的数据库访问方法。

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");2.4 XSS

安全方法:

a、用户可控数据不需存储直接相应的,应编码输出。

// html实体编码输出$this->securityUtil->encodeForHTML($data);// javascript编码输出$this->securityUtil->encodeForJavaScript($data)

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

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

把稳: 输入过滤会改变用户输入。
应结合详细业务场景搭配利用输入过滤、输出编码。

2.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防护方法失落效。
2.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)){// 非信赖域名,供应二次确认页}// 开始处理业务逻辑2.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}2.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 . "';");}2.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 . "' ]");//业务逻辑}}}}2.10 资源透露

安全方法: 此类安全问题应在干系操作完成后开释资源:

$file = fopen("file.txt", "w") or die("Unable to open file!");...//关闭由fopen()函数打开的文件fclose($file);2.11 XXE

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

//禁止加载外部实体libxml_disable_entity_loader(true);//解析xml数据$xml = simplexml_load_string($data);2.12 SSRF

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

if (!$this->securityUtil->verifySSRFURL($url)) {return ; //内部ip}//开始处理业务逻辑2.13 敏感信息透露

安全方法: 此类安全问题应在代码上线之前删除注释信息(特殊是敏感信息),合理设置忽略文件:

a、gitlab/github

根据目录新建文档 .gitignore,内容可参考.gitignore

b、SVN

新建文档.svnignore,内容可参考.gitignore,实行:

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

把稳: 当利用add的时候,禁止利用:

svn add

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

svn add --force .2.14 越权漏洞

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

$articleId=$_GET["articleId"];//用户登录状态下,从session取出用户唯一标识userid$userId = session("userId");...//关联用户信息实行数据库操作$stmt = $db->prepare("UPDATE articles SET del_flag=1 WHERE articleId=? AND userId=?");$stmt->bind_param("ss",$articleId, $userid);$stmt->execute();2.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);}

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

2.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)未找到时返回布尔值FALSE;

3)当$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 ;}//业务逻辑}2.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']上传到文件做事器2.18 本地文件包含

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

$filename =$_GET["filename"];switch ($filename) {case "lfi.txt":include("./lfi.txt");break;...default:include("./notexists.txt");}2.19 并发问题

安全方法:

php+mysql(InnoDB、REPEATABLE-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 somethingmysqli_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的膨胀。

2.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;}