session安全&&CBC字符反转攻击&&hash拓展攻击

session安全

p神写的:
在传统PHP开发中,$_SESSION变量的内容默认会被保存在服务端的一个文件中,通过一个叫“PHPSESSID”的Cookie来区分用户。这类session是“服务端session”,用户看到的只是session的名称(一个随机字符串),其内容保存在服务端。

然而,并不是所有语言都有默认的session存储机制,也不是任何情况下我们都可以向服务器写入文件。所以,很多Web框架都会另辟蹊径,比如Django默认将session存储在数据库中,而对于flask这里并不包含数据库操作的框架,就只能将session存储在cookie中。

所以这可能造成session的篡改,在 flask 中使用 session 很简单,只要使用 from flask import session 导入这个变量,在代码中就能直接通过读写它和 session 交互。

    val = self.get_signing_serializer(app).dumps(dict(session))
    response.set_cookie(app.session_cookie_name, val,
                        expires=expires, httponly=httponly,
                        domain=domain, path=path, secure=secure)

倒数第二步,用它的dumps方法将类型为字典的session对象序列化成字符串 最后一步用response.set_cookie将最后的内容保存在cookie中。 详细参数:

key: cookie的name
value:cookie的值
expires:具体过期时间
path:cookie的访问路径,只有在某个路径下访问
domain:域名,只有在某个域名下访问
secure:安全
httpoly:如果为True那么js就不能获取cookie   

伪造代码:

# coding:utf8

import uuid
from flask import Flask, request, make_response, session,render_template, url_for, redirect, render_template_string

app = Flask(__name__)
app.config['SECRET_KEY']=str(uuid.uuid4())

@app.route('/')
def index():
    app.logger.info(request.cookies)

    try:
        username=session['username']
        return render_template("index.html",username=username)
    except Exception,e:

        return """<form action="%s" method='post'>
            <input type="text" name="username" required>
            <input type="password" name="password" required>
            <input type="submit" value="登录">
            </form>""" %url_for("login")


@app.route("/login/", methods=['POST'])
def login():
    username = request.form.get("username")
    password = request.form.get("password")
    app.logger.info(username)
    if username.strip():
        if username=="admin" and password!=str(uuid.uuid4()):
            return "login failed"
        app.logger.info(url_for('index'))
        resp = make_response(redirect(url_for("index")))
        session['username']=username
        return resp
    else:
        return "login failed"

@app.errorhandler(404)
def page_not_found(e):
    template='''
        {%% block body %%}
        <div class="center-content error">
        <h1>Oops! That page doesn't exist.</h1>
        <h3>%s</h3>
        </div>
        {%% endblock %%}
    '''%(request.url)
    return render_template_string(template),404

@app.route("/logout")
def logout():
    resp = make_response(redirect(url_for("index")))
    session.pop('username')
    return resp

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=9999, debug=True)

知道秘钥之后我们利用工具就可以得到解密后的数据,也可以构造加密的数据。

C:UsersDellDesktop工具集各种编码解密flask-session-cookie-manager-master>python flask_session_cookie_manager2.py encode -s "a8bc2e85-d7a8-32f0-a56d-a86b19b42bb6" -t "{u'user': u'test' }"
eyJ1c2VyIjoidGVzdCJ9.XuRKWA.fs0nWIciQE_YTQ1hduVsrYWCoUg

利用hackerbar进行session的替换。

关于序列化session的主要过程:

json.dumps 将对象转换成json字符串,作为数据
如果数据压缩后长度更短,则用zlib库进行压缩
将数据用base64编码
通过hmac算法计算数据的签名,将签名附在数据后,用“.”分割  

目标网站通过session来储存随机token并认证用户是否真的在邮箱收到了这个token。但因为flask的session是存储在cookie中且仅签名而未加密,所以我们就可以直接读取这个token了。

参考文章:https://www.leavesongs.com/PENETRATION/client-session-security.html

签名使用hash函数而非hmac函数,导致利用hash长度扩展攻击来伪造session
任意文件读取导致密钥泄露,进一步造成身份伪造漏洞或反序列化漏洞( http://www.loner.fm/drops/#!/drops/227.Codeigniter%20%E5%88%A9%E7%94%A8%E5%8A%A0%E5%AF%86Key%EF%BC%88%E5%AF%86%E9%92%A5%EF%BC%89%E7%9A%84%E5%AF%B9%E8%B1%A1%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E )
如果客户端session仅加密未签名,利用CBC字节翻转攻击,我们可以修改加密session中某部分数据,来达到身份伪造的目的

CBC字节翻转攻击

cbc是AES加密的cbc模式 即密码分组链模式: 先将铭文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,在于密钥进行加密。
明文以16个字节为一组进行分组,给出初始化向量iv于明文进行异或然后利用密钥key在进行加密得到密文a,密文a就相当于下一段明文的初始化向量,以此类推。
解密的具体步骤:

首先从最终的密文中提取出IV (IV为加密时指定的X位)
将密文分组
使用密钥对第一组密文解密得到密文A,然后用IV进行异或得到第一组明文
使用密钥对第二组密文解密得到密文B,然后用A与B进行异或得到第二组明文

怎么加密的,就怎么解密,我们可以控制解密时所生成的明文,从而产生攻击,因为在无参数rce的时候,p神就曾经介绍过一种弄利用异或来生成一句话木马的操作,我想这是有异曲同工之妙的,详细的可以去了解一下异或生成的过程,就是二进制的变换罢了,解密的过程就是利用两段加密的去推到出明文,因为你明文是跟一段密文的一起异或生成另一段密文的,即C与B 异或得到A, 即 C ^ B = A。

A=ciphertext(N-1),B=plaintext(N),C为第N块待异或且经过解密的字符,C'为我们经过翻转要得到的明文。
明文原文为C,要更改为C';上一组密文原文为A,要更改为A';密文解密后和上一组密文异或前的中间数据为M 可以得到:

A = B ^ C

C = A ^ B

A ^ B ^ C = 0

A ^ B ^ C ^ C' = C'

根据关系式可以得到 A' = A ^ C ^ C'  

这就是我们进行CBC字节翻转的公式

此时我们已经将我们想要构造出的明文能够通过对上一段的密文的修改构造出来了,但是我们这前一段密文修改之后,很明显,之前的一段密文发生了改变,那么前一段明文会被全部变动掉,我们需要新生成一段密文,让他能够照常的经过解密生成原来的明文,这就是我们要干的第二件事。

这样我们以第一段为例,能够让第一段保持原来的密文,同时保证第二段能正确翻转,我们的可控汴梁旧事一概是直接设的vi明文,队名问津新修改就能完全的进行改变。

公式: C' = C ⊕ A ⊕ A'

C为原文明文,C'为要新生成的明文,A为改变后乱码的明文,A'为原来最初的明文。
参考链接:https://www.cnblogs.com/s1ye/p/9021202.html
先嫖个脚本:

#-*- coding:utf8 -*-
import base64
import urllib
# a:2:{s:8:"userna
# me";s:5:"admiN";
# s:8:"password";s
# :6:"123456";}

def Module1():
    ciphertext = raw_input("Please input the first round cipher:
")
    cipher = base64.b64decode(urllib.unquote(ciphertext))
    new_cipher = cipher[:13] + chr(ord(cipher[13]) ^ ord('N') ^ ord('n')) + cipher[14:]
    print urllib.unquote(base64.b64encode(new_cipher))

def Module2():
    errorcipher = base64.b64decode(urllib.unquote(raw_input('Please input errorcipher: 
')))
    ivtext = raw_input("Please input iv:
")
    iv = base64.b64decode(urllib.unquote(ivtext))
    cleartext = 'a:2:{s:8:"userna'
    newiv = ''
    for i in range(16):
        newiv += chr(ord(iv[i]) ^ ord(errorcipher[i]) ^ ord(cleartext[i]))
    print urllib.unquote(base64.b64encode(newiv))

option = raw_input("Please input option [1 or 2]:")
if option == '1':
    Module1()
elif option == '2':
    Module2()
else:
    pass

hash拓展攻击

关于md5:

1   MD5加密过程中512比特(64字节)为一组,属于分组加密,而且在运算的过程中,将512比特分为32bit*16块,分块运算   
2   每一块加密得到的密文作为下一次加密的初始向量IV
3   MD5的填充:加密的字符串进行填充(比特第一位为1其余比特为0),使之(二进制)补到448模512同余,即长度为512的倍数减64,最后的64位在补充为原来字符串的长度,这样刚好补满512位的倍数,如果当前明文正好是512bit倍数则再加上一个512bit的一组。  

先看一下补足的运算:

0x23478a696e**800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000**2800000000000000  

这一段加粗的就是用来补足的,即二进制补位100000.... 第三部分就是全部要加密明文的长度,并不包含他自动填充的长度。

进行完补足之后,如果我们还有第二段的数据,那么接下来要进行的操作就是先将第一段构成为初始的IV为第二部分的操作做准备,因为第一部分算出来的ABCD是会被拿来算第二部分的值。:

A = 0x67542301;
B = 0xEFCDAB89;
C = 0x98BADCFE;
D = 0x10325476;

原理是:原理就是有ABCD四个初始渐变量,还有上面的信息,每次都拿512bit的信息去计算,然后多次计算以后变量不停被覆盖,最后计算出的变量接在一起,再高低位互换得到最终的加密结果。

此时我们在第一段后面进行手动的补足,然后添加我们新一段的数据,这样才可以添加第二段。

我们在第二段中填入自己想填入的字符进行操作。

关于md5存储的运算:

假设我们存储了有一块:0x12233445那么MD5运算时候存储的顺序是0x54433221这个样子的,另外一个字节的长度是8bit。

那么关于Hash长度扩展攻击有是如何操作的: 实验吧的一道题目,用来理解一下

在这里我们知道了第一部分的hex的值,知道了所有具体的长度,我们完全可以进行填充,,利用hashpump或者是extender计算出拓展的值,放入到getmein的cookie变量提交,进行对比从而绕过题目上的两个限制。

参考链接:
https://www.cnblogs.com/p0pl4r/p/10317593.html

https://blog.csdn.net/qq_35078631/article/details/70941204

原文地址:https://www.cnblogs.com/ophxc/p/13124248.html