内容简介:最近看到不少网站都使用了字体库对数据进行加密,即页面源码中的数据与显示出来的数据不同,用户也无法直接进行复制。例如企信宝页面中的字母与数字,58房产频道中的数字:
*文章原创作者:manwu91,本文属于FreeBuf原创奖励计划,未经许可禁止转载。
最近看到不少网站都使用了字体库对数据进行加密,即页面源码中的数据与显示出来的数据不同,用户也无法直接进行复制。
例如企信宝页面中的字母与数字,58房产频道中的数字:
经过对字体库进行研究,找到了加密与解密方案。
加密
准备字体库样本
笔者在网上随便下载了一个ttf字体库,保存为’origin.ttf’,使用 fonttools 的命令行工具pyftsubset提取需要加密的字符:
pyftsubset origin.ttf --text='1234567890'
参数text为要提取字体的字符,运行结束后会在当前目录生成’origin.subset.ttf’,该字体库只包含’1234567890′共10个字符。
生成加密字体库
这里使用 [http://fontello.com/](http://fontello.com/) 网站提供的在线服务对上一步生成的字体库进行定制。首先将生成的subset.ttf转为svg,笔者使用的是 cloudconvert 提供的服务。然后将svg上传到fontello,选中要定制的字符,因为我们上传的字体库只包含0到9,所以这里全选,然后在 Customize Codes 功能下自定义码值。
码值与字符的关系可以看作是一种映射关系,比如 Unicode E801 对应字符 1 , Unicode E802 对应字符 2 。我们可以随意修改字符的unicode值,但一定要记住这个值与真实字符的对应关系,来对要显示在页面上的数据加密。这里使用该网站默认生成的unicode。对应关系如下:
CIPHER_BOOK = {
'0': '\uE800',
'1': '\uE801',
'2': '\uE802',
'3': '\uE803',
'4': '\uE804',
'5': '\uE805',
'6': '\uE806',
'7': '\uE807',
'8': '\uE808',
'9': '\uE809'
}
定制完成后下载字体文件。
使用
在css中定义字体,名为 fontello 。
@font-face {
font-family: 'fontello';
src: url('/static/fontello.woff2') format('woff');
font-weight: normal;
font-style: normal;
}
然后定义使用该字体的class:
.demo-icon {
font-family: "fontello";
}
这样只需要为页面标签添加上’demo-icon’的class就可以了。如:
<h1><small class="demo-icon">就是这串数字:<b>{{string}}</b></small></h1>
服务端在返回数据前需要需要将数字用CIPHER_BOOK进行转换。
CIPHER_BOOK = {
'0': '\uE800',
'1': '\uE801',
'2': '\uE802',
'3': '\uE803',
'4': '\uE804',
'5': '\uE805',
'6': '\uE806',
'7': '\uE807',
'8': '\uE808',
'9': '\uE809'
}
def _encrypt_secret(secret):
return ''.join(CIPHER_BOOK[c] for c in secret)
@app.route('/')
def index():
if 'guess' in request.values:
ts = session['ts'] if 'ts' in session else 0
secret = session['secret'] if 'secret' in session else None
if time.time() - ts < 2 and request.values['guess'] == secret:
return render_template('index.html', success=True)
secret = ''.join([random.choice('0123456789') for _ in range(20)])
# 通过CIPHER_BOOK将数字转换为不可见字符
s = _encrypt_secret(secret)
session['secret'] = secret
session['ts'] = time.time()
return render_template("index.html", string=s)
查看页面源码,会发现源码是无法显示的字符,且复制出来的是乱码。
58产房频道使用的就是本文介绍的方案,只加密了数字。但是不同页面的字体库是变化的。在字体加密破解中我们会详细介绍如何破解58的字体加密。示例代码已上传到 github ,有兴趣的可以看看。
破解
前面已经介绍如何制作加密字体库并在demo项目中使用来防止数据被抓取,下面介绍破解方法。
true-type字体简介
我们已经知道字体加密其实是一种明文到密文的双向映射,所以只要找到映射表就可以了。但我们在破解的时候只能拿到字体库文件,所以需要通过该文件找到CIPHER_BOOK。这就需要对字体库结构有一定了解。在查阅 相关文档 后,可以简单地将字体的绘制过程为理解为:
1.根据字符的unicode编码找到glyph名称 (cmap);
2.根据glyph名称找到glyph (glyf);
3.使用glyph进行绘制。
其中glyph可以理解为字体的绘制所需的数据,如点、线等。
一个TrueType Font字体文件包含几个table。这里需要用到的两个table如下(tag为table的名称):
| tag | table |
|---|---|
| cmap | character to glyph mapping |
| glyf | glyph data |
根据字体的绘制过程,可以猜测有两种方式实现字体加密:
1.打乱字符编码
2.打乱glyph名称
下面笔者就这两种情况用两个案例进行讲解。
破解demo
首先在页面中找到字体库的url并下载,得到fontello.woff2,然后用fonttools将文件转为ttx方便肉眼分析。
from fontTools.ttLib import TTFont
font = TTFont('fontello.woff2')
font.saveXML('fontello.ttx')
得到的ttx为xml文档,打开并查找cmap节点:
据此我们可以还原加密时的映射表(即cmap表):
CIPHER_BOOK = {
'\ue800': '0',
'\ue801': '1',
'\ue802': '2',
'\ue803': '3',
'\ue804': '4',
'\ue805': '5',
'\ue806': '6',
'\ue807': '7',
'\ue808': '8',
'\ue809': '9'
}
由于demo使用了静态的字体库,所以这个表不会变化,写死就可以了,破解代码如下:
import requests
from bs4 import BeautifulSoup as BS
CIPHER_BOOK = {
'\ue800': '0',
'\ue801': '1',
'\ue802': '2',
'\ue803': '3',
'\ue804': '4',
'\ue805': '5',
'\ue806': '6',
'\ue807': '7',
'\ue808': '8',
'\ue809': '9'
}
URL = 'http://127.0.0.1:5000'
sess = requests.Session()
resp = sess.get(URL).text
bs = BS(resp, 'lxml')
string = bs.select_one('.demo-icon b').text
guess = ''.join(CIPHER_BOOK[c] if c in CIPHER_BOOK else c
for c in string)
print('guess:', guess)
resp = sess.get(URL, params={'guess': guess}).text
assert 'Congratulations' in resp
破解58
demo中的字体库不会变化,所以映射表写死就可以了。但分析发现58房产频道不同页面的字体库是不一样的,而且glyph name与真实字符有差异,所以需要根据字体库动态处理。
首先页面中的字体文件是经过base64编码的,直接解码并保存到文件即可。
然后用上面的代码转为ttx文件,查看cmap节点:
通过观察对比发现,字符编码相同,但glyph名称是变化的,且glyph名称与真实数字的关系为:
glyph_name = 'glyph00%02d' % (real_num + 1)
据此我们可以还原glyph名称与真实字符的映射表(即glyf表):
GLYF_TABLE = {
'glyph00001': '0',
'glyph00002': '1',
'glyph00003': '2',
'glyph00004': '3',
'glyph00005': '4',
'glyph00006': '5',
'glyph00007': '6',
'glyph00008': '7',
'glyph00009': '8',
'glyph00010': '9'
}
另外由于cmap表是变化的,所以需要在解密时提取,使用fonttools库可以实现:
cmap = font['cmap'].getBestCmap()
返回一个dict,其中key为int型编码,v为glyph名称。整个解密过程为:
1.解析字库库,取得cmap;
2.根据cmap查询字符编码,得到glyph名称;
3.根据GLYF_TABLE查询glyph名称,得到真实字符。
代码有点长就不贴了,已上传到 gayhub ,有兴趣的可以下载看看。
*文章原创作者:manwu91,本文属于FreeBuf原创奖励计划,未经许可禁止转载。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- CSS 字体:字体特性
- iOS 自定义字体设置与系统自带的字体
- ReactNative字体大小不随系统字体大小变化而变化
- 再谈中文字体的子集化与动态创建字体
- 可爱气质的中文字体,字体视界法棍体-开源免费下载
- 深度字体安装器 V1.0 正式发布,打造个性化字体库
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
剑指Offer:名企面试官精讲典型编程题(第2版)
何海涛 / 电子工业出版社 / 2017-5 / 65.00
《剑指Offer:名企面试官精讲典型编程题(第2版)》剖析了80个典型的编程面试题,系统整理基础知识、代码质量、解题思路、优化效率和综合能力这5个面试要点。《剑指Offer:名企面试官精讲典型编程题(第2版)》共分7章,主要包括面试的流程,讨论面试每一环节需要注意的问题;面试需要的基础知识,从编程语言、数据结构及算法三方面总结程序员面试知识点;高质量的代码,讨论影响代码质量的3个要素(规范性、完整......一起来看看 《剑指Offer:名企面试官精讲典型编程题(第2版)》 这本书的介绍吧!