现代cms框架(laraval/symfony/slim)的出现,导致现今的php漏洞出现点、原理、利用方法,发生了一些变化,这个系列希望可以总结一下自己挖掘的此类cms漏洞。
今天这个漏洞是SRCMS 里由于Model的不合理使用造成的多处越权漏洞。
首先,我简要说明一下漏洞原理。
【漏洞源码下载: https://mega.nz/#!4UxCTaxJ!DpVvhBPK7YE9D_jdFQ0CjQ1ylJ4sQws-CT3LIJ5AA8Y 】
今天在 http://zone.wooyun.org/content/25144 里看到SRCMS更新了,其实之前一段时间就star了这套源码,但一直没看。因为我要写这个系列(现代框架系列),所以这次我下载源码进行了阅读。
在挖漏洞之前,先表示我对代码作者的感谢,感谢作者贡献了一套优秀的代码。漏洞是所有程序在所难免的,所以不必有什么心理压力。
SRCMS是一个 开源的企业安全应急响应中心, 基于ThinkPHP框架开发。我将ThinkPHP归为半现代框架,因为它在近些年的发展中已经越来越多地引入现代化的一些元素(命名空间、ORM等)。当然也由于这些元素的引入,将会造成一些以前写代码不会遇到的安全问题。
这里这个漏洞就是因为SRCMS不合理使用ORM(或者说是框架的Model),造成了越权。
0x01 ThinkPHP特性造成的越权漏洞
首先说一个半传统的漏洞,这个漏洞是由ThinkPHP特性造成的。/Application/User/Controller/PostController.class.php:77
/***查看漏洞报告*/public function view(){ $id = session('userId'); $rid = I('get.rid',0,'intval'); $model = M("Post"); $post = $model->where('user_id='.$id)->where('id='.$rid)->find(); $tmodel= M('setting'); $title = $tmodel->where('id=1')->select(); $this->assign('title', $title); $this->assign('model', $post); $this->display();}
view函数存在一个越权查看他人漏洞报告的漏洞,为什么?他这里明明有 where('user_id='.$id) ,查询了user_id的呀?
这里还是涉及到thinkphp一个常见错误(特性)。很多开发者没好好看文档,原因其实文档里已经明确说明了:
虽说ThinkPHP支持where函数的多次调用。但如果条件是字符串的话,就只能出现一次,如果出现多次的话,将只取最后一个。查看SQL日志,可以发现果然没有where user_id这个条件。
第一个越权漏洞产生:『查看任意漏洞』, http://localhost/srcms/user.php?m=User&c=post&a=view&rid=1
修复方法吧,我觉得还是得将字符串型where条件换成数组型。不知道这个开发者是为了挑战黑客还是为了体验ThinkPHP的功能,很多地方专门使用字符型拼接作为where的参数(虽然不存在SQL注入漏洞),这样我觉得是不合适的。由于作者使用的方法依旧是字符串拼接,所以我认为这个漏洞不能算『新型PHP安全漏洞』,只能说是在框架架构下产生的传统漏洞。
0x02 Model误用造成的越权漏洞
那么来个真正的新型php安全漏洞吧。
这个问题点造成的漏洞有很多处,我就以『更新联系方式』这个功能来讲解。
/Application/User/Controller/InfoController.class.php:56
public function update(){ //默认显示添加表单 $id = session('userId'); if (!IS_POST) { $info = M('info')->where('user_id='.$id)->select(); $this->assign('info',$info); $this->display(); } if (IS_POST) { //如果用户提交数据 $model = D("info"); $model->user_id = 1; $model->username = 1; if (!$model->create()) { // 如果创建失败 表示验证没有通过 输出错误提示信息 $this->error($model->getError()); exit(); } else { if ($model->save()) { $this->success("更新成功", U('info/index')); } else { $this->error("更新失败"); } } }}
这里作者使用了一种类似于ORM的数据库更新手段,来自动获取表单中的数据create(),并更新进数据库save()。我们查看create函数:
public function create($data='',$type='') { // 如果没有传值默认取POST数据 if(empty($data)) { $data = I('post.'); }elseif(is_object($data)){ $data = get_object_vars($data); } // 验证数据 if(empty($data) || !is_array($data)) { $this->error = L('_DATA_TYPE_INVALID_'); return false; } // 状态 $type = $type?:(!empty($data[$this->getPk()])?self::MODEL_UPDATE:self::MODEL_INSERT); ... // 创建完成对数据进行自动处理 $this->autoOperation($data,$type); // 赋值当前数据对象 $this->data = $data; // 返回创建的数据以供其他调用 return $data;}
加入第一个参数$data为空,则自己从 I('post.') 获取,实际上就是从POST数据中读取。
然后中间经过了很多处理,传入autoOperation方法,我们跟进一下autoOperation方法:
private function autoOperation(&$data,$type) { if(!empty($this->options['auto'])) { $_auto = $this->options['auto']; unset($this->options['auto']); }elseif(!empty($this->_auto)){ $_auto = $this->_auto; } // 自动填充 if(isset($_auto)) { foreach ($_auto as $auto){ // 填充因子定义格式 // array('field','填充内容','填充条件','附加规则',[额外参数]) if(empty($auto[2])) $auto[2] = self::MODEL_INSERT; // 默认为新增的时候自动填充 if( $type == $auto[2] || $auto[2] == self::MODEL_BOTH) { if(empty($auto[3])) $auto[3] = 'string'; switch(trim($auto[3])) { case 'function': // 使用函数进行填充 字段的值作为参数 case 'callback': // 使用回调方法 $args = isset($auto[4])?(array)$auto[4]:array(); if(isset($data[$auto[0]])) { array_unshift($args,$data[$auto[0]]); } if('function'==$auto[3]) { $data[$auto[0]] = call_user_func_array($auto[1], $args); }else{ $data[$auto[0]] = call_user_func_array(array(&$this,$auto[1]), $args); } break; case 'field': // 用其它字段的值进行填充 $data[$auto[0]] = $data[$auto[1]]; break; case 'ignore': // 为空忽略 if($auto[1]===$data[$auto[0]]) unset($data[$auto[0]]); break; case 'string': default: // 默认作为字符串填充 $data[$auto[0]] = $auto[1]; } if(isset($data[$auto[0]]) && false === $data[$auto[0]] ) unset($data[$auto[0]]); } } } return $data;}
这是个自动填入条件的方法,依据的的是具体model里_auto这个数组的值来确定,我们看看InfoModel.class.php里是怎么定义的:
<?phpnamespace User\Model;use Think\Model;class InfoModel extends Model{ protected $_validate = array( array('realname','require','请填写真实姓名'), //默认情况下用正则进行验证 array('zipcode','require','请填写邮编'), //默认情况下用正则进行验证 array('location','require','请填写地址'), //默认情况下用正则进行验证 array('tel','require','请填写联系电话'), //默认情况下用正则进行验证 array('alipay','require','请填写支付宝账号,方便发放现金奖励'), //默认情况下用正则进行验证 ); protected $_auto = array ( array('user_id','getUid',1,'callback'), // 对update_time字段在更新的时候写入当前用户ID array('username','getUsername',1,'callback'), // 对update_time字段在更新的时候写入当前用户名 ); protected function getUid(){ return session('userId'); } protected function getUsername(){ return session('username'); }}
可见,这里的user_id绑定了回调函数getUid,而getUid返回的正是当前用户的user_id。
感觉一切都没有错误啊?我们仔细观察这个_auto的第三个参数:1
我们看到第三个参数的定义:
1代表的是insert,只有在insert的时候才进行处理。而假设我们传入的POST中有user_id (getPk),ThinkPHP会自动判断当前type为update,也就是2:
// 状态$type = $type?:(!empty($data[$this->getPk()])?self::MODEL_UPDATE:self::MODEL_INSERT);
所以在autoOperation方法里,在这个if语句的时候就卡主了,进不去:
//type是update(2),但$auto[2]却是 1, $type==$auto[2]不成立if( $type == $auto[2] || $auto[2] == self::MODEL_BOTH) {
因为这个if语句进不去,所以实际上user_id没有被程序覆盖掉,我通过POST传入的user_id被保留了。于是,我就可以越权修改任意用户的联系方式。上诉的分析都是我在YY,现在验证一下。首先注册一个test用户(user_id为4),增加联系方式如下:
其他用户,登录以后发送如下数据包即可修改user_id=4的联系方式,包括手机号、支付宝等:
返回test用户的页面,发现已经被改:
这就是一个典型的新型框架的越权漏洞,因为不熟悉框架,在使用框架提供的『新式方法』时,造成了错误。
0x03 横向挖掘 —— 权限提升漏洞(可获取管理员权限)
既然我们这个漏洞是开发者对框架不熟悉造成的,属于是『架构漏洞』,那么srcms里可能并非一处地方存在此漏洞。
存在此类漏洞需要有这个条件:Model中设置的类型与实际执行的SQL类型不同。比如这个漏洞是设置的insert,实际执行的是update。
但我看了下前台,前台大部分地方都是调用的add()进行insert,都能够对上。暂时没发现其他地方存在这个问题,后台我就不看了。
等下,insert?看到这个的时候,我就想到下一步利用方法了。我们看到注册时候的代码:
public function add(){ //默认显示添加表单 if (!IS_POST) { $this->display(); } if (IS_POST) { //如果用户提交数据 $model = D("Member"); if (!$model->create()) { // 如果创建失败 表示验证没有通过 输出错误提示信息 $this->error($model->getError()); exit(); } else { if ($model->add()) { $this->success("用户添加成功", U('index/index')); } else { $this->error("用户添加失败"); } } }}
过程也是create()以后add(),再看到member.class.php
<?phpnamespace User\Model;use Think\Model;class MemberModel extends Model{ protected $_validate = array( array('username','require','请填写用户名!'), //默认情况下用正则进行验证 array('email','require','请填写邮箱!'), //默认情况下用正则进行验证 array('email','email','邮箱格式错误!'), //默认情况下用正则进行验证 array('password','require','请填写密码!','','',self::MODEL_INSERT), //默认情况下用正则进行验证 array('repassword','password','确认密码不正确',0,'confirm'), // 验证确认密码是否和密码一致 array('username','','用户名已存在!',0,'unique',self::MODEL_BOTH), // 在新增的时候验证name字段是否唯一 array('email','','邮箱已存在!',0,'unique',self::MODEL_BOTH), // 在新增的时候验证name字段是否唯一 ); protected $_auto = array( array('password','md5',1,'function') , //添加时用md5函数处理 array('update_at','time',2,'function'), //更新时 array('create_at','time',1,'function'), //新增时 array('login_ip','get_client_ip',3,'function'), //新增时 );}
可见,并没有对type进行设置,也就是说,我只要传入的POST数据里设置一下我自己的type=2,即可注册一个管理员。测试一下:
成功用hacker登陆后台:
前台也可以看到其积分是99999:
后台漏洞,我就不挖了,涉及不到新型CMS的漏洞挖掘了。

phpsessionstrackuserdataacrossmultiplepagerequestsusingauniqueIdStoredInacookie.here'showtomanageThemeffectionaly:1)startAsessionWithSessionwwithSession_start()和stordoredAtain $ _session.2)

在PHP中,遍历会话数据可以通过以下步骤实现:1.使用session_start()启动会话。2.通过foreach循环遍历$_SESSION数组中的所有键值对。3.处理复杂数据结构时,使用is_array()或is_object()函数,并用print_r()输出详细信息。4.优化遍历时,可采用分页处理,避免一次性处理大量数据。这将帮助你在实际项目中更有效地管理和使用PHP会话数据。

会话通过服务器端的状态管理机制实现用户认证。1)会话创建并生成唯一ID,2)ID通过cookies传递,3)服务器存储并通过ID访问会话数据,4)实现用户认证和状态管理,提升应用安全性和用户体验。

Tostoreauser'snameinaPHPsession,startthesessionwithsession_start(),thenassignthenameto$_SESSION['username'].1)Usesession_start()toinitializethesession.2)Assigntheuser'snameto$_SESSION['username'].Thisallowsyoutoaccessthenameacrossmultiplepages,enhanc

PHPSession失效的原因包括配置错误、Cookie问题和Session过期。1.配置错误:检查并设置正确的session.save_path。2.Cookie问题:确保Cookie设置正确。3.Session过期:调整session.gc_maxlifetime值以延长会话时间。

在PHP中调试会话问题的方法包括:1.检查会话是否正确启动;2.验证会话ID的传递;3.检查会话数据的存储和读取;4.查看服务器配置。通过输出会话ID和数据、查看会话文件内容等方法,可以有效诊断和解决会话相关的问题。

多次调用session_start()会导致警告信息和可能的数据覆盖。1)PHP会发出警告,提示session已启动。2)可能导致session数据意外覆盖。3)使用session_status()检查session状态,避免重复调用。

在PHP中配置会话生命周期可以通过设置session.gc_maxlifetime和session.cookie_lifetime来实现。1)session.gc_maxlifetime控制服务器端会话数据的存活时间,2)session.cookie_lifetime控制客户端cookie的生命周期,设置为0时cookie在浏览器关闭时过期。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

mPDF
mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

SecLists
SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

VSCode Windows 64位 下载
微软推出的免费、功能强大的一款IDE编辑器

SublimeText3汉化版
中文版,非常好用

WebStorm Mac版
好用的JavaScript开发工具