hash长度扩展攻击以及hashpump工具安装


hash长度扩展攻击


长度扩展攻击(length extension attack),是指针对某些允许包含额外信息的加密散列函数的攻击手段。对于满足以下条件的散列函数,都可以作为攻击对象:

  • ① 加密前将待加密的明文按一定规则填充到固定长度(例如512或1024比特)的倍数;

  • ② 按照该固定长度,将明文分块加密,并用前一个块的加密结果,作为下一块加密的初始向量(Initial Vector)。

满足上述要求的散列函数称为Merkle–Damgård散列函数(Merkle–Damgård hash function),下列散列函数都属于Merkle–Damgård散列函数:

MD4
MD5
RIPEMD-160
SHA-0
SHA-1
SHA-256
SHA-512
WHIRLPOOL

对于H(salt+data)形式的加密,在以下条件满足的情况下,攻击者可以通过该方法获取H(salt+一定规则构造的data):

  • ① 知道密文的加密算法且该算法满足Merkle–Damgård散列函数特征;

  • ② 不知道salt,但知道salt的长度,并可控制data的值;

  • ③ 可以得到一个H(salt+data)的值。

简而言之,你要满足的条件就是:

知道salt的长度
知道salt+data的Hash值,并且data是你所知道的明文
这样你就能填充其他字符来得到一个和该salt+data计算出的hash值相同的一串字符串,整个过程你是不知道秘钥salt的。

HashPump工具安装指令


git clone https://github.com/bwall/HashPump
apt-get install g++ libssl-dev
cd HashPump
make
make install

0x01 CTF题库-让我进去


做题链接:http://ctf5.shiyanbar.com/web/kzhan.php


随便输入数据用bp抓包发送到repeater,发现cookie有个source=0,一般上这样的都可以更改,我们尝试将source=1出现源码

$flag = "XXXXXXXXXXXXXXXXXXXXXXX";
$secret = "XXXXXXXXXXXXXXX"; // This secret is 15 characters long for security!

$username = $_POST["username"];
$password = $_POST["password"];

if (!empty($_COOKIE["getmein"])) {
    if (urldecode($username) === "admin" && urldecode($password) != "admin") {
        if ($COOKIE["getmein"] === md5($secret . urldecode($username . $password))) {
            echo "Congratulations! You are a registered user.\n";
            die ("The flag is ". $flag);
        }
        else {
            die ("Your cookies don't match up! STOP HACKING THIS SITE.");
        }
    }
    else {
        die ("You are not an admin! LEAVE.");
    }
}

setcookie("sample-hash", md5($secret . urldecode("admin" . "admin")), time() + (60 * 60 * 24 * 7));

if (empty($_COOKIE["source"])) {
    setcookie("source", 0, time() + (60 * 60 * 24 * 7));
}
else {
    if ($_COOKIE["source"] != 0) {
        echo ""; // This source code is outputted here
    }
}

关键代码:

if ($COOKIE["getmein"] === md5($secret . urldecode($username . $password)))

用cookie传参getmein会等于后面的md5值就可以得到flag,我们已知的有:md5($secret+’adminadmin’),密钥的长度,那么我们可以通过附加的字符串得到一个md5值和md5($secret+’admin’+’返回的字符串’)一样,那么我们只要输入这串返回的字符串,并且输入这个MD5就自然相等了。
利用hashpump工具来进行操作:

  • $secret+’admin’加起来有20个字节(这里将$secret+’admin’看作新的secret变量),data为第二个admin,附加data为任意字符


如图自动生成payload和新的md5值,将\x全部换成%

0x02 [De1CTF 2019]SSRF Me


ps:这题主要是对python的代码审计经验不多、代码量又大、传参方式还不少导致挺难整的,对python代码的审计就直接给出大佬链接


源码贴上:

#! /usr/bin/env python
# #encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')
 
app = Flask(__name__)
 
secert_key = os.urandom(16)
 
class Task:
    def __init__(self, action, param, sign, ip):		#是一个简单的赋值函数
        self.action = action
        self.param = param
        self.sign = sign
        self.sandbox = md5(ip)
        if(not os.path.exists(self.sandbox)):		#如果没有该文件夹,则创立一个文件夹
            os.mkdir(self.sandbox)
 
    def Exec(self):
        result = {}
        result['code'] = 500
        if (self.checkSign()):
            if "scan" in self.action:
                tmpfile = open("./%s/result.txt" % self.sandbox, 'w')   #注意w,可以对result.txt文件进行修改
                resp = scan(self.param)
                if (resp == "Connection Timeout"):
                    result['data'] = resp
                else:
                    print resp
                    tmpfile.write(resp)	#这个将resp中的数据写入result.txt中,可以利用为将flag.txt中的数据放进result.txt中
                    tmpfile.close()
                result['code'] = 200
            if "read" in self.action:
                f = open("./%s/result.txt" % self.sandbox, 'r')	#打开方式为只读
                result['code'] = 200
                result['data'] = f.read()	#读取result.txt中的数据
            if result['code'] == 500:
                result['data'] = "Action Error"
        else:
            result['code'] = 500
            result['msg'] = "Sign Error"
        return result
 
    def checkSign(self):
        if (getSign(self.action, self.param) == self.sign):
            return True
        else:
            return False
 
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
    param = urllib.unquote(request.args.get("param", ""))
    action = "scan"
    return getSign(action, param)
 
@app.route('/De1ta',methods=['GET','POST'])		#注意这个绑定,接下来的几个函数都很重要,这个相当于c语言里面的主函数,接下来是调用其他函数的过程
def challenge():
    action = urllib.unquote(request.cookies.get("action"))		#cookie传递action参数,对应不同的处理方式
    param = urllib.unquote(request.args.get("param", ""))		#传递get方式的参数param
    sign = urllib.unquote(request.cookies.get("sign"))			#cookie传递sign参数sign
    ip = request.remote_addr				#获取请求端的ip地址
    if(waf(param)):			#调用waf函数进行过滤
        return "No Hacker!!!!"
    task = Task(action, param, sign, ip) 			#创建Task类对象
    return json.dumps(task.Exec())			#以json的形式返回到客户端
 
@app.route('/')
def index():
    return open("code.txt","r").read()
 
def scan(param):
    socket.setdefaulttimeout(1)
    try:
        return urllib.urlopen(param).read()[:50]		#这个可以利用为访问flag.txt。读取然后为下一步将flag.txt文件中的东西放到result.txt中做铺垫
    except:
        return "Connection Timeout"
 
def getSign(action, param):				#getSign的作用是拼接secret_key,param,action,然后返回拼接后的字符串的md5加密值
    return hashlib.md5(secert_key + param + action).hexdigest()
 
def md5(content):			#将传入的字符串进行md5加密
    return hashlib.md5(content).hexdigest()
 
def waf(param):						#防火墙的作用是判断开头的几个字母是否是gopher 或者是file  如果是的话,返回true
    check=param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):
        return True
    else:
        return False
if __name__ == '__main__':
    app.debug = False
    app.run(host='0.0.0.0',port=9999)

同样secert_key+’flag.txt’的长度为24(长度就是固定的那部分),已知数据data为scan,额外添加数据为read


参考链接:


文章作者: 矢坕
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 矢坕 !
  目录