CVE-2023-26151 (Python, published 2023-10-03T05:15Z)
Description: value: Versions of the package asyncua before 0.9.96 are vulnerable to Denial of Service (DoS) such that an attacker can send a malformed packet and as a result, the server will enter into an infinite loop and consume excessive memory.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-835 \u2014 Loop with Unreachable Exit Condition ('Infinite Loop')
The product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop.
Repository: https://github.com/FreeOpcUa/opcua-asyncio
Files changed:
asyncua/server/binary_server_asyncio.py
tests/test_connections.py
Commit message: server handle malformed packets

fix test after rebase

server handle malformed packets

close connection on error

reorder imports

change test


Vulnerable code:
                    logger.debug('Not enough data while parsing header from client, waiting for more')
                if len(buf) < header.body_size:
                    logger.debug('We did not receive enough data from client. Need %s got %s', header.body_size,
                                 len(buf))
                    return
                # we have a complete message
                self.messages.put_nowait((header, buf))
                self._buffer = self._buffer[(header.header_size + header.body_size):]
from asyncua import Client, Server


Fixed code:
                    # a packet should at least contain a header otherwise it is malformed (8 or 12 bytes)
                    logger.debug('Not enough data while parsing header from client, empty the buffer')
                    self.transport.close()
                if header.header_size + header.body_size <= header.header_size:
                    # malformed header prevent invalid access of your buffer
                    logger.error(f'Got malformed header {header}')
                    self.transport.close()
                else:
                    if len(buf) < header.body_size:
                        logger.debug('We did not receive enough data from client. Need %s got %s', header.body_size,
                                    len(buf))
                        return
                    # we have a complete message
                    self.messages.put_nowait((header, buf))
                    self._buffer = self._buffer[(header.header_size + header.body_size):]
import asyncio
import struct
from asyncua import Client, Server, ua
async def test_dos_server(opc):
    # See issue 1013 a crafted packet triggered dos
    port = opc.server.endpoint.port
    async with Client(f'opc.tcp://127.0.0.1:{port}') as c:
        # craft invalid packet that trigger dos
        message_type, chunk_type, packet_size = [ua.MessageType.SecureOpen, b'E', 0]
        c.uaclient.protocol.transport.write(struct.pack("<3scI", message_type, chunk_type, packet_size))
        # sleep to give the server time to handle the message because we bypass the asyncio
        await asyncio.sleep(1.0)
        with pytest.raises(ConnectionError):
            # now try to read a value to see if server is still alive
            server_time_node = c.get_node(ua.NodeId(ua.ObjectIds.Server_ServerStatus_CurrentTime))
            await server_time_node.read_value()