This has nothing to do with using property. It's explained in the official FAQ as Why do lambdas defined in a loop with different values all return the same result?.
When you do this:
properties = ['a', 'b', 'c']
for p in properties:
    f = property(lambda self: p)
    print p # you see a, b, c
    print f # you can see it is 3 different objects
    setattr(MyClass, p, f)
… each f is a closure over the p variable.* This means that, when you call that f, it will return the current value of p within its defining scope, not the value of p at the time at which the closure was constructed. At the end of the loop, p's value is 'c', so that's what all of your properties return.
You can get around this by (among other solutions**) using the "default value hack":
properties = ['a', 'b', 'c']
for p in properties:
    f = property(lambda self, p=p: p)
    print p # you see a, b, c
    print f # you can see it is 3 different objects
    setattr(MyClass, p, f)
Now, each property has a parameter named p, whose default value is the value of p at function definition time. When called (without supplying a p argument), the body ends up with a local variable p with that value, instead of a closure variable p referring to the non-local scope.
* Actually, it's not technically a closure, because the defining scope is global. But it acts the same as if it were done inside another scope.
** There's also the JavaScript idiom of creating a new scope to define the function within, (lambda p: lambda self: p)(p), or functools.partial, or creating a callable object instead of a function, or… But, despite having the word "hack" in the name, the "default value hack" is a well-known Python idiom, enshrined in the docs and used within the stdlib, and it's usually the best answer, unless you have some other reason for another scope or a partial or a custom callable.