From 71d92c30e31b8aa9e158e2d5ab977e2d147a1f96 Mon Sep 17 00:00:00 2001 From: CityFun <31820853+zhengkunwang223@users.noreply.github.com> Date: Wed, 28 May 2025 18:20:22 +0800 Subject: [PATCH] feat: add openBaseDir for php website config (#8866) --- agent/app/api/v2/website.go | 20 ++++++ agent/app/dto/request/website.go | 5 ++ agent/app/dto/response/website.go | 1 + agent/app/service/website.go | 22 ++++++- agent/app/service/website_utils.go | 4 +- agent/router/ro_website.go | 2 + frontend/src/api/interface/website.ts | 6 ++ frontend/src/api/modules/website.ts | 4 ++ frontend/src/lang/modules/en.ts | 3 + frontend/src/lang/modules/ja.ts | 3 + frontend/src/lang/modules/ko.ts | 3 + frontend/src/lang/modules/ms.ts | 3 + frontend/src/lang/modules/pt-br.ts | 3 + frontend/src/lang/modules/ru.ts | 3 + frontend/src/lang/modules/zh-Hant.ts | 2 + frontend/src/lang/modules/zh.ts | 2 + .../website/config/basic/php/index.vue | 63 +++++++++++++------ 17 files changed, 127 insertions(+), 22 deletions(-) diff --git a/agent/app/api/v2/website.go b/agent/app/api/v2/website.go index 207c42bee..0eb20b852 100644 --- a/agent/app/api/v2/website.go +++ b/agent/app/api/v2/website.go @@ -1101,3 +1101,23 @@ func (b *BaseApi) ClearProxyCache(c *gin.Context) { } helper.Success(c) } + +// @Tags Website +// @Summary Operate Cross Site Access +// @Accept json +// @Param request body request.CrossSiteAccessOp true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/crosssite [post] +func (b *BaseApi) OperateCrossSiteAccess(c *gin.Context) { + var req request.CrossSiteAccessOp + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.OperateCrossSiteAccess(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/dto/request/website.go b/agent/app/dto/request/website.go index d65e67593..b12c56357 100644 --- a/agent/app/dto/request/website.go +++ b/agent/app/dto/request/website.go @@ -289,3 +289,8 @@ type WebsiteProxyDel struct { ID uint `json:"id" validate:"required"` Name string `json:"name" validate:"required"` } + +type CrossSiteAccessOp struct { + WebsiteID uint `json:"websiteID" validate:"required"` + Operation string `json:"operation" validate:"required,oneof=Enable Disable"` +} diff --git a/agent/app/dto/response/website.go b/agent/app/dto/response/website.go index bfe63b8cc..cbeaa0821 100644 --- a/agent/app/dto/response/website.go +++ b/agent/app/dto/response/website.go @@ -15,6 +15,7 @@ type WebsiteDTO struct { RuntimeName string `json:"runtimeName"` RuntimeType string `json:"runtimeType"` SiteDir string `json:"siteDir"` + OpenBaseDir bool `json:"openBaseDir"` } type WebsiteRes struct { diff --git a/agent/app/service/website.go b/agent/app/service/website.go index cc0cabaa1..38a016c20 100644 --- a/agent/app/service/website.go +++ b/agent/app/service/website.go @@ -124,6 +124,8 @@ type IWebsiteService interface { GetWebsiteResource(websiteID uint) ([]response.Resource, error) ListDatabases() ([]response.Database, error) ChangeDatabase(req request.ChangeDatabase) error + + OperateCrossSiteAccess(req request.CrossSiteAccessOp) error } func NewIWebsiteService() IWebsiteService { @@ -428,7 +430,7 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error) return err } if runtime.Type == constant.RuntimePHP && runtime.Resource == constant.ResourceAppstore { - createPHPConfig(website) + createOpenBasedirConfig(website) } } tx, ctx := helper.GetTxAndContext() @@ -573,6 +575,9 @@ func (w WebsiteService) GetWebsite(id uint) (response.WebsiteDTO, error) { } res.RuntimeType = runtime.Type res.RuntimeName = runtime.Name + if runtime.Type == constant.RuntimePHP { + res.OpenBaseDir = files.NewFileOp().Stat(path.Join(GetSitePath(website, SiteIndexDir), ".user.ini")) + } } return res, nil } @@ -3278,3 +3283,18 @@ func (w WebsiteService) ChangeDatabase(req request.ChangeDatabase) error { website.DbType = req.DatabaseType return websiteRepo.Save(context.Background(), &website) } + +func (w WebsiteService) OperateCrossSiteAccess(req request.CrossSiteAccessOp) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + if req.Operation == constant.StatusEnable { + createOpenBasedirConfig(&website) + } + if req.Operation == constant.StatusDisable { + fileOp := files.NewFileOp() + return fileOp.DeleteFile(path.Join(GetSitePath(website, SiteIndexDir), ".user.ini")) + } + return nil +} diff --git a/agent/app/service/website_utils.go b/agent/app/service/website_utils.go index 38ee4b579..ed1fe8616 100644 --- a/agent/app/service/website_utils.go +++ b/agent/app/service/website_utils.go @@ -349,11 +349,11 @@ func createAllWebsitesWAFConfig(websites []model.Website) error { return nil } -func createPHPConfig(website *model.Website) { +func createOpenBasedirConfig(website *model.Website) { fileOp := files.NewFileOp() userIniPath := path.Join(GetSitePath(*website, SiteIndexDir), ".user.ini") _ = fileOp.CreateFile(userIniPath) - _ = fileOp.SaveFile(userIniPath, fmt.Sprintf("open_basedir=/www/sites/%s/index", website.Alias), 0644) + _ = fileOp.SaveFile(userIniPath, fmt.Sprintf("open_basedir=/www/sites/%s/index:/tmp/", website.Alias), 0644) } func createWafConfig(website *model.Website, domains []model.WebsiteDomain) error { diff --git a/agent/router/ro_website.go b/agent/router/ro_website.go index d3f2a28a1..decfb746f 100644 --- a/agent/router/ro_website.go +++ b/agent/router/ro_website.go @@ -84,5 +84,7 @@ func (a *WebsiteRouter) InitRouter(Router *gin.RouterGroup) { websiteRouter.GET("/resource/:id", baseApi.GetWebsiteResource) websiteRouter.GET("/databases", baseApi.GetWebsiteDatabase) websiteRouter.POST("/databases", baseApi.ChangeWebsiteDatabase) + + websiteRouter.POST("/crosssite", baseApi.OperateCrossSiteAccess) } } diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index 42eb0a393..26922f39e 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -36,6 +36,7 @@ export namespace Website { appName: string; runtimeName: string; runtimeType: string; + openBaseDir: boolean; } export interface WebsiteRes extends CommonModel { protocol: string; @@ -646,4 +647,9 @@ export namespace Website { databaseID: number; databaseType: string; } + + export interface CrossSiteAccessOp { + websiteID: number; + operation: string; + } } diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index 48dcc59b9..c657d9a9e 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -343,3 +343,7 @@ export const operateCustomRewrite = (req: Website.CustomRewirte) => { export const listCustomRewrite = () => { return http.get(`/websites/rewrite/custom`); }; + +export const operateCrossSiteAccess = (req: Website.CrossSiteAccessOp) => { + return http.post(`/websites/crosssite`, req); +}; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 8e4186032..188bd47cd 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -2476,6 +2476,9 @@ const message = { useProxy: 'Use Proxy', useProxyHelper: 'Use the proxy server address in the panel settings', westCN: 'West Digital', + openBaseDir: 'Prevent Cross-Site Attacks', + openBaseDirHelper: + 'open_basedir is used to restrict the PHP file access path, which helps prevent cross-site access and enhance security', }, php: { short_open_tag: 'Short tag support', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 51c0b2188..ea20ece0d 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -2382,6 +2382,9 @@ const message = { useProxy: 'プロキシを使用', useProxyHelper: 'パネル設定のプロキシサーバーアドレスを使用', westCN: '西部デジタル', + openBaseDir: 'クロスサイト攻撃を防ぐ', + openBaseDirHelper: + 'open_basedir は PHP ファイルのアクセスパスを制限し、クロスサイトアクセスを防ぎセキュリティを向上させるために使用されます', }, php: { short_open_tag: '短いタグサポート', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 89a83ec3e..34ef4a99c 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -2341,6 +2341,9 @@ const message = { useProxy: '프록시 사용', useProxyHelper: '패널 설정의 프록시 서버 주소 사용', westCN: '서부 디지털', + openBaseDir: '사이트 간 공격 방지', + openBaseDirHelper: + 'open_basedir는 PHP 파일 액세스 경로를 제한하여 사이트 간 액세스를 방지하고 보안을 향상시키는 데 사용됩니다', }, php: { short_open_tag: '짧은 태그 지원', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 7ada74733..5d343a892 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -2436,6 +2436,9 @@ const message = { useProxy: 'Gunakan Proksi', useProxyHelper: 'Gunakan alamat pelayan proksi dalam tetapan panel', westCN: 'West Digital', + openBaseDir: 'Pencegahan Serangan Lintas Situs', + openBaseDirHelper: + 'open_basedir digunakan untuk membatasi jalur akses file PHP, yang membantu mencegah akses lintas situs dan meningkatkan keamanan', }, php: { short_open_tag: 'Sokongan tag pendek', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 7a5c30848..f3c2765a7 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -2433,6 +2433,9 @@ const message = { useProxy: 'Usar Proxy', useProxyHelper: 'Usar o endereço do servidor proxy nas configurações do painel', westCN: 'West Digital', + openBaseDir: 'Prevenir Ataques entre Sites', + openBaseDirHelper: + 'open_basedir é usado para restringir o caminho de acesso a arquivos PHP, ajudando a prevenir acesso entre sites e aumentar a segurança', }, php: { short_open_tag: 'Suporte para short tags', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 48b2bb7a5..41e8c608b 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -2432,6 +2432,9 @@ const message = { useProxy: 'Использовать прокси', useProxyHelper: 'Использовать адрес прокси-сервера в настройках панели', westCN: 'Западный цифровой', + openBaseDir: 'Предотвращение межсайтовых атак', + openBaseDirHelper: + 'open_basedir используется для ограничения пути доступа к файлам PHP, что помогает предотвратить межсайтовый доступ и повысить безопасность', }, php: { short_open_tag: 'Поддержка коротких тегов', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 8ce4b2c08..8878c98f0 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -2296,6 +2296,8 @@ const message = { useProxy: '使用代理', useProxyHelper: '使用面板設置中的代理服務器地址', westCN: '西部數碼', + openBaseDir: '防跨站攻擊', + openBaseDirHelper: 'open_basedir 用於限制 PHP 文件訪問路徑,有助於防止跨站訪問和提升安全性', }, php: { short_open_tag: '短標簽支持', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 69d983622..3b2fe7f46 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -2286,6 +2286,8 @@ const message = { useProxy: '使用代理', useProxyHelper: '使用面板设置中的代理服务器地址', westCN: '西部数码', + openBaseDir: '防跨站攻击', + openBaseDirHelper: 'open_basedir 用于限制 PHP 文件访问路径,有助于防止跨站访问和提升安全性', }, php: { short_open_tag: '短标签支持', diff --git a/frontend/src/views/website/website/config/basic/php/index.vue b/frontend/src/views/website/website/config/basic/php/index.vue index fec70d875..d7c3d82b8 100644 --- a/frontend/src/views/website/website/config/basic/php/index.vue +++ b/frontend/src/views/website/website/config/basic/php/index.vue @@ -2,28 +2,39 @@
- + -
- {{ $t('website.staticChangePHPHelper') }} -
+ + {{ $t('website.staticChangePHPHelper') }} +
- - - - - + + + + + + + + + + {{ $t('commons.button.save') }} + + + - - - {{ $t('commons.button.save') }} - + + + {{ $t('website.openBaseDirHelper') }}
@@ -36,7 +47,7 @@ import { SearchRuntimes } from '@/api/modules/runtime'; import { onMounted, reactive, ref } from 'vue'; import { Runtime } from '@/api/interface/runtime'; import { Website } from '@/api/interface/website'; -import { changePHPVersion, getWebsite } from '@/api/modules/website'; +import { changePHPVersion, getWebsite, operateCrossSiteAccess } from '@/api/modules/website'; import i18n from '@/lang'; import { MsgSuccess } from '@/utils/message'; const props = defineProps({ @@ -56,7 +67,9 @@ const loading = ref(false); const oldRuntimeID = ref(0); const website = ref({ type: '', + openBaseDir: false, }); +const openBaseDir = ref(false); const getRuntimes = async () => { try { @@ -95,6 +108,18 @@ const getWebsiteDetail = async () => { versionReq.runtimeID = res.data.runtimeID; oldRuntimeID.value = res.data.runtimeID; website.value = res.data; + openBaseDir.value = res.data.openBaseDir || false; +}; + +const operateCrossSite = async () => { + try { + await operateCrossSiteAccess({ + websiteID: props.id, + operation: openBaseDir.value ? 'Enable' : 'Disable', + }); + MsgSuccess(i18n.global.t('commons.msg.updateSuccess')); + getWebsiteDetail(); + } catch (error) {} }; onMounted(() => {