在menu.sh 裡增加功能

要在menu.sh 裡增加功能 1. scp功能, 但要讓user自己設定引數與參數 2. ssh 到特定主機, 但要user自己設定登入的帳號名稱

可以,但千萬不要直接讓使用者在 menu.sh 裡「自由輸入整串 scp/ssh 參數」然後丟進 shell 執行(會被用 ;$( )-oProxyCommand 等方式繞出去)。正確做法是:

  • 只讓 user 填「你允許的欄位」(目的主機、帳號、來源檔名、目的路徑…)
  • 其餘 SSH/SCP 參數由你固定寫死
  • 白名單主機 + 路徑限制(只允許在特定資料夾內操作)
  • 用 wrapper script(root 擁有)承接,menu 只呼叫 wrapper

下面給你可直接套用的 SOP。


0) 先做一個專用交換目錄(限制檔案範圍)

sudo mkdir -p /home/optim/transfer
sudo chown optim:optim /home/optim/transfer
sudo chmod 700 /home/optim/transfer

1) 建立 scp wrapper(允許 user 填「引數/參數」但被你控住)

1-1 建立 /usr/local/bin/optim_scp

sudo vi /usr/local/bin/optim_scp

貼上(把 ALLOWED_HOSTS 改成你允許的目的主機):

#!/bin/bash
set -euo pipefail

ALLOWED_HOSTS=("10.0.0.10" "server1.internal" "server2.internal")
BASE_DIR="/home/optim/transfer"

is_allowed_host() {
  local h="$1"
  for x in "${ALLOWED_HOSTS[@]}"; do
    [[ "$h" == "$x" ]] && return 0
  done
  return 1
}

valid_user() {
  [[ "$1" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]]
}

# 僅允許相對路徑(避免 /etc/passwd 之類)
valid_rel_path() {
  local p="$1"
  [[ "$p" != /* ]] && [[ "$p" != *".."* ]] && [[ "$p" =~ ^[A-Za-z0-9._/-]+$ ]]
}

echo "=== SCP (受控) ==="
echo "僅允許檔案位於: $BASE_DIR"
echo "允許主機: ${ALLOWED_HOSTS[*]}"
echo

read -r -p "方向 (1=上傳到遠端, 2=從遠端下載): " mode
read -r -p "遠端主機: " host
read -r -p "遠端登入帳號: " ruser

if ! is_allowed_host "$host"; then
  echo "ERR: 主機不在白名單"
  exit 1
fi
if ! valid_user "$ruser"; then
  echo "ERR: 帳號格式不合法"
  exit 1
fi

# 固定 SSH 選項:禁用危險玩法
SSH_OPTS=(
  -o "BatchMode=no"
  -o "PermitLocalCommand=no"
  -o "ClearAllForwardings=yes"
  -o "ProxyCommand=none"
  -o "RequestTTY=no"
)

case "$mode" in
  1)
    # 上傳:BASE_DIR/相對路徑 -> 遠端:目的路徑
    read -r -p "本機檔案(相對於 transfer/): " local_rel
    read -r -p "遠端目的路徑(例如 /tmp 或 /home/user/transfer ): " remote_path

    if ! valid_rel_path "$local_rel"; then
      echo "ERR: 本機路徑必須是相對路徑且不可含 .."
      exit 1
    fi
    if [[ "$remote_path" != /* ]] || [[ "$remote_path" == *".."* ]]; then
      echo "ERR: 遠端目的路徑必須是絕對路徑且不可含 .."
      exit 1
    fi

    src="$BASE_DIR/$local_rel"
    if [[ ! -f "$src" ]]; then
      echo "ERR: 找不到檔案: $src"
      exit 1
    fi

    /usr/bin/scp "${SSH_OPTS[@]}" -- "$src" "${ruser}@${host}:${remote_path%/}/"
    echo "OK: 上傳完成"
    ;;

  2)
    # 下載:遠端:檔案 -> BASE_DIR/目的檔名
    read -r -p "遠端檔案完整路徑(例如 /tmp/a.log): " remote_file
    read -r -p "本機目的檔名(放在 transfer/ 下): " local_name

    if [[ "$remote_file" != /* ]] || [[ "$remote_file" == *".."* ]]; then
      echo "ERR: 遠端檔案路徑必須是絕對路徑且不可含 .."
      exit 1
    fi
    if ! [[ "$local_name" =~ ^[A-Za-z0-9._-]+$ ]]; then
      echo "ERR: 本機目的檔名不合法"
      exit 1
    fi

    dst="$BASE_DIR/$local_name"
    /usr/bin/scp "${SSH_OPTS[@]}" -- "${ruser}@${host}:${remote_file}" "$dst"
    /bin/chown optim:optim "$dst"
    /bin/chmod 600 "$dst"
    echo "OK: 下載完成 -> $dst"
    ;;

  *)
    echo "ERR: 模式錯誤"
    exit 1
    ;;
esac

1-2 權限

sudo chown root:root /usr/local/bin/optim_scp
sudo chmod 755 /usr/local/bin/optim_scp

2) 建立 ssh wrapper(固定主機,但 user 可自填登入帳號)

2-1 建立 /usr/local/bin/optim_ssh

sudo vi /usr/local/bin/optim_ssh

貼上(把 TARGET_HOST 改成你指定的那台):

#!/bin/bash
set -euo pipefail

TARGET_HOST="10.0.0.10"

valid_user() {
  [[ "$1" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]]
}

echo "=== SSH (受控) ==="
echo "目的主機固定: $TARGET_HOST"
read -r -p "請輸入登入帳號: " ruser

if ! valid_user "$ruser"; then
  echo "ERR: 帳號格式不合法"
  exit 1
fi

exec /usr/bin/ssh \
  -o "ClearAllForwardings=yes" \
  -o "PermitLocalCommand=no" \
  -o "ProxyCommand=none" \
  -o "ServerAliveInterval=30" \
  -o "ServerAliveCountMax=3" \
  -l "$ruser" \
  "$TARGET_HOST"

2-2 權限

sudo chown root:root /usr/local/bin/optim_ssh
sudo chmod 755 /usr/local/bin/optim_ssh

3) 把功能加到 menu.sh

在你原本的 case 裡新增選項,例如:

echo "5) SCP 檔案傳輸 (受控)"
echo "6) SSH 到指定主機 (帳號自填)"

並加入:

5)
  /usr/local/bin/optim_scp
  ;;
6)
  /usr/local/bin/optim_ssh
  ;;

4) 重要提醒(避免你以為「外部 scp 也能用」)

你現在用的是 sshd_configForceCommand /opt/optim-menu/menu.sh,所以:

  • 使用者登入後在選單內執行 scp/ssh(由 server 端發起):可行
  • 從使用者電腦用 scp optim@你的主機:... 直接拉檔:通常會失敗(因為 scp 需要走 ssh 的 scp 子系統/命令,而你強制進選單)

如果你也需要「外部 scp 到這台主機」那要改成用 Match 區分 Subsystem sftp 或針對 sftp/scp 做不同規則(但那是另一套設計)。


如果你告訴我:

  • 你要允許的遠端主機清單(多台?一台?)
  • scp 只允許上傳?只允許下載?或都要?
  • 遠端允許的目錄範圍(例如只准 /home/*/upload

我可以把 wrapper 再加上「遠端路徑白名單」與「大小/副檔名限制」避免被拿去搬敏感檔。

信心指數:92%

ubuntu 24.04 限制使用者登入後僅能使用選單中的指令

以下是一套在 Ubuntu 24.04 中,限制使用者 optim 以「密碼登入 SSH 後,只能透過選單執行指定指令」的完整 SOP
此作法適合維運帳號、代管帳號、委外人員,避免任意下指令。


一、設計原則(先說清楚邏輯)

  1. SSH 可用密碼登入
  2. 登入後 不給 Shell
  3. 只執行一個「選單程式(Menu Script)」
  4. 選單內只呼叫 白名單指令
  5. 使用者無法:
    • cd
    • vi / nano
    • bash
    • ssh 到別台
    • Ctrl+C 跳出

二、建立使用者 optim(若已存在可略過)

sudo useradd -m optim
sudo passwd optim

三、準備「受控選單腳本」

1️⃣ 建立目錄

sudo mkdir -p /opt/optim-menu
sudo chown root:root /opt/optim-menu
sudo chmod 755 /opt/optim-menu

2️⃣ 建立選單腳本

sudo vi /opt/optim-menu/menu.sh

內容如下(可依需求調整指令):

#!/bin/bash

while true; do
    clear
    echo "================================="
    echo "   Optim Maintenance Menu"
    echo "================================="
    echo "1) 查看系統時間"
    echo "2) 查看磁碟使用量"
    echo "3) 查看服務狀態 (nginx)"
    echo "4) 查看系統負載"
    echo "0) 登出"
    echo "================================="
    read -p "請選擇操作: " choice

    case "$choice" in
        1)
            /usr/bin/date
            ;;
        2)
            /usr/bin/df -h
            ;;
        3)
            /usr/bin/systemctl status nginx --no-pager
            ;;
        4)
            /usr/bin/uptime
            ;;
        0)
            exit 0
            ;;
        *)
            echo "無效選項"
            ;;
    esac

    echo
    read -p "按 Enter 繼續..."
done

3️⃣ 設定權限(非常重要)

sudo chown root:root /opt/optim-menu/menu.sh
sudo chmod 755 /opt/optim-menu/menu.sh

⚠️ 使用絕對路徑(/usr/bin/df),避免 PATH 被濫用。


四、限制 optim 使用者的 Shell

方法一(最推薦):使用 ForceCommand

編輯 SSH 設定

sudo vi /etc/ssh/sshd_config

在檔案最下方加入:

Match User optim
    ForceCommand /opt/optim-menu/menu.sh
    PasswordAuthentication yes
    AllowTcpForwarding no
    X11Forwarding no
    PermitTTY yes

重新啟動 SSH

sudo systemctl restart ssh

五、(加強)防止逃逸技巧

1️⃣ 禁用 .bashrc / .profile

sudo chown root:root /home/optim/.bashrc /home/optim/.profile
sudo chmod 644 /home/optim/.bashrc /home/optim/.profile

或直接清空:

sudo truncate -s 0 /home/optim/.bashrc
sudo truncate -s 0 /home/optim/.profile

2️⃣ 限制 sudo(若不需要)

sudo visudo

確認 沒有

optim ALL=(ALL) ALL

六、測試驗證清單(必做)

請實際測試以下行為:

測試項目預期結果
SSH 密碼登入✅ 可登入
登入後看到 bash❌ 不可
出現選單
嘗試輸入 bash❌ 無效
Ctrl + C❌ 不可中斷
選 0✅ 正常登出
scp 傳檔❌ 失敗

七、常見錯誤與排查

🔴 登入直接斷線

  • menu.sh 沒有執行權限
  • ForceCommand 路徑錯誤

👉 解法:

chmod +x /opt/optim-menu/menu.sh

🔴 指令顯示 command not found

  • 沒用「絕對路徑」

👉 一律改成:

/usr/bin/xxx

八、進階強化(可選)

  • 搭配 auditd 記錄所有執行行為
  • 將 menu.sh 改為 Python / whiptail UI
  • 搭配 PAM + 時段限制
  • SSH 僅允許指定 IP

九、總結

此 SOP 能達成:

✅ 密碼登入
✅ 僅能選單操作
✅ 指令白名單
✅ 無 Shell、無逃逸
✅ 符合實務維運安全

若你需要:

  • 含審計版(auditd)
  • 多層子選單
  • 一鍵部署 Script
  • 搭配 sudo 精準放權

可直接說明使用情境,我可幫你客製化。


信心指數:96%

利用SSH上傳、下載(使用sz與rz命令)

1、情境簡述

通常,利用SSH管理遠端Linux伺服器時,經常需要與本地互動檔案。當然,我們可以利用FTP方式,比如通過Filezilla客戶端軟體。不過直接使用Xshell軟體自帶的上傳和下載功能無疑使最方便快捷的。通常SSH軟體支援的檔案傳輸協議主要有ASCII、Xmodem、Zmodem等。

rz,sz是便是Linux/Unix同Windows進行ZModem檔案傳輸的命令列工具。

使用前提:

  1. 首先,你的Linux端(CentOS, Ubuntu)需要安裝rz/sz命令,也就是 lrzsz 包。
  2. 其次,windows端需要支援ZModem的telnet/ssh客戶端(像Xshell)。

2、檔案傳輸協議

檔案傳輸是資料交換的主要形式。在進行檔案傳輸時,為使檔案能被正確識別和傳送,我們需要在兩臺計算機之間建立統一的傳輸協議。這個協議包括了檔案的識別、傳送的起止時間、錯誤的判斷與糾正等內容。常見的傳輸協議有以下幾種:

  • ASCII:這是最快的傳輸協議,但只能傳送文字檔案。
  • Xmodem:這種古老的傳輸協議速度較慢,但由於使用了CRC錯誤偵測方法,傳輸的準確率可高達99.6%。
  • Ymodem:這是Xmodem的改良版,使用了1024位區段傳送,速度比Xmodem要快
  • Zmodem:Zmodem採用了串流式(streaming)傳輸方式,傳輸速度較快,而且還具有自動改變區段大小和斷點續傳、快速錯誤偵測等功能。這是目前最流行的檔案傳輸協議。

3、在Linux上安裝lrzsz

單單是SSH客戶端軟體支援以上檔案傳輸協議(ASCII,Xmodem,Ymodem,Zmodem)還不行,我們的Linux伺服器上也得安裝相應的軟體,以支援這些檔案傳輸協議才行。在Linux上,lrzsz就是完成此任務的,lrzsz就是一個支援 Zmodem 傳輸協議的工具。我們通過sz/rz兩個命令,分別傳送/接收檔案。如果我們的系統中沒有安裝lrzsz這個包,就會報錯,安裝即可解決。

##以ubuntu為例

aliok@ulab01:~$ rz
Command 'rz' not found, but can be installed with:
sudo apt install lrzsz
aliok@ulab01:~$ sudo apt install lrzsz
[sudo] password for aliok:
Sorry, try again.
[sudo] password for aliok:
Reading package lists... Done
Building dependency tree      
Reading state information... Done
Suggested packages:
minicom
The following NEW packages will be installed:
lrzsz
0 upgraded, 1 newly installed, 0 to remove and 20 not upgraded.
Need to get 74.8 kB of archives.
After this operation, 531 kB of additional disk space will be used.
Get:1 http://tw.archive.ubuntu.com/ubuntu focal/universe amd64 lrzsz amd64 0.12.21-10 [74.8 kB]
Fetched 74.8 kB in 0s (303 kB/s)
Selecting previously unselected package lrzsz.
(Reading database ... 232559 files and directories currently installed.)
Preparing to unpack .../lrzsz_0.12.21-10_amd64.deb ...
Unpacking lrzsz (0.12.21-10) ...
Setting up lrzsz (0.12.21-10) ...
Processing triggers for man-db (2.9.1-1) ...

4、用法簡述

  • sz中的s意為send(傳送)
### 下載一個檔案:
# sz filename
### 下載多個檔案:
# sz filename1 filename2
### 下載dir目錄下的所有檔案,不包含dir下的資料夾:
# sz dir/*
  • rz中的r意為received(接收)
### 直接鍵入rz命令即可
# rz
### 直接拖動檔案到 xshell即可

輸入rz回車後,會出現檔案選擇對話方塊,選擇需要上傳檔案,一次可以指定多個檔案,上傳到伺服器的路徑為當前執行rz命令的目錄。

5、操作示範

rz命令(下載)

當我們鍵入rz命令之後,會彈出檔案選擇對話方塊,選擇需要傳輸的檔案,點選 Add 即可。

接收的目錄就是我們當前執行rz命令的目錄。

更簡單直接的方法,直接選中檔案,用滑鼠點住檔案往Xshell裡面一拖就可以了。

sz命令(上傳)

假設我要傳送檔案,直接使用sz命令, 其後接上檔名即可。同理,敲下ENTER後,也會跳出對話方塊,讓我們選擇存放的資料夾。

6、檔案大小限制

使用lrzsz非常方便,但是有一點不足之處: 無法傳輸大於 4G 的檔案。