mirror of
https://github.com/ngoduykhanh/wireguard-ui.git
synced 2025-04-18 19:49:30 +03:00
fix: add basic server-side input validation (#435)
This mitigates possible path traversal attacks by using e.g. "../user" as a user name.
This commit is contained in:
parent
a06bce88e0
commit
13a4c05ff5
3 changed files with 58 additions and 14 deletions
|
@ -8,6 +8,7 @@ import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -26,6 +27,8 @@ import (
|
||||||
"github.com/ngoduykhanh/wireguard-ui/util"
|
"github.com/ngoduykhanh/wireguard-ui/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var usernameRegexp = regexp.MustCompile("^\\w[\\w\\-.]*$")
|
||||||
|
|
||||||
// Health check handler
|
// Health check handler
|
||||||
func Health() echo.HandlerFunc {
|
func Health() echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
|
@ -63,6 +66,10 @@ func Login(db store.IStore) echo.HandlerFunc {
|
||||||
password := data["password"].(string)
|
password := data["password"].(string)
|
||||||
rememberMe := data["rememberMe"].(bool)
|
rememberMe := data["rememberMe"].(bool)
|
||||||
|
|
||||||
|
if !usernameRegexp.MatchString(username) {
|
||||||
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"})
|
||||||
|
}
|
||||||
|
|
||||||
dbuser, err := db.GetUserByName(username)
|
dbuser, err := db.GetUserByName(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot query user from DB"})
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot query user from DB"})
|
||||||
|
@ -135,9 +142,12 @@ func GetUsers(db store.IStore) echo.HandlerFunc {
|
||||||
// GetUser handler returns a JSON object of single user
|
// GetUser handler returns a JSON object of single user
|
||||||
func GetUser(db store.IStore) echo.HandlerFunc {
|
func GetUser(db store.IStore) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
|
|
||||||
username := c.Param("username")
|
username := c.Param("username")
|
||||||
|
|
||||||
|
if !usernameRegexp.MatchString(username) {
|
||||||
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"})
|
||||||
|
}
|
||||||
|
|
||||||
if !isAdmin(c) && (username != currentUser(c)) {
|
if !isAdmin(c) && (username != currentUser(c)) {
|
||||||
return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "Manager cannot access other user data"})
|
return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "Manager cannot access other user data"})
|
||||||
}
|
}
|
||||||
|
@ -200,12 +210,16 @@ func UpdateUser(db store.IStore) echo.HandlerFunc {
|
||||||
admin = false
|
admin = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !usernameRegexp.MatchString(previousUsername) {
|
||||||
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"})
|
||||||
|
}
|
||||||
|
|
||||||
user, err := db.GetUserByName(previousUsername)
|
user, err := db.GetUserByName(previousUsername)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, err.Error()})
|
return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
if username == "" {
|
if username == "" || !usernameRegexp.MatchString(username) {
|
||||||
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"})
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"})
|
||||||
} else {
|
} else {
|
||||||
user.Username = username
|
user.Username = username
|
||||||
|
@ -261,7 +275,7 @@ func CreateUser(db store.IStore) echo.HandlerFunc {
|
||||||
password := data["password"].(string)
|
password := data["password"].(string)
|
||||||
admin := data["admin"].(bool)
|
admin := data["admin"].(bool)
|
||||||
|
|
||||||
if username == "" {
|
if username == "" || !usernameRegexp.MatchString(username) {
|
||||||
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"})
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"})
|
||||||
} else {
|
} else {
|
||||||
user.Username = username
|
user.Username = username
|
||||||
|
@ -303,6 +317,10 @@ func RemoveUser(db store.IStore) echo.HandlerFunc {
|
||||||
|
|
||||||
username := data["username"].(string)
|
username := data["username"].(string)
|
||||||
|
|
||||||
|
if !usernameRegexp.MatchString(username) {
|
||||||
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"})
|
||||||
|
}
|
||||||
|
|
||||||
if username == currentUser(c) {
|
if username == currentUser(c) {
|
||||||
return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "User cannot delete itself"})
|
return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "User cannot delete itself"})
|
||||||
}
|
}
|
||||||
|
@ -357,6 +375,11 @@ func GetClient(db store.IStore) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
|
|
||||||
clientID := c.Param("id")
|
clientID := c.Param("id")
|
||||||
|
|
||||||
|
if _, err := xid.FromString(clientID); err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"})
|
||||||
|
}
|
||||||
|
|
||||||
qrCodeSettings := model.QRCodeSettings{
|
qrCodeSettings := model.QRCodeSettings{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
IncludeDNS: true,
|
IncludeDNS: true,
|
||||||
|
@ -485,6 +508,10 @@ func EmailClient(db store.IStore, mailer emailer.Emailer, emailSubject, emailCon
|
||||||
c.Bind(&payload)
|
c.Bind(&payload)
|
||||||
// TODO validate email
|
// TODO validate email
|
||||||
|
|
||||||
|
if _, err := xid.FromString(payload.ID); err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"})
|
||||||
|
}
|
||||||
|
|
||||||
qrCodeSettings := model.QRCodeSettings{
|
qrCodeSettings := model.QRCodeSettings{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
IncludeDNS: true,
|
IncludeDNS: true,
|
||||||
|
@ -536,6 +563,10 @@ func UpdateClient(db store.IStore) echo.HandlerFunc {
|
||||||
var _client model.Client
|
var _client model.Client
|
||||||
c.Bind(&_client)
|
c.Bind(&_client)
|
||||||
|
|
||||||
|
if _, err := xid.FromString(_client.ID); err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"})
|
||||||
|
}
|
||||||
|
|
||||||
// validate client existence
|
// validate client existence
|
||||||
clientData, err := db.GetClientByID(_client.ID, model.QRCodeSettings{Enabled: false})
|
clientData, err := db.GetClientByID(_client.ID, model.QRCodeSettings{Enabled: false})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -642,6 +673,10 @@ func SetClientStatus(db store.IStore) echo.HandlerFunc {
|
||||||
clientID := data["id"].(string)
|
clientID := data["id"].(string)
|
||||||
status := data["status"].(bool)
|
status := data["status"].(bool)
|
||||||
|
|
||||||
|
if _, err := xid.FromString(clientID); err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"})
|
||||||
|
}
|
||||||
|
|
||||||
clientData, err := db.GetClientByID(clientID, model.QRCodeSettings{Enabled: false})
|
clientData, err := db.GetClientByID(clientID, model.QRCodeSettings{Enabled: false})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, err.Error()})
|
return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, err.Error()})
|
||||||
|
@ -667,6 +702,10 @@ func DownloadClient(db store.IStore) echo.HandlerFunc {
|
||||||
return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Missing clientid parameter"})
|
return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Missing clientid parameter"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := xid.FromString(clientID); err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"})
|
||||||
|
}
|
||||||
|
|
||||||
clientData, err := db.GetClientByID(clientID, model.QRCodeSettings{Enabled: false})
|
clientData, err := db.GetClientByID(clientID, model.QRCodeSettings{Enabled: false})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Cannot generate client id %s config file for downloading: %v", clientID, err)
|
log.Errorf("Cannot generate client id %s config file for downloading: %v", clientID, err)
|
||||||
|
@ -700,6 +739,10 @@ func RemoveClient(db store.IStore) echo.HandlerFunc {
|
||||||
client := new(model.Client)
|
client := new(model.Client)
|
||||||
c.Bind(client)
|
c.Bind(client)
|
||||||
|
|
||||||
|
if _, err := xid.FromString(client.ID); err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"})
|
||||||
|
}
|
||||||
|
|
||||||
// delete client from database
|
// delete client from database
|
||||||
|
|
||||||
if err := db.DeleteClient(client.ID); err != nil {
|
if err := db.DeleteClient(client.ID); err != nil {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -18,7 +19,13 @@ func (host WakeOnLanHost) ResolveResourceName() (string, error) {
|
||||||
return "", errors.New("mac Address is Empty")
|
return "", errors.New("mac Address is Empty")
|
||||||
}
|
}
|
||||||
resourceName = strings.ToUpper(resourceName)
|
resourceName = strings.ToUpper(resourceName)
|
||||||
return strings.ReplaceAll(resourceName, ":", "-"), nil
|
resourceName = strings.ReplaceAll(resourceName, ":", "-")
|
||||||
|
|
||||||
|
if _, err := net.ParseMAC(resourceName); err != nil {
|
||||||
|
return "", errors.New("invalid mac address")
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const WakeOnLanHostCollectionName = "wake_on_lan_hosts"
|
const WakeOnLanHostCollectionName = "wake_on_lan_hosts"
|
||||||
|
|
|
@ -38,12 +38,12 @@ func New(dbPath string) (*JsonDB, error) {
|
||||||
func (o *JsonDB) Init() error {
|
func (o *JsonDB) Init() error {
|
||||||
var clientPath string = path.Join(o.dbPath, "clients")
|
var clientPath string = path.Join(o.dbPath, "clients")
|
||||||
var serverPath string = path.Join(o.dbPath, "server")
|
var serverPath string = path.Join(o.dbPath, "server")
|
||||||
|
var userPath string = path.Join(o.dbPath, "users")
|
||||||
var wakeOnLanHostsPath string = path.Join(o.dbPath, "wake_on_lan_hosts")
|
var wakeOnLanHostsPath string = path.Join(o.dbPath, "wake_on_lan_hosts")
|
||||||
var serverInterfacePath string = path.Join(serverPath, "interfaces.json")
|
var serverInterfacePath string = path.Join(serverPath, "interfaces.json")
|
||||||
var serverKeyPairPath string = path.Join(serverPath, "keypair.json")
|
var serverKeyPairPath string = path.Join(serverPath, "keypair.json")
|
||||||
var globalSettingPath string = path.Join(serverPath, "global_settings.json")
|
var globalSettingPath string = path.Join(serverPath, "global_settings.json")
|
||||||
var hashesPath string = path.Join(serverPath, "hashes.json")
|
var hashesPath string = path.Join(serverPath, "hashes.json")
|
||||||
var userPath string = path.Join(serverPath, "users.json")
|
|
||||||
|
|
||||||
// create directories if they do not exist
|
// create directories if they do not exist
|
||||||
if _, err := os.Stat(clientPath); os.IsNotExist(err) {
|
if _, err := os.Stat(clientPath); os.IsNotExist(err) {
|
||||||
|
@ -52,12 +52,12 @@ func (o *JsonDB) Init() error {
|
||||||
if _, err := os.Stat(serverPath); os.IsNotExist(err) {
|
if _, err := os.Stat(serverPath); os.IsNotExist(err) {
|
||||||
os.MkdirAll(serverPath, os.ModePerm)
|
os.MkdirAll(serverPath, os.ModePerm)
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(wakeOnLanHostsPath); os.IsNotExist(err) {
|
|
||||||
os.MkdirAll(wakeOnLanHostsPath, os.ModePerm)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(userPath); os.IsNotExist(err) {
|
if _, err := os.Stat(userPath); os.IsNotExist(err) {
|
||||||
os.MkdirAll(userPath, os.ModePerm)
|
os.MkdirAll(userPath, os.ModePerm)
|
||||||
}
|
}
|
||||||
|
if _, err := os.Stat(wakeOnLanHostsPath); os.IsNotExist(err) {
|
||||||
|
os.MkdirAll(wakeOnLanHostsPath, os.ModePerm)
|
||||||
|
}
|
||||||
|
|
||||||
// server's interface
|
// server's interface
|
||||||
if _, err := os.Stat(serverInterfacePath); os.IsNotExist(err) {
|
if _, err := os.Stat(serverInterfacePath); os.IsNotExist(err) {
|
||||||
|
@ -149,12 +149,6 @@ func (o *JsonDB) Init() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUser func to query user info from the database
|
|
||||||
func (o *JsonDB) GetUser() (model.User, error) {
|
|
||||||
user := model.User{}
|
|
||||||
return user, o.conn.Read("server", "users", &user)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUsers func to get all users from the database
|
// GetUsers func to get all users from the database
|
||||||
func (o *JsonDB) GetUsers() ([]model.User, error) {
|
func (o *JsonDB) GetUsers() ([]model.User, error) {
|
||||||
var users []model.User
|
var users []model.User
|
||||||
|
|
Loading…
Add table
Reference in a new issue