Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ channel_data/*
caddy_config/
data/kvrocks.sock
.env
.ds_store
.ds_store
0002-frontend-api-adjustment.txt
0001-backend-security-changes.txt
8 changes: 8 additions & 0 deletions SET.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ ROOT_STATIC_FOLDER=/path/files
במידה ומעוניינים לאפשר צפיה בקבצים רק לרשומים, יש להגדיר בהגדרות הניהול:
`require_auth_for_view_files` עם הערך 1.

## אישור טעינת האתר ב - i frame רק לדומיינים מאושרים
במידה וההגדרה `frame_ancestors_domains` מוגדרת בממשק הניהול עם רשימת דומיינים מופרדים ברווח, הטעינה של האתר ב - i frame מאתרים חיצוניים תוגבל לדומיינים האלו בלבד.

## הגדרת דומיינים מאושרים לגישה לכתובות המאובטחות של האתר
לתוספת אבטחה ניתן להגדיר את ההגדרה `validate_Origin` עם הערך 1 כדי שהאתר יסמוך רק על הדומיין המקורי של האתר לגישה לכתובות מאובטחות באתר למניעת התקפות CSRF. במידה וההגדרה הזו מופעלת ניתן להגדיר דומיינים מאושרים נוספים ע"י ההגדרה `allowed_origins` עם רשימת הדומיינים מופרדים ע"י פסיק.

## הפעלת כפתור צור קשר
במידה וההגדרה `contact_us` מוגדרת בממשק הניהול עם קישור להפניה, יוצג למשתמשים כפתור צור קשר המפנה לקישור.

Expand Down Expand Up @@ -153,6 +159,8 @@ POST https://example.com/api/import/post
|---------------|------|------|
|`require_auth` | `1` |חיוב הזדהות בכניסה לערוץ |
|`require_auth_for_view_files`|`1`|חיוב הזדהות לצפיה בקבצי תמונות וסרטונים בערוץ|
|`validate_Origin`|`1`|בדיקה של הOrigin ממנו נשלחת הבקשה לדומיין המקורי של האתר או לדומיינים המוגדרים בהגדרה `allowed_origins`|
|`frame_ancestors_domains`|url|רשימת כתובות מופרדת ברווחים של דומיינים המאושרים לטעינת האתר ב - i frame|
|`api_secret_key`|`1`|מפתח עבור יבוא הודעות באמצעות API|
|`webhook_url`|`https://example.com/webhook`|כתובת לשליחת וובהוק|
|`webhook_verify_token`|`your-secret-token`|טוקן לשליחה יחד עם וובהוק|
Expand Down
79 changes: 77 additions & 2 deletions backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import (
"encoding/gob"
"log"
"net/http"
"net/url"
_ "net/http/pprof"
"os"
"path/filepath"

"github.com/boj/redistore"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/gorilla/sessions"
)

var rootStaticFolder = os.Getenv("ROOT_STATIC_FOLDER")
Expand All @@ -37,6 +39,62 @@ func ifRequireAuth(next http.Handler) http.Handler {
})
}

func cspFrameAncestorsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if settingConfig.FrameAncestorsDomains != "" {
cspValue := "frame-ancestors 'self'"
cspValue = cspValue + " " + settingConfig.FrameAncestorsDomains
w.Header().Set("Content-Security-Policy", cspValue)
}
next.ServeHTTP(w, r)
})
}

func validateOrigin(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")

if !settingConfig.validateOrigin {
next.ServeHTTP(w, r)
return
}

if origin == "" {
next.ServeHTTP(w, r)
return
}

originURL, err := url.Parse(origin)
if err != nil {
http.Error(w, "Forbidden: Malformed Origin Header", http.StatusForbidden)
return
}

scheme := "http"
if r.TLS != nil {
scheme = "https"
}
appHostOrigin := scheme + "://" + r.Host
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ואם התוקף משנה גם את זה?
לכאורה צריך להעביר באמצעות משתנה סביבה את הדומיין ברירת מחדל שמאושר, אחרת גם יש מצב שמוגדרת הבדיקה הזאת ובפועל אין דומיין מוגדר ונשארים נעולים מחוץ למערכת.
https://www.calhoun.io/csrf-protection-via-headers-in-go-125/?utm_source=chatgpt.com
תראה גם את זה.
https://github.com/NetFree-Community/TheChannel/blob/master/backend/main.go#L19
בכל אופן, עדיף לשלב את הבדיקה הזאת כאן, בלי לעשות פונקציה נפרדת על זה.

Copy link
Author

@ClickAndGoScript ClickAndGoScript Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

את מה התוקף ישנה? את הHost?
זה נשלח ע"י הדפדפן, אי אפשר לשנות את זה.

אין שום אפשרות להינעל מחוץ למערכת, כי הדומיין של האתר עצמו תמיד מאושר בצורה קשיחה בקוד

ההגנה שביצעתי היא כמו מה שמופיע במדריך שקישרת, ומה שיש שם בגירסה 1.25 לא רלוונטי.

הפונקציה הנפרדת זה כיון שזה בדיקה נוספת ומיותרת בעיקרון, שמופעלת רק אם הפעילו אותה בהגדרות, ואין צורך לבלגן את הפונקצית התחברות הרגילה.


allowed := make(map[string]struct{})

allowed[appHostOrigin] = struct{}{}

if len(settingConfig.AllowedOrigins) > 0 {
for _, allowedOrigin := range settingConfig.AllowedOrigins {
allowed[allowedOrigin] = struct{}{}
}
}

if _, ok := allowed[originURL.String()]; !ok {
http.Error(w, "Forbidden: Invalid Origin", http.StatusForbidden)
return
}

next.ServeHTTP(w, r)
})
}

func main() {
gob.Register(Session{})
initializePrivilegeUsers()
Expand All @@ -48,11 +106,27 @@ func main() {
panic(err)
}
store.SetMaxAge(60 * 60 * 24 * 30)
store.Options.HttpOnly = true
store.Options = &sessions.Options{
Path: "/",
HttpOnly: true,
}

sameSitePolicy := os.Getenv("COOKIE_SAMESITE_POLICY")

switch sameSitePolicy {
case "None":
store.Options.SameSite = http.SameSiteNoneMode
store.Options.Secure = true
case "Strict":
store.Options.SameSite = http.SameSiteStrictMode
default:
store.Options.SameSite = http.SameSiteLaxMode
}
defer store.Close()

r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(cspFrameAncestorsMiddleware)

// Protected with api key
r.Post("/api/import/post", addNewPost)
Expand Down Expand Up @@ -86,10 +160,11 @@ func main() {

api.Route("/admin", func(protected chi.Router) {
// ⚠️ WARNING: Route not check privilege use protectedWithPrivilege to check privilege.
protected.Use(validateOrigin)

protected.Post("/new", protectedWithPrivilege(Writer, addMessage))
protected.Post("/edit-message", protectedWithPrivilege(Writer, updateMessage))
protected.Get("/delete-message/{id}", protectedWithPrivilege(Writer, deleteMessage))
protected.Post("/delete-message/{id}", protectedWithPrivilege(Writer, deleteMessage))
protected.Post("/upload", protectedWithPrivilege(Writer, uploadFile))
protected.Post("/edit-channel-info", protectedWithPrivilege(Moderator, editChannelInfo))
protected.Get("/statistics", protectedWithPrivilege(Moderator, getStatistics))
Expand Down
19 changes: 16 additions & 3 deletions backend/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ type SettingConfig struct {
MaxFileSize int64
CustomTitle string
ContactUs string
FrameAncestorsDomains string
AllowedOrigins []string
validateOrigin bool
}

type Setting struct {
Expand Down Expand Up @@ -184,8 +187,18 @@ func (s *Settings) ToConfig() *SettingConfig {
case "fcm_json_universe_domain":
config.FcmJson.UniverseDomain = setting.GetString()

case "contact_us":
config.ContactUs = setting.GetString()
case "frame_ancestors_domains":
config.FrameAncestorsDomains = setting.GetString()
case "allowed_origins":
originsStr := setting.GetString()
if originsStr != "" {
origins := strings.Split(originsStr, ",")
for _, origin := range origins {
config.AllowedOrigins = append(config.AllowedOrigins, strings.TrimSpace(origin))
}
}
case "validate_Origin":
config.validateOrigin = setting.GetBool()
}
}

Expand All @@ -197,7 +210,7 @@ func (s *Setting) GetBool() bool {
return b
}

func (s *Setting) GetString() string {
func (s *Setting) GetString() string {
str, _ := dyno.GetString(s.Value)
return str
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/services/admin.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class AdminService {
}

deleteMessage(id: number | undefined): Observable<ChatMessage> {
return this.http.get<ChatMessage>(`/api/admin/delete-message/${id}`);
return this.http.post<ChatMessage>(`/api/admin/delete-message/${id}` ,null);
}

uploadFile(formData: FormData) {
Expand Down
3 changes: 2 additions & 1 deletion sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ REDIS_PROTOCOL=unix
# Google-oauth2
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
ADMIN_USERS=example@gmail.com,example1@gmail.com
ADMIN_USERS=example@gmail.com,example1@gmail.com
COOKIE_SAMESITE_POLICY=None