# web-shell **Repository Path**: itisyue/web-shell ## Basic Information - **Project Name**: web-shell - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-09 - **Last Updated**: 2026-10-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 远程终端(Web Shell)部署说明 一个基于 **Spring Boot + WebSocket + xterm.js + pty4j** 的浏览器端交互式 Shell,内置**用户名/密码登录**与多层安全加固。 打包为单个可执行 JAR,部署到 Linux 服务器后,浏览器打开即获得**真实终端体验**(支持 `vim` / `top` / `less` 等全屏交互式程序)。 ## 技术结构 | 层 | 选型 | |---|---| | 后端框架 | Spring Boot 2.7.18(JRE 8+ 即可运行) | | 终端模拟 | xterm.js(已本地化打包,部署后**无需外网**) | | 伪终端 | pty4j **0.12.10-jdk8**(Java 8 字节码;内置 Linux / macOS / Windows 多平台原生库) | | 通信 | WebSocket,端点可配置(默认 `/shell`) | | 认证 | 登录页 + 服务端会话 + BCrypt 口令哈希 + 防爆破锁 | ## 运行环境要求 - **JDK 8 及以上**(JDK 1.8 / OpenJDK 1.8 均可,已在 `1.8.0_361` 实测通过)。 - 伪终端库固定使用 **`pty4j:0.12.10-jdk8`**,其字节码为 Java 8(class 52)。 普通 `pty4j 0.12.x` 编译目标为 Java 11(class 55),在 JDK 8 上会抛 `UnsupportedClassVersionError: com/pty4j/PtyProcessBuilder`, **请勿升级为不带 `-jdk8` 后缀的版本**。 ## 交付文件 - `target/web-shell.jar` —— 可执行 JAR(含 pty4j 原生库) - `start.sh` —— 启动 / 停止 / 状态 / 重启 脚本(**登录凭据内置其中**) - `gen-cert.sh` —— 一键生成自签 TLS 证书(启用 HTTPS 用) - `src/` —— 源码(如需二次开发) ## 部署到 Linux ```bash # 1. 上传 web-shell.jar 与 start.sh 到服务器 # 2. 确认已安装 JRE 8+ java -version # 3. 设置登录凭据(务必设置,否则启动时会生成一次性随机密码) export WEBSHELL_USERNAME=admin export WEBSHELL_PASSWORD='换成你的强密码' # 4. 启动 chmod +x start.sh ./start.sh # 前台运行(Ctrl+C 退出) ./start.sh -d # 后台运行 # 5. 浏览器访问(先登录,再进终端) http://<服务器IP>:8080 ``` ## 启用 HTTPS(强烈推荐:命令不再明文传输) 默认走**明文 HTTP**,抓包可直接看到登录密码与命令内容。开启内置 TLS 后,登录、命令、回显全部经加密传输。 ```bash # 1. 生成自签证书(第二个参数直接指定证书密码) ./gen-cert.sh 192.168.1.100 'S3cure-Pass_2026!' # 也可以传域名:./gen-cert.sh shell.example.com 'S3cure-Pass_2026!' # 产出 cert/keystore.p12,密码不会回显;密码至少 8 位 # 2. 打开 start.sh,只修改顶部部署配置区: # SSL_KEYSTORE_PASSWORD='S3cure-Pass_2026!' # 该值必须与 gen-cert.sh 的第二个参数一致 # 3. 以 HTTPS 启动(默认 HTTPS 8443;HTTP 8080 自动重定向到 8443) ./start.sh -d # 3. 浏览器访问(自签证书需点「高级 → 继续访问」) https://<服务器IP>:8443 ``` - 自定义端口:`WEBSHELL_SSL_ENABLED=true ./start.sh --server.port=9443`(HTTPS 9443,HTTP 8080 仍做重定向;两端口不可相同,可用 `WEBSHELL_HTTP_PORT` 改重定向端口)。 - 启用 TLS 后会话 Cookie 自动加 `Secure`,仅走加密通道。 - **生产环境**建议用正式证书:可把 PEM 转成 PKCS12 交给内置 TLS ```bash openssl pkcs12 -export -in fullchain.pem -inkey privkey.pem -out cert/keystore.p12 -alias webshell ``` 或改用 Nginx 终止 TLS(见文末反向代理配置)。 ## 传输安全:抓包到底能看到什么 实测方法:在客户端与服务端之间插入 TCP 中转代理,截获链路上**真实流过的原始字节**(等价于抓包),然后搜索敏感内容。 | 场景 | 登录密码 | 命令内容 | |---|---|---| | 明文 HTTP + WS(默认) | ❌ **明文可见** | ❌ 可被还原(WebSocket 仅用 4 字节掩码混淆,Wireshark 直接显示明文) | | 启用 HTTPS + WSS | ✅ 仅密文,搜不到 | ✅ 仅密文,搜不到 | 启用 HTTPS 后实测结论:链路中**搜不到命令明文、也搜不到登录密码**,而命令在服务端确实执行成功(客户端正常收到回显 `echo` 结果)。明文模式下,登录密码以 JSON 明文出现在链路上。 > 自己验证:浏览器 F12 → Network → 选中 `/shell` 这条 WS 连接 → Messages 标签页可看到收发帧;或用 Wireshark 抓端口,对比开关 HTTPS 的差异。 ## 自定义 | 需求 | 做法 | |---|---| | 改监听端口 | `./start.sh --server.port=9000` | | 改默认 shell | `./start.sh -Dwebshell.shell=/bin/zsh`(或 `export WSHELL_SHELL=/bin/zsh`) | | 改登录用户名/密码 | 环境变量 `WEBSHELL_USERNAME` / `WEBSHELL_PASSWORD`,或参数 `-Dwebshell.username=... -Dwebshell.password=...` | | 改 WebSocket 端点(规避扫描) | 环境变量 `WEBSHELL_WS_PATH=/terminal`,或参数 `-Dwebshell.ws-path=/terminal` | | 防爆破阈值/锁定时长 | 参数 `-Dwebshell.max-login-failures=5 -Dwebshell.login-lock-minutes=15` | | 启用 HTTPS | 修改 `start.sh` 顶部 `SSL_ENABLED=true`,先用 `./gen-cert.sh <证书密码>` 生成证书 | | 改 HTTPS / HTTP 端口 | 修改 `start.sh` 顶部 `DEFAULT_HTTPS_PORT` / `HTTP_REDIRECT_PORT` | | 改证书路径/口令 | 修改 `start.sh` 顶部 `SSL_KEYSTORE` / `SSL_KEYSTORE_PASSWORD` / `SSL_KEYSTORE_TYPE` | | 前置 HTTPS 时加固 Cookie | 环境变量 `WEBSHELL_COOKIE_SECURE=true` | | 停止后台进程 | `./start.sh stop` | | 查看状态 | `./start.sh status` | | 重启 | `./start.sh restart` | > 直接运行也可:`java -jar web-shell.jar --server.port=8080` ## 重新构建(在开发机 / CI) ```bash # 需 JDK 8+;建议直接用 JDK 8 构建,与运行环境保持一致 mvn clean package -DskipTests # 产物:target/web-shell.jar ``` > ⚠️ 升级依赖时注意:`pty4j` 必须使用带 `-jdk8` 后缀的版本(如 `0.12.10-jdk8`), > 否则构建出的 JAR 在 JDK 8 服务器上会启动即报 `UnsupportedClassVersionError`。 ## 安全设计(已实现) 本工具**已内置身份认证**,并做了多层加固,但仍属于高权限运维工具,请勿裸奔上公网。 1. **登录认证**:用户名/密码登录,口令用 **BCrypt(强度 12)** 哈希后仅存内存,明文不落盘、不进日志、不通过接口返回。 2. **会话安全**: - 登录成功执行 **会话固定防护**(先销毁旧会话再建新会话)。 - 会话 Cookie 设为 **HttpOnly + SameSite=Strict**(前置 HTTPS 时可加 `Secure`)。 - 未登录访问任意页面 → 302 跳登录页;未登录发起 WebSocket 握手 → 直接拒绝(1003)不起 shell。 3. **防爆破**:按客户端 IP 计数失败次数,超过阈值(默认 5 次)锁定一段时间(默认 15 分钟),返回 429;失败时人为延迟 500ms 减缓在线爆破;用户名比较走定长时间,避免时序侧信道。 4. **同源校验**:过滤器校验 `Origin` 与 `Host` 同源,跨站请求直接拒绝(配合 SameSite=Strict 双保险)。 5. **安全响应头**:`X-Content-Type-Options: nosniff`、`X-Frame-Options: DENY`、`Referrer-Policy: no-referrer`、内容安全策略(仅同源 + ws)、`Cache-Control: no-store`。 6. **可配置端点**:WebSocket 路径可通过 `ws-path` 修改,避免固定 `/shell` 被扫描器当作特征。 7. **传输加密(可选但强烈推荐)**:`WEBSHELL_SSL_ENABLED=true` 开启内置 TLS,登录凭据、命令、回显全部走 `https/wss` 加密通道;启用后会话 Cookie 自动加 `Secure`。 ## ⚠️ 仍然重要的部署建议(务必阅读) 尽管已内置认证,它仍是**以运行 JAR 的用户身份执行任意命令**的高危工具。务必至少做一项: 1. **仅内网 / 本机**:用防火墙(iptables / ufw / 安全组)限制端口只对内网开放。 2. **反向代理 + HTTPS(强烈推荐)**,用 Nginx 终止 TLS 并加 Basic Auth 双因子: ```nginx server { listen 443 ssl; server_name shell.example.com; ssl_certificate /path/fullchain.pem; ssl_certificate_key /path/privkey.pem; location / { auth_basic "Restricted"; auth_basic_user_file /etc/nginx/.htpasswd; # htpasswd -c /etc/nginx/.htpasswd proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # WebSocket 必需 proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; proxy_read_timeout 3600s; } } ``` 并在启动 JAR 时设置 `WEBSHELL_COOKIE_SECURE=true`。 3. **SSH 隧道**:本机执行 `ssh -L 8080:127.0.0.1:8080 user@server`,再访问 `http://localhost:8080`,完全不开放端口。 ## 故障排查 ### 终端能用,但过一会儿自动断开 - **现象**:登录正常、能执行命令,但空闲几秒后连接断开;服务端日志由 `shell-reader-xxx` 线程打印 `WebSocket closed: ... status=CloseStatus[code=1000]`,终端里可能出现 `网络繁忙,请稍后重试` / `timed out waiting for input: auto-logout`。 - **原因**:服务器(尤其做过安全加固的内网机器)在 `/etc/profile` 中设置了 `TMOUT` (bash 空闲自动登出)。该变量会随环境被继承进 Web Shell 启动的子 shell, 于是 shell 空闲若干秒后自行退出,表现为"用着用着就断线"。 - **处理**:本版本已在启动子 shell 时显式 `TMOUT=0` 覆盖,无需手工干预。 可用 `echo $TMOUT` 确认服务器是否设置了该变量;若系统在 profile 中强制 export, 可在服务器上注释掉,或启动前 `TMOUT=0 ./start.sh -d`。 - 服务端日志会记录 shell 退出原因(`Shell 会话结束 session=... 原因=...`),便于定位。 ### 重启服务后页面异常 / 需要手动刷新 - 重启服务会清空内存中的登录会话,未刷新的旧页面连接随之失效。 - 新版前端已支持**断线自动重连**(最多 3 次,间隔递增);若会话已失效, 会自动跳转登录页,无需手动刷新。 ## 已知限制 - 每个浏览器连接对应一个独立的 PTY 进程,多个标签页互不影响。 - 前端断线后,后端自动回收对应子进程,避免僵尸会话。 - 防爆破为内存实现,单实例生效;多实例/集群需改为共享存储(如 Redis)。 - pty4j 已在 macOS / Linux(x86_64 & aarch64) / Windows 上验证可加载原生库;极老的 glibc 环境若加载失败,请升级系统或自行替换 pty4j 版本。