Code Monkey home page Code Monkey logo

secguide's Introduction

代码安全指南

面向开发人员梳理的代码安全指南,旨在梳理API层面的风险点并提供详实可行的安全编码方案。

理念

基于DevSecOps理念,我们希望用开发者更易懂的方式阐述安全编码方案,引导从源头规避漏洞。

索引

规范 最后修订日期
C/C++安全指南 2021-05-18
JavaScript安全指南 2021-05-18
Node安全指南 2021-05-18
Go安全指南 2021-05-18
Java安全指南 2021-05-18
Python安全指南 2021-05-18

实践

代码安全指引可用于以下场景:

  • 开发人员日常参考
  • 编写安全系统扫描策略
  • 安全组件开发
  • 漏洞修复指引

贡献

盼与社区携手,一道维护完善。欢迎提交修订建议,详参阅贡献指南

授权许可

Secure Coding Guide by THL A29 Limited, a Tencent company, is licensed under CC BY 4.0.

secguide's People

Contributors

4blacktea avatar albertchamberlain avatar ardxwe avatar baijinping avatar baosj0 avatar code4happylife avatar martinzhou2015 avatar nongguangxin avatar sagexueqi avatar t4erg avatar xbalien avatar yanhuijessica avatar zheeeng avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

secguide's Issues

#java# 规范 正则表达式DOS 增补修订建议

正则表达式(Regex)经常遭受拒绝服务(DOS)攻击(称为ReDOS),根据特定的正则表达式定义,当分析某些字符串时,正则表达式引擎可能会花费大量时间甚至导致宕机。

脆弱代码:

符号 | 符号 [] 符号 + 三者联合使用可能受到 ReDOS 攻击:
表达式:  (\d+|[1A])+z
需求: 会匹配任意数字或任意(1或A)字符串加上字符z
匹配字符串: 111111111 (10 chars)
计算步骤数: 46342

如果两个重复运算符过近,那么有可能收到攻击。请看以下例子:
例子1:
表达式:  .*\d+\.jpg
需求: 会匹配任意字符加上数字加上.jpg
匹配字符串: 1111111111111111111111111 (25 chars)
计算步骤数: 9187

例子2:
表达式:  .*\d+.*a
需求: 会匹配任意字符串加上数字加上任意字符串加上a字符
匹配字符串: 1111111111111111111111111 (25 chars)
计算步骤数: 77600

最典型的例子,重复运算符嵌套:
表达式: ^(a+)+$ 处理 aaaaaaaaaaaaaaaaX 将使正则表达式引擎分析65536个不同的匹配路径。

解决方案:

对正则表达式处理的内容应进行长度限制
消除正则表达式的歧义,避免重复运算符嵌套。例如表达式^(a+)+$应替换成^a+$

#java# 规范 1.4 条修订建议

1、问题描述
java 代码安全规范的【1.4条】1.4 XML读写 需补充

2、解决建议
应补充下列编码规范:

// XStream安全编码示例
XStream xStream = newXStream();

// 开启安全模式,安全模式采用白名单限制输入的数据类型
XStream.setupDefaultSecurity(xStream);

// 在白名单内添加一些基本数据类型
xstream.addPermission(NullPermission.NULL);
xstream.addPermission(PrimitiveTypePermission.PRIMITIVES);
xstream.allowTypeHierarchy(Collection.class);

// 在白名单内添加一个包下所有的子类
xstream.allowTypesByWildcard(new String[] {
	Blog.class.getPackage().getName()+".*"
});

#java# 规范 HTTP参数污染 增补修订建议

如果应用程序未正确校验用户输入的数据,则恶意用户可能会破坏应用程序的逻辑以执行针对客户端或服务器端的攻击。

脆弱代码1:

// 攻击者可以提交 lang 的内容为:
// en&user_id=1#
// 这将使攻击者可以随意篡改 user_id 的值

String lang = request.getParameter("lang");
GetMethod get = new GetMethod("http://www.host.com");

// 攻击者提交 lang=en&user_id=1#&user_id=123 可覆盖原始 user_id 的值
get.setQueryString("lang=" + lang + "&user_id=" + user_id);
get.execute();

解决方案1:

// 参数化绑定
URIBuilder uriBuilder = new URIBuilder("http://www.host.com/viewDetails");
uriBuilder.addParameter("lang", input);
uriBuilder.addParameter("user_id", userId);

HttpGet httpget = new HttpGet(uriBuilder.build().toString());

脆弱逻辑2:

订单系统计算订单的价格
步骤1:
订单总价 = 商品1单价 * 商品1数量 + 商品2单价 * 商品2数量 + ...
步骤2:
钱包余额 = 钱包金额 - 订单总价

当攻击者将商品数量都篡改为负数,导致步骤1的订单总价为负数。而负负得正,攻击者不仅买入了商品并且钱包金额也增长了。

解决方案2:

应在后台严格校验订单中每一个输入参数的长度、格式、逻辑、特殊字符以及用户的权限。
  • 系统应按照长度、格式、逻辑以及特殊字符4个维度对每一个输入参数进行安全校验,然后再将其传递给敏感的API。
  • 原则上数据库主键不能使用自增纯数字,应使用uuid或雪花算法作为数据库表主键以保证唯一性和不可预测性。
  • 身份信息应使用当前请求的用户session或token安全的获取,而不是直接采用用户提交的身份信息。
  • 安全获取用户身份后,应对请求的数据资源进行逻辑判断,防止用户操作无权限的数据资源。

#java# 规范 jackson编码示例 增补修订建议

jackson版本应不低于2.11.x
禁用 enableDefaultTyping 函数
禁用 JsonTypeInfo 注解
如需使用jackson快速存储数据到redis中应使用 activateDefaultTyping + 白名单过滤器

// jackson白名单过滤
ObjectMapper om = new ObjectMapper();
BasicPolymorphicTypeValidator validator = BasicPolymorphicTypeValidator.builder()

	// 信任 com.china. 包下的类
	.allowIfBaseType("com.china.")
	.allowIfSubType("com.china.")
	
	// 信任 Collection、Map 等基础数据结构
	.allowIfSubType(Collection.class)
	.allowIfSubType(Number.class)
	.allowIfSubType(Map.class)
	.allowIfSubType(Temporal.class)
	.allowIfSubTypeIsArray()
	.build();
om.activateDefaultTyping(validator,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

C ++安全指南.md 修改建议

// Bad
char a[4] = {0};
_snprintf(a, 4, "%s", "AAAA");
foo = strlen(a);

上述代码在MSVC中执行后, a[4] == 'A',因此字符串未以0结尾。

这里的a[4]== 'A' 应该改成a[3]=='A'

#java# 规范 不安全的 SpringBoot Actuator 暴露 增补修订建议

SpringBoot Actuator 如果不进行任何安全限制直接对外暴露访问接口,可导致敏感信息泄露甚至恶意命令执行。

解决方案:

// 参考版本 springboot 2.3.2 
// pom.xml 配置参考 
<!-- 引入 actuator -->
	<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
	</dependency>

<!-- 引入 spring security -->
	<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
	</dependency>

// application.properties 配置参考
#路径映射
management.endpoints.web.base-path=/lhdmon
#允许访问的ip列表
management.access.iplist = 127.0.0.1,192.168.1.100,192.168.2.3/24,192.168.1.6
#指定端口
#management.server.port=8081
#关闭默认打开的endpoint
management.endpoints.enabled-by-default=false
#需要访问的endpoint在这里打开
management.endpoint.info.enabled=true
management.endpoint.health.enabled=true
management.endpoint.env.enabled=true
management.endpoint.metrics.enabled=true
management.endpoint.mappings.enabled=true
#sessions需要spring-session包的支持
#management.endpoint.sessions.enabled=true

#允许查询所有列出的endpoint
management.endpoints.web.exposure.include=info,health,env,metrics,mappings
#显示所有健康状态
management.endpoint.health.show-details=always

#java# 规范 1.1.1 条修订建议

1、问题描述
java 代码安全规范的【1.1.1 条】 SQL语句默认使用预编译并绑定变量 需补充

2、解决建议
应补充下列编码规范:

  • 在 Mybatis 中除了极为特殊的情况,应禁止使用 $ 拼接sql。
  • 所有 Mybatis 的实体bean对象字段都应使用包装类,防止 null 的二义性问题。
// 数据搜索时,编码注意要点
// 1)校验搜索数据的业务逻辑:例如搜索用户手机号,应限制输入数据只能输入数字,防止出现搜索英文或中文的无效搜索
// 2)mybatis预编译不会转义 % 符号,应阻止用户输入 % 符号以防止全表扫描
// 3)输入数据长度和搜索频率应进行限制,防止恶意搜索导致的数据库拒绝服务
<select id="getArticles" resultType="Article">
    SELECT * FROM article WHERE 1
    <if test="searchKeywordType == 'title'">
        AND title LIKE CONCAT('%', #{searchKeyword}, '%')
    </if>
        ORDER BY id DESC
</select>
// 编写 order by 时应使用内置指令,防止$拼接导致sql注入
<select id="findAllList" resultType="Article" >
    SELECT * FROM article ORDER BY
    <choose>
        <when test="page !=null and page != ''">
            page asc
        </when>
        <otherwise>
            time desc
        </otherwise>
    </choose>
</select>

#java# 规范 针对js脚本引擎的代码注入 增补修订建议

攻击者可以构造恶意js注入到js引擎执行恶意代码,所以在java中使用js引擎应使用安全的沙盒模式执行js代码。

脆弱代码:

public void runCustomTrigger(String script) {
	ScriptEngineManager factory = new ScriptEngineManager();
	ScriptEngine engine = factory.getEngineByName("JavaScript");

	// 不执行安全校验,直接eval执行可能造成恶意的js代码执行
	engine.eval(script); 
}

解决方案:

java 8 或者 8 以上版本使用 delight-nashorn-sandbox 组件

<dependency>
    <groupId>org.javadelight</groupId>
    <artifactId>delight-nashorn-sandbox</artifactId>
    <version>[insert latest version]</version>
</dependency>

// 创建沙盒
NashornSandbox sandbox = NashornSandboxes.create();

// 沙盒内默认禁止js代码访问所有的java类对象
// 沙盒可以手工授权js代码能访问的java类对象
sandbox.allow(File.class);

// eval执行js代码
sandbox.eval("var File = Java.type('java.io.File'); File;")

java 7 使用 Rhino 引擎

public void runCustomTrigger(String script) {
	// 启用 Rhino 引擎的js沙盒模式
	SandboxContextFactory contextFactory = new SandboxContextFactory();
	Context context = contextFactory.makeContext();
	contextFactory.enterContext(context);
	try {
		ScriptableObject prototype = context.initStandardObjects();
		prototype.setParentScope(null);
		Scriptable scope = context.newObject(prototype);
		scope.setPrototype(prototype);

		context.evaluateString(scope,script, null, -1, null);
	} finally {
		context.exit();
	}
}

Future Translations / i18n?

Hi,

Great job on these guides! I have always been interested in security, I regularly research it, and I keep updated with trends. Even though I can't read Chinese I can easily read the code and know what security features you are showing. I consider your guide to be of both great quality and in a compact format that allows for developers at any skill level to effectively use the content to secure their apps. Even for experienced developers like myself your compact format can be used as a good checklist when reviewing an app.

Do you have plans to translate this content into other languages or would you consider it? I feel this guide would be of great benefit to developers for many other languages. I'm a native english speaker so I'll include a Google Translate text as well. Personally I've worked with several open source projects where Google Translate was used (other options can be used as well) then a native translator was able to provide corrections and updates so the process can be completed without too much time.

Keep up the good work!
Conrad


你好,

在这些指南上做得很好!我一直对安全性感兴趣,我会定期对其进行研究,并且会随时了解最新趋势。即使我不会读中文,也可以轻松阅读代码并知道您所显示的安全功能。我认为您的指南质量很高,而且格式紧凑,可以使任何技能水平的开发人员都可以有效地使用内容来保护其应用程序。即使对于像我这样的经验丰富的开发人员,您的紧凑格式也可以用作检查应用程序时的良好核对清单。

您是否打算将这些内容翻译成其他语言,或者您会考虑使用?我觉得该指南对于许多其他语言的开发人员将大有裨益。我是说英语的母语人士,因此我还将包括Google翻译文本。就我个人而言,我曾与几个使用Google翻译(也可以使用其他选项)的开源项目一起工作,然后本机翻译程序可以提供更正和更新,因此可以在没有太多时间的情况下完成该过程。

保持良好的工作!
Conrad

#java# 规范 不安全的 Swagger 暴露 增补修订建议

不安全的 Swagger 暴露

Swagger 如果不进行任何安全限制直接对外暴露端访问路径,可导致敏感接口以及接口的参数泄露。

解决方案:

// 测试环境配置文件 application.properties 中
swagger.enable=true

// 生产环境配置文件 application.properties 中
swagger.enable=false

// java代码中变量 swaggerEnable 通过读取配置文件设置swagger开关
@Configuration
public class Swagger {
	@Value("${swagger.enable}")
	private boolean swaggerEnable;

	@Bean
	public Docket createRestApi() {
		return new Docket(DocumentationType.SWAGGER_2)
			//  变量 swaggerEnable 控制是否开启 swagger
			.enable(swaggerEnable)
			.apiInfo(apiInfo())
			.select()
			.apis(RequestHandlerSelectors.basePackage("com.tao.springboot.action"))
			//controller路径
			.paths(PathSelectors.any())
			.build();
    }

#java# 规范 1.9.1 条修订建议

1、问题描述
java 代码安全规范的【1.9.1 条】 返回信息最小化 需补充

2、解决建议
应补充下列编码规范:
解决方案1:

@Controller
class UserController {

	//禁止在url中使用业务变量,以防止篡改导致的越权
	@PostMapping("/user")
	public UserEntity getUser(@RequestParam("id") String id) {

	//返回用户所有字段内容,可能包括敏感字段
	return userService.findById(id).get(); 
	}
}

@Controller
class UserController {

	@InitBinder
	public void initBinder(WebDataBinder binder, WebRequest request){
		//限制返回给用户的字段
		binder.setAllowedFields(["username","password"]);
	}
}

解决方案2:

@Controller
class UserController {

	//禁止在url中使用业务变量,以防止篡改导致的越权
	@PostMapping("/user")
	public UserEntity getUser(@RequestParam("id") String id) {

	//返回用户所有字段内容,可能包括敏感字段
	return userService.findById(id).get(); 
	}
}

class UserEntity {

	@Id
	private Long id;
	private String username;

	// 如果使用jackson,可以使用@JsonIgnore禁止某字段参加格式化
	// 在某字段的get方法上使用@JsonIgnore对应禁止序列化,在set方法方法上使用@JsonIgnore对应禁止反序列化
	// 或者使用@JsonIgnoreProperties(value = "{password}")禁止某字段参与格式化
	@JsonIgnore
	private String password;
}

c# 指南

倒是出个c#指南啊,腾讯没有c#团队吗

#java# 规范 不安全的对象绑定 增补修订建议

对用户输入数据绑定到对象时如不做限制,可能造成攻击者恶意覆盖用户数据

脆弱代码:

@javax.persistence.Entity
class UserEntity {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;

	private String username;

	private String password;

	private Long role;
}
@Controller
class UserController {

	@PutMapping("/user/")
	@ResponseStatus(value = HttpStatus.OK)
	public void update(UserEntity user) {

	// 攻击者可以构造恶意user对象,将id字段构造为管理员id,将password字段构造为弱密码
	// 如果鉴权不完整,接口读取恶意user对象的id字段后会覆盖管理员的password字段成为弱密码
	userService.save(user); 
	}
}

解决方案:

  • setAllowedFields白名单
@Controller
class UserController {

	@InitBinder
	public void initBinder(WebDataBinder binder, WebRequest request){

		// 对允许绑定的字段设置白名单,阻止其他所有字段
		binder.setAllowedFields(["role"]); 
	}
}
  • setDisallowedFields黑名单
@Controller
class UserController {

	@InitBinder
	public void initBinder(WebDataBinder binder, WebRequest request){

		// 对不允许绑定的字段设置黑名单,允许其他所有字段
		binder.setDisallowedFields(["username","password"]); 
	}
}

C/C++安全指南建议

1.6 【必须】不应当把用户可修改的字符串作为printf系列函数的“format”参数
如果用户可以控制字符串,则通过 %n %p 等内容,最坏情况下可以直接执行任意恶意代码。

在以下情况尤其需要注意: WIFI名,设备名……

错误:

snprintf(buf, sizeof(buf), wifi_name);
正确:

snprinf(buf, sizeof(buf), "%s", wifi_name); // 这里应该是 snprintf,少个 t

#go# 规范 1.1.9 条 “slice是引用类型,在作为函数入参时采用的是地址传递” 勘误

1、问题描述

文档中章节 1.1.9【推荐】不使用slice作为函数入参 对于slice的描述不准确,原始内容如下:

slice是引用类型,在作为函数入参时采用的是地址传递,对slice的修改也会影响原始数据。

2、分析及修改建议

2.1、一段代码示例

package main

import "fmt"

func main() {
    si := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Printf("%v  len %d\n", si, len(si))
    test1(si)
    fmt.Printf("%v  len %d\n", si, len(si))
}

func test1(si []int) {
    si = append(si[:3], si[4:]...)
}

该代码的运行结果如下:

[1 2 3 4 5 6 7 8 9]  len 9
[1 2 3 5 6 7 8 9 9]  len 9

2.2、推论结果

从代码示例及运行结果可以看出:

  1. 函数内部对 slice 结构中数组的操作,对于函数外部来说部分成功了(第4个元素已经成功删除)
  2. 函数内部对 slice 结构中长度( length)的操作,对于函数外部来说失败了(第二次输出长度是9,输出元素也是9个)

2.2.1、分析:

先假设 slice做为参数是引用(或地址)传递 成立。那么在函数内部对slice的操作应当 完整的 反馈在原slice上,即:函数外部slice结构中的数组(array)、长度(len)、容量(cap)这3个都应当发生与之对应的改变。

但实践后的结果与预期不符,也就是说:slice做为参数是引用(或地址)传递 不成立。也就是说:slice作为参数是值传递

2.2.2、进一步解答:

  1. 问题一:既然是值传递,那为什么数组内容变了?
    因为slice整个结构是值传递,但slice中的数组是个 指针,在进行数据复制的过程中新的slice的数组内容使用的还是 原指针的值,因此函数内slice和外部的slice使用的是 同一份数据

    所以函数内部对 slice 数组的操作也会反馈到函数外的 slice上。

    但是长度、容量这两个是整型数值,不是指针类型,因此复制的过程中只是复制的内容,在函数内部的操作也不会影响外部。

    附slice底层结构:

    type slice struct { 
        array unsafe.Pointer 
        len int 
        cap int 
    }

参考资料及附录


以上是个人分析,如果大家有不同结论和分析,欢迎探讨。

#java# 规范 fastjson 编码示例 增补修订建议

  • fastjson 版本应不低于1.2.76
  • 如果不需要快速存储数据则应开启 fastjson 的 safeMode 模式
  • 如需使用 fastjson 快速存储数据到redis中应使用 autotype 白名单进行数据存储
// 开启safeMode模式,完全禁用autoType。
1. 在代码中配置
    ParserConfig.getGlobalInstance().setSafeMode(true);
    如果使用new ParserConfig的方式,需要注意单例处理,否则会导致低性能full gc

2. 加上JVM启动参数
    -Dfastjson.parser.safeMode=true

3. 通过fastjson.properties文件配置。
    通过类路径的fastjson.properties文件配置,配置方式如下:
    fastjson.parser.safeMode=true

// 添加autotype白名单,添加白名单有三种方式
1. 在代码中配置,如果有多个包名前缀,分多次addAccept
    ParserConfig.getGlobalInstance().addAccept("com.hyit.pac.client.sdk.dataobject.");

2. 加上JVM启动参数,如果有多个包名前缀,用逗号隔开
    -Dfastjson.parser.autoTypeAccept=com.hyit.pac.client.sdk.dataobject.,com.cainiao. 

3. 通过类路径的fastjson.properties文件配置,如果有多个包名前缀,用逗号隔开safeMode模式
    fastjson.parser.autoTypeAccept=com.hyit.pac.client.sdk.dataobject.,com.cainiao. 

#C,C++# 指南4.3条修订建议

1、问题描述
rand类使用srand()初始化也会产生一些逻辑漏洞,比如以下示例:
image
如果使用该随机数生成验证码时,可能会导致产生生成的随机数相同,从而产生的多个验证码相同,导致逻辑漏洞的产生。
而且rand本身随机性就不高,可直接禁用该函数。

2、解决建议
可以使用libsodium库提供的一组函数,来产生不可预测的数据。可以使用该库的随机数生成函数来生成安全随机数,
如示例(网上copy的,没验证过):
#include "sodium.h"
int foo() {
char myString[32];
int myInt;
randombytes_buf(myString, 32);
/* myString will be a string of 32 random bytes /
myInt = randombytes_uniform(10);
/
myInt will be a random number between 0 and 9 */
}

Go安全编码1.3.1 通过黑名单校验命令注入不可靠

比如:

curl%0a1.1.1.1

利用%0a可以绕过命令注入校验!!!

正确的解决方案

https://github.com/alessio/shellescape/blob/master/shellescape.go
https://pkg.go.dev/github.com/alessio/shellescape#example-Quote

过程中的其他思考:

参考PHP中的防范命令注入的两个函数。

PHP对于命令注入漏洞提供了escapeshellarg()和escapeshellcmd()两个函数来进行防御,当然两者针对的场景有区别。

escapeshellarg

主要是为了防止用户的输入逃逸出“参数值”的位置,变成一个“参数选项”。
处理过程:如果输入内容不包含单引号,则直接对输入的字符串添加一对单引号括起来;如果输入内容包含单引号,则先对该单引号进行转义,再对剩余部分字符串添加相应对数的单引号括起来。
场景功能:

1.确保用户只传递一个参数给命令
2.用户不能指定更多的参数一个
3.用户不能执行不同的命令

escapeshellcmd

主要是防止用户利用shell的一些技巧(如分号、管道符、反引号等)来进行命令注入攻击。

处理过程:如果输入内容中

&#;`|*?~<>^()[]{}$\, \x0A 和 \xFF

等特殊字符会被反斜杠给转义掉;如果单引号和双引号不是成对出现时,会被转义掉。

场景功能:

1.确保用户只执行一个命令
2.用户可以指定不限数量的参数
3.用户不能执行不同的命令

#java# 规范 1.10.3 条修订建议

1、问题描述
java 代码安全规范的【1.10.3条】【建议】加锁操作

2、解决建议
应补充下列编码规范:

  • HashMap、HashSet是非线程安全的;
  • 而Vector、HashTable内部的方法基本都是synchronized,所以是线程安全的。
  • 在高并发下应使用Concurrent包中的集合类,同时在单线程下应禁止使用synchronized。

#Go# 规范1.2.1条修订建议

1、问题描述

Go安全指南的1.2.1【必须】路径穿越检查中所列举的unzipGood例子,存在任意文件写入的问题

// good: 检查压缩的文件名是否包含..路径穿越特征字符,防止任意写入
func unzipGood(f string) bool {
	r, err := zip.OpenReader(f)
	if err != nil {
		fmt.Println("read zip file fail")
		return false
	}
	for _, f := range r.File {
		p, _ := filepath.Abs(f.Name)
		if !strings.Contains(p, "..") {
			ioutil.WriteFile(p, []byte("present"), 0640)
		}
	}
	return true
}

2、解决建议

func Abs(path string) (string, error)

Abs returns an absolute representation of path. If the path is not absolute it will be joined with the current working directory to turn it into an absolute path. The absolute path name for a given file is not guaranteed to be unique. Abs calls Clean on the result.

应当对目标路径的合法性进行校验,而不是检查是否包含..

filepath.Abs()自身就已经调用了filepath.Clean(),因此上述函数中的!strings.Contains(p, "..")是无效的检查。

#java# 规范 1.2.4修订建议

编写编码规范应注意,开发人员很讨厌东一个规则西一个规则,最好把所有安全校验的逻辑写到一个接口示例中,保证一次性能看完所有的相关安全问题。

1、问题描述
java 代码安全规范的【1.2.4 条】 避免路径穿越 需补充

import org.apache.commons.io.FilenameUtils;

@GET
@Path("/images/{image}")
@Produces("images/*")
public Response getImage(@javax.ws.rs.PathParam("image") String image) {

	// 首先进行逻辑校验,判断用户是否有权限访问接口 以及 用户对访问的资源是否有权限
	
	// 过滤image变量中的 ../ 或 ..\
	File file = new File("resources/images/", FilenameUtils.getName(image)); 

	if (!file.exists()) {
		return Response.status(Status.NOT_FOUND).build();
	}
	return Response.ok().entity(new FileInputStream(file)).build();
}
  • 如果不对用户发起请求的文件参数进行校验,会导致潜在的路径遍历漏洞。
  • 应对所有文件的上传操作进行权限判断,无上传权限应直接提示无权限。
  • 危险的文件后缀应严禁上传,包括: .jsp .jspx .war .jar .exe .bat .js .vbs .html .shtml
  • 应依照业务逻辑对文件后缀进行前后端的白名单校验,禁止白名单之外的文件上传
    • 图片类型 .jpg .png .gif .jpeg
    • 文档类型 .doc .docx .ppt .pptx .xls .xlsx .pdf
    • 以此类推
  • 上传的文件保存时应使用uuid、雪花算法等算法进行强制重命名,以保证文件的不可预测性和唯一性。
  • 应对所有文件的下载操作依照 “除了公共文件,只有上传者才能下载” 的原则进行权限判断,防止越权攻击。

#Node# 规范 1.5 数据输出 修改意见

1、问题描述
数据输出规范,属于数据业务安全范畴,并非Node语言独立特性。并浏览了Java、Go等的安全规范中,也并未包含此说明(或描述不统一)。

2、解决建议
可以新增一个数据安全类的md文档,所有服务端数据出输范畴的安全描述,都可以外联到这个文档上。

#java# 规范 日志伪造 增补修订建议

日志注入攻击是将未经验证的用户输入写到日志文件中,可以允许攻击者伪造日志条目或将恶意内容注入到日志中。

如果用户提交val的字符串"twenty-one",则会记录以下条目:

INFO: Failed to parse val=twenty-one 
HACK: User logged in=badguy

然而,如果攻击者提交包含换行符%0d和%0a的字符串”twenty-one%0d%0aHACK:+User+logged+in%3dbadguy”,会记录以下条目:

INFO: Failed to parse val=twenty-one 
HACK: User logged in=badguy

显然,攻击者可以使用相同的机制插入任意日志条目。所以所有写入日志的条目必须去除\r和\n字符。

脆弱代码:

public void risk(HttpServletRequest request, HttpServletResponse response) {
	String val = request.getParameter("val");
	try {
		int value = Integer.parseInt(val);
		out = response.getOutputStream();
	}
	catch (NumberFormatException e) {
		e.printStackTrace(out);
		log.info(""Failed to parse val = "" + val);
	}
}

解决方案:

public void risk(HttpServletRequest request, HttpServletResponse response) {
	String val = request.getParameter("val");
	try {
		int value = Integer.parseInt(val);
	}
	catch (NumberFormatException e) {
		val = val.replace("\r", "");
		val = val.replace("\n", "");
		log.info(""Failed to parse val = "" + val);
		//不要直接 printStackTrace 输出错误日志
	}
}

#java# 规范 1.8.4 条修订建议

1、问题描述
建议采用随机盐+明文密码进行多轮哈希后存储密码。

2、解决建议

【必须】随机盐必须是符合密码学安全的随机数。禁止使用Random类生产。
【必须】随机盐的长度不能太短,建议至少256比特。
【建议】盐值至少和哈希函数的输出一样长
【必须】每次使用的盐必须随机化和一次性,禁止使用共享的随机盐,注册或者修改密码时,都应该使用新的盐值进行加密。
【必须】使用哈希函数必须是sha256或以上
【建议】建议使用受业界认可的慢哈希算法
【建议】多轮的次数应该充分多
【建议】可以记录多轮的次数和加密算法,以便进行后续的维护升级

#java# 规范 错误的十六进制串联 增补修订建议

将包含哈希签名的字节数组转换为人类可读的字符串时,如果逐字节读取该数组,则可能会发生转换错误。 所有对于数据格式化的操作应优先使用规范的数据格式化处理机制。

脆弱代码:

MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] resultBytes = md.digest(password.getBytes("UTF-8"));

StringBuilder stringBuilder = new StringBuilder();
for(byte b :resultBytes) {
	stringBuilder.append( Integer.toHexString( b & 0xFF ) );
}
return stringBuilder.toString();

对于上述功能,哈希值 “0x0679” 和 “0x6709” 都将输出为 “679”

解决方案:

stringBuilder.append(String.format("%02X", b));

工具使用

组合使用 cppcheck pvs-studio JetBrains.ReSharper 基本可以发现大多数问题。

#java# 规范 1.2.1 条修订建议

1、问题描述
java 代码安全规范的【1.2.1 条】 文件类型限制 需补充

2、解决建议
通过后缀名进行文件类型校验不总是可靠的,建议补充魔数的校验方式

参考 List of file signatures

通用解决方案:
定义白名单的文件类型魔数枚举类,通过 commons-io 的 FileFilterUtils 类的 magicNumberFileFilter 进行过滤。

#java# 规范 1.8.3 条修订建议

1、问题描述
1.8.3【建议】哈希算法推荐使用SHA256或更高级别的哈希算法,已获得金融级别的安全保证。
综合国密、**金融移动支付相关规范、等保2.0、等保3.0等相关规范,建议安全认证的使用哈希算法至少SHA256和以上。

2、解决建议
1.8.3【建议】哈希算法推荐使用SHA256或更高级别的哈希算法

3 参考
**金融移动支付

#java# 规范 1.6 OS命令执行 修订建议

1、问题描述
java 代码安全规范的【1.6 】 OS命令执行 需修改

2、解决建议
应修改下列编码建议:
或过滤转义以下符号:|;&$><(反引号)!
修改为
或过滤转义以下符号: & | ; $ > < ` ' " ! ? #

主要增加一些ctf里,命令执行绕过的技巧中用到的特殊符号

#java# 规范 配置全局反序列化白名单 增补修订建议

jep290安全策略:全进程反序列化原则上使用白名单优先的设计模式,只有允许的类才能被反序列化,其它一律被阻止。

// 能被反序列化的流的限制
maxdepth=value // 单次反序列化堆栈最大深度
maxrefs=value  // 单次反序列化类的内部引用的最大数目
maxbytes=value // 单次反序列化输入流的字节数上限
maxarray=value // 单次反序列化输入流中数组数上限
// 以下示例介绍了限制反序列化的类名称的配置方法

// 允许唯一类 org.example.Teacher ,输入字节数最大为100,并阻止其它一切的类
jdk.serialFilter=maxbytes=100;org.example.Teacher;!*

// 允许 org.example. 下的所有类,输入字节数最大为100,并阻止其它一切的类
jdk.serialFilter=maxbytes=100;org.example.*;!*

// 允许 org.example. 下的所有类和子类,输入字节数最大为100,并阻止其它一切的类
jdk.serialFilter=maxbytes=100;org.example.**;!*

//允许一切类
jdk.serialFilter=*;

;    作为表达式的分隔符
.*   代表当前包下的所有类
.**  代表当前包下所有类和所有子类
!   代表取反,禁止匹配符号后的表达式被反序列化
*    通配符
  1. 使用配置文件配置白名单
jdk11+:%JAVA_HOME%\conf\security\java.security
 jdk8: %JAVA_HOME%\jre\lib\security\java.security
  1. 启动参数配置白名单
java -Djdk.serialFilter=org.example.**;maxbytes=100;!*
  1. 使用代码配置白名单
Properties props = System.getProperties();
props.setProperty("jdk.serialFilter", "org.example.**;maxbytes=100;!*");

#java# 规范 可预测的伪随机数生成器 增补修订建议

当在某些安全关键的上下文中使用可预测的随机值时,可能会导致漏洞。

例如,当该值用作:

  • CSRF令牌:可预测的令牌可能导致CSRF攻击,因为攻击者将知道令牌的值
  • 密码重置令牌(通过电子邮件发送):可预测的密码令牌可能会导致帐户被接管,因为攻击者会猜测“更改密码”表单的URL
  • 任何其他敏感值

脆弱代码:

String generateSecretToken() {
	Random r = new Random();
	return Long.toHexString(r.nextLong());
}

解决方案:

替换 java.util.Random 使用强度更高的 java.security.SecureRandom

import org.apache.commons.codec.binary.Hex;

String generateSecretToken() {
	SecureRandom secRandom = new SecureRandom();

	byte[] result = new byte[32];
	secRandom.nextBytes(result);
	return Hex.encodeHexString(result);
}

#java# 规范 XPath注入 增补修订建议

XPath注入风险类似于SQL注入,如果XPath查询包含不受信任的用户输入,则可能会暴露完整的数据源。这可能使攻击者可以访问未经授权的数据或恶意修改目标XML。

下面以登录验证中的模块为例,说明 XPath注入攻击的实现原理。

在应用程序的登录验证程序中,一般有用户名(username)和密码(password) 两个参数,程序会通过用户所提交输入的用户名和密码来执行授权操作。

若验证数据存放在XML文件中,其原理是通过查找user表中的用户名 (username)和密码(password)的结果进行授权访问。

例存在user.xml文件如下:

<user>
	<firstname>Ben</firstname>
	<lastname>Elmore</lastname>
	<loginID>abc</loginID>
	<password>test123</password>
</user>
<user>
	<firstname>Shlomy</firstname>
	<lastname>Gantz</lastname>
	<loginID>xyz</loginID>
	<password>123test</password>
</user>

则在XPath中其典型的查询语句如下:

//users/user[loginID/text()='xyz'and password/text()='123test']

正常用户传入 login 和 password,例如 loginID = 'xyz' 和 password = '123test',则该查询语句将返回 true。但如果恶意用户传入类似 ' or 1=1 or ''=' 的值,那么该查询语句也会得到 true 返回值,因为 XPath 查询语句最终会变成如下代码:

//users/user[loginID/text()=''or 1=1 or ''='' and password/text()='' or 1=1 or ''='']

脆弱代码:

public int risk(HttpServletRequest request, 
		Document doc, XPath xpath ,org.apache.log4jLogger logger) {
	int len = 0;
	String path = request.getParameter("path");
	try {
		XPathExpression expr = xpath.compile(path);
		Object result = expr.evaluate(doc, XPathConstants.NODESET);
		NodeList nodes = (NodeList) result;
		len = nodes.getLength();
	} catch (XPathExpressionException e) {
		logger.warn("Exception", e);
	}
	return len;
}

解决方案:

public int fix(HttpServletRequest request, 
		Document doc, XPath xpath ,org.apache.log4j.Logger logger) {
	int len = 0;
	String path = request.getParameter("path");
	try {
		// 使用过滤函数
		String filtedXPath = filterForXPath(path);
		XPathExpression expr = xpath.compile(filtedXPath);
		Object result = expr.evaluate(doc, XPathConstants.NODESET);
		NodeList nodes = (NodeList) result;
		len = nodes.getLength();
	} catch (XPathExpressionException e) {
		logger.warn("Exception", e);
	}
	return len;
}
// 限制用户的输入数据,尤其应限制特殊字符
public String filterForXPath(String input) {
	if (input == null) {
		return null;
	}
	StringBuilder out = new StringBuilder();
	for (int i = 0; i < input.length(); i++) {
		char c = input.charAt(i);
		if (c >= 'A' && c <= 'Z') {
			out.append(c);
		} else if (c >= 'a' && c <= 'z') {
			out.append(c);
		} else if (c >= '0' && c <= '9') {
			out.append(c);
		} else if (c == '_' || c == '-') {
			//限制特殊字符的使用
			out.append(c);
		} else if (c >= 0x4e00 && c <= 0x9fa5) {
			//允许汉字使用
			out.append(c);
		}
	}
	return out.toString();
}

#java# 规范 1.5.4 条修订建议

1、问题描述
java 代码安全规范的 1.5.4 外部输入拼接到HTTP响应头中需进行过滤 需补充

2、解决建议
应补充下列编码规范:

public void risk(HttpServletRequest request, HttpServletResponse response) {
	String val = request.getParameter("val");
	try {
		int value = Integer.parseInt(val);
	}
	catch (NumberFormatException e) {
		val = val.replace("\r", "");
		val = val.replace("\n", "");
		log.info(""Failed to parse val = "" + val);
		//不要直接 printStackTrace 输出错误日志
	}
}

#java# 规范 1.8.1 条修订建议

1、问题描述
java 代码安全规范的【1.8.1 条】 对称加密 需修改

2、解决建议
应修改下列编码建议:
建议使用AES,秘钥长度256位以上 (多个行业都要求aes最低为256位)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.