背景:
在工作中,我们的交付团队在交付项目时,可能会遇到甲方会使用一些第三方工具(奇安信等)对项目代码进行扫描,特别是一些对安全性要求比较高的企业,比如涉及到一些证券公司、银行、金融等。他们会在项目上线前进行代码安全检测,通过了对方才会发布上线。正好我所在企业中的交付团队遇到了这种情况,我将最后我们团队针对一些漏洞的修复代码分享出来供大家参考,方便未来自己修复同样的漏洞,当然漏洞的种类不是很全,我会在以后遇到其他类型时及时更新。
1.存储型XSS&反射型XSS
1.1 漏洞描述
存储型XSS是指应用程序通过Web请求获取不可信赖的数据,并且在未检验数据是否存在XSS代码的情况下,将其存入数据库。当程序下一次从数据库中获取该数据时,致使页面再次执行XSS代码。存储型XSS可以持续攻击用户,在用户提交了包含XSS代码的数据存储到数据库后,每当用户在浏览网页查询对应数据库中的数据时,那些包含XSS代码的数据就会在服务器解析并加载,当浏览器读到XSS代码后,会当做正常的HTML和JS解析并执行,于是发生存储型XSS攻击。
反射型XSS是指应用程序通过Web请求获取不可信赖的数据,并在未检验数据是否存在恶意代码的情况下,将其发送给用户。反射型XSS一般可以由攻击者构造带有恶意代码参数的URL来实现,在构造的URL地址被打开后,其中包含的恶意代码参数被浏览器解析和执行。这种攻击的特点是非持久化,必须用户点击包含恶意代码参数的链接时才会触发。
我们的项目中被扫出这两个问题的时候还是比较头疼的,因为出现的频率很高,有一千五百多个暴露代码行,特别是存储型XSS,基本在我们所有写的接口返回那都暴露了,比如下面这张截图。
1.2 漏洞修复
我们后台的接口很多,我们不可能一行一行处理。经过几分钟的和甲方讨论我们将这两种类型视为一种漏洞类型,并且进行在公共地方修改加过滤处理。大致代码如下:
@ControllerAdvice
@Slf4j
public class CleanXssResponseAdvice implements ResponseBodyAdvice<Object> {
//只对返回json格式的起作用
private static List<MediaType> list = Arrays.asList(APPLICATION_JSON_UTF8, APPLICATION_JSON);
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return false;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (list.contains(selectedContentType) && !ObjectUtils.isEmpty(body) && body instanceof InvokeResultUtil){
InvokeResultUtil invokeResultUtil = (InvokeResultUtil) body;
Object data = invokeResultUtil.getData();
if (!ObjectUtils.isEmpty(data) && invokeResultUtil.isSuccess() ) {
String dataString = JSONObject.toJSONString(data, SerializerFeature.WriteDateUseDateFormat);
Object object = JSON.parseObject(StringUtils.cleanXSS(dataString), data.getClass());
InvokeResultUtil result = new InvokeResultUtil(object);
result.setCode(200);
result.setMessage("sucess");
return result;
} else {
return body;
}
} else {
return body;
}
}
}
import org.owasp.esapi.ESAPI;
import org.owasp.esapi.Encoder;
import org.owasp.esapi.errors.EncodingException;
public static String cleanXSS(String value) {
Encoder encoder = ESAPI.encoder();
value = encoder.encodeForJavaScript(value);
value = encoder.encodeForHTML(value);
try {
String v2 = encoder.encodeForURL(value);
value = v2;
} catch (EncodingException e) {
log.error("cleanXSS url编码错误");
e.printStackTrace();
}
return value;
}
2.HTTP响应截断
2.1 漏洞描述
程序从一个不可信赖的数据源获取数据,未进行验证就置于HTTP头文件中发给用户,可能会导致HTTP响应截断攻击。
例如如果请求中提交的是一个Jane Smith字符串,那么包含该cookie的HTTP响应可能表现为以下形式:
HTTP/1.1 200 OK
Set-Cookie: author=Jane Smith
那么如果攻击者提交的是一个恶意字符串,比如Wiley Hacker\r\nHTTP/1.1 200 OK\r\n…,那么HTTP响应就会被分割成以下形式的
HTTP/1.1 200 OK
...
Set-Cookie: author=Jane Smith
...
那么如果攻击者提交的是一个恶意字符串,比如Wiley Hacker\r\nHTTP/1.1 200 OK\r\n...
,那么HTTP响应就会被分割成以下形式的两个响应:
HTTP/1.1 200 OK
...
Set-Cookie: author=Wiley Hacker
HTTP/1.1 200 OK
...
2.2 漏洞修复
程序中在处理请求头时,在设置请求头值时过滤:
headers.add("Authorization", HtmlUtil2.encodeForHtml(authorization));
or
response.setHeader("content-length", HtmlUtil2.encodeForHtml("" + contentLength));
public class HtmlUtil2 {
public static String encodeForHtml(String input) {
if (input == null) {
return null;
}
//HtmlUtils.htmlEscape
// 在这里添加适当的HTML编码逻辑,以防止恶意内容被解释为HTML
return input.replace("<", "<").replace(">", ">")
.replaceAll("\r", "").replaceAll("\n", "")
.replaceAll(" ", "").replaceAll("/","");
}
}
3.Json注入
3.1 漏洞描述
JSON注入是指应用程序所解析的JSON文档或请求来源于不可信赖的数据源,程序没有对这些不可信赖的数据进行验证、过滤,攻击者可以插入一些元素做一些可以预测的操作,比如解析JSON失败,导致抛出异常,甚至可以导致XSS和动态解析代码。
3.2 漏洞修复
我们在使用工具类进行json转换时进行判断是否合法:
public static JSONObject fromJson(String json) {
json = isValidJson(json);
return JSONObject.parseObject(json);
}
public static String isValidJson(String json) {
json = StringEscapeUtils.escapeJson(json);
json = StringEscapeUtils.unescapeJson(json);
try {
JSON.parse(json, Feature.IgnoreNotMatch);
return json;
} catch (JSONException e) {
throw new IllegalArgumentException("不是有效的Json ");
}
}
4.文件上传
4.1 漏洞描述
文件可能会被攻击者注入危险内容或恶意代码,当程序允许用户上传时,攻击者可以通过文件将恶意代码在服务器上运行或者将危险内容注入程序。
4.2 漏洞修复
对上传文件后缀与MIME Type进行匹配校验, 对文件头信息与文件后缀进行匹配校验,并且对文件路径和文件名字进行校验,校验是否存在"/“,”“,”~“,”…“,”./“,”…/"。
public static boolean valid(String str) {
return str.contains("../") || str.contains("/") || str.contains("~") || str.contains("..") || str.contains("./") || str.contains("\\");
}
5.硬编码URL
5.1 漏洞描述
程序中采用硬编码方式处理URL地址,一方面会降低系统安全性,另一方面不易于程序维护。
5.2 漏洞修复
先加密后解密:
public static final String ALI_NLS_TOKEN = new String(Base64.getDecoder().decode("Base64后的密文"));
6.明文密码
6.1 漏洞描述
明文密码会降低系统安全性,应对程序中使用的密码值进行加密。
6.2 漏洞修复
同硬编码URL一样的处理方式,或者将password加密成密文,这样扫描工具就不会扫到相关字段。
7.重定向
7.1 漏洞描述
应用程序允许未验证的用户输入控制重定向中的URL,攻击通过构建URL,使用户重定向到任意URL,利用这个漏洞可以诱使用户访问某个页面,挂马、密码记录、下载任意文件等,常被用来钓鱼。
7.2 漏洞修复
主要是检验url是不是合法url:
注意这里方法名一定是safeUrls,这里是按照扫描工具修复建议。
public static boolean safeUrls(String url) {
URI uri;
try {
uri = new URI(url);
} catch (URISyntaxException e) {
e.printStackTrace();
return false;
}
if(uri.getHost() == null){
return false;
}
return "http".equalsIgnoreCase(uri.getScheme()) || "https".equalsIgnoreCase(uri.getScheme());
}
8.路径遍历
8.1 漏洞描述
应用程序对用户可控制的输入未经合理校验,就传送给一个文件API。攻击者可能会使用一些特殊的字符(如…和/)摆脱受保护的限制,访问一些受保护的文件或目录。
8.2 漏洞修复
在对文件进行操作时,调用此方法过滤一下:
public static Boolean securityFilePathCheck(String filePath, String filePathWhite) {
String[] filePathWhitelist = new String[]{};
if (StringUtils.isNotBlank(filePathWhite)) {
filePathWhitelist = filePathWhite.split(",");
}
if (StringUtils.isBlank(filePath)) {
return false;
}
// 路径安全校验
if (filePath.contains("../")) {
throw new BusinessException("不支持的操作");
}
filePath = getSecurityFilePath(filePath);
File file = FileUtils.getFile(filePath);
filePath = file.getAbsolutePath();
if (filePathWhitelist == null || filePathWhitelist.length == 0) return true;
for (String whiteFilePath : filePathWhitelist) {
PathMatcher matcher = FileSystems.getDefault().getPathMatcher(whiteFilePath);
if (matcher.matches(Paths.get(filePath))) {
return true;
}
}
throw new BusinessException("该路径不在白名单中");
}
9.命令注入
9.1 漏洞描述
命令注入是指,在应用程序执行的命令中包含来源于不可信数据时,程序本身没有对这些不可信数据做正确、合理的验证和过滤,导致系统执行恶意命令。
9.2 漏洞修复
过滤:
public static String getSecurityFilePath(String filePath) {
if (StringUtils.isBlank(filePath)) {
return null;
}
filePath = filePath.replace("<", "<").replace(">", ">")
.replaceAll("\r", "").replaceAll("\n", "")
.replaceAll(" ", "");
File file = FileUtils.getFile(filePath);
return file.getAbsolutePath();
}
还有漏洞比如SQL相关的注入和安全问题处理起来就比较简单了,这里就不再赘述。其实很多漏洞的修复就是在原本的代码上加了一层所谓的“安全检测”,目的仅仅是能够通过工具的安全扫描,上面部分代码我要求方法名必须是什么也是满足工具给的建议,工具不会去看你写的过滤器里面有什么逻辑,可能就仅仅扫描到你含有建议中的关键字、关键方法就可以了。