首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

从一道CTF题目看Gopher攻击MySql

前言

虽然比赛过程中没做出来,结束后仔细研究了一下。感觉很有意思,分享给大家。再次体会到重要的不是结果,而是研究的过程。

题目简介

34c3CTF web中的extract0r。

题中的目是一个安全解压服务,用户输入zip的url地址,程序对url进行合法性校验后会下载该zip,然后为用户创建一个目录,把文件解压进去

0×00 任意文件读取

经过测试,发现输入的域名中不能含有数字,并且压缩文件中不能含有目录,解压后的目录不解析php。通过上传一个含有符号链接文件的压缩包,可以达到任意文件读取的效果。

上传后访问得到源代码

index.php (html部分已删去)

url.php

简要分析代码流程

经rebirth提醒,可以使用以.开头的文件来绕过中对链接目录的检测。把打包上传即可。这里是因为*遍历不到以.开头的文件。故绕过了对文件类型的检测,成功了链接到了根目录。

翻一翻目录会发现:

这里创建了一个空密码的mysql用户,并且flag就在数据库中。之前已经有利用gopher协议攻击redis、fastcgi等的案例。我们可以试着利用gopher攻击一下mysql。这里有两个要点

绕过ip检查,实现ssrf

研究mysql协议,构造payload

0×01 SSRF

通过代码逻辑我们可知

url->php parse_url(过滤ip)->过滤url各部分(空白字符和数字)->curl发送请求

这里可利用和对url解析的差异来绕过。经过测试,得出以下结论(我本地环境)

那么我们可以构造出一个域名,让php解析出来的host是a.com,dns解析后ip不在黑名单,这样就绕过了黑名单检查。而libcurl实际请求时候是另外一个域名,这样我们就可以实现任意ip请求了。

但此题目中php解析url后在中过滤了空白字符和数字,所以以上url均不可用。

题目作者给出的url是:刚开始不太理解,后来@rebirth告诉我在rfc3986是这样定义url的:

A host identified by an Internet Protocol literal address, version 6 or later, is distinguished by enclosing the IP literal within square brackets (“[" and "]“). This is the only place where square bracket characters are allowed in the URI syntax.

IP-literal = “[" ( IPv6address / IPvFuture ) "]”

也就是说[ip]是一种host的形式,libcurl在解析时候认为[]包裹的是host

另外ricter大佬的在题目环境中是可用的,我本地不可用(题目的libcurl版本比我本地高)

0×02 mysql协议分析

研究的目的是为了构造出gopher连接mysql的payload,mysql协议分为4.0之前和4.0之后两个版本,这里仅讨论4.0之后的协议,mysql交互过程:

MySQL数据库用户认证采用的是挑战/应答的方式,服务器生成该挑战数(scramble)并发送给客户端,客户端用挑战数加密密码后返回相应结果,然后服务器检查是否与预期的结果相同,从而完成用户认证的过程。

登录时需要用服务器发来的scramble加密密码,但是当数据库用户密码为空时,加密后的密文也为空。client给server发的认证包就是相对固定的了。这样就无需交互,可以通过gopher协议来发送。

mysql数据包前需要加一个四字节的包头。前三个字节代表包的长度,第四个字节代表包序,在一次完整的请求/响应交互过程中,用于保证消息顺序的正确,每次客户端发起请求时,序号值都会从0开始计算。

1. 握手初始化报文(服务器 -> 客户端)

具体到抓包数据

2. 认证报文(客户端->服务器)

当用户密码为空时,认证包唯一的变量挑战认证数据为0×00(NULL),所以认证包就是固定的了,不需要根据server发来的初始化包来计算了

这里顺带提一下密码的算法为

3. 命令报文

命令报文相当简单

第一个字节表示当前命令的类型,比如0×02(切换数据库),0×03(SQL查询)后面的参数就是要执行的sql语句了。

4. 验证

经过分析,执行一句sql语句时,发送了两个packet(认证packet、命令packet) ,那么我们把两个packet一起发给server端,server就会响应给我们结果。 packet的构造参见上文协议格式,需要注意的是mysql协议是小端字节序。

这里我用socket做一个简单的测试,使用的是无密码用户,发送的sql语句是

那么在php下,使用libcurl请求也是一样的

php的payload最后加了四个空字节,这是为了让server端解析第三个数据包时出错,断开与我们的连接。尽快返回数据,题目中curl的超时时间是3s

至此,我们完成了从gopher到sql执行。反观题目,这里需要curl得到的响应是可以被解压的。所以我们需要想办法把查出来的数据构造成压缩文件格式。

0×03 压缩文件格式

zip压缩算法压缩出来的文件一般包括四部分。

经过测试,7z是可以成功解压一个格式合法的压缩文件的,即使是文件CRC错误,部分字段异常。

那么思路就来了,利用sql语句构造查询出zip的头和尾部,把我们想要的数据concat到中间的Deflate部分即可。(7z解压时候发现部分header异常,Deflate部分的数据会不经解压直接写入到解压后的文件)

形如

针对zip具体的构造,不在赘述,参见zip算法详解

这里我写了一个函数帮助我们创建

需要注意的是,zip的Deflate部分是保存文件压缩后的内容,zip格式又要求必须给出Deflate部分的大小。这里我们只需把查出数据保存在Deflate部分,并且根据查询结果的预期大小来指定Deflate部分的尺寸。

比如查询时候Deflate大小20就够了。 这里给出一个sql大家可以自行测试

这里的1000就是Deflate数据部分占用大小。 至此我们也就完成了sql语句的构造,可以通过sql查出一个压缩包格式的数据。并且解压后的文件内容就是查询结果。

那么梳理一下,先是通过符号链接,得到了一个没有密码的数据库用户。又通过和的解析差异,绕过了对ip的合法性校验,从而可以实现ssrf任意ip。又通过分析mysql协议,发现空密码用户可以直接构造出packet执行sql语句。最终我们只需要输入就可以得到结果。

0×04 利用

为了方便,我写了一个简单的mysql client,测试与mysql 的通信并生成payload。

输入后:

有兴趣的可以连接自己的mysql,dump出packet

0×05 总结

这道题目融合了很多知识点,测试中还是学到不少东西。尤其是题目脚本中防dns rebindingb部分。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180111B0K0G600?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券