PHP语言的特性
1.正则函数
数组绕过
| if(preg_match("/[0-9]/", $unjoke)){
die("you are joker");
}
else(intval($unjoke)){
echo $flag;
}
//intval:用于获取变量的整数值;通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer数值
|
这行代码要求$num
不能是一个数字但是intval($num)
又要求你的变量是数字,这个时候你问gpt
他就会告诉你这是矛盾的
这个时候就要引入一下php函数的数组绕过了
原理:preg_match第二个参数subject要求是字符串,如果传入数组时会返回false,则不会进入if语句
%0a换行符绕过
先来看一个正则表达式:preg_match('/^unjoke$/')
学习过正则的肯定知道这个是匹配一个unjoke
字符串,看如下例子
| <?php
$cmd = $_GET['you'];
if(preg_match('/^unjoke$/', $cmd) && $cmd !== 'unjoke'){
echo 'yes, you are good!'
#eval('')
}
|
这个时候要求你传入进来的是unjoke
字符串但是又不能被正则匹配,还是一个矛盾式,这个时候就可以用换行符绕过
原理:空模式下的正则不会匹配第二行
| payload:you=unjoke%0a
joke
|
PCRE回溯次数限制
这个单独留作业,自己了解一下
2.intval函数
这个前面讲过一点了,就重复一下吧
定义:用于获取变量的整数值;通过使用指定的进制 base 转换(默认是十进制),返回变量<font style="color:#DF2A3F;">var</font>
的<font style="color:#DF2A3F;">integer</font>
数值。<font style="color:rgb(34, 34, 34);">intval()</font>
不能用于<font style="color:rgb(34, 34, 34);">object</font>
。
基本语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | int intval ( mixed $value [, int $base = 10 ] )
参数说明:
$value:要转换成整数的变量。
$base(可选):转换所使用的进制,默认是 10(十进制)。
返回值:
成功时,返回 $value 的整数值。
失败时,返回 0。
空数组返回 0,非空数组返回 1。
$a = '12';
$a = 12;
|
进制转换绕过
要先了解官方文档中注意的内容

第一种情况
| if($num === "1234"){
die("no no no!");
}
if(intval($num,0)===1234){
echo $flag;
}
else{
echo intval($num,0);
}
|
注意第一个if
是字符串的比较,所以可以利用进制转化进行绕过
| 0b?? : 二进制
0??? : 八进制
0x?? : 十六进制
|
payload:
| num = 0x4D2(十六进制)
$num = '0x4D2'
|
第二种情况
| if($num==1234){
die("no no no!");
}
if(preg_match("/[a-z]|[A-Z]/i", $num)){
die("no no no!");
}
if(intval($num,0)==1234){
echo $flag;
}
|
这个例子利用正则把字母ban
掉了,这个时候就要用到八进制了
payload:
字母绕过
看一下intval函数
的一个性质
- 转换字符串时, 会从字符串的开始进行转换,直到遇到一个非数字的字符,如果字符串的第一个字符不是整数,则返回0
那么如下情况你就可以使用这个性质,进行绕过
| if($num == 1234){
die("no no no!");
}
if(intval($num,0)==1234){
echo $flag;
}
|
这个时候你就可以传入
| num = 1234a
$num = '1234a1234'
|
小数点绕过
当题目ban掉了字母和0
,这个时候你就需要小数点绕过了
1
2
3
4
5
6
7
8
9
10
11
12 | if($num==="114514"){
die("no no no!");
}
if(preg_match("/[a-z]|[A-Z]/i", $num)){ //禁用字母
die("no no no!");
}
if(!strpos($num, "0")){ //禁止0开头
die("no no no!");
}
if(intval($num,0)===114514){
echo $flag;
}
|
这个时候你就可以传入
intval函数会帮你把它转换成整形,以此达到绕过的目的
数组绕过
当intval
转换数组时,只会关心数组有没有内容,而不会在乎数组里面是什么

3.MD5函数绕过
md5函数
在PHP中,md5()
函数用于计算字符串的MD5哈希值。
语法:
| md5(string $str, bool $raw_output = false): string
|
参数:
str
:必需。要计算的字符串。
raw_output
:可选。如果为true
,返回原始的16字符二进制格式;如果为false
(默认),返回32字符的十六进制数。
示例:
| <?php
$str = "Hello, World!";
$hash = md5($str);
echo $hash; // 输出:fc3ff98e8c6a0d3087d515c0473f8677
?>
|
假设题目是$md5==md5($md5)
0e开头的数且md5加密后还是0e,<font style="color:#000000;">md5=0e215962017</font>
假设是弱比较md5($a)==md5($b)
== 判断,php中==左右两个变量的类型若不相等,则会转换成相等的类型后再进行判断。
PHP在处理哈希字符串的时候,它把每一个以0E开头并且后面字符均为纯数字的哈希值都解析为0
1
2
3
4
5
6
7
8
9
10
11
12 | //md5加密后以0E开头,且后面均为纯数字
QNKCDZO
240610708
s878926199a
s155964671a
//md5加密后变成万能密码
ffifdyop
//sha1加密后以0E开头,且后面均为纯数字
aaroZmOk
aaK1STfY
|
若使用的是**强类型**
md5强类型绕过
| (string)$_POST['a1']!==(string)$_POST['a2'] && md5($_POST['a1'])===md5($_POST['a2'])
|
则需要左右两个变量类型相等且值相等才会返回true,这时候就需要选择md5后散列值完全相等的一对字符串传入了
| array1=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
array2=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
|
fastcoll
数组绕过强比较
原理:MD5无法处理数组,如果传入数组则返回NULL,两个NULL是相等的。
看如下例子
| if ($_GET['unjoke_a'] != $_GET['unjoke_b']){
if (md5($_GET['unjoke_a']) === md5($_GET['unjoke_b'])){
echo $flag;
}
else{
print 'Wrong.';
}
}
|
payload如下:
| http://url?unjoke_a[]=1&unjoke_b[]=2
|
4.字符串解析特性
PHP语言会将所有参数转换为有效的变量名,就是在解析字符串时:
1)删除空白符
2)将某些字符([,+,.)等等转换为下划线(包括空格)
这个时候又可以整出一些考点了,就比如
| GET或POST方式传进去的变量名,会自动将(空格) + . [
这四个个字符任何一个都行,都可以被处理成_
|
用之前结束的newsatr的部分waf来举例子
| $_GET['NewStar_CTF.2024'] !== 'Welcome' && preg_match('/^Welcome$/', $_GET['NewStar_CTF.2024'])
|
这个你要是直接传入NewStar_CTF.2024=Welcome%0a
就会被解析成NewStar_CTF_2024=Welcome%0a
在PHP_7
中,可以使用[
字符的非正确替换漏洞。当传入的参数名中出现[
且之后没有]
时,PHP 会将[
替换为_
,但此之后就不会继续替换后面的特殊字符了,因此GET
传参NewStar[CTF.2024=Welcome%0a
5.其他函数
还有一些其他函数的绕过这里留作业,自己了解一下
- is_numeric()函数
- strcmp()函数
- sha1()函数
- extract()函数
- in_array()函数
- parse_str()变量覆盖
参考文章ctf中常见php漏洞 - dtwin - 博客园
文件包含
1.文件包含漏洞含义以及成因
服务器执行PHP文件
时,可以通过文件包含函数加载另一个文件中的PHP代码
,并且当PHP
来执行,这会为开发者节省大量的时间。这意味着你可以创建供所有网页引用的标准页眉或菜单文件。当页眉需要更新时,你只更新一个包含文件就可以了,或者当你向网站添加一张新页面时,仅仅需要修改一下菜单文件(而不是更新所有网页中的链接)
利用条件:
| (1)include()等函数通过动态变量的方式引入包含文件。
(2)用户能够控制该动态变量。
|
文件包含函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | 1.include():在执行期间包含并运行指定文件的内容。如果文件不存在或包含错误,include()会生成警告(E_WARNING),但脚本会继续执行。
2.include_once():与include()类似,但如果文件已被包含,则不会再次包含,避免函数重定义或变量重复赋值的问题。
3.require():与include()功能相似,但在文件不存在或包含错误时,会生成致命错误(E_COMPILE_ERROR),并停止脚本执行。
4.require_once():与require()类似,但如果文件已被包含,则不会再次包含。
5.file_get_contents():读取文件的内容并将其作为字符串返回。
6.readfile():读取文件并直接输出其内容。
7.fopen():打开文件或 URL,返回文件指针资源,可用于读取或写入操作。
8.highlight_file():用于对指定文件的代码进行语法高亮显示。 它会根据PHP内置的语法高亮器定义的颜色,将文件内容以高亮格式输出或返回。
|
2.读取敏感文件
| <?php
include($_GET['unjokefile']);
?>
//传入?unjokefile=/etc/passwd
|
不存在waf
的情况下随便读取,给一个常见的敏感文件表格
目录 |
内容 |
/etc/passwd |
Linux系统账号信息 |
/etc/httpd/conf/httpd.conf |
Apache配置信息 |
/etc/my.conf |
MySQL配置信息 |
/usr/etc/php.ini |
PHP配置信息 |
3.利用封装伪协议读取源码
| ?unjokefile=php://filter/read=convert.base64-encode/resource=flag.php
|
我这里就只是告诉你们这些个利用手段,其余的伪协议
还得你们自己学一学
4.包含日志文件Getshell
条件:知道日志文件access.log
的存放位置 ,Apache
默认位置:/var/log/httpd/access_log
,Apache2
是/var/log/apache2/access.log
,nginx
默认位置:<font style="color:rgb(0, 0, 0);background-color:rgb(250, 250, 250);">/var/log/nginx/access.log</font>
利用方法:
就是改请求包的对应部分进行getshell
,用hackbar
和burpsuite
都行,就比如下图

5.参考文章
| https://www.cnblogs.com/Zeker62/p/15322771.html#%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB%E7%9A%84%E5%AE%9A%E4%B9%89
https://wiki.wgpsec.org/knowledge/web/fileincludes.html#%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A8
|
PHP反序列化
1.序列化之后的格式

注意的是private和protected成员
的序列化会有所不同如下例子
| <?php
class test{
private $pub = 'benben';
protected $b = 'asd';
function jineng(){
echo $this->pub;
}
}
$a = new test();
echo serialize($a);
?>
|

里面的空字符url编码是%00
各种类型的标识
1
2
3
4
5
6
7
8
9
10
11
12
13 | a - array
b - boolean
d - double
i - integer
o - common object
r - reference
s - string
S - 转义后的内容(识别16进制的转义字符串,但是转义字符串表示不带x,如\6c)
C - custom object
O - class
N - null
R - pointer reference
U - unicode string
|
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 | <?php
class Person {
public $name;
public $age;
public $gender;
protected $salary;
private $address;
public function __construct($name, $age, $gender, $salary, $address) {
$this->name = $name;
$this->age = $age;
$this->gender = $gender;
$this->salary = $salary;
$this->address = $address;
}
}
$unjoke = new Person('unjoke', '100', 'man', 50, 'china');
echo serialize($unjoke);
echo "\n";
var_dump(serialize($unjoke));
echo "\000"."\n";
echo urlencode("\000");
echo "\n";
echo base64_encode("\000");
|
2.常见魔术方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | __destruct(), 对象销毁的时候调用
__toString(), 类被当成字符串时的回应方法
__construct(),当对象创建(new)时会自动调用,注意在unserialize()时并不会自动调用
__sleep(),serialize()时会先被调用,__sleep()先执行再序列化
__call(),在对象中调用一个不可访问方法时调用
__callStatic(),用静态方式中调用一个不可访问方法时调用
__get(),调用一个不存在的成员变量触发
__set(),设置一个不存在的或者不可访问的类的成员变量时调用
__isset(),当对不可访问属性调用isset()或empty()时调用
__unset(),当对不可访问属性调用unset()时被调用。
__wakeup(),执行unserialize()时,先会调用这个函数
__invoke(),调用函数的方式调用一个对象时的回应方法
__set_state(),调用var_export()导出类时,此静态方法会被调用。
__clone(),当对象复制完成时调用
__autoload(),尝试加载未定义的类
__debugInfo(),当调用 var_dump() 输出一个对象时
|
1.__call( )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | class google
{
public function search()
{
}
public function __call($method, $parameters)
{
var_dump($parameters);
}
}
$google = new google();
$keyword = array("poc"=>"a");
$google->search($keyword);
$google->operate($keyword);
?>
|
结果:

2.__get( )
把不存在的成员属性的名称赋值给参数
1
2
3
4
5
6
7
8
9
10
11
12 | <?php
class test{
public $aa;
function __get($a) {
echo $a;
}
}
$b = new test();
$b->p;
?>
结果就是输出
p
|
3.__set( )
把调用的属性名称赋值给第一个参数,属性的值赋值给第二个参数
1
2
3
4
5
6
7
8
9
10
11
12 | <?php
class NotExists{
public function __set($b,$c)
{
echo $b . '-----' . $c;
}
}
$ne = new NotExists();
$ne->libai = 'xiaoba';
?>
输出结果为:
libai-----xiaoba
|
3.自主学习部分
字符串逃逸
phar反序列化
题目:http://localhost/classshow/1.php