• 不会吧 不会吧 不会吧 还真有人拿硬编码秘钥出来做技术分享?


    事情的起因是公司业务线上采用了开源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需要去改,所以影响范围不大

  • 相关阅读:
    react脚手架和JSX
    promise
    防抖和节流
    call/apply/bind 用法
    js this指向
    vue单页面应用刷新网页后vuex的state数据丢失的解决方案
    Echarts基础
    继承
    原型链
    vue项目中使用生成动态二维码
  • 原文地址:https://www.cnblogs.com/jinqi520/p/13600757.html
Copyright © 2020-2023  润新知