【问题标题】:Determine Which Part(s) of WHERE Statement Failed确定 WHERE 语句的哪些部分失败
【发布时间】:2010-11-27 14:03:22
【问题描述】:

假设我有一个这样的 SQL 语句来检查用户登录:

SELECT * FROM users 
WHERE username='test@example.com', password='abc123', expire_date>=NOW();

SQL 中有没有一种方法可以明确确定哪些 WHERE 条件失败,而不必将每个条件分成自己的查询并单独测试?

在这个具体的例子中,它允许开发者准确地告诉用户他们尝试登录失败的原因。

出于我的目的,我使用的是 PHP/MySQL。

【问题讨论】:

  • expire_date 的时间部分可能小于 NOW() 中返回的时间,导致查询无法返回您期望的结果。
  • 谢谢,我意识到,尽管名称有点误导,但该字段确实存储了时间。
  • 你会把失败的原因传递给用户吗?这可能会让黑客更容易获得访问权限。

标签: php sql mysql conditional-statements where


【解决方案1】:

好吧,您可以做的一件事是更改您的查询,使其仅匹配用户名。然后在代码中检查密码和到期日期,返回相应的错误。

另外,我希望您的示例查询是一种简化;当然,您应该对密码进行加盐/加密/散列处理,并且应该包括一些限制在特定时间范围内失败尝试次数的内容,等等...

就您的实际问题(与您正在寻找的结果相反)而言,没有办法从 where 子句中获取该信息。你可以做的最接近的是:

SELECT *,
    CASE WHEN Password = 'asdf' THEN 1 ELSE 0 END AS IsPasswordMatch,
    CASE WHEN Expiration >= NOW() THEN 1 ELSE 0 END AS IsActiveAccount
FROM Users
WHERE Username = 'user'

【讨论】:

  • 由于我们当前的代码结构,仅 SQL 的解决方案将是理想的。
  • 这是推荐的方法。根据名称获取用户行(如果没有用户失败)然后比较密码哈希(如果失败密码错误)最后存储行 id(用户 id)和会话中的其他任何内容并登录。
【解决方案2】:

不,where 子句作为一个块应用,并且使用了各种技术,因此不必扫描所有行。另外,你怎么知道哪一行是你想要的?

此外,您可能不想过多地告诉用户登录尝试失败的原因。说得太多会导致账户挖掘和密码攻击等漏洞。

编辑如果您确实想向用户显示此内容,请将您的逻辑拆分为不同的部分:

  1. 验证身份
    行动:从数据库中获取相应的用户行
    结果:
    • 如果不存在这样的行 => 无效帐户
    • 如果返回行,则继续执行步骤 2。
  2. 验证凭据
    行动:检查存储的凭据(密码、密码哈希或加密密码)与提供的密码相同,处理方式与凭据存储方式相同。
    结果:
    • 不匹配 => 密码/凭据无效
    • 匹配 => 登录尝试成功
  3. 登录用户
    行动:将数据添加到会话等。

【讨论】:

  • 在我的特殊情况下,我们确实想告诉用户这些信息。我们已经考虑到安全风险。
  • 这个(和比尔的)仍然是最好的答案。仅仅因为您的查询返回结果并不意味着您实际上已将所有这些信息提供给用户。
  • 好吧,我同意。不必公开身份验证失败的原因。但开发人员可能仍想了解给定用户帐户的重复密码失败,例如建立锁定。
【解决方案3】:

你可能只需要用'AND'分隔where子句的各个部分

SELECT * FROM users 
WHERE username='test@example.com'
   And password='abc123'
   And expire_date>=NOW();

【讨论】:

    【解决方案4】:

    在 MySQL 中,您可以将布尔表达式放在选择列表中。布尔表达式为真时为整数 1,为假时为整数 0。

    SELECT password = 'abc123' AS is_authenticated,
           expire_date >= NOW() AS is_not_expired
    FROM users
    WHERE username='test@example.com';
    

    注意:如果您需要编写适用于其他品牌 RDBMS 的查询,请记住,布尔表达式的这种使用是非标准的。使用其他人发布的CASE 语法。

    PS:这与您的问题相切,但我敦促您不要以明文形式存储密码。存储加盐密码的哈希摘要。见How does password salt help against a rainbow table attack?

    【讨论】:

    • 感谢您对我的安全的关注,这实际上只是一个示例查询,而不是真实的!我同意哈希是必须的!
    • 好的,很酷,我想也许您的查询是从真实的事情中简化的,但我想确保提到散列以防万一。 :)
    【解决方案5】:

    这是我想出的:

    SELECT
      IF(mem_username='test@example.com','true','Error: Bad Username') AS mem_username,
      IF(mem_password ='abc123','true','Error: Bad Password') AS mem_password'
    FROM MEMBERS
    WHERE mem_username='test@example.com' AND mem_password='abc123'
    

    这样我可以在代码中检测错误消息,然后根据需要显示它们。

    注意:对于所有引用示例代码的安全问题的人,感谢您的关注。但是,这不是真正的生产代码,这只是一个简单的示例来演示我的问题。很明显,您不应该以明文形式存储密码,也不应该向用户详细说明登录失败的原因。

    【讨论】:

    • 不要让黑客知道他们输入了用户名和密码错误来帮助他们!另请参阅其他人关于不在数据库中明文存储密码的回复!!!
    • 这个 SQL 真的很 hinky;首先,将字段名称重复为别名是不好的计划;其次,它永远不会给你一条错误消息——WHERE 子句只会返回使两个 IF 子句都为真的记录;第三,“坏用户名”测试有点奇怪;您真的想告诉某人“您的密码正确,但使用的是其他用户”吗?我建议 SELECT IF(mem_password_hash='c0e1732fa', true, false) FROM Members WHERE mem_username='test@example.com' 你已经知道用户名了;如果返回 true,则通过,false 表示密码错误,NULL 表示用户名错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-15
    • 1970-01-01
    • 2010-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多