MikroTik路由器SSH认证绕过复现

MikroTik认证绕过
2026-09-09 06:41
44130

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

图片.png

CVE-2026-67276

通过漏洞情报得知,RouterOS 校验 SSH 公钥认证时存在两个叠加缺陷:按模数定位用户授权密钥时不覆盖指数,且签名验算对指数 e 不做任何校验(未要求 e≥3 或 e=65537)。

我们定位到 /bndl/security/nova/bin/ssh,0x28dec 处是签名验证调用点,sub_28644 的返回值直接决定认证成败。

图片.png

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

图片.png

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

图片.png

由于 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'

图片.png

再通过伪造认证,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

图片.png

成功无密码登录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

图片.png

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

图片.png

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

图片.png

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

图片.png

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

图片.png

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

图片.png

随后按 user\0policy 格式切分——数据内容完全可伪造,login 不做任何可信校验。

最终在 0x2c16c 调用 strtoul(policy_str, 0, 10) 把 policy 字符串转成整数,写入会话 NV 消息的 id 11(policy掩码)字段,控制台(console) 直接信任这个掩码。

图片.png

攻击链:以畸形用户名 -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

图片.png

图片.png

CVE-2026-67277

根据漏洞情报,bandwidth-test 带宽测试服务可被未认证连接触发内核内存泄露

我们定位到 /nova/bin/btest,0x1e688 处是 related 标志判定,sub_1e678 直接决定新连接能否绕过认证挂进已有会话。

图片.png

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

图片.png

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

图片.png

泄露最终在发包路径形成:命令 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);  // 发送未初始化的内存数据
}

图片.png

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

图片.png

然后 0xa90 处比较 random 标志,为零则跳过 0xaa0 get_random_bytes 的填充——skb 数据区是 __alloc_skb 出来的未初始化内核内存,直接上线。

也就是说zeros 模式发出的每个 UDP 包 = 4 字节序列号 + 设备内存

图片.png

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

图片.png

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

sudo python3 spray3.py

图片.png

结语

这三个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 模式又把未清零的内存整块发出,形成预认证内存泄露。

分享到

参与评论

0 / 200

全部评论 1

Legion_的头像
为民dddd,🐂🍺
2026-09-09 13:08
投稿
签到
联系我们
关于我们