Web安全防护(二)
点击劫持
点击劫持,也称UI覆盖攻击
1.1 iframe覆盖攻击
黑客创建一个网页,用iframe包含了目标网站,并且把它隐藏起来。做一个伪装的页面或图片盖上去,且按钮与目标网站一致,诱导用户去点击。伪装的按钮背后可能就会执行了危险操作。
<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html;
charset=utf-8">
<head>
<title>点击劫持 POC</title>
<style>
iframe {
width: 1440px;
height: 900px;
position: absolute;
top: -0px;
left: -0px;
z-index: 2;
-moz-opacity: 0;
opacity: 0;
filter: alpha(opacity=0);
}
button {
position: absolute;
top: 270px;
left: 1150px;
z-index: 1;
width: 90px;
height:40px;
}
</style>
</head>
<body>
<button>美女图片</button>
<img src="http://pic1.win4000.com/wallpaper/2018-03-
19/5aaf2bf0122d2.jpg">
<iframe src="http://i.youku.com/u/UMjA0NTg4Njcy"
scrolling="no"></iframe>
</body>
</html>
防护方案:
使用一个HTTP头-X-Frame-Options。它可以设置frame的加载权限。它有三个可选的值:
- DENY:浏览器会拒绝当前页面加载任何frame页面
- SAMEORIGIN:frame页面的地址只能为同源域名下的页面
- ALLOW-FROM uri:允许frame加载的页面地址
nginx配置:
add_header X-Frame-Options SAMEORIGIN;
1.2 图片覆盖攻击
图片覆盖攻击,攻击者使用一张或多张图片,利用图片的style或者能够控制的CSS,将图片覆盖在网页上,形成点击劫持。
<a href="http://tieba.baidu.com/f?kw=%C3%C0%C5%AE">
<img src="XXXXXX"
style="position:absolute;top:90px;left:320px;" />
</a>
你以为你点了图片,其实你点了其他的链接。
防护方案:
在防御图片覆盖攻击时,需要检查用户提交的HTML代码中,img标签的style属性是否可能导致浮出。
URL跳转漏洞
URL跳转漏洞(URL重定向漏洞),跳转漏洞一般用于钓鱼攻击
攻击原理:
URL跳转漏洞本质上是利用Web应用中带有重定向功能的业务,将用户从一个网站重定向到另一个网站。
示例:http://www.
aaa.com?returnUrl=http://www.evil.com,www.aaa.com 有一个功能会取到returnUrl后的路径,然后跳转。这种功能就会被黑客利用,跳转到他想跳转的其他页面上去。
一般容易出现的漏洞的点:
- 登录跳转、第三方授权
- 分享
- 收藏
URL跳转漏洞校验绕过
- 域名字符串校验
//获取参数
String url = request.getParameter("returnUrl");
//判断是否包含域名
if (url.indexOf("www.abc.com ") !=-1){
response.sendRedirect(url);
}
黑客可以注册一个包含abc.com的域名来绕过,如:
http://www.abc.com?returnUrl=http://www.abc.com.evil.com
2. 利用URL的各种特性符号
http://www.aaa.com?returnUrl=http://www.evil.com?www.aaa.com
http://www.aaa.com?returnUrl=http://www.evil.comwww.aaa.com
http://www.aaa.com?returnUrl=http://www.aaa.com@www.evil.com
http://www.aaa.com?returnUrl=http://www.evil.com#www.aaa.com
防护方案
- 代码固定跳转地址,不让用户随意输
- 跳转目标地址采用映射机制,如 1代表aaa.com,2代表bbb.com这样,用户只能输1和2才能过校验
- 合理充分的校验机制,校验跳转的目标地址,非己方的地址跳转时可以提示用户,类似知乎做的那种。
Session攻击
通过窃取用户SessionId,使用SessionId登录进目标账户的攻击方法。如果SessionId是保存在Cookie中的,则这种攻击可以称为Cookie劫持。
3.1 Session劫持
攻击原理:
- 目标用户登录网站
- 登录成功后,用户的SessionId服务器会认为是已登录的,可以做一些他权限内的操作
- 攻击者通过某种攻击手段捕获Session ID
- 攻击者通过捕获到的Session ID访问站点即可获得目标用户合法会话。
获取SessionId的方式有多种:
- 暴力破解:不断尝试各种Session ID
- 预测:如果Session ID使用非随机的方式产生,那么就有可能计算出来
- 窃取:使用网络嗅探、本地木马窃取、XSS攻击等方法
防御方法:
- Cookie HttpOnly
- Cookie Secure,是设置Cookie时,可以设置的一个属性,设置了之后只有https访问时,浏览器才会发送该cookie。这样攻击者从网络上窃听到,也是拿到一个加密的cookie
response.setHeader("SET-
HEADER","user="+request.getParameter("cookie")+";HttpOnly;Sec
ure");
3.2 会话固定
会话固定是让用户使用黑客预先设置的sessionID进行登录,这样同样黑客能拿到用户SessionId。
攻击步骤:
- 攻击者通过某种手段重置用户的SessinoID,然后监听用户会话状态
- 目标用户携带攻击者设定的Session ID登录站点
- 攻击者通过Session ID获得合法会话
攻击者如果让目标使用黑客的Session ID呢?如果是保存在Cookie中是比较难做的,但如果是SessionID保存在URL中,则攻击者只要诱导用户打开这个URL即可。
防御方法:
- 用户登录的时候就进行重置SessionID
// 会话失效
session.invalidate();
// 会话重建
session=request.getSession(true);
- SessionID闲置过久时,进行重置
- 设置HttpOnly
3.3 Session保持攻击
一般情况下,Session是有生命周期的,当用户长时间未活动,或者用户点击退出后,服务器将销毁Session。但如果攻击者拿到了一个用户的Session,它可以通过不停的发起访问请求,让Session一直存活。
<script>
//要保持session的url
var url =
"http://bbs.yuanjing.com/wap/index.php?/sid=LOXSAJH4M";
//定时任务
window.setInterval("keeyId()",6000);
function keepsid(){
document.getElementById("iframe1").src=url+"&time"+Math.rand
om();
}
</script>
<iframe id="iframe1" src=""/></iframe>
攻击者设置可以为Session Cookie增加一个Expire事件,使得原本浏览器关闭就会失效的Cookie持久化地保存在本地。
防护方案:
- 可以设置一个固定的时间,如3天后就强制过期,但是也影响了正常用户
- 比如用户的IP、UserAgent信息发生变化,就可以强制销毁当前的Session,并要求用户重新登录
注入攻击
注入攻击是Web安全领域中一种最常见的攻击方式。XSS本质上也是一种针对HTML的注入攻击。注入攻击的本质,是把用户输入的数据当做了代码执行。这里有两个关键条件,第一个是用户能控制输入,第二个是原本程序要执行的代码拼接了用户输入的数据。解决注入攻击的核心思想:数据与代码分离原则
4.1 SQL注入
本应该输入正常的数据,而黑客却输入一些sql语句。如果应用程序比较脆弱的话,则可能应用程序会被攻击。
最简单的示例就是一个:查询某个用户
String query = "select * from accounts where name ='"+request.getParameter("name")+"';
用户如果输张三李四是没问题的,如果输入 李四 or 1=1 那么它会查到所有用户数据。
容易导致SQL注入的弱点:
- 应用程序中使用字符串连接方式或联合查询方式组合SQL语句
- 应用程序连接数据库时使用权限过大的账户(很多开发人员喜欢直接用管理员账户)
- 未校验用户输入的字符串
攻击步骤:
- SQL盲注
所谓盲注,就是在服务器没有返回错误信息的情况下完成的注入攻击。最常见的盲注验证方法是构造简单的条件语句,根据返回的页面是否变化来判断是否存在漏洞。
示例:下面是一个根据userId查询用户的功能:
1) 我先输入
1 ' and 1=1 #
发现能查询到数据
2)再输入
1 ' and 1=2 # 发现查不出数据,证明注入成功。
- 猜解数据库
3)继续输入利用order by猜列数
1' order by 1 #
成功,然后输入 1' order by 2 # ,依次递增,直到提示
Unknown column '3' in 'order clause'
成功得到该表的列数。
4)知道列数之后,使用union select 联合查询获取更多的信息,union 查询的使用前提是关联查询的两个查询列数要一致
1 ' union select database(),user() #
查询到数据库名和当前查询的用户名
5)继续查询当前数据库版本、操作系统
1 ' union select version(),@@version_compile_os#
6)根据数据库名,查询里面的表
1 ' union select table_name,table_schema from information_schema.tables where table_schema= 'dvwa' #
7)猜users表的字段,根据经验试试是不是user和password
1' union select user,password from users#
成功爆出了某网站的用户名和密码
- ORM注入
1) Mybatis:
${}:单纯替代,纯粹的将参数传进去,没有做任何的转义操作和预编译。
#{}: Mybatis会通过预编译机制生成PreparedStatement参数,然后再安全的给参数赋值
${}是存在注入风险的。
2) Hibernate
usernameString//前台输入的用户名
passwordString//前台输入的密码
//hql语句
String queryString = "from User t where t.username= " +
usernameString + " and t.password="+ passwordString;
//执行查询
List result = session.createQuery(queryString).list();
通过hql拼接的方式不安全。建议用参数绑定:
usernameString//前台输入的用户名
passwordString//前台输入的密码
//hql语句
String queryString = "from User t where t.username:
usernameString and t.password: passwordString";
//执行查询
List result = session.createQuery(queryString)
.setString("usernameString ",
usernameString )
.setString("passwordString",
passwordString)
.list();
3)JDBC:使用预处理执行SQL语句,对所有传入SQL语句中的变量做绑定,这样用户拼接进来的变量无论内容是什么,都会被当做替代符号"?"所替代的值。
示例:
String sql = "select * from users where userid = " + userid;
如果用户输入userid是"1;delete users;"会被编译成2条sql,那么users表会被非法删除。
但是如果是预编译的方式,那么会编译成一条sql,查询userid为'"1;delete users;"的用户,查询结果会是不存在,不会存在危险。
4.2 XML注入
XML注入是将用户录入的信息作为XML节点。
示例:
//userData是准备保存的XML数据,接受了name和email两个用户提交的数据
String userData = "<USER >"+
"<name>"+
request.getParameter("name")+
"</name>"+
"<email>"+
request.getParameter("email")+
"</email>"
"</USER>"
//保存XML数据
userDao.save(userData);
如果用户输入的时候name输入张三,email输入:
user1@lagou.com</email></USER><USER><name>user2</name>
<email>user2@lagou.com
这样就恶意构造了一个xml,拼接起来一次保存了两条数据。
防护方案:
对xml数据进行转义:
String userData = "<USER>"+
"
<name>"+StringUtil.xmlEncode(request.getParameter("name"))+"
</name>"+
"
<email>"+StringUtil.xmlEncode(request.getParameter("email"))+
"</email>"+
"</USER>";
4.3 代码注入
web应用程序中如果有允许接收用户输入一段代码并执行。那么用户可以根据这个功能写一个远程控制木马,进行恶意攻击。代码注入往往是由一些不安全的函数或者方法引起的,其中的典型代表就是eval()
public static void main(String[] args) {
//在Java中也可以实施代码注入,比如利用Java的脚本引擎。
ScriptEngineManager manager = new
ScriptEngineManager();
//获得JS引擎对象
ScriptEngine engine =
manager.getEngineByName("JavaScript");
try {
//用户录入
String param = "hello";
String command = "print('"+param+"')";
//调用JS中的eval方法
engine.eval(command);
} catch (ScriptException e) {
e.printStackTrace();
}
}
攻击示例:
hello'); var fImport = new JavaImporter(java.io.File);
with(fImport) { var f = new File('new'); f.createNewFile(); }
防护方案:
对抗代码注入,需要禁止eval()等可以执行命令的函数,如果一定要用,则需要对用户输入的数据进行处理。如只能由开发人员定义代码内容,用户只能提交对应的id”1、2、3“等参数。
4.4 OS命令注入
OS命令注入(操作系统命令注入),仅当Web应用程序代码包括操作系统调用,并且使用了用户的输入内容。
示例:
应用程序开发人员希望用户能够在Web应用程序中查看Ping命令的输出。用户正常输入ip,那还好。
如果输入:
127.0.0.1 && whoami
或者
127.0.0.1 && ps -ef
就能够执行一些危险的操作。
防护方案:
最有效的方法就是不要再应用程序代码中调用OS命令。如果非要这么搞,那么还是一样需要对用户输入进行严格的验证。
文件操作防护
5.1 文件上传漏洞
互联网当中,我们经常用到文件上传功能,传excel、传图片、传视频等等。文件上传后,服务器的处理逻辑如果做的不够安全,就会导致漏洞的产生。
文件上传后导致的常见安全问题一般有:
- 上传文件是Web脚本语言,服务器的Web容器解释并执行了用户上传的脚本
- 上传文件是病毒、木马文件,然后诱使用户或管理员下载执行
- 上传文件是钓鱼图片或为包含了脚本的图片,在某些版本的浏览器中会被作为脚本执行,被用于钓鱼和欺诈。
防护方案:
- 检查上传文件扩展名
- 上传文件的目录必须是http请求无法直接访问到的。如果需要访问,需要上传到其他(和web服务器不同的)域名下,并设置该目录为不可执行目录
- 上传文件要保存的文件名和目录名由系统根据时间生成,不允许用户自定义
- 图片上传,要通过处理(缩略图、水印等),无异常才能保存到服务器
- 上传文件需要做日志记录
5.2 文件下载和目录浏览漏洞
文件下载和目录浏览漏洞是属于程序设计和编码上的不严谨导致的。处理用户请求下载文件时,用户提交文件路径,就把服务器上对应的文件发送给用户,这就造成了任意文件下载危险。如果用户提交目录,就把目录下的文件列表发给用户,会造成目录遍历安全威胁。良好的设计应该是:不允许用户提交任意文件路径进行下载,而是用户单机下载按钮默认传递ID到后台程序。
防护方案:
- 要下载的文件地址保存在数据库中
- 文件路径保存至数据库,让用户提交文件对应ID下载文件
- 下载文件之前做权限判断
- 文件放在web无法直接访问的目录下
- 记录文件下载日志
- 不允许提供目录遍历服务
访问控制
6.1 功能权限漏洞
功能权限漏洞是指Web应用没有做权限控制,或仅仅在菜单上做了权限控制,导致恶意用户只要猜到了其他页面的URL地址,就可以访问到他权限外的页面和数据。
防护方案:
针对任何URL,每次用户访问,都要判断用户是否有访问此URL的权限。
6.2 数据权限漏洞
这种问题出现在未对用户与数据级别之间建立关系,如用户A和用户B都有查询订单的权限,但是A只能查询A的订单,B只能查B的订单。如果只校验用户是否有某个功能的权限,不校验时候有这个数据的权限,就会出现越权问题。
防护方案:
根据用户的ID做好数据级权限控制,比如针对CRUD操作进行身份验证,且对用户访问的数据进行数据权限校验,防止通过修改ID的方式查看别人的数据。
DDOS攻击
7.1 DDOS攻击
DDOS(Distributed Denial of Service)又称为分布式拒绝服务,DDOS是利用合理的请求造成资源过载,导致服务不可用。常见的DDOS攻击有SYN flood、UDP flood、ICMP flood等。其中SYN flood是一种最为经典的DDOS攻击,其发现于1996年,至今仍然保持着非常强大的生命力。SYN flood如此猖獗是因为它利用了TCP协议设计中的缺陷,而TCP/IP是整个互联网的基础,想要修复这样的缺陷机不是不可能的事情。
SYN flood攻击原理:
SYN flood攻击是根据TCP协议的三次握手过程进行的攻击。在TCP服务器收到SYN请求包时,在发送ACK包回去之前,TCP服务器需要先分配一个数据区专门服务于这个快形成的TCP连接。
在常见的SYN flood攻击中,攻击者短时间内发送大量的SYN包给受害者服务器,服务器需要为每个TCP SYN包分配一个数据区,只要这些SYN包具有不同的源地址。这将给受害者服务器造成很大的系统负担,最终导致系统不能正常工作。
SYN flood防护方案:
syn cookie:
syn cookie是通过修改TCP三次握手协议,来专门防御DDOS的一种手段。服务器收到客户端的SYN包,并且返回一个ACK+SYN包时,不专门分配一个数据区,而是根据这个包计算出一个cookie值。然后再收到ACK包时,服务器根据这个cookie值检查这个ACK包是否合法。如果合法,再分配专门的数据区进行处理未来的TCP连接。
7.2 CC攻击
CC攻击是DDOS攻击的一种,可以看作是应用层面的DDOS攻击。攻击原理是:针对一些耗费资源较大的应用页面不断发起请求,以达到消耗服务器资源的目的。
防护方案:
- 代码要做好性能优化,合理的使用缓存,减轻数据库负担
- 网络架构优化,善用负债均衡,避免用户流量集中在单台服务器,同时可以充分利用CDN和镜像站点的分流作用,缓解主站的压力。
- 使用页面静态化技术,利用客户端浏览器的缓存功能或服务器的缓存功能,CDN节点的缓冲服务,均可以降低服务器的检索压力和计算压力。
- 也可以使用一些对抗手段。如限制每个ip的请求频率,超出频率的ip加入黑名单
IP黑白名单的开发
可以使用OpenResty+Lua脚本实现。也可以使用KONG