《Java安全编码标准》一2.11 IDS10-J不要拆分两种数据结构中的字符串

简介: 本节书摘来自华章出版社《Java安全编码标准》一书中的第2章,第2.11节,作者 (美)Fred Long,Dhruv Mohindra,Robert C. Seacord,Dean F. Sutherland,David Svoboda,更多章节内容可以访问云栖社区“华章计算机”公众号查看

2.11 IDS10-J不要拆分两种数据结构中的字符串

在历史遗留系统中,常常假设字符串中的每一个字符使用8位(一个字节,Java中的byte)。而Java语言使用16位表示一个字符(Java中的Char类型)。遗憾的是,不管是Java的byte类型还是char类型数据,都不能表示所有的Unicode字符。许多字符串使用例如UTF-8编码的方式存储和通信,而在这种编码中,字符长度是可变的。
当Java字符串以字符数组的方式存储时,它可以用一个字节数组来表示,字符串里的一个字符可以用两个连续的或更多的byte类型或者char类型表示。如果拆分一个char类型或byte类型的数组,将会对多字节的字符产生风险。
如果忽略那些补充字符(supplementary character)多字节字符或者整合字符(修改其他字符的那些字符),攻击者可能绕过输入验证。因此,不应该拆分两种数据结构中的字符。

2.11.1 多字节字符

在一些字符集中会使用多字节字符编码,这些字符集要求用一个以上的字节来唯一标识每一个字符。比如,在日文Shift-JIS编码中,就支持多字节编码,其最大的字符长度为2个字节(一个起始字节,一个结尾字节)。
字节类型 范围
image

对结尾字节而言,它可能覆盖单个字节或者多字节字符的起始字节。当一个多字节字符被分拆时,特别是跨不同的缓冲区边界分拆时,它会产生不同的解释,如果没有按照正常的缓冲区边界进行分拆的话。这种差异一般由构造字符时使用的字节有二义性造成。

2.11.2 补充字符

根据Java API[API 2006]对Character类的描述(Unicode的字符表示):
Char数据类型(和Character对象封装的值)需要依赖于初始的Unicode编码定义,其中将其定义为长度为16位的字符编码。Unicode编码后来经过改动,允许使用多于16位来表示字符编码。合法的字符编码位于u0000?~u10FFFF,这就是我们所熟悉的Unicode字符编码值。
Java 2平台在char数组、String和StringBuffer?类中使用UTF-16编码表示。在这种字符表示中,补充字符会用一对char值来表示,第一个高位字符范围是uD800~uDBFF,第二个低位字符范围是uDC00~uDFFF。
一个int值可以表示所有的Unicode编码字符,包括那些补充码。int类型中的最低21位是用来表示Unicode编码的,其他的11个高位必须为0。除非特指,关于补充码和字符值有下面的规则:
那些只能接受char值的方法是不支持补充码的。替代范围内的char值会被认为是未定义的字符。比如,Character.isLetter('uD840')会返回false,即使这种特殊字符紧跟着任何表示字母的字符串的低位替代值。
接受int值的方法支持所有的Unicode字符,包括字符。比如,Character.isLetter(0x2F81A)会返回true,因为这个码点值代表一个字母(在CJK编码中)。

2.11.3 不符合规则的代码示例(读取)

这个不符合规则的代码示例会从一个套接字中读取1024个字节,并且使用这些数据创建一个字符串。它同时使用一个while循环来读取这些字节,就像在FIO10-J中推荐的那样。当检测到套接字中的数据多于1024个字节时,它就会抛出异常。这样的机制能够防止非受信的输入耗尽程序的内存。

public final int MAX_SIZE = 1024;

public String readBytes(Socket socket) throws IOException {
??InputStream in = socket.getInputStream();
??byte[] data = new byte[MAX_SIZE+1];
??int offset = 0;
??int bytesRead = 0;
??String str = new String();
??while ((bytesRead = in.read(data, offset, data.length - offset))
??????????!= -1) {
????offset += bytesRead;
????str += new String(data, offset, data.length - offset, "UTF-8");
????if (offset >= data.length) {
??????throw new IOException("Too much input");
????}
??}
??in.close();
??return str;
}
AI 代码解读

这个代码示例没有考虑到使用多字节编码的字符和循环选代边界之间的关系。如果在最后一个通过read()方法读取的数据流中存在一个对字节编码的首字节,那么其他的字节只能在循环的下一次处理。然而,可以通过在一个循环中创建一个新的字符串来解决多字节编码问题。这样的话,多字节编码就可能会被错误地解释。

2.11.4 符合规则的方案(读取)

该符合规则的方案将字符串的创建推迟到接收完所有的数据时才完成。

public final int MAX_SIZE = 1024;

public String readBytes(Socket socket) throws IOException {
??InputStream in = socket.getInputStream();
??byte[] data = new byte[MAX_SIZE+1];
??int offset = 0;
??int bytesRead = 0;
??while ((bytesRead = in.read(data, offset, data.length - offset))
??????????!= -1) {
????offset += bytesRead;
????if (offset >= data.length) {
??????throw new IOException("Too much input");
????}
??}
??String str = new String(data, "UTF-8");
??in.close();
??return str;
}
AI 代码解读

这段代码避免了将跨不同缓冲区的多字节编码字符分隔的问题,使用的方法是,直到读取完所有的数据,才开始创建字符串。

2.11.5 符合规则的方案(Reader)

这个符合规则的方案使用的是Reader而不是InputStream。这个Reader类会快速地将字节数据转换为字符数据,所以能够避免分隔多字节字符的问题。当套接字使用多于1024个字符而不是恰好使用1024字节时,这个例程会自动退出。

public final int MAX_SIZE = 1024;

public String readBytes(Socket socket) throws IOException {
??InputStream in = socket.getInputStream();
??Reader r = new InputStreamReader(in, "UTF-8");
??char[] data = new char[MAX_SIZE+1];
??int offset = 0;
??int charsRead = 0;
??String str = new String(data);
??while ((charsRead = r.read(data, offset, data.length - offset))
?????????!= -1) {
????offset += charsRead;
????str += new String(data, offset, data.length - offset);
????if (offset >= data.length) {
??????throw new IOException("Too much input");
????}
??}
??in.close();
??return str;
}
AI 代码解读

2.11.6 不符合规则的代码示例(子字符串)

这个不符合规则的代码示例想截取字符串中的首字母。它的做法不对,因为使用了Character.isLetter()方法,这个方法不能处理补充字符和合并字符[Hornig 2007]。

// Fails for supplementary or combining characters
public static String trim_bad1(String string) {
??char ch;
??int i;
??for (i = 0; i < string.length(); i += 1) {
????ch = string.charAt(i);
????if (!Character.isLetter(ch)) {
??????break;
????}
??}
??return string.substring(i);
}
AI 代码解读

2.11.7 不符合规则的代码示例(子字符串)

这个不符合规则的代码示例想要纠正使用?String.codePointAt()?的错误,这个方法使用了int类型作为输入参数。这对补充字符来说是正确的,但对于合并字符而言却是错误的[Hornig 2007]。

// Fails for combining characters
public static String trim_bad2(String string) {
??int ch;
??int i;
??for (i = 0; i < string.length(); i += Character.charCount(ch)) {
????ch = string.codePointAt(i);
????if (!Character.isLetter(ch)) {
??????break;
????}
??}
??return string.substring(i);
}
AI 代码解读

2.11.8 符合规则的方案(子字符串)

这个方案可以处理补充字符和合并字符[Hornig 2007]。根据Java API[API 2006]文档对java.text.BreakIterator的说明:
BreakIterator实现了能够在文本边界内定位的方法。BreakIterator?的对象实例维护了当前的位置信息,并且会对文本进行扫描,当遇到文本边界的时候,它会返回字符所在的位置索引。
返回的边界可能是那些补充字符、合并字符。例如,一个音节字符可以被存储为一个基础字符加上一个用来区分的符号。

public static String trim_good(String string) {
??BreakIterator iter = BreakIterator.getCharacterInstance();
??iter.setText(string);
??int i;
??for (i = iter.first(); i != BreakIterator.DONE; i = iter.next()) {
????int ch = string.codePointAt(i);
????if (!Character.isLetter(ch)) {
??????break;
????}????
??}
??// Reached first or last text boundary
??if (i == BreakIterator.DONE) {?
????// The input was either blank or had only (leading) letters
????return "";?
??} else {
????return string.substring(i);
??}
}
AI 代码解读

如果要对locale敏感的字符串比较、搜索和排序,可以使用?java.text.Collator?类。

2.11.9 风险评估

如果没有考虑到补充字符和合并字符的话,将会导致不可预计的行为。
image

2.11.10 参考书目

image

目录
打赏
0
0
0
0
1408
分享
相关文章
Java 字符串详解
本文介绍了 Java 中的三种字符串类型:String、StringBuffer 和 StringBuilder,详细讲解了它们的区别与使用场景。String 是不可变的字符串常量,线程安全但操作效率较低;StringBuffer 是可变的字符串缓冲区,线程安全但性能稍逊;StringBuilder 同样是可变的字符串缓冲区,但非线程安全,性能更高。文章还列举了三者的常用方法,并总结了它们在不同环境下的适用情况及执行速度对比。
52 17
Java字符串缓冲区
字符串缓冲区是用于处理可变字符串的容器,Java中提供了`StringBuffer`和`StringBuilder`两种实现。由于`String`类不可变,当需要频繁修改字符串时,使用缓冲区更高效。`StringBuffer`是一个线程安全的容器,支持动态扩展、任意类型数据转为字符串存储,并提供多种操作方法(如`append`、`insert`、`delete`等)。通过这些方法,可以方便地对字符串进行添加、插入、删除等操作,最终将结果转换为字符串。示例代码展示了如何创建缓冲区对象并调用相关方法完成字符串操作。
43 13
|
4月前
|
java小工具util系列2:字符串工具
java小工具util系列2:字符串工具
182 83
Java智慧工地(源码):数字化管理提升施工安全与质量
随着科技的发展,智慧工地已成为建筑行业转型升级的重要手段。依托智能感知设备和云物互联技术,智慧工地为工程管理带来了革命性的变革,实现了项目管理的简单化、远程化和智能化。
55 5
|
4月前
|
java小工具util系列1:日期和字符串转换工具
java小工具util系列1:日期和字符串转换工具
92 26
Java 集合江湖:底层数据结构的大揭秘!
小米是一位热爱技术分享的程序员,本文详细解析了Java面试中常见的List、Set、Map的区别。不仅介绍了它们的基本特性和实现类,还深入探讨了各自的使用场景和面试技巧,帮助读者更好地理解和应对相关问题。
79 5
java 中操作字符串都有哪些类,它们之间有什么区别
Java中操作字符串的类主要有String、StringBuilder和StringBuffer。String是不可变的,每次操作都会生成新对象;StringBuilder和StringBuffer都是可变的,但StringBuilder是非线程安全的,而StringBuffer是线程安全的,因此性能略低。
143 8
Java 异常处理:筑牢程序稳定性的 “安全网”
本文深入探讨Java异常处理,涵盖异常的基础分类、处理机制及最佳实践。从`Error`与`Exception`的区分,到`try-catch-finally`和`throws`的运用,再到自定义异常的设计,全面解析如何有效管理程序中的异常情况,提升代码的健壮性和可维护性。通过实例代码,帮助开发者掌握异常处理技巧,确保程序稳定运行。
88 2
|
10月前
|
Java数据结构:链表
Java数据结构:链表
68 2
java数据结构,双向链表的实现
文章介绍了双向链表的实现,包括数据结构定义、插入和删除操作的代码实现,以及双向链表的其他操作方法,并提供了完整的Java代码实现。
java数据结构,双向链表的实现
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等