It was found that libreoffice before versions 6.0.7 and 6.1.3 was vulnerable to a directory traversal attack which could be used to execute arbitrary macros bundled with a document. An attacker could craft a document, which when opened by LibreOffice, would execute a Python method from a script in any arbitrary file system location, specified relative to the LibreOffice install location.
space-r7 at May 09, 2019 5:57pm UTC reported:
LibreOffice offers the ability to create program events that when triggered, will execute a macro. LibreOffice gives the option to develop custom macros or select a macro from a list of scripts included with the installation. The included macros are written in a variety of languages, including Python.
Creating a mouse over event that will execute a macro upon hovering over a hyperlink will result in XML that looks similar to this:
<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|TableSample.py$createTable?language=Python&location=share" xlink:type="simple"/>
Alex Inführ discovered that a directory traversal vulnerability exists in the
xlink:href attribute, allowing the ability to call functions (with its arguments) of other Python scripts included with the LibreOffice installation. The
tempfilepager() function in
program/python-core-3.5.5/lib/pydoc.py was found to both accept function arguments and pass those arguments to
os.system(), allowing for arbitrary code execution.
def tempfilepager(text, cmd): """Page through text by invoking a program on a temporary file.""" import tempfile filename = tempfile.mktemp() with open(filename, 'w', errors='backslashreplace') as file: file.write(text) try: os.system(cmd + ' "' + filename + '"') finally: os.unlink(filename)
The directory traversal vulnerability stems from how the URI in the
xlink:href attribute is converted to the actual URI of the Python script on disk. The function that does this conversion is located in
def scriptURI2StorageUri( self, scriptURI ): try: myUri = self.m_uriRefFac.parse(scriptURI) ret = self.m_baseUri + "/" + myUri.getName().replace( "|", "/" ) log.debug( "converting scriptURI="+scriptURI + " to storageURI=" + ret ) return ret except UnoException as e: log.error( "error during converting scriptURI="+scriptURI + ": " + e.Message) raise RuntimeException( "pythonscript:scriptURI2StorageUri: " +e.getMessage(), None ) except Exception as e: log.error( "error during converting scriptURI="+scriptURI + ": " + str(e)) raise RuntimeException( "pythonscript:scriptURI2StorageUri: " + str(e), None )
scriptURI variable passed to the function is the attacker-controlled path. In the line
ret = self.m_baseUri + "/" + myUri.getName().replace( "|", "/" ), the local scripts path gets built.
m_baseUri, the base installation path, gets concatenated with a
/ and the controllable path (with
vnd.sun.star.script: removed) after any
| characters are replaced with
The final storage URI
ret would look like this on a Linux LibreOffice installation:
Assessed Attacker Value: 4
Assessed Attacker Value: 4Assessed Attacker Value: 3