都说bluecms是代码审计入门的香饽饽,我这个web菜鸡也来凑个热闹
安装上打开目录,随便点了一个ad_js.php
里面有个sql语句
$ad = $db->getone("SELECT * FROM ".table('ad')." WHERE ad_id =".$ad_id);
看一下getone
function getone($sql, $type=MYSQL_ASSOC){ $query = $this->query($sql,$this->linkid); $row = mysql_fetch_array($query, $type); return $row; }
是一个执行sql语句的函数
逆向追踪$ad_id变量
$ad_id = !empty($_GET['ad_id']) ? trim($_GET['ad_id']) : '';
$ad_id是从$_GET['ad_id']中拿来的,只经过了一步trim,很明显的sql注入
试了一下发现转义了,回头看了一下,上面包含了一个common.inc.php文件
里面有一个转义的操作
if(!get_magic_quotes_gpc()) { $_POST = deep_addslashes($_POST); $_GET = deep_addslashes($_GET); $_COOKIES = deep_addslashes($_COOKIES); $_REQUEST = deep_addslashes($_REQUEST); }
这里过滤缺少了对$SESSION的过滤,可能存在对session的注入,比如请求头之类的,或者 session中的数据
deep_addslashes函数
function deep_addslashes($str) { if(is_array($str)) { foreach($str as $key=>$val) { $str[$key] = deep_addslashes($val); } } else { $str = addslashes($str); } return $str; }
addlashes会对这些转义
单引号(')、双引号(")、反斜线(\)与 NUL(NULL 字符)
难道到这里就凉了吗,回头看了一下sql语句,发现参数位置并没有被单引号包住,那addslashes不就没用了吗
union联合查询注一下。sql注入一个
还有一个问题 响应头哪里显示的content-type是 gb2312编码 可能存在宽字节注入
再去找几个登陆点试一下 有没有宽字节
在search.php中找到了一处搜索,
if(!empty($keywords)) { $condition .= " AND title LIKE '%".$keywords."%' OR keywords LIKE '%".$keywords."%' "; } $row = $db->getone("SELECT COUNT(*) AS num FROM ".table('post')." WHERE 1=1 ".$condition);
回溯keywords 可控
$keywords = !empty($_REQUEST['keywords']) ? trim($_REQUEST['keywords']) : '';
试了一下 因为搜索型的注入要闭合一个% 所以宽字节不能用
再找找其他地方有没有数据库查询操作
首页那里有一个登录框,看看这个后台的代码
user.php 873行 这里调用了一个查询
$row = $db->getone("SELECT COUNT(*) AS num FROM ".table('admin')." WHERE admin_name='$user_name'");
溯源$user_name变量
$user_name = !empty($_REQUEST['user_name']) ? trim($_REQUEST['user_name']) : '';
直接赋值 不需要绕过
输入单引号 不报错
输入%df' 报错
实锤了 宽字节注入
这里不显示 查询结果的信息 可以用盲注或者报错注入
还有其他几处宽字节注入的地方
user.php的154行处 注册时 也调用了一步查询,并且$username可控
if($db->getone("SELECT * FROM ".table('user')." WHERE user_name='$user_name'")){ showmsg('该用户名已存在'); }
。。。
还有很多 不过原理都一样,就不多说了
在category.php 调用了url_rewrite函数,跟进一下,
url_rewrite()
涉及到一大堆的url赋值操作,但是都需要满足一定的值才可以,一开始我是这样想的 这里让他一个也不满足,利用变量覆盖,覆盖url的值,直接到return url
function url_rewrite($act, $arr) { global $_CFG; $url = $id = $cid = $aid = $page_id = $ann_id = ''; extract($arr); if($act == 'category') { ....... ....... return $url; }
并且这里的if判断$act 还是一个弱类型的判断,存在一定问题
回溯$act $arr变量来源
$act变量是固定的不可控,$arr是从数据库里拿出来的
这条路貌似行不通了,因为$act不可控,不能直接覆盖url,然后返回了
接着往下看
这里有个htmlspecialchars($_POST['comment'])
$content = !empty($_POST['comment']) ? htmlspecialchars($_POST['comment']) : '';
看下htmlspecialchars函数的作用
& (& 符号) & " (双引号) ",除非设置了 ENT_NOQUOTES ' (单引号) 设置了 ENT_QUOTES 后, ' (如果是 ENT_HTML401) ,或者 ' (如果是 ENT_XML1、 ENT_XHTML 或 ENT_HTML5)。 < (小于) < > (大于) >
没有开启ENT_QUOTES选项 也就是说单引号没有转义,可能存在xss
comment.php 和 guest_book.php 这两个文件一个是评论 一个是留言,大多都容易出现xss
guest_book.php
这里找到了一个插入的地方,本来想找xss来着 没想到找到一个sql注入
这里有个online_ip
$sql = "INSERT INTO " . table('guest_book') . " (id, rid, user_id, add_time, ip, content) VALUES ('', '$rid', '$user_id', '$timestamp', '$online_ip', '$content')"; $db->query($sql); showmsg('恭喜您留言成功', 'guest_book.php?page_id='.$_POST['page_id']);
获取ip很有可能是通过$SESSION来获取的,溯源$online_ip
在这个文件内,没找到$online_ip 那他的$online_ip是咋来的,回头想了一下,前面还包含了几个文件,应该是在这几个文件内,这个变量总不能是凭空来的吧
在comment.inc.php中找到
跟踪getip()
function getip() { if (getenv('HTTP_CLIENT_IP')) { $ip = getenv('HTTP_CLIENT_IP'); } elseif (getenv('HTTP_X_FORWARDED_FOR')) { //获取客户端用代理服务器访问时的真实ip 地址 $ip = getenv('HTTP_X_FORWARDED_FOR'); }
x-forwarded-for 很常见的ip绕过 注入一下试试
插入成功
注入
这里要注意$SESSION中的数据貌似没有自动的url解码 +不能被解码为空格
在conment.php中也存在同样的问题 113行调用了getip 直接当到了数据库里面
$sql = "INSERT INTO ".table('comment')." (com_id, post_id, user_id, type, mood, content, pub_date, ip, is_check) VALUES ('', '$id', '$user_id', '$type', '$mood', '$content', '$timestamp', '".getip()."', '$is_check')";
不演示了,原理都一样的
做到这里就能够发现,这里的与sql相关的操作,貌似都可以注入,因为过滤程序都是相同的,变量的获取方式也是相同
sql注入的漏洞到这里先放一下 太多了
user.php里的代码太多了 先大体上看了一下内容,都是些if-elseif分支结构,通过判断$act来决定执行那部分分支语句
user.php 上面有几个变量 不像之前的那些文件那样经过Intval, 这两个变量都可控
$act = !empty($_REQUEST['act']) ? trim($_REQUEST['act']) : 'default'; $from = !empty($_REQUEST['from']) ? $_REQUEST['from'] : '';
$act变量主要是作为if判断来使用的 没啥大用处,
主要注意一下$from,调用$from的地方, 当时这里应该直接搜一下的 正向的变量追踪
在112行看到 url跳转
showmsg('欢迎您 '.$user_name.' 回来,现在将转到...', $from);
在回溯一下,看看$from中间经过了那些变化
在66行
$from = !empty($from) ? base64_decode($from) : 'user.php';
很好绕过base64编码一下就行
而且后面几个地方出现了相同的问题
注册处
showmsg('恭喜您注册成功,现在将转向...', $from);
不多说了 原理和上面的一样
还是user.php 在780行
if (strpos($_POST['face_pic1'], 'http://') != false && strpos($_POST['face_pic1'], 'https://') != false){ showmsg('只支持本站相对路径地址'); }
这里有一处if判断,对于strpos函数应该使用===来判断 因为0和false弱类型相等 这里过滤有问题
搜一下看哪里有调用$_POST['face_pic1']的地方
$face_pic = trim($_POST['face_pic1']); if(isset($_FILES['face_pic2']['error']) && $_FILES['face_pic2']['error'] == 0){ $face_pic = $image->img_upload($_FILES['face_pic2'],'face_pic'); } $face_pic = empty($face_pic) ? '' : $face_pic; 后面就会存到数据库里面 $sql = "UPDATE ".table('user')." SET birthday = '$birthday', sex = '$sex', face_pic = '$face_pic', email = '$email', msn = '$msn', qq = '$qq'," . " mobile_phone = '$mobile_phone', office_phone = '$office_phone', home_phone = '$home_phone', address='$address' WHERE user_id = ".intval($_SESSION['user_id']);
这个face_pic是通过路径引用头像的地方 很明显存在SSRF漏洞
绕过也很好绕过 前面的不上传图片,令地址http://或者https://开头就行
再看看其他的分支结构是干嘛的
在user.php的266行 看到了一个过滤
$content = !empty($_POST['content']) ? filter_data($_POST['content']) : '';
跟进一下 看看他过滤的严不严格 说不定我就是漏网之鱼
function filter_data($str) { $str = preg_replace("/<(\/?)(script|i?frame|meta|link)(\s*)[^<]*>/", "", $str); return $str; }
就过滤了这么几个标签,img就可以绕过
<img src onerror=alert(1)>
测试一下
弹窗
这次的cms属于入门级别,漏洞比较多,一共找出来sql注入 宽字节注入 insert注入 SSRF 任意url跳转 xss这6个漏洞,可以说这些漏洞主要都是过滤不严格导致的
我的审计思路 主要是先找到危险函数,然后回溯可控变量
中间也踩到了不少的坑,危险函数挺多的, 但是回溯一下可控变量,就没找到几个可以利用的