MikroTik RouterOS 授权签名验证分析
MikroTik 软路由
百科
https://baike.baidu.com/item/mikrotik/9776775官网
https://mikrotik.com/
授权文件分析
-----BEGIN MIKROTIK SOFTWARE KEY------------
mr3jH5qhn9irtF53ZICFTN7Tk7wIx7ZkxdAxJ19ydASY
ShhFteHMntBTyaS8wuNdIJJPidJxbuNPLTvCsv7zLA==
-----END MIKROTIK SOFTWARE KEY--------------
采用自定义的Base64编码,自定义Base64的转换如下:
MIKRO_BASE64_CHARACTER_TABLE = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
def mikro_base64_encode(data:bytes, pad = False)->str:encoded = ''left = 0for i in range(0, len(data)):if left == 0:encoded += chr(MIKRO_BASE64_CHARACTER_TABLE[data[i] & 0x3F])left = 2else:if left == 6:encoded += chr(MIKRO_BASE64_CHARACTER_TABLE[data[i - 1] >> 2])encoded += chr(MIKRO_BASE64_CHARACTER_TABLE[data[i] & 0x3F])left = 2else:index1 = data[i - 1] >> (8 - left)index2 = data[i] << (left)encoded += chr(MIKRO_BASE64_CHARACTER_TABLE[(index1 | index2) & 0x3F])left += 2if left != 0:encoded += chr(MIKRO_BASE64_CHARACTER_TABLE[data[len(data) - 1] >> (8 - left)])if pad:for i in range(0, (4 - len(encoded) % 4) % 4):encoded += '='return encoded
def mikro_base64_decode(data:str)->bytes:ret = b""data = data.replace("=", "").encode()left = 0for i in range(0, len(data)):if left == 0:left = 6else:value1 = MIKRO_BASE64_CHARACTER_TABLE.index(data[i - 1]) >> (6 - left)value2 = MIKRO_BASE64_CHARACTER_TABLE.index(data[i]) & (2 ** (8 - left) - 1)value = value1 | (value2 << left)ret += bytes([value])left -= 2return ret
Base64解码后为固定64字节长度的数据,其中16字节授权内容、16字节随机数、32字节签名。
16字节授权内容通过如下方式加密解密
def mikro_encode(s:bytes)->bytes:s = list(struct.unpack('>' + 'I' * (len(s) // 4), s))for i in reversed(range(16)):s[(i+0) % 4] = to32bits(rotl(s[(i+3) % 4], MIKRO_SHA256_K[i*4+3] & 0x0F) ^ (s[(i+0) % 4] - s[(i+3) % 4]))s[(i+3) % 4] = to32bits(s[(i+3) % 4] + s[(i+1) % 4] + MIKRO_SHA256_K[i*4+3])s[(i+1) % 4] = to32bits(rotl(s[(i+2) % 4], MIKRO_SHA256_K[i*4+2] & 0x0F) ^ (s[(i+1) % 4] - s[(i+2) % 4]))s[(i+0) % 4] = to32bits(s[(i+0) % 4] + s[(i+2) % 4] + MIKRO_SHA256_K[i*4+2])s[(i+2) % 4] = to32bits(rotl(s[(i+1) % 4], MIKRO_SHA256_K[i*4+1] & 0x0F) ^ (s[(i+2) % 4] - s[(i+1) % 4]))s[(i+1) % 4] = to32bits(s[(i+1) % 4] + s[(i+3) % 4] + MIKRO_SHA256_K[i*4+1])s[(i+3) % 4] = to32bits(rotl(s[(i+0) % 4], MIKRO_SHA256_K[i*4+0] & 0x0F) ^ (s[(i+3) % 4] - s[(i+0) % 4]))s[(i+2) % 4] = to32bits(s[(i+2) % 4] + s[(i+0) % 4] + MIKRO_SHA256_K[i*4+0])encodedLicensePayload = b''for x in s:encodedLicensePayload += x.to_bytes(4, 'big')return encodedLicensePayloaddef mikro_decode(s:bytes)->bytes:s = list(struct.unpack('>'+'I'*(len(s) // 4), s))for i in range(16):s[(i+2) % 4] = to32bits(s[(i+2) % 4] - s[(i+0) % 4] - MIKRO_SHA256_K[i*4+0])s[(i+3) % 4] = to32bits((rotl(s[(i+0) % 4], MIKRO_SHA256_K[i*4+0] & 0x0F) ^ s[(i+3) % 4]) + s[(i+0) % 4])s[(i+1) % 4] = to32bits(s[(i+1) % 4] - s[(i+3) % 4] - MIKRO_SHA256_K[i*4+1])s[(i+2) % 4] = to32bits((rotl(s[(i+1) % 4], MIKRO_SHA256_K[i*4+1] & 0x0F) ^ s[(i+2) % 4]) + s[(i+1) % 4])s[(i+0) % 4] = to32bits(s[(i+0) % 4] - s[(i+2) % 4] - MIKRO_SHA256_K[i*4+2])s[(i+1) % 4] = to32bits((rotl(s[(i+2) % 4], MIKRO_SHA256_K[i*4+2] & 0x0F) ^ s[(i+1) % 4]) + s[(i+2) % 4])s[(i+3) % 4] = to32bits(s[(i+3) % 4] - s[(i+1) % 4] - MIKRO_SHA256_K[i*4+3])s[(i+0) % 4] = to32bits((rotl(s[(i+3) % 4], MIKRO_SHA256_K[i*4+3] & 0x0F) ^ s[(i+0) % 4]) + s[(i+3) % 4])ret = b''for x in s:ret += x.to_bytes(4, 'big')return ret
16字节解密后前6个字节为授权软件ID,第7个字节为授权软件版本号,第8个字节为授权等级
6字节转换为软件ID的方式如下:
SOFTWARE_ID_CHARACTER_TABLE = b'TN0BYX18S5HZ4IA67DGF3LPCJQRUK9MW2VE'
def mikro_softwareid_decode(software_id:str)->int:assert(isinstance(software_id, str))software_id = software_id.replace('-', '')ret = 0for i in reversed(range(len(software_id))):ret *= len(SOFTWARE_ID_CHARACTER_TABLE)ret += SOFTWARE_ID_CHARACTER_TABLE.index(ord(software_id[i]))return retdef mikro_softwareid_encode(id:int)->str:assert(isinstance(id, int))ret = ''for i in range(8):ret += chr(SOFTWARE_ID_CHARACTER_TABLE[id % 0x23])id //= 0x23if i == 3:ret += '-'return ret
16字节随机数和32字节校验码通过EC-KCDSA算法签名生成,采用toyecc库,其中sha256采用自定义的K和State表
自定义的sha256实现算法
MIKRO_SHA256_K = (0x0548D563, 0x98308EAB, 0x37AF7CCC, 0xDFBC4E3C,0xF125AAC9, 0xEC98ACB8, 0x8B540795, 0xD3E0EF0E,0x4904D6E5, 0x0DA84981, 0x9A1F8452, 0x00EB7EAA,0x96F8E3B3, 0xA6CDB655, 0xE7410F9E, 0x8EECB03D,0x9C6A7C25, 0xD77B072F, 0x6E8F650A, 0x124E3640,0x7E53785A, 0xE0150772, 0xC61EF4E0, 0xBC57E5E0,0xC0F9A285, 0xDB342856, 0x190834C7, 0xFBEB7D8E,0x251BED34, 0x0E9F2AAD, 0x256AB901, 0x0A5B7890,0x9F124F09, 0xD84A9151, 0x427AF67A, 0x8059C9AA,0x13EAB029, 0x3153CDF1, 0x262D405D, 0xA2105D87,0x9C745F15, 0xD1613847, 0x294CE135, 0x20FB0F3C,0x8424D8ED, 0x8F4201B6, 0x12CA1EA7, 0x2054B091,0x463D8288, 0xC83253C3, 0x33EA314A, 0x9696DC92,0xD041CE9A, 0xE5477160, 0xC7656BE8, 0x5179FE33,0x1F4726F1, 0x5F393AF0, 0x26E2D004, 0x6D020245,0x85FDF6D7, 0xB0237C56, 0xFF5FBD94, 0xA8B3F534
)
class MikroSHA256(SHA256):K = MIKRO_SHA256_KINITIAL_STATE = SHA256.State(0x5B653932, 0x7B145F8F, 0x71FFB291, 0x38EF925F,0x03E1AAF9, 0x4A2057CC, 0x4CAF4DD9, 0x643CC9EA)
def mikro_sha256(data:bytes)->bytes:return MikroSHA256(data).digest()
EC-KCDSA签名验证算法
from toyecc import AffineCurvePoint, getcurvebyname, FieldElement,ECPrivateKey,ECPublicKey,Tools
from toyecc.Random import secure_rand_int_between
def mikro_kcdsa_sign(data:bytes,private_key:bytes)->bytes:assert(isinstance(data, bytes))assert(isinstance(private_key, bytes))curve = getcurvebyname('Curve25519')private_key:ECPrivateKey = ECPrivateKey(Tools.bytestoint_le(private_key), curve)public_key:ECPublicKey = private_key.pubkeywhile True:nonce_secret = secure_rand_int_between(1, curve.n - 1)nonce_point = nonce_secret * curve.Gnonce = int(nonce_point.x) % curve.nnonce_hash = mikro_sha256(Tools.inttobytes_le(nonce,32))data_hash = bytearray(mikro_sha256(data))for i in range(16):data_hash[8+i] ^= nonce_hash[i] data_hash[0] &= 0xF8data_hash[31] &= 0x7Fdata_hash[31] |= 0x40data_hash = Tools.bytestoint_le(data_hash)signature = pow(private_key.scalar, -1, curve.n) * (nonce_secret - data_hash)signature %= curve.nif int((public_key.point * signature + curve.G * data_hash).x) == nonce:return bytes(nonce_hash[:16]+Tools.inttobytes_le(signature,32))def mikro_kcdsa_verify(data:bytes, signature:bytes, public_key:bytes)->bool:assert(isinstance(data, bytes))assert(isinstance(signature, bytes))assert(isinstance(public_key, bytes))curve = getcurvebyname('Curve25519')#y^2 = x^3 + ax^2 + xx = Tools.bytestoint_le(public_key)X_field = FieldElement(x, curve.p)YY = ((X_field**3) + (curve.a * X_field**2) + X_field).sqrt()public_keys = []for y in YY:public_keys += [AffineCurvePoint(x, int(y), curve)]data_hash = bytearray(mikro_sha256(data))nonce_hash = signature[:16]signature = signature[16:]for i in range(16):data_hash[8+i] ^= nonce_hash[i]data_hash[0] &= 0xF8data_hash[31] &= 0x7Fdata_hash[31] |= 0x40data_hash = Tools.bytestoint_le(data_hash)signature = Tools.bytestoint_le(signature)for public_key in public_keys:nonce = int((public_key * signature + curve.G * data_hash).x) if mikro_sha256(Tools.inttobytes_le(nonce,32))[:len(nonce_hash)] == nonce_hash:return Truereturn False
def generate_kcdsa_keypair():curve = getcurvebyname('Curve25519')private_key = ECPrivateKey.generate(curve)return Tools.inttobytes_le(private_key.scalar, 32),Tools.inttobytes_le(int(private_key.pubkey.point.x), 32)
授权文件解析
通过分析/nova/bin/keyman可以获取授权文件验证签名的公钥为
MIKRO_LICENSE_PUBLIC_KEY = bytes.fromhex('8E1067E4305FCDC0CFBF95C10F96E5DFE8C49AEF486BD1A4E2E96C27F01E3E32')
因此可以解析验证授权文件是否有效
def prase_license(lic:str,public_key:bytes):assert(isinstance(public_key, bytes))slic = lic.replace(MIKRO_LICENSE_HEADER, '').replace(MIKRO_LICENSE_FOOTER, '').replace('\n', '').replace(' ','')lic:bytes = mikro_base64_decode(slic)licVal = mikro_decode(lic[:16])software_id = int.from_bytes(licVal[:6], 'little')print(f"Software ID: {mikro_softwareid_encode(software_id)}({hex(software_id)})")print(f"RouterOS Version: {licVal[6]}")print(f"License Level: {licVal[7]}")nonce_hash = lic[16:32]print(f"Nonce Hash: {nonce_hash.hex()}")signature = lic[32:64]print(f"Signature: {signature.hex()}")print(f'License valid:{mikro_kcdsa_verify(licVal, nonce_hash+signature,public_key)}')
解析后的输出
Software ID: TI09-7WK3(0x137f8e8673d)
RouterOS Version: 6
License Level: 6
Nonce Hash: b34fe40e23f19e917107c449ddcb1d20
Signature: 61521816ad7730671b4cb226f1b0db7448923c6297c49bdb3ccbf40aecbbcf0b
License valid:True
授权文件生成
生成秘钥对,通过私钥对授权内容进行签名即可。
MIKRO_LICENSE_HEADER = '-----BEGIN MIKROTIK SOFTWARE KEY------------'
MIKRO_LICENSE_FOOTER = '-----END MIKROTIK SOFTWARE KEY--------------'
def generate_license(software_id,private_key:bytes,version:int=7, level:int=6):assert(isinstance(private_key, bytes))if isinstance(software_id, str):software_id = mikro_softwareid_decode(software_id)lic = software_id.to_bytes(6, 'little')lic += version.to_bytes(1, 'little')lic += level.to_bytes(1, 'little')lic += b'\0'*8sig = mikro_kcdsa_sign(lic, private_key)lic = mikro_base64_encode(mikro_encode(lic)+sig,True)return MIKRO_LICENSE_HEADER + '\n' + lic[:len(lic)//2] + '\n' + lic[len(lic)//2:] + '\n' + MIKRO_LICENSE_FOOTER
替换公钥
用生成的秘钥对的公钥替换原先官方的公钥
squashfs-root/nova/bin/loader public key patched 8E1067E4305FCDC0CFBF95C10F96E5DF...
squashfs-root/nova/bin/keyman public key patched 8E1067E4305FCDC0CFBF95C10F96E5DF...
squashfs-root/nova/bin/mode public key patched 8E1067E4305FCDC0CFBF95C10F96E5DF...
initramfs/init public key patched 8E1067E4305FCDC0CFBF95C10F96E5DF...
initramfs/setup public key patched 8E1067E4305FCDC0CFBF95C10F96E5DF...
但是routeros的npk文件也是验证签名的,因此直接替换公钥,系统启动的时候验证npk的文件的签名是不通过的,所以还得对npk文件重新签名
NPK文件签名分析
npk文件的签名由132个字节组成,其中20字节sha1,48字节EC-KCDSA签名,64字节EDDSA签名
同理生成自定义秘钥对替换原来的官方公钥,用自定义的私钥对npk重新签名即可。
squashfs-root/nova/bin/installer public key patched C293CED638A2A33C681FC8DE98EE26C5...
squashfs-root/nova/bin/sys2 public key patched C293CED638A2A33C681FC8DE98EE26C5...
initramfs/init public key patched C293CED638A2A33C681FC8DE98EE26C5...
initramfs/setup public key patched C293CED638A2A33C681FC8DE98EE26C5...
源代码
MikroTikPatch
https://github.com/elseif/MikroTikPatch
netinstall.py,修改netinstall.exe,通过修改过的netinstall可以网络安装ISO里重新签名的npk文件
upgrade.py,通过在routeros添加静态域名解析,实现upgrade的时候安装ISO里重新签名的npk文件。
相关文章:
MikroTik RouterOS 授权签名验证分析
MikroTik 软路由 百科https://baike.baidu.com/item/mikrotik/9776775官网https://mikrotik.com/ 授权文件分析 -----BEGIN MIKROTIK SOFTWARE KEY------------ mr3jH5qhn9irtF53ZICFTN7Tk7wIx7ZkxdAxJ19ydASY ShhFteHMntBTyaS8wuNdIJJPidJxbuNPLTvCsv7zLA …...
C#开发-集合使用和技巧(六)特殊转换方法SelectMany的介绍和用法
介绍 SelectMany 方法在C#中用于将集合中的元素转换为其他类型的集合,并将这些集合扁平化为一个单一的序列。它是LINQ的一部分,允许你在一个序列上进行投影和过滤操作,然后将结果合并成一个序列。 方法定义 public static IEnumerable<…...
高考后的抉择:如何在心仪专业与知名学校之间做出选择?
目录 前言1. 专业选择的深度探讨1.1 专业的优势与挑战1.1.1 课程学习1.1.2 就业前景 1.2 专业选择的个人经验与思考 2. 名校对个人发展的长短期影响2.1 名校声誉的品牌效应2.1.1 职业发展2.1.2 社会认可度 2.2 教育资源与学术氛围2.2.1 教育资源2.2.2 学术氛围 2.3 就业优势 3.…...
黄仁勋提到的机器人世界,还需要AI数据来“调教” | CVPR 2024
本周,CVPR 2024正在美国西雅图拉开序幕。今年CVPR论文投稿数再次创下新纪录,可想而知本届会议的火热。 从研究主题来看,具身智能这一大热点值得关注。 黄仁勋在COMPUTEX大会开幕前夕的演讲中预言:AI的下一个浪潮将是物理AI。 即…...
语言中 函数用地址传参的好处
在C语言中,使用地址传参(传递指针)有以下几个好处: 1. **减少内存开销**: - 传递一个指针(通常是一个地址)比传递一个大的结构体或数组要高效得多,因为指针通常是一个固定大小&a…...
Python进阶二: NumPy基础:数组和矢量计算
二、NumPy基础:数组和矢量计算 本文源自微博客(www.microblog.store),且以获得授权 NumPy(Numerical Python的简称)是Python数值计算最重要的基础包。大多数提供科学计算的包都是用NumPy的数组作为构建基础。 NumPy的部分功能如下…...
2024北京智源大会开幕,智源推出大模型全家桶及全栈开源技术基座新版图,大模型先锋集结共探AGI之路
2024年6月14日,第六届“北京智源大会”在中关村展示中心开幕。 北京智源大会是智源研究院主办的“AI内行顶级盛会”,以“全球视野、思想碰撞、前沿引领”为特色,汇聚海内外研究者分享研究成果、探寻前沿知识、交流实践经验。2024北京智源大会…...
李光明从程序员到架构师的逆袭之路(三)
我,李光明,正在参加一个重要的技术会议。会场上,我们团队正在讨论着接口设计以及接口设计模式。我深知,一个好的接口设计应当遵循简洁、清晰、可扩展的原则。比如,在设计一个用户信息查询接口时,我们会定义…...
基于Spring Boot+VUE毕业生信息招聘平台
系统详细设计 1管理员功能模块 管理员登录,管理员通过输入用户名、密码、角色等信息进行系统登录,如图1所示。 图1管理员登录界面图 管理员登录进入毕业生信息招聘平台可以查看首页、个人中心、企业管理、空中宣讲会管理、招聘岗位管理、毕业生管理、个…...
设计模式-创建型-04-建造者模式
1、盖房项目需求 1)需要建房子:这一过程为打桩、砌墙、封顶2)房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是要求不要相同的3)请编写程序…...
jenkins中配置参数化,并在python脚本接收参数实现参数化执行
在公司内进行Monkey测试脚本集成jenkins时,因为需要指定公司内不同的app进行测试。那么可以有两种方法解决该问题,要么创建多个脚本文件,多个jenkins jobs,进行构建。要么可以在配置job时通过传参数的方式,在python脚本…...
【SCAU数据挖掘】数据挖掘期末总复习题库应用题及解析
1. 给定圆的半径为e ,令 MinPts3,考虑下面两幅图。 (1)哪些对象是核心对象? m,p,o,r(因为这些核心对象在半径e的范围内都至少包含MinPts3个对象) (2)哪些对象是直接密度可达的? 对象q是…...
ADB->获取当前正在显示的Fragment和Activity的ADB命令
获取当前显示的Activity adb shell "dumpsys window | grep mCurrentFocus"指令拆解adb shell:启动一个远程shell来运行设备上的命令dumpsys window:获取当前窗口管理器的信息|:将前一个命令的输出作为后一个命令的输入grep mCurr…...
C#——集合List
list list集合和Arraylist基本一样,只不过list是C#2.0版本新加入的范型类型。list也可以通过索引操作里面的元素,也有对list进行增删改查 概念 Array静态数组 * Arraylist 动态数组 * list集合 * 1. Array是容量是固定的,但是ArrayList和…...
小程序-生命周期(2) 应用周期/页面周期
一.应用周期 应用周期指的是小程序:启动->运行->销毁的整个过程。 应用周期伴随一些函数来进行控制,这些函数卸载app.js里面的App方法里。 分别由onLaunch, onShow,onHide依次进行。 onLaunch:初始化的时候运行…...
什么是模板字符串?
模板字符串(Template Literals)是ES6(ECMAScript 2015)中引入的一种新的字符串表示方法,允许我们嵌入表达式,并在运行时将它们转换为字符串。模板字符串使用反引号()来定义ÿ…...
服务器数据恢复—热备盘未完全启用导致raid5阵列崩溃的数据恢复案例
服务器存储故障: 一台EMC某型号存储由于存储中raid5阵列出现故障导致服务器崩溃,由于数据涉密,需要工程师到现场恢复数据。 服务器数据恢复工程师到现场后对数据进行检测,经过检测发现服务器崩溃是由于raid中某些硬盘掉线所导致。…...
微服务项目雪崩的解决思路
雪崩的介绍 雪崩是微服务中某个服务挂了,无法返回请求,导致调用改服务的上层服务也故障,最终形成连锁反应,导致整个系统故障。 解决思路 一般有四种思路: 1.最简单的就是超时处理,即超过一段时间就返回…...
汇编语言程序设计 - 新建一个文件:d:\abc.txt,从键盘输入文件的内容(不超过100个字符)
80x86汇编习题 题目描述:编写一个程序,新建一个文件:d:\abc.txt,从键盘输入文件的内容(不超过100个字符) 思路: 1,定义好文件名,记得末尾0 2,定义好缓冲区…...
【Linux】进程间通信2——命名管道
1. 命名管道(FIFO) 1.1. 基本概念 简单,给匿名管道起个名字就变成了命名管道 那么如何给 匿名管道 起名字呢? 结合文件系统,给匿名管道这个纯纯的内存文件分配 inode,将文件名与之构建联系,关键点在于不给它分配 D…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...
Linux 下 DMA 内存映射浅析
序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存,但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程,可以参考这篇文章,我觉得写的非常…...
前端高频面试题2:浏览器/计算机网络
本专栏相关链接 前端高频面试题1:HTML/CSS 前端高频面试题2:浏览器/计算机网络 前端高频面试题3:JavaScript 1.什么是强缓存、协商缓存? 强缓存: 当浏览器请求资源时,首先检查本地缓存是否命中。如果命…...
一些实用的chrome扩展0x01
简介 浏览器扩展程序有助于自动化任务、查找隐藏的漏洞、隐藏自身痕迹。以下列出了一些必备扩展程序,无论是测试应用程序、搜寻漏洞还是收集情报,它们都能提升工作流程。 FoxyProxy 代理管理工具,此扩展简化了使用代理(如 Burp…...
[特殊字符] 手撸 Redis 互斥锁那些坑
📖 手撸 Redis 互斥锁那些坑 最近搞业务遇到高并发下同一个 key 的互斥操作,想实现分布式环境下的互斥锁。于是私下顺手手撸了个基于 Redis 的简单互斥锁,也顺便跟 Redisson 的 RLock 机制对比了下,记录一波,别踩我踩过…...
