zoukankan      html  css  js  c++  java
  • Linux System Account SSH Weak Password Detection Automatic By System API

    catalog

    1. Linux弱口令攻击向量
    2. Linux登录验证步骤
    3. PAM
    4. 弱口令风险基线检查

    1. Linux弱口令攻击向量

    0x1: SSH密码暴力破解

    hydra -l root -P /root/passwdCracker/password.lst -t 16 -vV -e ns 112.124.51.10 ssh

    对于Linux系统来说,从外部破解系统密码的途径只有SSH这一条路,攻击者必须借助网络进行密码猜解尝试

    0x2: Linux SSH空口令帐号

    1. 通过Bash指令: cut -d: -f1 /etc/passwd,获取当前账户列表
    2. 遍历列表,调用getpwnam、getgrgid获取每个账户的pw_name、pw_shell,过滤出是shell为/bin/bash或/bin/sh(非/sbin/nologin)的账户
    3. 将过滤出的帐号在/etc/shadow中逐行查找,判断当前帐号是否为空口令(第二段为空)

    但是需要注意的是,用户账号刚创建时没有口令,但是被系统锁定,无法使用,必须为其指定口令后才可以使用,即使是指定空口令

    0x2: /etc/shadow本地暴力破解(john and ripper)

    tar zxvf john-1.ll
    cd john-1.8.0/src 
    make 
    make clean generic
    cd ../run
    ./unshadow /etc/passwd /etc/shadow > mypasswd
    ./john ./mypasswd

    Relevant Link:

    http://www.openwall.com/john/
    http://www.openwall.com/john/doc/EXAMPLES.shtml
    http://www.douban.com/note/326215912/
    http://darren.blog.51cto.com/1081720/634241
    http://linuxconfig.org/password-cracking-with-john-the-ripper-on-linux
    http://4506912.blog.51cto.com/4496912/999577
    http://www.kafan.cn/edu/42559051.html

    2. Linux登录验证步骤

    1. 根据用户名从/etc/passwd提取uid,gid以及家目录和shell配置 
    2. /etc/shadow根据uid核对密码 
    3. 进入shell控制阶段
    //此时并不意味着登录成功,若用户对应的shell为/sbin/nologin,则禁止该用户登录系统,但可以进行其他工作,譬如操作邮件,注: /etc/nologin.txt可以显示用户不能登录的原因
    4. 进行PAM验证,读取/etc/pam.d/login,必须通过验证才能最终登录,该文件内容通常如下 
    auth required pam_securetty.so
    //pam_securetty.so读取/etc/securrety,设定root用户可以登陆的终端,一般只设置tty而没有pts/0,telnet使用该模块故无法使用root用户登录,而sshd没有使用到该模块故不受限制
    
    auth required pam_stack.so service=system-auth
    auth required pam_nologin.so
    //pam_nologin.so 模块始终允许root用户可以登陆,其他用户只有在/etc/nologin文件不存在时可以登陆, 若/etc/nologin文件存在,则在输入正确用户名及任意密码后将该文件内容显示给用户
    
    account required pam_stack.so service=system-auth
    password required pam_stack.so service=system-auth
    session required pam_stack.so service=system-auth
    session optional pam_console.so
    +----------------+
      | application: X |
      +----------------+       /  +----------+     +================+
      | authentication-[---->----] Linux-   |--<--| PAM config file|
      |       +        [----<--/--]   PAM    |     |================|
      |[conversation()][--+      |          |     | X auth .. a.so |
      +----------------+  |    /  +-n--n-----+     | X auth .. b.so |
      |                |  |       __|  |           |           _____/
      |  service user  |  A      |     |           |____,-----'
      |                |  |      V     A
      +----------------+  +------|-----|---------+ -----+------+
                             +---u-----u----+    |      |      |
                             |   auth....   |--[ a ]--[ b ]--[ c ]
                             +--------------+
                             |   acct....   |--[ b ]--[ d ]
                             +--------------+
                             |   password   |--[ b ]--[ c ]
                             +--------------+
                             |   session    |--[ e ]--[ c ]
                             +--------------+

    0x1: 密码验证模块验证流程

    1. 执行/usr/bin/passwd,并输入密码 
    2. passwd调用PAM模块,读取/etc/pam.d/passwd配置文件并加载相应.so文件,对输入的密码进行验证 
    3. 将验证结果传回passwd程序 
    //注: passwd修改密码时,除了参考/etc/login.defs,还要满足/etc/pam.d/passwd模块的验证

    Relevant Link:

    http://blog.itpub.net/15480802/viewspace-1406088

    3. PAM

    PAM的英文全称是Pluggable Authentication Module系统,即此程序是有关执行用户鉴别和帐号维护的服务。鉴别部分通常通过: (合法性)质询-回应的交互来完成的。使用PAM,管理员可以通过不重编辑鉴定程序来定制一些使用方法,PAM有四部分组成

    1. libpam: 实现PAM API的库
    2. PAM配置文件: /etc/pam.conf
    3. 一套动态可装载二进制对象组成,常常用来调用一些处理实际鉴别(authentication)工作的服务模块
    4. 使用PAM API的系统命令组成,如login、us、ftp、telnet、etc..

    0x1: LIBPAM

    PAM API的认证(authentication)常规程序有三个主要函数组成

    1. pam_start( const char *service_name, const char *username, const struct pam_conv *conv, pam_handle_t **pamh_p );
    /*
    pam_start()和pam_end()函数是开始和结束一个PAM会话,传递给pam_start()
    函数的参数如下所示:
    + service_name: 一定义在pam.conf中的特殊服务 
    + username:     需要鉴权的用户登录名。
    + conv:         一指向pam_conf结构的指针
    + pamh_p:       一双精度指向pam_handle_t结构的指针。PAM构架会分配或不分配内存给这个结构,并且一应用程序不能直接访问它。它基本上是用来通过PAM构架(framework)来处理多个并发的PAM会话 
    
    pam_conv结构如下所示:
    struct pam_conv {
        int (*conv)(int num_msg, const struct pam_message **msg,
                    struct pam_response **resp, void *appdata_ptr);
        void *appdata_ptr;
    }
    
    *conv是指向PAM对话函数的指针,appdata_ptr指针指向特殊应用程序数据,它并不常用 
    */
    
    2. pam_end( pam_handle_t *pamh, int exit_status );
    /*
    pam_end()函数的参数由在pam_start()函数中填充的pam_handle_t*组成,并且返回的是exit状态。exit状态正常情况下是PAM_SUCCESS。pam_edn()会收回与pam_handle_t*相关联的内存,并且任何企图重用这个句柄将返回一个seg fault错误 
    */
    
    3. pam_authenticate( pam_handle_t *pamh, int flags );
    /*
    pam_authenticate()函数也再一次由通过pam_start()填充的pam_handle_t*组成,并且可选的标志(flags)可以传递给结构(framework)
    */

    另外一些可以应用于应用程序的PAM API函数如下所示

    + pam_set_item() - 为PAM会话写状态信息
    + pam_get_item() - 为PAM获得状态信息
    + pam_acct_mgmt() - 检查当前用户帐号是否合法
    + pam_open_session() - 开始一个新的会话
    + pam_close_session() - 关闭当前会话进程
    + pam_setcred() -管理用户信任资格(credentials)
    + pam_chauthtok() - 改变用户的鉴权(authentication)token牌
    + pam_strerror() - 返回错误字符串,类似与perror()函数

    0x2: PAM.CONF

    PAM配置文件通常位于/etc/pam.conf,它可以分为四个部分

    1. 鉴权(authentication)
    2. 帐号管理
    3. 会话管理
    4. 密码管理

    一个标准的一行配置如下所示:

    login  auth  required  /usr/lib/security/pam_unix.so.1  try_first_pass
        1) 第一栏是服务名,这是参照在pam_start()函数中的第一个参数。如果通过pam_start()的服务请求不列在pam.conf,则将使用默认的"other"服务。"other"服务名字可以是"su""rlogin",如果服务名不止一次说明,模块将会提示"stacked",并且framework将会通过第三栏的值来决定
        2) 第二栏指示这特定的服务将执行何种类型的行为,合法的值是: "auth"为鉴权行为、"account"为帐号管理、"session"为会话管理、"password"为密码管理。不是所有的应用程序需要使用每一个行为,如,su仅仅需要使用"auth"鉴权行为,"passwd"只需要来使用"password"管理行为 
        3) 第三栏作为一个控制类型的一栏。如果用户在鉴定行为中失败,它指示PAM 架构(framework)行为。此栏正确的值为:"requisite","required", "sufficient", "optional":
            3.1) + "requisite"意思指如果用户在鉴定(quthentication)时失败,PAM framework会立即返回一失败信息,其中没有其他模块调用。
            3.2) + "required"指示如果一个用户鉴定(quthentication)时失败,PAM framework只在调用其他所有模块后在返回失败信息。这样做的话用户会不知道哪个模块鉴权被拒绝,如果一个用户成功被鉴别,所有"required"模块必须返回成功
            3.3) + "optional"意思是用户将被允许访问即使鉴权失败,失败的结果是下一个在堆栈中的模块将被处理
            3.4) + "sufficient"指的是如果用户传递一这特定模块,PAM framework会立即返回成功,即使随后的模块有"requisite"或者"required"控制值,类似于"optional""sufficient"回允许访问即使鉴全步骤失败
        4) pam.conf第四栏中是认证模块的路径,各个系统路径不同,如在Linux-PAM系统中PAM模块在/usr/lib目录下,而Solaris在/usr/lib/security中维护模块 
        5) 第五栏是一个空格分开的module-dependent选项列表,是传递给认证模块调用 

    0x3: 模块 MODULES

    每一个PAM模块本质上是一个必须输出特定函数的库,这些函数可以被PAM framework调用(面向接口编程),通过库输出的函数有如下列表

    + pam_sm_authenticate()
    + pam_sm_setcred()
    + pam_sm_acct_mgmt()
    + pam_sm_open_session()
    + pam_sm_close_session()
    + pam_sm_chauthtok()

    如果实现者不决定在一模块内支持特定的操作,模块会为此操作返回PAM_SUCCESS,例如:如果一个模块设计成为不支持帐号管理(account management),pam_sm_acct_mgmt()函数会简单的返回PAM_SUCCESS
    pam_sm_authenticate()是按下面的方式声明的

    extern int pam_sm_authenticate( pam_handle_t *pamh, int flags, int argc, char **argv);
    上面的指针是指想一个PAM句柄--已经通过framework填充了,flags是应用程序调用pam_authenticate()传递给framework的一组标志,argc和argv是在pam.conf中此服务的选项参数的数字和值

    一个简单的pam_unix 模块中的pam_sm_authenticate()函数应该如下所示

    #include <security/pam_modules.h>
    #include <...>
    
    extern int
    pam_sm_authenticate( pam_handle_t *pamh, int flgs, int c, char **v )
    {
            char *user;
            char *passwd;
            struct passwd *pwd;
            int ret;
    
            /* ignore flags and optional arguments */
    
            if ( (ret = pam_get_user( ..., &user )) != PAM_SUCCESS )
               return ret;
            if ( (ret = pam_get_pass( ..., &passwd )) != PAM_SUCCESS )
               return ret;
            if ( (pwd = getpwnam(user)) != NULL ) {
               if ( !strcmp(pwd->pw_passwd, crypt(passwd)) )
                  return PAM_SUCCESS;
               else
                  return PAM_AUTH_ERR;
            }
    
            return PAM_AUTH_ERR;
    }

    0x4: 应用程序 APPLICATION

    一个应用程序处理PAM部分必须由pam_start()和pam_end()组成和一PAM对话函数。比较幸运的是,user-space PAM API定义的比较成熟和稳定所以对话函数是比较模板型的代码。一个简单的su PAM实现所下所示

    #include <security/pam_appl.h>
    #include <...>
    
    int su_conv(int, const struct pam_message **,
                struct pam_response **, void *);
    
    static struct pam_conv pam_conv = { su_conv, NULL };
    
    int
    main( int argc, char **argv )
    {
            pam_handle_t *pamh;
            int ret;
            struct passwd *pwd;
    
            /* assume arguments are correct and argv[1] is the username */
    
            ret = pam_start("su", argv[1], &pam_conv, &pamh);
            if ( ret == PAM_SUCCESS )
               ret = pam_authenticate(pamh, 0);
            if ( ret == PAM_SUCCESS )
               ret = pam_acct_mgmt(pamh, 0);
    
            if ( ret == PAM_SUCCESS ) {
               if ( (pwd = getpwnam(argv[1])) != NULL )
                  setuid(pwd->pw_uid);
               else {
                  pam_end(pamh, PAM_AUTH_ERR);
                  exit(1);
               }
            }
            pam_end(pamh, PAM_SUCCESS);
    
            /* return 0 on success, !0 on failure */
            return ( ret == PAM_SUCCESS ? 0 : 1 );
    }
    
    int
    su_conv(int num_msg, const struct pam_message **msg,
            struct pam_response **resp, void *appdata)
    {
            struct pam_message *m = *msg;
            struct pam_message *r = *resp;
    
            while ( num_msg-- )
            {
                    switch(m->msg_style) {
    
                    case PAM_PROMPT_ECHO_ON:
                         fprintf(stdout, "%s", m->msg);
                         r->resp = (char *)malloc(PAM_MAX_RESP_SIZE);
                         fgets(r->resp, PAM_MAX_RESP_SIZE-1, stdin);
                         m++; r++;
                         break;
    
                    case PAM_PROMPT_ECHO_OFF:
                         r->resp = getpass(m->msg);
                         m++; r++;
                         break;
    
                    case PAM_ERROR_MSG:
                         fprintf(stderr, "%s
    ", m->msg);
                         m++; r++;
                         break;
    
                    case PAM_TEXT_MSG:
                         fprintf(stdout, "%s
    ", m->msg);
                         m++; r++;
                         break;
    
                    default:
                            break;
                    }
            }
            return PAM_SUCCESS;
    }

    Relevant Link:

    http://www.xfocus.net/articles/200006/45.html
    http://www.helplib.net/s/linux.die/65_1666/man-3-pam-start.shtml

    3. 弱口令风险基线检查

    0x1: 检测方案

    1. 通过Bash指令: cut -d: -f1 /etc/passwd,获取当前账户列表
    2. 遍历列表,调用getpwnam、getgrgid获取每个账户的pw_name、pw_shell,过滤出是shell为非"/sbin/nologin"的账户
    3. 将上一步得到的账号在/etc/shadow中查找,过滤出password hash不为空的帐号(即只有显式通过passwd指令设置过密码的账号才可以登录)
    4. 遍历过滤出的"可登录帐号",调用pam_authenticate API/或者SSH login API进行登录尝试
    5. 记录并上报猜解出的弱口令

    0x2: 基于PAM API框架进行密码猜解尝试

    对于Linux弱口令风险来说,空口令风险不存在(帐号创建默认空口令但是被系统锁定)、john and ripper本地破解/etc/shadow(密码学原理)无法防御,因为不可能禁止用户读取这个文件,禁止读取会造成破坏正常业务的风险

    #include <stdlib.h>
    #include <iostream>
    #include <fstream>
    #include <security/pam_appl.h>
    #include <unistd.h>
    #include <string.h>
    
    // To build this:
    // g++ test.cpp -lpam -o test
    
    struct pam_response *reply;
    
    //function used to get user input
    int function_conversation(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
    {
      *resp = reply;
      return PAM_SUCCESS;
    }
    
    int main(int argc, char** argv)
    {
      if(argc != 2) 
      {
          fprintf(stderr, "Usage: check_user <username>
    ");
          exit(1);
      }
      const char *username;
      username = argv[1];
    
      const struct pam_conv local_conversation = { function_conversation, NULL };
      pam_handle_t *local_auth_handle = NULL; // this gets set by pam_start
    
      int retval;
    
      // local_auth_handle gets set based on the service 
      retval = pam_start("login", username, &local_conversation, &local_auth_handle);
      //retval = pam_start("common-auth", username, &local_conversation, &local_auth_handle);
    
      if (retval != PAM_SUCCESS)
      {
        std::cout << "pam_start returned " << retval << std::endl;
        exit(retval);
      }
    
      reply = (struct pam_response *)malloc(sizeof(struct pam_response));
    
      // *** Get the password by any method, or maybe it was passed into this function.
      //reply[0].resp = getpass("Password: ");
      reply[0].resp =  strdup("test");
      reply[0].resp_retcode = 0;
    
      retval = pam_authenticate(local_auth_handle, 0 | PAM_DISALLOW_NULL_AUTHTOK);
    
      if (retval != PAM_SUCCESS)
      {
        if (retval == PAM_AUTH_ERR)
        {
          std::cout << "Authentication failure." << std::endl;
        }
        else
        {
          std::cout << "pam_authenticate returned " << retval << std::endl;
        }
        exit(retval);
      }
    
      std::cout << "Authenticated." << std::endl;
    
      retval = pam_end(local_auth_handle, retval);
    
      if (retval != PAM_SUCCESS)
      {
        std::cout << "pam_end returned " << retval << std::endl;
        exit(retval);
      }
    
      return retval;
    }

    pam位于SSH的底层(更下一层),调用pam_authenticate api不受/sbin/nologin的影响,从本质上来说和ssh登录尝试是一样的,在代码中要注意的是,调用pam_start时传入"login"服务器名即可,因为仅仅需要使用"auth"鉴权行为

    pam_start中的参数service_name服务名指定了使用哪个配置文件,如service_name为"login",将使用/etc/pam.d/login这个配置

    在centos、ubuntu下测试都通过

    Relevant Link:

    http://stackoverflow.com/questions/5913865/pam-authentication-for-a-legacy-application/5970078#5970078
    http://stackoverflow.com/questions/5913865/pam-authentication-for-a-legacy-application
    http://www.jiancool.com/article/58701980019/;jsessionid=798A5A41EB356A6A1FC5C8E449523F6B

    Copyright (c) 2015 LittleHann All rights reserved

  • 相关阅读:
    Django 安装
    node上的__dirname和./的区别
    Mysql存储之ORM框架SQLAlchemy(一)
    selenium只打开一个浏览器窗口
    Mysql存储之原生语句操作(pymysql)
    汽车之家反爬
    javascript反混淆之packed混淆(一)
    python近期遇到的一些面试问题(三)
    [转载]关于python字典类型最疯狂的表达方式
    .net爬虫了解一下
  • 原文地址:https://www.cnblogs.com/LittleHann/p/4875535.html
Copyright © 2011-2022 走看看