记一次不停自我追问的学习(下)——有趣的探索之旅

  • A+
所属分类:安全文章

记一次不停自我追问的学习(下)——有趣的探索之旅


记一次不停自我追问的学习(下)——有趣的探索之旅


Goby社区第17 篇技术分享文章

全文共:5551    预计阅读时间:14 分钟


前言:

上篇>>一个文件上传 1day 的 PoC 编写,从简单的 GUI 编写,不满足于是选择用 Go 语言编写,再到逐个使用 Goby 自带 API 优化 PoC,最终实现一键反弹 shell。不仅学习了 Go,也对 EXP 进一步的完善。

下篇>>代码审计,从一个为什么产生出发,不断地问自己问题,虽然是一次简单的代审,延伸出一次溯源,最后的收获远不止一次代码审计,而是学习方法!




记一次不停自我追问的学习(下)——有趣的探索之旅 01

代码审计 TL;DR
  • 为什么文件名后缀使用.<>php就可以绕过?

  • 为什么我尝试.<php的后缀绕过方式不行?

  • 为什么有这种神奇的绕过方法?

  • 什么原因导致的?

十万个为什么,最终促使了我进行代码审计!

不会代码审计,只能一步步的断点跟踪分析,笨办法

1.1 showdoc 如何限制文件上传的

1.1.1 黑名单检测

检测文件名中是否包含 php 字样

strstr(strtolower($_FILES['editormd-image-file']['name']), ".php")

记一次不停自我追问的学习(下)——有趣的探索之旅

1.1.2 白名单检测

检测文件后缀名是否在 upload 类的$ext数组白名单中。

return empty($this->config['exts']) ? true : in_array(strtolower($ext), $this->exts);

记一次不停自我追问的学习(下)——有趣的探索之旅

1.1.3 图片内容检测

仅对图像文件进行进一步检测,其他类型则放行(有些鸡肋)

记一次不停自我追问的学习(下)——有趣的探索之旅

1.2 绕过方法一:畸形后缀绕过

1.2.1 绕过方式

文件名为:test.<>php

1.2.2 原理

.<>php的方式绕过了黑名单对.php检测

而在之后的 showdocserverThinkPHPLibraryThinkUpload.class.php 的upload()函数中对文件名使用了strip_tags()函数进行处理,去掉了<>标签,还原后缀名为 php。

记一次不停自我追问的学习(下)——有趣的探索之旅

1.2.3 修复方案

在进行黑名单检测时,先用strip_tags()函数对输入文件名进行处理(有趣的是我在另外一个路径下的同名文件中发现了这种修复方案):

//漏洞版本:showdocserverApplicationHomeControllerPageController.class.php
strstr(strtolower($_FILES['editormd-image-file']['name']), ".php")
//另外一个路径下的修复版本:showdocserverApplicationApiControllerPageController.class.php
strstr(strip_tags(strtolower($_FILES['editormd-image-file']['name'])), ".php")

1.2.4 追问:不是后面还会进行白名单检测吗?

重头戏,这个引发后续一系列问题

虽然前面绕过了黑名单检测,但是后面还有白名单检测,那么这里怎么绕过的呢?

赋值问题:后缀的白名单数组赋值给了 upload 类中的allowExts变量:$upload->allowExts = array('jpg', 'gif', 'png', 'jpeg'); ,但是检测文件名后缀的时候使用 upload 类中的exts变量:$upload['exts']来检测,很明显白名单数组赋值错了,导致 exts 变量为空,故绕过了后缀名的 check。

这是开发者在开发时疏漏导致的白名单检测缺陷。

记一次不停自我追问的学习(下)——有趣的探索之旅

1.3 绕过方法二:修改输入名称

1.3.1 追问:绕过方式

将文件输入名称改为editormd-image-file1,如name="editormd-image-file1"; filename="test.php"(绕过了黑名单的检测,同时结合白名单的缺陷,以实现目的)

1.3.2 原理

因为他只检测输入名为editormd-image-file文件的文件名是否包含 php,修改对应的输入名即可。举一反三:这也解释了为什么其他场景的文件上传中可以通过修改这个字段名称的方式绕过了文件上传限制

strstr(strtolower($_FILES['editormd-image-file']['name']), ".php")

1.3.3 缺陷

虽然在文件夹中看到文件成功上传,但是返回的文件路径中没有文件名

查看源码后发现:是因为在返回的文件名中又一次使用到了editormd-image-file,并通过这个名来获取文件保存路径,由于我们更改了输入名,并不存在editormd-image-file的键值,故返回了文件名为空。

记一次不停自我追问的学习(下)——有趣的探索之旅

记一次不停自我追问的学习(下)——有趣的探索之旅

1.3.4 进一步绕过以获取文件名

虽然没有返回文件名,但是文件是上传成功的,那么如何知道上传后的文件名?

跟踪源码,发现文件命名调用了uniqid()函数,而该函数的定义是基于以微秒计的当前时间,生成一个唯一的 ID,那么在极短的时间内发送两个包,那么文件名应该是相近的。

方法一:第一个正常上传返回路径,第二个修改输入名不返回路径,依据时间递增遍历即可找到,但是爆破不知上限有些盲目。

方法二:php 文件在两个包之间,对应文件名也在两个包时间之间。如下图,需要爆破5位数

记一次不停自我追问的学习(下)——有趣的探索之旅

1.3.5 更进一步以缩短爆破时间

通过查看 thinkphp 文档后发现,其支持多文件上传。

http://document.thinkphp.cn/manual_3_2.html#upload

测试后多文件上传后,发现文件处理时间间隔更近,命名更相似,3 位数,对应爆破次数不超过 4000 次,效率更高。

记一次不停自我追问的学习(下)——有趣的探索之旅

1.4 修复方案

修复白名单赋值导致的缺陷即可:将$upload->allowExts= array('jpg', 'gif', 'png', 'jpeg');对应代码更改为$upload->exts= array('jpg', 'gif', 'png', 'jpeg');

https://github.com/star7th/showdoc/commit/189b6cedc011a0d2758f4207cb85c565372093dd

记一次不停自我追问的学习(下)——有趣的探索之旅

1.5 其他失败尝试

1.5.1 直接上传 php?

原因:最早跟踪<>的漏洞成因,发现是白名单后缀检测失效导致的,既然白名单失效,为什么不考虑直接上传 php

过程:测试失败,然后下断点跟踪代码。

收获:发现了其还有一层黑名单验证机制

记一次不停自我追问的学习(下)——有趣的探索之旅

1.5.2 strlower 绕过?Unicode 字符?

原因:最近打 CISCN2021 的一道 upload 题目,遇到在strtolower进行黑名单匹配时可通过 unicode 绕过

过程:仔细查看后发现我记错了,CTF 中题目是mb_strtolower,而此处是strtolower

收获:strtolowermb_strtolower理解更深

1.5.3 .htaccess 可以吗?

背景:黑名单只限制 php,phtml 可以上传,但无法被解析,那么可以上传.htaccess使之支持解析吗?

过程:测试,发现上传的.htaccess会被重命名为 “60d150f6ee711.htaccess”

结局:不再发散,点到为止



记一次不停自我追问的学习(下)——有趣的探索之旅 02

有趣的溯源

2.1 漏洞起源于 CTF

1. plzmyy 师傅最早通报这个漏洞给 showdoc 官方在 2020 年的八月

2. plzmyy 师傅提到了最早出现在 RoarCTF2019 的赛事中的 simple_upload 题目 Ethan 师傅的 WP

https://www.fuzzer.xyz/2019/10/14/RoarCTF2019

3. Ethan 师傅发现<>绕过方法是 fuzz 出来这个非预期解,而查看绝大部分 WP 以及疑似官方的 WP 中介绍的是利用 uniqid() 的函数可爆破的弱点。

https://github.com/berTrAM888/RoarCTF-Writeup-some-Source-Code/tree/master/Web/simple_upload/writeup

4. Ethan 师傅文章中提到疑似 0day,引起了我的兴趣,我想分析到底是哪个 day?

2.2 有0day?

1. 检索相关关键词,没有发现 0day,而且到现在都没有爆出来,离谱!更加吸引了我的兴趣

2. 搜索引擎限定搜索时间为 19 年 10 月 RoarCTF 开赛之前,发现一个 14 的《ThinkPHP文件上传实例教程》一个 15 年的《ThinkPHP文件上传的实例代码》网页介绍了如何使用 ThinkPHP 文件上传 ,其使用的方法就是$upload->allowExts错误的写法,而 ThinkPHP 官方 3.2 版本的说明文档中则使用的是正确的$upload->exts写法

《ThinkPHP文件上传实例教程》:https://www.jb51.net/article/54209.htm

《ThinkPHP文件上传的实例代码》:http://www.splaybow.com/post/thinkphp-file-upload-sample.html

3. 那么为什么 ThinkPHP 官方是对的,而民间大家用的是这种错误的写法?会不会是官方早期版本教学用的就是这个错误的写法

2.3 官方自相矛盾?

1. 怀疑是 ThinkPHP3.2 以下版本的官方文档自身用了错误的写法,而民间只是历史沿用这一用法,搜了 ThinkPHP3.1 的说明文档,果不其然用的就是$upload->allowExts的错误方式

http://www.thinkphp.cn/info/194.html

2. 那么 TP3.2 官方为什么在文档中更正了这种错误的写法?是因为当时爆出了漏洞吗?检索后发现并没有这类漏洞

3. 继续追踪,查看 3.1 和 3.2 官方源码发现:是在TP从3.1到3.2版本升级的时候,由于编写文件上传类的负责人换了一个liu21stzuojiazi,使得 ① 类名UploadFile()Upload() ② 白名单后缀数组名allowExtsext ③ 文件名,文件路径等发生变更。

4. 仔细对比 3.1 和 3.2 的文档,发现虽然源码发生改变,对应文档也发生了改变,本质上并不存在漏洞。

5. 也就说 ①3.1 版本的源码配合 3.1 的文档,$upload = new UploadFile()搭配$upload->allowExts不会产生漏洞;② 3.2版本的源码配合 3.2 的文档,$upload = new ThinkUpload()搭配$upload->exts也不会发生漏洞。③ 但是 3.2 的版本使用3.1的文档,$upload = new ThinkUpload()搭配$upload->allowExts就会产生漏洞,而showdoc的漏洞成因很大几率来源于此。

6. 再次百度、Google 相关教程网页,发现民间教程也都和官方文档一样,一一对应(刚刚只关注键名没关住类名了)。

7. 也就说官方教程不存在问题,网上教程也不存在问题,那么只能是个人了。

记一次不停自我追问的学习(下)——有趣的探索之旅记一次不停自我追问的学习(下)——有趣的探索之旅

2.4 个人问题?

1. 再往前追:2016 年 8 月 showdocV1.0.0 版本就存在这种错误的写法,但是 14 年 TP3.2 就出现了

https://github.com/star7th/showdoc/blob/v1.0.0/Application/Home/Controller/PageController.class.php

2. 由于作品发布前后间隔两年,首先怀疑作者是基于一个通用模板或 CMS 改的,但是检索了半天开源框架并没有。

3. 其次只能怀疑是面向百度编程的通病:① 百度内容 ② 发现官网文档 ③ 发现 demo,但是运行失败 ③ 魔改一番,手动将类名由 uploadfile 改为 upload ⑤ 程序运行成功 ⑥ 不影响正常业务 ⑦ 大功告成!

4. 我按照这个步骤复现之后发现很类似,最后和作者取得联系,和怀疑的基本差不多,

作者回复如下:

那块代码应该是从某个网页上拷贝下来的。可能是 thinkphp 官网。这种语法我是不会花精力去记住的,因为它跟框架强相关。我是多个框架使用者,我用某个框架的时候,我才会去搜索它的语法。所以我一开始其实并不知道 exts 和 allowexts 的区别。

记一次不停自我追问的学习(下)——有趣的探索之旅

2.5 时间线

2013年6月7日:ThinkPHP3.1.3 发布,使用UploadFile()->allowExts

2014年2月3日 :ThinkPHP3.2 发布,使用Upload()->exts

2016年8月7日 :showdocV1.0.0 发布 ,使用UploadFile()->exts

2019年10月12日:RoarCTF 开赛,并于14日 Ethan 师傅 Fuzz 出了这个点,但未深究

2020年8月12日 :plzmyy 师傅根据 Ethan 师傅的 WP 发现 showDoc 的漏洞

2.6 小结

溯源到最后,可以说 showdoc 是个特例,是由于开发者的一时疏忽导致的。诚然在这个里面个人开发者占很大的问题,但也希望给厂商企业敲响一个警钟,在版本升级时不仅要注意文档和代码的对应性,也需要在更改类的字段名时候慎重考虑。

对于 Ethan 师傅也有点可惜,他觉得是个 0day,但未深究跟踪一下代码,也许他就真的发现了一个 0day。

对于CTFer来说,也需要多看WP,也许能收获到不同的东西。

对于面向百度编程的我,也需要警惕不要做一个盲目 CV 的代码首席移动工程师。



记一次不停自我追问的学习(下)——有趣的探索之旅 03

总结

正如 Zwell 说的“不断问自己问题”的学习方法,记录下问题解决它,虽然是一次简单漏洞复现 EXP 编写和代码审计,但是随着不停的问自己问题,好奇心吸引着我,到最后不知不觉我发现我的总结上升到了一个新的高度,共勉!

Go 编写 PoC,再到 API 优化 PoC,最终实现反弹 shell,详见上篇




记一次不停自我追问的学习(下)——有趣的探索之旅



最新Goby使用技巧分享

• zzlyzq | 利用Goby发现企业云网络中的安全隐患

• zhzyker | 如何编写合格的 PoC 领取 Goby 红队专版

• HuaiNian | Json 编写 PoC&EXP 遇到的那些坑

• PeiQi | 快速上手 Golang 编写 PoC&EXP

• Corp0ra1 | 记一次不停的自我追问式学习(上)

更多 >>  打野手册


如果表哥/表姐也想把自己上交给社区(Goby 介绍/扫描/口令爆破/漏洞利用/插件开发/ PoC 编写等文章均可记一次不停自我追问的学习(下)——有趣的探索之旅,欢迎投稿到我们公众号,红队专版等着你们~~~


记一次不停自我追问的学习(下)——有趣的探索之旅

本文始发于微信公众号(GobySec):记一次不停自我追问的学习(下)——有趣的探索之旅

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: