做内容安全审查时,发现了一个奇怪的case。 明明用户的评论中含有敏感词,但是用关键词匹配却无法匹配成功。就好像like这个匹配方法失效了一样。
sql select '一个敏感词啊' like '%敏感词%' -- 结果竟然为false
用肉眼是无法看出问题的,把评论以文件形式下载到本地,用vim打开,才发现,原来文本的顺序是错乱的!见下图
以这一段文本为例,<202d><202e>耳木<202c><202c>品质差别<202d><202e>大太<202c><202c>,看起来是木耳品质差别太大,但是存储的却是耳木 和 大太。这样,用普通的方法,便是无法进行字符串匹配,从而逃过了安全审核。
这其实是unicode提供的一个功能,有一些unicode是控制字符。\u202e 的含义就是后面的字符翻转显示,\u202c 的含义是翻转控制到此结束。所以,在这两个控制字符之间的内容,全都是从右向左展示。
查询了一下,unicode相关的控制字符其实还有很多参考链接
| 开头 | 结尾 | 说明 |
|---|---|---|
| \u2066 | \u2069 | 之间的字符从左到右显示,不影响外围字符 |
| \u2067 | \u2069 | 之间的字符从右到左显示,不影响外围字符 |
| \u2068 | \u2069 | 之间的字符以第一个方向设定字符为准 |
| \u202A | \u202C | 之间的字符从左到右显示,可以影响外围字符 |
| \u202B | \u202C | 之间的字符从右到左显示,可以影响外围字符 |
| \u202D | \u202C | 之间的字符按照内存中的顺序,从左到右显示 |
| \u202E | \u202C | 之间的字符按照内存中的顺序,从右到左显示 |
也可以在这个网站查询unicode具体的含义https://www.fileformat.info/info/unicode/char/202e/index.htm
虽然unicode里面定义了,但是实现的实现却各有不同,有的可以生效,有的无法生效。在hive中只有\u202E 是生效的。
有了这些知识,翻转字符串变得简单了,只要在最前面加一个 \u202E 即可实现翻转。
select
'hello world',
'\u2067hello world\u2069',
'\u202Bhello world\u202C',
'\u202Ehello world'
这个sql只有最后一个hello world可以被翻转。
翻转字符带来很多麻烦,为了识别出实际展示的字符串,可以制作一个udf把实际展示的字符串翻译出来。java 本身已经内置了一个java.text.Bidi来方便进行处理。
import org.apache.hadoop.hive.ql.exec.UDF;
import org.apache.hadoop.io.Text;
import java.text.Bidi;
public final class StringNormalizeUDF extends UDF {
public String evaluate(final String s) {
if (s == null) {
return null;
}
if (! Bidi.requiresBidi(s.toCharArray(), 0, s.length()) ){
return s;
}
return visualToLogical(s);
}
private static String visualToLogical(String text) {
if ((text == null) || (text.length() == 0)) {
return text;
}
Bidi bidi = new Bidi(text, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT);
if (bidi.isLeftToRight()) {
return text;
}
int count = bidi.getRunCount();
byte[] levels = new byte[count];
Integer[] runs = new Integer[count];
for (int i = 0; i < count; i++){
levels[i] = (byte)bidi.getRunLevel(i);
runs[i] = i;
}
Bidi.reorderVisually(levels, 0, runs, 0, count);
StringBuilder result = new StringBuilder();
for (int i = 0; i < count; i++) {
int index = runs[i];
int start = bidi.getRunStart(index);
int end = bidi.getRunLimit(index);
int level = levels[index];
if ((level & 1) != 0) {
for (; --end >= start;) {
result.append(text.charAt(end));
}
} else {
result.append(text, start, end);
}
}
return result.toString();
}
}
在内容安全审查中遇到一个奇特现象,用户评论中的敏感词无法被匹配。问题在于评论中的字符串顺序因Unicode控制字符而错乱。字符U+202E导致文本从右向左显示,而U+202C则结束翻转。这种特性使得常规字符串匹配方法失效。了解Unicode的控制字符后,通过在字符串前添加U+202E可实现翻转。在Hive中,只有U+202E生效。为了解决这个问题,可以创建UDF将实际显示的字符串转换出来,Java提供了内置工具进行处理。
19万+

被折叠的 条评论
为什么被折叠?



