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
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.
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)
GeoWebPlayer - http://ovision.com.tw/
8.3 - CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CWE-129 - Improper Validation of Array Index
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.
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]);`
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]
}
}
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]
} }
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.
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.
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;
}
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]
}
}
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]
}
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.
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.
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.
2026-03-25 - Initial Vendor Contact
2026-04-21 - Vendor Disclosure
2026-04-28 - Vendor Patch Release
2026-07-01 - Public Release
Philippe Laulheret of Cisco Talos