一个类似 SSH 登录两步验证(2FA)作用的 Shell 脚本

原计划是想基于 PAM 做一个需要输入接收到的验证码才能进入 SSH 的两步验证的。

其实这个功能已有现有的软件包—— Google-Authenticator 可供使用,但是这个不是我现在想要的那种。

登录验证码 2FA

编辑/etc/profile.d/目录下新增一个文件,文件名暂定login_verify_code.sh

#!/bin/bash

# 登录验证码比对

# 发送验证码
code=$(openssl rand -base64 6 | cksum | cut -c1-6);
curl --silent --location 'https://gotify.cyzwb.com/message' --header 'X-Gotify-Key: xxxx' --header 'Content-Type: application/json' --data '{ "title": "验证码", "priority": 5, "message": "你的验证码:'"${code}"'" }'  >> null
curl --silent --location --request PUT 'https://ntfy.cyzwb.com' --header 'Content-Type: application/json' --header 'Authorization: Bearer tk_xxxxx' --data '{ "title": "验证码", "topic": "2mBN3DwlwZP2Grhf", "message": "你的验证码:'"${code}"'" }' >> null

# 比较验证码

# 安全增强版:限制重试次数,防止暴力破解

MAX_RETRIES=5  # 最大重试次数
RETRY_COUNT=0
TARGET_NUMBER=$code

echo "请输入验证码(最多${MAX_RETRIES}次机会)"

while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
    read -s -p ">>> " input

    # 验证输入是否为纯数字
    if ! [[ "$input" =~ ^[0-9]+$ ]]; then
        echo "错误:请输入有效的数字!"
        ((RETRY_COUNT++))
        continue
    fi

    # 验证目标数字
    if [ "$input" -eq "$TARGET_NUMBER" ]; then
        echo "验证通过。"
        return 0 
    else
        ((RETRY_COUNT++))
        remaining=$((MAX_RETRIES - RETRY_COUNT))
        echo "输入错误,剩余${remaining}次机会。"
        
        # 失败后增加延迟,减缓暴力破解速度
        sleep 1
    fi
done

# 达到最大重试次数后的处理
echo "错误:重试次数过多,程序强制退出。"
exit 1

脚本启动时随机生成6位数字,随后再将生成的6位数发送到 Gotify 以及 NTFY 两个消息通知服务。之所以一并发送到两处,那是因为我就部署了两套不同的这类服务。

题外话

本来以为很好的以为可以在登录 SSH 后挂起终端直至输入正确的验证码,谁料在我后面研究用 C 语言写个 PAM 模块的时候,在调试中无意间的一次Ctrl-c就给退出了。

也曾想过用软件将 BASH 脚本转成 C 来替换这个脚本的。

在用 C 写好 PAM 模块之后准备试试 Terminal 会不会也需要验证码才能进入系统的,在准备启动 VNC 进入终端时发现, VNC 因为一些错误无法启动。

错误一

 SConnection: AuthFailureException: Authentication failure
 VNCSConnST:  closing 147.182.182.196::42636: Authentication failure
 EncodeManager: Framebuffer updates: 0
 EncodeManager:   Total: 0 rects, 0 pixels
 EncodeManager:          0 B (1:-nan ratio)
 Connections: closed: 147.182.182.196::42636
 ComparingUpdateTracker: 0 pixels in / 0 pixels out
 ComparingUpdateTracker: (1:-nan ratio)
 Connections: accepted: 147.182.182.196::42644
 SConnection: Client needs protocol version 3.3
 SConnection: AuthFailureException: Authentication failure
请输入验证码(最多5次机会)
错误:请输入有效的数字!
错误:请输入有效的数字!
错误:请输入有效的数字!
错误:请输入有效的数字!
错误:请输入有效的数字!
错误:重试次数过多,程序强制退出。
 VNCSConnST:  closing 147.182.182.196::42644: Authentication failure
 EncodeManager: Framebuffer updates: 0
 EncodeManager:   Total: 0 rects, 0 pixels
 EncodeManager:          0 B (1:-nan ratio)
 Connections: closed: 147.182.182.196::42644
 ComparingUpdateTracker: 0 pixels in / 0 pixels out
 ComparingUpdateTracker: (1:-nan ratio)
 ComparingUpdateTracker: 0 pixels in / 0 pixels out
 ComparingUpdateTracker: (1:-nan ratio)
X connection to :0 broken (explicit kill or server shutdown).
Killing Xtigervnc process ID 30371... success!
============================================================================================

Session startup via '/etc/X11/Xtigervnc-session' cleanly exited too early (< 3 seconds)!

Maybe try something simple first, e.g.,
	tigervncserver -xstartup /usr/bin/xterm
The X session cleanly exited!
The Xtigervnc server cleanly exited!
root@UnitedStates:~# 

错误二

New Xtigervnc server 'UnitedStates.YunLtd.cyzwb.com:0 (root)' on port 5900 for display :0.
Use xtigervncviewer -SecurityTypes VncAuth,TLSVnc -passwd /tmp/tigervnc.8ohvY0/passwd UnitedStates.YunLtd.147180.com:0 to connect to the VNC server.


=================== tail /root/.vnc/UnitedStates.YunLtd.147180.com:0.log ===================
================================ 服务器运行时间 ================================
up 1 day, 10 hours, 39 minutes
启动时间: 2026-05-11 01:26:56
================================= 内存占用情况 =================================
               total        used        free      shared  buff/cache   available
Mem:           768Mi       110Mi       467Mi       636Ki       190Mi       657Mi
Swap:          768Mi          0B       768Mi
================================= 其他硬件信息 =================================
CPU 核心数: 1
================================= 当前登录用户 =================================
root     pts/0        2026-05-12 11:33 (240e:xxx.xxx.xx)
root     pts/1        2026-05-12 08:57 (240e:xxx.xxx.xx)
root     pts/2        2026-05-12 09:04 (240e:xxx.xxx.xx)
root     pts/3        2026-05-12 11:58 (113.xxx.xxx.xxx)
root     pts/4        2026-05-12 12:00 (113.xxx.xxx.xxx)
root     pts/5        2026-05-12 12:03 (113.xxx.xxx.xxx)
===================================== tmux 会话 =====================================
没有运行中的 tmux 会话

Tue May 12 12:06:14 2026
 Connections: accepted: 164.90.151.212::58974
 SConnection: Client needs protocol version 3.3
请输入验证码(最多5次机会)
错误:请输入有效的数字!
错误:请输入有效的数字!
错误:请输入有效的数字!
错误:请输入有效的数字!
错误:请输入有效的数字!
错误:重试次数过多,程序强制退出。
 SConnection: AuthFailureException: Authentication failure
 VNCSConnST:  closing 164.90.151.212::58974: Authentication failure
 EncodeManager: Framebuffer updates: 0
 EncodeManager:   Total: 0 rects, 0 pixels
 EncodeManager:          0 B (1:-nan ratio)
 Connections: closed: 164.90.151.212::58974
 ComparingUpdateTracker: 0 pixels in / 0 pixels out
 ComparingUpdateTracker: (1:-nan ratio)
 ComparingUpdateTracker: 0 pixels in / 0 pixels out
 ComparingUpdateTracker: (1:-nan ratio)
X connection to :0 broken (explicit kill or server shutdown).
Killing Xtigervnc process ID 30433... success!
============================================================================================

Session startup via '/etc/X11/Xtigervnc-session' cleanly exited too early (< 3 seconds)!

Maybe try something simple first, e.g.,
	tigervncserver -xstartup /usr/bin/xterm
The X session cleanly exited!
The Xtigervnc server cleanly exited!
root@UnitedStates:~# 
Socket error Event: 32 Error: 10053.
Connection closing...Socket close.

Connection closed by foreign host.

Disconnected from remote host(xxx.xxx.xxx.xxx) at 01:08:41.

Type `help' to learn how to use Xshell prompt.

结论

反正都已经编写好 PAM 模块,至于这个刚准备没24小时的脚本就这么舍弃掉了。不过也不是没有用的,可以用来做需要动态码才能启动的运行脚本。当然需要将脚本加密/混淆后这个才有意义,这不想着加密/混淆或者是转成 C 语言。

ChiuYut

2026年05月12日

发布者

ChiuYut

咦?我是谁?这是什么地方? Ya ha!我是ChiuYut!这里是我的小破站!