分类: LINUX
2014-07-30 15:00:29
PAM是个什么东西,词典上没有。
放狗查了下,这是个简写,全称是:Pluggable Authentication Modules,中文叫“可插入认证模块”。 它是一种统一的认证方案。PAM 让您能随时改变您的认证方法以及需求,并且不需要重新编译任何代码就封装了所有本地认证方法。具体见 。 对于 PAM 您只需要做:
======================================== 我们看看shadow文件的格式: root@localhost:~# cat /etc/shadow root:$1$Bg1H/4mz$X89TqH7tpi9dX1B9j5YsF.:14838:0:99999:7::: daemon:*:14838:0:99999:7::: bin:*:14838:0:99999:7::: sys:*:14838:0:99999:7::: sync:*:14838:0:99999:7::: games:*:14838:0:99999:7::: man:*:14838:0:99999:7::: lp:*:14838:0:99999:7::: mail:*:14838:0:99999:7::: news:*:14838:0:99999:7::: uucp:*:14838:0:99999:7::: proxy:*:14838:0:99999:7::: www-data:*:14838:0:99999:7::: backup:*:14838:0:99999:7::: list:*:14838:0:99999:7::: irc:*:14838:0:99999:7::: gnats:*:14838:0:99999:7::: nobody:*:14838:0:99999:7::: libuuid:!:14838:0:99999:7::: Debian-exim:!:14838:0:99999:7::: statd:*:14838:0:99999:7::: sshd:*:14838:0:99999:7::: test::14879:0:99999:7::: 格式是: {用户名}:{加密后的密码字符串}:{口令最后修改时间距1970年1月1日的天数}:{ 口令能被修改之前的天数(防止修改口令,然后立刻将它改回到老口令)}:{口令必须被修改之后的天数}:{口令期满前的天数}:{口令期满后的天数}:{保留} shadow是个可读文件,普通用户没有读写权限,超级用户权限为可读写。 如果密码字符串为*,表示系统用户不能被登入,为!表示用户名被禁用,如果密码字符串为空,表示没有密码,通过passwd -d 用户名 可以清空一个用户的口令。 具体shadow可参考,尽管这是一份已经过时了的文档。 下面探讨下shadow中的密码加密问题: 还是以上面的root用户为例: root:$1$Bg1H/4mz$X89TqH7tpi9dX1B9j5YsF.:14838:0:99999:7::: 其中的密码域为$1$Bg1H/4mz$X89TqH7tpi9dX1B9j5YsF. ,参考linux标准的passwd.c源文件,在pw_encrypt函数中可以找到加密方法: 40 char *pw_encrypt (const char *clear, const char *salt) 41 { 42 static char cipher[128]; 43 char *cp; 44 45 cp = crypt (clear, salt); 46 if (!cp) { 47 /* 48 * Single Unix Spec: crypt() may return a null pointer, 49 * and set errno to indicate an error. The caller doesn't 50 * expect us to return NULL, so... 51 */ 52 perror ("crypt"); 53 exit (1); 54 } 55 56 /* The GNU crypt does not return NULL if the algorithm is not 57 * supported, and return a DES encrypted password. */ 58 if (salt && salt[0] == '$' && strlen (cp) <= 13) 59 { .......... 79 fprintf (stderr, 80 _("crypt method not supported by libcrypt? (%s)\n"), 81 method); 82 exit (1); 83 } 84 85 if (strlen (cp) != 13) 86 return cp; /* nonstandard crypt() in libc, better bail out */ 87 strcpy (cipher, cp); 88 89 return cipher; 90 } 也就是说加密用明文密码和一个叫salt的东西用crypt()加密生成密文。 再来看看crypt的帮助: 可发现原来crypt密文里是由三部分组成的,即:$id$salt$encrypted 目前常用的是当id为1时,使用md5加密,id为5,采用SHA256进行加密,id为6采用SHA512进行加密。 分析上面的函数,可看出我们的shadow密码中,直接把$id$salt$encrypted 当做salt参数带入进行crypt加密。 那好,我们可以写个简短的代码进行试验: #include #include #include #include #include #include int main(int argc, char *argv[]) { if(argc < 2) { printf("no usrname input"); return 1; } if (geteuid() != 0) fprintf(stderr, "must be setuid root"); struct passwd *pwd; pwd = getpwnam(argv[1]); if(pwd ==NULL) printf("no username found.\n"); else { printf("passwd: %s\n", pwd->pw_passwd); if(strcmp(pwd->pw_passwd, "x") == 0) { printf("shadow used.\n"); struct spwd *shd= getspnam(argv[1]); if(shd != NULL) { static char crypt_char[80]; strcpy(crypt_char, shd->sp_pwdp); char salt[13]; int i=0,j=0; while(shd->sp_pwdp[i]!='\0'){ salt[i]=shd->sp_pwdp[i]; if(salt[i]=='$'){ j++; if(j==3){ salt[i+1]='\0'; break; } } i++; } if(j<3)perror("file error or user cannot use."); if(argc==3) printf("salt: %s, crypt: %s\n", salt, crypt(argv[2], salt)); printf("shadowd passwd: %s\n", shd->sp_pwdp); } } } return 0; } 保存后执行gcc passwd.c -lcrypt -o passwd 编译成功后运行./passwd root 123 其中./passwd是生成的命令,root是我们用来测试的账户,123是测试系统中root用户的密码,执行的结果是: passwd: x shadow used. salt: $1$Bg1H/4mz$, crypt: $1$Bg1H/4mz$X89TqH7tpi9dX1B9j5YsF. shadowd passwd: $1$Bg1H/4mz$X89TqH7tpi9dX1B9j5YsF. 可见,我们得到了和系统shadow文件下相同结果的密文。 根据我们的运行结果我们可以看到,在root用户的shadow中,他的salt是$1$Bg1H/4mz$ 我们之前也是用这个salt来进行加密匹配的。但是,问题是:这个salt到底是怎么来的?? 还是分析标准的passwd.c, 在passwd.c中,找到了生成salt的函数:crypt_make_salt 201 char *crypt_make_salt (const char *meth, void *arg) 202 { 203 /* Max result size for the SHA methods: 204 * +3 $5$ 205 * +17 rounds=999999999$ 206 * +16 salt 207 * +1 \0 208 */ 209 static char result[40]; 210 size_t salt_len = 8; 211 const char *method; 212 213 result[0] = '\0'; 214 215 if (NULL != meth) 216 method = meth; 217 else { 218 if ((method = getdef_str ("ENCRYPT_METHOD")) == NULL) 219 method = getdef_bool ("MD5_CRYPT_ENAB") ? "MD5" : "DES"; 220 } 221 222 if (!strcmp (method, "MD5")) { 223 MAGNUM(result, '1'); 224 #ifdef USE_SHA_CRYPT 225 } else if (!strcmp (method, "SHA256")) { 226 MAGNUM(result, '5'); 227 strcat(result, SHA_salt_rounds((int *)arg)); 228 salt_len = SHA_salt_size(); 229 } else if (!strcmp (method, "SHA512")) { 230 MAGNUM(result, '6'); 231 strcat(result, SHA_salt_rounds((int *)arg)); 232 salt_len = SHA_salt_size(); 233 #endif 234 } else if (0 != strcmp (method, "DES")) { 235 fprintf (stderr, 236 _("Invalid ENCRYPT_METHOD value: '%s'.\n" 237 "Defaulting to DES.\n"), 238 method); 239 result[0] = '\0'; 240 } 241 242 /* 243 * Concatenate a pseudo random salt. 244 */ 245 assert (sizeof (result) > strlen (result) + salt_len); 246 strncat (result, gensalt (salt_len), 247 sizeof (result) - strlen (result) - 1); 248 249 return result; 250 } 除了一大段条件判断语句,其实最重要的只有一句gensalt(salt_len)。 看看gensalt的定义: 167 168 static char *gensalt (unsigned int salt_size) 169 { 170 static char salt[32]; 171 172 salt[0] = '\0'; 173 174 assert (salt_size >= MIN_SALT_SIZE && 175 salt_size <= MAX_SALT_SIZE); 176 seedRNG (); 177 strcat (salt, l64a (random())); 178 do { 179 strcat (salt, l64a (random())); 180 } while (strlen (salt) < salt_size); 181 salt[salt_size] = '\0'; 182 183 return salt; 184 } 哦,原来神秘的salt只是某个的固定长度的随机的可见的字符串而已。 每次改写密码时,都会再随机生成一个这样的salt。而用户登入时,会拿用户登入的明文密码经过上述演示的步骤生成密文后和shadow里的密码域进行比较。 有了上述的分析,要暴利破解linux下的口令也不是什么问题,但前提是你有机会拿到shadow文件,这个前提条件貌似很难。 本文只是篇技术分析文章,没有其它任何初衷。 |