作者 | 描述 | 时间 |
---|---|---|
雨中星辰 | 2021-03-29 | 记录项目中遇到的fortify问题及处理办法 |
JSON Injection(json注入)
- 摘要
该方法会将未经验证的输入写入 JSON。攻击者可以利用此调用将任意元素或属性注入 JSON 实体。 - 解释
JSON injection 会在以下情况中出现:
- 数据从一个不可信赖的数据源进入程序。
- 将数据写入到 JSON流。 应用程序通常使用 JSON 来存储数据或发送消息。用于存储数据时, JSON 通常会像缓存数据那样处理, 而且可能会包含敏感信息。用于发送消息时, JSON 通常与 RESTful 服务一起使用,并且可以用于传输敏感信息,例如身份验证凭据。 如果应用程序利用未经验证的输入构造 JSON,则可以更改 JSON 文档和消息的语义。在相对理想的情况下,攻击者可能会插入无关的元素,导致应用程序在解析 JSON 文档或请求时抛出异常。在更为严重的情况下,例如涉及 JSON Injection,攻击者可能会插入无关的元素,从而允许对JSON 文档或请求中对业务非常关键的值执行可预见操作。还有一些情况, JSON Injection 可以导致 CrossSite Scripting 或 Dynamic Code Evaluation。
例 1: 以下 Java 代码使用 Jackson 将非特权用户(这些用户具有“默认”角色,与之相反,特权用户具有“管理员”角色)的用户帐户身份验证信息从用户控制的输入变量
username 和 password 序列化为位于 ~/user_info.json 的 JSON 文件:
JsonFactory jfactory = new JsonFactory();
JsonGenerator jGenerator = jfactory.createJsonGenerator(new File("~/
user_info.json"), JsonEncoding.UTF8);
jGenerator.writeStartObject();
jGenerator.writeFieldName("username");
jGenerator.writeRawValue("\"" + username + "\"");
jGenerator.writeFieldName("password");
jGenerator.writeRawValue("\"" + password + "\"");
jGenerator.writeFieldName("role");
jGenerator.writeRawValue("\"default\"");
jGenerator.writeEndObject();
jGenerator.close();
但是,由于 JSON 序列化使用 JsonGenerator.writeRawValue() 来执行,将不会对 username 和
password 中的不可信赖数据进行验证以转义与 JSON 相关的特殊字符。这样,用户就可以任意插入 JSON
密钥,可能会更改已序列化的 JSON 的结构。在本例中,在设置 username 的值的提示符下输入用户名时,
如果非特权用户 mallory(密码为 Evil123!)将 ","role":"admin 附加到其用户名中,则最终保存到
~/user_info.json 的 JSON 将为:
{
"username":"mallory",
"role":"admin",
"password":"Evil123!",
"role":"default"
}
如果随后将此序列化 JSON 文件反序列化为 HashMap 对象,其中 Jackson 的 JsonParser 如下所示:
JsonParser jParser = jfactory.createJsonParser(new File("~/user_info.json"));
while (jParser.nextToken() != JsonToken.END_OBJECT) {
String fieldname = jParser.getCurrentName();
if ("username".equals(fieldname)) {
jParser.nextToken();
userInfo.put(fieldname, jParser.getText());
}
if ("password".equals(fieldname)) {
jParser.nextToken();
userInfo.put(fieldname, jParser.getText());
}
if ("role".equals(fieldname)) {
jParser.nextToken();
userInfo.put(fieldname, jParser.getText());
}
if (userInfo.size() == 3)
break;
}
jParser.close();
HashMap 对象中 username
、 password
和 role
密钥的最终值将分别为 mallory
、 Evil123!
和 admin
。 在没有进一步验证反序列化 JSON 值是否有效的情况下,应用程序会错误地为用户mallory
分配 管理员
特权。
- 解决办法
引入依赖
<dependency>
<groupId>com.mikesamuel</groupId>
<artifactId>json-sanitizer</artifactId>
<version>1.2.2</version>
</dependency>
使用方法com.google.json.JsonSanitizer
类的public static String sanitize(String jsonish)
对json字符串进行一个预处理,避免json注入。
例:
JsonSanitizer.sanitize(json)
Cross-Site Scrpting(xss)跨站脚本漏洞
- 摘要
向一个 Web 浏览器发送未经验证的数据会导致该浏览器执行恶意代码。 - 描述
Cross-Site Scripting (XSS) 漏洞在以下情况下发生:
- 数据通过一个不可信赖的数据源进Web 应用程序。 对于基于 DOM 的 XSS,将从 URL 参数或浏览器中的其他值读取数据,并使用客户端代码将其重新写入该页面。对于 Reflected XSS,不可信赖的数据源通常为 Web 请求,而对于 Persisted(也称为 Stored)XSS,该数据源通常为数据库或其他后端数据存储。
- 未检验包含在动态内容中的数据,便将其传送给了Web 用户。对于基于 DOM 的 XSS,任何时候当受害人的浏览器解析 HTML 页面时,恶意内容都将作为DOM(文档对象模型)创建的一部分执行。 传送到 Web 浏览器的恶意内容通常采用 JavaScript 代码片段的形式,但也可能会包含一些HTML、 Flash 或者其他任意一种可以被浏览器执行的代码。基于 XSS 的攻击手段花样百出,几乎是无穷无尽的,但通常它们都会包含传输给攻击者的私有数据(如 Cookie 或者其他会话信息)。 在攻击者的控制下,指引受害者进入恶意的网络内容;或者利用易受攻击的站点,对户的机器进行其他恶意操作。
例 1: 下面的 JavaScript 代码片段可从 URL 中读取雇员 ID eid,并将其显示给用户
<SCRIPT>
var pos=document.URL.indexOf("eid=")+4;
document.write(document.URL.substring(pos,document.URL.length));
</SCRIPT>
示例 2: 考虑使用 HTML 表单:
<div id="myDiv">
Employee ID: <input type="text" id="eid"><br>
...
<button>Show results</button>
</div>
<div id="resultsDiv">
...
</div>
下面的 jQuery 代码片段可从表单中读取雇员 ID,并将其显示给用户。
$(document).ready(function(){
$("#myDiv").on("click", "button", function(){
var eid = $("#eid").val();
$("resultsDiv").append(eid);
...
});
});
如果雇员 ID eid 只包含标准的字母数字文本,此代码就会正常运行。如果 eid 里有包含元字符或源代码中的值,那么 Web 浏览器就会像显示 HTTP 响应那样执行代码。 起初,这个例子似乎是不会轻易遭受攻击的。 毕竟,有谁会输入导致恶意代码的 URL,并且还在自己的电脑上运行呢?真正的危险在于攻击者会创建恶意的 URL,然后采用电子邮件或者社会工程的欺骗手段诱使受害者访问此 URL 的链接。当受害者单击这个链接时,他们不知不觉地通过易受攻击的网络应用程序,将恶意内容带到了自己的电脑中。这种对易受攻击的 Web 应用程序进行盗取的机制通常被称为反射式 XSS。 正如例子中所显示的, XSS 漏洞是由于 HTTP 响应中包含了未验证的数据代码而引起的。
受害者遭受 XSS 攻击的途径有三种:
- 系统从 HTTP 请求中直接读取数据,并在 HTTP 响应中返回数据。当攻击者诱使用户为易受攻击的 Web 应用程序提供危险内容,而这些危险内容随后会反馈给用户并在 Web 浏览器中执行,就会发生反射式 XSS 盗取。发送恶意内容最常用的方法是,把恶意内容作为一个参数包含在公开发表的 URL 中,或者通过电子邮件直接发送给受害者。以这种手段构造的 URL 构成了多种“网络钓鱼”(phishing) 阴谋的核心,攻击者借此诱骗受害者访问指向易受攻击站点的 URL。站点将攻击者的内容反馈给受害者以后,便会执行这些内容,接下来会把用户计算机中的各种私密信息(比如包含会话信息的 cookie)传送给攻击者,或者执行其他恶意活动。
- 应用程序将危险数据存储在数据库或其他可信赖的数据存储器中。这些危险数据随后会被回写到应用程序中,并包含在动态内容中。Persistent XSS 盗取发生在如下情况:攻击者将危险内容注入到数据存储器中,且该存储器之后会被读取并包含在动态内容中。从攻击者的角度看,注入恶意内容的最佳位置莫过于一个面向许多用户,尤其是相关用户显示的区域。相关用户通常在应用程序中具备较高的特权,或相互之间交换敏感数据,这些数据对攻击者来说有利用价值。如果某一个用户执行了恶意内容,攻击者就有可能以该用户的名义执行某些需要特权的操作, 或者获得该用户个人所有的敏感数据的访问权限。
- 应用程序之外的数据源将危险数据储存在一个数据库或其他数据存储器中,随后这些危险数据被当作可信赖的数据回写到应用程序中,并储存在动态内容中。
- 解决办法
使用下面两个工具类都可以解决该问题
public static String org.apache.commons.lang3.StringEscapeUtils.escapeHtml4(String input)
public static String cn.hutool.http.HtmlUtil.escape(String text)
看个人喜欢用哪个工具库。
maven依赖分别是:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.1</version>
</dependency>
但是对参数逐个进行预处理,效率太低,不太现实,这里可以使用spring过滤器对请求参数,进行预处理,避免xss漏洞。
XssFilter
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.extern.slf4j.Slf4j;
/**
* <p>
* xss拦截器
* </p>
*
* @author 雨中星辰
* @since 2020-07-10
*/
@Slf4j
//拦截所有的请求
@WebFilter(filterName = "xssFilter", urlPatterns = "/*", asyncSupported = true)
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
XssHttpServletRequestWrapper xssHttpServletRequestWrapper = new XssHttpServletRequestWrapper(request);
filterChain.doFilter(xssHttpServletRequestWrapper, servletResponse);
}
/**
* 过滤json类型的对象解析器
*
* @param builder builder
* @return ObjectMapper
*/
@Bean
@Primary
public ObjectMapper xssObjectMapper(Jackson2ObjectMapperBuilder builder) {
// 解析器
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
// 注册xss解析器
SimpleModule xssModule = new SimpleModule("XssStringJsonSerializer");
xssModule.addSerializer(new XssJacksonSerializer());
xssModule.addDeserializer(String.class, new XssJacksonDeserializer());
objectMapper.registerModule(xssModule);
// 返回
return objectMapper;
}
}
XssHttpServletRequestWrapper
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.lang3.ArrayUtils;
import cn.hutool.http.HtmlUtil;
/**
* <p>
* xss请求包装类
* </p>
*
* @author 雨中星辰
* @since 2020-07-10
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getQueryString() {
return HtmlUtil.escape(super.getQueryString());
}
@Override
public String getParameter(String name) {
return HtmlUtil.escape(super.getParameter(name));
}
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (ArrayUtils.isEmpty(values)) {
return values;
}
int length = values.length;
String[] escapeValues = new String[length];
for (int i = 0; i < length; i++) {
escapeValues[i] = HtmlUtil.escape(values[i]);
}
return escapeValues;
}
}
import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import cn.hutool.http.HtmlUtil;
/**
* <p>
* xss反序列化器
* </p>
*
* @author 雨中星辰
* @since 2020-07-10
*/
public class XssJacksonDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
return HtmlUtil.escape(jsonParser.getText());
}
}
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import cn.hutool.http.HtmlUtil;
import lombok.extern.slf4j.Slf4j;
/**
* <p>
* xss序列化器
* </p>
*
* @author 雨中星辰
* @since 2020-07-10
*/
@Slf4j
public class XssJacksonSerializer extends JsonSerializer<String> {
@Override
public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException {
jsonGenerator.writeString(HtmlUtil.escape(s));
}
}
Path Manipulation (路径操作)
- 概述
允许用户输入控制文件系统操作所用的路径会导致攻击者能够访问或修改其他受保护的系统资源。 - 详情
当满足以下两个条件时,就会产生 path manipulation 错误:
- 攻击者可以指定某一文件系统操作中所使用的路径。
- 攻击者可以通过指定特定资源来获取某种权限,而这种权限在一般情况下是不可能获得的。
例如:在某一程序中,攻击者可以获得特定的权限,以重写指定的文件或是在其控制的配置环境下运行程序。
例 1: 下面的代码使用来自于 HTTP 请求的输入来创建一个文件名。程序员没有考虑到攻击者可能使用像
../../tomcat/conf/server.xml”一样的文件名,从而导致应用程序删除它自己的配置文件。
String rName = request.getParameter("reportName");
File rFile = new File("/usr/local/apfr/reports/" + rName);
...
rFile.delete();
示例 2: 以下代码使用来自于配置文件的输入来决定打开哪个文件,并返回给用户。如果程序以足够的权限
运行,且恶意用户能够篡改配置文件,那么他们可以通过程序读取系统中以扩展名 .txt 结尾的任何文件。
fis = new FileInputStream(cfg.getProperty("sub")+".txt");
amt = fis.read(arr);
out.println(arr);
有些人认为在移动世界中,典型的漏洞(如 path manipulation)是无意义的 -- 为什么用户要攻击自己?但是, 谨记移动平台的本质是从各种来源下载并在相同设备上运行的应用程序。恶意软件在银行应用程序附近运行的可能性很高,它们会强制扩展移动应用程序的攻击面(包括跨进程通信)。
例 3: 以下代码将例 1 改编为适用于 Android 平台。
...
String rName = this.getIntent().getExtras().getString("reportName");
File rFile = getBaseContext().getFileStreamPath(rName);
...
rFile.delete();
...
解决办法
对文件名称及路径进行预处理,避免上述问题,由于项目中文件相关参数并不多,这里就不适用拦截器的方式做了,采取直接对参数预处理的方式。
添加依赖
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
使用方法public static String org.apache.commons.io.FilenameUtils.normalize(String filename)
入参为文件名称
或文件路径
例: