Talos Vulnerability Report

TALOS-2026-2373

GeoVision GeoWebPlayer Websocket Server out-of-bounds read vulnerabilities

July 1, 2026
CVE Number

CVE-2026-13131,CVE-2026-13132,CVE-2026-57264,CVE-2026-57265,CVE-2026-57266,CVE-2026-57267,CVE-2026-57268,CVE-2026-57269,CVE-2026-57270,CVE-2026-57271,CVE-2026-57272

Summary

Multiple exploitable out-of-bounds read vulnerabilities exist in the Websocket Server functionality of GeoWebPlayer (version(s): 1.1.1.0). A specially crafted websocket message can lead to a arbitrary code execution. An attacker can stage a malicious webpage to trigger these vulnerabilities.

Confirmed Vulnerable Versions

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

GeoWebPlayer (version(s): 1.1.1.0)

Product URLs

GeoWebPlayer - http://ovision.com.tw/

CVSSv3 Score

8.3 - CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H

CWE

CWE-129 - Improper Validation of Array Index

Details

GeoWebPlayer (also called “Web Plugin” in the GV-VMS documentation and “WS Player” for VMS-Cloud) is an addon that can be installed with various GeoVision software (GV-VMS, GV-Cloud, …). It creates a websocket server that expands the capabilities of the various web-interfaces provided by the GeoVision software and may be necessary for them to function properly.

CVE-2026-13131 - connectInfo command index-out-of-bound

When sending the connectInfo command, the index field is extracted from the websocket message:

  entry = get_entry(json_root, "index");
  if ( json_is_value_int(entry) )
  {
    v3 = get_entry(json_root, "index");
    index = json_value_to_int(&v3->value);
  }    Then passed to the following command: ` GeoWebPlayer_cam_connect(index, conn_details);` 

Which, depending on the code flow, can lead to out-of-bound writes:

      viewer->array_of_IPCams[index]->field_FC = 0;
      sub_405760(&viewer->array_of_IPCams[index]->field_94, &conn_details.conn_details.probably_title_str);
      viewer->array_of_IPCams[index]->field_FA4 = index;
      viewer->array_of_IPCams[index]->field_FC0 = conn_details.conn_details.streamIndex;
      sub_405760(&viewer->array_of_IPCams[index]->field_13AC, &conn_details.conn_details.relayQRCode_str);
      viewer->array_of_IPCams[index]->field_13B0 = conn_details.conn_details.relayType;
      viewer->array_of_IPCams[index]->field_EC8 = conn_details.conn_details.sync;
      BYTE1(viewer->array_of_IPCams[index]->field_139C) = conn_details.conn_details.field_270;
      BYTE2(viewer->array_of_IPCams[index]->field_139C) = conn_details.conn_details.showFisheyeMenu;
      LOBYTE(viewer->array_of_IPCams[index]->field_11E8) = conn_details.conn_details.keepRatio;
      LOBYTE(viewer->array_of_IPCams[index]->field_F8C) = conn_details.conn_details.improveVideoDelay;

Or calling a function pointer that is read out of bounds, potentially leading to code execution:

` (*(void (__thiscall **)(CCriticalSection *))(viewer->crit_sections[index].vtbl + 20))(&viewer->crit_sections[index]);`

CVE-2026-13132 - setStream command index-out-of-bound

When sending the setStream command, the index field is extracted from the websocket message:

 entry = get_entry(a2, "index");
  result = json_is_value_int(entry);
  if ( (_BYTE)result )
  {
    v4 = get_entry(a2, "index");
    index = json_value_to_int(&v4->value);
    v5 = get_entry(a2, "stream");
    if ( json_is_value_int(v5) )
    {
      v6 = get_entry(a2, "stream");
      stream_id = json_value_to_int(&v6->value);
    }

Then without checking the range of the index, it is used to trigger a Critical section and release it. The release function call [(0)] is executed using a function pointer which will be read out of bounds potentially leading to code execution:

    result = CCriticalSection::EnterCritSection(&this->crit_sections[index]); 
    if ( result )
    {
      if ( this->array_of_IPCams[index] )
        do_PostMessageA((CViewer *)this->array_of_IPCams[index], 0x1396u, stream_id, 0);
      return (*(int (__thiscall **)(CCriticalSection *))(this->crit_sections[index].vtbl + 20))(&this->crit_sections[index]);  // [0] 
    }
  }

CVE-2026-57264 - setPIP command index-out-of-bound

When sending the setPIP command, the index field is extracted from the websocket message:

 v6 = get_entry(a2, "index");
  result = json_is_value_int(v6);
  if ( (_BYTE)result )
  {
    v8 = get_entry(a2, "index");
    index = json_value_to_int(&v8->value);

Then without checking the range of the index, it is used to trigger a CriticalSection and releases it. The release function call [(0)] is executed using a function pointer which will be read out of bounds potentially leading to code execution:

result = CCriticalSection::EnterCritSection(&this->crit_sections[index]);
if ( result )
{
  if ( this->array_of_IPCams[index] )
    do_PostMessageA((CViewer *)this->array_of_IPCams[index], 0x138Cu, v11, 0);
  return (*(int (__thiscall **)(CCriticalSection *))(this->crit_sections[index].vtbl + 20))(&this->crit_sections[index]); // [0]
}   }

CVE-2026-57265 - audio command index-out-of-bound

When sending the audio command, the index field is extracted from the websocket message:

 v6 = get_entry(root, "index");
  result = json_is_value_int(v6);
  if ( (_BYTE)result )
  {
    v8 = get_entry(root, "index");
    index = json_value_to_int(&v8->value);

Then without checking the range of the index, it is used to trigger a CriticalSection and releases it. The release function call [(0)] is executed using a function pointer which will be read out of bounds potentially leading to code execution:

result = CCriticalSection::EnterCritSection(&this->crit_sections[index]);
if ( result )
{
  if ( this->array_of_IPCams[index] )
    CGvIPCamConnect::setAudio(this->array_of_IPCams[index], isPlay);
  return (*(int (__thiscall **)(CCriticalSection *))(this->crit_sections[index].vtbl + 20))(&this->crit_sections[index]);  // [0]
}

In this case, the CGvIPCamConnect::setAudio function can also be called which might lead to more out-of-bound reads and write.

CVE-2026-57266 - 2wayAudio command index-out-of-bound

When sending the audio command, the index field is extracted from the websocket message:

  v6 = get_entry(a2, "index");
  result = json_is_value_int(v6);
  if ( (_BYTE)result )
  {
    v8 = get_entry(a2, "index");
    v10 = json_value_to_int(&v8->value);

Then without checking the range of the index, it is used to trigger a CriticalSection and releases it. The release function call [(0)] is executed using a function pointer which will be read out of bounds potentially leading to code execution:

result = CCriticalSection::EnterCritSection(&this->crit_sections[v10]);
if ( result )
{
  if ( this->array_of_IPCams[v10] )
    CGvIPCamConnect::handle_2wayAudio(this->array_of_IPCams[v10], play);
  return (*(int (__thiscall **)(CCriticalSection *))(this->crit_sections[v10].vtbl + 20))(&this->crit_sections[v10]);  // [0]
}

In this case, the CGvIPCamConnect::handle_2wayAudio function can also be called which might lead to more out-of-bound reads and writes.

CVE-2026-57267 - snapshot command index-out-of-bound

When sending the snapshot command, the index field is extracted from the websocket message [1]. Then without checking the range of the index, it is used to trigger a CriticalSection ([2]) and releases it [3]. The release function call ([3]) is executed using a function pointer which will be read out of bounds potentially leading to code execution:

int __thiscall handle_snapshot(CViewer *this, json_node *a2)
{
  json_node *entry; // eax
  int result; // eax
  json_node *v4; // eax
  int v6; // [esp+4h] [ebp-4h]

  entry = get_entry(a2, "index");
  result = json_is_value_int(entry);
  if ( (_BYTE)result )
  {
    v4 = get_entry(a2, "index");
    v6 = json_value_to_int(&v4->value);  //[1]
    result = CCriticalSection::EnterCritSection(&this->crit_sections[v6]); //[2]
    if ( result )
    {
      if ( this->array_of_IPCams[v6] )
      {
        if ( this->array_of_IPCams[v6]->field_20 )
          do_PostMessageA((CViewer *)this->array_of_IPCams[v6], 0x111u, 0x1391u, 0);
      }
      return (*(int (__thiscall **)(CCriticalSection *))(this->crit_sections[v6].vtbl + 20))(&this->crit_sections[v6]);  // [3]
    }
  }
  return result;
}

CVE-2026-57268 - saveVideo command index-out-of-bound

When sending the saveVideo command, the index field is extracted from the websocket message [1]. Then without checking the range of the index, it is used to trigger a CriticalSection ([2]) and releases it [3]. The release function call ([3]) is executed using a function pointer which will be read out of bounds potentially leading to code execution:

 v6 = get_entry(a2, "index");
  result = json_is_value_int(v6);
  if ( (_BYTE)result )
  {
    v8 = get_entry(a2, "index");
    index = json_value_to_int(&v8->value);  // [1]
    result = CCriticalSection::EnterCritSection(&this->crit_sections[index]);  //[2]
    if ( result )
    {
      if ( this->array_of_IPCams[index] )
      {
        if ( this->array_of_IPCams[index]->field_20 )
          do_PostMessageA((CViewer *)this->array_of_IPCams[index], 0x111u, 0x139Fu, v11);
      }
      return (*(int (__thiscall **)(CCriticalSection *))(this->crit_sections[index].vtbl + 20))(&this->crit_sections[index]); //[3]
    }
  }

CVE-2026-57269 - disconnect command index-out-of-bound

When sending the disconnect command, the index field is extracted from the websocket message:

 index = -1;
  entry = get_entry(json_root, "index");
  if ( json_is_value_int(entry) )
  {
    v3 = get_entry(json_root, "index");
    index = json_value_to_int(&v3->value);
  }

Then without bound-check it is passed to the do_disconnect function:

   if ( this->playback_type == 1 )
    do_PostMessageA(this, 0x13A0u, index, 0);
  else
    do_disconnect(this, index, 0);

The do_disconnect functions handles multiple different cases, some can lead to out-of-bound read and write and calling out-of-bound function pointers ([0]) which could lead to code execution:

      else if ( index >= 0 && CCriticalSection::EnterCritSection(&this->crit_sections[index]) )
    {
      if ( this->array_of_IPCams[index] )
      {
        IPCam5Disconnect2(this->array_of_IPCams[index], 0);
        sub_411290((int)this->array_of_IPCams[index]);
        if ( this->array_of_IPCams[index]->field_20 )
          CWindow::InvalidateRect((CViewer *)this->array_of_IPCams[index], 1);
      }
      (*(void (__thiscall **)(CCriticalSection *))(this->crit_sections[index].vtbl + 20))(&this->crit_sections[index]);  // [0]
    }

CVE-2026-57270 - play command index-out-of-bound

When sending the play command, the index field is extracted from the websocket message:

index = -1;
entry = get_entry(a2, "index");
if ( json_is_value_int(entry) )
{
  v3 = get_entry(a2, "index");
  index = json_value_to_int(&v3->value);
}

Then without checking the range of the index, it is used to trigger a CriticalSection and releases it. The release function call [(0)] is executed using a function pointer which will be read out of bounds potentially leading to code execution:

{
  v4 = CCriticalSection::EnterCritSection(&this->crit_sections[index]);
  if ( v4 )
  {
    if ( this->array_of_IPCams[index] )
      CGvIPCamConnect::play_pause(this->array_of_IPCams[index], 1);
    LOBYTE(v4) = (*(int (__thiscall **)(CCriticalSection *))(this->crit_sections[index].vtbl + 20))(&this->crit_sections[index]);  // [0]
  }
}

In this case, the CGvIPCamConnect::play_pause function can also be called which might lead to more out-of-bound reads and write.

CVE-2026-57271 - pause command index-out-of-bound

When sending the pause command, the index field is extracted from the websocket message:

v8 = -1;
entry = get_entry(a2, "index");
if ( json_is_value_int(entry) )
{
  v4 = get_entry(a2, "index");
  v8 = json_value_to_int(&v4->value);
}

Then without checking the range of the index, it is used to trigger a CriticalSection and releases it. The release function call [(0)] is executed using a function pointer which will be read out of bounds potentially leading to code execution:

  v2 = CCriticalSection::EnterCritSection(&this->crit_sections[v8]);
  if ( v2 )
  {
    if ( this->array_of_IPCams[v8] )
    {
      CGvIPCamConnect::play_pause(this->array_of_IPCams[i], 0);
      do_PostMessageA((CViewer *)this->array_of_IPCams[i], 0x13A1u, 0, 0);
    }
    LOBYTE(v2) = (*(int (__thiscall **)(CCriticalSection *))(this->crit_sections[v8].vtbl + 20))(&this->crit_sections[v8]); // [0]
  }

In this case, the CGvIPCamConnect::play_pause function can also be called which might lead to more out-of-bound reads and write.

CVE-2026-57272 - byPass command index-out-of-bound

When sending the byPass command, the index field is extracted from the websocket message:

  entry = get_entry(roote_node, "index");
  if ( json_is_value_int(entry) )
  {
    v3 = get_entry(roote_node, "index");
    index = json_value_to_int(&v3->value);
  }

Then without checking the range of the index, it is used to trigger a CriticalSection and releases it. The release function call [(0)] is executed using a function pointer which will be read out of bounds potentially leading to code execution:

   if ( CCriticalSection::EnterCritSection(&this->crit_sections[index]) )
    {
      if ( this->array_of_IPCams[index] )
        CGvIPCamConnect::set_bypass(this->array_of_IPCams[index], showPickupBtn, showHandupBtn, showDoorBtn);
      (*(void (__thiscall **)(CCriticalSection *))(this->crit_sections[index].vtbl + 20))(&this->crit_sections[index]);  // [0]
    }
  }

In this case, the CGvIPCamConnect::set_bypass function can also be called which might lead to more out-of-bound writes.

Timeline

2026-03-25 - Initial Vendor Contact
2026-04-21 - Vendor Disclosure
2026-04-28 - Vendor Patch Release
2026-07-01 - Public Release

Credit

Philippe Laulheret of Cisco Talos