红日攻防实验室

红日攻防实验室



基于wavsep靶场的jsp代码审计实战

360桌面截图20180220205040.jpg

本次实验是基于wavsep进行设计,因此我们主要做了sql注入及xss跨站请求漏洞的实验结束。

wavsep靶场搭建

1、java环境搭建
因为本地已搭建好java环境,因此不进行演示了,网上也有很多java环境搭建教程。
2、wavsep靶场搭建
在网上下载wavsep.war包,将war包放在tomcat下的webapps下则会自动解压为wavsep文件夹;在浏览器中打开http://127.0.0.1:8080/wavsep/:

通过页面信息我们可知当我们需要打开sql注入或者xss跨站脚本请求漏洞进行实验时只需在url:http://127.0.0.1:8080/wavsep/ 后面跟上 index-xss.jsp等练习页面即可。

SQL注入代码审计防御

1.字符型(String)sql注入
打开本次sql注入页面(wavsep中GET500中的case1):http://127.0.0.1:8080/wavsep/SInjection-Detection-Evaluation-GET-500Error/Case1-InjectionInLogin-String-LoginBypass-WithErrors.jsp?username=textvalue&password=textvalue2

在用户名和密码处插入注入语句进行尝试绕过登录('or'7'='7):

服务器回显"hello user1",登陆成功,该点存在注入,查看一下源码:

<%@ page language="java" contentType="text/html; charset=windows-1255"
pageEncoding="windows-1255"%>
<%@page import="java.sql.*" %>
<%@page import="com.sectooladdict.constants.DatabaseConstants" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1255">
<title>Case 1 - Injection into string values in a login page with erroneous responses</title>
</head>
<body>

<%
if (request.getParameter("username") == null
     && request.getParameter("password") == null    ) {
%>
    Login Page:<br><br>
    <form name="frmInput" id="frmInput" action="Case1-InjectionInLogin-String-LoginBypass-WithErrors.jsp" method="POST">
        <input type="text" name="username" id="username"><br>
        <input type="password" name="password" id="password"><br>
        <input type=submit value="submit">
    </form>
<%
} 
else {
    try {
           String username = request.getParameter("username");
          String password = request.getParameter("password");
        /* Test loading driver */
        String driver = DatabaseConstants.DATABASE_DRIVER;
        Class.forName(driver).newInstance();
    
        /* Test the connection */
         String url = DatabaseConstants.CONNECTION_STRING;
        Connection conn=DriverManager.getConnection(url);
 
        System.out.print("Connection Opened Successfully");
         String SqlString = 
            "SELECT username, password " +
             "FROM users " +
             "WHERE username='" + username + "'" +
             " AND password='" + password + "'";
       
         Statement stmt = conn.createStatement();
         ResultSet rs = stmt.executeQuery(SqlString);
     
     
         
         if(rs.next()) {
             out.println("hello " + rs.getString(1));
        } else {
              out.println("login failed");
         }
      
        out.flush();
  } catch (Exception e) {
        response.sendError(500,"Exception details: " + e);
   }
} //end of if/else block
%>

</body>
</html>

以上便是这个页面的所有源码,看一下其中的查询语句:

         String SqlString = 
             "SELECT username, password " +
             "FROM users " +
             "WHERE username='" + username + "'" +
             " AND password='" + password + "'";
         Statement stmt = conn.createStatement();
         ResultSet rs = stmt.executeQuery(SqlString);

通过上述登录成功('or'7'='7')的事例可以发现其sql查询语句为:select username,password from users where username=''or'7'='7' and password=''or'7'='7''当username或username和password为真的时候则查询成功,为否则查询失败;这个就是利用相关语句组合的查询语句造成注入成功的注入语句;
对于这类sql注入漏洞该如何预防呢?

             "WHERE username='" + username + "'" +
             " AND password='" + password + "'";

发现传入值为单引号进行包含起来了,该注入为字符型查询,针对字符型查询直接可以将其单引号替换成双引号,数据为库oracle、mssql:则函数为String.replace("'",""");数据库为mysql:则函数为String.replace("'","\'");具体在源码中进行替换如下:

          String username = request.getParameter("username").replace("'","\\'");
          String password = request.getParameter("password").replace("'","\\'");

在对该源码进行替换防御后我们再次实验,在用户名和密码处插入注入语句进行尝试绕过登录('or'7'='7):


登录失败,说明此方法对单引号进行转译,起到了对SQL注入的防护作用。
上面通过对单引号的转译,防止了sql注入漏洞,同时,还可以利用参数化查询的方式防止字符型sql注入漏洞,对于参数化查询我们可以先看看owasp官网上的文档:https://www.owasp.org/index.php/SQL_Injection_Prevention_Cheat_Sheet
实例如下:

String custname = request.getParameter("customerName"); // This should REALLY be validated too
// perform input validation to detect attacks
String query = "SELECT account_balance FROM user_data WHERE user_name = ? ";
PreparedStatement pstmt = connection.prepareStatement( query );
pstmt.setString( 1, custname); 
ResultSet results = pstmt.executeQuery( );

参考上述实例,我们对源码中sql查询语句进行修改:

<%@ page language="java" contentType="text/html; charset=windows-1255"
pageEncoding="windows-1255"%>
<%@page import="java.sql.*" %>
<%@page import="com.sectooladdict.constants.DatabaseConstants" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1255">
<title>Case 1 - Injection into string values in a login page with erroneous responses</title>
</head>
<body>

<%
if (request.getParameter("username") == null
     && request.getParameter("password") == null    ) {
%>
    Login Page:<br><br>
    <form name="frmInput" id="frmInput" action="Case1-InjectionInLogin-String-LoginBypass-WithErrors.jsp" method="POST">
        <input type="text" name="username" id="username"><br>
        <input type="password" name="password" id="password"><br>
        <input type=submit value="submit">
    </form>
<%
} 
else {
    try {
          String username = request.getParameter("username");
          String password = request.getParameter("password");
        /* Test loading driver */
        String driver = DatabaseConstants.DATABASE_DRIVER;
        Class.forName(driver).newInstance();
    
        /* Test the connection */
         String url = DatabaseConstants.CONNECTION_STRING;
        Connection conn=DriverManager.getConnection(url);
 
        System.out.print("Connection Opened Successfully");
         String SqlString1 = 
        "SELECT username, password " +
         "FROM users " +
         "WHERE username=?" +
         " AND password=?";  //用?代替参数
       
        PreparedStatement prepstmt = conn.prepareStatement(SqlString1);
        prepstmt.setString( 1, username); //利用set给一个值
        prepstmt.setString( 2, password);
        ResultSet rs = prepstmt.executeQuery();  //进行查询
     
     
         
         if(rs.next()) {
             out.println("hello " + rs.getString(1));
        } else {
              out.println("login failed");
         }
      
        out.flush();
  } catch (Exception e) {
        response.sendError(500,"Exception details: " + e);
   }
} //end of if/else block
%>

</body>
</html>

在查询语句中,利用?代替我们传入的username、password,然后再分别给其set一个值进行查询,进行实验一下:

登录失败,参数化查询防护成功。
以上就是本次字符型sql注入的测试及防护。
2.数字型sql注入
针对数字型sql注入,利用函数进行防护,把常见参数转换成数字类型进行查询,该函数如下:

Integer.parseInt(s)

3.非字符非数字型
对于非字符型又非数字型的sql注入该如何应对,先访问该页面(wavsep中GET500中的case5):http://127.0.0.1:8080/wavsep/SInjection-Detection-Evaluation-GET-500Error/Case5-InjectionInSearchOrderBy-String-BinaryDeliberateRuntimeError-WithErrors.jsp?orderby=msgid:

在参数后边加上单引号,页面出现sql报错,存在sql注入:

查询一些该页面源码:

<%@ page language="java" contentType="text/html; charset=windows-1255"
    pageEncoding="windows-1255"%>
<%@page import="java.sql.*" %>
<%@page import="com.sectooladdict.constants.DatabaseConstants" %>
<%@ page import="com.sectooladdict.validators.InputValidator" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1255">
<title>Case 5 - Injection into an order by clause (any type) in a search page with erroneous responses</title>
</head>
<body>

<%
if (request.getParameter("orderby") == null) {
%>
    View Messages and Organize Results According to the Field:<br><br>
    <form name="frmInput" id="frmInput" action="Case5-InjectionInSearchOrderBy-String-BinaryDeliberateRuntimeError-WithErrors.jsp" method="POST">
        <input type="text" name="orderby" id="orderby" value="msgid" ><br>
        <input type=submit value="submit">
    </form>
<%
}
else {
    try {
          String order = request.getParameter("orderby");
          if (InputValidator.validateSemicolon(order)) {
              throw new Exception("Invalid Characters in Input: Semicolon (;)");
          }
      
        /* Test loading driver */
        String driver = DatabaseConstants.DATABASE_DRIVER;
        Class.forName(driver).newInstance();

        /* Test the connection */
        String url = DatabaseConstants.CONNECTION_STRING;
        Connection conn=DriverManager.getConnection(url);
 
        System.out.print("Connection Opened Successfully");
         String SqlString = 
            "SELECT msgid, title, message " +
             "FROM messages " +
             "ORDER BY " + order;
         Statement stmt = conn.createStatement();
         ResultSet rs = stmt.executeQuery(SqlString);
     
         out.println("The list of messages:");
         out.println("<TABLE>");         
         out.println("<TR>");
        out.println("<TD>");
        out.println("<B>");
        out.println("MsgId");
        out.println("</B>");
        out.println("</TD>");
        out.println("<TD>");
        out.println("<B>");
        out.println("Title");
        out.println("</B>");
        out.println("</TD>");
        out.println("<TD>");
        out.println("<B>");
        out.println("Message");
        out.println("</B>");
        out.println("</TD>");
        out.println("</TR>");
         while(rs.next()) {
         
             out.println("<TR>");
             out.println("<TD>");
             out.println(rs.getString(1));
             out.println("</TD>");
             out.println("<TD>");
             out.println(rs.getString(2));
             out.println("</TD>");
             out.println("<TD>");
             out.println(rs.getString(3));
             out.println("</TD>");
             out.println("</TR>");
         } 
         out.println("</TABLE>");
      
          out.flush();
    } catch (Exception e) {
        response.sendError(500,"Exception details: " + e);
    }
} //end of if/else block
%>

</body>
</html>  

通过查阅源码,可以发现在查询时即不是字符型也不是数字型,是通过orderby进行传参查询,因为一般表名的长度是有一定的长度限制的,所以这个时候可以利用限制查询时的字符长度来进行防护;
在源码上进行防护修改,在String order = request.getParameter("orderby");下面加入长度判断条件:

String order = request.getParameter("orderby");
            if(order.length()>10){
                                return;
            }  

首先在未进行限制参数长度的情况下插入超过10个以上字符的参数:

出先sql报错;
接下来对站点传入参数长度进行限制,并插入超过10个以上的字符参数:

未出现sql报错,该防护成功。
以上就是在wavsep上相对具有代表性的三类注入,对其进行了测试及防护。

XSS跨站请求漏洞代码审计防御

本次xss跨站请求漏洞代码审计防御实验选取GET型中的反射性xss漏洞做实例进行查看及防御;首先访问存在反射型xss漏洞地址(xssGET中的case1):http://127.0.0.1:8080/wavsep/RXSS-Detection-Evaluation-GET/Case1-Tag2HtmlPageScope.jsp?userinput=textvalue

jsp代码审计防御在userinput后面填写xss脚本代码进行提交:


查看该页面源码:

<%@ page language="java" contentType="text/html; charset=windows-1255"
pageEncoding="windows-1255"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1255">
<title>Case 1 - RXSS via tag injection into the scope of an HTML page</title>
</head>
<body>
   
<%
if (request.getParameter("userinput") == null) {
%>
    Enter your input:<br><br>
    <form name="frmInput" id="frmInput" action="Case1-Tag2HtmlPageScope.jsp" method="POST">
        <input type="text" name="userinput" id="userinput"><br>
        <input type=submit value="submit">
    </form>
<%
} 
else {
   try {
              String userinput = request.getParameter("userinput"); 
                 out.println("The reflected value: " + userinput);
           out.flush();
     } catch (Exception e) {
        out.println("Exception details: " + e);
  }
} //end of if/else block
%>

</body>
</html>

我们发现在插入的userinput参数未做任何防御,直接进行插入,导致xss反射脚本执行成功。
对于xss跨站请求漏洞在代码层的防御简单介绍以下一种,如下是我们需要插入的防御代码:

<%!
String csHTML(String text){
if(text == null)return "";
StringBuffer results = null;
char[] orig = null;
int beg = 0, len = text.length();
for(int i = 0;i < len;++i){
    char c = text.charAt(i);
    switch(c){
    case 0:
    case '&':
    case '<':
    case '>':
    case '"':
        if(results == null){
            orig = text.toCharArray();
            results = new StringBuffer(len + 10);
        }
        if(i>beg)results.append(orig,beg,i-beg);
        beg = i + 1;
        switch(c){
        default://case 0:
            continue;
        case '&':
        results.append("&amp;");
        break;
        case '<':
        results.append("&lt;");
        break;
        case '>':
        results.append("&gt;");
        break;
        case '"':
        results.append("\"");
        break;
        }
        break;
    }
}
if(results == null) return text;
results.append(orig,beg,len - beg);
return results.toString();
}
%>

我们将此防御代码插入到页面源码中:

<%@ page language="java" contentType="text/html; charset=windows-1255"
pageEncoding="windows-1255"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1255">
<title>Case 1 - RXSS via tag injection into the scope of an HTML page</title>
</head>
<body>
<%!
String HTML(String text){
    if(text == null)return " ";
    StringBuffer results = null;
    char[] orig = null;
    int beg = 0, len = text.length();
    for(int i = 0;i < len;++i){
        char c = text.charAt(i);
        switch(c){
        case 0:
        case '&':
        case '<':
        case '>':
        case '"':
            if(results == null){
                orig = text.toCharArray();
                results = new StringBuffer(len + 10);
            }
            if(i>beg)results.append(orig,beg,i-beg);
            beg = i + 1;
            switch(c){
            default://case 0:
                continue;
            case '&':
            results.append("&amp;");
            break;
            case '<':
            results.append("&lt;");
            break;
            case '>':
            results.append("&gt;");
            break;
            case '"':
              results.append("\"");
            break;
            }
            break;
        }
    }
    if(results == null) return text;
    results.append(orig,beg,len - beg);
    return results.toString();

}
%>
<%
if (request.getParameter("userinput") == null) {
%>
    Enter your input:<br><br>
    <form name="frmInput" id="frmInput" action="Case1-Tag2HtmlPageScope.jsp" method="POST">
        <input type="text" name="userinput" id="userinput"><br>
        <input type=submit value="submit">
    </form>
<%
} 
else {
  try {
            String userinput = HTML(request.getParameter("userinput")); 
             out.println("The reflected value: " + userinput);
          out.flush();
  } catch (Exception e) {
    out.println("Exception details: " + e);
  }
} //end of if/else block
%>
</body>
</html>

访问该页面,提交xss脚本代码:

插入xss脚本代码解析失败,防御代码生效。
以次修复方法可以对靶机内所有的xss跨站请求漏洞进行实验修复。

 标签: none

作者  :  redBu11



关于我

about me

redBu11

联系我