网页抓取 加密html(不少加密与解密方案,你知道吗?(组图))

优采云 发布时间: 2021-10-26 19:05

  网页抓取 加密html(不少加密与解密方案,你知道吗?(组图))

  *文章原创 作者:manwu91,本文属于FreeBuf原创奖励计划,未经许可禁止转载。

  最近看到很多网站使用字体库对数据进行加密,即页面源代码中的数据与显示的数据不同,用户无法直接复制。

  比如七心宝页面的字母和数字,58房产频道的数字:

  

  

  经过对字体库的研究,找到了一种加解密方案。

  加密和准备字体库样本

  笔者在网上随机下载了一个ttf字体库,保存为'origin.ttf',使用fonttools的命令行工具pyftsubset提取需要加密的字符:

  pyftsubset origin.ttf --text='1234567890'

  参数 text 是要提取的字体的字符。操作完成后,会在当前目录生成'origin.subset.ttf'。字库仅收录 10 个字符的 '1234567890'。

  生成加密字体库

  这里使用[]()网站提供的在线服务自定义上一步生成的字体库。首先,将生成的subset.ttf 转换为svg。作者使用的是cloudconvert提供的服务。然后将svg上传到fontello,选择需要自定义的字符,因为我们上传的字体库只收录0到9,所以这里全选,然后在Customize Codes功能下自定义code值。

  

  编码值和字符的关系可以看成是一种映射关系,例如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;

}

  然后定义使用字体的类:

  .demo-icon {

font-family: "fontello";

}

  这样,您只需要在页面标签中添加“demo-icon”类即可。喜欢:

  就是这串数字:{{string}}

  服务器在返回数据之前需要使用 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,有兴趣的可以看看。

  裂缝

  我已经介绍了如何制作加密字体库并在演示项目中使用它来防止数据被捕获。下面介绍破解方法。

  真字型介绍

  我们已经知道字体加密其实就是明文到密文的双向映射,所以只要找到映射表就行了。但是我们在破解的时候只能得到字体库文件,所以需要通过这个文件找到CIPHER_BOOK。这需要对字体库结构有一定的了解。查阅相关文档后,可以简单的将字体绘制过程理解为:

  1. 根据字符的unicode编码找到字形名称(cmap);

  2.根据字形名称查找字形(glyf);

  3.使用字形绘制。

  其中glyph可以理解为字体绘制所需的数据,如点、线等。

  TrueType Font 字体文件收录多个表格。这里需要用到的两张表如下(tag为表名):

  可标记的

  地图

  字符到字形映射

  格莱夫

  字形数据

  根据字体绘制过程,可以猜测实现字体加密有两种方式:

  1.乱码字符编码

  2.打乱字形名称

  笔者将用两个案例来解释这两种情况。

  破解演示

  先在页面上找到字体库的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

  演示中的字体库不会改变,所以写映射表就够了。但分析发现,58置业频道不同页面的字体库不同,字形名称与真实字符不同,需要根据字体库动态处理。

  首先页面中的字体文件是base64编码的,直接解码保存到文件中。

  

  然后用上面的代码转换成ttx文件,查看cmap节点:

  

  通过观察对比发现,字符编码相同,只是字形名称变了,字形名称与实数的关系为:

  glyph_name = 'glyph00d' % (real_num + 1)

  基于此,我们可以还原出字形名称与真实字符的映射表(即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,其中键是 int 类型代码,v 是字形名称。整个解密过程为:

  1.分析字体库获取cmap;

  2. 根据cmap查询字符码,得到字形名称;

  3. 根据GLYF_TABLE查询字形名称,得到真实字符。

  代码有点长,我就不贴了。它已上传到 gayhub。有兴趣的可以下载看看。

  *文章原创 作者:manwu91,本文属于FreeBuf原创奖励计划,未经许可禁止转载。

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线