zoukankan      html  css  js  c++  java
  • 不会吧 不会吧 不会吧 还真有人拿硬编码秘钥出来做技术分享?

    事情的起因是公司业务线上采用了开源BI框架Redash进行二次开发做了一个监控系统,Redash是使用的Python的知名web框架Flask,且在github上有16.9k星。在走安全测试流程时,由于之前对paython的代码审计并不了解,所以我在安全测试的时候主要注重接口问题登录、找回密码等业务逻辑漏洞。

    在看代码时,发现找回密码处存在问题

    找回密码流程:

    1. 用户输入账号后点击找回密码(/forgot)
    2. 后台向账户邮箱发送重置密码邮件
    3. 用户访问邮箱中的邮件直接重置密码(/reset/<token>)

    先看下第一步中的代码

     

    再查看用户是否存在且不处于禁用状态,然后调用send_password_reset_email(user)发送重置密码的邮件,跟进

     

    64行中reset_link_for_user(user),顾名思义是根据user对象生成重置密码的连接,继续跟进

     

    很明显是重置密码的连接是根据token和base_url(user.org)拼凑来的,我们走一遍业务流程就可以发现base_url(user.org)实际就是项目地址,我们只需要关注token是如何生成的,跟进invite_token(user)

     

    嗯?这么简单?就这?

    发现是通过URLSafeTimedSerializer和userid来生成的token,userid很简单我们一般就用1就好了,userid为1一般就是管理员了,不行就23456789。

    那么就看下URLSafeTimedSerializer的安全性了,最开始我看到方法名中有serializer是序列化的意思,心想对象序列化后的字符串不都是固定的吗,然后我连续拿了两次同一用户重置密码的token发现是不一样的,回过头发现方法名中有time的字样,心想是根据当前时间戳生成的随机序列化字符串,这样就没法猜解了(不考虑URLSafeTimedSerializer自身安全性问题),于是我想到那么程序又是如何校验token的正确性的呢,可以在上图中看到生成的token并没有存在数据库中

    再去看看Redash倒是是如何校验token的

     

    访问/reset/<token>后调用了render_token_login_page(),跟进后第一行就可以看到调用

    user_id = validate_token(token)直接将token还原成了user_id

    跟进validate_token方法

     

    可以看到原来这里使用dump是序列化成token,loads反序列化成数据(userid)

    那么我们知道这里token其实就是序列化一个userid的字符串,我们能本地序列化一个token后用在目标系统进行重置密码吗

    答案显示是不行的,毕竟github上一万多颗星不可能这点安全意识都没有,我们可以看到12行处生成URLSafeTimedSerializer对象是根据一个SECRET_KEY来生成的

    于是网上百度了下URLSafeTimedSerializer,大概发现了没有正确的SECRET_KEY是无法正确序列化token和反序列化成userid的

    那么这里就没有问题了吗,记住这是一个开源框架啊,SECRET_KEY是写死的,看了下我们项目中的SECRET_KEY

     

    再去看看github中的SECRET_KEY

     

    发现是一样额,这。。。

    复现一下,拿着这个secret_key本地生成token

    if __name__ == "__main__":

        SECRET_KEY = os.environ.get("REDASH_COOKIE_SECRET", "c292a0a3aa32397cdb050e233733900f")

        serializer = URLSafeTimedSerializer(SECRET_KEY)

        key = serializer.dumps("1")

        print(key)

    在fofa上搜了下redash

     

    真不少,找了几个测试了一下,通过我们自己生产的token进行拼凑的url,访问后能正常进去重置密码的如下图,这就是有问题

     

    发现效果不是很理想,虽然又不少成功的,但是大部分都不行,

    报错“Invalid invite link. Please ask for a new one” 说明咋们的userid不对,这么没关系,我们可以直接枚举userid就行

    报错“Your invite link has expired. Please ask for a new one”的说明人家系统把key修改了

     

    我当时还挺好奇的,系统既然没提示修改key为啥有这么多人去修改secret_key呢,安全意识这么强

    后来考虑下一个问题的时候发现原因了,原来这个Redash的这个secret_key应该也是Flask框架session使用到的secret_key

    在我百度搜索URLSafeTimedSerializer这个方法时,网上出现最多的就是flask session的实现方式,所以我也去了解了下flask的session的存储方式

    原来Flask的session默认时客户端session(也可以选择使用redis等数据库的存储方式),这样好像颠覆了我们之前理解的session时服务器端的cookie,简单解释下flask客户端session只所以能行的原因是放在cookie的session,客户端虽然能解码,但是无法篡改,因为有签名,客户端session的具体实现和可能导致的安全问题可以看P牛的文章

    https://www.leavesongs.com/PENETRATION/client-session-security.html

    我们自己写项目的时候如果是使用Flask的这种客户端session,都是必须手动设置secret_key的,除非开发者使用123456这样的弱key,一般是没有问题的,但是在开源软件中这个secret_key强度再高不也是硬编码在配置文件中的吗,这样还有意义吗

    我在github上找了一些flask的流行开源系统

     

    发现确实有一些通用系统是将secret_key写死的,但是也有安装系统时根据配置文件模版自动生成配置文件,此时的secret_key都是随机生成的,这种情况是安全的

    思考:

    1. 对于flask框架的客户端session其实可以考虑下爆破secret_key,不排除确实存在有些开发安全意识不好或不知道flask中secret_key的作用而选择使用低强度的secret_key
    2. 使用了flask的开源软件,可以看下是否是硬编码了secret_key
    3. Flask中还原session,查看敏感信息,例如短信图片验证码、验证使用的token
    4. 审计开软软件的时候需要开始注意硬编码密钥的问题了,通过秘钥逆向去挖掘漏洞,关键在于了解密钥的用途
    5. CBC字节翻转攻击?

    这次的key比较特殊,由于是flask的session使用到的key,熟悉flask的开发都知道这个key需要去改,所以影响范围不大

  • 相关阅读:
    16. 3Sum Closest
    17. Letter Combinations of a Phone Number
    20. Valid Parentheses
    77. Combinations
    80. Remove Duplicates from Sorted Array II
    82. Remove Duplicates from Sorted List II
    88. Merge Sorted Array
    257. Binary Tree Paths
    225. Implement Stack using Queues
    113. Path Sum II
  • 原文地址:https://www.cnblogs.com/jinqi520/p/13600757.html
Copyright © 2011-2022 走看看