CVE-2021-3156 sudo堆溢出 漏洞分析

2023-11-15

漏洞简介

sudo 是 linux 系统管理指令,是允许系统管理员让普通用户执行一些或者全部的 root 命令的一个工具,它允许授权用户以 root 权限执行命令或者程序。

sudo 的 sudoer 插件里面存在一个堆溢出漏洞,攻击者可以利用该漏洞进行提权

影响范围

all legacy versions from 1.8.2 to 1.8.31p2
all stable versions from 1.9.0 to 1.9.5p1

漏洞分析

该漏洞位于 sudoers.c: set_cmnd 函数

static int
set_cmnd(void)
{
struct sudo_nss *nss;
char *path = user_path;
int ret = FOUND;


    /* Alloc and build up user_args. */
    for (size = 0, av = NewArgv + 1; *av; av++)
    size += strlen(*av) + 1;
    if (size == 0 || (user_args = malloc(size)) == NULL) {
    sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
    debug_return_int(-1);
    }
    if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
    /*
     * When running a command via a shell, the sudo front-end
     * escapes potential meta chars.  We unescape non-spaces
     * for sudoers matching and logging purposes.
     */ 
    for (to = user_args, av = NewArgv + 1; (from = *av); av++) { // NewArgv是一个二维数组,下面拷贝这个NewArgv到user_args
        while (*from) { //判断是否到字符串结尾
        if (from[0] == '\\' && !isspace((unsigned char)from[1])) //[1]
            from++;//[2]
        *to++ = *from++;//[3]
        }
        *to++ = ' ';
    }
    *--to = '\0';

debug_return_int(ret);

}
当 21-28 行代码处理以\ 为结尾或者作为一个单独的参数时

from[0] 指向\ ,而 from[1] 指向\x00 ,\x00被拷贝到 user_args 中

然后 from++ 指向 user_args 后面的内存,再一次进入到 while 循环中,越界拷贝后面的的数据到 user_args 中。

举个例子

启动命令如下

sudo -s ‘’ ‘aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa’

图片
当from[0]指向0x7ffcd8c9415d时,from[1] 会指向\x00 然后把 \x00拷贝到 user_args 中

然后 from++ 指向第二个参数的 data,再次进入到 while 循环中,把第二个参数的 data 拷贝到 user_args 中

执行完第一次 for 循环,av++ 这时的 from 指向第二个参数,进行第二个参数的拷贝

在拷贝第二个参数时,user_args 的分配的内容就已经用完,这时就溢出修改 user_args 后面的数据

NewArgv 后面接着是一些环境变量,可以利用环境变量来控制溢出的数据,(比如想要写\x00 可以在环境变量里面加很多\ )

利用

从漏洞的类型,user_args 的数据类型和 sudo 本身进行思考

这个漏洞是堆溢出
这个漏洞无法泄露数据
从这两点可以看出,这个漏洞的利用很大的可能就是覆盖 user_args 后面的结构体或者函数地址(一般函数地址在 heap 上的,估摸就是结构体内部的成员)

要实现溢出修改目标结构体,需要控制堆上的内存布局。Qualys团队提到了一种方法,控制传入 sudo 的LC环境变量

setlocale(LC_ALL, "");
bindtextdomain(PACKAGE_NAME, LOCALEDIR);
textdomain(PACKAGE_NAME);

setlocale函数里面调用了 malloc 和 free,在 malloc 和 free 的过程中,产生了许多 hole,可以通过这些 hole 来控制内存布局,使得想要溢出控制的堆块落在 user_args 后面

Qualys团队提出了三种利用方法

本次分析 Qualys团队 里面介绍的第二种和第三种利用方法

1、struct service_user overwrite

这个利用方法是覆盖 service_user 结构体,控制 nss_load_library 函数的执行流程

static int
nss_load_library (service_user ni)
{
if (ni->library == NULL)
{
/
This service has not yet been used. Fetch the service
library for it, creating a new one if need be. If there
is no service table from the file, this static variable
holds the head of the service_library list made from the
default configuration. */
static name_database default_table;
ni->library = nss_new_service (service_table ?: &default_table,//[1]
ni->name);
if (ni->library == NULL)
return -1;
}

if (ni->library->lib_handle == NULL)
{
/* Load the shared library. */
size_t shlen = (7 + strlen (ni->name) + 3
+ strlen (__nss_shlib_revision) + 1);
int saved_errno = errno;
char shlib_name[shlen];

  /* Construct shared object name.  */
  __stpcpy (__stpcpy (__stpcpy (__stpcpy (shlib_name,//[2]
                      "libnss_"),
                ni->name),
          ".so"),
    __nss_shlib_revision);

  ni->library->lib_handle = __libc_dlopen (shlib_name);//[3]
  if (ni->library->lib_handle == NULL)
{
  /* Failed to load the library.  */
  ni->library->lib_handle = (void *) -1l;
  __set_errno (saved_errno);
}

//*************************

return 0;
}
service_user 结构

typedef struct service_user
{
/* And the link to the next entry. */
struct service_user next;
/
Action according to result. /
lookup_actions actions[5];
/
Link to the underlying library object. */
service_library library;
/
Collection of known functions. */
void known;
/
Name of the service (files',dns’, `nis’, …). */
char name[0];
} service_user;
利用思路

首先覆盖 library == NULL,然后就进入到 [1] ,这样就能保证 ni->library->lib_handle == NULL

static service_library *
nss_new_service (name_database *database, const char *name)
{
service_library **currentp = &database->library;

while (*currentp != NULL)
{
if (strcmp ((*currentp)->name, name) == 0)
return *currentp;
currentp = &(*currentp)->next;
}

/* We have to add the new service. */
*currentp = (service_library *) malloc (sizeof (service_library));
if (*currentp == NULL)
return NULL;

(*currentp)->name = name;
(currentp)->lib_handle = NULL; //[]
(*currentp)->next = NULL;

return *currentp;
}
在 [2] 组装 __libc_dlopen 的传入参数 shlib_name,因为 ni->name 是可控的,所以 shlib_name 也是可控的

在 [3] 加载 so 文件,因为 shlib_name 可控,所以可以任意加载 so 文件。在编写 so 文件的代码时,需要在执行的函数前加上 _attribute((constructor)) 这个属性

这是 gcc 的一个属性,加这个属性的函数,会在 __libc_dlopen 返回之前执行,就有点类似于 c++ 的构造函数

Initialization and finalization functions
Shared objects may export functions using the attribute((constructor)) and attribute((destructor)) function attributes.Constructor functions are executed before dlopen() returns, and destructor functions are executed before dlclose() returns. A shared object may export multiple constructors and destructors, and priorities can be associated with each function to determine the order in which they are executed.See the gcc info pages (under “Function attributes”) for fur‐ther information.
调试

首先需要测试出,执行完 set_cmnd 后第一个 dl_open 的目标

在 nss_load_library 下断点,然后执行完 set_cmnd 就断在 nss_load_library ,测试哪一个 service_user 结构体会在执行完 set_cmnd 之后,在 nss_load_library 中调用 dl_open 加载 so 文件

图片
经调试发现,是 systemd,最后加载的是 libnss_systemd.so.2,所以需要控制这个 ni->name 为 systemd 的结构体,需要使用到前面所说在LC环境变量来控制堆上的 bin 的分布

这个 ni->name 为 systemd 的结构体需要控制在 fastbin,largebin ,tcache 的 heap 的附近,这样在分配 user_args 时,就能分配到这个结构体前面

2、def_timestampdir overwrite

def_timestampdir

#define def_timestampdir (sudo_defs_table[I_TIMESTAMPDIR].sd_un.str)
这个函数里面引用到了 def_timestampdir

int
check_user(int validated, int mode)
{
struct getpass_closure closure = { TS_ERROR };
int ret = -1;
bool exempt = false;
debug_decl(check_user, SUDOERS_DEBUG_AUTH)

/*
 * Init authentication system regardless of whether we need a password.
 * Required for proper PAM session support.
 */
if ((closure.auth_pw = get_authpw(mode)) == NULL)
goto done;
if (sudo_auth_init(closure.auth_pw) == -1)
goto done;

/*
 * Don't prompt for the root passwd or if the user is exempt.
 * If the user is not changing uid/gid, no need for a password.
 */
if (!def_authenticate || user_is_exempt()) {
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %s", __func__,
    !def_authenticate ? "authentication disabled" :
    "user exempt from authentication");
exempt = true;
ret = true;
goto done;
}
if (user_uid == 0 || (user_uid == runas_pw->pw_uid &&
(!runas_gr || (sudo_user.pw, runas_gr->gr_name)))) {

#ifdef HAVE_SELINUX
if (user_role == NULL && user_type == NULL)
#endif
#ifdef HAVE_PRIV_SET
if (runas_privs == NULL && runas_limitprivs == NULL)
#endif
{
sudo_debug_printf(SUDO_DEBUG_INFO,
“%s: user running command as self”, func);
ret = true;
goto done;
}
}

ret = check_user_interactive(validated, mode, &closure);

done:
if (ret == true) {
/* The approval function may disallow a user post-authentication. */
ret = sudo_auth_approval(closure.auth_pw, validated, exempt);

/*
 * Only update time stamp if user validated and was approved.
 * Failure to update the time stamp is not a fatal error.
 */
if (ret == true && closure.tstat != TS_ERROR) {
    if (ISSET(validated, VALIDATE_SUCCESS))
    (void)timestamp_update(closure.cookie, closure.auth_pw);
}
}
timestamp_close(closure.cookie);
sudo_auth_cleanup(closure.auth_pw);
if (closure.auth_pw != NULL)
sudo_pw_delref(closure.auth_pw);

debug_return_int(ret);

}
要进入到这个函数要在启动命令多加一个 -A,然后需要设置SUDO_ASKPASS环境变量

SUDO_ASKPASS=/bin/false
这里用gdb调试

chmod u+s /usr/bin/gdb
利用思路

那就从 check_user 函数开始

check_user 里面会有个验证 uid,这个 uid 是存储在 sudo 里面的一个全局的结构体 sudo_user 里面

if (user_uid == 0 || (user_uid == runas_pw->pw_uid && //[1]
(!runas_gr || (sudo_user.pw, runas_gr->gr_name)))) {

#ifdef HAVE_SELINUX
if (user_role == NULL && user_type == NULL)
#endif
#ifdef HAVE_PRIV_SET
if (runas_privs == NULL && runas_limitprivs == NULL)
#endif
{
sudo_debug_printf(SUDO_DEBUG_INFO,
“%s: user running command as self”, func);
ret = true;
goto done;
}
}

ret = check_user_interactive(validated, mode, &closure);//[2]

因为 [1] 处 user_uid=1000,然后后面的判断也不成立,就进入了 [2]

static int
check_user_interactive(int validated, int mode, struct getpass_closure *closure)
{
struct sudo_conv_callback cb, *callback = NULL;
int ret = -1;
char *prompt;
bool lectured;
debug_decl(check_user_interactive, SUDOERS_DEBUG_AUTH)

/* Open, lock and read time stamp file if we are using it. */
if (!ISSET(mode, MODE_IGNORE_TICKET)) {
/* Open time stamp file and check its status. */
closure->cookie = timestamp_open(user_name, user_sid); //[1]
if (timestamp_lock(closure->cookie, closure->auth_pw))//[2]
    closure->tstat = timestamp_status(closure->cookie, closure->auth_pw);

/* Construct callback for getpass function. */
memset(&cb, 0, sizeof(cb));
cb.version = SUDO_CONV_CALLBACK_VERSION;
cb.closure = closure;
cb.on_suspend = getpass_suspend;
cb.on_resume = getpass_resume;
callback = &cb;
}

switch (closure->tstat) {
case TS_FATAL:
/* Fatal error (usually setuid failure), unsafe to proceed. */
goto done;

case TS_CURRENT:
/* Time stamp file is valid and current. */
if (!ISSET(validated, FLAG_CHECK_USER)) {
    ret = true;
    break;
}
sudo_debug_printf(SUDO_DEBUG_INFO,
    "%s: check user flag overrides time stamp", __func__);
/* FALLTHROUGH */

default:
/* Bail out if we are non-interactive and a password is required */
if (ISSET(mode, MODE_NONINTERACTIVE)) {
    validated |= FLAG_NON_INTERACTIVE;
    log_auth_failure(validated, 0);
    goto done;
}

/* XXX - should not lecture if askpass helper is being used. */
lectured = display_lecture(closure->tstat);

/* Expand any escapes in the prompt. */
prompt = expand_prompt(user_prompt ? user_prompt : def_passprompt,
    closure->auth_pw->pw_name);
if (prompt == NULL)
    goto done;

ret = verify_user(closure->auth_pw, prompt, validated, callback);
if (ret == true && lectured)
    (void)set_lectured();   /* lecture error not fatal */
free(prompt);
break;
}

done:
debug_return_int(ret);
}
重点在 [1] [2]

timestamp_open(const char *user, pid_t sid)
{
struct ts_cookie *cookie;
char *fname = NULL;
int tries, fd = -1;
debug_decl(timestamp_open, SUDOERS_DEBUG_AUTH)

/* Zero timeout means don't use the time stamp file. */
if (!sudo_timespecisset(&def_timestamp_timeout)) {
errno = ENOENT;
goto bad;
}

/* Sanity check timestamp dir and create if missing. */
if (!ts_secure_dir(def_timestampdir, true, false))//[1]
goto bad;

/* Open time stamp file. */
if (asprintf(&fname, "%s/%s", def_timestampdir, user) == -1) { //[2]
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto bad;
}
for (tries = 1; ; tries++) {
struct stat sb;
// 上面能看到 (asprintf(&fname, "%s/%s", def_timestampdir, user)  这个是配置 stampfile的文件名字的
fd = ts_open(fname, O_RDWR|O_CREAT);
switch (fd) {
case TIMESTAMP_OPEN_ERROR:
    log_warning(SLOG_SEND_MAIL, N_("unable to open %s"), fname);
    goto bad;
case TIMESTAMP_PERM_ERROR:
    /* Already logged set_perms/restore_perms error. */
    goto bad;
}

/* Remove time stamp file if its mtime predates boot time. */
if (tries == 1 && fstat(fd, &sb) == 0) {
    struct timespec boottime, mtime, now;

    if (sudo_gettime_real(&now) == 0 && get_boottime(&boottime)) {
    /* Ignore a boot time that is in the future. */
    if (sudo_timespeccmp(&now, &boottime, <)) {
        sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
        "ignoring boot time that is in the future");
    } else {
        mtim_get(&sb, mtime);
        if (sudo_timespeccmp(&mtime, &boottime, <)) {
        /* Time stamp file too old, remove it. */
        sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
            "removing time stamp file that predates boot time");
        close(fd);
        unlink(fname);
        continue;
        }
    }
    }
}
break;
}

/* Allocate and fill in cookie to store state. */
cookie = malloc(sizeof(*cookie));
if (cookie == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto bad;
}
cookie->fd = fd;
cookie->fname = fname;
cookie->sid = sid;
cookie->pos = -1;

debug_return_ptr(cookie);

bad:
if (fd != -1)
close(fd);
free(fname);
debug_return_ptr(NULL);
}
[1] 是对路径的一个检查,检查路径是否存在,如果存在,权限是否正确

[2] 这里会组装路径,user 就是你的用户的名字,如果发生溢出,然后溢出修改 def_timestampdir,那么 def_timestampdir 就受我们控制

那么现在先说 怎么绕过 ts_secure_dir 的检测

static bool
ts_secure_dir(char *path, bool make_it, bool quiet)
{
struct stat sb;
bool ret = false;
debug_decl(ts_secure_dir, SUDOERS_DEBUG_AUTH)

sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "checking %s", path);
switch (sudo_secure_dir(path, timestamp_uid, -1, &sb)) {//[1]
case SUDO_PATH_SECURE:
ret = true;
break;
case SUDO_PATH_MISSING:
if (make_it && ts_mkdirs(path, timestamp_uid, timestamp_gid, S_IRWXU,//[2]
    S_IRWXU|S_IXGRP|S_IXOTH, quiet)) {
    ret = true;
    break;
}
errno = ENOENT;
break;
case SUDO_PATH_BAD_TYPE:
errno = ENOTDIR;
if (!quiet)
    sudo_warn("%s", path);
break;
case SUDO_PATH_WRONG_OWNER:
if (!quiet) {
    sudo_warnx(U_("%s is owned by uid %u, should be %u"),
    path, (unsigned int) sb.st_uid,
    (unsigned int) timestamp_uid);
}
errno = EACCES;
break;
case SUDO_PATH_GROUP_WRITABLE:
if (!quiet)
    sudo_warnx(U_("%s is group writable"), path);
errno = EACCES;
break;
}
debug_return_bool(ret);

}

static bool
ts_mkdirs(char *path, uid_t owner, gid_t group, mode_t mode,
mode_t parent_mode, bool quiet)
{
bool ret;
mode_t omask;
debug_decl(ts_mkdirs, SUDOERS_DEBUG_AUTH)

/* umask must not be more restrictive than the file modes. */
omask = umask(ACCESSPERMS & ~(mode|parent_mode));
ret = sudo_mkdir_parents(path, owner, group, parent_mode, quiet);
if (ret) {
/* Create final path component. */
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
    "mkdir %s, mode 0%o, uid %d, gid %d", path, (unsigned int)mode,
    (int)owner, (int)group);
if (mkdir(path, mode) != 0 && errno != EEXIST) { //[3]
    if (!quiet)
    sudo_warn(U_("unable to mkdir %s"), path);
    ret = false;
} else {
    if (chown(path, owner, group) != 0) { //[4]
    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
        "%s: unable to chown %d:%d %s", __func__,
        (int)owner, (int)group, path);
    }
}
}
umask(omask);
debug_return_bool(ret);

}
这里有四步蛮重要的

第一步检测路径和权限,这时候让它不存在,进入 [2]

然后在 [3] 里面会创建 文件夹,这时候需要竞争

首先在 mkdir 之前创建一个文件夹,执行到 mkdir 就报错(但不影响),因为这个 mkdir 创建的文件是 root 权限,如果它创建成功,那后面的符号链接就创建失败

创建的时间需要在 [1] 到[3] 这个时间段 ,这个竞争的成功率蛮高的,

在[4]处会有一个设置文件所有者和文件关联组的操作,这个操作是把目录加入到 root 组,所以不能让它执行成功,这时候需要把目录删除掉,chown 就报错(这个也不影响)

然后这里就绕过了 这个 ts_secure_dir 成功走到 timestamp_open 的下面

/* Open time stamp file. */
if (asprintf(&fname, "%s/%s", def_timestampdir, user) == -1) { //[1]
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto bad;
}
for (tries = 1; ; tries++) {
struct stat sb;
// 上面能看到 (asprintf(&fname, "%s/%s", def_timestampdir, user)  这个是配置 stampfile的文件名字的
fd = ts_open(fname, O_RDWR|O_CREAT); //[2]
switch (fd) {
case TIMESTAMP_OPEN_ERROR:
    log_warning(SLOG_SEND_MAIL, N_("unable to open %s"), fname);
    goto bad;
case TIMESTAMP_PERM_ERROR:
    /* Already logged set_perms/restore_perms error. */
    goto bad;
}

/* Remove time stamp file if its mtime predates boot time. */
if (tries == 1 && fstat(fd, &sb) == 0) { //[3]
    struct timespec boottime, mtime, now;

    if (sudo_gettime_real(&now) == 0 && get_boottime(&boottime)) {
    /* Ignore a boot time that is in the future. */
    if (sudo_timespeccmp(&now, &boottime, <)) {
        sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
        "ignoring boot time that is in the future");
    } else {
        mtim_get(&sb, mtime);
        if (sudo_timespeccmp(&mtime, &boottime, <)) {
        /* Time stamp file too old, remove it. */
        sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
            "removing time stamp file that predates boot time");
        close(fd);
        unlink(fname); //[4]
        continue;
        }
    }
    }
}
break;
}

/* Allocate and fill in cookie to store state. */
cookie = malloc(sizeof(*cookie));
if (cookie == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto bad;
}
cookie->fd = fd;
cookie->fname = fname;
cookie->sid = sid;
cookie->pos = -1;

debug_return_ptr(cookie);

bad:
if (fd != -1)
close(fd);
free(fname);
debug_return_ptr(NULL);
}
[1] 把名字和路径组装起来,然后 [2] open 这个文件

需要在这个 open 和前面的 chown 之间把这个文件夹创建回来,然后设置符号链接到 /etc/passwd,因为我们的最终目的是修改 passwd

下面遇到一个问题,就是 [3] ,这里用到 fstat,如果它用在符号链接的文件上,就会直接获取到符号链接到的程序的信息,而不是符号链接本身的信息,而下面有个时间判断,一般的默认情况下,passwd 创建的时间是比 boottime 要早的,这样文件就会给 unlink 掉,那这个符号链接就给删除掉

但这个操作执行一次,再第二次执行到 [3] 的时候 tries != 1,所以只要再 race 一次,创建一个符号链接就能成功建立一个符号链接指向 /etc/passwd

这也是为什么要让 chown 函数执行错误的原因

下面就到了 timestamp_lock 函数 ,这个函数里面调用 ts_write,ts_write 可以往 /etc/passwd 写入栈上的数据

bool
timestamp_lock(void *vcookie, struct passwd *pw)
{
struct ts_cookie *cookie = vcookie;
struct timestamp_entry entry;
off_t lock_pos;
ssize_t nread;
debug_decl(timestamp_lock, SUDOERS_DEBUG_AUTH)

if (cookie == NULL) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
    "called with a NULL cookie!");
debug_return_bool(false);
}

/*
 * Take a lock on the "write" record (the first record in the file).
 * This will let us seek for the record or extend as needed
 * without colliding with anyone else.
 */
if (!timestamp_lock_record(cookie->fd, 0, sizeof(struct timestamp_entry)))
debug_return_bool(false);

/* Make sure the first record is of type TS_LOCKEXCL. */
memset(&entry, 0, sizeof(entry)); 
nread = read(cookie->fd, &entry, sizeof(entry));//[1]
if (nread == 0) {
/* New file, add TS_LOCKEXCL record. */
entry.version = TS_VERSION;
entry.size = sizeof(entry);
entry.type = TS_LOCKEXCL;
if (ts_write(cookie->fd, cookie->fname, &entry, -1) == -1)//[2]
    debug_return_bool(false);
} else if (entry.type != TS_LOCKEXCL) {
/* Old sudo record, convert it to TS_LOCKEXCL. */

}

static ssize_t
ts_write(int fd, const char *fname, struct timestamp_entry *entry, off_t offset)
{
ssize_t nwritten;
off_t old_eof;
debug_decl(ts_write, SUDOERS_DEBUG_AUTH)

if (offset == -1) {
old_eof = lseek(fd, 0, SEEK_CUR);
nwritten = write(fd, entry, entry->size);
} else {
old_eof = offset;

#ifdef HAVE_PWRITE
nwritten = pwrite(fd, entry, entry->size, offset);//[3]
#else
if (lseek(fd, offset, SEEK_SET) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
“unable to seek to %lld”, (long long)offset);
nwritten = -1;
} else {
nwritten = write(fd, entry, entry->size);
}
#endif
}
if ((size_t)nwritten != entry->size) {//[4]
if (nwritten == -1) {
log_warning(SLOG_SEND_MAIL,
N_(“unable to write to %s”), fname);
} else {
log_warningx(SLOG_SEND_MAIL,
N_(“unable to write to %s”), fname);
}

/* Truncate on partial write to be safe (assumes end of file). */
if (nwritten > 0) {
    sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
    "short write, truncating partial time stamp record");
    if (ftruncate(fd, old_eof) != 0) {
    sudo_warn(U_("unable to truncate time stamp file to %lld bytes"),
        (long long)old_eof);
    }
}
debug_return_ssize_t(-1);
}
debug_return_ssize_t(nwritten);

}
图片
上面可以看到 pwrite的执行参数,[4] 处有一个检测写入的 size 是否和传进去的 size 一样,不一样就会清空掉写入的数据,所以要在环境变量里面加 padding

图片
总结

第一种:覆盖 service_user结构体,这方法需要堆内存的碎片把握的比较好,在整个调试过程中花费了大量的时间在找一个合适的 heap 布局,使得 service_user 结构体在 user_args 的下面

第二种:就比较好调试,因为整个过程需要跑竞争,而竞争只是时间问题,使用 gdb 调试,然后一步步操作不影响后面的 exp

参考链接
https://www.qualys.com/2021/01/26/cve-2021-3156/baron-samedit-heap-based-overflow-sudo.txt)
https://github.com/stong/CVE-2021-3156)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CVE-2021-3156 sudo堆溢出 漏洞分析 的相关文章

  • 一个令牌与多个令牌防止 CSRF 攻击

    我正在使用 Codeigniter 我想防止可能发生的 CSRF 攻击尝试 为了实现这一目标 我向我想要保护的每个表单添加一个带有随机令牌的隐藏输入标记 同时我将此令牌保留在会话中 以便在开始处理此表单数据时进行比较 set a token
  • 使用 python 请求获取 CSRF 令牌

    我目前正在使用 Python 请求 并且需要 CSRF 令牌才能登录站点 根据我的理解 requests Session 获取 cookie 但显然我需要令牌 我还想知道将它放在我的代码中的哪里 导入请求 user name input U
  • 在 Ionic 应用程序中一起使用 CORS 和 CSRF

    我正在开发一个 Android 应用程序 使用离子框架 http ionicframework com 基于我开发的 AngularJS 网站Jhipster https jhipster github io 由于我的 Web 应用程序中已
  • Django CSRF 框架无法禁用并且正在破坏我的网站

    django csrf 中间件无法禁用 我已从项目的中间件中将其注释掉 但由于缺少 CSRF 问题 我的登录失败 我在 Django 主干上工作 如果中间件中未启用 CSRF 它如何会导致问题 我必须禁用它 因为我的网站上有很多 POST
  • 如何扩展或重写 BeginForm 以包含 AntiForgeryToken 字段

    我正在读这篇文章 http weblogs asp net dixin archive 2010 05 22 anti forgery request recipes for asp net mvc and ajax aspx http w
  • Spring security中每个请求不同的csrf令牌

    我在用
  • 使用客户端指纹对 JWT 令牌进行编码?

    我想知道是否会是最佳实践使用客户端指纹作为 JWT 秘密进行编码 然而 我在 WWW 上找不到有关这个问题的任何内容 但到目前为止 我这样做是有意义的 我正在考虑使用 JavaScript 生成指纹客户端 并在每次调用时将其发送到 API
  • Symfony 2 - 删除表单和 CSRF 令牌

    我有一个来自数据库的条目列表 我希望在每一行的末尾都有一个 删除按钮 这样用户就不必先转到编辑 显示页面来删除条目 我尝试使用 csrf 令牌创建一个隐藏的输入字段 如下所示 return this gt createFormBuilder
  • 我怎样才能看到csrftoken?

    有没有办法直接在View中获取csrftoken 我想获取当前的 csrftoken 但有时会发生变化 因此从 Cookie 获取它不是一个好主意 有什么办法可以做到这一点吗 Thanks 我相信您正在寻找这个 django middlew
  • 反 CSRF cookie?

    我正在构建一个大量使用ajax 的应用程序 大多数反 CSRF 解决方案都围绕将一些信息放入视图状态并在发布时处理该数据 但是 我无权访问 ajax 调用中的视图状态 我计划生成一个 GUID 以在 cookie 和会话状态中插入令牌 使
  • Play Framework 2.6 CSRF 和会话

    我遇到了奇怪的问题 我正在我的网站上实现购物车功能 并使用会话来存储购物车位置 我有一个 POST 操作来将新位置添加到购物车 并且我启用了 CSRF 过滤器来保护网站 我在产品页面上用ajax调用它 所以第一次调用没问题 但第二次调用说未
  • @csrf_exempt 在 Django 1.4 中停止工作

    我有以下代码 在 Django 1 2 5 中运行良好 from django views decorators csrf import csrf exempt class ApiView object def call self requ
  • 为什么codeigniter2不以更安全的方式存储csrf_hash,例如会话?

    为什么生成的 CSRF 保护令牌没有像建议的那样通过 SESSION 保存和使用here http codeutopia net blog 2008 10 16 how to csrf protect all your forms 目前在C
  • 保护登录和评论表单免受 CSRF 攻击

    我读过很多关于CSRF保护的文章 这个不错 http seclab stanford edu websec csrf csrf pdf 以及关于SO的各种问题 但它们似乎都没有足够的信息来回答我的问题 我正在开发自己的 CMS 我想保护我的
  • 切换到负载均衡器后,django 帖子收到 CSRF 验证失败

    我有一个有效的登录模板 可以发布帖子 如下所示
  • ASP.NET MVC - ValidateAntiForgeryToken 过期

    在网页中 我们提供一个超链接 GET 用户可以单击该超链接进行身份验证 Html ActionLink Please Login MyMethod MyController 这映射到以下返回视图的控制器方法 RequireHttps pub
  • 无法登录 mediawiki:为了防止会话劫持而取消?

    我多年来一直使用托管在 AWS EC2 实例上的私有 mediawiki 我认为某些扩展出了问题 特别是在数学渲染过程中停止了 所以我尝试使用 Google Chrome 浏览器重新加载页面 缓存全部被删除 之后 我无法登录并看到此消息 您
  • 同一域上的多个 Django 站点 - CSRF 失败

    我有两个应用程序在同一域的不同端口上运行 都使用 csrf 中间件 当我登录其中一个应用程序时POST从另一个提交失败 我推测是因为SESSION COOKIE DOMAIN是一样的 我尝试改变SESSION COOKIE NAME 但是
  • ASP.NET MVC 防伪造令牌不安全

    在没有 ssl 的情况下向服务器发出请求时 我实际上可以看到 MVC3 框架以纯文本形式生成的验证令牌密钥 该密钥存储在名为 RequestVerificationToken Lw 的 cookie 中 在混合安全环境中 实际上可以在向非
  • Laravel 中的发布请求 - 错误 - 419 抱歉,您的会话/419 您的页面已过期

    我安装了 Laravel 5 7 向文件添加了一个表单 resources views welcome blade php

随机推荐

  • git 拉取分支到本地文件夹!

    前言 我现在需要改项目 我把项目来下来了却发现只需要修改某个分支的项目 所以只需要拉下项目的某个分支就行了 废话不多说直接开始教程 目录 1 创建本地仓库 2 与远程仓库建立联系 3 确定你需要拉的分支名 4 本地创建的分支与远程分支相互连
  • 【java.lang.ref】当WeakReference的referent重写了finalize方法时会发生什么

    问题 question 当WeakReference的referent重写了finalize方法时会发生什么 测试代码 JVM中是存在这样的情况的 一个Java对象 重写了finalize方法 在使用的过程中又被SoftReference或
  • 阿里云服务器搭建FRP实现内网穿透-转发

    前言 1 什么是frp frp是一个专注于内网穿透的高性能的反向代理应用 支持TCP UDP HTTP HTTPS等多种协议 且支持P2P通信 可以将内网服务以安全 便捷的方式通过具有公网IP节点的中专暴露到公网 2 演示环境 一 frp软
  • 火狐插件之(1) 用ScribeFire写csdn博客(很棒)

    ScribeFire 是火狐插件 简单快速的写博客 支持CSDN博客 关键是以下地址 http blog csdn net whyacinth services metablogapi aspx 将红色部分换成你的账号 自动检测回失败 手动
  • 内存卡永久删除的文件如何恢复?

    内存卡是和机械硬盘 U盘一个性质的数据存储工具 可以说是 同行 而作用更是不必多说 就是存储文件数据 谈谈今天的主题 万一真出现了这种情况 那存储我们电脑数据的内存卡永久删除的文件怎么恢复 内存卡永久删除的文件怎么恢复 内存卡永久删除的文件
  • gitee中git不能使用http协议提交项目

    git使用https协议提交项目的时候出现以下错误 error RPC failed curl 56 GnuTLS recv error 110 The TLS connection was non properly terminated
  • mixins详解

    实现一个日志功能 组件在挂载前打印 Component will mount 组件挂载后打印 Component did mount 不能忍受的写法 var AComponent React createClass componentWil
  • README_Albumentations

    一 文档 GitHub https github com albumentations team albumentations 官方文档 Albumentations Documentation 二 Installation pip ins
  • Amazon AWS —— 免费的午餐不好吃

    转自acgcss 众技术宅所周知 Amazon AWS 之前提供了 新用户凭有效信用卡免费试用一年 的活动 至今没有给出截止日期 虽说免费的午餐很诱人 而且由于信用卡的门槛 也避免了一部分的滥用 但是要安心吃好这顿饭 没有第一个吃螃蟹的人提
  • Python简要复习

    Python程序设计复习 Python基础知识 python的特点 兼具编译型和解释型特性 兼顾过程式 函数式和面向对象编程范式的通用编程语言 解释型语言无需像编译型需要一次性的编译成机器码 然后运行 而是由名叫解释器的程序动态的将源代码逐
  • 快手登录不上去 显示服务器繁忙,快手登录失败怎么回事

    大家好 我是时间财富网智能客服时间君 上述问题将由我为大家进行解答 快手登录失败的原因 1 可能是登录环境不太安全 2 可能是新手机的原因 3 可能是长期未登录或者是异地登录 4 可能是账号存在被盗风险或者已经被其他人登录了 建议修改密码
  • JAVA注解实现@WebServlet(一)

    JAVA注解实现 WebServlet 提示 需要些反射和文件操作 文章目录 JAVA注解实现 WebServlet 前言 一 创建注解RequestMapping 二 创建一个继承HttpServlet的类 三 创建过滤器 总结 前言 在
  • mysql invalid uuid_我为什么不建议开发中使用UUID作为MySQL的主键

    我是少侠露飞 学习塑造人生 技术改变世界 引言 我在之前一篇博客专门介绍了MySQL聚簇索引和非聚簇索引 附传送门 享学MySQL 系列 MySQL索引的数据结构 索引种类及聚簇索引和非聚簇索引 简单来说 就是我们设计表的时候 基本都会人为
  • 【linux kernel】linux中断管理—软中断

    linux中断管理 软中断 一 简介 软中断是linux预留给系统中对时间要求最为严苛和最重要的中断下半部使用的 并且 驱动中只有一些对时间极其敏感的模块使用了 例如 块设备和网络子系统 linux系统中定义了几种软中断类型 如下所示 in
  • 面试题:连续子数组的最大和与循环列表中的子数组最大和

    一 连续子数组的最大和 LeetCode 53 Maximum Subarray 题意 给定一个整数数组 nums 找到一个具有最大和的连续子数组 子数组最少包含一个元素 返回其最大和 定义dp i 为前i个数中的连续子数组的最大和 状态转
  • Jenkins+SonarQube 代码质量检测详解

    一 SonarQube 概述 1 SonarQube 简介 Sonar Qube是一个开源的代码分析平台 支持Java Python PHP JavaScript CSS等25种以上的语言 可以检测出重复代码 代码漏洞 代码规范和安全性漏洞
  • HTTP基础知识(用一万字帮助你入门)

    HTTP中文意思是超文本传输协议 它可以承载的内容有很多像html web Api css js等等 入门HTTP 一 初识 1 1背景知识 二 协议分析 2 1http的发展历程 2 2状态码 2 3缓存 2 4HTTP 2概述 2 5H
  • 利用Python实现黑客帝国代码雨,打造属于自己的黑客帝国

    导语 看安全类文章的时候 发现文章前面经常会加个代码雨的特效图 感觉拿来用python实现一下当成一个小案例还是不错的 让我们愉快地开始吧 开发工具 Python版本 3 6 4 相关模块 pygame模块 以及一些python自带的模块
  • 计算机打印错误,打印机错误正在打印处理方法,详细教您电脑打印机错误正在打印处理方法...

    打印机 是办公常见的打印设备 平时需要打印表格 订单什么的 但是有的时候打印东西会提示 正在打印打印错误 但是打印机连接完好 驱动也安装正确 这是怎么回事呢 下面 小编就教大家如何去解决打印机出现正在打印处理方法 如今在办公室中电脑和打印机
  • CVE-2021-3156 sudo堆溢出 漏洞分析

    漏洞简介 sudo 是 linux 系统管理指令 是允许系统管理员让普通用户执行一些或者全部的 root 命令的一个工具 它允许授权用户以 root 权限执行命令或者程序 sudo 的 sudoer 插件里面存在一个堆溢出漏洞 攻击者可以利