代码审计入门之CSRF原理及分析

初识Java代码审计,所以分析比较稚嫩,大佬请绕行。本篇分析CSRF漏洞原理。本篇文章的代码基于本人自己编写的留言板项目,项目未使用框架,框架还不太会,等学会了,再分析关于框架的。

相关文章:

留言板项目下载地址
代码审计入门之前篇
代码审计入门之XSS原理及分析

0x00 概述

CSRF:跨站请求伪造,是一种“挟持用户(浏览器)”在当前已登陆的Web应用程序上执行“非本意操作”的攻击方法。

0.jpg

原理:

简单说就是攻击者盗用你的身份,伪装成你发送恶意请求,对服务器来说是合法的操作,却完成了攻击者所期望的一个操作。

漏洞前提:

登录受信任网站A,并在本地生成Cookie。
在不登出A的情况下,访问危险网站B。

0x01 挖掘技巧

对目标网站增删改的地方进行标记,并观察其逻辑,判断请求是否可以被伪造;
比如修改管理员账号时,并不需要验证旧密码,导致请求容易被伪造;
比如对于敏感信息的修改并没有使用安全的token验证导致请求容易被伪造;
确认凭证的有效期(这个问题会提高CSRF被利用的概率)虽然退出或者关闭了浏览器,但cookie仍然有效,或者session并没有及时过期,导致csrf攻击变得简单。

0x02 漏洞原理分析

CSRF的漏洞在我编写的留言板上也有,具体分为GET型和POST型分开分析:

1.GET型的CSRF

GET类型的CSRF利用非常简单,只需要一个HTTP,一般会这样利用:

<img src="http://xxx/Message/DelLyServlet?lyid=33">

 在受害者访问含有这个img的页面之后,浏览器会自动向http://xxx/Message/DelLyServlet?lyid=33发送一次HTTP请求。

测试:访问CSRF页面就可以自动删除留言

例如要删除留言内容是1 的留言,lyid是31(从前端可以获取)
1.1.png

打开CSRF文件,可以看到自动加载src中的链接,提示消息“删除成功”,然后刷新查看留言。留言内容为1的留言已经被删除。

前提:用户已登陆且cookie没有过期的情况下打开CSRF文件
1.2.png

1.3.png

分析代码:

index.jsp页面:删除按钮的链接就传递了一个lyid。只要点击此链接,对应lyid的留言就会被删除。
1.4.png

1.5.png

编写的CSRF页面:其中的src属性加载删除留言的链接,只要打开此页面就会自动加载src中的链接。

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    delete:自动删除留言
    <img src="http://xxx/Message/DelLyServlet?lyid=33">

</body>
</html>

接下来再来看看/Message/DelLyServlet文件是如何处理删除留言操作的
直接接受前端传来的lyid值,然后赋值给ly对象,ld对象直接调用删除delly()方法。过程没有任何校验。

1.6.png

继续看LyDao文件中的delly()方法,直接将ly对象中的lyid拼接到SQL语句中。

1.7.png

此处存在GET型CSRF。

2.POST型的CSRF

这种类型的CSRF利用起来通常使用的是一个自动提交的表单,如:

<form action="/Message/PerinfoServlet" method="post" name="csrf">   
    <input type="text" name="username" style="width:300px;" value="${user.username}" >
    <input type="password" name="password" style="width:300px;" value="aaa123">
</form>
<script>document.csrf.submit();</script>

访问该页面后,表单会自动提交,相当于模拟用户完成了一次POST操作。

 POST类型的攻击通常比GET要求更加严格一点,但并不复杂。任何个人网站、博客,被黑客上传页面的网站都有可能是发起攻击的来源,后端接口不能将安全寄托在仅允许POST上面。

测试:访问CSRF页面就会自动更新用户信息。

修改用户信息页面:
2.1.png

仿照此页面,编写了一个csrf.jsp,只要打开此页面,form表单会自己提交。用户密码会被修改为aaa123.

2.2.png

测试用户:admin,密码:123456。在登陆的情况下访问:(为了测试,直接在本地写了一个链接跳转到CSRF页面)

2.3.png

<a href="http://xxxx/Message/csrf.jsp">跳转修改用户信息</a>

访问链接,提示消息:修改成功。

2.4.png

再次登陆admin用户测试是否修改成功:
admin,123456登陆失败。admin,aaa123.登陆成功。

2.5.png

查看数据库:
用户admin的密码是aaa123

2.6.png

分析代码:

修改用户信息页面perinfo.jsp:form表单将当前input中的值,以POST型提交给Message/PerinfoServlet,我们的CSRF页面就是仿照此页面写的。
3.1.png

继续看Message/PerinfoServlet文件:
直接接受传递过来的值,赋值给user对象。ud对象调用perinfo()方法修改用户信息。过程全程无检测,开发人员太相信浏览器传递过来的值。
3.2.png

继续看UserDao文件中的perinfo()方法:
直接就把user对象的值拼接到SQl语句中。

3.3.png

0x03 漏洞防御

1.添加token验证
CSRF的另一个特征是,攻击者无法直接窃取到用户的信息(Cookie,Header,网站内容等),仅仅是冒用Cookie中的信息。而CSRF攻击之所以能够成功,是因为服务器误把攻击者发送的请求当成了用户自己的请求。那么我们可以要求所有的用户请求都携带一个CSRF攻击者无法获取到的Token。服务器通过校验请求是否携带正确的Token来把正常的请求和攻击的请求区分开。注意:一定要把token进行失效处理。

2.判断Referer;请求从哪里发起
 在HTTP协议中,有一个Header叫Referer,用于标记来源域名。在浏览器发起请求时,大多数情况会自动带上这个Header,并且不能由前端自定义其内容,所以服务器可以通过解析这个Header中的域名,确定请求的来源域。对于Ajax请求,图片和script等资源请求,Referer为发起请求的页面地址。对于页面跳转,Referer为打开页面历史记录的前一个页面地址。因此通过Referer中链接的Origin部分就可以得知请求的来源域名。

除此之外,后端接口不要在GET页面中做用户操作。为了更好的防御CSRF,最佳实践应该是结合上面总结的防御措施方式中的优缺点来综合考虑,结合当前Web应用程序自身的情况做合适的选择,才能更好的预防CSRF的发生。

后台Java防御代码参考:

第一步,新建CSRF令牌添加进用户每次登陆以及存储在httpsession里,这种令牌至少对每个用户会话应是唯一的,或者是对每个请求是唯一的。

//this code is in the Defaulter implementation of ESAPI
/**this user’s CSRF token. */
Private String csrfToken = resetCSRFToken();
Public StringresetCSRFToken() {
    csrfToken = ESAPI.random().getRandomString(8, DefaultEncoder.CHAR_ALPHANUMBERICS);
    //利用ESAPI生成随机TOKEN
    
    Return csrfToken
}

第二步,令牌可以包含在URL中或作为一个URL参数记/隐藏字段。

//from HTTP Utilitiles interface
Final static String CSRF_TOKEN_NAME="token";
//this code is from the Default HTTP Utilities implementation in ESAPI
Public  String addCSRFToken(Stringhref) {
    User user=ESAPI.authenticator().getCurrentUser();
    if(user.isAnonymous()){returnhref;}
    //if there are already parameters append with&,otherwise append with?
    String token=CSRF_TOKEN_NAME+"="+user.getCSRFToken();
    return href.indexOf('?')!=-1?href+"&"+token:href+"?"+token;
}
...

public StringgetCSRFToken() {
    User user=ESAPI.authenticator().getCurrentUser();
    if(user==null) return null;return user.getCSRFToken();
}

第三步,在服务器端检查提交令牌与用户会话对象令牌是否匹配。

//this code is from the Defaul tHTTP Utilities implementation in 
//ESAPI
Public  void verifyCSRFToken(HttpServletRequest request) throws IntrusionException {
    User user=ESAPI.authenticator().getCurrentUser();
    //check if user authenticated with this request-noCSRFprotection required
    if(request.getAttribute(user.getCSRFToken())!=null) {
        return;
    }
    String token=request.getParameter(CSRF_TOKEN_NAME);
    if(!user.getCSRFToken().equals(token)) {
        //比较session中token与客户端参数中token是否一致
        throw new IntrusionException("Authenticationfailed","Possibly forgeted HTTP request without proper CSRFtokendetected");
    }
}

第四步,在注销和会话超时,删除用户对象会话和会话销毁。

//this code is in the DefaultUser implementation of ESAPI
Public  void logout() {
    ESAPI.httpUtilities().killCookie(ESAPI.currentResponse(),ESAPI.currentRequest(),HTTPUtilities.REMEMBER_TOKEN_COOKIE_NAME);
    HttpSession session=ESAPI.currentRequest().getSession(false);
    if(session!=null) {
        removeSession(session);
        session.invalidate();
    }
    ESAPI.httpUtilities().killCookie(ESAPI.currentRequest(),ESAPI.currentResponse(),"JSESSIONID");
    loggedIn=false;
    logger.info(Logger.SECURITY_SUCCESS,"Logout successful");
    ESAPI.authenticator().setCurrentUser(User.ANONYMOUS);
}

0x04 参考文章:

https://segmentfault.com/a/1190000020520350
https://www.cnblogs.com/volcano-liu/p/11188748.html#b
https://www.lmlphp.com/user/56/article/item/5904/
https://www.jianshu.com/p/7ca272efe06b

标签: none

添加新评论