intval($var, $base)

intval() 函数用于获取变量的整数值。其中,$base 参数用于指定进制, base=0 表示根据 $var 开始的数字判断进制:0x0X 开头的为 16 进制,0 开头的为 8 进制,其他为 10 进制

intval() 函数转换数组时,不关心数组的内容,只判断数组中有没有元素。空数组返回 0,非空数组返回 1

md5() & sha1()

1
2
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;

数组不能被 md5()sha1() 加密,对数组加密返回 null

所以可以构造如下 payload 绕过

1
a[]=1&b[]=2

md5(string, true)

  • md5() 函数第二个参数默认为 false,返回 32 字符的十六进制数字,每个字符代表4位二进制数据,总共 128 位
  • md5 第二个参数为 true 时,返回 16 字符的原始二进制数据,每个字节由 8 位组成,总共也是 128 位,但用二进制形式紧凑地表示,而不是人类可读的十六进制文本,所以一般会有乱码

所以可以利用 md5(string, true) 构造出我们想要的字符串,脚本如下,其中 stripos 中填入我们想要构造的字符串

1
2
3
4
5
6
7
8
9
10
# 构造 sql 闭合语句 ' or '
for ($i = 0;;) {
for ($c = 0; $c < 1000000; $c++, $i++)
if (stripos(md5($i, true), '\'or\'') !== false)
echo "\nmd5($i) = " . md5($i, true) . "\n";
echo ".";
}

# 得到 ffifdyop,它的 md5 为 `’or’6XXXX`,其中 `XXXX` 为乱码

md5() 弱比较

在进行 == 弱比较时,会先将两边的变量类型转化成相同的,再进行比较

0e 在比较的时候会将其视作为科学计数法,所以无论 0e 后面是什么,0 的多少次方还是 0

于是可以去寻找明文不同但 MD5 值为 “0exxxxx”,比如 QNKCDZO 和 s878926199a

变量覆盖

$$ 动态变量覆盖

1
$$a = $$b

该语句的效果类似于将 $a 的地址指向 $b ,所以无论 $b 怎么改变值,$a 的值也会跟着改变

extract() 函数变量覆盖

extract() 函数从数组中将变量导入到当前的变量表。该函数使用数组键名作为变量名,使用数组键值作为变量值

1
2
3
4
5
6
<?php
$a = array('nickname' => '1ndex');
extract($a);
echo $nickname;
?>
//运行结果为输出 1ndex

该函数中第二个参数 flags 默认值为 EXTR_OVERWRITE,表示如果有重复的变量名,将会覆盖原有变量

parse_str 函数变量覆盖

parse_str() 函数把字符串解析成多个变量。其作用就是解析字符串并注册成变量

其在注册变量之前不会验证当前变量是否存在,所以直接覆盖掉已有变量

register_globals = on 变量覆盖

全局变量注册,该配置在 PHP5.3 之前,默认开启;PHP5.3 默认关闭,PHP5.6 及 5.7 已经被移除

全局变量设置开启时,传递过来的值(POST/GET/Cookie)会被直接注册为全局变量而使用,这会造成全局变量覆盖

1
2
3
http://127.0.0.1/blfg.php?authorized=1

# 注册 $authorized 为全局变量,值为 1

import_request_variables() 变量覆盖

该函数将 GET/POST/COOKIE 变量导入到全局作用域中,当禁止 register_golbals 却又想用一些全局变量,可以尝试使用该函数

1
2
3
import_request_variables('G');

# string 指定导入哪些变量为全局变量,G 为 GET 变量、P 为 POST 变量、C 为 Cookie 变量

ereg()

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

该函数存在 NULL %00 截断漏洞,读取到 %00 就不继续读取了

==

比较运算符 == 会进行类型转换(称为弱类型比较)

PHP 在进行弱类型比较时,会将字符串转换成整数(如果可能),然后再进行比较
PHP 会从字符串的开头开始读取,直到遇到第一个非数字字符为止。如 '123abc' 会被转换成 123'abc123' 会被转换成 0

trim()

trim() 函数用于删除字符串两端的空白字符或其他预定义字符

1
trim($num)!=='36'

去除普通空格、制表符(%09)、换行符(%0a)、回车符(%0d)、空字节符(%00)、垂直制表符(%0b),但不去除换页符(%0c)

PHP GET/POST 字符串解析特性

PHP在解析查询字符串时,需要将所有参数转换为有效的变量名,需要做两件事:

  1. 删除空白符
  2. 将某些字符转换为下划线(包括空格

添加空白符

有些 WAF 会不让 num 参数传入字母,但是又需要传入字母来构成我们的命令,那么可以在参数之前添加一个空格,这样在 PHP 的语言特性下会默认删除这个空格,但是 WAF 会因为这个空格导致检测不到 num 这个参数,最终导致 WAF 被绕过

1
2
http://node4.buuoj.cn:25591/calc.php?num=a #被拦截
http://node4.buuoj.cn:25591/calc.php? num=a #绕过WAF

转换 [ 为下划线 _

1
if(isset($_POST['CTF_SHOW.COM']))

如上例所示,通常来说,由于 PHP 中变量名只有数字字母下划线,被 GET/POST 传递的变量名中如果含有 . + [ 会被转换成 _

但 PHP 有个特性就是如果传入 [,他被转换为 _ 之后,后面的字符会被保留下来不会被替换,所以可以构造如下 payload 生成 CTF_SHOW.COM 变量

1
CTF[SHOW.COM = 1

get_defined_vars

该函数会返回由所有已定义变量所组成的数组

可以尝试利用该函数获取 $flag 变量

_() 函数

_() 是一个函数,它是 gettext() 函数的简写形式,其具体作用参考:关于PHP中gettext的用法

当需要构造一个函数时,但函数名的过滤条件字母数字,如下,则可以尝试使用 _() 函数绕过

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

PHP PCRE 正则表达式匹配次数特性

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

因此,当遇到下面的匹配机制时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;

}

可以构建一个很大的数据绕过匹配:

1
2
3
4
5
6
7
import requests
url = " "
data = {
'f': 'dotast'*170000+'ctfshow'
}
res = requests.post(url=url,data=data)
print(res.text)

PHP 内置类(原生类)& 匿名类

PHP 的类分为 内置类、用户自定义类、匿名类

内置类

PHP 原生类(Built-in Classes)是在标准 PHP 库中已经封装好的类,它们是由 PHP 本身提供的,不需要用户手动定义。在其中,有些类具有一些功能(例如文件读取、目录遍历等),这就有了利用机会,我们只需要实例化这些类,就可以实现文件读取等敏感操作

常用的内置类有如下几个

  1. Error : Error 是所有PHP内部错误类的基类,用于自动自定义一个 Error。该类中有一个 _toString 方法,如果把它当做字符串使用,就会触发该魔术方法。例如我们对其进行输出操作 echo new Error(),此时就会自动调用 __toString 魔术方法。(适用于 PHP7 及以上版本)

  2. Exception : Exception 是所有用户级异常的基类,它与 Error 类似,也存在一个 _toString 方法。(适用于 PHP5 及以上版本)

  3. SplFileObject : 该类的构造方法可以构造一个新的文件对象用于后续的读取。其大致原理为当类中 __toString 魔术方法被触发时,如果类中内容为存在文件名,那么它会对此文件名进行内容获取

    1
    2
    3
    4
    5
    <?php
    $context = new SplFileObject('/etc/passwd');
    echo $context;

    # 上述代码会输出 `/etc/passwd` 文件的内容,但只能读取一行,想读取多行的话可以 `foreach` 遍历输出
  4. FilesystemIterator : 该类可以理解为一个文件系统迭代器,其构造方法将会创建一个指定目录的迭代器。该类可以用于遍历指定目录中的文件名,其大致原理为当类中 __toString 魔术方法被 echo 等方法触发时,会返回这个迭代器的第一项,亦即返回文件名

    1
    2
    3
    4
    5
    <?php
    $dir=new FilesystemIterator("/");
    foreach($dir as $f){
    echo($f.'<br>');
    }
  5. DirectoryIterator : 该类可以理解为一个目录迭代器,其构造方法将会创建一个指定目录的迭代器,可以获取其指定目录下全部文件名。其利用方法类似于 FilesystemIterator

  6. GlobIterator : 类似于 DirectoryIteratorFilesystemIterator,但是可以通过模式匹配来寻找文件路径

    1
    2
    3
    4
    5
    6
    <?php
    highlight_file(__FILE__);
    $dir=new GlobIterator("/*flag*");
    echo $dir;

    # 匹配当前目录下所有包含 flag 的文件

匿名类

PHP7 的新特性,匿名类是一种没有类名的类,可以用来实例化对象,通过 new class 关键字来实例化匿名类

无字母数字绕过正则表达式

如果题目过滤字母数字,如下所示

1
2
3
4
5
6
7
8
9
<?php
error_reporting(0);
highlight_file(__FILE__);
$code=$_GET['code'];
if(preg_match('/[a-z0-9]/i',$code)){
die('hacker');
}
eval($code);

可以通过脚本通过异或构造出 payload 绕过

system('ls') 可以构造为 ("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%0c%08"^"%60%7b")

具体见 无字母数字绕过正则表达式总结

php_mt_seed

php 通过 mt_srand(seed) 函数通过分发 seed 种子,依靠 mt_rand() 使用 Mersenne Twister 算法返回随机整数

1
2
3
4
<?php  
mt_srand(123);
echo mt_rand();
?>

如何生成的随机整数,它与设置的 seed 值和调用该函数的次数有关

使用 php_mt_seed 即可通过随机数进行种子的爆破

1
time ./php_mt_seed 984489752

从 PHP 4.2.0 开始,随机数生成器自动播种,因此可以不使用 mt_srand 函数。当不使用随机数播种函数时,php 也会自动为随机数播种,因此是否确定种子都不会影响正常运行