一、需求

最近业务部分希望在几百台主机上安装python expect包(pexpect),向其了解了下需求,其需求是通过4a审计平台登陆到他们业务的任一主机后(默认是user1 用户登陆),其希望可以在user1下执行某命令后,可以向user2、user3、user4几个用户免密切换,除此之外的其他用户不能切换。了解需求后,发现可以通过如下方法实现该需求:

  1. 通过expect(包括其他语言的衍生类)通过自动匹配并输入密码实现;
  2. 通过sudo配置NOPASSWD su实现(将需要切换的用户在sudo里配死);
  3. 通过OS底层函数进行uid、gid切换(需要s权限位配置和相关权限控制)

像expect、python、java、ruby等语言实现的expect的代码是不加密或编译后弱加密的,都可以读取源码后获取其密码内容。尽管shell 类语言也可以加密,但相对于更底层的语言c、c++、golang等编译出的二进制文件相比还是太弱鸡。所以本篇就以golang和c语言为例实现下这个需求。

二、golang expect实现

github上找了几个expect相关功能的模块,发现都不成功,后来找了一个可用的模块:github.com/ThomasRooney/gexpect ,具体代码如下:

 1package main
 2import (
 3    "fmt"
 4    gexpect "github.com/ThomasRooney/gexpect"
 5)
 6func main() {
 7    child, err := gexpect.Spawn("su - zabbix")
 8    if err != nil {
 9        panic(err)
10    }
11    child.Expect("Password")
12    child.SendLine("zabbixpasswd")
13    child.Interact()
14}

上面并没有进行用户控制,如有需要可以使用os/user模块或syscall模块实现。这部分代码是以切换zabbix用户为例,如果需要匹配多个用户,可以通过switch……case语句实现。

虽然上面的代码实现了功能,但是副作用也比较大。比如:输入的命令会重复输出,ps执行不能全屏幕,无法 tab补全等。虽然后面也尝试和其他pty和term模块进行整合,但功力不够,并没有成功。

三、c语言实现

网上找了下su命令的源码看了下,发现其内部调用了两个头文件unistd.h和pwd.h ,前都用以实现uid、gid、euid、egid等的切换和调用(需s权限位),后都用于读取/etc/passwd文件,返回结构体中相关的值。由于unistd.h的帮助信息较多,这里只截取下pwd.h的相关帮助信息,如下图:

pwd-header
pwd-header

这里使用的getpwnam方法,用于匹配哪一个用户可以调用该程序实现用户功能切换,同上面golang一样,这里以只切换zabbix为例,需要匹配多个用户,可以通过switch……case语句实现。代码如下:

 1#include 
 2#include 
 3#include 
 4#include 
 5int main(void)
 6{
 7    int current_uid = getuid();
 8    printf("My UID is: %d. My GID is: %dn", current_uid, getgid());
 9    system("/usr/bin/id");
10    if (setuid(0))
11    {
12        perror("setuid");
13        return 1;
14    }
15    struct passwd *pw;
16    pw = getpwnam("kiosk");
17    if(pw->pw_uid==current_uid){  //进行用户控制,只有kiosk用户可以执行切换
18        system("/usr/bin/su - zabbix");
19    }
20    return 0;
21}

可以使用如下命令进行编译和设置用户属性和s权限位:

1gcc -o setuid setuid.c
2chown root.root setuid
3chmod u+s setuid 或chmod 4755 setuid 

pwd.h相关的用户可以参考如下两篇文章:

https://aljensencprogramming.wordpress.com/tag/etcpasswd/

https://www.adampalmer.me/iodigitalsec/2009/10/03/linux-c-setuid-setgid-tutorial/

后来查了下golang的手册,发现syscall 模块包中也包含有上面的相关方法,理论也可以实现以上功能。具体可以参考官方文档:

https://golang.org/pkg/syscall/

s权限位比较便利,但也比较危险,使用不当很容易被提权,所以系统下的很多安全工具,都会通过find 查看带有s 权限的文件。当然本篇实现的这个需求,也有一个好处,就是不论主机的其他用户的密码如何更换,都可以成功进行用户切换,这点其实和sudo实现的功能是类似的。