Quantcast
Channel: DaniWeb Solved Topics
Viewing all articles
Browse latest Browse all 587

Need some help with secure attribute access

$
0
0

So I've successfully written a metaclass that gives python the functionality of private attributes in classes.

Q: But what about inst.__private?
A:

>>> class A(object):
    __slots__ = ['__private']

    def __new__(cls):
        inst = object.__new__(cls)
        inst.__private = 10
        return inst

    def showprivate(inst):
        return inst.__private

>>> inst = A()
>>> inst.showprivate()
10
>>> inst._A__private = 20 # security through obscurity never works
>>> inst.showprivate() # sorry not private
20

With my metaclass, private attributes are inaccessible outside native and super-native namespaces:

class A(object, metaclass=privatetype):
    __slots__ = ['A']
    __private__ = ['B'] # NOTE: __slots__ is enforced for security regardless of local definition

    def __new__(cls): # native
        inst = object.__new__(cls)
        inst.B = 10 # can only be accessed here
        return inst

class B(A):
    # NOTE: __slots__ = ['B'] is not allowed
    __private__ = A.__private__ # inheritance would add insecurity
    # ^ with this we can restrict specific private attributes to superclasses

    def method(inst):
        return inst.B # or here (including further subclasses)

# NOTE: you can not add methods after class creation and expect to access private attributes.

So how does it work?
The answer is frame validation, to make sure we're in a native namespace before looking up private attributes from an external dictionary.

Since the code for this is currently written in python,
it's currently exploitable by accessing __closure__[1].cell_contents of either B.__getattribute__ or B.__setattr__ (both are the same function).
This will give you access to a mappingproxy (read-only dict) of ['attr'](inst, *val) functions,
the functions return either static values, or the result of a member_descriptor (__get__/__set__) call.

What's __closure__[0].cell_contents?
It's a frozenset (read-only set) containing native code objects the function uses to validate frame f_code objects with.
This would only be useful to a hacker if they could modify it, allowing them to add "native" functions to classes to manipulate private attributes.

So there's 2 things I want to ask here before I show the code for the metaclass.

1: is it possible to restrict access to the mappingproxy to close the final backdoor and prevent external access to manipulating private attributes??

2: I'm having an issue with super-native functions where calling a super-native while supplying the class/instance operates on super-class private attributes...

To explain this a bit further:

class A(object, metaclass=privatetype):
    __private__ = ['B']

    B = {'A':10, 'B':20} # static

    def __new__(cls):
        inst = object.__new__(cls)

        for k,v in inst.B.items():
            print( '%s = %s'%(k,v) )

        return inst

class B(A):
    __private__ = A.__private__

    B = {'A':30, 'B':40, 'C':50} # static

    def __new__(cls):
        return A.__new__(cls)

When we call inst = B() the result prints this: (hash order ignored)

A = 10
B = 20

This is because we're calling A.__new__ which has the private context of class A instead of B as expected

How can I use the context of class B without compromising security??

For the metaclass code, keep in mind this isn't final, so it's still a bit messy:

from sys import _getframe, _functools import reduce
class _c(object): __slots__=['_a']; _m=lambda i: None
mappingproxy = _c.__dict__.__class__; method = _c()._m.__class__; del _c # yeeted

getstatic = lambda value: lambda inst, *val: None if val else value # getset for static items
newtype = type.__new__
class privatetype(type):
    def __new__( typ, name, bases, NS ):
        # won't be so hacky in C
        def __getsetattr__(inst,attr,*val):
            # return typical methods from super-class (extended security for preventing access here)
            if attr == '__setattr__': return None if val else super(cls,inst).__setattr__
            if attr == '__getattribute__': return None if val else super(cls,inst).__getattribute__
            try:
                f = _getframe(1)
                return privateattrs[attr](inst,*val) if f.f_code in nativecodes else( # getset private attribute
                    super(cls,inst).__setattr__(attr,*val) if val else super(cls,inst).__getattribute__(attr) ) # normal attribute
            finally: del f
        NS['__getattribute__'] = NS['__setattr__'] = __getsetattr__
        oldslots = NS.get('__slots__',frozenset()) # backup

        # check for subclass globalization of private attributes
        superprivateslots = reduce(frozenset.union, (frozenset(getattr(cls,'__private__', frozenset())) for cls in bases))
        for attr in oldslots:
            if attr in superprivateslots:
                raise AttributeError("can't make private attribute '%s' public."%attr)

        # remove private static attributes from NS
        nativecodes = { None, __getsetattr__.__code__ }; addnativecode = nativecodes.add
        privateattrs = {}
        privateslots = set(NS.get('__private__', set()))
        for privateattr in privateslots:
            if privateattr in NS: # make static
                item = NS.pop(privateattr)
                privateattrs[privateattr] = getsetstatic(item)
                addnativecode(getattr(item, '__code__', None)) # private methods are native too

        # create private members
        NS['__slots__'] = frozenset(oldslots).union(frozenset(privateslots.difference(privateattrs))) # exclude static
        cls = newtype(typ, name, bases, NS)

        # remove remaining private items and add super-natives
        for attr in dir(cls): # dir() to get ALL public items, not just cls.__dict__ local items
            item = getattr(cls,attr)
            if isinstance(item, staticmethod): item = item.__func__
            if isinstance(item, property):
                for a in ('fget','fset','fdel'): addnativecode(getattr(getattr(item,a), '__code__', None))
            else: addnativecode(getattr(item, '__code__', None))
            if attr in privateslots:
                delattr(cls, attr)
                privateattrs[attr] = method(lambda dsc, inst,*val: dsc.__set__(inst,*val) if val else dsc.__get__(inst), item) # getset method

        # freeze to prevent modification (won't be so easy to access once written in C)
        nativecodes = frozenset(nativecodes)
        privateattrs = mappingproxy(privateattrs) # not sure why this isn't builtin
        # note that private mutable objects can still be modified

        cls.__slots__ = oldslots
        return cls

No I will not follow PEP8, I'm sorry if you're offended.


Viewing all articles
Browse latest Browse all 587

Trending Articles