PHP应用

留言板功能

mysqli函数&增删改查

PHP操作数据库函数

常用:
mysqli_connect() 打开一个到MySQL的新的连接。
mysqli_select_db() 更改连接的默认数据库。
mysqli_query() 执行某个针对数据库的查询。
mysqli_fetch_row() 从结果集中取得一行,并作为枚举数组返回。
mysqli_close() 关闭先前打开的数据库连接。
查:select * from 表名 where 列名=‘条件’;
增:insert into 表名(列名1, 列名2) value(‘列11’, ‘列22’);
删:delete from 表名 where 列名 = ‘条件’;
改:update 表名 set 列名 = 数据 where 列名 = ‘条件’;

mysqli.connect函数连接数据库

构建数据库连接,以及执行数据库插入操作

  • 发现可以插入数据,但是由于用户名和内容可以不进行添加加入数据库
  • 加一层判空,如果<font style="background-color:rgba(255, 255, 255, 0);">!empty($u)</font>用户名为空则不写入数据即可

<!-- 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 = 'dome01';

// 使用 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);//con表示链接该数据库为con(存在多个数据库时防止混淆)
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混编&超全局变量

html混编:使HTML(JS)在PHP语言中运行

超全局变量

https://www.w3school.com.cn/php/php_superglobals.asp

https://www.php.net/manual/zh/language.variables.superglobals.php

<?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>";**
}

创建gbook-admin.php文件和admin目录,方便执行删除等后台功能

发现重复的代码太多,所以将经常用到的数据库链接操作生成config.php文件,方便管理。修改后正常访问增加,查询。

删除功能创建成功

<?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>";**
}

发现问题,可以将代码加一层判断当触发的时候,再进行删除操作

// 获取要删除的留言用户名
$delstr = @$_GET['del'];

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

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

第三方插件引用-js传参&函数对象调用

htmlCopy code
<script src='../xxx.js'></script>

这是一个在 HTML 中引用外部 JavaScript 文件的标签。src 属性指定了 JavaScript 文件的路径,相对于当前 HTML 文件的位置。在这里,../xxx.js 表示 JavaScript 文件位于上一级目录(../)并且文件名为 xxx.js。

**<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>

这里的ueditor需要单独下载文件,放入目录中

完整代码

<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');

<?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>";
}*/
<?php
/*配置全局文件。如果有用到数据库链接的时候直接包含此文件即可*/
$dbip='localhost';
$dbuser='root';
$dbpass='root';
$dbname='dome01';
$con=mysqli_connect($dbip,$dbuser,$dbpass,$dbname);

后台验证功能

生成cookie的原理图过程:见图

1、客户端向服务器发送HTTP请求。

2、服务器检查请求头中是否包含cookie信息。

3、如果请求头中包含cookie信息,则服务器使用该cookie来识别客户端,否则服务器将生成一个新的cookie。

4、服务器在响应头中设置cookie信息并将其发送回客户端。

5、客户端接收响应并将cookie保存在本地。

6、当客户端发送下一次HTTP请求时,它会将cookie信息附加到请求头中。

7、服务器收到请求并检查cookie的有效性。

8、如果cookie有效,则服务器响应请求。否则,服务器可能会要求客户端重新登录。

php函数

setcookie(): 设置一个cookie并发送到客户端浏览器。

unset(): 用于删除指定的cookie。

Cookie安全性

信息泄露:如果Cookie中包含敏感信息(如用户ID、密码等),攻击者可以通过窃取Cookie来获取这些敏感数据。为了防止信息泄露,应确保敏感信息在传输和存储过程中进行加密,并使用安全的传输协议(如HTTPS)来保护Cookie的传输过程。

跨站点脚本攻击(XSS):攻击者可以通过在Web应用程序中注入恶意脚本来获取用户的Cookie。为了防止XSS攻击,开发人员应对用户输入进行正确的验证和过滤,并对输出进行适当的转义,以防止恶意脚本的注入。

跨站点请求伪造(CSRF):攻击者可以通过欺骗用户访问恶意网站来利用用户的身份进行非法操作。为了防止CSRF攻击,可以使用随机生成的令牌(CSRF令牌)来验证每个请求的合法性,并确保令牌在每个请求中都是唯一且难以预测的。

会话劫持:攻击者可以通过窃取用户的会话Cookie来冒充用户身份。为了防止会话劫持,可以通过使用安全标志(如”Secure”和”HttpOnly”)来限制Cookie的使用范围,并在会话身份验证上使用额外的保护措施,如双因素身份验证。

不安全的存储:如果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函数

session_start(): 启动会话,用于开始或恢复一个已经存在的会话。

$_SESSION: 用于存储和访问当前会话中的所有变量。

session_destroy(): 销毁当前会话中的所有数据。

session_unset(): 释放当前会话中的所有变量。

Session存储路径:PHP.INI中session.save_path设置路径:

Cookie和Session的区别

Cookie和Session都是用来在Web应用程序中跟踪用户状态的机制

1、存储位置不同:

Cookie是存储在客户端(浏览器)上的,而Session是存储在服务器端的。

2、安全性不同:

Cookie存储在客户端上,可能会被黑客利用窃取信息,而Session存储在服务器上,更加安全。

3、存储容量不同:

Cookie的存储容量有限,一般为4KB,而Session的存储容量理论上没有限制,取决于服务器的硬件和配置。

4、生命周期不同:

Cookie可以设置过期时间,即便关闭浏览器或者重新打开电脑,Cookie仍然存在,直到过期或者被删除。而Session一般默认在浏览器关闭后就会过期。

5、访问方式不同:

Cookie可以通过JavaScript访问,而Session只能在服务器端进行访问。

6、使用场景不同:

Cookie一般用于存储小型的数据,如用户的用户名和密码等信息。而Session一般用于存储大型的数据,如购物车、登录状态等信息。

总之,Cookie和Session都有各自的优缺点,选择使用哪一种方式,取决于具体的应用场景和需求。一般来说,如果需要存储敏感信息或者数据较大,建议使用Session;如果只需要存储少量的数据,并且需要在客户端进行访问,可以选择使用Cookie。

唯一性判断-Token

在Web应用程序中,使用token和不使用token的主要差异在于身份验证和安全性。

1.身份验证:采用token机制的Web应用程序,用户在登录成功后会收到一个token,这个token可以在每次请求时发送给服务器进行身份验证。而不采用token机制的Web应用程序,一般会使用session机制来保存用户登录状态,服务器会在用户登录成功后创建一个session,之后的每个请求都需要在HTTP头中附带这个session ID,以便服务器能够验证用户身份。

2、安全性:采用token机制的Web应用程序,在服务器上不会存储用户的登录状态,只需要存储token即可。因此,即使token被盗取,黑客也无法获得用户的密码或者其他敏感信息。而不采用token机制的Web应用程序,一般会在服务器上存储用户的登录状态,因此如果服务器被黑客攻击,黑客可能会获得用户的敏感信息。

3、跨域访问:采用token机制的Web应用程序,在跨域访问时,可以使用HTTP头中的Authorization字段来传递token信息,方便实现跨域访问。而不采用token机制的Web应用程序,在跨域访问时,需使用cookie或session来传递用户身份信息,比较麻烦。

总之,采用token机制可以提高Web应用程序的安全性,并且方便实现跨域访问。不过,使用token机制也需要开发者自己来实现身份验证和token的生成和验证,相对来说比较复杂。而不采用token机制,**使用session机制则相对简单,但是安全性相对较低。**因此,具体采用哪种机制,需要根据实际情况进行权衡和选择。

使用token之后,无法进行抓包爆破,即使抓包爆破有正确账户密码,但是抓包的token是写死的,而token每次是随机生成的,经token检验也会登录失败;可以正常使用网站功能登录,在网站登录时每次登录的token会与服务器端同步

完整代码

<!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>"; // 输出登录失败的提示
}
}

<?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>
<?php
setcookie('username', '', time() - 3600, '/');
setcookie('password', '', time() - 3600, '/');
// 跳转到登录页面
header('Location: admin-c.php');
exit;
?>

session

<!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>"; // 输出登录失败的提示
}
}

//登入界面文件,使用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>

<?php
//登出文件,使用session验证
// 开始会话
session_start();

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

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

token

<?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>

<?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 '登录失败!';
}
}
?>

文件管理

上传

$_FILES:PHP中一个预定义的超全局变量,用于在上传文件时从客户端接收文件,并将其保存到服务器上。它是一个包含上传文件信息的数组,包括文件名、类型、大小、临时文件名等信息。

$_FILES[“表单值”][“name”] 获取上传文件原始名称

$_FILES[“表单值”][“type”] 获取上传文件MIME类型

$_FILES[“表单值”][“size”] 获取上传文件字节单位大小

$_FILES[“表单值”][“tmp_name”] 获取上传的临时副本文件名

$_FILES[“表单值”][“error”] 获取上传时发生的错误代码

move_uploaded_file() 将上传的文件移动到指定位置的函数

上传的两种架构:

1.上传到服务器本身存储磁盘

2.上传到云存储OSS(OSS中的文件无法被解析)

文件后缀无过滤

创建upload.php和upload.html(upload.html用chatgpt实现实现)

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

<!-- 提交表单到 "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>

upload.php的实现并在项目创建新的目录,upload目录来存放上传的内容

<?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 "文件上传成功!";
}**
?>

文件后缀黑名单过滤

使用 explode 函数通过点号分割文件名,获取文件后缀

使用 end 函数获取数组中的最后一个元素,即文件后缀

检查文件后缀是否在黑名单中

如果文件后缀在黑名单中,输出非法后缀文件信息

如果文件后缀不在黑名单中,移动上传的文件到指定目录

输出上传成功的提示信息

// 上传文件后缀过滤,使用黑名单机制
$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>";
}
?>

文件后缀白名单过滤

//白名单过滤机制
**// 允许上传的文件后缀白名单
$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>";
}

文件MIME类型过滤

//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>';
}

显示

1.打开目录读取文件列表

2.递归循环读取文件列表

3.判断是文件还是文件夹

4.PHP.INI目录访问控制

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

opendir() readdir() 区别

opendir() 函数:

opendir() 用于打开一个目录句柄(directory handle)。

接受一个参数,即要打开的目录的路径。

返回一个目录句柄,该句柄可以用于后续对目录的操作。

通常与 closedir() 配合使用,用于关闭目录句柄。

phpCopy code
$dir_handle = opendir('/path/to/directory');

readdir() 函数:

readdir() 用于读取目录句柄中的条目。

接受一个参数,即之前使用 opendir() 打开的目录句柄。

在每次调用时,返回目录中的下一个文件或目录的名称。

当没有更多的文件或目录时,返回 false。

phpCopy code
$file = readdir($dir_handle);

使用 opendir() 打开目录,然后使用 readdir() 循环读取目录中的文件和目录,最后使用 closedir() 关闭目录句柄。

phpCopy code
$dir_handle = opendir('/path/to/directory');

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

总体代码实现

<?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目录访问控制

目的是避免目录穿越,限制目录访问范围

php的配置文件

open_basedir

规定了目录访问的根目录,我们不能跳出该目录查看其它文件内容

php配置文件前面有;则为被注释(不生效)

包含

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

如果使用文件包含函数,并且函数的参数为用户可控

比如

include($_GET['page'])

用户可以通过手动修改page的参数来进行任意文件包含

编辑

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

删除

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

下载

修改HTTP头实现文件读取解析下载:
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename="");
header("Content-Length: " . filesize($file));
readfile($file);

新闻列表&模版引用

自写模板

模板:由于牵涉到美观问题,将php代码中插入html代码以达到更美观,而当我们使用模板时,对于模板相应的位置进行替换即可

模板要被调用运行,有eval函数,可能存在RCE

第三方模板安全性较高,查看网上是否爆出相关版本漏洞

插件能做到哪些功能,需要用到哪些函数,这些函数会引发什么安全漏洞

**// 从文件中读取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);

如果在数据库中任何地方添加<font style="background-color:rgba(255, 255, 255, 0);"><?php phpinfo();?></font> ,在调用数据库内容的时候会自动显示

如果在html模板源码中加入<font style="background-color:rgba(255, 255, 255, 0);"><?php phpinfo();?></font> ,在执行HTML并不会显示,但通过php解析调用,则依然会展示有关内容

php代码解析调用,存在RCE

Smarty模板

smarty模板使用

使用:

1、创建一个文件夹,命名为smarty

2、下载Smarty对应版本并解压缩到该文件夹中。

3、创建一个PHP文件,命名为index.php,并在文件中添加以下代码:

<?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' 模板文件进行显示
?>

路径要进行更改,index.tpl要自行创建

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

<!DOCTYPE html>
<html>
<head>
<title>{$title}</title>
</head>
<body>
<h1>{$title}</h1>
<p>这是一个使用 Smarty 的例子。</p>
</body>
</html>

在网上查找相关版本漏洞

框架ThinkPHP(TP)

MVC:model view controller(模板、视图、控制器)

ThinkPHP官方手册

路由访问

路由访问,要规定路由关系

两种访问方式

数据库操作

使用TP框架操作数据库,默认是受到框架内置过滤保护,语句写法会改变

1.连接数据库找到数据库配置文件(database.php),并打开调试模式

2.引用数据的的类use think\Db;,使用数据库的查询语句

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);
}

数据库配置文件

文件上传

网站根目录创建upload.html

写入表单代码,并修改action的值

创建php文件并写入控制器代码

前端页面渲染view

在例如index(需要渲染的文件当前并行目录下)→view→index→index.html等

http://192.168.137.1:84/index.php/Index/index/index 输入对应的路由访问即可看到以及渲染的页面

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

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');**
}

安全

think还提供了原生写法

如果使用原生写法,仍然存在安全漏洞

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