悠哉杂学 - 暴力破解
type: channel
link: https://uinit.cn/index.php/tag/暴力破解/
items: 登录接口怎么样才算安全？

登录接口怎么样才算安全？
type: item
date: 2021-07-03T13:36:00+08:00
description: [scode type="share"]本文转载于：哒哒哒哒打代码[/scode]前言大家学写程序时，第一行代码都是hello world。但是当你开始学习WEB后台技术时，很多人的第一个功能就是写的登录 （小声：别人我不知道，反正我是）。但是我在和很多工作经验较短的同学面试或沟通的时候，发现很多同学虽然都有在简历上写：负责项目的登录/注册功能模块的开发和设计工作，但是都只是简单的实现了功能逻辑，在安全方面并没有考虑太多。这篇文章主要是和大家聊一聊，在设计一个登录接口时，不仅仅是功能上的实现，在安全方面，我们还需要考虑哪些地方。安全风险暴力破解！只要网站是暴露在公网的，那么很大概率上会被人盯上，尝试爆破这种简单且有效的方式：通过各种方式获得了网站的用户名之后，通过编写程序来遍历所有可能的密码，直至找到正确的密码为止伪代码如下：# 密码字典 password_dict = [] # 登录接口 login_url = '' def attack(username): for password in password_dict: data = {'username': username, 'password': password} content = requests.post(login_url, data).content.decode('utf-8') if 'login success' in content: print('got it! password is : %s' % password) 那么这种情况，我们要怎么防范呢？验证码有聪明的同学就想到了，我可以在它密码错误达到一定次数时，增加验证码校验！比如我们设置，当用户密码错误达到3次之后，则需要用户输入图片验证码才可以继续登录操作：伪代码如下：fail_count = get_from_redis(fail_username) if fail_count >= 3: if captcha is None: return error('需要验证码') check_captcha(captcha) success = do_login(username, password) if not success: set_redis(fail_username, fail_count + 1) [scode type="yellow"]伪代码未考虑并发，实际开发可以考虑加锁。[/scode]这样确实可以过滤掉一些非法的攻击，但是以目前的OCR技术来说的话，普通的图片验证码真的很难做到有效的防止机器人（我们就在这个上面吃过大亏）。当然，我们也可以花钱购买类似于三方公司提供的滑动验证等验证方案，但是也并不是100%的安全，一样可以被破解（惨痛教训）。登录限制那这时候又有同学说了，那我可以直接限制非正常用户的登录操作，当它密码错误达到一定次数时，直接拒绝用户的登录，隔一段时间再恢复。比如我们设置某个账号在登录时错误次数达到10次时，则5分钟内拒绝该账号的所有登录操作。伪代码如下：fail_count = get_from_redis(fail_username) locked = get_from_redis(lock_username) if locked: return error('拒绝登录') if fail_count >= 3: if captcha is None: return error('需要验证码') check_captcha(captcha) success = do_login(username, password) if not success: set_redis(fail_username, fail_count + 1) if fail_count + 1 >= 10: # 失败超过10次，设置锁定标记 set_redis(lock_username, true, 300s) umm，这样确实可以解决用户密码被爆破的问题。但是，这样会带来另一个风险：攻击者虽然不能获取到网站的用户信息，但是它可以让我们网站所有的用户都无法登录！攻击者只需要无限循环遍历所有的用户名（即使没有，随机也行）进行登录，那么这些用户会永远处于锁定状态，导致正常的用户无法登录网站！IP限制那既然直接针对用户名不行的话，我们可以针对IP来处理，直接把攻击者的IP封了不就万事大吉了嘛。我们可以设定某个IP下调用登录接口错误次数达到一定时，则禁止该IP进行登录操作。伪代码如下：ip = request['IP'] fail_count = get_from_redis(fail_ip) if fail_count > 10: return error('拒绝登录') # 其它逻辑 …