内容简介:最近看到不少网站都使用了字体库对数据进行加密,即页面源码中的数据与显示出来的数据不同,用户也无法直接进行复制。例如企信宝页面中的字母与数字,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 正式发布,打造个性化字体库
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Types and Programming Languages
Benjamin C. Pierce / The MIT Press / 2002-2-1 / USD 95.00
A type system is a syntactic method for automatically checking the absence of certain erroneous behaviors by classifying program phrases according to the kinds of values they compute. The study of typ......一起来看看 《Types and Programming Languages》 这本书的介绍吧!