百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

PHP安全编码规范不可忽略(php安全攻防)

toyiye 2024-08-26 22:51 4 浏览 0 评论

目录

  • 1、安全编码的基本原则
  • 1.1 所有输入数据都是有害的
  • 1.2 不依赖运行环境的安全配置
  • 1.3 安全控制措施落实在最后执行阶段
  • 1.4 最小化
  • 1.5 失败终止
  • 二、 常见漏洞安全编码
  • 2.1 低版本框架、库漏洞
  • 2.2 Command Injection
  • 2.3 SQL Injection
  • 2.4 XSS
  • 2.5 CSRF
  • 2.6 URL Redirect
  • 2.7 路径可控
  • 2.8 Code Injection
  • 2.9 Xpath Injection
  • 2.10 资源泄露
  • 2.11 XXE
  • 2.12 SSRF
  • 2.13 敏感信息泄露
  • 2.14 越权漏洞
  • 2.15 MongoDB Injection
  • 2.16 弱类型漏洞
  • 2.17 任意文件上传
  • 2.18 本地文件包含
  • 2.19 并发问题
  • 2.20 WebSocket跨站劫持

1、安全编码基本原则

1.1 所有输入数据都是有害的

直接输入数据:

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

间接地输入数据:

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

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,执行:

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

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

  1. svn add *

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

  1. 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运算时,应在各拼接字符串之间添加分隔符:

  1. //避免md5("1234"."14"."234")===md5("1234"."1"."4234")
  2. md5($secret . "|" . $uid . "|" . $code);

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

以内置函数strpos为例:

  1. 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 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指定用法如下:

  1. redis > SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]
  2. ● EX seconds - 设置指定的过期时间,单位为秒。
  3. ● PX milliseconds - 设置指定的过期时间,单位为毫秒。
  4. ● NX - 仅当KEY不存在时,设置KEY的值为VALUE。
  5. ● XX - 仅当KEY存在时,设置KEY的值为VALUE。

代码示例:

$res=$redis->set($key, $value, ["nx", "px"=>$ps]);
if(!$res){
throw new Exception("操作太快了,请稍后再试!");
}
  1. //开始处理业务逻辑

以上代码,在$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;
}

相关推荐

# Python 3 # Python 3字典Dictionary(1)

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中,格式如...

Python第八课:数据类型中的字典及其函数与方法

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值...

Python中字典详解(python 中字典)

字典是Python中使用键进行索引的重要数据结构。它们是无序的项序列(键值对),这意味着顺序不被保留。键是不可变的。与列表一样,字典的值可以保存异构数据,即整数、浮点、字符串、NaN、布尔值、列表、数...

Python3.9又更新了:dict内置新功能,正式版十月见面

机器之心报道参与:一鸣、JaminPython3.8的热乎劲还没过去,Python就又双叒叕要更新了。近日,3.9版本的第四个alpha版已经开源。从文档中,我们可以看到官方透露的对dic...

Python3 基本数据类型详解(python三种基本数据类型)

文章来源:加米谷大数据Python中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。在Python中,变量就是变量,它没有类型,我们所说的"类型"是变...

一文掌握Python的字典(python字典用法大全)

字典是Python中最强大、最灵活的内置数据结构之一。它们允许存储键值对,从而实现高效的数据检索、操作和组织。本文深入探讨了字典,涵盖了它们的创建、操作和高级用法,以帮助中级Python开发...

超级完整|Python字典详解(python字典的方法或操作)

一、字典概述01字典的格式Python字典是一种可变容器模型,且可存储任意类型对象,如字符串、数字、元组等其他容器模型。字典的每个键值key=>value对用冒号:分割,每个对之间用逗号,...

Python3.9版本新特性:字典合并操作的详细解读

处于测试阶段的Python3.9版本中有一个新特性:我们在使用Python字典时,将能够编写出更可读、更紧凑的代码啦!Python版本你现在使用哪种版本的Python?3.7分?3.5分?还是2.7...

python 自学,字典3(一些例子)(python字典有哪些基本操作)

例子11;如何批量复制字典里的内容2;如何批量修改字典的内容3;如何批量修改字典里某些指定的内容...

Python3.9中的字典合并和更新,几乎影响了所有Python程序员

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

Python3大字典:《Python3自学速查手册.pdf》限时下载中

最近有人会想了,2022了,想学Python晚不晚,学习python有前途吗?IT行业行业薪资高,发展前景好,是很多求职群里严重的香饽饽,而要进入这个高薪行业,也不是那么轻而易举的,拿信工专业的大学生...

python学习——字典(python字典基本操作)

字典Python的字典数据类型是基于hash散列算法实现的,采用键值对(key:value)的形式,根据key的值计算value的地址,具有非常快的查取和插入速度。但它是无序的,包含的元素个数不限,值...

324页清华教授撰写【Python 3 菜鸟查询手册】火了,小白入门字典

如何入门学习python...

Python3.9中的字典合并和更新,了解一下

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

python3基础之字典(python中字典的基本操作)

字典和列表一样,也是python内置的一种数据结构。字典的结构如下图:列表用中括号[]把元素包起来,而字典是用大括号{}把元素包起来,只不过字典的每一个元素都包含键和值两部分。键和值是一一对应的...

取消回复欢迎 发表评论:

请填写验证码