mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-12 16:36:12 +08:00
fix: 工具箱管理防止命令注入 (#3215)
This commit is contained in:
parent
40da8a7525
commit
a6e12d88a3
10 changed files with 157 additions and 4 deletions
|
@ -10,6 +10,7 @@ type Fail2BanBaseInfo struct {
|
||||||
BanTime string `json:"banTime"`
|
BanTime string `json:"banTime"`
|
||||||
FindTime string `json:"findTime"`
|
FindTime string `json:"findTime"`
|
||||||
BanAction string `json:"banAction"`
|
BanAction string `json:"banAction"`
|
||||||
|
LogPath string `json:"logPath"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Fail2BanSearch struct {
|
type Fail2BanSearch struct {
|
||||||
|
@ -17,7 +18,7 @@ type Fail2BanSearch struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Fail2BanUpdate struct {
|
type Fail2BanUpdate struct {
|
||||||
Key string `json:"key" validate:"required,oneof=port bantime findtime maxretry banaction"`
|
Key string `json:"key" validate:"required,oneof=port bantime findtime maxretry banaction logpath"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -109,6 +109,9 @@ func (u *DeviceService) CheckDNS(key, value string) (bool, error) {
|
||||||
func (u *DeviceService) Update(key, value string) error {
|
func (u *DeviceService) Update(key, value string) error {
|
||||||
switch key {
|
switch key {
|
||||||
case "TimeZone":
|
case "TimeZone":
|
||||||
|
if cmd.CheckIllegal(value) {
|
||||||
|
return buserr.New(constant.ErrCmdIllegal)
|
||||||
|
}
|
||||||
if err := ntp.UpdateSystemTimeZone(value); err != nil {
|
if err := ntp.UpdateSystemTimeZone(value); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -123,11 +126,17 @@ func (u *DeviceService) Update(key, value string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "Hostname":
|
case "Hostname":
|
||||||
|
if cmd.CheckIllegal(value) {
|
||||||
|
return buserr.New(constant.ErrCmdIllegal)
|
||||||
|
}
|
||||||
std, err := cmd.Execf("%s hostnamectl set-hostname %s", cmd.SudoHandleCmd(), value)
|
std, err := cmd.Execf("%s hostnamectl set-hostname %s", cmd.SudoHandleCmd(), value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(std)
|
return errors.New(std)
|
||||||
}
|
}
|
||||||
case "Ntp", "LocalTime":
|
case "Ntp", "LocalTime":
|
||||||
|
if cmd.CheckIllegal(value) {
|
||||||
|
return buserr.New(constant.ErrCmdIllegal)
|
||||||
|
}
|
||||||
ntpValue := value
|
ntpValue := value
|
||||||
if key == "LocalTime" {
|
if key == "LocalTime" {
|
||||||
ntpItem, err := settingRepo.Get(settingRepo.WithByKey("NtpSite"))
|
ntpItem, err := settingRepo.Get(settingRepo.WithByKey("NtpSite"))
|
||||||
|
@ -193,6 +202,9 @@ func (u *DeviceService) UpdateHosts(req []dto.HostHelper) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *DeviceService) UpdatePasswd(req dto.ChangePasswd) error {
|
func (u *DeviceService) UpdatePasswd(req dto.ChangePasswd) error {
|
||||||
|
if cmd.CheckIllegal(req.User, req.Passwd) {
|
||||||
|
return buserr.New(constant.ErrCmdIllegal)
|
||||||
|
}
|
||||||
std, err := cmd.Execf("%s echo '%s:%s' | %s chpasswd", cmd.SudoHandleCmd(), req.User, req.Passwd, cmd.SudoHandleCmd())
|
std, err := cmd.Execf("%s echo '%s:%s' | %s chpasswd", cmd.SudoHandleCmd(), req.User, req.Passwd, cmd.SudoHandleCmd())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "does not exist") {
|
if strings.Contains(err.Error(), "does not exist") {
|
||||||
|
@ -204,6 +216,9 @@ func (u *DeviceService) UpdatePasswd(req dto.ChangePasswd) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *DeviceService) UpdateSwap(req dto.SwapHelper) error {
|
func (u *DeviceService) UpdateSwap(req dto.SwapHelper) error {
|
||||||
|
if cmd.CheckIllegal(req.Path) {
|
||||||
|
return buserr.New(constant.ErrCmdIllegal)
|
||||||
|
}
|
||||||
if !req.IsNew {
|
if !req.IsNew {
|
||||||
std, err := cmd.Execf("%s swapoff %s", cmd.SudoHandleCmd(), req.Path)
|
std, err := cmd.Execf("%s swapoff %s", cmd.SudoHandleCmd(), req.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -208,4 +208,9 @@ func loadFailValue(line string, baseInfo *dto.Fail2BanBaseInfo) {
|
||||||
itemValue = strings.ReplaceAll(itemValue, "=", "")
|
itemValue = strings.ReplaceAll(itemValue, "=", "")
|
||||||
baseInfo.BanAction = strings.TrimSpace(itemValue)
|
baseInfo.BanAction = strings.TrimSpace(itemValue)
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(line, "logpath") {
|
||||||
|
itemValue := strings.ReplaceAll(line, "logpath", "")
|
||||||
|
itemValue = strings.ReplaceAll(itemValue, "=", "")
|
||||||
|
baseInfo.LogPath = strings.TrimSpace(itemValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@ func (f *Fail2ban) ReBanIPs(ips []string) error {
|
||||||
|
|
||||||
func (f *Fail2ban) ListBanned() ([]string, error) {
|
func (f *Fail2ban) ListBanned() ([]string, error) {
|
||||||
var lists []string
|
var lists []string
|
||||||
stdout, err := cmd.Exec("fail2ban-client get sshd banned")
|
stdout, err := cmd.Exec("fail2ban-client get sshd banip")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return lists, err
|
return lists, err
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,7 @@ maxretry = 5
|
||||||
findtime = 300
|
findtime = 300
|
||||||
bantime = 600
|
bantime = 600
|
||||||
action = %(action_mwl)s
|
action = %(action_mwl)s
|
||||||
logpath = /var/log/secure`
|
logpath = $logpath`
|
||||||
|
|
||||||
banaction := ""
|
banaction := ""
|
||||||
if active, _ := systemctl.IsActive("firewalld"); active {
|
if active, _ := systemctl.IsActive("firewalld"); active {
|
||||||
|
@ -158,6 +158,14 @@ logpath = /var/log/secure`
|
||||||
banaction = "iptables-allports"
|
banaction = "iptables-allports"
|
||||||
}
|
}
|
||||||
initFile = strings.ReplaceAll(initFile, "$banaction", banaction)
|
initFile = strings.ReplaceAll(initFile, "$banaction", banaction)
|
||||||
|
|
||||||
|
logPath := ""
|
||||||
|
if _, err := os.Stat("/var/log/secure"); err == nil {
|
||||||
|
logPath = "/var/log/secure"
|
||||||
|
} else {
|
||||||
|
logPath = "/var/log/auth.log"
|
||||||
|
}
|
||||||
|
initFile = strings.ReplaceAll(initFile, "$logpath", logPath)
|
||||||
if err := os.WriteFile(defaultPath, []byte(initFile), 0640); err != nil {
|
if err := os.WriteFile(defaultPath, []byte(initFile), 0640); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -965,6 +965,8 @@ const message = {
|
||||||
allPorts: ' (All Ports)',
|
allPorts: ' (All Ports)',
|
||||||
ignoreIP: 'IP Whitelist',
|
ignoreIP: 'IP Whitelist',
|
||||||
bannedIP: 'IP Blacklist',
|
bannedIP: 'IP Blacklist',
|
||||||
|
logPath: 'Log Path',
|
||||||
|
logPathHelper: 'Default is /var/log/secure or /var/log/auth.log',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
logs: {
|
logs: {
|
||||||
|
|
|
@ -916,6 +916,8 @@ const message = {
|
||||||
allPorts: ' (所有端口)',
|
allPorts: ' (所有端口)',
|
||||||
ignoreIP: 'IP 白名單',
|
ignoreIP: 'IP 白名單',
|
||||||
bannedIP: 'IP 黑名單',
|
bannedIP: 'IP 黑名單',
|
||||||
|
logPath: '日誌路徑',
|
||||||
|
logPathHelper: '預設為 /var/log/secure 或者 /var/log/auth.log',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
logs: {
|
logs: {
|
||||||
|
|
|
@ -917,6 +917,8 @@ const message = {
|
||||||
allPorts: ' (所有端口)',
|
allPorts: ' (所有端口)',
|
||||||
ignoreIP: 'IP 白名单',
|
ignoreIP: 'IP 白名单',
|
||||||
bannedIP: 'IP 黑名单',
|
bannedIP: 'IP 黑名单',
|
||||||
|
logPath: '日志路径',
|
||||||
|
logPathHelper: '默认 /var/log/secure 或者 /var/log/auth.log',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
logs: {
|
logs: {
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
<el-select v-model="row.sizeUnit" style="width: 85px">
|
<el-select v-model="row.sizeUnit" style="width: 85px">
|
||||||
<el-option label="KB" value="KB" />
|
<el-option label="KB" value="KB" />
|
||||||
<el-option label="MB" value="MB" />
|
<el-option label="MB" value="MB" />
|
||||||
<el-option label="GB" value="G" />
|
<el-option label="GB" value="GB" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
@ -202,6 +202,7 @@ const onSave = async (row) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadItemSize = (row: any) => {
|
const loadItemSize = (row: any) => {
|
||||||
|
console.log(row.size, row.sizeUnit);
|
||||||
switch (row.sizeUnit) {
|
switch (row.sizeUnit) {
|
||||||
case 'KB':
|
case 'KB':
|
||||||
return row.size;
|
return row.size;
|
||||||
|
|
|
@ -102,6 +102,15 @@
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('toolbox.fail2ban.logPath')" prop="logPath">
|
||||||
|
<el-input disabled v-model="form.logPath">
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="onChangeLogPath" icon="Setting">
|
||||||
|
{{ $t('commons.button.set') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
@ -154,6 +163,7 @@
|
||||||
<BanTime ref="banTimeRef" @search="search" />
|
<BanTime ref="banTimeRef" @search="search" />
|
||||||
<FindTime ref="findTimeRef" @search="search" />
|
<FindTime ref="findTimeRef" @search="search" />
|
||||||
<BanAction ref="banActionRef" @search="search" />
|
<BanAction ref="banActionRef" @search="search" />
|
||||||
|
<LogPath ref="logPathRef" @search="search" />
|
||||||
|
|
||||||
<IPs ref="listRef" />
|
<IPs ref="listRef" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -168,6 +178,7 @@ import MaxRetry from '@/views/toolbox/fail2ban/max-retry/index.vue';
|
||||||
import BanTime from '@/views/toolbox/fail2ban/ban-time/index.vue';
|
import BanTime from '@/views/toolbox/fail2ban/ban-time/index.vue';
|
||||||
import FindTime from '@/views/toolbox/fail2ban/find-time/index.vue';
|
import FindTime from '@/views/toolbox/fail2ban/find-time/index.vue';
|
||||||
import BanAction from '@/views/toolbox/fail2ban/ban-action/index.vue';
|
import BanAction from '@/views/toolbox/fail2ban/ban-action/index.vue';
|
||||||
|
import LogPath from '@/views/toolbox/fail2ban/log-path/index.vue';
|
||||||
import IPs from '@/views/toolbox/fail2ban/ips/index.vue';
|
import IPs from '@/views/toolbox/fail2ban/ips/index.vue';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
@ -185,6 +196,7 @@ const banTimeRef = ref();
|
||||||
const findTimeRef = ref();
|
const findTimeRef = ref();
|
||||||
const banActionRef = ref();
|
const banActionRef = ref();
|
||||||
const listRef = ref();
|
const listRef = ref();
|
||||||
|
const logPathRef = ref();
|
||||||
|
|
||||||
const autoStart = ref('enable');
|
const autoStart = ref('enable');
|
||||||
|
|
||||||
|
@ -242,6 +254,9 @@ const onChangeFindTime = () => {
|
||||||
const onChangeBanAction = () => {
|
const onChangeBanAction = () => {
|
||||||
banActionRef.value.acceptParams({ banAction: form.banAction });
|
banActionRef.value.acceptParams({ banAction: form.banAction });
|
||||||
};
|
};
|
||||||
|
const onChangeLogPath = () => {
|
||||||
|
logPathRef.value.acceptParams({ logPath: form.logPath });
|
||||||
|
};
|
||||||
|
|
||||||
const onOperate = async (operation: string) => {
|
const onOperate = async (operation: string) => {
|
||||||
let msg = operation === 'enable' || operation === 'disable' ? 'ssh.' : 'commons.button.';
|
let msg = operation === 'enable' || operation === 'disable' ? 'ssh.' : 'commons.button.';
|
||||||
|
|
102
frontend/src/views/toolbox/fail2ban/log-path/index.vue
Normal file
102
frontend/src/views/toolbox/fail2ban/log-path/index.vue
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('toolbox.fail2ban.logPath')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('toolbox.fail2ban.logPath')"
|
||||||
|
prop="logPath"
|
||||||
|
:rules="Rules.requiredInput"
|
||||||
|
>
|
||||||
|
<el-input v-model="form.logPath">
|
||||||
|
<template #prepend>
|
||||||
|
<FileList @choose="loadLogPath"></FileList>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<span class="input-help">{{ $t('toolbox.fail2ban.logPathHelper') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSave(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { updateFail2ban } from '@/api/modules/toolbox';
|
||||||
|
import { ElMessageBox, FormInstance } from 'element-plus';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
logPath: string;
|
||||||
|
}
|
||||||
|
const drawerVisible = ref();
|
||||||
|
const loading = ref();
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
logPath: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
form.logPath = params.logPath;
|
||||||
|
drawerVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadLogPath = async (path: string) => {
|
||||||
|
form.logPath = path;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSave = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
i18n.global.t('ssh.sshChangeHelper', [i18n.global.t('toolbox.fail2ban.logPath'), form.logPath]),
|
||||||
|
i18n.global.t('toolbox.fail2ban.fail2banChange'),
|
||||||
|
{
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
},
|
||||||
|
).then(async () => {
|
||||||
|
await updateFail2ban({ key: 'logPath', value: form.logPath })
|
||||||
|
.then(async () => {
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
loading.value = false;
|
||||||
|
drawerVisible.value = false;
|
||||||
|
emit('search');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
drawerVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Add table
Reference in a new issue