赞
踩
前两天做了一个关于后台的防止SQL注入的操作。因为项目数据层全部编码完成,现在再来大动干戈修改数据层,有点繁琐耗时。所以就添加了一个过滤器来拦截前台传递到后台的参数信息,在数据进入控制层之前先拦截信息进行检查,如果含有SQL注入的关键字,则直接返回前台。所以需要针对前台传递的各种json字符串和json数组进行解析。
因为在过滤器里面先获取了request里面的payload的信息,而在request里面的payload信息是以流的形式存储的,所以读取了,后面控制层将无法获取,那么就需要在进行json解析之前,先处理一下request里面的信息。
将request读取之后,再将流读取位置置为开始,那么其他的request就能继续读取了。具体操作如下:
public class RequestWrapper extends HttpServletRequestWrapper { private final String body; public RequestWrapper(HttpServletRequest request) { super(request); StringBuilder stringBuilder = new StringBuilder(); BufferedReader bufferedReader = null; InputStream inputStream = null; try { inputStream = request.getInputStream(); if (inputStream != null) { bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); char[] charBuffer = new char[128]; int bytesRead = -1; while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { stringBuilder.append(charBuffer, 0, bytesRead); } } else { stringBuilder.append(""); } } catch (IOException ex) { ex.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } } body = stringBuilder.toString(); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes()); return new ServletInputStream() { public boolean isFinished() { return false; } public boolean isReady() { return false; } public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return bais.read(); } }; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } public String getBody() { return this.body; } }
下面继续解决SQL注入的问题,这里选择了两种json解析方式.
我开始解析json用了net.sf.json的包。导入依赖如下。
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
下面首先是获取前台请求的URL中采用get方式传递过来的是否含有SQL注入的关键字。然后再判断request的payload中的参数值是否含有关键字。解析body里面的json,采用了递归的方式,判断body是否实例化了jsonObject或jsonArray来区分json字符串、json数组、数组等。具体如下代码:
import net.sf.json.JSONArray; import net.sf.json.JSONException; import net.sf.json.JSONObject; import net.sf.json.util.JSONTokener; @Component public class SQLValidateFilter implements Filter { private static final Logger logger = LoggerFactory.getLogger(SQLValidateFilter.class); @Value("${spring.profiles.active}") private String activeProfile; @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { List<String> listValues = new ArrayList<String>(); RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) request); HttpServletResponse resp = (HttpServletResponse) response; listValues = getParametersBehindUrl(requestWrapper, listValues); String body = requestWrapper.getBody(); if (body != null && !StringUtils.EMPTY.equals(body)) { listValues = getBodyValue(listValues, body); } getValidate(resp, chain, requestWrapper, listValues); } /** * 验证request里面是否含有非法字符 * @param response * @param filterChain * @param requestWrapper * @param listValues * @throws IOException * @throws ServletException */ private void getValidate(HttpServletResponse response, FilterChain filterChain, RequestWrapper requestWrapper, List<String> listValues) throws IOException, ServletException { if (sqlValidate(listValues)) { response.setStatus(417); if ("test".equals(activeProfile)) { response.setContentType("text/html;charset=GBK"); } else { response.setContentType("text/html;charset=UTF-8"); } response.getOutputStream().write("您发送请求中的参数中含有非法字符".getBytes()); return; } else { filterChain.doFilter(requestWrapper, response); } } /** * 获取URL后面的值 * @param req * @param listValues * @return */ private List<String> getParametersBehindUrl(ServletRequest req, List<String> listValues) { Enumeration<?> params = req.getParameterNames(); while (params.hasMoreElements()) { String name = params.nextElement().toString(); String[] value = req.getParameterValues(name); for (int i = 0; i < value.length; i++) { listValues.add(value[i]); } } return listValues; } /** * 获取body里面的参数值 * @param listValues * @param body * @return */ private List<String> getBodyValue(List<String> listValues, String body) { JSONTokener token = new JSONTokener(body); try { while (token.more()) { Object value = token.nextValue(); if (value instanceof JSONObject) { listValues.addAll(parseJsonObject((JSONObject) value)); } else if (value instanceof JSONArray) { listValues.addAll(parseJsonArray((JSONArray) value)); } else { listValues.add(value.toString()); } } } catch (JSONException e) { logger.error(e.getMessage(), e); } return listValues; } /** * 解析 Json 对象,获取值列表 * * @param object * @return * @throws JSONException */ private List<String> parseJsonObject(JSONObject object) throws JSONException { List<String> results = new ArrayList<String>(); Iterator<?> keys = object.keys(); while (keys.hasNext()) { String key = (String) keys.next(); Object value = object.get(key); if (value instanceof JSONObject) { results.addAll(parseJsonObject((JSONObject) value)); } else if (value instanceof JSONArray) { results.addAll(parseJsonArray((JSONArray) value)); } else { results.add(value.toString()); } } return results; } /** * 解析 Json 数组,获取值列表 * * @param array * @return * @throws JSONException */ private List<String> parseJsonArray(JSONArray array) throws JSONException { List<String> results = new ArrayList<String>(); for (int index = 0; index < array.size(); index++) { Object value = array.get(index); if (value instanceof JSONObject) { results.addAll(parseJsonObject((JSONObject) value)); } else if (value instanceof JSONArray) { results.addAll(parseJsonArray((JSONArray) value)); } else { results.add(value.toString()); } } return results; } // 校验 protected static boolean sqlValidate(List<String> listValues) { String dataValues = listValues.toString().toLowerCase(Locale.ENGLISH);// 统一转为小写 String badStr = "'|and|exec|execute|insert|select|delete|count|drop|*|chr|mid|master|truncate|" + "char|declare|net user|xp_cmdshell|;|+|like'|and|exec|execute|insert|create" + "table|from|grant|use|group_concat|column_name|" + "information_schema.columns|table_schema|union|where|order|by|*|//|--|#|"; String[] badStrs = badStr.split("\\|"); for (int i = 0; i < badStrs.length; i++) { if (dataValues.contains(badStrs[i])) { logger.info("dataValues=" + dataValues + " marchValue=" + badStrs[i]); return true; } } return false; } @Override public void destroy() { // TODO Auto-generated method stub } }
因为后台与前台的数据交互,封装的json本身就是采用jackson的,所以后面对上的代码进行了优化。直接使用jackson实现对json数据的处理。添加依赖如下:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.10</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.10</version>
<dependency>
下面直接使用jackson的属性来获取
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @Component public class SQLValidateFilter implements Filter { private static final Logger logger = LoggerFactory.getLogger(SQLValidateFilter.class); @Value("${spring.profiles.active}") private String activeProfile; @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { RequestWrapper wrapper = new RequestWrapper((HttpServletRequest) request); HttpServletResponse resp = (HttpServletResponse) response; if (existsInvalidSqlTokenInRequest(wrapper)) { resp.setStatus(417); String contentType = "test".equals(activeProfile) ? "text/html;charset=GBK" : "text/html;charset=UTF-8"; response.setContentType(contentType); response.getOutputStream().write("您发送请求中的参数中含有非法字符".getBytes()); return; } chain.doFilter(wrapper, resp); } /** * 判断请求中是否有非法 SQL 关键字 * * @param request * @return * @throws IOException * @throws JsonProcessingException */ private boolean existsInvalidSqlTokenInRequest(RequestWrapper request) throws JsonProcessingException, IOException { for (String value : getParameterValuesBehindUrl(request)) { if (findInvalidSqlToken(value)) { return true; } } for (String value : getParameterValuesInBody(request)) { if (findInvalidSqlToken(value)) { return true; } } return false; } /** * 从 URL 中提取参数值 * * @param request * @return */ private List<String> getParameterValuesBehindUrl(RequestWrapper request) { List<String> results = new ArrayList<String>(); Enumeration<String> params = request.getParameterNames(); while (params.hasMoreElements()) { String name = params.nextElement().toString(); String[] values = request.getParameterValues(name); for (String value : values) { results.add(value); } } return results; } /** * 从报文体中提取参数值 * * @param request * @return * @throws JsonProcessingException * @throws IOException */ private List<String> getParameterValuesInBody(RequestWrapper request) throws JsonProcessingException, IOException { List<String> results = new ArrayList<String>(); String body = request.getBody(); if (StringUtils.isNotBlank(body)) { ObjectMapper mapper = new ObjectMapper(); JsonNode node = mapper.readTree(body); results.addAll(parseJsonNode(node)); } return results; } /** * 从 JSON 节点中提取参数值 * * @param node * @return */ private List<String> parseJsonNode(JsonNode node) { List<String> results = new ArrayList<String>(); switch (node.getNodeType()) { case ARRAY: for (int index = 0; index < node.size(); index++) { results.addAll(parseJsonNode(node.get(index))); } break; case OBJECT: Iterator<Map.Entry<String, JsonNode>> fields = node.fields(); while (fields.hasNext()) { results.addAll(parseJsonNode(fields.next().getValue())); } break; default: results.add(node.toString()); break; } return results; } /** * 从字符串中查找 SQL 关键字 * * @param value * @return */ private boolean findInvalidSqlToken(String value) { String lowerCaseValue = value.toLowerCase(Locale.ENGLISH); String sqlTokens = "'|and|exec|execute|insert|select|delete|count|drop|*|chr|mid|master|truncate|" + "char|declare|net user|xp_cmdshell|;|+|like'|and|exec|execute|insert|create" + "table|from|grant|use|group_concat|column_name|" + "information_schema.columns|table_schema|union|where|order|by|*|//|--|#|"; for (String token : sqlTokens.split("\\|")) { if (lowerCaseValue.contains(token)) { logger.info("dataValue=" + lowerCaseValue + ", marchValue=" + token); return true; } } return false; } @Override public void destroy() { // TODO Auto-generated method stub } }
到此,项目的SQL注入问题解决。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。