内容简介:最近看到不少网站都使用了字体库对数据进行加密,即页面源码中的数据与显示出来的数据不同,用户也无法直接进行复制。例如企信宝页面中的字母与数字,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 正式发布,打造个性化字体库
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
社交网站界面设计
Christian Crumlish、Erin Malone / 樊旺斌、师蓉 / 机械工业出版社 / 2010-9-1 / 69.00元
《社交网站界面设计》提供100多种模式、原则以及最佳实践,并针对在设计社交网站时经常遇到的问题给出明确建议。本书将提供给你培养用户交互习惯和构建社区最具价值的参考。 本书作者将与你分享难得的经验,教会你平衡各种不同的因素,并与你的用户共同构建和谐健康的网络社区。 本书教会你 掌握创建任何网站时都会用到的原则 学习基本设计模式,以便向现有的网站中添加新的社交组件 学会在......一起来看看 《社交网站界面设计》 这本书的介绍吧!