声明:文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担!
你能收获的
1
2
3
1.1 基于wavsep靶场的jsp代码审计防御
本次实验是基于wavsep进行设计,因此我们主要做了sql注入及xss跨站请求
漏洞的实验结束。
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
等练习页面即可。
1.字符型(String)sql注入 打开本次sql注入页面(wavsep中GET500中的
case1):
http://127.0.0.1:8080/wavsep/SInjection-Detection-Evaluation-GET-500E
rror/Case1-InjectionInLogin-String-LoginBypass-WithErrors.jsp?username=textvalue&password=textvalue2
在用户名和密码处插入注入语句进行尝试绕过登录('or'7'='7):
<%@ 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>Case1-Injectionintostringvaluesinaloginpagewitherroneous
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);
发现其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>Case1-Injectionintostringvaluesinaloginpagewitherroneous
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);ResultSetrs=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-G
ET-500Error/Case5-InjectionInSearchOrderBy-String-BinaryDeliberateRun
timeError-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-BinaryDeliberateRuntim
eError-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跨站请求漏洞代码审计防御实验选取GET型中的反射性xss漏洞做
实例进行查看及防御;首先访问存在反射型xss漏洞地址(xssGET中的case1):
http://127.0.0.1:8080/wavsep/RXSS-Detection-Evaluation-GET/Case1-Tag2
HtmlPageScope.jsp?userinput=textvalue
查看该页面源码:
pageEncoding="windows-1255"%><%@ page language="java" contentType="text/html; charset=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("&");
break;
case '<':
results.append("<");
break;
case '>':
results.append(">");
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("&");
break;
case '<':
results.append("<");
break;
case '>':
results.append(">");
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 {
Stringuserinput=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跨站请求漏洞进行实验修复。
本文作者:红日安全团队
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/201916.html