- 修复RDP协议连接导致的任意文件读取漏洞
- RDP协议增加「域」参数 - 增加安全访问功能 - 优化代码
This commit is contained in:
@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/labstack/echo/v4"
|
||||
"next-terminal/pkg/constant"
|
||||
"next-terminal/pkg/model"
|
||||
"next-terminal/pkg/utils"
|
||||
"strconv"
|
||||
@ -85,7 +86,7 @@ func AssetImportEndpoint(c echo.Context) error {
|
||||
Protocol: record[1],
|
||||
IP: record[2],
|
||||
Port: port,
|
||||
AccountType: model.Custom,
|
||||
AccountType: constant.Custom,
|
||||
Username: record[4],
|
||||
Password: record[5],
|
||||
PrivateKey: record[6],
|
||||
|
@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"errors"
|
||||
"github.com/labstack/echo/v4"
|
||||
"next-terminal/pkg/constant"
|
||||
"next-terminal/pkg/model"
|
||||
"next-terminal/pkg/utils"
|
||||
"strconv"
|
||||
@ -26,7 +27,7 @@ func CredentialCreateEndpoint(c echo.Context) error {
|
||||
item.Created = utils.NowJsonTime()
|
||||
|
||||
switch item.Type {
|
||||
case model.Custom:
|
||||
case constant.Custom:
|
||||
item.PrivateKey = "-"
|
||||
item.Passphrase = "-"
|
||||
if len(item.Username) == 0 {
|
||||
@ -35,7 +36,7 @@ func CredentialCreateEndpoint(c echo.Context) error {
|
||||
if len(item.Password) == 0 {
|
||||
item.Password = "-"
|
||||
}
|
||||
case model.PrivateKey:
|
||||
case constant.PrivateKey:
|
||||
item.Password = "-"
|
||||
if len(item.Username) == 0 {
|
||||
item.Username = "-"
|
||||
@ -90,7 +91,7 @@ func CredentialUpdateEndpoint(c echo.Context) error {
|
||||
}
|
||||
|
||||
switch item.Type {
|
||||
case model.Custom:
|
||||
case constant.Custom:
|
||||
item.PrivateKey = "-"
|
||||
item.Passphrase = "-"
|
||||
if len(item.Username) == 0 {
|
||||
@ -99,7 +100,7 @@ func CredentialUpdateEndpoint(c echo.Context) error {
|
||||
if len(item.Password) == 0 {
|
||||
item.Password = "-"
|
||||
}
|
||||
case model.PrivateKey:
|
||||
case constant.PrivateKey:
|
||||
item.Password = "-"
|
||||
if len(item.Username) == 0 {
|
||||
item.Username = "-"
|
||||
|
@ -3,8 +3,10 @@ package api
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/labstack/echo/v4"
|
||||
"net"
|
||||
"next-terminal/pkg/constant"
|
||||
"next-terminal/pkg/global"
|
||||
"next-terminal/pkg/model"
|
||||
"next-terminal/pkg/utils"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
@ -26,6 +28,62 @@ func ErrorHandler(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func TcpWall(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
|
||||
return func(c echo.Context) error {
|
||||
|
||||
if global.Securities == nil {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
ip := c.RealIP()
|
||||
for i := 0; i < len(global.Securities); i++ {
|
||||
security := global.Securities[i]
|
||||
|
||||
if strings.Contains(security.IP, "/") {
|
||||
// CIDR
|
||||
_, ipNet, err := net.ParseCIDR(security.IP)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !ipNet.Contains(net.ParseIP(ip)) {
|
||||
continue
|
||||
}
|
||||
} else if strings.Contains(security.IP, "-") {
|
||||
// 范围段
|
||||
split := strings.Split(security.IP, "-")
|
||||
if len(split) < 2 {
|
||||
continue
|
||||
}
|
||||
start := split[0]
|
||||
end := split[1]
|
||||
intReqIP := utils.IpToInt(ip)
|
||||
if intReqIP < utils.IpToInt(start) || intReqIP > utils.IpToInt(end) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// IP
|
||||
if security.IP != ip {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if security.Rule == constant.AccessRuleAllow {
|
||||
return next(c)
|
||||
}
|
||||
if security.Rule == constant.AccessRuleReject {
|
||||
if c.Request().Header.Get("X-Requested-With") != "" || c.Request().Header.Get(Token) != "" {
|
||||
return Fail(c, 0, "您的访问请求被拒绝 :(")
|
||||
} else {
|
||||
return c.HTML(666, "您的访问请求被拒绝 :(")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
|
||||
func Auth(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
|
||||
startWithUrls := []string{"/login", "/static", "/favicon.ico", "/logo.svg", "/asciinema"}
|
||||
@ -80,7 +138,7 @@ func Admin(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return Fail(c, 401, "您的登录信息已失效,请重新登录后再试。")
|
||||
}
|
||||
|
||||
if account.Type != model.TypeAdmin {
|
||||
if account.Type != constant.TypeAdmin {
|
||||
return Fail(c, 403, "permission denied")
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"next-terminal/pkg/constant"
|
||||
"next-terminal/pkg/model"
|
||||
)
|
||||
|
||||
@ -21,7 +22,7 @@ func OverviewCounterEndPoint(c echo.Context) error {
|
||||
credential int64
|
||||
asset int64
|
||||
)
|
||||
if model.TypeUser == account.Type {
|
||||
if constant.TypeUser == account.Type {
|
||||
countUser, _ = model.CountOnlineUser()
|
||||
countOnlineSession, _ = model.CountOnlineSession()
|
||||
credential, _ = model.CountCredentialByUserId(account.ID)
|
||||
|
@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"next-terminal/pkg/constant"
|
||||
"next-terminal/pkg/global"
|
||||
"next-terminal/pkg/log"
|
||||
"next-terminal/pkg/model"
|
||||
@ -34,6 +35,7 @@ func SetupRoutes() *echo.Echo {
|
||||
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
|
||||
}))
|
||||
e.Use(ErrorHandler)
|
||||
e.Use(TcpWall)
|
||||
e.Use(Auth)
|
||||
|
||||
e.POST("/login", LoginEndpoint)
|
||||
@ -121,7 +123,7 @@ func SetupRoutes() *echo.Echo {
|
||||
sessions.POST("/:id/mkdir", SessionMkDirEndpoint)
|
||||
sessions.POST("/:id/rm", SessionRmEndpoint)
|
||||
sessions.POST("/:id/rename", SessionRenameEndpoint)
|
||||
sessions.DELETE("/:id", SessionDeleteEndpoint)
|
||||
sessions.DELETE("/:id", Admin(SessionDeleteEndpoint))
|
||||
sessions.GET("/:id/recording", SessionRecordingEndpoint)
|
||||
}
|
||||
|
||||
@ -158,6 +160,15 @@ func SetupRoutes() *echo.Echo {
|
||||
jobs.DELETE("/:id/logs", JobDeleteLogsEndpoint)
|
||||
}
|
||||
|
||||
securities := e.Group("/securities", Admin)
|
||||
{
|
||||
securities.POST("", SecurityCreateEndpoint)
|
||||
securities.GET("/paging", SecurityPagingEndpoint)
|
||||
securities.PUT("/:id", SecurityUpdateEndpoint)
|
||||
securities.DELETE("/:id", SecurityDeleteEndpoint)
|
||||
securities.GET("/:id", SecurityGetEndpoint)
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
@ -218,7 +229,7 @@ func HasPermission(c echo.Context, owner string) bool {
|
||||
return false
|
||||
}
|
||||
// 检测是否为管理人员
|
||||
if model.TypeAdmin == account.Type {
|
||||
if constant.TypeAdmin == account.Type {
|
||||
return true
|
||||
}
|
||||
// 检测是否为所有者
|
||||
|
114
pkg/api/security.go
Normal file
114
pkg/api/security.go
Normal file
@ -0,0 +1,114 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"next-terminal/pkg/global"
|
||||
"next-terminal/pkg/model"
|
||||
"next-terminal/pkg/utils"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func SecurityCreateEndpoint(c echo.Context) error {
|
||||
var item model.AccessSecurity
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
item.ID = utils.UUID()
|
||||
item.Source = "管理员添加"
|
||||
|
||||
if err := model.CreateNewSecurity(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
// 更新内存中的安全规则
|
||||
if err := ReloadAccessSecurity(); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, "")
|
||||
}
|
||||
|
||||
func ReloadAccessSecurity() error {
|
||||
rules, err := model.FindAllAccessSecurities()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rules != nil && len(rules) > 0 {
|
||||
var securities []*global.Security
|
||||
for i := 0; i < len(rules); i++ {
|
||||
rule := global.Security{
|
||||
IP: rules[i].IP,
|
||||
Rule: rules[i].Rule,
|
||||
}
|
||||
securities = append(securities, &rule)
|
||||
}
|
||||
global.Securities = securities
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SecurityPagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
ip := c.QueryParam("ip")
|
||||
rule := c.QueryParam("rule")
|
||||
|
||||
order := c.QueryParam("order")
|
||||
field := c.QueryParam("field")
|
||||
|
||||
items, total, err := model.FindPageSecurity(pageIndex, pageSize, ip, rule, order, field)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, H{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func SecurityUpdateEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
var item model.AccessSecurity
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := model.UpdateSecurityById(&item, id); err != nil {
|
||||
return err
|
||||
}
|
||||
// 更新内存中的安全规则
|
||||
if err := ReloadAccessSecurity(); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func SecurityDeleteEndpoint(c echo.Context) error {
|
||||
ids := c.Param("id")
|
||||
|
||||
split := strings.Split(ids, ",")
|
||||
for i := range split {
|
||||
jobId := split[i]
|
||||
if err := model.DeleteSecurityById(jobId); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// 更新内存中的安全规则
|
||||
if err := ReloadAccessSecurity(); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func SecurityGetEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
item, err := model.FindSecurityById(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, item)
|
||||
}
|
@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"next-terminal/pkg/constant"
|
||||
"next-terminal/pkg/global"
|
||||
"next-terminal/pkg/model"
|
||||
"next-terminal/pkg/utils"
|
||||
@ -36,10 +37,10 @@ func SessionPagingEndpoint(c echo.Context) error {
|
||||
}
|
||||
|
||||
for i := 0; i < len(items); i++ {
|
||||
if status == model.Disconnected && len(items[i].Recording) > 0 {
|
||||
if status == constant.Disconnected && len(items[i].Recording) > 0 {
|
||||
|
||||
var recording string
|
||||
if items[i].Mode == model.Naive {
|
||||
if items[i].Mode == constant.Naive {
|
||||
recording = items[i].Recording
|
||||
} else {
|
||||
recording = items[i].Recording + "/recording"
|
||||
@ -77,7 +78,7 @@ func SessionConnectEndpoint(c echo.Context) error {
|
||||
|
||||
session := model.Session{}
|
||||
session.ID = sessionId
|
||||
session.Status = model.Connected
|
||||
session.Status = constant.Connected
|
||||
session.ConnectedTime = utils.NowJsonTime()
|
||||
|
||||
if err := model.UpdateSessionById(&session, sessionId); err != nil {
|
||||
@ -118,11 +119,11 @@ func CloseSessionById(sessionId string, code int, reason string) {
|
||||
return
|
||||
}
|
||||
|
||||
if s.Status == model.Disconnected {
|
||||
if s.Status == constant.Disconnected {
|
||||
return
|
||||
}
|
||||
|
||||
if s.Status == model.Connecting {
|
||||
if s.Status == constant.Connecting {
|
||||
// 会话还未建立成功,无需保留数据
|
||||
_ = model.DeleteSessionById(sessionId)
|
||||
return
|
||||
@ -130,7 +131,7 @@ func CloseSessionById(sessionId string, code int, reason string) {
|
||||
|
||||
session := model.Session{}
|
||||
session.ID = sessionId
|
||||
session.Status = model.Disconnected
|
||||
session.Status = constant.Disconnected
|
||||
session.DisconnectedTime = utils.NowJsonTime()
|
||||
session.Code = code
|
||||
session.Message = reason
|
||||
@ -161,15 +162,15 @@ func SessionCreateEndpoint(c echo.Context) error {
|
||||
assetId := c.QueryParam("assetId")
|
||||
mode := c.QueryParam("mode")
|
||||
|
||||
if mode == model.Naive {
|
||||
mode = model.Naive
|
||||
if mode == constant.Naive {
|
||||
mode = constant.Naive
|
||||
} else {
|
||||
mode = model.Guacd
|
||||
mode = constant.Guacd
|
||||
}
|
||||
|
||||
user, _ := GetCurrentAccount(c)
|
||||
|
||||
if model.TypeUser == user.Type {
|
||||
if constant.TypeUser == user.Type {
|
||||
// 检测是否有访问权限
|
||||
assetIds, err := model.FindAssetIdsByUserId(user.ID)
|
||||
if err != nil {
|
||||
@ -196,7 +197,7 @@ func SessionCreateEndpoint(c echo.Context) error {
|
||||
Protocol: asset.Protocol,
|
||||
IP: asset.IP,
|
||||
Port: asset.Port,
|
||||
Status: model.NoConnect,
|
||||
Status: constant.NoConnect,
|
||||
Creator: user.ID,
|
||||
ClientIP: c.RealIP(),
|
||||
Mode: mode,
|
||||
@ -208,7 +209,7 @@ func SessionCreateEndpoint(c echo.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if credential.Type == model.Custom {
|
||||
if credential.Type == constant.Custom {
|
||||
session.Username = credential.Username
|
||||
session.Password = credential.Password
|
||||
} else {
|
||||
@ -273,7 +274,7 @@ func SessionUploadEndpoint(c echo.Context) error {
|
||||
} else if "rdp" == session.Protocol {
|
||||
|
||||
if strings.Contains(remoteFile, "../") {
|
||||
logrus.Warnf("IP %v 尝试进行攻击,请ban掉此IP", c.RealIP())
|
||||
SafetyRuleTrigger(c)
|
||||
return Fail(c, -1, ":) 您的IP已被记录,请去向管理员自首。")
|
||||
}
|
||||
|
||||
@ -331,7 +332,7 @@ func SessionDownloadEndpoint(c echo.Context) error {
|
||||
return c.Stream(http.StatusOK, echo.MIMEOctetStream, bytes.NewReader(buff.Bytes()))
|
||||
} else if "rdp" == session.Protocol {
|
||||
if strings.Contains(remoteFile, "../") {
|
||||
logrus.Warnf("IP %v 尝试进行攻击,请ban掉此IP", c.RealIP())
|
||||
SafetyRuleTrigger(c)
|
||||
return Fail(c, -1, ":) 您的IP已被记录,请去向管理员自首。")
|
||||
}
|
||||
drivePath, err := model.GetDrivePath()
|
||||
@ -413,7 +414,7 @@ func SessionLsEndpoint(c echo.Context) error {
|
||||
return Success(c, files)
|
||||
} else if "rdp" == session.Protocol {
|
||||
if strings.Contains(remoteDir, "../") {
|
||||
logrus.Warnf("IP %v 尝试进行攻击,请ban掉此IP", c.RealIP())
|
||||
SafetyRuleTrigger(c)
|
||||
return Fail(c, -1, ":) 您的IP已被记录,请去向管理员自首。")
|
||||
}
|
||||
drivePath, err := model.GetDrivePath()
|
||||
@ -446,6 +447,18 @@ func SessionLsEndpoint(c echo.Context) error {
|
||||
return errors.New("当前协议不支持此操作")
|
||||
}
|
||||
|
||||
func SafetyRuleTrigger(c echo.Context) {
|
||||
logrus.Warnf("IP %v 尝试进行攻击,请ban掉此IP", c.RealIP())
|
||||
security := model.AccessSecurity{
|
||||
ID: utils.UUID(),
|
||||
Source: "安全规则触发",
|
||||
IP: c.RealIP(),
|
||||
Rule: constant.AccessRuleReject,
|
||||
}
|
||||
|
||||
_ = model.CreateNewSecurity(&security)
|
||||
}
|
||||
|
||||
func SessionMkDirEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
session, err := model.FindSessionById(sessionId)
|
||||
@ -464,7 +477,7 @@ func SessionMkDirEndpoint(c echo.Context) error {
|
||||
return Success(c, nil)
|
||||
} else if "rdp" == session.Protocol {
|
||||
if strings.Contains(remoteDir, "../") {
|
||||
logrus.Warnf("IP %v 尝试进行攻击,请ban掉此IP", c.RealIP())
|
||||
SafetyRuleTrigger(c)
|
||||
return Fail(c, -1, ":) 您的IP已被记录,请去向管理员自首。")
|
||||
}
|
||||
drivePath, err := model.GetDrivePath()
|
||||
@ -525,7 +538,7 @@ func SessionRmEndpoint(c echo.Context) error {
|
||||
return Success(c, nil)
|
||||
} else if "rdp" == session.Protocol {
|
||||
if strings.Contains(key, "../") {
|
||||
logrus.Warnf("IP %v 尝试进行攻击,请ban掉此IP", c.RealIP())
|
||||
SafetyRuleTrigger(c)
|
||||
return Fail(c, -1, ":) 您的IP已被记录,请去向管理员自首。")
|
||||
}
|
||||
drivePath, err := model.GetDrivePath()
|
||||
@ -566,7 +579,7 @@ func SessionRenameEndpoint(c echo.Context) error {
|
||||
return Success(c, nil)
|
||||
} else if "rdp" == session.Protocol {
|
||||
if strings.Contains(oldName, "../") {
|
||||
logrus.Warnf("IP %v 尝试进行攻击,请ban掉此IP", c.RealIP())
|
||||
SafetyRuleTrigger(c)
|
||||
return Fail(c, -1, ":) 您的IP已被记录,请去向管理员自首。")
|
||||
}
|
||||
drivePath, err := model.GetDrivePath()
|
||||
@ -591,7 +604,7 @@ func SessionRecordingEndpoint(c echo.Context) error {
|
||||
}
|
||||
|
||||
var recording string
|
||||
if session.Mode == model.Naive {
|
||||
if session.Mode == constant.Naive {
|
||||
recording = session.Recording
|
||||
} else {
|
||||
recording = session.Recording + "/recording"
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"next-terminal/pkg/constant"
|
||||
"next-terminal/pkg/global"
|
||||
"next-terminal/pkg/guacd"
|
||||
"next-terminal/pkg/model"
|
||||
@ -62,7 +63,7 @@ func SSHEndpoint(c echo.Context) (err error) {
|
||||
}
|
||||
|
||||
user, _ := GetCurrentAccount(c)
|
||||
if model.TypeUser == user.Type {
|
||||
if constant.TypeUser == user.Type {
|
||||
// 检测是否有访问权限
|
||||
assetIds, err := model.FindAssetIdsByUserId(user.ID)
|
||||
if err != nil {
|
||||
@ -137,7 +138,7 @@ func SSHEndpoint(c echo.Context) (err error) {
|
||||
ConnectionId: sessionId,
|
||||
Width: cols,
|
||||
Height: rows,
|
||||
Status: model.Connecting,
|
||||
Status: constant.Connecting,
|
||||
Recording: recording,
|
||||
}
|
||||
// 创建新会话
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/sirupsen/logrus"
|
||||
"next-terminal/pkg/constant"
|
||||
"next-terminal/pkg/global"
|
||||
"next-terminal/pkg/guacd"
|
||||
"next-terminal/pkg/model"
|
||||
@ -49,7 +50,7 @@ func TunEndpoint(c echo.Context) error {
|
||||
logrus.Warnf("会话不存在")
|
||||
return err
|
||||
}
|
||||
if session.Status != model.Connected {
|
||||
if session.Status != constant.Connected {
|
||||
logrus.Warnf("会话未在线")
|
||||
return errors.New("会话未在线")
|
||||
}
|
||||
@ -188,7 +189,7 @@ func TunEndpoint(c echo.Context) error {
|
||||
ConnectionId: tunnel.UUID,
|
||||
Width: intWidth,
|
||||
Height: intHeight,
|
||||
Status: model.Connecting,
|
||||
Status: constant.Connecting,
|
||||
Recording: configuration.GetParameter(guacd.RecordingPath),
|
||||
}
|
||||
// 创建新会话
|
||||
|
Reference in New Issue
Block a user