java开发常见漏洞_详解Javaweb中常见漏洞的防御

上一篇给大家介绍了SpringMVC中常见的客户端数据输入点,这一篇给大家讲解下java中常见漏洞的防御方法。

0x01.sql注入

下面我们就用利用SpringMVC自带的数据库操作类jdbcTemplate举例。比如下面Dao中有如下的两个函数。

函数save使用的是绑定变量的形式很好的防止了sql注入,而queryForInt_函数接收id参数直接对sql语句进行了拼接,测试时出现sql注入。

public static void save(String username,String password) {

jdbcTemplate.update("insert into test_table(user_name,password) values(?,?)",

new Object[]{username,password});

}

public static int queryForInt_(String id){

return jdbcTemplate.queryForInt("select count(0) from test_table where id = " + id);

}

public static void save(String username,String password) {

jdbcTemplate.update("insert into test_table(user_name,password) values(?,?)",

new Object[]{username,password});

}

public static int queryForInt_(String id){

return jdbcTemplate.queryForInt("select count(0) from test_table where id = " + id);

}

#为了方便仅仅贴出了DAO层代码

所以,在java代码的开发过程中,我们尽量避免使用拼接sql语句的形式去执行数据库语句。如果需要使用拼接sql语句的形式进行数据库查询,那么OWASP提供了一个防御sql注入的Esapi包,这个包中的encodeForSQL方法能对sql注入进行很好的防御。

接着我们就分析下这个encodeForSQL方法。

首先我们介绍这个方法的使用,使用时调用如下,不同的数据库使用不到的方法。

//防止Oracle注入

ESAPI.encoder().encodeForSQL(new OracleCodec(),queryparam)

//防止mysql注入

ESAPI.encoder().encodeForSQL(new MySQLCodec(Mode.STANDARD),queryparam) //Mode.STANDARK为标准的防注入方式,mysql一般用使用的是这个方式

//防止DB2注入

ESAPI.encoder().encodeForSQL(new DB2Codec(),queryparam)

//防止Oracle注入的方法例子,为了方便仅仅给出sql语句的拼接部分

Codec ORACLE_CODEC = new OracleCodec();

String query ="SELECT user_id FROM user_data WHERE user_name = ‘"+ESAPI.encoder().encodeForSQL(ORACLE_CODEC,req.getParameter("userID"))+"’ and user_password = ‘"+ESAPI.encoder().encodeForSQL(ORACLE_CODEC,req.getParameter("pwd"))+"’";

//防止Oracle注入

ESAPI.encoder().encodeForSQL(new OracleCodec(),queryparam)

//防止mysql注入

ESAPI.encoder().encodeForSQL(new MySQLCodec(Mode.STANDARD),queryparam) //Mode.STANDARK为标准的防注入方式,mysql一般用使用的是这个方式

//防止DB2注入

ESAPI.encoder().encodeForSQL(new DB2Codec(),queryparam)

//防止Oracle注入的方法例子,为了方便仅仅给出sql语句的拼接部分

Codec ORACLE_CODEC = new OracleCodec();

String query ="SELECT user_id FROM user_data WHERE user_name = ‘"+ESAPI.encoder().encodeForSQL(ORACLE_CODEC,req.getParameter("userID"))+"’ and user_password = ‘"+ESAPI.encoder().encodeForSQL(ORACLE_CODEC,req.getParameter("pwd"))+"’";

下面我们就用mysql为例字分析encodeForSQL函数做了什么防御。具体函数过程就不跟踪了,直接分析最后调用了哪个方法。根据代码可知最后调用的是encodeCharacter方法。

public String encodeCharacter( char[] immune, Character c ) {

char ch = c.charValue();

// check for immune characters

if ( containsCharacter( ch, immune ) ) {

return ""+ch;

}

// check for alphanumeric characters

String hex = Codec.getHexForNonAlphanumeric( ch );

if ( hex == null ) {

return ""+ch;

}

switch( mode ) {

case ANSI: return encodeCharacterANSI( c );

case STANDARD: return encodeCharacterMySQL( c );

}

return null;

}

public String encodeCharacter( char[] immune, Character c ) {

char ch = c.charValue();

// check for immune characters

if ( containsCharacter( ch, immune ) ) {

return ""+ch;

}

// check for alphanumeric characters

String hex = Codec.getHexForNonAlphanumeric( ch );

if ( hex == null ) {

return ""+ch;

}

switch( mode ) {

case ANSI: return encodeCharacterANSI( c );

case STANDARD: return encodeCharacterMySQL( c );

}

return null;

}

上述方法中containsCharacter函数是不进行验证的字符串白名单,Codec.getHexForNonAlphanumeric函数查找字符传中是否有16进制,没有返回空值。

而encodeCharacterANSI和encodeCharacterMySQL才是防御的重点,我们看一下这两个函数的不同,如果选择的我们选择是Mode.ANSi模式,则字符串则进入下面的函数,可以看到这个函数对单撇号和双撇号进行了转义。

private String encodeCharacterANSI( Character c ) {

if ( c == '\'' )

return "\'\'";

if ( c == '\"' )

return "";

return ""+c;

}

private String encodeCharacterANSI( Character c ) {

if ( c == '\'' )

return "\'\'";

if ( c == '\"' )

return "";

return ""+c;

}

如果选择的是Mode.STANDARD模式,则字符串则进入下面的函数,可以看到这个函数对单撇号和双撇号、百分号、反斜线等更多的符号进行了转换,所以使用时推荐使用标准模式。

private String encodeCharacterMySQL( Character c ) {

char ch = c.charValue();

if ( ch == 0x00 ) return "\\0";

if ( ch == 0x08 ) return "\\b";

if ( ch == 0x09 ) return "\\t";

if ( ch == 0x0a ) return "\\n";

if ( ch == 0x0d ) return "\\r";

if ( ch == 0x1a ) return "\\Z";

if ( ch == 0x22 ) return "\\\"";

if ( ch == 0x25 ) return "\\%";

if ( ch == 0x27 ) return "\\'";

if ( ch == 0x5c ) return "\\\\";

if ( ch == 0x5f ) return "\\_";

return "\\" + c;

}

private String encodeCharacterMySQL( Character c ) {

char ch = c.charValue();

if ( ch == 0x00 ) return "\\0";

if ( ch == 0x08 ) return "\\b";

if ( ch == 0x09 ) return "\\t";

if ( ch == 0x0a ) return "\\n";

if ( ch == 0x0d ) return "\\r";

if ( ch == 0x1a ) return "\\Z";

if ( ch == 0x22 ) return "\\\"";

if ( ch == 0x25 ) return "\\%";

if ( ch == 0x27 ) return "\\'";

if ( ch == 0x5c ) return "\\\\";

if ( ch == 0x5f ) return "\\_";

return "\\" + c;

}

我们介绍了利用绑定变量和利用esapi两种方式对sql注入进行防御,我的建议是尽量使用绑定变量的是形式进行防注入,安全性能都比较好。

0x02:跨站脚本攻击

关于跨站脚本攻击的防御,我们分析esapi的防御方式。

esapi的防御方式是根据输出点的不同在不同的输出点进行相应的编码。我们看一下使用方法:

xss输出点在html网页中

ESAPI.encoder().encodeForHTML(String input)

xss输出点在html属性中

ESAPI.encoder().encodeForHTMLAttribute(String input)

xss输出点在JavaScript代码中

ESAPI.encoder().encodeForJavaScript(String input)

xss输出点在CSS代码中

ESAPI.encoder().encodeForCSS(String input)

xss输出点在VBScript代码中

ESAPI.encoder().encodeForVBScript(String input)

xss输出点在XPath中

ESAPI.encoder().encodeForXPath(String input)

xss输出点在XML中

ESAPI.encoder().encodeForXML(String input)

xss输出点在XML属性中

ESAPI.encoder().encodeForXMLAttribute(String input)

直接对url进行URL编码

ESAPI.encoder().encodeForURL(String input)

xss输出点在html网页中

ESAPI.encoder().encodeForHTML(String input)

xss输出点在html属性中

ESAPI.encoder().encodeForHTMLAttribute(String input)

xss输出点在JavaScript代码中

ESAPI.encoder().encodeForJavaScript(String input)

xss输出点在CSS代码中

ESAPI.encoder().encodeForCSS(String input)

xss输出点在VBScript代码中

ESAPI.encoder().encodeForVBScript(String input)

xss输出点在XPath中

ESAPI.encoder().encodeForXPath(String input)

xss输出点在XML中

ESAPI.encoder().encodeForXML(String input)

xss输出点在XML属性中

ESAPI.encoder().encodeForXMLAttribute(String input)

直接对url进行URL编码

ESAPI.encoder().encodeForURL(String input)

如果java输出在html页面,使用如下示例的方法即可。

String username = ESAPI.encoder().encodeForHTML(req.getParameter("name"))

String username = ESAPI.encoder().encodeForHTML(req.getParameter("name"))

接下来我们就研究这个方法的具体实现。

public String encodeCharacter( char[] immune, Character c ) {

// check for immune characters

if ( containsCharacter(c, immune ) ) {

return ""+c;

}

// check for alphanumeric characters

String hex = Codec.getHexForNonAlphanumeric(c);

if ( hex == null ) {

return ""+c;

}

// check for illegal characters

//ascii码中的非数字,字符的编码,一般为非打印字符,即不能转换的为uncoide的ascii字符,直接替换成\ufffd,显示的为?

if ( ( c <= 0x1f && c != '\t' && c != '\n' && c != '\r' ) || ( c >= 0x7f && c <= 0x9f ) )

{

hex = REPLACEMENT_HEX;  // Let's entity encode this instead of returning it

c = REPLACEMENT_CHAR;

}

// check if there's a defined entity

//#恶意字符以实体的形式输出

String entityName = (String) characterToEntityMap.get(c);

if (entityName != null) {

return "&" + entityName + ";";

}

// return the hex entity as suggested in the spec,#如果是16进制就转换为html16进制实体字符输出

return "" + hex + ";";

}

public String encodeCharacter( char[] immune, Character c ) {

// check for immune characters

if ( containsCharacter(c, immune ) ) {

return ""+c;

}

// check for alphanumeric characters

String hex = Codec.getHexForNonAlphanumeric(c);

if ( hex == null ) {

return ""+c;

}

// check for illegal characters

//ascii码中的非数字,字符的编码,一般为非打印字符,即不能转换的为uncoide的ascii字符,直接替换成\ufffd,显示的为?

if ( ( c <= 0x1f && c != '\t' && c != '\n' && c != '\r' ) || ( c >= 0x7f && c <= 0x9f ) )

{

hex = REPLACEMENT_HEX; // Let's entity encode this instead of returning it

c = REPLACEMENT_CHAR;

}

// check if there's a defined entity

//#恶意字符以实体的形式输出

String entityName = (String) characterToEntityMap.get(c);

if (entityName != null) {

return "&" + entityName + ";";

}

// return the hex entity as suggested in the spec,#如果是16进制就转换为html16进制实体字符输出

return "" + hex + ";";

}

containsCharacter函数一般定义不需要编码的字符,如果我们不想编码那个字符就可以利用这个函数定义。

Codec.getHexForNonAlphanumeric函数判断是否是数字和字母,如果仅仅是字符和字母就直接返回,不在编码。

剩下的代码是对非数字和字母的字符进行实体编码或者html十六进制字符编码。

如果java输出在js代码页面,使用如下示例的方法即可。

String username = ESAPI.encoder().encodeForJavaScript(req.getParameter("name"))

String username = ESAPI.encoder().encodeForJavaScript(req.getParameter("name"))

我们研究一下encodeForJavaScript方法:

public String encodeCharacter( char[] immune, Character c ) {

// check for immune characters

if ( containsCharacter(c, immune ) ) {

return ""+c;

}

// check for alphanumeric characters

String hex = Codec.getHexForNonAlphanumeric(c);

if ( hex == null ) {

return ""+c;

}

// Do not use these shortcuts as they can be used to break out of a context

// if ( ch == 0x00 ) return "\\0";

// if ( ch == 0x08 ) return "\\b";

// if ( ch == 0x09 ) return "\\t";

// if ( ch == 0x0a ) return "\\n";

// if ( ch == 0x0b ) return "\\v";

// if ( ch == 0x0c ) return "\\f";

// if ( ch == 0x0d ) return "\\r";

// if ( ch == 0x22 ) return "\\\"";

// if ( ch == 0x27 ) return "\\'";

// if ( ch == 0x5c ) return "\\\\";

// encode up to 256 with \\xHH,编码成js十六进制的形式

String temp = Integer.toHexString(c);

if ( c 

String pad = "00".substring(temp.length() );

return "\\x" + pad + temp.toUpperCase();

}

// otherwise encode with \\uHHHH,编码成jsunicode编码格式

String pad = "0000".substring(temp.length() );

return "\\u" + pad + temp.toUpperCase();

}

public String encodeCharacter( char[] immune, Character c ) {

// check for immune characters

if ( containsCharacter(c, immune ) ) {

return ""+c;

}

// check for alphanumeric characters

String hex = Codec.getHexForNonAlphanumeric(c);

if ( hex == null ) {

return ""+c;

}

// Do not use these shortcuts as they can be used to break out of a context

// if ( ch == 0x00 ) return "\\0";

// if ( ch == 0x08 ) return "\\b";

// if ( ch == 0x09 ) return "\\t";

// if ( ch == 0x0a ) return "\\n";

// if ( ch == 0x0b ) return "\\v";

// if ( ch == 0x0c ) return "\\f";

// if ( ch == 0x0d ) return "\\r";

// if ( ch == 0x22 ) return "\\\"";

// if ( ch == 0x27 ) return "\\'";

// if ( ch == 0x5c ) return "\\\\";

// encode up to 256 with \\xHH,编码成js十六进制的形式

String temp = Integer.toHexString(c);

if ( c < 256 ) {

String pad = "00".substring(temp.length() );

return "\\x" + pad + temp.toUpperCase();

}

// otherwise encode with \\uHHHH,编码成jsunicode编码格式

String pad = "0000".substring(temp.length() );

return "\\u" + pad + temp.toUpperCase();

}

恶意字符在js中的编码大家可以看到使用的是js的十六进制编码或者jsunicode编码进行的编码。

其实上面的方法大都是对字符进行html实体编码,html十六进制编码,js十六进制编码,jsunicode的编码和url编码来防止恶意标签的执行。如果感兴趣可以看一下其他的编码方法,原理大致相同就不在一一介绍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,你的问题是如何解决JavaWeb上传图片时的过滤问题。这个问题其实可以通过在JavaWeb使用Filter来解决。 具体的实现方法如下: 1. 创建一个过滤器类,实现javax.servlet.Filter接口。 2. 在doFilter方法,获取上传文件的文件名,然后判断文件名是否以.jpg、.jpeg、.png、.gif等图片格式结尾。 3. 如果是图片文件,则直接放行,否则就拒绝上传并返回错误信息。 4. 在web.xml文件配置该过滤器。 下面是一个简单的示例代码,仅供参考: ``` import java.io.IOException; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebFilter(filterName = "UploadFilter", urlPatterns = {"/upload/*"}) public class UploadFilter implements Filter { private String[] allowedExt = {".jpg", ".jpeg", ".png", ".gif"}; public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; String fileName = request.getHeader("file-name"); if (fileName != null) { for (String ext : allowedExt) { if (fileName.endsWith(ext)) { chain.doFilter(request, response); return; } } } response.sendError(HttpServletResponse.SC_BAD_REQUEST, "只允许上传图片文件"); } public void init(FilterConfig filterConfig) throws ServletException { } public void destroy() { } } ``` 这个过滤器会拦截所有以"/upload/"开头的URL,并检查上传文件名是否以.jpg、.jpeg、.png、.gif等图片格式结尾。如果是图片文件,则直接放行,否则就拒绝上传并返回错误信息。你可以根据实际需求进行修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值