前言
在渗透过程中我们拿下一台主机,往往需要一些权限维持后门手段,一般留后门手段如:增加超级用户、PROMPT_COMMAND变量、放置后门程序等等这些方法往往比较容易被发现,/etc/passwd是管理员的重点检查文件,PROMPT_COMMAND变量又需要管理员登录才能触发,后门程序也可能被管理员找到可疑进程发现,因此想到是否能重新安装openssh在其中中加入后门,而找了一圈分析openssh的文章资料发现又比较少,于是尝试自己分析一波,在openssh中加入后门
分析源码:
首先通过github下载openssh的源码,而想要在ssh中加入后门就需要分析sshd的部分,我们可以在源代码中直接找到sshd.c文件,找到main函数浏览整个流程发现ssh用户验证主要在一个函数:
而参数ssh可以猜测ssh结构体保存了用户的验证信息,我们找到了ssh结构体的定义
ssh 结构体:
ssh结构体保存了所有的用户输入数据,包括套接字、密码、套接字、请求类型
do_authentication2(ssh) 验证函数
ssh_dispatch_init(ssh,fun) :
初始化 ssh->dispatch[] 数组为dispatch_protocol_error()函数,此函数记录日志并输出错误信息
ssh_dispatch_set(ssh,SSH2_MSG_SERVICE_REQUEST,fun):
定义ssh2_msg_service_request类型处理函数为input_service_requests()
ssh_dispatch_run_fatal() && ssh_dispatch_run()
ssh_dispatch_run_fatal()调用ssh_dispatch_run()函数判断函数结果,失败则记录日志
ssh_dispatch_run(ssh,mode,done) 循环,直到验证成功退出循环,或者超过最大重试次数
ssh_packet_read_seqnr(ssh,type,seqnr)!=0 | ssh_packet_read_poll_seqnr(ssh,type,seqnr)
获取socket收到的数据,存储在ssh结构体中,type保存收到的请求类型
r = (*ssh->dispatch[type])(type,seqnr,ssh)
调用前面设置的函数处理数据,数据都在ssh结构体中,done指向ssh->authctxt->success 处理成功时处理函数会改变done的结果,done为True时循环结束
继续跟进,sshd收到第一条请求时会调用函数 input_service_request()
input_service_request(type,seq,ssh) 处理service_request
sshpkt_get_cstring(ssh,&service,null) //获取请求类型,保存在&service
strcmp(service,'ssh-userauth') //判断如果成立则断开连接,并设置SSH2_MSG_USERAUTH_REQUESTS处理函数,也就是用户验证请求,根据此函数的内容,可以判断 sshd启动后收到请求触发设置auth_rquest处理函数
ssh_dispatch_set(ssh,SSH2_MSG_USERAUTH_REQUEST,&input_userauth_requests)//设置验证处理函数,现在可以开始验证用户了
sshpkt_start(ssh,SSH2_MSG_SERVICE_ACCEPT) || sshpkt_put_cstring(ssh,service) || sshpkt_send(ssh) || ssh_packet_write_wait(ssh) //如果strmcp没有成立,正常处理请求
input_userauth_request(type,seq,ssh) 处理用户验证
通过method选择其中一个模式的处理函数,而method通过sshpkt_get_cstring()获取到,这里的method大概是通过客户端发送的数据包获取,
查看authmethod_lookup函数最终的返回的最终结果:
> m = authmethod_lookup(authctxt,method){
> return authmethods[i]
> }
authmethod_lookup()函数最终返回为authmethods结构体中存储的结构体:
可以猜测数组中的每一项都对应了openssh不同的验证模式,kdbint为keyboard_interactive模式、pubkey公私钥验证模式,而结构体都是类似的结构:
m->userauth()调用了结构体中的函数,结构体中只有userauth_kbdint是函数,分析得出passwd验证模式的具体调用流程:
m->userauth(ssh);
-> userauth_passwd(ssh)[auth2-passwd.c]
-> auth_passwd(ssh,passwd)[auth-passwd.c]
-> sshpam_auth_passwd[auth-passwd.c]
userauth_finish(ssh,authenticated,method,null)
看到这一步可以想到修改流程中验证函数的返回值,绕过验证,但是经过测试,直接修改userauth_passwd()(auth2-passwd.c)的返回值为0, 并不能绕过验证并且会导致输入密码错误直接崩溃报错
而修改sshpam_auth_passwd(authctxt,password)的返回值也没有达到效果
重新尝试分析keyboard-interactive模式调用流程,发现userauth_kbdint函数有两个(其他类型的同样):
调用流程:
m->userauth(ssh)
-> userauth_kbdint(ssh)]auth2-kbdint.c] debug: keyboard-interactive devs;
-> auth2_challenge(ssh,devs)[auth2-chall.c] debug: auth2_challenge: user=%s devs=%s
-> kbdint_alloc(devs)
auth2_challenge_start(ssh)
此时,进入sshd debug模式,查看执行流程,发现在input_userauth_request()请求处理函数中,验证为keyboard-interactive模式,也就是debug2中的method值为keyboard……
而在之前测试过程中发现,重新编译后的sshd走的是keyboard-interactive模式,系统自带的sshd走的是password模式,经过对比sshd_config文件,发现启用了ChallengeResponseAuthentication ,可见当sshd_config中关闭challengeResponseAuthentication时sshd走的password模式验证。(需要开启UsePAM)
至此在sshd_config中加入ChallengeResponseAuthentication no,我们再尝试修改sshpam_auth_passwd()函数可以实现绕过验证效果:
任意密码登录,至此我们再加入判断条件,就可以实现openssh后门
sshpam_auth_passwd(authctxt,password) 修改函数加入后门
参考资料
https://man.openbsd.org/sshd.8
https://github.com/openssh/openssh-portable