『安全开发』PHP安全开发
本文并不是系统的介绍如何进行PHP开发,而是从安全漏洞的视角来学习如何看懂大部分的php web开发代码,从而可以更方便的了解漏洞原理。
基本概念
PHP(全称:PHP:Hypertext Preprocessor,即"PHP:超文本预处理器")是一种通用开源脚本语言,PHP 脚本一般在服务器上执行。
PHP脚本以 <?php
开始,以 ?>
结束。
1 | <?php |
PHP 文件通常包含 HTML 标签和一些 PHP 脚本代码,其中PHP 脚本可以放在文件中的任何位置。
一个简单的PHP文件示例:
1 | <!DOCTYPE html> |
基本语法
一些基本的PHP语法参考:菜鸟PHP 教程
函数对象调用
使用字符串调用函数:直接用字符串变量存储函数名,并调用它。
1 | function sayHello($name) { |
超全局变量
PHP 中的许多预定义变量都是“超全局的”,这意味着它们在一个脚本的全部作用域中都可用。在函数或方法中无需执行 global $variable;
就可以访问它们,超全局变量包括以下几种:
1 | $GLOBALS 用于在 PHP 脚本中的任意位置访问全局变量 |
html混编
通过echo函数,可以将内容在html上进行输出,如果输出的内容是js函数,则会使得JS在PHP语言中运行
例如:
1 |
|
数据库操作
PHP使用mysqli函数函数实现MySQL数据库的增删改查操作
操作数据库连接的php函数:
1 | mysqli_connect() 打开一个到MySQL的新的连接。 |
执行的sql语句:
1 | 增:insert into 表名(列名1, 列名2) value(‘列1值1’, ‘列2值2’); |
例子:
1 | <form id="form1" name="form1" method="post" action=""> |
身份验证(Cookie、Session和Token)
参考:一文讲透Token与Cookie、Session的区别
文件操作
文件上传
超全局变量 $_FILES
$_FILES
是PHP中一个预定义的超全局变量,用于在上传文件时从客户端接收文件,并将其保存到服务器上。它是一个 关联数组,存储了上传文件的相关信息,如文件名、类型、大小、临时存储位置等。
1 | $_FILES[“表单值”][“name”] 获取上传文件原始名称 |
文件上传过滤
-
无过滤机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14$name=$_FILES['f']['name'];
$type=$_FILES['f']['type'];
$size=$_FILES['f']['size'];
$tmp_name=$_FILES['f']['tmp_name'];
$error=$_FILES['f']['error'];
echo $name."<br>";
echo $type."<br>";
echo $size."<br>";
echo $tmp_name."<br>";
echo $error."<br>";
if(move_uploaded_file($tmp_name,'upload/'.$name)){
echo "文件上传成功!";
} -
黑名单过滤机制
1
2
3
4
5
6
7
8
9
10
11// 定义上传文件后缀的黑名单
$black_ext = array('php','asp','jsp','aspx');
$fenge = explode('.',$name);
$exts = end($fenge);
if(in_array($exts,$black_ext)){
echo '非法后缀文件'.$exts;
}else{
move_uploaded_file($tmp_name,'upload/'.$name);
echo '<script>alert("上传成功")</script>';
}- explode 函数分割文件名
$name
并返回数组$fenge
- end() 函数用于返回数组的最后一个元素,即拓展名
- explode 函数分割文件名
-
白名单过滤机制
1
2
3
4
5
6
7
8
9
10
11
12// 定义上传文件后缀的白名单
$allow_ext = array('png','jpg','gif','jpeg');
$fenge = explode('.',$name);
$exts = end($fenge);
if(!in_array($exts,$allow_ext)){
echo '非法后缀文件'.$exts;
}else{
move_uploaded_file($tmp_name,'upload/'.$name);
echo '<script>alert("上传成功")</script>';
} -
文件类型过滤机制
直接根据数据包的上传文件
MIME
类型Content-Type
判断前面几种方法都是根据文件名称后缀进行判断
1
2
3
4
5
6
7
8
9$allow_type = array('image/png', 'image/jpg', 'image/jpeg', 'image/gif');
if (!in_array($type, $allow_type)) {
echo '非法后缀文件';
} else {
move_uploaded_file($tmp_name, 'upload/' . $name);
echo '<script>alert("上传成功")</script>';
}
文件上传的安全性
-
文件上传的存储
1)上传至服务器本身的存储磁盘,和源码放在一起
2)通过云产品OSS存储对象存储文件-
优点:无脚本执行环境,降低安全风险
-
缺点:如果前端源码泄露ak/sk,可以利用OSS浏览器、行云管家等工具进行bucket接管
-
目录浏览
1 | // 功能: |
- 通过
$_GET['path']
获取用户传递的目录路径,如果为空,则默认为当前目录./
。 opendir($dir)
:打开指定目录。readdir($dh)
:循环读取目录中的文件和子目录。is_dir($file)
:- 如果是目录,显示文件夹图标并提供超链接(
?path=$file
)。 - 如果是文件,显示文件图标,但只是普通文本。
- 如果是目录,显示文件夹图标并提供超链接(
显示效果:
文件删除
1 | function del($file){ |
文件删除方法:
unlink()
:删除$_GET['del']
传入的文件路径。- 调用 system shell_exec exec 命令
文件下载
1 | function down($filepath){ |
$_GET['down']
传入文件路径,使用readfile()
读取并发送给浏览器下载。
文件编辑
1、file_get_contents() 读取文件内容
2、fopen() fread() 文件打开读入
文件包含
PHP 文件包含(File Inclusion) 即:允许一个 PHP 文件在执行时引入另一个 PHP 文件的内容,从而重用代码,避免重复编写相同的功能模块。
PHP 提供了四种主要的文件包含方式:
-
include() :如果文件不存在或路径错误,PHP 会发出警告,但脚本会继续执行。
-
require() :如果包含的文件不存在或出错,脚本会终止执行。
-
include_once() 和 require_once() :作用与
include
和require
类似,但它们确保文件不会被重复包含。适用于 避免重复加载相同文件,尤其是包含函数定义或类定义的文件。1
2
3
4
include_once 'header.php'; // 只包含一次
include_once 'header.php'; // 第二次不会再包含
模板引用
自定义模板引用
1 |
|
file_get_contents
函数读取名为new.html
的HTML文件,并将文件的内容存储到$template
变量中。该HTML文件将用作页面的模板,动态替换其中的一些占位符- 通过
mysqli_fetch_row
逐行获取查询结果。此处假设每行的字段按照顺序存储在$row
数组中。 - 使用
str_replace
函数将模板中的占位符替换为从数据库中获取到的数据。
安全问题:
因为后面使用了eval函数将模板字符串作为PHP代码执行,模板字符串基于数据库中提取,如果数据库中的字段存在异常代码,会对其代码执行。
如果在html模板源码中加入
<?php phpinfo();?>
,在执行HTML时并不会显示,因为没有以PHP的形式对其执行。
第三方模版引用Smarty
-
使用
1)创建一个文件夹,命名为smarty-demo
2)下载Smarty对应版本并解压缩到该文件夹中。
3)创建一个PHP文件,命名为index.php,并在文件中添加以下代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 引入 Smarty 类文件
require('smarty-demo/libs/Smarty.class.php');
// 创建 Smarty 实例
$smarty = new Smarty;
// 设置 Smarty 相关属性
$smarty->template_dir = 'smarty-demo/templates/';
$smarty->compile_dir = 'smarty-demo/templates_c/';
$smarty->cache_dir = 'smarty-demo/cache/';
$smarty->config_dir = 'smarty-demo/configs/';
// 赋值变量到模板中
$smarty->assign('title', '欢迎使用 Smarty');
// 显示模板
$smarty->display('index.tpl');4)创建一个名为index.tpl的模板文件,并将以下代码复制到上述点定义文件夹中
1
2
3
4
5
6
7
8
9
10
<html>
<head>
<title>{$title}</title>
</head>
<body>
<h1>{$title}</h1>
<p>这是一个使用 Smarty 的例子。</p>
</body>
</html> -
安全隐患
CVE-2017-1000480参考:https://blog.csdn.net/qq_33020901/article/details/79150260
TP(Thinkphp)框架
基本信息
-
未启用路由情况下的URL访问
在没有启用路由的情况下,TP框架典型的URL访问规则是:
http://serverName/index.php(或者其它应用入口文件)/模块/控制器/操作/[参数名/参数值…]
例如:在application/index/controller下新建cc.php
访问url:
localhost/thinkphp5.1/public/index.php/index/cc/sayhi
1
2
3
4
5
6
7
8
9
10
11
12
namespace app\index\controller;
use think\Controller;
use think\Db;
class cc extends Controller
{
public function sayhi()
{
return 'hi';
}
}
数据库操作
使用TP框架操作数据库,默认受到框架内置过滤保护
-
规矩写法:使用 ThinkPHP 提供的 查询构造器(Query Builder)
- 相对安全,如果TP版本存在漏洞可能被绕过
-
部分安全写法
- 这里使用了
Db::query()
执行 字符串拼接的 SQL 语句。 - 如果
$id
来自用户输入(未过滤),攻击者可以通过id=1 OR 1=1
绕过限制,导致 SQL 注入。
- 这里使用了
-
不安全写法(原生写法):完全没有使用TP语法
-
SQL 注入漏洞:攻击者可以构造恶意 SQL 代码(同上
id=1 OR 1=1
)。不使用 ThinkPHP 的数据库安全机制,直接操作
mysqli_query()
,必须手动处理 SQL 安全(如mysqli_real_escape_string()
)。
-
1 | public function testsql() |
文件上传
1 | public function upload(){ |
框架版本安全
确定目标所使用的Thinkphp版本
- 看报错页面
- 看THINK_VERSION全局变量
- 看url地址构造
然后通过版本找历史漏洞