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.

Recommended Answers

All 22 Replies

eh... I guess you could say I have my own view which superseeds that

But this really isn't about view on privates, if everything is open then you have everything to lose,
why not just hand over the credentials for all your accounts if you're so willing to open yourself up ;)
(watch what happens when everyone including your enemies is allowed to control your account)

That's what this is about, some stuff NEEDS to be private and can't be accessed outside of a bubble, for example part of an encryption key.
and that's where this metaclass comes in.
Python has no concept of security, so I'm adding one where needed for my API :)

But that doesn't mean I know everything about what I'm doing, which is where I need some help
at least insight as to what to do, for both 1 and 2

What set me on this path to be so focused on security?
Well, for what I mentioned, your account being in your enemy's hands...
That's exactly what happened to me on Discord
A staff member gave my token to a hacker he had relations with all because I was protecting a kid from being picked on by both of them.
You'll never see me on Discord again, they don't care about you, your account, or your security

in fact, that didn't just stop there...
if you have an account on any of these services, you should consider deleting it:
Google, Apple, Microsoft, Facebook, Twitter, Discord, Twitch, Yahoo, Amazon, PayPal, Yelp, Nimses
there's more on my death list, but I'm only mentioning what's important, I don't think EA or EPIC is part of that here ;)

of everything mentioned, the only thing I have an account on is Google, but I'm working to get off it :)

as for that bit on debugging, there's no reason to not write temporary code to make private things public
after all, that's what helped me figure out #2:
stumped.png

obj.__inits__ is not tracking['Context'] (what's expected)
instead
obj.__inits__ is tracking['UGEObject'] (the superclass of Context)

tracking is a temporary variable added to help me peek into the __inits__ private variable
it's been removed now that I know what's going on and need help with solving.

I'm going to stay on the topic of access of privates inside our app. You did expand your questions to what may be web security and more.

Since your code is in Python and that is not on the client but the web server then the client can't access most of the variables and classes in your Python code.

As to all the NEW issues you bring up in your reply, well, that needs its own discussion.

seeing as my last post seems to have been lost by the server, I'll just write a short version...
I never went off topic, just provided examples bringing scopes into view

my initial questions haven't changed from a general standpoint, all I ask is help or insight:
1: how can I restrict the ability to access the mappingproxy??
2: how can I manage the private contexts in a secure way??

Out on a walk I recall something we did long ago to hide values that were at the time IN PLAIN SIGHT if you looked at the compiled code.

Let's say the secret number was coded like:

secret_number = 12345; // Hey that's my luggage combination too.

Since we had to distribute the compiled exe we would then do another operation on the variable so it wasn't so easy to pick off.

answer = value * (secret_number xor magic); // Not terrible secure but better.

Since Python is usually some plain text file, if the server was compromised then all secrets are laid bare but at least we have no real exposure of internal variables and constants from say Python scripts that run on the server and the client that is getting the output in their web browser.

Then again we could broach SQL injection which by now you know how to mitigate.

yeah, encoding does help to hide values in plain sight
but also if said code is compiled

I have to admit I was thinking of working on a protectedtype metaclass that stored encrypted values with the very xor logic you mention ;)
but that's a discussion for another topic

I'm not so much focused on the penetration over interfaces and such, so much as I'm focused on apps that import the library
if the app is written well, there should be no access into the encapsulated bubble that is my API (hackers can not access the data in my API backend no matter how hard they try)
that's the absolute purpose of this metaclass.

the metaclass also serves as a barrier between the backend and frontend of my API
basically I can dictate what sections of my API are allowed to access what, and there's no way to gain access to anything else, despite the API being written in python

currently my exploit defeats this A.__getattribute__.__closure__[1].cell_contents['B'](inst,'value') as there's no way I know of other than using C to restrict access to the mappingproxy at __closure__[1].cell_contents
I'm only asking and hoping someone knows something better than my noob knowledge
(sure it's fair I'm not an average noob, I only hacked the interpreter to figure this stuff out, but I'm still a noob learning things)

but anyways, the main importance really is managing contexts (the issue in my image)
A.__getattribute__ holds a closure to the mappingproxy context relative to it's associate class (not subclass)
meaning it can't be referenced for a relative subclass calling it for operation on it's instance (the frame belongs to the superclass)
(the referenced private attribute is of the superclass, not the expected subclass)

I could probably solve both issues with some sort of context manager that also does frame analysis to manage itself supplied as the closure in place of the mappingproxy :thinking:
with that I could associate superclasses appropriately
though I'm still not sure I could protect the private attributes as I'd like (just make access much more complex)

actually, looking at my #2 issue
I think this is somethign to do with python itself

in the issue displayed in the image, you have Context.__new__ calling UGEObject.__new__ supplying a Context instance...
the function __getsetattr__ in my class should be relative to Context, which further investigation proves is true...
this means that since privateattrs is relative to the Context instance, it should be returning Context.__inits__, not UGEObject.__inits__ as is the case...

I think something's going on that's making __closure__ states unreliable

even an expression evaluation when in __getsetattr__ on privateattrs['__inits__'](inst) returns the correct object
yet what's returned by __getsetattr__ is incorrect.... :/

EDIT:
looking into this issue more, apparently f.f_code in nativecodes unexpectedly returns False
meaning super(cls,inst).__getattribute__(attr) or UGEObject.__inits__ is being returned

I think I've solved that problem
which now makes sense as UGEObject.__new__.__code__ wouldn't be in nativecodes as Context.__new__.__code__ is set from dir(cls)...
I guess I made an oversight

so that means issue #1 is still on the table
I hope someone has an answer :)

the primary issue (#2) of this post is now solved (needs to be fixed, but at least I know what's wrong)

EDIT2:
before I forget to inform

Q: what's the fix?
A: apply the natives (strict) from the superclass hierarchy to the current class natives

since nativecodes is a set, duplicates should be auto-removed
to make access easier, nativecodes can take a private static attribute __natives__ of each superclass while adding privatetype.__new__.__code__ as a global native.
(this shouldn't affect external frame validation privacy)

I think a lot of what you are noting is indirectly covered at https://wiki.python.org/moin/Asking%20for%20Help/How%20do%20you%20protect%20Python%20source%20code%3F

You have to read nearly to the bottom where it writes about Pyarmor and:

  • Encrypt code object by DES to protect constants and literal strings.
  • Obfuscate byte code of each code object when code object completed execution.
  • Clear f_locals of frame as soon as code object completed execution.

So backing up all the way to the top and running at this again, old Python does not secure your internal constants, literal strings and more. It's not what Python does so you have to think of ways such as that XOR use or other ways if you want to obfuscate secrets in Python or for that matter almost all other languages.

I'm not looking for obfuscation though or anything like that
I'm looking for code that's ONLY accessible by class methods

take note the valid examples from above and add these to it:

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

    def method(inst):
        inst.B = 20 # this can NOT be accessed outside of this class

inst = A() # external
B = inst.B # AttributeError 'A' object has no attribute 'B'
A.__dict__['B'] # KeyError 'B'

I have secured everything but that 1 exploit
which when that metaclass is written in C, will also be taken care of
but yeah, I get what you're saying, basically I can't protect that mappingproxy while the code is python

While you are not looking for obfuscation, it's still possible to get at those values as noted in my last link using the tools and inspections they note.

I'm reminded of this old saying. "Any code devised by humans can be decoded by humans." So if you are looking for value security then you never place the final value in a single variable. Use as much security as you see fit for the task.

In parting I'm reminded again of where there was a secret value but the programmer passed it from a web service and they picked it off with a packet sniffer. This was one determined attacker so about all they could have done was to make it a few steps harder by adding some XOR or encryption to the value so it never lives in a variable. Also, Python and the security level you appear to want is something you'll have to work at. I also would take bet few consider this a bug.

I mean, yes I would prefer I didn't have to link a read-only dictionary containing all the protected values through a closure
but I can't use a global, or that just exposes it on the upper local level
a closure is the only thing that seems to keep it contained on a relative level (each time a new class is created)

I don't think I can XOR encode a dictionary/mappingproxy
nor do I think I can reverse id() reference it's memory address (without something like _ctypes anyways, which idk how to do at this current moment)

in any case, you're right about those not considering normal __nonprivate variables a bug
sure the values could easily be hidden away in C memory and referenced on frame lookup, if that's even needed
I know C can do private variables, so why can't we just store a private attribute dictionary on the class struct for lookup
it would do very much the same thing and wouldn't need to do frame validation like my metaclass

but yeah, they wouldn't even consider it, python's an insecure language not meant for actual applications
it's like Guido wants a programming language, but then doesn't want a programming language >.<
unless this is just me actually understanding python never really had a point as a programming language... heh

but anyways, I'm gonna post the fixed version in a bit and mark this as solved then
I'm still cleaning the thing up and checking for anything I can hope to optimize
4 for loops is a bit of a mess for a single function, especially a metaclass... heh

As a test, take your .pyc and decompile it to see if your work at hiding the value is exposed with a decompiler.

https://sourceforge.net/projects/decompyle/

This is why, if there is a secret number or string we can't just assign it to anything directly.

IOW, I don't know how deeply you think about all this but variables with secrets are dangerous as they live for a time where as something transitory will be short lived and not in long term memory. I'm a season microprocessor user (think embedded micros) so back to thinking about how to keep the secrets from being used for a variable directly is one thing then another is if said secret is used to make a system API call which can be exposed with most debuggers.

This rabbit hole goes deep.

I tend to work more in RAM than on the HDD
it doesn't matter what my pyc/text contains, what matters is the randomly generated value in RAM being protected

EDIT: *click*
well I feel stupid now
this whole time you were thinking I was talking about files and static definitions

lol not at all XD

"Bamboozled again."

At some point the code has to exist somewhere. Let's say you distribute your .pyc files for others to run, they decompile, add prints and more?

  1. Security is an illusion?
  2. Now to fix this, make a a software as a service? (SAAS!)

first off, why would you even distribute a pyc
they're not always compatable with different interpreters

just distribute your py files
the HDD is public space, so your code shouldn't matter what's contained and visible
what matters is what your code sets up in RAM, the py files are pointless
the values to be secured are created in RAM based off a given set of parameters with whatever randomizers or hash values are available
it's pointless to secure static values that can always be decrypted and figured out with time
what's important is to make sure whatever private (encrypted?) values in the bubble in your RAM stay in that bubble

if your code is secure, there's nothing to be afraid of, display it all you like, nobody can get past the bubble your code creates

also
eww SAAS, I think I'll stick with FOSS tyvm
I'm not a fan of temporary software, nor do I support/use it

With py files being text what stops me from editing the app and showing what these variables are?

This is why I thought you could compile, and maybe do work in hiding the variables, XOR, and more with Pyarmor.

If you give up the source, where is this secure?

If you give up the source, where is this secure?

ah the age old argument of why open source is better than closed source
the security comes from not being able to break past the bubble even if the code is known

what stops me from editing the app and showing what these variables are?

sure go ahead, modify it all you like, you'll eventually find out how pointless this is once you hit the wall of standards you need to follow for things to work elsewhere.
as such if you develope anything with it, you can't expect your local exploit to work on someone else's system that hasn't been broken.

and that is why FOSS is better
you have more eyes on things to catch any particular exploit that has been found
you can either fix the exploit yourself, or report it to have it fixed by someone knowledgeable

need an example?
Windows is the most insecure popular OS you could possibly run these days
Linux is the most secure popular OS you could run these days

now arguably there is more secure (OpenBSD) and more insecure, but I was just using popular examples
a notable mention though is Android, which is Linux, and open source, but insecure as heck thanks to Google and Samsung
so yes, it's possible to be open source and insecure when under a governing body, and nobody cares about it

it seems to be a big trend that with control comes insecurity, and with freedom comes security

but if I have anything to add, it's not just freedom alone that gives security but awareness about what's secure as well
sure if you had proper standards set and knowledgeable people using common sense and logic to determine what makes things insecure flocking towards you, you could have something closed source that's actually secure.
an example of that would be Comodo Anti-Virus, MEGA, or (not so much) Opera

the problem here though is it's usually harder to secure things (tackle exploits) when dealing with incompitence on closed source
where open source is usually in everyone's control to secure, but incompitence does come here too, just look at Mozilla

my point is you have a much greater chance of tackling security with open source
though at the same time that highly depends on the project as well
tox and retroshare are extremely secure and open source, but they're also dead, so they lack in functionality
where as Discord, Skype, Facebook, and others are closed source, and don't care about security, while being extremely popular.

I guess to put it simply, the REAL security comes from those who care about it
and it's no coincidence that those who care also happen to write FOSS while being unheard of.

Here's the exploit. Let's say you make FOSS of say Opera.

Evil person now takes that and makes an version that keylogs and sends files from the system to some other place.
Evil persons make fake web sites claiming we have the real Opera for download or "version 65 - Newest out today!"

Oh wait, that is exactly what is happening out there.

I feel this is far off topic but it's a real problem out there.

It seems to me that if you have something that really needs to be hidden, instead of creating a complex workaround to try to hide stuff in (sort of) private properties (resulting in something that may be impossible to maintain), you'd be better off writing the critical stuff in C/C++, compiling it, then adding a wrapper the same way that wxPython is a wrapper for wxWidgits.

@Reverend Jim
I mean, I did say the ultimate solution was to write my metaclass in C where I can actually privatize things with the functionality python seems to lack
I was only asking if there was actually some way I may have been unaware of to tackle the only current exploit

@rproffitt
indeed, and I don't count that as an insecurity on my part
in fact if anything, it's my job to prevent things like that from happening, just to maintain security
which I think you can already guess the approach I'd take... heh

also yes it is off topic, but the primary topic was already solved anyways
I'm still working on things...
should have it posted soon

welp, I think this is about as good as it gets:

from sys import _getframe
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__

        nativecodes = { None, __getsetattr__.__code__, newprivatetype.__code__ }; addnativecode = nativecodes.add
        superprivateslots = set()
        for cls in set(bases).union(base.__mro__ for base in bases):
            superprivateslots |= set(getattr(cls,'__private__', superprivateslots))
            try: nativecodes |= cls.__getattribute__(cls,'__natives__') # getattr() doesn't work here
            except: continue # faster logic

        # check for subclass globalization of private attributes
        oldslots = NS.get('__slots__',frozenset())
        for attr in oldslots:
            if attr in superprivateslots:
                raise AttributeError("can't make private attribute '%s' public."%attr)

        # remove private static attributes from NS
        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)|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 i in (item.fget, item.fset, item.fdel):
                    if i: addnativecode(i.__code__) # either None (False) or a function (True)
            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['__natives__'] = getsetstatic(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
newprivatetype = privatetype.__new__

this gets past quite a number of classes initialized in my API, and every last one of them works properly
I mean heck, if that ain't enough, it works so well my debugger lies to me:

debugger_caught_lying.png

though I can't help but get the feeling I'm looking at another issue where my frontend might be able to access __inits__ from UGEObject...
I won't know until I can actually run a frontend script
currently I've only gotten as far as creating the context itself before erroring on the first thing created in the context
yeah I got a long way to go... heh

if anyone else would like to test how well this works for me and suggest any changes, I'd appreciate it :)

minor issue with line 54 in the previous post (can't edit)
here's the fix: (tested)
if i: addnativecode(getattr(i,'__code__',None))
turns out this is actually needed...
I'm getting method-wrapper objects of member_descriptor objects, neither of which have a __code__ attribute.

I guess maybe I should've waited and done a bigger test before posting :P
that's what I get for trying to cut corners for better performance
my bad

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.