PHP与MYSQL动态网站开发笔记-3.网站攻击与防御演示及源码
一 基础功能
如图所示,为页面的根界面,未登录时自动进入此页面
1.注册
点击注册,会进入注册页面,如下图所示. 注册成功之后会自动跳转到登录界面
- 用户名不能少于3位
- 密码不能少于5位
- 密码用MD5哈希后存放到数据库中
- 邮箱可以不填
- 注册的数据库中数据如图所示:
2.登录和验证
注册成功之后,点击登录按钮,可以进入登录界面,如图所示
- 每次登录都需要输入验证码
- 验证码存在session中,每次刷新都会更换
- 当密码或者验证码错误时都无法登录
- 登录使用预编译防止SQL注入
- 登录功能中的验证码功能如下:
- 验证码的内容是随机变化的,无论是字符的位置,还是内容
- 验证码中添加了200个干扰点,防止机器识别
- 验证码中添加了3个干扰线
登录成功之后,进入如图所示的主界面
右上角有文件上传,SQL注入,反射型XSS,存储型XSS,CSRF,LOGOUT选项,不同选项具有不同功能
3.文件上传功能
点击Fileload
选项,进入文件上传功能.如图所示
- 文件上传只能上传图片类型,除此之外不允许上传
- 文件大小不能超过2M
- 文件上传成功后会使用
md5(uniqid(microtime(true),true))
重命名,来确保文件名不会重复 - 文件上传到根目录
uploads
文件夹,若该文件夹不存在会先创建此文件夹,再移动到此文件夹内 - 只允许通过HTTP POST类型上传过来的文件
- 上传文件的存储目录如图所示,由文件可看到文件均已重命名
4.数据查询功能
点击SQL INJECTION选项,可以进入数据查询页面,如图所示
- 输入id,查询用户名
- 如果id不存在,则不输出值
5.评论功能
点击XSS REFLECT或者XSS SRORE 选项可以进入评论页面,如图所示
- 输入评论会输出在下方
6.更改密码功能
点击CSRF选项,进入如图所示界面,该界面可以更改密码
7.登出功能
当点击LOGOUT
选项,会清空网站的所有session
- 当用户成功登陆后,创建session用来存储登录成功的信息
- 登出后再访问上述功能页面,本地的session自然与服务端不相等,会弹窗警告未登录,然后跳转登录界面,如下图所示
- 登出功能的代码如下所示:
- 登出就删除所有session
//1.开启 Session
session_start();
//2.判断是否未登录就访问此页面
if($_SESSION['loginsucess']!='wzx'){
echo"<script type='text/javascript'>alert('please log in ');location='login.html';</script>";}
//3.销毁所有 Session,并跳转会最初的根页面
session_unset();
echo"<script>location='index.html';</script>"
二 攻击与防御
在自己编写还未采用安全措施的网站上进行攻击
2.1 跨站点请求伪造攻击
CSRF攻击
- 假设被攻击者已经登录了系统,如图所示,该界面没有任何防护措施,功能是更改密码
-
攻击者构造请求连接
http://www.test.com/unsafeweb/csrf.php?newpass=qwert&confirmpass=qwert
然后通过一些手段欺骗用户点击
-
假设用户在登录状态时点击了此连接,就会导致攻击成功,用户的密码已经被更改,攻击如图所示
防御演示
- 如图所示为防御CSRF的网页,该网页具有如下特征
- 输入验证码才能更改密码
- 验证码是随机变化的,防止攻击者伪造
- 表单提交的时候,会提交本表单的Token值,如果与服务器的不一致,则更改失败
- 攻击者即使构造了伪造请求,但是由于token是基于微秒生成的,所以难以攻击成功
- Token值使用
burpsuite
进行抓包可以得到,如下图所示
- 即使抓到了Token值,但是它是使用
$_SESSION['token'] = md5(microtime());
来变化的 - 每微秒Token都会变化,每次刷新验证码都会变化
- 攻击者很难伪造出相同Token和验证码,确保不会发生CSRF攻击
- 该页面的部分代码如下所示:
//1.将提交过来的token与服务端的比较
if(isset($_REQUEST['token'])){
if($_REQUEST['token']!=$_SESSION['token']){;
echo"<script type='text/javascript'>alert('Token error');location='csrf.php';</script>";}}
//2.判断验证码是否正确
if(isset($_REQUEST['authcode1'])){
if(strtolower($_REQUEST['authcode1'])!=$_SESSION['authcode1']){
echo"<script type='text/javascript'>
alert('verification code error');location='csrf.php';</script>";}}
//3.判断两次输入的密码是否相同
if($newpass==$confirmpass){
if(($newpass!='')&&($confirmpass!='')){
$md_pass=md5($newpass);//使用MD5
//4.将md5后的新密码放入数据库
$sql="update user set password='$md_pass' where id=5";
$result=mysqli_query($link,$sql);
if($result){
echo "<script>alert('Alert sucess');window.location.href='csrf.php'</script>";}}}
防御策略
-
验证请求的
Referer
值- 如果
Referer
是以自己的网站开头的域名,则说明该请求来自网站自己,是合法的 - 如果
Referer
是其他网站域名或空白,就有可能是CSRF攻击,服务器应拒绝该请求 - 但是此方法存在被绕过的可能
- 如果
-
在请求中放入攻击者不能伪造的信息
- 可以在HTTP请求中以参数的形式加入一个随机产生的token,并在服务器验证此token
- 若请求中没有token或token的内容不正确,则认为请求可能是CSRF攻击从而拒绝该请求
2.2 SQL注入攻击
SQL注入(字符型)
- 如图所示页面,存在字符型注入漏洞
2.在用户名输入框中构造1' or 1=1#
,密码随便输入,验证码输入正确,如图所示
- ,点击
Login
,会显示登录成功,即注入成功,如图所示
SQL注入(数字型)
-
如下图所示页面,输入id,用来查询是否存在该用户,如果存在就返回用户名,不存在就返回空,该页面存在数字型注入漏洞
-
在页面中输入
4 and 1=1#
返回用户名 -
输入
4 and 1=2#
不返回结果.由此得出该页面存在数字型注入漏洞 -
输入
4 order by 4#
返回值,如下图所示;输入4 order by 5#
不返回结果,由此得出字段名由4个 -
输入
-1 union select 1,2,3,4#
,只输出2,表明第二个字段明会输出结果 -
输入
-1 union select 1,(select database()),3,4#
,返回了数据库的名字 -
输入
-1 union select 1,(select table_name from information_schema.tables where table_schema='member' limit 1,1),3,4#
查询到表名一个表名为user -
输入
-1 union select 1,(select column_name from information_schema.columns where table_schema='member' and table_name='user' limit 2,1),3,4#
得到一个字段名为password
,同理可以获得字段名id,username,email
-
输入
-1 union select 1,(select password from member.user where id=4),3,4#
得到id为4的用户的密码为b0baee9d279d34fa1dfd71aadb908c3f
,密码使用了MD5,可以使用彩虹表破解,破解后为11111
防御演示
- 在查询语句中使用预编译,可以有效防止SQL注入
- 如下图所示,使用同样的注入语句
- 点击登录按钮后,后弹出错误,注入失败
- 在数字型注入页面同样使用预编译,输入注入相同的内容:
-1 union select 1,(select password from member.user where id=4),3,4#
,但是这次没有返回值
5.使用id来查询的预编译代码如下所示:
//根据输入的id来查询用户名(使用预编译)
$id=$_POST['id'];
if($id!=''){
$sql="select * from user where id =?";
$stmt = $link->prepare($sql);
$stmt->bind_param("i",$id);
$stmt->execute();
$result=$stmt->get_result();
if($result){
$rows = mysqli_fetch_array($result);
echo "<p style='margin: auto;width:320px;color: #bce8f1'>'username:'$rows[1]<br/></p>";
}
}
防御策略
-
过滤危险字符
- 采用正则表达式匹配union,sleep,load_file等关键字,如果匹配到就退出程序
-
使用预编译语句,绑定变量
-
使用存储过程
- 先将SQL语句定义在数据库中
- 尽量避免在存储过程中使用动态SQL语句
- 若无法避免,应使用严格的输入过滤或编码函数来处理用户输入数据
-
检查数据类型
- 检查输入数据的数据类型,很大程度上可以对抗SQL注入
2.3 跨站脚本攻击
反射型XSS
1.在下面界面输入<script>alert("xss")</script>
- 点击comment,会弹窗显示xss
存储型XSS
1.在下面界面输入<script>alert("xss")</script>
2.点击comment,会显示xss,当再刷新页面,还会显示该弹窗
防御演示
- 以存储型XSS为例
- 输入评论后,后端会进行检查,将<,> , ' , " , & ,#等进行Html编码,让它以Html编码的形式存入数据库中
- 当输出到页面的时候,html会自动进行解释,然后还原成为原来的字符
- 不过输出的内容为字符串,不会再触发XSS攻击
- 特殊字符经过HTML编码后存到数据库,如下所示:
- 特殊字符转换代码如下所示:
//1.对传入的字符串str进行Html encode转换
$str_com='';
if ($comment !=null && $comment.trim()!=""){
for ($i = 0, $len=strlen($comment); $i < $len; $i++) {
$arr1 = str_split($comment);
switch($arr1[$i]) {
case '&':
$str_com.="&";
break;
case '<':
$str_com.="<";
break;
case '>':
$str_com.=">";
break;
case '"':
$str_com.=""";
break;
case ' ':
$str_com.=" ";
break;
case '%':
$str_com.="";
break;
default:
$str_com.= $arr1[$i];
}
}
}
XSS的防御策略
- 输入检查
- 在服务端检查用户输入的数据是否包含一些特殊的字符,如
<
,>
,'
,"
,等
- 在服务端检查用户输入的数据是否包含一些特殊的字符,如
- 输出检查
- 除了富文本的输出外,在变量输出到HTML页面时,可以使用编码或转义的方式来防御XSS
- 使用安全的编码函数
- 使用
HTMLEncode
等编码方式
- 使用
2.4 文件上传漏洞
文件上传绕过
-
如下图所示,为文件上传,该页面只允许上传图片类型
-
但是它的源码中只使用了
$_FILE['myFile']['type']
来判断类型
$type=$_FILES['myFile']['type'];
$allowExt=array('jpeg','jpg','png','gif');
if(!in_array($type,$allowExt)){
echo"<script>alert('非法文件类型');window.location.href='do_upload.html'</script>";
}
-
$_FILE['myFile']['type']
是根据客户端发送过来的数据中Content-Type来判断类型,因此可以绕过 -
如下图上传图片类型的文件,而是扩展名为txt的文本文件
-
上传时使用
burpsuite
抓包,可以看到Content-Type的类型为text -
将text类型改为
image/png
-
然后将数据发送,显示上传成功
-
在上传目录uploads内可以发现此txt文件,文件经过了重命名
防御演示
-
如下图上传一个pptx类型的文件
-
使用
burpsuite
抓包,并修改Content-Type数据 -
但是上传失败
-
文件上传检测部分代码如下所示:
$_FILES['myFile']['type']
判断文件类型,只允许图片类型- 设置文件最大不能超过2M
- 移动文件到根目录下的uploads下,没有就先创建再移动
$ext
变量用来判断文件扩展名是否为$allowExt
白名单中的类型,防止$_FILES['myFile']['type']
被绕过is_uploaded_file($tmp_name)
用来只允许post传过来的文件getimagesize($tmp_name)
用来防止其他类型的文件更改后缀名,伪造成图片类型来上传$uniName=md5(uniqid(microtime(true),true)).'.'.$ext
用来重命名,并确保文件名不会重复- 重命名同时可以防止
.php.png
等这种多重扩展名的文件
//1.将文件属性赋值给变量
$filename=$_FILES['myFile']['name'];
$type=$_FILES['myFile']['type'];
$tmp_name=$_FILES['myFile']['tmp_name'];
$size=$_FILES['myFile']['size'];
$error=$_FILES['myFile']['error'];
$allowExt=array('jpeg','jpg','png','gif');//允许上传类型白名单
//2.规定文件最大大小
$maxSize=2097152;//(2M)
if($error==0){
if($size>$maxSize){
echo"<script>alert('上传文件过大');window.location.href='do_upload.html'</script>";
}
//3.检查扩展名,如果扩展名不在规定的白名单内就禁止上传
$ext=pathinfo($filename,PATHINFO_EXTENSION);
if(!in_array($ext,$allowExt)){
echo"<script>alert('非法文件类型');window.location.href='do_upload.html'</script>";
}
//4.检查是否通过http post过来
if(!is_uploaded_file($tmp_name)){
echo"<script>alert('文件不是通过HTTP POST方式上传过来的');
window.location.href='do_upload.html'</script>";
}
//5.检测是否是真的图片类型,不能是.txt改成.jpg等类似此种情况
//getimagesize可以检测是否为图片,如果不是返回false
$flag=true;
if($flag){
if(!getimagesize($tmp_name)){
echo"<script>alert('不是真正的图片类型');window.location.href='do_upload.html'</script>";
}
}
//6.检查是否存在uploads目录,没有文件就创建一个
$path='uploads';
if(!file_exists($path)){
mkdir($path,0777,true);
chmod($path,0777);
}
//7.重命名文件,并确保文件名唯一
$uniName=md5(uniqid(microtime(true),true)).'.'.$ext;//
$destination=$path.'/'.$uniName;
//@ 错误抑制符
//8.移动文件到指定的目录
if(@move_uploaded_file($tmp_name,$destination)){
echo"<script>alert('上传成功');window.location.href='do_upload.html'</script>";
}else{
echo"<script>alert('上传失败');window.location.href='do_upload.html'</script>";
}
}
防御策略
- 文件上传目录设置为不可执行
- 只要web容器无法解析该目录下的文件,即使上传了脚本文件,服务器也不会受到影响
- 判断文件类型
- 判断类型时,结合使用MIME Type,后缀检查等方式
- 文件检查时使用白名单的方式
- 对于图片的处理可以使用压缩函数或者resize函数,用来破坏图片中可能存在的HTML代码
- 使用随机数改写文件名和文件路径
- 文件上传如果要执行代码,需要用户能访问到这个文件
- 使用随机数更改文件路径和文件名,极大增加了攻击成本
- 此外文件重命名可以使
.shell.php.rar
,crossdomain
这种文件所实施的攻击无法成功
- 单独设置文件服务器域名
- 由于浏览器的同源策略,一系列客户端攻击将失效
- 比如上传
crossdomain.xml
,上传包含JavaScript
的XSS
利用等问题将得到解决
三 网站源码
网站源码
解压密码为:Alucardlink