基于Appscan扫描漏洞修复方案
安全漏洞问题清单
序号 | 问题类型 | 问题原因 | 修复建议 | 数量 |
---|---|---|---|---|
1 | JWT中的弱签名 | 应用程序服务器对JWT头具有"none"算法的请求没有适当的验证。 | 有必要在应用程序端保留一份授权算法的白名单,并取消签名算法不同于服务器上授权的签名算法的所有令牌 | 65 |
2 | Unix文件参数变更 | 未对用户输入正确执行危险字符清理 未检查用户输入中是否包含“…”(两个点)字符串 | 确保所访问的文件驻留在虚拟路径中并具有特定扩展名;除去用户输入中的特殊字符 | 31 |
3 | SQL注入 | 未对用户输入正确执行危险字符清理。 动态生成包含未经验证的用户输入的查询可能会导致 SQL 注入攻击。攻击者可以在用户输入中插入 SQL 命令或修饰符,从而导 致以不安全的方式运行查询。 如果没有充分地验证并封装用户可控制的输入,则生成的 SQL 查询可能会导致这些输入被解释为 SQL 而不是普通用户数据。这 种做法可用于修改查询逻辑以绕过安全检查,或插入其他可用于修改后端数据库的语句(可能包括执行系统命令)。 SQL 有效负载可以通过任何不受信任的数据进入系统,这些数据包括用户输入、先前存储在数据库中的数据、文件、第三方 API 等。 | 查看危险字符串注入的可能解决方案 | 2 |
4 | 检测到应用程序测试脚本 | 在生产环境中留下临时文件 | 去除服务器中的测试脚本 | 4 |
4 | 过度许可的CORS访问测试 | Web 应用程序编程或配置不安全 | 修改"Access-Control-Allow-Origin”头以仅获取允许的站点 | 30 |
5 | 发现电子邮件地址模式 | Web 应用程序编程或配置不安全 | 除去Web站点中的电子邮件地址 | 7 |
6 | 发现内部IP泄露模式 | Web 应用程序编程或配置不安全 | 除去Web站点中的内部IP地址 | 6 |
7 | 具有不安全、不正确或缺少SameSite属性的Cookie | 具有不正确、不安全或缺少SameSite 属性的敏感Cookie | 查看将SameSiteCookie属性配置为推荐值的可能解决方案 | 4 |
8 | 发现可能的服务器路径泄露模式 | 未安装第三方产品的最新补丁或最新修补程序 | 为Web服务器或Web应用程序下载相关的安全补丁 | 3 |
9 | Content-Security-Policy”头缺失 | Web 应用程序编程或配置不安全 | 将服务器配置为使用安全策略的“Content-Security-Policy"头 | 1 |
10 | X-Content-Type-Options头缺失或不安全 | Web 应用程序编程或配置不安全 | 将服务器配置为使用值为"nosniff”'的“X-Content-Type-Options"头 | 1 |
11 | X-XSS-Protection”头缺失或不安全 | Web 应用程序编程或配置不安全 | 将服务器配置为使用值为“1”(已启用)的“X-XSS-Protection"头 | 1 |
12 | 在应用程序中发现不必要的Http响应头 | Web 应用程序编程或配置不安全 | 请勿允许敏感信息泄漏。 | 1 |
13 | Referral PolicySecurity头缺失 | 不安全的 Web 应用程序编程或配置 | 将服务器配置为使用安全策略的"Referrer Policy"头 | 1 |
14 | 客户端(JavaScript)Cookie引用 | Cookie是在客户端创建的 | 除去客户端中的业务逻辑和安全逻辑 | 1 |
JWT中的弱签名
基本介绍
JWT
是全称是JSON WEB TOKEN
,是一个开放标准,用于将各方数据信息作为JSON格式进行对象传递,可以对数据进行可选的数字加密,可使用RSA
或ECDSA
进行公钥/私钥签名
JWT
为三个部分组成,分别是Header
,Payload
,Signature
,使用.
符号分隔
Header
:标头是一个JSON
对象,由两个部分组成,分别是令牌是类型(JWT
)和签名算法(SHA256
,RSA
){ "alg": "HS256", "typ": "JWT" }
Payload
:负荷部分也是一个JSON
对象,用于存放需要传递的数据,例如用户的信息{ "username": "_island", "age": 18 }
Signature
:签名,这一部分,是由前面两个部分的签名,防止数据被篡改。 在服务器中指定一个密钥,使用标头中指定的签名算法,按照下面的公式生成这签名数据HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
在拿到签名数据之后,把这三个部分的数据拼接起来,每个部分中间使用
.
来分隔。这样子我们就生成出一个了JWT
数据了,接下来返回给客户端储存起来。而且客户端在发起请求时,携带这个JWT
在请求头中的Authorization
字段,服务器通过解密的方式即可识别出对应的用户信息。
问题原因
弱签名算法(无算法)
更改头部中声明的加密算法
例如将下面的HS256更改为none,可以只篡改前两部分,而不改变第三部分签名的情况下进行绕过,或者删除第三部分,但是记得保留“.” 这个符号,以保证符合JWT的格式
{
"alg": "none",
"typ": "JWT"
}
存在这种漏洞一般是第三方JWT库存在问题,或者是代码编写错误,生成JWT的代码中使用了none,如下所示
风险隐患
可能会升级用户特权并通过 Web 应用程序获取管理许可权 可能会绕开 Web 应用程序的认证机制
修订建议
有必要在应用程序端保留一份授权算法的白名单,并取消签名算法不同于服务器上授权的签名算法的所有令牌,使用一种算法
### 解决办法
1. 指定生成表JWT的唯一算法
2. 对JWT的签名进行校验
- 签名不能为空
- 签名不为空,但是算法不能为空或者不能为NONE
- 签名不为空,算法不为空或者NONE,校验签名和算法是否匹配
Unix文件参数变更
基本介绍
Unix文件参数:操作系统文件的路径参数
问题原因
未对用户输入正确执行危险字符清理 未检查用户输入中是否包含“…”(两个点)字符串
风险隐患
可能会查看 Web 服务器(在 Web 服务器用户的许可权限制下)上的任何文件(例如,数据库、用户信息或配置文件)的内容
修订建议
确保所访问的文件驻留在虚拟路径中并具有特定扩展名;除去用户输入中的特殊字符
解决办法
- 前台打包带有.的方法
前台增加路径中包含.的路径的拦截,一旦发现,页面返回404
- 后台接口路径包含…非法字符
示例:GET /prod-api/report/report/list?reportType=d&state=…/webapps/prod-api/report/report/
-
增加通用请求处理类
CustomRequestWrapper
package com.btdl.common.filter;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Map;
public class CustomRequestWrapper extends HttpServletRequestWrapper {
private final String body;
public CustomRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
StringBuilder sb = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
char[] charBuffer = new char[512];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
sb.append(charBuffer, 0, bytesRead);
}
}
} catch (IOException e) {
e.printStackTrace();
throw e;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
throw e;
}
}
}
body = sb.toString();
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes("UTF-8"));
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() {
return bais.read();
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream(), StandardCharsets.UTF_8));
}
public String getBody() {
return this.body;
}
@Override
public String getParameter(String name) {
return super.getParameter(name);
}
@Override
public Map<String, String[]> getParameterMap() {
return super.getParameterMap();
}
@Override
public Enumeration<String> getParameterNames() {
return super.getParameterNames();
}
@Override
public String[] getParameterValues(String name) {
return super.getParameterValues(name);
}
}
-
增加特殊字符过滤类
SpecialCharFilter
用于拦截post、get以及其他请求中参数包含…/的请求
package com.btdl.common.filter;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.btdl.common.utils.verify.UtilValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SpecialCharFilter implements Filter {
private static Logger log = LoggerFactory.getLogger(SpecialCharFilter.class);
private static final String REG_EXP = "\\.\\./";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
CustomRequestWrapper requestWrapper = new CustomRequestWrapper(request);
Map<String, Object> parameterMap = new HashMap<>();
parameterMap = getParameterMap(parameterMap, request, requestWrapper);
// 正则校验是否有路径符号关键字
if (ObjectUtil.isEmpty(parameterMap)) {
return;
}
for (Object obj : parameterMap.entrySet()) {
Map.Entry entry = (Map.Entry) obj;
Object value = entry.getValue();
if (value != null) {
boolean isValid = isSpecialChar(value.toString(), servletResponse);
if (!isValid) {
return;
}
}
}
filterChain.doFilter(requestWrapper, servletResponse);
}
private Map<String, Object> getParameterMap(Map<String, Object> paramMap, HttpServletRequest request, CustomRequestWrapper requestWrapper) {
// 1.POST请求获取参数
if ("POST".equalsIgnoreCase(request.getMethod())) {
String body = requestWrapper.getBody();
if (!UtilValidator.isEmpty(body) && body.startsWith("[")){
paramMap.put("List", JSONObject.parseObject(body, ArrayList.class));
} else {
paramMap = JSONObject.parseObject(body, HashMap.class);
}
if (paramMap == null) {
paramMap = new HashMap<>();
Map<String, String[]> parameterMap = requestWrapper.getParameterMap();
//普通的GET请求
if (parameterMap != null && parameterMap.size() > 0) {
Set<Map.Entry<String, String[]>> entries = parameterMap.entrySet();
for (Map.Entry<String, String[]> next : entries) {
paramMap.put(next.getKey(), next.getValue()[0]);
}
} else {
//GET请求,参数在URL路径型式,比如server/{var1}/{var2}
String afterDecodeUrl = null;
try {
//编码过URL需解码解码还原字符
afterDecodeUrl = URLDecoder.decode(request.getRequestURI(), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
paramMap.put("pathVar", afterDecodeUrl);
}
}
} else {
Map<String, String[]> parameterMap = requestWrapper.getParameterMap();
//普通的GET请求
if (parameterMap != null && parameterMap.size() > 0) {
Set<Map.Entry<String, String[]>> entries = parameterMap.entrySet();
for (Map.Entry<String, String[]> next : entries) {
paramMap.put(next.getKey(), next.getValue()[0]);
}
} else {
//GET请求,参数在URL路径型式,比如server/{var1}/{var2}
String afterDecodeUrl = null;
try {
//编码过URL需解码解码还原字符
afterDecodeUrl = URLDecoder.decode(request.getRequestURI(), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
paramMap.put("pathVar", afterDecodeUrl);
}
}
return paramMap;
}
private boolean isSpecialChar(String value, ServletResponse servletResponse) throws IOException {
Pattern pattern = Pattern.compile(REG_EXP);
if (value == null) {
return true;
}
Matcher matcher = pattern.matcher(value);
if (matcher.find()) {
log.info("入参中有非法字符: " + value);
HttpServletResponse response = (HttpServletResponse) servletResponse;
Map<String, Object> responseMap = new HashMap<>();
// 匹配到非法字符,立即返回
responseMap.put("code", 500);
responseMap.put("msg", "入参中有非法字符");
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpStatus.OK.value());
response.getWriter().write(JSON.toJSONString(responseMap));
response.getWriter().flush();
response.getWriter().close();
return false;
}
return true;
}
}
-
在全局过滤配置类中增加特殊字符过滤bean
FilterConfig
SQL注入
基本介绍
基本含义:SQL 注入即是指 web 应用程序对用户输入数据的合法性没有判断或过滤不严,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息
SQL注入的原理:
- 恶意拼接查询
SQL语句可以对数据进行增删改查,且使用分号来分隔不同命令。例如:
SELECT * FROM user WHERE user_id = $user_id
其中user_id是传入的参数,如果传入参数的值为"1234;DELETE FROM user",那么最终执行的查询为:
SELECT * FROM users WHERE user_id = 1234; DELETE FROM users
如果执行以上语句,则会删除user表中的所有数据
- 利用注释执行非法命令
SQL语句中可以插入注释,例如SELECT COUNT(*) AS 'num' FROM score WHERE id=24411 AND version=$version
如果version包含了恶意的字符串" ‘-1’ OR 3 AND SLEEP(500) ",那么最终查询的语句会变为
SELECT COUNT(*) AS 'num' FROM score WHERE id=24411 AND version='-1' OR 3 AND SLEEP(500)
以上恶意查询只是想耗尽系统资源,SLEEP(500) 将导致 SQL 语句一直运行。如果其中添加了修改、删除数据的恶意指令,那么将会造成更大的破坏。
- 传入非法参数
SQL 语句中传入的字符串参数是用单引号引起来的,如果字符串本身包含单引号而没有被处理,那么可能会篡改原本 SQL 语句的作用。 例如:SELECT * FROM user_name WHERE user_name = $user_name
如果 user_name 传入参数值为 G’chen,那么最终的查询语句会变为:
SELECT * FROM user_name WHERE user_name ='G'chen'
一般情况下,以上语句会执行出错,这样的语句风险比较小。虽然没有语法错误,但可能会恶意产生 SQL 语句,并且以一种你不期望的方式运行。
- 添加额外条件
在 SQL 语句中添加一些额外条件,以此来改变执行行为。条件一般为真值表达式。例如:UPDATE users SET userpass='$userpass' WHERE user_id=$user_id;
如果 user_id 被传入恶意的字符串“1234 OR TRUE”,那么最终的 SQL 语句会变为:
UPDATE users SET userpass= '123456' WHERE user_id=1234 OR TRUE;
如果执行以上语句,将更改所有用户的密码
问题原因
未对用户输入正确执行危险字符清理。
风险隐患
动态生成包含未经验证的用户输入的查询可能会导致 SQL 注入攻击。攻击者可以在用户输入中插入 SQL 命令或修饰符,从而导 致以不安全的方式运行查询。 如果没有充分地验证并封装用户可控制的输入,则生成的 SQL 查询可能会导致这些输入被解释为 SQL 而不是普通用户数据。这 种做法可用于修改查询逻辑以绕过安全检查,或插入其他可用于修改后端数据库的语句(可能包括执行系统命令)。 SQL 有效负载可以通过任何不受信任的数据进入系统,这些数据包括用户输入、先前存储在数据库中的数据、文件、第三方 API 等。
修订建议
查看危险字符串注入的可能解决方案
解决办法
- 避免sql中使用${}的写法,建议采用#{}
两者的区别:
**#{}: ** 是以预编译的形式,将参数设置到sql语句中(把#{ }中间的参数转义成字符串,预编译后,动态解析成参数标记符);可以防止sql注入。
**KaTeX parse error: Expected 'EOF', got '#' at position 56: …下,我们取参数的值都应该去使用#̲{ },原生jdbc不支持占位…{ }进行取值。
- 对前台传参进行sql关键词过滤
/**
* 关键词校验
* @param strList
* @return
*/
public boolean sqlValidate(List<String> strList) {
if(com.btdl.common.utils.verify.UtilValidator.isEmpty(strList)){
return false;
}
for (String str : strList) {
// 统一转为小写
str = str.toLowerCase();
// 过滤掉的sql关键字,可以手动添加
String badStr = "'|and|exec|execute|insert|select|delete|update|count|drop|*|%|chr|mid|master|truncate|" +
"char|declare|sitename|net user|xp_cmdshell|;|or |-|+|,|like'|and|exec|execute|insert|create|drop|" +
"table|from|grant|use|group_concat|column_name|" +
"information_schema.columns|table_schema|union|where|select|delete|update|order|by|count|*|" +
"chr|mid|master|truncate|char|declare|or |;|-|--|+|,|like|//|/|%|#";
String[] badStrs = badStr.split("\\|");
for (int i = 0; i < badStrs.length; i++) {
if (str.indexOf(badStrs[i]) >= 0) {
LogUtil.info(badStrs[i]);
return true;
}
}
}
return false;
}
- 避免直接向用户显示数据库错误
攻击者可以使用这些错误消息来获取有关的数据库信息。
其他方法:
避免使用动态SQL
避免将用户的输入数据直接放在SQL语句中,最好使用准备好的语句和参数化查询,这样更安全。
不要将敏感数据保留在纯文本中
加密存储在数据库中的私有/机密数据,这样可以提供了另一级保护,以防攻击者成功的排出敏感数据
限制数据库的权限和特权
将数据库用户的功能设置为最低要求;这将限制攻击者在设法获取访问权限时可以执行的操作
检测到应用程序测试脚本
基本介绍
问题原因
在生产环境中留下临时文件
风险隐患
可以下载临时脚本文件,它可以公开应用程序逻辑和其他感兴趣信息,如用户名和密码。
修订建议
去除服务器中的测试脚本
解决办法
-
删除存在的测试脚本
-
防止有人恶意尝试tes请求,可以对参数中已test结尾的请求进行拦截
通过nginx配置接口拦截,,一经发现包含test结尾的请求,页面返回404
过度许可的CORS访问测试
基本介绍
Http协议访问资源需要符合同源策略,同源策略[same origin policy]是浏览器的一个安全功能, 同源策略是浏览器安全的基石。同源就是必须访问的域名与端口号完全相同。例如下面例子
http://test.com与http?/test.com:81不同源
http://test.com与http?/a.test.com不同源
http://test.com/a.html与http://test.com/b.html同源互联网应用系统需要高可用和高并发,架构设计需要将也分解到不同的域名,再通过Nginx代理统一起来,这样不可避免地处理不同源间系统访问。为了解决这个问题,提出了跨源资源共享,即 CORS(Cross-Origin Resource Sharing)
CORS实现原理
传统的Http协议头信息:
Http协议头信息 说明 示例 Accept 指定客户端能够接受的MIME类型列表(响应内容类型)。其格式通常为一个由逗号分隔的MIME类型列表,可以指定多个类型,并按优先级排序。 application/json, text/plain, / Accept-Encoding 指示客户端可以接受的压缩算法 gzip, deflate:,例如gzip和deflate Accept-Language 指示客户端可以接受的语言类型,例如中文(中国大陆)、中文、英语等,其中q参数表示优先级 zh-CN,zh;q=0.9,en;q=0.8 Authorization 用于携带用户的身份认证信息,以便服务器对用户进行身份验证 Connection 指定客户端和服务器之间连接的选项。它包含一个逗号分隔的选项列表,每个选项代表一种连接的属性或选项。 close:表示客户端和服务器之间的连接在响应结束后会关闭,需要重新建立连接才能发送新的请求。
keep-alive:表示客户端和服务器之间的连接会保持持久化,可以在同一连接上发送多个请求和响应。这样可以减少每次请求时建立和断开连接的开销,提高网络传输的效率。
Upgrade:表示客户端请求升级到一个不同的协议,例如从HTTP/1.1升级到WebSocket协议。Cookie Cookie是一种在客户端(通常是Web浏览器)和服务器之间传递信息的机制。在HTTP协议中,Cookie是通过HTTP头部字段Set-Cookie和Cookie来进行传输的。 Host 指示请求的目标主机的主机名或IP地址。在HTTP/1.1协议中,所有的HTTP请求都必须包含该字段。 192.168.1.41:8000 Referer 指示请求的来源页面或URL。当用户通过点击链接或提交表单等方式访问一个页面时,浏览器会在HTTP请求头部中包含Referer字段,该字段的值为访问前一个页面的URL。 http://192.168.1.41:8000/ Content-Type 用于指示HTTP消息主体(body)的媒体类型。它告诉接收方如何解析HTTP消息主体的数据。 text/html:表示HTML文档; application/json:表示JSON数据; image/png:表示PNG格式的图片; audio/mpeg:表示MP3格式的音频。 User-Agent 用于标识发出请求的客户端类型、操作系统、软件应用程序等信息。 应用程序名称和版本号 操作系统名称和版本号 浏览器内核类型和版本号 浏览器类型和版本号 硬件类型 CORS扩展头信息:
CORS头信息 说明 Origin: 指示获取资源的请求是从什么域发起的 Access-Control-Allow-Origin: 指示请求的资源能共享给哪些域
可以是具体的域名或者*表示所有域。Access-Control-Allow-Methods: 指定对预请求的响应中,
哪些 HTTP 方法允许访问请求的资源。Access-Control-Allow-Headers: 用在对预请求的响应中,
指示实际的请求中可以使用哪些 HTTP 头。Access-Control-Allow-Credentials: 指示当请求的凭证标记为 true 时
是否响应该请求。Access-Control-Max-Age: 指示预请求的结果能被缓存多久。 CORS跨域访问有两种访问方式,一是直接访问,在HTTP请求头上添加Origin字段(记录本域名),如果访问的服务器允许此域名CORS访问,在响应头上添加如下字段
Access-Control-Allow-Origin: http://mydomain1.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
如果不允许此域名CORS访问,不需要添加上述响应头信息。二是间接范围,首先发生一个OPTION请求询问服务器端是否运行本域名跨域访问,如果允许访问返回响应头信息
Access-Control-Allow-Origin: http://mydomain1.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
为全部Controller设置跨域支持
package com.gf; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE") .maxAge(3600) .allowCredentials(true); } }
问题原因
Web 应用程序编程或配置不安全 ,Access-Control-Allow-Origin”头的许可权太多
当CORS的设置不正确时,就会带来安全问题;当响应头中的Access-Control-Allow-Origin设置为null或*时,表示信任任何域,这时候就可能引入安全问题。
风险隐患
可能会收集有关 Web 应用程序的敏感信息,如用户名、密码、机器名和/或敏感文件位置 可能会劝说初级用户提供诸如用户名、密码、信用卡号、社会保险号等敏感信息
修订建议
修改"Access-Control-Allow-Origin”头以仅获取允许的站点
解决办法
-
修复方法是合理配置CORS,判断Origin是否合法;具体说就是不让在nginx或tomcat中配置【Access-Control-Allow-Origin *】或【Access-Control-Allow-Origin null】
nginx配置方式
- 指定ip与端口,也可以逗号拼接;
location / {
add_header Access-Control-Allow-Origin http://10.130.222.222:6500,http://10.130.222.223:6500;
add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept";
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
}
- 使用正则表达式
location ~ /myurl(.*) {
if ( $http_origin ~ ^http(s)?://(localhost|10.130.222.222):6500$ ){
add_header Access-Control-Allow-Origin $http_origin;
}
if ( $http_origin ~ ^http(s)?://(localhost|10.130.222.223):6500$ ){
add_header Access-Control-Allow-Origin $http_origin;
}
add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept";
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
}
说明:
● $ http_origin可以获取到请求头中的Origin字段;但是如果请求头没有,就获取不到了;
●^是正则表达式,表示开头位置;$是正则表达式,表示结尾位置
●?是正则表达式,表示s可能有,也可能没有,这两种情况都可以匹配
●.是把.转义成普通字符的意思 ●nginx中,if后必须加空格,然后才能写(,否则会报错
●nginx中,没有else if
发现电子邮件地址模式
基本介绍
问题原因
Web 应用程序编程或配置不安全
风险隐患
可能会收集有关 Web 应用程序的敏感信息,如用户名、密码、机器名和/或敏感文件位置
修订建议
除去Web站点中的电子邮件地址
解决办法
后台响应增加隐私信息的加密处理:手机号、密码、登录ip以及邮箱
package com.btdl.common.utils.privacy;
import cn.hutool.core.util.ObjectUtil;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @SystemName esa
* @ModuleName com.btdl.common.utils.privacy
* @ClassName PrivacyUtil
* @Author Zhangml
* @Date 2023/6/27 18:56
* @Version v1.0
* @Description
**/
public class PrivacyUtil {
/*** 隐藏手机号中间四位*/
public static String hidePhone(String phone) throws Exception {
if (ObjectUtil.isEmpty(phone)){
return phone;
}
if (phone.length() > 11) {
return phone;
} else {
return AesUtils.encryptRSA(phone, null);
}
}
/*** 隐藏邮箱*/
public static String hideEmail(String email) throws Exception {
if (ObjectUtil.isEmpty(email)){
return email;
}
if (email.length() > 50) {
return email;
} else {
return AesUtils.encryptRSA(email,null);
}
}
/*** 隐藏身份证*/
public static String hideIDCard(String idCard) {
return idCard.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1*****$2");
}
/*** IP (25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))*/
public static String hideIP(String IP) {
if (ObjectUtil.isEmpty(IP)){
return IP;
}
return IP.replaceAll("((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)", "*");
}
public static void main(String[] args) throws Exception {
String ip = hideIP("http://192.168.1.41:9002/ptte/yfuevrfy/http:000000");
String phone = hidePhone("12345677777");
System.out.println(phone);
}
}
package com.btdl.common.utils.privacy;
import cn.hutool.core.util.ObjectUtil;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class AesUtils {
//可配置到Constant中,并读取配置文件注入,16位,自己定义
private static final String KEY = "abcdefghijklmnop";
//参数分别代表 算法名称/加密模式/数据填充方式
private static final String ALGORITHM = "AES/ECB/PKCS5Padding";
//私钥解密
private static final String privateKeyRSA = "MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAqWRoBuGMHr+oqz4l\n" +
"6DbgXelJfxy04dpYmc1Ym1qKnNhjYrdbxsJjv7gWNzzNdeHH0Vpj+wt7arSd2QRu\n" +
"CCpNPQIDAQABAkAGtwpAq5RIa8Sd9Tq/d91xG5xkQ5kQEv3MKw3GKtKv4PuYgEg2\n" +
"paUGpI9QG8z9Ls2U/uDvDyDN0PfvZWAKENGBAiEA2g0JoCjqMDGvWW/wIgVtt5ba\n" +
"nMGBgoMRHEVvMquJirECIQDG33IlWqfzR7gjY3hP5lMTmT8lCdc9Mlo4CB89Yz52\n" +
"TQIgIbbEVzwcQYldGFIDae828Jzlfjk5IgnL2ngt4kK9iHECIDie953BAN5YBgo1\n" +
"UNTWy8JhuEOwWJK42kznqxVMQ0VlAiA018Q6/6PezXEuh0iGUGj1ODWSlWB+FM1h\n" +
"CfGOeItcJw==";
private static final String publicKeyRSA = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKlkaAbhjB6/qKs+Jeg24F3pSX8ctOHa\n" +
"WJnNWJtaipzYY2K3W8bCY7+4Fjc8zXXhx9FaY/sLe2q0ndkEbggqTT0CAwEAAQ==";
/* 加密
* @param content 加密的字符串
* */
public static String encrypt(String content) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(KEY.getBytes(), "AES"));
byte[] b = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
// 采用base64算法进行转码,避免出现中文乱码
return Base64.encodeBase64String(b);
}
/* 解密
* @param encryptStr 解密的字符串
* */
public static String decrypt(String encryptStr) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(KEY.getBytes(), "AES"));
// 采用base64算法进行转码,避免出现中文乱码
byte[] encryptBytes = Base64.decodeBase64(encryptStr);
byte[] decryptBytes = cipher.doFinal(encryptBytes);
return new String(decryptBytes);
}
/**
* RSA公钥加密
*
* @param str 加密字符串
* @param publicKey 公钥
*/
public static String encryptRSA(String str, String publicKey) throws Exception {
if(ObjectUtil.isEmpty(publicKey)){
publicKey = publicKeyRSA;
}
//base64编码的公钥
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
return Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
}
/**
* RSA私钥解密
*
* @param str 加密字符串
*/
public static String decryptRSA(String str) throws Exception {
//64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(str);
//base64编码的私钥
byte[] decoded = Base64.decodeBase64(privateKeyRSA);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
return new String(cipher.doFinal(inputByte));
}
public static void main(String[] args) throws Exception {
String s = encryptRSA("123456",publicKeyRSA);
System.out.println(s);
System.out.println(decryptRSA(s));
}
}
发现内部IP泄露模式
基本介绍
问题原因
Web 应用程序编程或配置不安全
风险隐患
可能会收集有关 Web 应用程序的敏感信息,如用户名、密码、机器名和/或敏感文件位置
修订建议
除去Web站点中的内部IP地址
解决办法
同《发现电子邮件地址模式》
具有不安全、不正确或缺少SameSite属性的Cookie
基本介绍
SameSite 属性
Chrome 51 开始,浏览器的 Cookie 新增加了一个
SameSite
属性,用来防止 CSRF 攻击 和用户追踪(第三方恶意获取cookie),限制第三方 Cookie,从而减少安全风险。(典型案例:在A页面访问B页面,b页面获取了A页面请求的cookie)SameSite属性可以设置三个值:Strict、Lax、None。
Strict
:严格,完全禁止第三方获取cookie,跨站点时,任何情况下都不会发送cookie;只有当前网页的 URL 与请求目标一致,才会带上 Cookie。这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。Set-Cookie: CookieName=CookieValue; SameSite=Strict;
Lax
:防范跨站,大多数情况下禁止获取cookie,除非导航到目标网址的GET请求(链接、预加载、GET表单);设置了Strict或Lax以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。SameSite属性的默认SameSite=Lax 【该操作适用于2019年2月4号谷歌发布Chrome 80稳定版之后的版本】
Set-Cookie: CookieName=CookieValue; SameSite=Lax;
None
:没有限制必须同时设置
Secure
属性(Cookie 只能通过 HTTPS 协议发送),否则无效。 【该操作适用于2019年2月4号谷歌发布Chrome 80稳定版之后的版本】Set-Cookie: widget_session=abc123; SameSite=None; Secure
【注1】CSRF 攻击是什么?
Cookie 往往用来存储用户的身份信息,恶意网站可以设法伪造带有正确 Cookie 的 HTTP 请求,这就是 CSRF 攻击。
一个典型的CSRF攻击有着如下的流程:
*受害者登录a.com,并保留了登录凭证(Cookie)。 *攻击者引诱受害者访问了b.com。 *b.com 向 a.com 发送了一个请求:a.com/act=xx。浏览器会默认携带a.com的Cookie。 *a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。 *a.com以受害者的名义执行了act=xx。 *攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作。
举例来说,用户登陆了银行网站your-bank.com,银行服务器发来了一个 Cookie。
Set-Cookie:id=a3fWa;
用户后来又访问了恶意网站malicious.com,上面有一个表单。
<form action="your-bank.com/transfer" method="POST"> ... </form>
用户一旦被诱骗发送这个表单,银行网站就会收到带有正确 Cookie 的请求。为了防止这种攻击,表单一般都带有一个随机 token,告诉服务器这是真实请求。
<form action="your-bank.com/transfer" method="POST"> <input type="hidden" name="token" value="dad3weg34"> ... </form>
这种第三方网站引导发出的 Cookie,就称为第三方 Cookie。它除了用于 CSRF 攻击,还可以用于用户追踪。
比如,Facebook 在第三方网站插入一张看不见的图片。
<img src="facebook.com" style="visibility:hidden;">
浏览器加载上面代码时,就会向 Facebook 发出带有 Cookie 的请求,从而 Facebook 就会知道你是谁,访问了什么网站。
【注2】2019年2月4号谷歌发布Chrome 80稳定版后,关于cookie的SameSite属性做了以下更改:
(1) 没有设置SameSite属性的默认SameSite=Lax。
(2)SameSite=None的cookie则必须设置为Secure,即安全链接(https)。
上面说的两个更改对于跨域请求会有很大的影响。
一些老的项目中跨域获取cookie的情况(如基于cookie的统一登录)。因为旧项目中不会设置SameSite属性,所以此次更新后,SameSite属性会默认为Lax,这就导致跨域的请求获取不到cookie数据。从而导致统一登录失败。
解决方案:
首先要将SameSite属性设置为None,其次要使用https安全链接。
问题原因
具有不安全、不正确或缺少SameSite属性的Cookie
风险隐患
通过将 Cookie 限制为第一方或同一站点上下文来防止 Cookie 信息泄漏,如果没有额外的保护措施(如反 CSRF 令牌),攻击 可能会扩展为跨站点请求伪造 (CSRF) 攻击。
修订建议
查看将SameSiteCookie属性配置为推荐值的可能解决方案
解决办法
首先要将SameSite属性设置为None,其次要使用https安全链接。
发现可能的服务器路径泄露模式
基本介绍
问题原因
未安装第三方产品的最新补丁或最新修补程序
风险隐患
修订建议
为Web服务器或Web应用程序下载相关的安全补丁
解决办法
- 通过nginx配置接口拦截,,一经发现包含.的请求,页面返回404
Content-Security-Policy头缺失
基本介绍
Content Security Policy (CSP) 是一个额外的安全层,用于帮助检测和缓解某些类型的攻击,包括 Cross Site Scripting (XSS) 和数据注入攻击。这些攻击主要用于实现数据窃取、网站破坏或恶意软件分发。
问题原因
Web 应用程序编程或配置不安全
风险隐患
可能会收集有关 Web 应用程序的敏感信息,如用户名、密码、机器名和/或敏感文件位置 可能会劝说初级用户提供诸如用户名、密码、信用卡号、社会保险号等敏感信息
修订建议
将服务器配置为使用安全策略的“Content-Security-Policy"头
解决办法
通过nginx配置,增加头部安全策略配置
策略介绍:
script-src:外部脚本
style-src:样式文件
img-src:图片文件
media-src:媒体文件(音频和视频)
font-src:字体文件
object-src:插件(比如 Flash)
child-src:框架
frame-ancestors:嵌入的外部资源(比如<frame>、<iframe>、<embed>和<applet>)
connect-src:HTTP 连接(通过 XHR、WebSockets、EventSource等)
worker-src:worker脚本
manifest-src:manifest 文件
default-src:用来设置上面各个选项的默认值。
#外部脚本
add_header Content-Security-Policy "script-src 'self' 'unsafe-eval'";
#嵌入的外部资源(比如<frame>、<iframe>、<embed>和<applet>)
add_header Content-Security-Policy "frame-ancestors 'self'";
#插件(比如 Flash)
add_header Content-Security-Policy "object-src 'self'";
#样式文件
add_header Content-Security-Policy "style-src 'self' 'unsafe-inline'";
X-Content-Type-Options头缺失或不安全
基本介绍
是用来禁用浏览器内容嗅探行为。
问题原因
Web 应用程序编程或配置不安全
风险隐患
可能会收集有关 Web 应用程序的敏感信息,如用户名、密码、机器名和/或敏感文件位置 可能会劝说初级用户提供诸如用户名、密码、信用卡号、社会保险号等敏感信息
修订建议
将服务器配置为使用值为"nosniff”'的“X-Content-Type-Options"头
解决办法
通过nginx配置,增加头部安全策略配置
add_header X-Content-Type-Options nosniff;
策略介绍:
此标头可防止Internet Explorer从MIME中嗅探到已声明的内容types的响应,因为标题指示浏览器不要覆盖响应内容types。 使用nosniff选项,如果服务器说内容是text / html,那么浏览器将把它呈现为text / html。
X-XSS-Protection头缺失或不安全
基本介绍
XSS 攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。
HTTP
X-XSS-Protection
响应头是Internet Explorer,Chrome和Safari的一个功能,当检测到跨站脚本攻击 (XSS)时,浏览器将停止加载页面。虽然这些保护在现代浏览器中基本上是不必要的,当网站实施一个强大的Content-Security-Policy
来禁用内联的JavaScript ('unsafe-inline'
)时, 他们仍然可以为尚不支持 CSP 的旧版浏览器的用户提供保护
问题原因
Web 应用程序编程或配置不安全
风险隐患
可能会收集有关 Web 应用程序的敏感信息,如用户名、密码、机器名和/或敏感文件位置 可能会劝说初级用户提供诸如用户名、密码、信用卡号、社会保险号等敏感信息
修订建议
将服务器配置为使用值为“1”(已启用)的“X-XSS-Protection"头
解决办法
通过nginx配置,增加头部安全策略配置
add_header X-XSS-Protection "1; mode=block";
策略介绍:
启用XSS过滤。 如果检测到攻击,浏览器将不会清除页面,而是阻止页面加载。
在应用程序中发现不必要的Http响应头
基本介绍
问题原因
Web 应用程序编程或配置不安全
风险隐患
可能会收集有关 Web 应用程序的敏感信息,如用户名、密码、机器名和/或敏感文件位置
修订建议
请勿允许敏感信息泄漏
解决办法
通过nginx配置, 不显示nginx版本号
#不显示nginx版本号
server_tokens off;
Referral PolicySecurity头缺失
基本介绍
Referer
是 HTTP 请求头的一个字段,当浏览器(或者模拟浏览器行为)向服务器发送请求时,浏览器会自动在请求头中加上Referer
字段,表示的意思是链接的来源地址,比如在页面引入图片、JS 等资源,或者跳转链接,一般不修改策略,都会带上Referer。Referer 头可以出现不同类型的请求里:
- 导航请求,当用户点击一个链接时
- 资源请求,当浏览器请求网页所需的图片,iframe,脚本及其他资源文件时
但是跨域时如果 Referer 的值是完整的 URL,会包含路径和参数信息,这样可能会有隐私泄露和安全问题
![]()
Referrer Policy是W3C官方提出的一个候选策略,主要用来规范Referrer,简单来说就是规定什么时候发送
Referer
字段,以及发送哪些信息。目前有9种策略
no-referrer
整个 Referer 首部会被移除,Referer 不随着请求一起发送。
no-referrer-when-downgrade (默认值)
在没有指定任何策略的情况下用户代理的默认行为。在同等安全级别(HTTPS -> HTTPS)的情况下,Referer 会被发送,在协议降级(HTTPS -> HTTP)的情况下 Referer 不会被发送。
origin
Referrer 发送的信息只包括协议+域名+端口,不包括其它信息。例如 https://example.com/page.html 会将 https://example.com/ 作为引用地址。
origin-when-cross-origin
对于同源的请求,会发送完整的URL作为引用地址,但是跨域时候只发送协议+域名+端口。
same-origin
同源请求发送,否则不发送。
strict-origin
在同等安全级别(HTTPS -> HTTPS)的情况下,发送Referrer(协议+域名+端口),但是在协议降级(HTTPS -> HTTP)的情况下不会发送。
strict-origin-when-cross-origin
对于同源的请求,会发送完整的URL作为引用地址;对于跨域请求,在同等安全级别(HTTPS -> HTTPS)的情况下,发送Referrer(协议+域名+端口);在协议降级(HTTPS -> HTTP)的情况下不发送此首部。
unsafe-url
无论协议是否降级,也不管是同源请求还是跨域请求,都发送完整的 URL(移除参数信息之后)作为引用地址,所以这种是一种不安全的协议。这项设置会将受 TLS 安全协议保护的资源的源和路径信息泄露给非安全的源服务器。进行此项设置的时候要慎重考虑。—— Referrer-Policy,MDN文档
空字符串
相当于没有设置,在没有此类更高级别策略的情况下,默认使用 no-referrer-when-downgrade
问题原因
不安全的 Web 应用程序编程或配置
风险隐患
可能会收集有关 Web 应用程序的敏感信息,如用户名、密码、机器名和/或敏感文件位置 可能会劝说初级用户提供诸如用户名、密码、信用卡号、社会保险号等敏感信息
缺少 Referrer Policy 或 Referrer Policy 的值不正确会导致 URL 本身泄露,甚至包含在 URL 中的敏感信息也会泄露到跨站点。 这是规则集的一部分,用于检查 Referrer Policy 是否存在,如果存在则测试其配置。“Referer Policy”头定义了在 Referer 头中可用的数据,用 于目标位置 (document.referrer) 中的导航和 iframe。该头被设计用来修改浏览器呈现页面的方式,从而防止跨域 Referer 泄露。请务必要正确 设置该头值,以免妨碍网站的正常运行。 Referer 头是一个请求报头,它指示流量来自哪个站点。如果没有适当的预防措施,URL 本身,甚至包含在 URL 中的敏感信息都会泄露到跨站 点。 “no-referrer-when-downgrade”和“unsafe-url”是泄露第三方网站完整 URL 的策略。其余策略包括“no-referrer”、“origin”、“origin-when-crossorigin”、“same-origin”、“strict-origin”、“strict-origin-when-cross-origin”。
修订建议
将服务器配置为使用安全策略的"Referrer Policy"头
解决办法
设置 referrer 策略:最佳实践
有多种方式在网页中设置 referrer 策略:
-
设置为 HTTP 头
-
在 HTML 内设置
-
用 JavaScript 为每个请求单独设置(用 fetch() 方法发起请求时)
你可以为不同的页面,请求或者元素设置不同的策略。
HTTP头和 meta 标签都是页面级别的。决定一个元素的 referrer 策略时,优先级如下:
- 元素级别的策略
<a href="http://example.html" referrerpolicy="origin" target="_blank">链接</a>
<img src="/example.jpg" referrerpolicy="strict-origin-when-cross-origin" />
- 页面级别的策略
<meta name="referrer" content="strict-origin-when-cross-origin">
<script>
fetch(url, {referrerPolicy: "strict-origin-when-cross-origin"});
</script>
- 浏览器默认策略
客户端(JavaScript)Cookie引用
基本介绍
问题原因
Cookie是在客户端创建的
风险隐患
此攻击的最坏情形取决于在客户端所创建的 cookie 的上下文和角色 cookie 是一则信息,通常由 Web 服务器创建并存储在 Web 浏览器中。 web 应用程序主要(但不只是)使用 cookie 包含的信息来识别用户并维护用户的状态。 AppScan 检测到客户端上的 JavaScript 代码用于操控(创建或修改)站点的 cookie。 攻击者有可能查看此代码、了解其逻辑并根据所了解的知识将其用于组成其自己的 cookie,或修改现有 cookie。 攻击者可能导致的损坏取决于应用程序使用其 cookie 的方式或应用程序存储在这些 cookie 中的信息内容。 此外,cookie 操控还可能导致会话劫持或特权升级。 由 cookie 毒害导致的其他漏洞包含 SQL 注入和跨站点脚本编制。
修订建议
除去客户端中的业务逻辑和安全逻辑
解决办法
- nginx配置,过滤接口路径包含.的所有请求(此项操作可屏蔽appscan对js中cookie的获取)
- 后台关闭使用cookie
参考文献:
[1] SQL注入 https://blog.csdn.net/m0_51457307/article/details/121201312
[2]Cors跨域访问 https://blog.csdn.net/qixiang_chen/article/details/86692517
[3] Cookie的SameSite属性https://blog.csdn.net/qq_36690992/article/details/117965822
[4]Referer和Referrer Policy详解https://blog.csdn.net/weixin_43487782/article/details/114393863
[5]X-Content-Type-Options: nosniff 禁用浏览器类型猜测保证安全性 https://blog.csdn.net/taoshihan/article/details/127460099
[6]浅谈CSRF攻击方式,原理,防范 https://blog.csdn.net/chenpeng0708/article/details/105533901