Here is the light analysis of all three vulnerabilities that I've submitted to MITRE. Small note, the description of the reserved CVE-2023-30188 must be changed from "Buffer Overflow vulnerability in ONLYOFFICE Document Server 4.0.3 through 7.3.2 allows remote attackers to cause a denial of service via crafted JavaScript file" to "Memory Exhaustion vulnerability in ONLYOFFICE Document Server 4.0.3 through 7.3.2 allows remote attackers to cause a denial of service via crafted JavaScript file"
Docbuilder uses a v8 engine to execute JavaScript code inside of the process. Besides the usual JavaScript code, Docbuilder also implements several custom JS objects to include special functionality dedicated to them. Native Engine is one such object having function callbacks implemented in C++ by means of an embedded v8 engine.
The mentioned Docbuilder can be accessed from the server side by sending a request to /docbuilder
API route. Though, from verion 7.2 an attacker has to know the generated JWT token to be able to communicate with /docbuilder
because JWT tokens were enabled by default starting from version 7.2. Then, JWT tokens are essential to operate with DocumentServer, therefore end users still must have them and the attacker can be one of the end users too.
/docbuilder
route requires a url parameter pointing to the attacker's controlled remote server that has a JavaScript file that will be executed in Docbuilder process.
Below are the relevant members of CNativeControl
class:
class CNativeControl
{
private:
std::wstring m_strFilePath;
std::wstring m_strFileId;
public:
...
BYTE* m_pSaveBinary;
int m_nSaveLen;
int m_nSaveBinaryLen;
...
}
m_pSaveBinary
is the pointer to document's data and m_nSaveLen is the length of this data. The API functions that Native Engine exposes relevant to the vulnerability are the following:
void Save_Alloc(int nLen)
{
m_nSaveLen = nLen;
m_pSaveBinary = new BYTE[m_nSaveLen];
memset(m_pSaveBinary, 0xFF, m_nSaveLen);
}
void Save_ReAlloc(int pos, int len)
{
BYTE* pOld = m_pSaveBinary;
m_nSaveLen = len;
m_pSaveBinary = new BYTE[m_nSaveLen];
memcpy(m_pSaveBinary, pOld, pos);
RELEASEARRAYOBJECTS(pOld);
}
By utilizing Save_Alloc
function, a user can allocate a new chunk of arbitrary size initialized with 0xff
. Then in Save_ReAlloc
this chunk can be freed and a new one is reallocated to store a part of the previous data. In both cases, a new v8::ArrayBuffer
is constructed based on the allocated heap chunk (either allocation from Save_Alloc
or reallocation from Save_ReAlloc
) in function CJSContext::createUint8Array
for the corresponding action implemented in the methods of CNativeControlEmbed
class.
Consider the following set of actions:
- Create a new
v8::ArrayBuffer
object by utilizingSave_Alloc
. - Create a data viewer for this buffer such as
Uint8Array
. In this case,Uint8Array
's backing store will point to the allocatedm_pSaveBinary
. - Execute
Save_ReAlloc
which will free the previous heap chunk pointed bym_pSaveBinary
and allocate a new one.
After these steps, the data viewer's backing store still points to the m_pSaveBinary
which was freed previously. This leads to Use-After-Free because an attacker can manipulate with data viewer to leak or change the content of the freed heap chunk.
By utilizing Save_Alloc
function, a user can allocate a new chunk of arbitrary size initialized with 0xff
. Then in Save_ReAlloc
this chunk can be freed and a new one is reallocated to store a part of the previous data. In both cases, a new v8::ArrayBuffer
is constructed based on the allocated heap chunk (either allocation from Save_Alloc
or reallocation from Save_ReAlloc
) in function CJSContext::createUint8Array
for the corresponding action implemented in the methods of CNativeControlEmbed
class.
Notice that no validation is performed on pos
and len
parameters neither in Save_ReAlloc
nor in CNativeControlEmbed::Save_ReAllocNative
functions.
Consider the following set of actions:
- Create a new
v8::ArrayBuffer
object by utilizingSave_Alloc
with a size equal to 0x100. - Create a data viewer for this buffer such as
Uint8Array
. In this case,Uint8Array
's backing store will point to the allocatedm_pSaveBinary
. - Populate the allocated
m_pSaveBinary
with some attacker's controlled data. - Execute
Save_ReAlloc
withpos = 0x100
andlen = 0x20
.
After these steps, the larger-sized chunk will be copied into the smaller one which clearly leads to Heap Buffer Overflow. The same approach can be applied to copy the data below the source buffer into the destination giving the ability to leak data from the heap section.
Hence, Out-of-Bounds Memory Access vulnerability is present in Save_ReAlloc
function.
By utilizing Save_Alloc
function, a user can allocate a new chunk of arbitrary size initialized with 0xff
. The lifetime of the allocated object is not limited, therefore executing Save_Alloc
in an infinite loop will lead to the Denial of Service on DocumentServer application and possibly the host system as well.
To reproduce this issue, the following JavaScript code can be used:
builder.CreateFile("docx");
engine = CreateNativeEngine();
while (true) {
engine.Save_AllocNative(0x808);
}
Here is the patch file: https://github.com/ONLYOFFICE/core/commit/2b6ad83b36afd9845085b536969d366d1d61150a
You need to pay attention to the changes in DesktopEditor/doctrenderer/embed/jsc/jsc_NativeControl.mm
file that remove the functionality containing the vulnerabilities