1、在Linux主机上准备一套Xampp:模拟攻防2、在VSCode利用Remote Development进行远程调试3、在Lampp的htdos目录下创建security目录,用于编写服务器PHP代码<?php $username = $_POST['username'];$password = $_POST['password'];$vcode = $_POST['vcode'];
if($vcode !== '0000'){ die("vcode-error"); }
$conn = mysqli_connect('127.0.0.1','root','123456','learn') or die("数据库连接不成功!");
mysqli_query($conn,"set names utf8;");
$sql = "select * from user where username ='$username' and password = '$password'";$result = mysqli_query($conn,$sql);
if (mysqli_num_rows($result) == 1){ echo "login-pass<br/>";
echo "<script>location.href='welcome.php'</script>";}else{ echo "<script>location.href='login.html'</script>";}
mysqli_close($conn)?>
四、编写一个登录后才能访问的welcome.php<?php include "common.php";if (!isset($_SESSION['islogin']) or $_SESSION['islogin'] != 'true'){ die ("请登录后再访问此页面</br>");}echo '欢迎来到安全测试平台';?>
在登录界面输入一个单引号[']作为用户名,Burp响应如下:<b>Warning</b>: mysqli_num_rows() expects parameter 1 to be mysqli_result, boolean given in <b>/opt/lampp/htdocs/security/login.php</b>
以上响应出现MySQL报错信息,上述报错信息存在两个漏洞:1、单引号可以成功引起SQL语句报错,说明后台没有专门对单引号进行处理select * from user where username ='$username' and password = '$password' 正常情况:select * from user where username ='root' and password = '$password' 攻击情况:select * from user where username =''' and password = '$password' 攻击Payload: username:x' or userid=1 Post正文: username=x' or+userid=1#'&password=111111&vcode=0000 select * from user where username ='x' or userid=1
/opt/lampp/htdocs/security/login.php(当前代码的绝对路径)
1、welcome.php页面谁都可以访问,没有进行登录判断(中)2、在登录界面输入’作为用户名,报错信息存在login.php的绝对路径,暴露了系统后台的敏感信息(低)3、保存用户信息的数据表中,密码字段是明文保存的,不够安全(中)4、登录界面可以进行SQL注入,进而轻易实现登录(高)import requests
def login_fuzz(): url = 'http://192.168.72.148/security/login.php' data = {'username': "'", 'password': '666666', 'vcode': '0000'} resp = requests.post(url=url, data=data)
if 'Warning' in resp.text: print("本登录功能可能存在SQL注入漏洞,可以尝试") payload_list = ["x' or id=1#", "x' or userid=1#", "x' or userid=2#"] for username in payload_list: data = {'username': username, 'password': '666666', 'vcode': '0000'} resp = requests.post(url=url, data=data) if "login-fail" not in resp.text: print(f'登录成功,payload为:{data}') else: print('通过试探,发现后台界面对单引号不感兴趣;')
if __name__ == '__main__': login_fuzz()
welcome.php页面谁都可以访问,没有进行登录判断,该页面是登录后才能访问,所以在该页面需要进行登录判断,代码修改为:1、在common.php中添加session_start(),让其他页面引入,便于直接使用Session<?phpsession_start();function create_connection(){ // 连接数据库 $conn = mysqli_connect('127.0.0.1','root','123456','learn') or die("数据库连接不成功!"); // 设置数据库的编码格式 mysqli_query($conn,"set names utf8;"); mysqli_set_charset($conn,'utf8'); return $conn;}?>
<?php include "common.php";if (!isset($_SESSION['islogin']) or $_SESSION['islogin'] != 'true'){ die ("请登录后再访问此页面</br>");}echo '欢迎来到安全测试平台';?>
3、在login.php中,登录成功后添加以下代码if (mysqli_num_rows($result) == 1){ echo "login-pass<br/>"; $_SESSION['username'] = $username; $_SESSION['islogin'] = 'true'; echo "<script>location.href='welcome.php'</script>";}
当在用户名输入单引号时,会引起后台报错,一方面说明后台没有对单引号进行转义处理,导致单引号可以被注入到SQL语句中,进而导致SQL语句中存在单独的一个单引号,SQL语句无法有效闭合,发生错误。同时,还将该代码的绝对路径暴露出来,属于敏感信息,应该将其屏蔽,修复代码如下:$result = mysqli_query($conn,$sql) or die("SQL语句执行错误!") ;
$source = 'YikJiang';echo md5($source);
从代码和SQL语句的逻辑层面进行考虑,不能轻易让密码对比失效基于将用户输入的引号(单引号和双引号)进行转义处理的前提,可以使用PHP内置函数addslashes进行强制转义1、该SQL语句在实现登录操作时,存在严重的逻辑问题,用户名和密码的对比不应该放在同一条SQL语句中。2、应先通过用户名查询user表,如果确实找到一条记录(用户名唯一的情况下),找到记录后再进行密码的单独对比。$sql = "select * from user where username ='$username'";$result = mysqli_query($conn,$sql) or die("SQL语句执行错误!") ; if (mysqli_num_rows($result) == 1){ $row = mysqli_fetch_assoc($result); if ($password == $row['password']){ echo "login-pass<br/>"; $_SESSION['username'] = $username; $_SESSION['islogin'] = 'true'; echo "<script>location.href='welcome.php'</script>"; } else{ echo "login-fail"; }
}else{ echo "login-fail<br/>"; echo "<script>location.href='login.html'</script>";}
addslashes函数可以将字符串中的单引号、双引号、反斜杠、NULL值自动添加转义符,从而防止SQL注入中对单引号和双引号的预防。select * from user where username ='$username' and password = '$password'
如果用户输入x' or userid=1#',则SQL语句变成:select * from user where username ='x' or userid=1
如果使用addslashes强制为用户输入添加转义符,则变成:select * from user where username ='x\' or userid=1#\'' and password = '$password'
上述SQL语句的用户名为:x\’ or userid=1#\’function create_connection(){ $conn = mysqli_connect('127.0.0.1','root','123456','learn') or die("数据库连接不成功!"); mysqli_query($conn,"set names utf8;"); mysqli_set_charset($conn,'utf8'); return $conn;}$row = mysqli_fetch_assoc($result);
function create_connection_oop(){ $conn = new mysqli('127.0.0.1','root','123456','learn') or die("数据库连接不成功!"); $conn->set_charset('utf8'); return $conn;}
function test_mysqli_opp(){ $conn = create_connection_oop(); $sql = "select * from user where userid < 6"; $result = $conn->query($sql); echo $result->num_rows."</br>"; $rows = $result->fetch_all(MYSQLI_ASSOC); foreach ($rows as $row){ echo "username:". $row['username'] . ",passwrod" . $row['password'] . "</br>"; }}
预处理的过程,就是先交给SQL数据库进行SQL语句的准备,准备好后再将SQL语句中的参数进行值的替换,引号会进行转义处理,将所有参数变成普通字符串,再进行第二次正式的SQL语句执行。MySQL的预处理既支持面向过程,也支持面向对象方式,但是我们后续直接使用面向对象的方式。$conn = create_connection_oop();$sql = "select userid,username,password,role from user where username= ?";$stmt = $conn->prepare($sql);$stmt->bind_param("s",$username); $stmt->bind_result($userid,$username2,$password2,$role); $stmt->execute(); $stmt->store_result();
在MySQL数据库中运行以下语句,开启临时日志,将日志信息保存到表格mysql数据库的general_log表中。use mysql;set global log_output = 'TABLE';set global general_log = 'ON';show variables like "general_log";
MySQLi的预处理功能同样支持面向过程和面向对象除了MySQLi用于处理数据库外,在PHP中还有最传统的MySQL和PDO两种方式
核心目的是确保是人在使用系统,图片验证码、拖动验证码、拼图验证码、问答验证码、计算验证码等。添加源代码vcode.php,基于PHP绘制基础图片,生成验证码,然后将该验证码保存到Session变量<?phpsession_start();getCode();function getCode($vlen=4 , $width = 80 , $height = 25){ header("content-type:image/png"); $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'; $vcode =substr(str_shuffle($chars),0,$vlen); $_SESSION['vcode'] = $vcode; $image = imagecreate($width,$height); $imageColor = imagecolorallocate($image,100,200,100); $color = imagecolorallocate($image,0,0,0); imagestring($image,5,20,5,$vcode,$color); for($i=0;$i<50;$i++){ imagesetpixel($image,rand(0,$width),rand(0,$height),$color); } imagepng($image); imagedestroy($image);}?>
input[name='vcode']{ width: 200px;}<body style="background-image:url(./image/1.png); background-size:cover;"> <div class="login top-100 font-30">YikJiang</div> <form action="login-3.php" method="post"> <div class="login"> <input type="text" name="username" /> </div> <div class="login"> <input type="password" name="password" /> </div> <div class="login"> <input type="text" name="vcode" /> <img src="vocde.php"/> </div>
<div class="login"> <button type="submit">登录</button> </div> </form> <div class="footer top-100">版权所有©YikJiang 沪ICP备18011293号</div></body></html>
if(strtoupper($_SESSION['vcode']) != strtoupper($vcode) ){ die("vcode-error"); }
验证码一旦生成后,不一定必须保存在Session中,任何可以存储数据的方式均可以。比如数据库、文件、内存中,或者保存在Redis缓存服务器中。比如短信验证码,通常会有一个时间限制(5分钟内有效),最好的解决方案就是使用Redis缓存,并设置Key的过期时间