php

现在学应该不会太晚?,,ԾㅂԾ,,

基础项

比较

== 只比较值,不比较类型

=== 除了比较值,也比较类型

0 == false: bool(true)

0 === false: bool(false)

ps:在php中 "字符串"==0 是成立的

输出

  • echo - 可以输出一个或多个字符串

  • print- 只允许输出一个字符串,返回值总为 1

数组

array() 函数用于创建数组

1
2
3
4
<?php
$cars=array("Hello","CTF");
echo "I like " . $cars[0] . " " . $cars[1] . ".";
?>

魔术常量

行如 __FILE__ 这样的 __XXX__ 预定义常量,被称为魔术常量。

1
2
__FILE__ //返回文件的完整路径和文件名
highlight_file(__FILE__); //代码高亮的显示当前文件内容

表单数据

$_GET —— 接受 GET 请求传递的参数。

示例: example.com/index.php?book=HELLOCTF,你可以使用 $_GET['book'] 来获取相应的值。

$_POST —— 接受 POST 请求传递的参数。

示例:对 example.com/index.php 进行 POST 传参,参数名为 book 内容为 HelloCTF,你可以使用 $_POST['book'] 来获取相应的值。

$_REQUEST —— 接受 GET 和 POST 以及 Cookie 请求传递的参数。 示例:

  • 如果你通过 URL 传递了一个参数 example.com/index.php?key=value_from_get,你可以通过$_REQUEST['key'] 获取这个值。

  • 如果你通过 POST 方法提交了一个表单,其中有一个名为 key 的字段且其值为value_from_post,你也可以通过 $_REQUEST['key'] 获取这个值。

  • 同时,如果你设置了一个名为 key 的 cookie,其值为 value_from_cookie,你还是可以使用 $_REQUEST[‘key’] 来获取这个值。

懒人必备^o^/

函数

一般来说名字≈功能,所以理解大于记忆,列几个常见的

  • assert() : 用于调试,检查一个条件是否为 true。

  • unserialize() : 将一个已序列化的字符串转换回 PHP 的值。例如: $array = unserialize($serializedStr) 可以将一个序列化的数组字符串转换为数组。

  • mysql_query(): 发送一个 MySQL 查询。

函数安全

常见绕过

1
2
3
4
5
6
7
<?php
$a = $_GET['a'];
if (is_numeric($a)) {
    exit();
} elseif ($a == 404) {
    echo "flag";
}

想要绕过可以在数字前面或者后面加上 %0a %0b %0c %0d %09

例如: ?a="404%0a"

弱类型比较

strcmp()

当 strcmp 比较出错后,会返回 null,null 则为 0 。

1
2
3
4
5
6
$flag = 'flag{123}';
if (strcmp($flag, $_GET['str']) == 0) {
    echo $flag;
}else{
    echo "Out!";
}

为了使 strcmp 比较出错,可以传入一个数组

Payload: ?str[]

is_switch()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$a = "233a"; 
$flag = "flag{Give you FLAG}";
switch ($a) {
    case 1:
        echo "No Flag";
        break;
    case 2:
        echo "No Flag";
        break;
    case 233:
        echo $flag;
        break;
    default:
        $a = 233;
        echo "Haha...";
}

case 会自动将字符转换成数值,即遇到非数字就停止

md5()

md5 在处理哈希字符串的时候,如果 md5 编码后的哈希值时 0e (科学计数法)开头的,都一律解释为 0

例如:

1
2
3
4
5
6
7
8
$flag = "flag{THIS_IS_REAL_FLAG}";
$v1 = $_GET['gat'];
$v2 = $_GET['tag'];
if ($v1 != $v2 && md5($v1) == md5($v2)) {
    echo $flag;
}else{
    echo "Out!";
}

此时找到两个不相同但md5编码后以0e开头的字符串即可

如果是这样:

1
2
3
4
5
6
$flag = "flag{THIS_IS_REAL_FLAG}";
$str1 = $_GET['gat'];
$str2 = $_GET['tag'];
if (md5($str1) === md5($str2)) {
    echo $flag;
}

用上述 0e 方法自然是不可行的(注意:=== ),这时候就得使用数组来绕过了,如果传入一个数组的值,会报出错误(md5 只能使用字符串),报错后就相当于绕过 === 这个条件了

注: 在 PHP 8.0.0 时,该方法行不通了

如果遇到不能传入数组,只能传入字符串的时候,如下例

1
2
3
4
5
6
7
8
$flag = "flag{THIS_IS_REAL_FLAG}";
$str1 = $_GET["gat"];
$str2 = $_GET["tag"];
if((string)$str1 !== (string)$str2 && md5($str1)===md5($str2)){
    echo "flag{THIS_IS_REAL_FLAG}";
}else{
    echo "Out!";
}

这时候就得需要 md5 碰撞,上面判断条件的意思是,str1 和 str2 内容必须不同,但是 md5 必须相同(详细方法待整理)

sha1()

sha1 的参数不能为数组,传入数组会返回 NULL

1
2
3
4
5
6
7
8
9
$flag = "flag{Chain!}";
$get = $_GET['get'];
$teg = $_GET['teg'];
if ($get != $teg && sha1($get) === sha1($teg)) {
#if ($get != $teg && sha1($get) == sha1($teg)) {
    echo $flag;
}else{
    echo 'Out!';
}

Payload: ?get[]=&teg[]=1

变量覆盖漏洞

环境得先开启

  register_globals=ON  //此时传递的参数会自动注册为全局变量

最简单的一种就是我们传入参数例如 ?id=1 把原来id的值给替换

$$

类似于c++的指针,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$a = "A"; #
$b = "B"; # 
echo $a . "<br>";
echo $b . "<br>";
foreach ($_GET as $key => $value) {
    $$key = $value;
}
echo $a . "<br>";
echo $b . "<br>";
echo $key . "<br>";
echo $$key . "<br>";

传入

    ?a=I'm A&b=I'm B   

后,得到

 A
 B
 I'm A
 I'm B
 b
 I'm B

这里使用了 foreach 来遍历数组的值,遍历 I'm B 到这一项,后面的代码表示把数组的键名b赋值给key,数组的键值赋值给value,并且key可以指向value

就当成指针理解吧[]`( ̄▽ ̄)’*

extract()

描述:extract(array,flags,prefix)

功能​:将数组元素动态转换为变量,常用于处理表单数据(如 $_POST、$_GET)或配置数组。

flags: 可选,控制冲突处理方式(默认 EXTR_OVERWRITE) 常用值:

  • EXTR_OVERWRITE:如果有冲突,覆盖已有变量
  • EXTR_SKIP:跳过冲突
  • EXTR_PREFIX_ALL:如果有冲突,为所有变量添加前缀

prefix: 可选,仅在指定flags为前缀模式时有效,用于生成变量名前缀。

parse_str()

描述:parse_str(str) 用于将字符串解析成多个变量,没有返回值。简而言之,就是你输入(“a=1&b=2”)它会自动地生成a,b两个变量

伪协议

php://filter

php://filter/read or write=/resource=数据流
  • resource=< 要过滤的数据流> (必须) 它指定了你要筛选过滤的数据流

  • read和write可选对应对筛选列表的操作

常用过滤器convert.base64

  • 编码 convert.base64-encode
  • 解码 convert.base64-decode

例如

php://filter/read=convert.base64-encode/resource=files.txt

更多的可以看P神的谈一谈php://filter的妙用

php://input

如果 html 表单编码设置为"multipart/form-data",请求是无效的。 一般在 CTF 中用于执行 php 代码(一般在bp里使用)

需要在php 中设置 allow_url_include = On

zip:// & bzip2 & zlib://

需要:

  • allow_url_fopen: on

  • allow_url_include: on

zip的一般格式:

zip://[压缩文件绝对路径]%23[压缩文件内的子文件名](#编码为%23)

file://

和zip一样需要打开两个_url_,用于访问本地文件系统,在 CTF 中通常用来读取本地文件

data://

常用于绕过php://过滤,直接执行PHP代码

语法:

data:[<mediatype>][;charset=<encoding>][;base64],<data>

​- mediatype​:数据类型(如text/plain、image/png、text/html)。

​- charset​:字符编码(如utf-8)。

​- base64​:若数据需编码则添加此标识。

​- data​:实际内容(如PHP代码或文件路径)。

需存在文件包含漏洞(如 include($_GET['file']) )和打开两个_url_

序列化及反序列化

  • 序列化 是将 PHP 对象转换为字符串的过程,可以使用 serialize() 函数来实现。该函数将对象的状态以及它的类名和属性值编码为一个字符串。序列化后的字符串可以存储在文件中,存储在数据库中,或者通过网络传输到其他地方。

  • 反序列化 是将序列化后的字符串转换回 PHP 对象的过程,可以使用 unserialize() 函数来实现。该函数会将序列化的字符串解码,并将其转换回原始的 PHP 对象。

普通对象注意 protectedprivate 类型的变量中都加入了不可见字符:

如果是 protected 变量,则会在变量名前加上 \x00*\x00

如果是 private 变量,则会在变量名前加上 \x00类名

一般我们在输出的时候都会先编码后输出

自定义类时,例如:

1
2
3
4
5
6
7
public function serialize() {
        return serialize([
            'name' => $this->name,
            'email' => $this->email,
            'phoneNumber' => $this->phoneNumber,
        ]);
    }

此时输出的格式如图:

by hello ctf

其它一些标识,一般都是英文首字母

  • b:boolean bool 值

  • C:custom object 自定义对象序列化

  • d:double 小数

  • i:integer 整数

  • O:Object 对象

  • r:reference 对象引用 && R:pointer reference 指针引用

  • S:encoded string

  • N:null NULL 值

魔术方法

在 PHP 的序列化中,魔术方法(Magic Methods)是一组特殊的方法,这些方法以双下划线(__)作为前缀,可以在特定的序列化阶段触发从而使开发者能够进一步的控制 序列化 / 反序列化 的过程。

一般在题目中常见的几个方法如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 __wakeup() //------ 执行unserialize()时,先会调用这个函数
 __sleep() //------- 执行serialize()时,先会调用这个函数,只能返回数组
 __construct() //------- 适合在使用对象之前做一些初始化工作
 __destruct() //---- 某个对象的所有引用都被删除或者当对象被显式销毁时执行
 __call() //-------- 在对象上下文中调用不可访问的方法时触发
 __callStatic() //-- 在静态上下文中调用不可访问的方法时触发
 __get() //--------- 用于从不可访问的属性读取数据或者不存在这个键都会调用此法
 __set() //--------- 用于将数据写入不可访问的属性
 __isset() //------- 在不可访问的属性上调用isset()或empty()触发
 __unset() //------- 在不可访问的属性上使用unset()时触发
 __toString() //---- 把类当作字符串使用时触发
 __invoke() //------ 当尝试将对象调用为函数时触发

详细可参考:PHP:魔术方法 - Manual

属性重载

  • 在给不可访问(protected 或 private)或不存在的属性赋值时,__set() 会被调用。
  • 读取不可访问(protected 或 private)或不存在的属性的值时,__get() 会被调用。
  • 当对不可访问(protected 或 private)或不存在的属性调用 isset() 或 empty() 时,__isset() 会被调用。
  • 当对不可访问(protected 或 private)或不存在的属性调用 unset() 时,__unset() 会被调用。

详细可参考:PHP:重载 - Manual

本文参考资料:Hello CTF (其实就是摘抄啦)

我的奶龙为什么显示不出来┗|`O′|┛

生活由投入其中的每一天构成
使用 Hugo 构建
主题 StackJimmy 设计