Sec Coding - PHP
2023-02-03
[TOC]
PHP代码安全规范
1. 代码实现
1.1 输入验证
1.1.1 【必须】按类型进行数据校验
- 所有程序的外部输出的参数,都应进行数据校验。校验内容包括但不限于:
数据长度
、数据范围
、数据类型
和格式
。校验不通过,应拒绝。 - 对于输入数据的验证,应根据预期进行严格的数据校验,对参数的长度、组成、格式等应同时进行验证。任何一项不符合都应拒绝该数据输入。
- 不建议使用PHP自带的
filter_var
和它的参数FILTER_VALIDATE_EMAIL
进行Email
的数据验证。因为在RFC 3696中规定,邮箱的local part
部分可以用双引号包裹,在双引号内即可填入任意字符。在此可能会造成安全风险。
// bad: 未进行输入验证
if(isset($_GET['email'])){
echo "<br>your email is <br>" . $_GET['email'];
}else{
echo "<br>please input your email<br>";
}
// good: 根据数据类型、格式、组成等编写正则表达式进行输入验证
$pattern = "/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,})$/";
if(isset($_GET['email'])){
if(preg_match($pattern, $_GET['email'])){
echo "<br>your email is <br>" . $_GET['email'];
}else{
echo "<br>input email is not valid<br>";
}
}else{
echo "<br>please input your email<br>";
}
-
如使用开发框架,应遵循官方文档指引,启用数据校验。如:Laravel Validation、Codeigniter Form Validation、Thinkphp验证。
-
下面以Laravel为例做详细讲解:
-
如果字符串有明确的范围,如op只能为
add
和del
。// good namespace App\Rules; use Illuminate\Contracts\Validation\Rule; class OpRule implements Rule { /** * 判断验证规则是否通过。 * * @param string $attribute * @param mixed $value * @return bool */ public function passes($attribute, $value) { $arr = array('add', 'del'); return in_array($value, $arr); } /** * 获取验证错误消息。 * * @return string */ public function message() { return 'The :attribute must be add or del.'; } }
-
如字符串虽然范围不限定,但有明确的格式。例如:一个Email地址,可以使用框架自带的验证规则进行验证。
// good $result = $request->validate([ 'input' => 'email' ]);
-
如不属于上述情况,需要添加非空或者长度限制,若功能需要可以除外。
<?php // good namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class StoreBlogPost extends FormRequest { public function authorize() { return false; } public function rules() { return [ 'title' => 'required|unique:posts|max:255', ]; } }
-
1.2 执行命令
1.2.1 【必须】过滤传入命令执行函数的字符
- 在调用各类函数来执行系统命令时,如果涉及的命令由外部传入,需要对传入命令执行函数的字符进行过滤或者校验。
- 执行的命令使用
escapeshellcmd
和escapeshellarg
进行处理。
// bad 未对输入进行验证和处理
if(isset($_GET['ip'])){
$ip = $_GET['ip'];
system("ping -c 2 " . $ip);
}
// good: 执行命令前,过滤/转义指定符号, 校验数据格式。
$black_arr = [
"\n", "&", "|", "'", "\"", "\\", "`", "$", "(", ")", "-"
];
if(isset($_GET['ip'])){
$ip = $_GET['ip'];
foreach($black_arr as $key => $value){
$ip = str_replace($value, "", $ip);
}
if(filter_var($ip, FILTER_VALIDATE_IP)){
system("ping -c 2 " . $ip);
}else{
echo "ip is not valid";
}
}
// good: 通过白名单,限定外部可执行命令范围
if(isset($_GET['cmd']) && isset($_GET['arg'])){
switch($_GET['cmd']){
case "ping":
$ip = $_GET['arg'];
if(filter_var($ip, FILTER_VALIDATE_IP)){
system("ping -c 2 ". $ip);
}
break;
case "nslookup":
system("nslookup www.qq.com");
break;
default:
echo "plz input cmd args";
}
}
// good: 使用escapeshellarg处理传入的参数,处理过后的arg不可使用双引号进行包裹。
if(isset($_GET['arg'])){
$arg = escapeshellarg($_GET['arg']);
echo "" . $arg . "<br>";
system("ping -c 2 " . $arg);
}
1.2.2 【建议】避免直接调用函数执行系统命令
- 相关功能的实现应避免直接调用系统命令(如
system()
,exec()
,passthru()
,shell_exec()
等),优先使用其他同类操作进行代替,比如:通过文件系统API进行文件操作而非直接调用操作系统命令。 - 如评估无法避免,在执行命令过程中应避免拼接外部数据,建议使用白名单进行限制。
// good: 通过白名单,限定外部可执行命令范围
if(isset($_GET['cmd']) && isset($_GET['arg'])){
switch($_GET['cmd']){
case "ping":
$ip = $_GET['arg'];
if(filter_var($ip, FILTER_VALIDATE_IP)){
system("ping -c 2 ". $ip);
}
break;
case "nslookup":
system("nslookup www.qq.com");
break;
}
}
1.3 文件操作
1.3.1 【必须】文件类型限制
- 根据业务需求,使用白名单限制操作文件的后缀范围。
1.3.2 【必须】校验并限定文件路径范围,避免路径穿越
- 应固定上传、访问文件的路径。若需要拼接外部可控变量值,需要检测是否包含
..
、\
、/
等路径穿越字符,如果存在,应拒绝。
// bad:未检查文件名/路径
if(isset($_GET['filename'])){
$path = "/var/www/html/" . $_GET['filename'];
echo file_get_contents($path);
}
// good:检查了文件名/路径,是否包含路径穿越字符
if(isset($_GET['filename'])){
$path = "/var/www/html/" . $_GET['filename'];
if(strrpos($path, '..') === false){
echo file_get_contents($path);
}else{
echo "filename is not valid";
}
}
1.3.3 【必须】及时释放资源
对资源进行操作完毕后及时释放资源。
// good: 打开文件读取完毕后及时关闭文件
$myfile = fopen("webdictionary.txt", "r") or die("Unable to open file!");
echo fgets($myfile);
fclose($myfile);
1.3.4 【建议】避免路径拼接
- 文件目录避免外部参数拼接。保存文件目录建议写死,并对文件名进行校验。
1.3.5 【建议】上传文件存储到第三方系统
- 建议将存储文件的容器与业务系统分隔开,可上传到腾讯云COS中。
- 在使用腾讯云COS进行数据存储时,应结合业务实际需求,遵循最小权限原则对COS进行配置,不对外开放公有读写权限,禁用对匿名用户的列目录权限。
1.3.6 【建议】存储文件名随机化
- 建议保存文件时,将文件名替换为随机字符串。
1.3.7 【必须】避免使用PHP伪协议
- 在使用
include()
、include_once( )
、require( )
、require_once( )
包含文件时,文件的路径不能使用外部输入的数据进行拼接。 - 若无法避免包含文件的路径中存在外部输入数据,应进行严格的限制,在文件包含之前检测文件的路径是否在预期目录下。
- 在使用PHP的文件操作函数时,应对输入的文件名或者文件路径进行检测,避免使用PHP伪协议进行数据操作。若业务需要采用伪协议,应使用白名单的方式严格限制输入伪协议的协议名称。
1.4 网络请求
1.4.1 【必须】限定访问网络资源协议、地址范围
当程序需要从用户指定的URL地址获取网页文本内容
、加载指定地址的图片
、进行下载
等操作时,需要对URL地址进行安全校验:
-
只允许
HTTP
或HTTPS
协议 -
解析目标URL,获取其HOST
-
解析HOST,获取HOST指向的IP地址转换成Long型
-
检查IP地址是否为内网IP
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
100.64.0.0/10
9.0.0.0/8
127.0.0.0/8
11.0.0.0/8
30.0.0.0/8
21.0.0.0/8
22.0.0.0/8
26.0.0.0/8
28.0.0.0/8
29.0.0.0/8
- 请求URL
- 如果有跳转,跳转后执行1,否则对URL发起请求。
可使用SSRF_BLCAK_DECT进行安全请求。
1.4.2 【推荐】请求网络资源,应加密传输
- 应优先选用
HTTPS
协议请求网络资源,避免中间人劫持。
1.5 响应输出
1.5.1 【必须】设置正确的HTTP响应包类型
- 响应头
Content-Type
与实际响应内容,应保持一致。如:API响应数据类型是JSON,则响应头使用application/json
;若为XML,则设置为text/xml
。
1.5.2 【必须】设置安全的HTTP响应头
- 所有接口、页面,添加响应头
X-Content-Type-Options: nosniff
。 - 所有接口、页面,添加响应头
X-Frame-Options
。按需合理设置其允许范围,包括:DENY
、SAMEORIGIN
、ALLOW-FROM origin
。用法参考:MDN文档
1.5.3 【必须】外部输入并且在响应页面展示的数据需进行编码处理
场景 | 编码规则 |
---|---|
输出点在HTML标签之间 | 需要对以下6个特殊字符进行HTML实体编码(&, <, >, “, ‘,/)。 示例: & –> & < –> < >–> > ” –> " ’ –> ' / –> / |
输出点在HTML标签普通属性内(如href、src、style等,on事件除外) | 要对数据进行HTML属性编码。 编码规则:除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为&#xHH;(以&#x开头,HH则是指该字符对应的十六进制数字,分号作为结束符) |
输出点在JS内的数据中 | 需要进行js编码 编码规则: 除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为 \xHH (以 \x 开头,HH则是指该字符对应的十六进制数字) Tips:这种场景仅限于外部数据拼接在js里被引号括起来的变量值中。除此之外禁止直接将代码拼接在js代码中。 |
输出点在CSS中(Style属性) | 需要进行CSS编码 编码规则: 除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为 \HH (以 \ 开头,HH则是指该字符对应的十六进制数字) |
输出点在URL属性中 | 对这些数据进行URL编码 Tips:除此之外,所有链接类属性应该校验其协议。禁止JavaScript、data和Vb伪协议。 |
1.5.4 【必须】响应禁止展示物理资源、程序内部代码逻辑等敏感信息
- 业务生产(正式)环境,应用异常时,响应内容禁止展示敏感信息。包括但不限于:
物理路径
、程序内部源代码
、调试日志
、内部账号名
、内网ip地址
等。
1.5.5 【建议】添加安全纵深防御措施
- 按《腾讯内容安全防护策略(CSP)部署规范》部署CSP,规则中应引入最新的严格模式特性
nonce-
<?php
// good:正确配置Content-Type、添加了安全响应头,引入了CSP
header("Content-Type: application/json");
header("X-Content-Type-Options: nosniff");
header("X-Frame-Options: SAMEORIGIN");
header("Content-Security-Policy: script-src 'self'");
1.6 数据输出
1.6.1 【必须】敏感数据加密存储
- 敏感数据应使用SM2、RSA等算法进行加密存储。
- 敏感数据应使用独立的存储层,并且在访问层开启访问控制。
- 包含敏感信息的临时文件或者缓存一旦不需要使用应立即删除。
1.6.2 【必须】敏感信息必须由后台进行脱敏处理
- 敏感信息须再后台进行脱敏后返回,禁止接口返回敏感信息交由前端/客户端进行脱敏处理。
1.6.3 【必须】高敏感信息禁止存储、展示
- 口令、密保答案、生理标识等鉴权信息禁止展示。
- 非金融类业务,信用卡cvv码及日志禁止存储。
1.6.4 【必须】个人敏感信息脱敏展示
在满足业务需求的情况下,个人敏感信息需脱敏展示。
脱敏范围参考 http://km.oa.com/group/31829/articles/show/313706 《IDC开发运维安全标准》5.3.2 的建议:
- 身份证只显示第一位和最后一位字符,如
3****************1
。 - 移动电话号码隐藏中间6位字符,如
134******48
。 - 工作地址/家庭地址最多显示到**
区
**一级。 - 银行卡号仅显示最后4位字符,如
************8639
1.6.5 【必须】后台地址不对外开放
- 登录后台的地址应进行随机化处理来进行隐藏,不能使用
login.php
等路径。 - 在访问后台的地址进行IP白名单校验,只允许管理员等允许登录的人员进行登录管理。
1.7 执行代码
1.7.1 【必须】安全的代码执行方式
- 禁止使用
eval
、assert
等处理存在外部输入的数据。 - 在使用
preg_replace
函数时,禁止使用\e修饰符。 - 严格限制
call_user_func
、call_user_func_array
、create_function
、array_map
等函数的回调参数的值,不应传入外部输入数据。
1.8 Web跨域
1.8.1 【必须】限定JSONP接口的callback字符集范围
- JSONP接口的callback函数名为固定白名单。如callback函数名可用户自定义,应限制函数名仅包含 字母、数字和下划线。如:
[a-zA-Z0-9_-]+
1.8.2 【必须】安全的CORS配置
- 使用CORS,应对请求头Origin值做严格过滤、校验。具体来说,可以使用“全等于”判断,或使用严格的正则进行判断。如:
^https://domain\.qq\.com$
if($_SERVER['HTTP_ORIGIN'] && $_SERVER['HTTP_ORIGIN'] === 'https://domain.qq.com'){
header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']);
header("Access-Control-Allow-Credentials: true");
}
1.9 SQL操作
1.9.1 【必须】SQL语句默认使用预编译并绑定变量
- 应使用预编译绑定变量的形式编写SQL语句,保持查询语句和数据相分离,推荐使用PDO。
// bad:未使用参数绑定方式执行SQL查询
$id = $_GET['id'];
$sql = "SELECT * FROM pages WHERE id = $id";
$result = $mysql->query($sql);
// good: 使用PDO,基于参数绑定的方式执行SQL语句
$sth = $dbh->prepare('SELECT name, colour, calories
FROM fruit
WHERE calories < ? AND colour = ?');
$sth->execute(array(150, 'red'));
$red = $sth->fetchAll();
// good: 使用PDO,基于参数绑定的方式执行SQL语句
$sql = 'SELECT name, colour, calories
FROM fruit
WHERE calories < :calories AND colour = :colour';
$sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute(array(':calories' => 150, ':colour' => 'red'));
$red = $sth->fetchAll();
- 无法使用预编译时采用白名单
1.9.2 【必须】安全地使用开发框架SQL查询方法
- 应使用CodeIgniter框架的参数绑定功能,禁止拼接SQL语句。详参考官方手册Queries部分。
// bad:错误地拼接SQL语句
$dbResult = $this->db->query("SELECT * FROM users WHERE username = '".$_POST['user_name']."'");
// good:正确地使用CodeIgniter框架的where语句
$this->db->where('username',$this->input->post('user_name'));
$dbResult = $this->db->get('users');
// good:正确地使用CodeIgniter框架的参数绑定功能
$sql = "SELECT * FROM some_table WHERE id = ? AND status = ? AND author = ?";
$this->db->query($sql, array(3, 'live', 'Rick'));
- 应使用Laravel框架提供的ORM方法。详参考官方手册Database: Query Builder部分。
// good:正确地使用Laravel框架提供的ORM方法
$someVariable = Input::get("some_variable");
DB::select("SELECT * FROM some_table WHERE some_col = :somevariable", array(
'somevariable' => $someVariable,
));
1.10 NoSQL操作
1.10.1 【必须】校验参数值类型
- 将HTTP参数值代入NoSQL操作前,应严格校验类型。
<?php
$mongo = new MongoClient(); // 连接到mongodb
$db = $mongo->test; //选择数据库
$coll = $db->test; //选择集合
$username = $_GET['username'];
$password = $_GET['password'];
// bad: 在执行NoSQL操作之前,未做任何判断。
$data = array(
'username'=>$username,
'password'=>$password
);
$data = $coll->find($data);
$count = $data->count();
if ($count>0) {
foreach ($data as $user) {
echo 'username:'.$user['username']."</br>";
echo 'password:'.$user['password']."</br>";
}
}
else{
echo '未找到';
}
// good: 在进入NoSQL操作之前,判断输入的值是否为字符串。
if(gettype($username) !== "string"){
echo "username must be a string";
exit();
}
if(gettype($password) !== "string"){
echo "password must be a string";
exit();
}
$data = array(
'username'=>$username,
'password'=>$password
);
$data = $coll->find($data);
$count = $data->count();
if ($count>0) {
foreach ($data as $user) {
echo 'username:'.$user['username']."</br>";
echo 'password:'.$user['password']."</br>";
}
}
else{
echo '未找到';
}
?>
为什么要这么做?
在PHP中,因为PHP本身的弱类型特性,如果我们传入的参数是个数组,PHP会自动进行解析成数组形式。比如我们传入了一个
?a=1
,在$_GET
这个数组中的表现形式就是Array('a'=>'1');
,如果我们传入了一个?a[key]=value
,那么在$_GET
中的表现就是Array('a'=>array('key'=>'value'))
。在上述例子中:
- 期望收到的数据:
username=foo&password=bar
- 期望的等价条件:
db.test.find({username:'foo',password:'bar'});
- 黑客构造的攻击数据:
username[$ne]=1&password[$ne]=1
- 此时进行的查询语句:
db.test.find({username:{'$ne':'1'},password:{'$ne':'1'}});
- 相当于查询数据库中所有用户名不等于1且密码不等于1的数据。
- 在此就绕过了正常的逻辑,对应用进行了攻击。
1.10.2 【必须】NoSQL操作前,应校验权限/角色
- 在执行NoSQL进行增、删、改、查逻辑之前,应进行权限校验,避免越权行为。
<?php
$mongo = new MongoClient(); // 连接到mongodb
$db = $mongo->test; //选择数据库
$coll = $db->test; //选择集合
if(isset($_GET['action']) && in_array($_GET['action'], array('add', 'del'))){
$action = $_GET['action'];
if(isset($_GET['id'])){
$id = (int)($_GET['id']);
// bad: 未进行权限校验,直接执行NoSQL操作
$coll->remove(array("id" => $id));
// good: 进行权限校验,用户身份验证通过后,执行NoSQL操作
if(isset($_SESSION['admin']) && $_SESSION['admin'] === true){
$coll->remove(array("id" => $id));
}
}
}
1.10.3 【必须】避免使用字符串拼接来执行NoSQL数据库的Shell命令
- 在此以MongoDB数据库为例, 在PHP中可以直接使用函数来执行数据库的Shell命令,不应使用拼接外部输入数据的方式构造Shell命令。
<?php
error_reporting(0);
if(isset($_POST["username"]) && isset($_POST["password"])){
if(login($_POST["username"],$_POST["password"])){
die("Good, can you find out my password?");
}
die("username or password error!");
}
else{
highlight_file(__FILE__);
}
function login($u,$p){
$manager = new MongoDB\Driver\Manager("mongodb://mongo:27017");
// bad: 使用字符串拼接的方式构造执行的Shell操作。
$q = '{"username": "'.$u.'", "password": "'.$p.'"}';
$query = new MongoDB\Driver\Query(json_decode($q));
$cursor = $manager->executeQuery('babyDB.user', $query);
$data = [];
foreach($cursor as $doc) {
$data[] = $doc;
}
if (isset($data) && isset($data[0]->password)) {
return true;
}
return false;
}
?>
1.11 URL跳转
1.12.1 【必须】限定跳转目标地址
- 应使用白名单对重定向目标做校验/限制,限定重定向地址的协议前缀(默认只允许HTTP、HTTPS)、域名(默认只允许指定三级及以上域名),或固定值;
- 适用场景包括,使用PHP原生/框架提供的函数:
header('Location: '. $params)
、ThinkPHP的redirect(__USER_CONTROLLED_VALUE__,__STATUS_CODE__)
、CodeIgniter的redirect()
// bad:直接取外部可控输入做跳转
$url = __USER_CONTROLLED_VALUE__;
header('Location: '. $url);
// good:跳转做前限定协议和域名白名单的检查
$url = "http://www.baidu.com";
function check_url($url){
// 定义协议和域名白名单
// 最佳情况下,host白名单应限制到指定的三级域名
$scheme_white_list = array("http", "https");
$host_white_list = array("www.qq.com");
// PHP历史版本存在对URL的错误解析逻辑,将导致URL白名单检查被绕过
// 请确保使用安全的PHP版本:php >=5.6.28, >=7.0.13
// 参考:https://bugs.php.net/bug.php?id=73192
$url_arr = parse_url($url);
$scheme = isset($url_arr['scheme']) ? $url_arr['scheme'] : '';
$host = isset($url_arr['host']) ? $url_arr['host'] : '';
if (!in_array($scheme, $scheme_white_list)) {
return false;
}
if (!in_array($host, $host_white_list)) {
return false;
}
return true;
}
if (check_url($url)) {
header('Location: '. $url);
} else {
echo '非法重定向地址';
}
1.12 Cookie与登录态
1.12.1 【必须】为Cookies中存储的关键登录态信息添加http-only保护
-
使用
setcookie
函数时,应将关建登录态字段的httponly
属性设置为true
<?php $arr_cookie_options = array ( 'expires' => time() + 60*60*24*30, 'path' => '/', 'domain' => '.example.com', 'secure' => true, 'httponly' => true, // 将httponly设置为True 'samesite' => 'None' ); setcookie('TestCookieKey', 'TestCookieValue', $arr_cookie_options);
1.12.2 【建议】接入ptlogin登录鉴权的业务,应使用p_skey票据
- 接入ptlogin登录鉴权的业务,应使用包含域隔离机制的
p_skey
票据
1.13 表单提交
1.13.1 【必须】启用框架级的CSRF防护机制
- 如使用Laravel框架,应启用
CSRF Protection
特性。 - 如使用CodeIgniter框架,应启用CSRF防护功能
- 第 1 步:在
application/config/config.php
设置如下:
$config['csrf_protection'] = TRUE;
- 第 2 步:如果使用了[`Form Helper`](https://cloud.tencent.com/developer/section/1065926)会自动添加token。如果未使用,可使用`get_csrf_token_name()`和`get_csrf_hash()`手动添加。 ```php+HTML $csrf = array( 'name' => $this->security->get_csrf_token_name(), 'hash' => $this->security->get_csrf_hash() ); ... <input type="hidden" name="<?=$csrf['name'];?>" value="<?=$csrf['hash'];?>" />
- 第 1 步:在
1.14 反序列化对象
1.14.1 【必须】输入不能作为unserialize的值
- 不应将外部输入数据进行反序列化操作。
- 如果需要在用户处获取序列化数据,可以使用Json,通过PHP的
json_encode()
和json_decode()
函数进行解析。
<?php
class Test{
var $name;
var $age;
function __construct($name, $age){
$this->name = $name;
$this->age = $age;
}
function printData(){
echo "My Name is $this->name, and I'm $this->age years old";
}
}
// bad: 读取cookie中序列化的用户数据,直接反序列化成为对象。
$user_data = base64_decode($_COOKIE['user']);
$user_obj = unserialize($user_data); // 传入的user_data: O:4:"Test":2:{s:4:"name";s:4:"Tome";s:3:"age";i:13;}
$user_obj->printData();
// good: 使用安全的序列化数据格式json,接收用户传入的数据,并进行处理。
$user_data = json_decode(base64_decode($_COOKIE['user']));
$user_arr = json_decode($user_data, true); // 传入的user_data: {"name":"Tome","age":13}
$user_obj = new Test($user_arr['name'], $user_arr['age']);
$user_obj->printData();
1.14.2 【必须】反序列化外部读取的序列化数据之前,使用hash值进行校验
- 若程序运行过程中需要保存对象的序列化数据,应同时保存其hash值,在后续读取进行反序列化操作之前,应校验hash值,校验通过后再进行反序列化操作。
1.14.3 【必须】配置allowed_classes限定可序列化的类
在PHP7中,unserialize函数增加了一个allowed_classes
参数用来指定可以反序列化的类,在使用PHP7进行开发过程中,应配置allowed_classes
来将可以进行反序列化的类加入白名单,避免一些非预期的序列化数据传入导致安全风险。
1.15 XML读写
1.15.1 【必须】禁用外部实体
- 应在解析XML前,禁用实体解析。注:PHP8.0开始,默认禁用实体解析。
// good:加载XML前,禁用实体解析
libxml_disable_entity_loader(true);
$xml = simplexml_load_string($xmlContent);
2. 配置&环境
2.1 敏感配置
2.1.1 【必须】不在请求头中展示PHP信息
expose_php=Off
2.1.2 【必须】关闭PHP运行错误展示,记录错误日志
play_errors=Off
display_startup_errors=off
log_errors=On
error_log=/var/log/httpd/php_scripts_error.log
2.1.3 【必须】按需开启文件上传功能和最大上传文件限制
file_uploads=On upload_max_filesize=1M
2.1.4 【必须】配置disable_function禁用不必要的函数
2.1.5 【必须】使用低权限用户运行PHP
2.1.6 【必须】生产环境的安全框架配置
-
应在Laravel框架的
.env
文件中,设置APP_DEBUG=false
-
应在ThinkPHP5框架的
application/config.php
文件中,设置app_debug
为false// 关闭调试模式 'app_debug' => false,
也可以在应用根目录的
.env
文件,做如下设置:// 设置关闭调试模式 APP_DEBUG = false
ThinkPHP3.x(不推荐使用,安全版本>=3.2.4)应在入口文件修改定义代码如下:
<?php // 关闭调试模式 define('APP_DEBUG', false); ... // 加载框架入口文件 require './ThinkPHP/ThinkPHP.php';