分类目录归档:心得总结

由 Python 偶现 SSL 错误引发的 TLS cipher 问题探究

近期在用 Python (v3.12.0) 对某 API 接口持续性地调用测试过程中,发现会偶现 SSL 错误:

raise SslHandShakeException("ssl error", reason=e)
somesdk.exception.exceptions.SslHandShakeException: SslHandShakeException - ssl error
reason: SSLError(SSLError(1, '[SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1002)'))

由于拨测是在 Windows 云主机上进行,把脚本拉到本地后在本地重新安装 SDK 等相关 py 环境,发现问题依旧。

此前从同事那里得知 ipv6 网络环境曾出现过不稳定的情况,初步怀疑是是 ipv6 引起此问题,是否在走与不走 ipv6 链路的区别中产生了 “不稳定” 现象。把 Windows 系统的 ipv4/ipv6 分别禁用,排除了这种可能性。祭出 Wireshark 在本地抓包,同时不停地运行 Python 调用 API 脚本发送请求,终于在一段时间测试后发现了问题:API 所在主机有 A 、 B 两个 ipv4 解析结果,在解析结果为 B 时稳定复现此问题,解析结果为 A 时则稳定不出现问题。

由于 Python 的底层运行库提示为 SSL 错误,因此这两个 server 的 TLS 支持情况成为关键。此前从一些网络技术文章可知,对于客户端 TLS 指纹(包括但不限于 client cipher suite 的组合特征)、甚至 HTTP2 指纹的识别早已经应用到脚本、爬虫等 “非人” 客户端的识别封禁等。

调查 Python 客户端与 A 主机 TLS 握手失败的包,发现以下现象:在服务端返回握手失败包之前客户端所发送的 Client Hello 包中,客户端(Python3.12.0 及相关网络库)向服务端提供了以下 18 个加密套件:

Pyhton 3.12.0 提供的 18 个 cipher suite(点击展开)
1TLS_AES_256_GCM_SHA384(0x1302)
2TLS_CHACHA20_POLY1305_SHA256(0x1303)
3TLS_AES_128_GCM_SHA256(0x1301)
4TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xc02c)
5TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(0xc030)
6TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xc02b)
7TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xc02f)
8TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(0xcca9)
9TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256(0xcca8)
10TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384(0xc024)
11TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384(0xc028)
12TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256(0xc023)
13TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256(0xc027)
14TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(0x009f)
15TLS_DHE_RSA_WITH_AES_128_GCM_SHA256(0x009e)
16TLS_DHE_RSA_WITH_AES_256_CBC_SHA256(0x006b)
17TLS_DHE_RSA_WITH_AES_128_CBC_SHA256(0x0067)
18TLS_EMPTY_RENEGOTIATION_INFO_SCSV(0x00ff)
前三个属于 TLS1.3 的加密套件在本次实际交互中不被支持,最后一个 SCSV 不是真正的加密套件,只有剩下的 14 个可用,而这 14 个套件皆为 DHE 或 ECDHE 。
使用 sslscan 工具分别扫描 A 、 B 两台服务端所支持的 cipher 情况(点击展开)
A 支持的 cipher
TLSv1.2128 bits0xC013ECDHE-RSA-AES128-SHA
TLSv1.2256 bits0xC028ECDHE-RSA-AES256-SHA384
TLSv1.2256 bits0x0035AES256-SHA
TLSv1.2128 bits0x002FAES128-SHA
B 支持的 cipher
TLSv1.2256 bits0x0035AES256-SHA
TLSv1.2128 bits0x002FAES128-SHA

可见 Python 3.12.0 这样的 Client 在 TLS 握手时向服务端提供的加密套件中,和 B 没有交集,和 A 有交集 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,HEX 代码为 (0xc028),因此只能和 A TLS 握手成功。

在和其他部门同事复现、调试的过程中,据称同事的 Python3.6 没有这个问题,因此使用了自己另一台电脑的 Python3.9.2 环境进行复测,Python3.9.2 的相关 SSL 库在 TLS 握手 Client Hello 时提供了 43 个 cipher,最终服务端 B 在 Server Hello 中回复商定了 TLS_RSA_WITH_AES_256_CBC_SHA (0x0035) 作为加密套件。

Pyhton 3.9.2 提供的 43 个 cipher suite(点击展开)
1TLS_AES_256_GCM_SHA384(0x1302)
2TLS_CHACHA20_POLY1305_SHA256(0x1303)
3TLS_AES_128_GCM_SHA256(0x1301)
4TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xc02c)
5TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(0xc030)
6TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xc02b)
7TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xc02f)
8TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(0xcca9)
9TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256(0xcca8)
10TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(0x009f)
11TLS_DHE_RSA_WITH_AES_128_GCM_SHA256(0x009e)
12TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256(0xccaa)
13TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8(0xc0af)
14TLS_ECDHE_ECDSA_WITH_AES_256_CCM(0xc0ad)
15TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8(0xc0ae)
16TLS_ECDHE_ECDSA_WITH_AES_128_CCM(0xc0ac)
17TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384(0xc024)
18TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384(0xc028)
19TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256(0xc023)
20TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256(0xc027)
21TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA(0xc00a)
22TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(0xc014)
23TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA(0xc009)
24TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(0xc013)
25TLS_DHE_RSA_WITH_AES_256_CCM_8(0xc0a3)
26TLS_DHE_RSA_WITH_AES_256_CCM(0xc09f)
27TLS_DHE_RSA_WITH_AES_128_CCM_8(0xc0a2)
28TLS_DHE_RSA_WITH_AES_128_CCM(0xc09e)
29TLS_DHE_RSA_WITH_AES_256_CBC_SHA256(0x006b)
30TLS_DHE_RSA_WITH_AES_128_CBC_SHA256(0x0067)
31TLS_DHE_RSA_WITH_AES_256_CBC_SHA(0x0039)
32TLS_DHE_RSA_WITH_AES_128_CBC_SHA(0x0033)
33TLS_RSA_WITH_AES_256_GCM_SHA384(0x009d)
34TLS_RSA_WITH_AES_128_GCM_SHA256(0x009c)
35TLS_RSA_WITH_AES_256_CCM_8(0xc0a1)
36TLS_RSA_WITH_AES_256_CCM(0xc09d)
37TLS_RSA_WITH_AES_128_CCM_8(0xc0a0)
38TLS_RSA_WITH_AES_128_CCM(0xc09c)
39TLS_RSA_WITH_AES_256_CBC_SHA256(0x003d)
40TLS_RSA_WITH_AES_128_CBC_SHA256(0x003c)
41TLS_RSA_WITH_AES_256_CBC_SHA(0x0035)
42TLS_RSA_WITH_AES_128_CBC_SHA(0x002f)
43TLS_EMPTY_RENEGOTIATION_INFO_SCSV(0x00ff)

那么 Python3.12.0 和 Python3.9.2 的 Clinet cipher 到底有什么区别?略一对比可以看出 Python3.12.0 抛弃了相当多的 RSA 相关加密套件,而保留了和 DHE 、 ECDHE 相关的。

TLS1.3 一项重要的改进就是抛弃了 RSA 相关算法而保留了 Diffie–Hellman(DH)作为唯一的密钥交换算法。因为前者不具有前向安全性(Forward Secrecy,简称 FS),简单说,把 https 密文保存下来后,即使在未来私钥泄露的情况下,也无法解开所保存的密文。

Python3.10 的更新日志 中有一条:
The ssl module now has more secure default settings. Ciphers without forward secrecy or SHA-1 MAC are disabled by default. Security level 2 prohibits weak RSA, DH, and ECC keys with less than 112 bits of security. SSLContext defaults to minimum protocol version TLS 1.2. Settings are based on Hynek Schlawack’s research. (Contributed by Christian Heimes in bpo-43998.)
其思路和 TLS1.3 抛弃 RSA 比较一致,把不支持前向安全的加密套件废除以提高安全性。

最终服务端 B 包括域名的 v6 地址所接受的 cipher 与 A 拉齐,问题得到解决。一个看似简单又有点奇怪的 “SSL 偶现报错” 问题原来与 DNS 解析负载均衡、新版 Python 严格的 TLS 加密套件策略等一系列原因有关。

身临其境 CISP-PTE

最近报名参加了公司集体组织的 CISP-PTE 学习考证。 CISP 是纯理论考试,CISP-PTE 和 CISP-PTS 则都是实操,后者的难度层次更高。这俩实操型考试的形式非常类似于 CTF 夺旗赛,侦测漏洞后利用工具渗透进测试靶机系统取得 key(对应 CTF 比赛中的 flag)为得分点。 PTE 有 20 道 1 分理论题,5 道一步拿 key 的 “小题”(10 分)和 1 道需由浅入深按步骤获取 3 个 key 的综合 “大题”(每个 key 10 分共计 30 分),70 分为及格线。今年(2023 年)考试时间由之前的 4 小时缩短为 3 小时,并且不允许去厕所,难道之前厕所 py 的现象太过于严重了?

理论问答题比较基础、简单,事先认真看一遍复习题大约能获得 85% 的正确率。但也有一些只能靠死记硬背或者因工作涉及而有着条件反射般的记忆,否则几乎无法现场推测答案——比如问你 Windows 登录密码错误会在产生何种编号的事件?……

考试时是用浑身长满眼睛的特制 “安全” 浏览器以 vnc 的形式登录到考试提供的攻击机环境——安装了扫描(如 nmap 、御剑目录爆破等)、抓包(经典 burpsuite)、注入(SQLmap)还有字典、转码等等很多工具的 Win7 操作系统,靶机和攻击机处于简单的同一 LAN 下,这就为 “反弹 shell” 、 “用 img src 储存型 xss 抓 cookie” 、 “远程文件包含” 等方法提供了网络环境支持。考试全程中攻击机和靶机都不通公网,且考生的操作机也不能以任何形式暂离考试专用浏览器。

第一道小题为 SQL 注入考察,以寻常的登录框起手,此处直接用 SQLmap 可以一步以时间延迟跑出结果,key 藏在文件系统(题目会告诉具体路径)而非数据库中,对于手工做题来说免去了从 information_schema 库中查询结构,上面提到的靶机和物理机间的网络环境简单且稳定,也为 SQLmap 以二分法+ sleep() 函数逐个去试字母提供了极大的有利条件。据其他考生说以 admin’ or ‘1’=’1 万能账密登入后在搜索框也有注入点,将会利用到课上讲过的 union select 联合查询注入的方法,以查询文章列表为例,假设注入点为 www.example.com/article.php?id=1

  • 先用 order by 试出注入点所查询的数据表列数:1' order by 5#(从 1 慢慢往上加,直到页面无任何显示即后台查询出错为止,出错前最后一个数字就是列数)
  • 再判断这些列中有哪些是展示在页面上的 -1' union select 1,2,3,4,5# 如果页面上显示 2 、 4 、 5 那么则只有这些对应的列在查询后被使用(显示出来),-1 则是故意传递的非法值以便查询不到数据 “腾空” 页面。
  • 确定了哪些列会被展示在页面上后,就可以使用 -1' union select 1,2,3,group_concat(select database()),5# 这样的语句来显示出数据库的信息,MySQL 的表和列信息都可以从元数据库 information_schemaselect 取得,就算是文件也可以用 select load_file() 读出。

第二道小题(可能非考试顺序)为命令注入,此题非常像 dvwa 练习场中的 Command Execution,给出目标主机 IP 地址填写框,执行后会调用系统 ping 命令来探测目标主机。那么加入 && || ; 等分隔符都有可能造成 ping 以外的命令被执行。做练习题时发现此类题狗的地方在于底层会屏蔽一些读取 key 文件的关键命令如 cat ls 甚至可能包括空格,要知道一些绕过的 trick 如 tac 、 ca''t 、 ${IFS}代替空格等。

第三小题是文件上传考察,利用 php 遇到合法扩展名文件中 <?php …… ?> 就会执行其中代码的特性,在本该上传图片的靶机上传点中传入内含 php 代码的 “假图片” 。考点在于绕过各种上传限制,可能的限制有:扩展名、 MIME 、文件头等等中的一个或多个,由于是考试所以一定至少有一种可用的方法。找到绕过方法后往 POST body 图片源码中写入早已要求熟记的一句话木马 <?php eval($_REQUEST['cmd']); ?> 后可以直接传入 php 的 system() 方法执行系统命令或使用 webshell 管理工具拿到靶机站点权限。这里如果遇到过滤(如精确匹配 eval),和上述命令执行题一样用一些方法去变形绕过,如 $a = 'ass'.'ert'; ,想来也是 webshell 免杀的一些套路吧。接着直接去找 key 就成了。

第四小题是 php 代码审计,一般不会太过于复杂,但对于 php 的常见函数方法等还是得有所了解。这里遇到了一句代码关键点是: eval("\$o=strtolower(\"$a\");"); ,那么传入参数 a 的时候把前后语句闭合掉,让中间的代码传到 eval 那里执行即可, 如 ");system('pwd');(" ,果然填空游戏啊。

第五题是访问控制题,考察一些 http 头的使用方法,如 X-Forwarded-For 绕过 IP 限制,cookie 中的布尔值绕过权限检查等,很简单,再难也不过涉及一些 base64 编码。但一定程度上有助于初学者理解服务器是怎么 “消化” 客户端所提交的东西的吧。

综合大题,从以往的一些考题来看基本上就和综合渗透比较相似了,但也不会过于挖坑。一般考试题会提供一个独立 IP 地址的仿真站点,用 nmap 可以扫出一些如 ftp 、 http 、 mysql 端口等,从哪里入手就要看扫描结果和个人经验了。考试中 web 服务的登录框比较无解,用目录扫描扫出 phpMyAdmin,使用默认密码直接进去了数据库管理端,在数据库角落获取了 key1;之后进入 user 表修改管理员密码(利用攻击机内工具计算 hash 值填入),再从 web 登入,此后既可以用上传图片马的方法,也可以用 MySQL 执行 into outfile () 方法向 Web 目录写入木马,因为担心后续步骤较多,这里使用攻击机提供的蚁剑连上 webshell,在 web 目录下发现 key2 。做到这一步时我应该是在写图片马时手抖写错,使 php 报错时爆出了 Windows 下 Web 服务的绝对路径(也是获得绝对路径的技巧之一),这才为 SQL 写马提供了可能(SQL 写马需配置支持和知道绝对路径)。使用攻击机工具集已有的 3389 强制打开工具(看了下,实质是写注册表),再用 net user 命令修改 Administrator 的密码,mstsc 登录后在回收站中发现 key3 。

综上所述题目难度都不算大,多刷一些过往考试题,多了解一些 bypass 方法都对通过考试很有裨益。但我觉得最大的收货还是在于明白了一些原理层面的东西,而这些东西,可能平时往往因为手头 “差生文具多” 般眼花缭乱的工具而最容易使人忽略的——漏洞和问题的本质。这些本质性的东西可不会管你是什么最好的语言还是冒着热气的语言的。懂得了原理,是脚本小子还是手搓 poc 其实又有什么关系呢。