最新の cms フレームワーク (laraval/symfony/slim) の登場により、今日の PHP 脆弱性の発生箇所、原理、利用方法にいくつかの変化が生じています。このシリーズでそれをお伝えできれば幸いです。このような cms の脆弱性がマイニングされました。
今日の脆弱性は、Model の不当な使用によって引き起こされる、SRCMS における複数の権限侵害の脆弱性です。
まず、脆弱性の原理を簡単に説明します。
[脆弱性ソース コードをダウンロード: https://mega.nz/#!4UxCTaxJ!DpVvhBPK7YE9D_jdFQ0CjQ1ylJ4sQws-CT3LIJ5AA8Y]
今日 http://zone.wooyun .org /content/25144 SRCMS が更新されたことを確認しました。実は、少し前にこのソース コードにスターを付けましたが、まだ見ていません。このシリーズ(モダンフレームワークシリーズ)を書きたいので、今回はソースコードをダウンロードして読んでみました。
抜け穴を探す前に、優れたコード セットを提供してくれたコードの作成者に感謝の意を表したいと思います。脆弱性はすべてのプログラムに避けられないため、心理的なプレッシャーを感じる必要はありません。
SRCMS は、ThinkPHP フレームワークに基づいて開発されたオープンソースのエンタープライズ セキュリティ緊急対応センターです。私が ThinkPHP を準モダンなフレームワークとして分類しているのは、近年の開発において、いくつかのモダンな要素 (名前空間、ORM など) がますます導入されているためです。もちろん、これらの要素の導入により、以前にコードを作成したときには発生しなかったセキュリティ上の問題がいくつか発生します。
ここでの脆弱性は、SRCMS による ORM (またはフレームワークのモデル) の不当な使用が原因で、結果として権限がオーバーライドされます。
まず、この脆弱性は 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();}
他人の脆弱性レポートを勝手に閲覧できる脆弱性が存在します。彼は明らかに 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 の機能を体験したいのかはわかりませんが、多くの場所では (SQL インジェクションの脆弱性はありませんが) 特に文字のスプライシングが使用されていますが、これは不適切だと思います。著者が使用している手法は依然として文字列のスプライシングであるため、この脆弱性は「新しい PHP セキュリティ脆弱性」とはみなされないと思います。フレームワーク アーキテクチャの下で生成された伝統的な脆弱性としか言えません。
次に、実際に新しい 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 が空の場合、実際には POST データから読み取る I('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;}
これは、条件を自動的に入力するメソッドです。特定のモデルの _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 の 3 番目のパラメーターを注意深く観察します: 1
3 番目のパラメーターの定義を確認します:
1 は挿入を表し、挿入時にのみ処理されます。 。渡した POST に user_id (getPk) があると仮定すると、ThinkPHP は現在のタイプが 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 の私が行っています。今から検証させてください。まずテスト ユーザーを登録し (user_id は 4)、次のように連絡先情報を追加します。
他のユーザーは、ログイン後、次のデータ パケットを送信して user_id= の連絡先情報を変更します。 4、携帯電話番号、Alipayなどを含む:
返回test用户的页面,发现已经被改:
这就是一个典型的新型框架的越权漏洞,因为不熟悉框架,在使用框架提供的『新式方法』时,造成了错误。
既然我们这个漏洞是开发者对框架不熟悉造成的,属于是『架构漏洞』,那么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的漏洞挖掘了。