Java漏洞演示平台 BodgeIt试用

这里是下载地址:https://github.com/psiinon/bodgeit

总结来说,它是一款很好的Java环境下的漏洞学习环境,搭建起来也很简单。推荐使用虚拟机,最好自己搞实验,测试的时候,不要在本机操作(有可能带来意外风险。)

因为是Java,想要运行它,jdk肯定是必要的。jdk的下载地址在这里。

http://www.oracle.com/technetwork/java/javase/downloads/jdk10-downloads-4416644.html

现在官网最新的版本是jdk 10。

安装完jdk之后,需要配置一下系统环境变量,这个可以利用一下搜索引擎,自给自足。

jdk安装完毕后,需要安装tomcat。

https://tomcat.apache.org/download-90.cgi

根据系统类型自己选择

如果你打开localhot:8080后如期看到了同样的内容,说明Java运行环境已经妥当。 这里,只需要把源码跑起来就行。仔细想了一下,我没有把该环境用作其它,只是测试,于是直接把webapps目录下的ROOT文件夹删除。 将BodgeIt压缩包的root目录改成大写放置到webapps下,刷新,看到预期页面,说明环境已经可以了。

这里有一个要注意的是,演示环境不能放置在公网。 继续看。

当我在search中输入一段

 alert(1)

时发现,它弹出了一个窗口,这代表着,我的Javascript代码已经被执行。可以去代码里看一下,它究竟为什么要这个样子做捏?

<h3>Search</h3>
<font size="-1">
<%
String query = (String) request.getParameter("q");

if (request.getMethod().equals("GET") && query != null){
        if (query.replaceAll("\\s", "").toLowerCase().indexOf("<script>alert("xss")</script>") >= 0) {
                conn.createStatement().execute("UPDATE Score SET status = 1 WHERE task = 'SIMPLE_XSS'");
        }
   
%>
<b>You searched for:</b> <%= query %><br/><br/>

search部分的代码是这样的,我们看到,它在通过getparameter取值后,下面只判断了提交方式和不为空,满足后看到下面只对指定字符串处理了空白,再往下看直接使用了模板取值方法 <%= query %> 获取了q value, 于是,这里我们可以使用任意javascript造成xss。

看到这里,问题来了,如何进行修复? 有以下几种办法

1. 通过apache commons的StringEscapeUtils.escapeHtml4(query)对参数进行处理,这里BodgeIt其实给提供了一个很好的实例

StringEscapeUtils.escapeHtml4(query).replaceAll(“‘”, “&#39”);

直接将query转义成实体

2. 通过str.replace();

这种存在缺陷,如果存在遗漏容易导致被绕过

所以一般都会采用组合过滤办法,比如,如果遇到了一个int型的xss,在通过escape进行处理后,输出前,还可以再次对数据类型进行判断,非int不输出。也是一种防御的办法。

继续看第二个xss

第二个xss是个留言本,我们在留言本中输入xss代码时,并没有如期看到弹出了框框,在开发者模式下观察,

<script></script>

消失了

去web目录下打开contact.jsp

    // Strip script tags, because that will make everything alright...
        comments = comments.replace("<script>", "");
        comments = comments.replace("</script>", "");
        // And double quotes, just to make sure
        comments = comments.replace(""", "");

观察后发现, 它应用了replace方法,对script标签进行了替换,这里因为是只处理了这一个标签,于是我们换成任意一种xss均可达到绕过的效果成功的看到了弹框,因为留言数据本身就是属于不含有特殊意义的文本数据,所以这里直接转义实体就可以,继续看第三处

可以通过register.jsp注册账户,我们来看一下注册逻辑部分

String username = (String) request.getParameter("username");
String password1 = (String) request.getParameter("password1");
String password2 = (String) request.getParameter("password2");
String usertype = (String) session.getAttribute("usertype");
String userid = (String) session.getAttribute("userid");
String debug = "";
String result = null;
boolean registered = false;

if (request.getMethod().equals("POST") && username != null) {
    if (username == null || username.length() < 5) {
        result = "You must supply a username of at least 5 characters.";
   
    } else if (username.indexOf("@") < 0) {
        result = "Invalid username - please supply a valid email address.";

    } else if (password1 == null || password1.length() < 5) {
        result = "You must supply a password of at least 5 characters.";

    } else if (password1.equals(password2)) {
        Statement stmt = conn.createStatement();
        ResultSet rs = null;
        try {
            stmt.executeQuery("INSERT INTO Users (name, type, password) VALUES ('" + username + "', 'USER', '" + password1 + "')");
            rs = stmt.executeQuery("SELECT * FROM Users WHERE (name = '" + username + "' AND password = '" + password1 + "')");
            rs.next();
            userid =  "" + rs.getInt("userid");

            session.setAttribute("username", username);
            session.setAttribute("usertype", "USER");
            session.setAttribute("userid", userid);

request.getParameter后,比对了对象是否相等和注册名不为空,下面的条件是取反判断,均和安全无关,目的是为了符合注册规则,在往下看, 没有进行任何处理注册信息就进入了INSERT,所以这里我们只要找一个在前端有显示注册信息的地方就可以造成xss(一般是用户名)

这里可以先随便注册个用户进行尝试

注册后看到,它是把用户名取到了模板当中,我们可以再去看下代码确认一下

<%
    if (username != null) {
        out.println("User: <a href="password.jsp">" + username + "</a>");
    } else {
        out.println("Guest user");
    }
%>

可以看到, 用户名只判断了非空,所以这里用户名部分是可以xss的,注册一个账户

 test@test.com xsscode

成功弹框。 这里修复同样可以对username和password进行转义处理。

继续看,登陆处的sql注入

boolean loggedIn = false;
String username = (String) request.getParameter("username");
String password = (String) request.getParameter("password");
String debug = "Clear";

if (request.getMethod().equals("POST") && username != null) {
    Statement stmt = conn.createStatement();
    ResultSet rs = null;
    try {
        rs = stmt.executeQuery("SELECT * FROM Users WHERE (name = '" + username + "' AND password = '" + password + "')");
        if (rs.next()) {
            loggedIn = true;
            debug="Logged in";
            // We must have been given the right credentials, right? ;)
            // Put credentials in the session
            String userid = "" + rs.getInt("userid");
            session.setAttribute("username", rs.getString("name"));
            session.setAttribute("userid", userid);
            session.setAttribute("usertype", rs.getString("type"));

可以看到,username,password直接进入了select, 且条件中为单引号

    rs = stmt.executeQuery("SELECT * FROM Users WHERE (name = '" + username + "' AND password = '" + password + "')");

如果输入admin@thebodgeitstore.com’ or 1=’1 那么,select就变成了这样

select * from users where name=admin@thebodgeitstore.com’ or 1=’1 and password=’

因为or是只要条件中有一个为真即为真, 1=1永远是true, 就这样绕过了密码验证。 如何修复? 这里修复可以从以下两个方面入手。

1. replace掉登陆账户中的特殊字符。 2. Sql语句能采用参数化查询就采用参数化查询,如果不能采用就不要用单引号。

举例:

        rs = stmt.executeQuery("SELECT * FROM Users WHERE (name = " + username + " AND password = " + password + ")");

不采用单引号的情况下, 再使用 or 1=1 就没有成功通过验证。

继续看, 有时候注释里面也可能会发现线索, 比如在此例当中就发现了一条

访问admin.jsp, 成功看到了一个未授权管理面板,有关这类漏洞的修复。首先写在html的注释里肯定是不行的,再一个,要对它的访问加上权限验证。 比如,我们可以加上一个判断,只有在当前已获取session的情况下并且session中uid等于指定范围才给出管理界面,不然就跳到登陆页面。

继续看,访问别人的篮子

通过

http://localhost:8080/basket.jsp

可以访问购物车, 可以通过开启debug查看当前的uid,开启方法是url后附加参数?debug=true, 查看后,确认我的当前uid为

DEBUG basketid = 4

观察请求头发现, 请求头中包含了一个b_id参数,尝试修改值。

成功访问了其他账户的购物车。

如何修复? 这是一个水平权限越权的问题,造成这个漏洞的原因有以下几点

1. 访问授权后数据前,没有对授权验证身份数据进行验证。

2. uid和uname未进行绑定。

3. 会话验证关键信息未进行加密。

所以,这里就可以这样修复

查询购物车数据时,前端隐藏域传一个u_id, 接收到u_id后,进行验证,查出当前登陆账户uid是否和隐藏域传过来的一致,如果一致,执行where u_id 结果并取值。 为了保险起见,可以对传输和展现进行加密,这样即使验证过程出现了问题,黑客也会因为无法了解加密数据格式导致重放攻击失败。

比如, hidden表单数据的值是md5取6位的字符串+时间戳+随机数后的u_id, 后端进行比较时, 也采用这个格式进行, 一致后才进入取值,即使是get传输的,也会是加密字符串形式展现。 这样黑客再想输入脏数据,就会因为加密数据格式判断不通过,导致取值失败。

下一个, 让商店欠你的钱, 这个其实很简单, 在浏览器的调试模式下,把表单数据进行修改再次提交就可以了,比如把价格标签改成负数。

修复方法

必要的加密和数据类型验证之前,增加数据范围判断,比如,在购买产品和价格当中,应该不允许负数的存在。

下一个是一个csrf

http://localhost:8080/password.jsp

修改密码的时候没有验证原密码,我增加get参数,随便弄一个密码,再整一个iframe或者去前台找一个xss,欺骗管理员访问/打开。duang的一下密码就被改了。

修复方式: 修改密码前查出原密码, 如果原密码不正确不允许修改。

advanced.jsp文件报错,这里只看一下代码就知道了

<%
    AdvancedSearch as = new AdvancedSearch(request, session, conn);
    if(as.isAjax()){
        response.setContentType("application/json");
        out.print(as.getResultsOutput());
        return;
    }
%>
<jsp:include page="/header.jsp"/>
<SCRIPT>
    var key = "<%= as.getEncryptKey() %>";
    var debug = <%= as.isDebug() ? "true" : "false" %>;
    loadfile('./js/jquery-1.6.4.min.js');
    window.setTimeout(loadOthers, 10);
   
    function loadOthers(){
        if (window.jQuery) {
            loadfile('./js/encryption.js');
            loadfile('./js/advanced2.js');
            loadfile('./js/jquery-ui-1.85.js');
            loadfile('./css/jquery-ui-1.85.css');
        } else {
            window.setTimeout(loadOthers, 10);
        }
    };
</SCRIPT>
   
<h3>Search</h3>
<font size="-1">
<% if (as.isSearchRequest()){ %>
<b>You searched for:</b> <%= as.getQueryString() %><br/><br/>
    <%= as.getResultsOutput() %>
    <a href="javascript:window.location=window.location.href">New Search</a>
<% } else { %>

实例化对象参数用户是可以控制的,接收到ajax请求后,设置完数据类型标签就进行了print。 这里面犯的错误是前端进行了处理,但处理不完全,后端又没有进行确认。 导致产生的漏洞。

前端是这样处理的

function prepareInput(strInput){
    var params = strInput.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39');
    val = ((params.length > 0)) ? Aes.Ctr.encrypt(params, key, 128) : false;
    return (val) ? val : false;
}

可以观察到,只对少量字符比如<>‘”进行了处理。 下面就进行了return。 加密其实不影响,因为后面要解析到模板里面。
比如我在url中的参数是&a=xxxx&b=ccccc&d=hhhhhh 通过querystring方法就可以完全获取到,获取到后,output输出结果。
所以这里xss肯定是存在的。

通过观察search.jsp, 发现sql注入也是存在的, 比如

http://localhost:8080/jspvul/search.jsp?q=a%27%25&debug=true

可以看到,查询条件被破坏了。

SELECT PRODUCT, DESC, TYPE, TYPEID, PRICE FROM PRODUCTS AS a JOIN PRODUCTTYPES AS b ON a.TYPEID = b.TYPEID WHERE PRODUCT LIKE '%a'%%' OR DESC LIKE '%a'%%' OR PRICE LIKE '%a'%%' OR TYPE LIKE '%a'%%'

很明显,这里是不应该使用单引号的。 在高级查询下, 可能还会存在更多存在sql注入问题的变量。除了不应该使用单引号之外,更好的修复方式实际上是对sql查询进行预编译处理。 正则,str.replace, 都存在绕过的可能。 apache commons 的字符串处理类是很强大的。任何对Java安全了解有所缺少的人士都有必要去学习一下这个工具。

此条目发表在java, Uncategorized分类目录,贴了, , , , 标签。将固定链接加入收藏夹。

发表评论

电子邮件地址不会被公开。 必填项已用*标注