MikroTik路由器SSH认证绕过复现
使用官方镜像直接启动:
https://download.mikrotik.com/routeros/7.21.5/chr-7.21.5-arm64.img.zip
qemu-system-aarch64 \
-M virt \
-m 2048 \
-cpu cortex-a72 \
-bios /usr/share/qemu-efi-aarch64/QEMU_EFI.fd \
-drive file=chr-7.21.5-arm64.img,format=raw,if=none,id=hd0 \
-device virtio-blk-pci,drive=hd0,bootindex=0 \
-netdev user,id=net0,hostfwd=tcp::5555-:22,hostfwd=tcp::6666-:80,hostfwd=tcp::7777-:443 \
-device virtio-net-pci,netdev=net0 \
-nographic

CVE-2026-67276
通过漏洞情报得知,RouterOS 校验 SSH 公钥认证时存在两个叠加缺陷:按模数定位用户授权密钥时不覆盖指数,且签名验算对指数 e 不做任何校验(未要求 e≥3 或 e=65537)。
我们定位到 /bndl/security/nova/bin/ssh,0x28dec 处是签名验证调用点,sub_28644 的返回值直接决定认证成败。

跟进分析发现它负责打包一个用于验证请求的 NV 消息,随后通过insertnv::raw_id 把提交的签名/公钥 blob 原样传到内部验证逻辑。

最终在 0x1f8d4 调用 sub_24BE0 做签名内容比对(期望 EMSA 块 vs 验算结果),比对失败走 "signatures are not matching!" 。

由于 e 可控制,取 e=1 时 s^e mod n = s,验算结果可以构造合法 EMSA 块,比对必然通过,只要知道受害用户公钥的模数即可伪造认证(实测完整模数匹配通过、错误模数被拒)。
我们先用真实私钥形成对照,victim_rsa是保存的私钥,拿到系统信息
ssh -i victim_rsa -p 5555 admin@192.168.135.137 '/system resource print'

再通过伪造认证,victim_rsa.pub是保存的公钥提供模数,forge_67276.py构造 e=1 密钥和签名
python3 poc_67276.py --host 192.168.135.137 --port 5555 --username admin \
--pubkey victim_rsa.pub --algos rsa-sha2-256,ssh-rsa \
--exec '/system resource print' --lab-i-own-this-target

成功无密码登录admin
CVE-2026-86060
根据漏洞情报可知,RouterOS 的 SSH 登录流程存在两个叠加缺陷:未认证的连接在 rekey 后可以被打开会话通道(CVE-2026-67279),且登录助手login 对传入的用户名参数不做来源和格式校验,把 -N 形式的畸形用户名当作"从 fd N 读取认证数据"的指令处理。
我们定位到/bndl/security/nova/bin/ssh,0x2b22c处是取 "ssh:" 前缀(vaddr 0x3e5e6)拼 conn-type,即"ssh:" + 用户名 + "@" + IP

到这里直接传递给login,期间并没有过滤用户名

再到 /nova/bin/login,0x2c450 处 atoi(str+4) 把 -N 解析成 fd 号(文件描述符),也就是允许通过 -z N 让 RouterOS 从任意文件描述符读取数据。

唯一的防御是 0x2c460 处的范围检查(N≤32)。可以构造-z -1 或 -z 0xFFFFFFFF

随后分析发现,0x2c46c 起的读取循环从该 fd 读入最多 4096 字节

要求末字节为 NULL,如果最后一个字符是 NULL(字符串结束符),将NULL之前的缓冲区作为用户名,如果还有剩余数据作为密码

随后按 user\0policy 格式切分——数据内容完全可伪造,login 不做任何可信校验。
最终在 0x2c16c 调用 strtoul(policy_str, 0, 10) 把 policy 字符串转成整数,写入会话 NV 消息的 id 11(policy掩码)字段,控制台(console) 直接信任这个掩码。

攻击链:以畸形用户名 -2 拼接进 argv 执行 login -c ssh:-2@ip -2 <pol>,-2 被当成 fd 2(stderr,是我们控制的通道数据),login 从中读到伪造的 admin\0-1;由于 strtoul 把 -1 转成 0xffffffff,会话直接提权到管理员。
python poc_86060.py 192.168.135.137 5555


CVE-2026-67277
根据漏洞情报,bandwidth-test 带宽测试服务可被未认证连接触发内核内存泄露
我们定位到 /nova/bin/btest,0x1e688 处是 related 标志判定,sub_1e678 直接决定新连接能否绕过认证挂进已有会话。

随后分析发现取命令字低 16 位作为会话 id

该 id 在会话树中逐节点比对,命中即调用 sub_1E1A4 把新 fd 原样挂进主会话连接数组,并按主会话参数直接启动测试并且无检查。

泄露最终在发包路径形成:命令 byte[2] bit0 (命令结构: byte[0] = 命令, byte[1] = 标志, byte[2] = 参数)是 random 标志(0x1e76c 解析),zeros(random_data=false)模式下用户态只写 4 字节 seq 就跳过了缓冲区初始化整块 send,本意是发全零包省 CPU。
// 正常模式:发送前填充随机数据
if (random_data) {
fill_with_random(buffer, size); // 填充随机数据
memcpy(buffer, seq, 4); // 前4字节写入序列号
send(buffer, size); // 发送完整数据包
}
// zeros 模式:不填充随机数据
if (!random_data) { // zeros mode
// 只写 4 字节 seq,其余数据未初始化
memcpy(buffer, seq, 4); // 只写前4字节
// buffer[4..size-1] 保持原有内容
send(buffer, size); // 发送未初始化的内存数据
}

内核 btest.ko 在 0x988 __alloc_skb 分配 skb,0x9ec skb_put 后只写了 52字节头

然后 0xa90 处比较 random 标志,为零则跳过 0xaa0 get_random_bytes 的填充——skb 数据区是 __alloc_skb 出来的未初始化内核内存,直接上线。
也就是说zeros 模式发出的每个 UDP 包 = 4 字节序列号 + 设备内存

猜错 id 只记一条 "unexpected related" 日志并只关掉这条猜测连接,不会影响主连接

只要开 60 条未认证主连接即可预认证挂接并启动泄露,head64解码得泄露信息
sudo python3 spray3.py

结语
这三个CVE都不是很高深的内存漏洞,而是鉴权的逻辑漏洞。
CVE-2026-67276:SSH 公钥认证不校验指数 e,构造 e=1 的密钥使签名验算等于自造数据,知道用户公钥模数即可无密码登录。
CVE-2026-86060:sshd 把畸形用户名 -2 原样传给 login,login 将其当作"从 fd 2 读认证数据",经 stderr 通道注入policy=-1(strtoul 成 0xffffffff)提权为全权限管理员。
CVE-2026-67277:btest 的 related 会话只靠一个可枚举的 16 位槽位号鉴权,未认证即可挂接启动测试,zeros 模式又把未清零的内存整块发出,形成预认证内存泄露。
