CVE-2024-2912 (Python, published 2024-04-16T00:15Z)
Description: value: An insecure deserialization vulnerability exists in the BentoML framework, allowing remote code execution (RCE) by sending a specially crafted POST request. By exploiting this vulnerability, attackers can execute arbitrary commands on the server hosting the BentoML application. The vulnerability is triggered when a serialized object, crafted to execute OS commands upon deserialization, is sent to any valid BentoML endpoint. This issue poses a significant security risk, enabling attackers to compromise the server and potentially gain unauthorized access or control.
Severity: nan (CVSS v3 base score: )
Weakness: CWE-1188 \u2014 Initialization of a Resource with an Insecure Default
The product initializes or sets a resource with a default that is intended to be changed by the administrator, but the default is not secure.
Repository: https://github.com/bentoml/bentoml
Files changed:
src/_bentoml_impl/client/__init__.py
src/_bentoml_impl/client/http.py
src/_bentoml_impl/loader.py
src/_bentoml_impl/server/app.py
src/_bentoml_impl/server/serving.py
src/_bentoml_impl/worker/service.py
Commit message: feat(client): disallow media type other than json for HTTP clients (#4520)

* feat(client): disallow media type other than json for HTTP clients

Signed-off-by: Frost Ming <me@frostming.com>

* fix: check media type on server side

Signed-off-by: Frost Ming <me@frostming.com>

---------

Signed-off-by: Frost Ming <me@frostming.com>

Vulnerable code:
from .http import AsyncHTTPClient
from .http import SyncHTTPClient
    Example:

        with SyncHTTPClient("http://localhost:3000") as client:
            resp = client.call("classify", input_series=[[1,2,3,4]])
            assert resp == [0]
            # Or using named method directly
            resp = client.classify(input_series=[[1,2,3,4]])
            assert resp == [0]
    Example:

        async with AsyncHTTPClient("http://localhost:3000") as client:
            resp = await client.call("classify", input_series=[[1,2,3,4]])
            assert resp == [0]
            # Or using named method directly
            resp = await client.classify(input_series=[[1,2,3,4]])
            assert resp == [0]

    .. note::

        If the endpoint returns an async generator, it should be awaited before iterating.

        Example:

            resp = await client.stream(prompt="hello")
            async for data in resp:
                print(data)
        bento_path = pathlib.Path(".").absolute()
    if bento_path != pathlib.Path("."):
        extra_python_path = str(bento_path.absolute())
    def __call__(self, is_main: bool = False) -> Starlette:
        if is_main:
            "--main",
@click.option("--main", "is_main", type=click.BOOL, default=False, is_flag=True)
    is_main: bool = False,
    app_factory = ServiceAppFactory(service)
    asgi_app = app_factory(is_main=is_main)

Fixed code:
from .http import AsyncHTTPClient as _AsyncHTTPClient
from .http import SyncHTTPClient as _SyncHTTPClient


class SyncHTTPClient(_SyncHTTPClient):
    """A synchronous client for BentoML service.

    Args:
        url (str): URL of the BentoML service.
        token (str, optional): Authentication token. Defaults to None.
        timeout (float, optional): Timeout for the client. Defaults to 30.

    Example::

        with SyncHTTPClient("http://localhost:3000") as client:
            resp = client.call("classify", input_series=[[1,2,3,4]])
            assert resp == [0]
            # Or using named method directly
            resp = client.classify(input_series=[[1,2,3,4]])
            assert resp == [0]
    """

    def __init__(
        self, url: str, *, token: str | None = None, timeout: float = 30
    ) -> None:
        super().__init__(url, token=token, timeout=timeout)


class AsyncHTTPClient(_AsyncHTTPClient):
    """An asynchronous client for BentoML service.

    Args:
        url (str): URL of the BentoML service.
        token (str, optional): Authentication token. Defaults to None.
        timeout (float, optional): Timeout for the client. Defaults to 30.

    Example::

        async with AsyncHTTPClient("http://localhost:3000") as client:
            resp = await client.call("classify", input_series=[[1,2,3,4]])
            assert resp == [0]
            # Or using named method directly
            resp = await client.classify(input_series=[[1,2,3,4]])
            assert resp == [0]

            # Streaming
            resp = client.stream(prompt="hello")
            async for data in resp:
                print(data)
    """

    def __init__(
        self, url: str, *, token: str | None = None, timeout: float = 30
    ) -> None:
        super().__init__(url, token=token, timeout=timeout)
    .. note:: Inner usage ONLY
    .. note:: Inner usage ONLY
        bento_path = pathlib.Path(".")
    bento_path = bento_path.absolute()
    if bento_path != pathlib.Path(".").absolute():
        extra_python_path = str(bento_path)
from http import HTTPStatus
        is_main: bool = False,
        self.is_main = is_main
    def __call__(self) -> Starlette:
        if self.is_main:
        if self.is_main and media_type == "application/vnd.bentoml+pickle":
            # Disallow pickle media type for main service for security reasons
            raise BentoMLException(
                "Pickle media type is not allowed for main service",
                error_code=HTTPStatus.UNSUPPORTED_MEDIA_TYPE,
            )
    asgi_app = ServiceAppFactory(
        service, is_main=component_context.component_type == "entry_service"
    )()