四.1 项目入门
\1. 利用MobaXterm工具将proj1.tar.gz拷贝到虚拟机目录下,并解压。
\2. 确保可以成功编译zookws Web服务器并运行
访问http://IPADDRESS:8080/
IPADDRESS为虚拟机IP地址
四.2 查找缓冲区溢出漏洞
\1. (任务1)研究Web服务器的C代码(zookd.c和http.c),找到一个允许攻击者覆盖函数返回地址的代码片段。
通过Makefile发现main函数在zookd.c中。分析zookd.c
服务器是一个简单的用多进程来处理用户的socket服务器,main函数中只有run_server函数,第一个命令行参数是服务器端口。
在run_server函数的循环中,每次accept一个新的client描述符后,会fork出一个新进程,调用process_client处理这个client请求。
process_client首先调用http_request_line处理请求行,也就是类似“GET/HTTP/1.0\r\n”的请求行。如果请求没有问题的话,再调用env_deserialize解析环境变量,然后再调用http_request_headers处理请求headers。如果headers的解析也没有问题的话,再调用http_serve函数处理请求。
任务一需要对socket server进行栈缓冲区攻击,所以对http_request_line和http_request_headers进行分析。
http_request_line函数:
http_request_headers函数:
这两个函数都是先用http_read_line读入一行,然后校验读入行的格式,校验通过之后用url_decode解码,最后使用sprintf设定环境变量。
http_read_line函数功能为读取一行,使用了size函数约束读入字符的长度,无法进行栈溢出。
这里可以发现漏洞点,url_decode调用的两个参数为两个数组指针,但是没有判断两个指针所在的数组长度,或者限制长度,而只是一直复制到src中的\0
才停止,相当于一个带url解码的strcpy。
再观察对url_decode的调用
http_request_line和http_request_headers中使用这个函数的时候,传入的两个参数都是 len(dst)<len(src)的,那么我们利用src与dst的长度差,即可将溢出的数据写入到*src外面。
src的实参分别是reqpath和value两个数组,reqpath是zookd.c中传进来process_client的reqpath[4096],value是http_request_headers中定义的value[512]。
选择http_request_headers做exploit,直接用最大容量为8192的buf数组来覆盖最大容量为512的value数组。所以只要大于512且小于8192的一行输入就可以覆盖http_request_headers的返回地址。
\2. (任务2)编写利用缓冲区溢出漏洞使Web服务器(或其创建的进程之一)崩溃的利用代码。
只要溢出覆盖了函数的返回地址,就可以使程序崩溃。使用exploit_template.py中的代码为模板,修改build_exploit函数得到exploit-2.py。
成功
四.3 代码注入
\1. (任务3)修改shellcode.S以删除文件/home/ssc/grades.txt。
一般通过注入代码获取shell,即执行execve(“/bin/sh”),参数只有一个字符串,此处通过调用unlink来删除一个文件,那么参数就不止一个了,相当于执行了这样一个过程:
由于参数是三个,所以需要在栈上布局execve所需要的参数。可以使用像普通的shellcode中一样传递字符串指针的方法:用pop来把call的下一条指令的返回值弹出,而该指令放一个.ascii “/usr/bin/unlinkA/home/ssc/grades.txtA”,这样的话弹出的结果就是指向.ascii的一个指针了,用它来作为基础指针来进行后续的操作。
execve的指针有三个:执行文件路径字符串的指针,执行文件参数字符串数组的指针,环境变量数组的指针。其中字符串的结尾要用’\0’来分割,而argv数组的结尾需要用一个NULL指针来填充。
由于注入shellcode里不能出现’\0’(0x00会被http_read_line当作字符串的结尾截断),所以仿照获取shell的代码,用xorq %rax,%rax来直接获得一个全0的寄存器,用这个rax来代替之后代码里需要用到的0x00字节。
根据字符串数组在内存中的分布模型,这时argv指向的就是“/usr/bin/unlink”,而后的字符串/home/ssc/grade.txt用’\0’分割开就行。把"/usr/bin/unlinkA/home/ssc/grades.txt" 里的’A’替换成’\0’即可。
构造shellcode:
测试Shellcode是否完成工作:
\2. (任务4)从任务2中找到的一个漏洞开始,编写一个能够劫持Web服务器控制流并删除文件/home/ssc/grades.txt的利用代码。
在任务3中已经有了shellcode,现在还需要找到value数组和程序返回地址在内存中的位置,以编写代码进行注入。
在zookd.c:113处下一个断点,以进入http_request_headers函数调试
进入gdb
发现了warning,查找资料解决,修改home/.gdbinit,加上一句set auto-load safe-path /home/ssc/proj1
再次进入gdb,出现以下问题
找到办法解决
最后一行1改为0(重启!)
再次进入gdb,可以正常调试
运行zookd-exstack
进入gdb调试
根据提示,先set follow-fork-mode child然后在zookd.c:113和http.c:172处分别下断点,然后c
用exploit-2发起请求(此时要将exploit-2修改为无溢出的代码)
发现http_request_headers的返回地址是0x0000555555556b02 (call的下一条指令地址)
继续运行到ret
发现存放0x0000555555556b02的地址为0x7fffffffdca8
同时value数组的地址在 0x7fffffffda70
根据两个地址构造exploit-4.py
四.4 Rerurn-to-libc攻击
\1. (任务5)以任务2和4中的漏洞利用代码为基础,构造一个新的漏洞利用代码,对于具有不可执行栈的zookd,能够删除文件/home/ssc/grades.txt。将此新漏洞利用代码命名为exploit-5.py。
攻击的要点是利用栈缓冲区溢出做到以下几点:
(1) 先将所选libc函数的参数放到栈上
(2) 然后使程序运行accidentally()函数,进而将参数放置到%rdi中
(3) 最后使accidentally()函数返回到所选的libc函数
因为栈不可执行,之前的shellcode无法直接利用,这里我们需要找到程序自身的代码运行我们的shellcode。根据提示,我们需要在libc中找到unlink的位置。这里可以将任务分成俩个。
① 把return地址改为libc中的unlink函数地址
② 设置好unlink函数需要的参数
运行zookd-nxstack
首先使用gdb找到unlink函数的地址0x155555427d00
接下来设置参数,由于在x86-64环境下,函数调用的模式是把前6个参数的指针存储在寄存器里,而不是栈上,所以我们的攻击存在困难,参数不在栈上,我们无法通过buffer overflow来操纵他们,而又无法执行注入的代码。
为了解决这个问题,可以像使用libc中的库函数那样,直接使用程序已存在的指令段,比如某一行有mov 0x10(%rbp),%rdi这种能被我们拿来利用的小代码段,就可以把栈上的(rbp是栈底指针)相应位置安排上数据作为参数处理。就可以把栈上的相应位置覆盖所需数据作为参数处理。
其中zookd.c中存在一个accidentally函数可以使用
查看对应汇编代码
首地址0x0000555555556b62
为了将指向字符串/home/ssc/grades.txt的指针存储到rdi寄存器里,可以通过利用这段代码,把字符串的地址放到rbp+0x10里,并且此处的rbp在前一行被rsp的值所覆盖,所以要存放的目标地址就是栈顶指针rsp+24的位置。(此处操作类似于任务4,也要发包)
在http.c:173处下断点
然后运行到ret
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m0GWhcdK-1671086605138)(file:///C:\Users\24633\AppData\Local\Temp\ksohtml17156\wps55.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BMxfJUFg-1671086605138)(file:///C:\Users\24633\AppData\Local\Temp\ksohtml17156\wps56.jpg)]
所以返回地址为7FFF FFFF DCC0
那么按照思路,我们先在栈溢出中把返回地址(rsp+8)指向accidentally的开头0x0000555555556b62,然后接下来在更高的8字节(rsp+16)上放libc函数unlink的调用地址,再往后8个字节(rsp+24)的位置需要放上指向字符串的指针。再往后,我们把字符串本身放进去作为payload的结尾。
64位系统理论上可以提供2^64字节的虚拟地址空间,然而目前只用到了其中的后48位,也就是说地址是从0000 0000 0000 0000到 0000 7fff ffff ffff,这其实会对很多基于strcpy之类的攻击造成阻碍,因为开头的两个空字节0x00会直接让读入函数以为自己已经读到’\0’了,从而抛弃了后面的数据。
在本次实验中攻击对象是一个url_decode函数,这个函数虽然也是当src指针指向’\0’的时候停止,但是他内部有一个很大的逻辑漏洞是当遇到百分号%的时候他会直接把后面两位拿过来当作16进制数据。这么一来我们只需要把我们的payload后面需要用到的地址部分每个字节都加一个%的前缀就可以了,可以写一个简单的urlencode函数来进行这个操作。
综合上面的分析,可以得到攻击代码:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TPehYckE-1671086605138)(file:///C:\Users\24633\AppData\Local\Temp\ksohtml17156\wps57.jpg)]
验证
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nX08Oiku-1671086605138)(file:///C:\Users\24633\AppData\Local\Temp\ksohtml17156\wps58.jpg)]
挑战1 函数accidentally()的存在是人为故意设置的。请弄清楚如何在不依赖该函数的情况下执行Return-to-libc 攻击 (假设程序中不存在这个函数并找到另一种使漏洞利用起作用的方法)。在exploit-challenge.py中写出你的利用代码。另外,请解释你是如何实施攻击的,并在项目报告中列出你使用的ROP配件。
为了完成这个挑战,你需要找到另一段可重用的代码 (即ROP配件),使你可以控制%rdi。你可以通过反汇编(例如,使用objdump命令)来查找所需的ROP 配件。由于x86/x86-64的性质,你可以使用另一种技术来查找甚至没有出现在反汇编中的指令序列。x86/x86-64的指令的长度是可变的(从1到15个字节),并且未对齐的解析(通过跳转到目标指令的中间)会导致一系列机器代码被误解。例如,指令序列pop %r15; ret对应于机器代码41 5F C3。但是,如果你跳过1个字节,而不是从该指令序列的开始执行,机器代码5F C3将对应于指令序列 pop %rdi; ret。诸如ROPgadget.py之类的自动化工具也可以帮助你搜索ROP配件,甚至可以用来查找由于未对齐的解析而产生的配件。
首先找需要利用的代码片段pop rdi,ret。
安装ROPgadget工具用来查找
ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 --only “pop|ret”
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IvkgALuQ-1671086605139)(file:///C:\Users\24633\AppData\Local\Temp\ksohtml17156\wps60.jpg)]
0x23b72为需要的地址,然后找libc加载地址(用程序发送请求)
155555318000-15555533a000 r–p 00000000 08:05 930329 /usr/lib/x86_64-linux-gnu/libc-2.31.so
基地址为155555318000
修改代码
通过
四.5 修复缓冲区溢出和其他错误
\1. (任务6)查看源代码,并尝试查找更多可以使攻击者破坏这个Web服务器安全性的漏洞。在项目报告中描述你所发现的其他漏洞,并说明利用这个漏洞所能实施的攻击、攻击的局限性、攻击者可以完成的任务、起作用的原因以及如何修复这些漏洞。至少查找两个漏洞。
① 缓冲区溢出漏洞
所能实施的攻击:缓冲区溢出漏洞
攻击的局限性:很难找到这样的机会
攻击者可以完成的任务:使服务器崩溃
起作用的原因:url_decode调用的两个参数为两个数组指针,但是没有判断两个指针所在的数组长度,或者限制长度。
如何修复:修改代码限制长度,或修改数组大小
url_decode
http_request_line函数
src的实参分别是reqpath和sp1两个数组,reqpath是zookd.c中传进来的(process_client的reqpath[4096]),sp1是http_request_line中定义的与buf[8192]有关。这样的话sp1就可以比reqpath大了,可以进行溢出。只需要一个小于8192的大于4096的一行输入就可以覆盖http_request_line的返回地址。
修改exploit-2.py
运行成功
② 存储型XSS漏洞
所能实施的攻击:存储型XSS
攻击的局限性:需要受害者点击攻击者主页
攻击者可以完成的任务:泄露用户cookie
起作用的原因:网页未进行xss防护
如何修复:开启xss防护,对关键字符进行过滤、替换等处理
试试xss
发现xss可执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gnuydpCF-1671086605142)(file:///C:\Users\24633\AppData\Local\Temp\ksohtml17156\wps71.jpg)]
获取cookie
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dsbFENQC-1671086605142)(file:///C:\Users\24633\AppData\Local\Temp\ksohtml17156\wps72.jpg)\
可以实现存储型xss攻击。
修复方法:在存储或者取出profile时加上过滤或者转码,把<符直接转码或者删除即可或者直接开启XSS保护。
\2. (任务7)对于在任务 2、4 和 5 中利用的每个缓冲区溢出漏洞,请首先修改 Web 服务器的代码以修复该漏洞。这里的修复,不要依赖编译时或运行时机制,例如栈保护,删除 -fno-stack-protector,漏洞代码检查等。
将http_request_headers函数中的value数组修改为大于8192
zookd.c中process_client函数中的reqpath数组修改为8192
测试
修复成功