fix: 工具箱管理防止命令注入 (#3215)

pull/3218/head
ssongliu 2023-12-07 14:40:07 +08:00 committed by GitHub
parent 40da8a7525
commit a6e12d88a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 157 additions and 4 deletions

View File

@ -10,6 +10,7 @@ type Fail2BanBaseInfo struct {
BanTime string `json:"banTime"`
FindTime string `json:"findTime"`
BanAction string `json:"banAction"`
LogPath string `json:"logPath"`
}
type Fail2BanSearch struct {
@ -17,7 +18,7 @@ type Fail2BanSearch 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"`
}

View File

@ -109,6 +109,9 @@ func (u *DeviceService) CheckDNS(key, value string) (bool, error) {
func (u *DeviceService) Update(key, value string) error {
switch key {
case "TimeZone":
if cmd.CheckIllegal(value) {
return buserr.New(constant.ErrCmdIllegal)
}
if err := ntp.UpdateSystemTimeZone(value); err != nil {
return err
}
@ -123,11 +126,17 @@ func (u *DeviceService) Update(key, value string) error {
return err
}
case "Hostname":
if cmd.CheckIllegal(value) {
return buserr.New(constant.ErrCmdIllegal)
}
std, err := cmd.Execf("%s hostnamectl set-hostname %s", cmd.SudoHandleCmd(), value)
if err != nil {
return errors.New(std)
}
case "Ntp", "LocalTime":
if cmd.CheckIllegal(value) {
return buserr.New(constant.ErrCmdIllegal)
}
ntpValue := value
if key == "LocalTime" {
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 {
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())
if err != nil {
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 {
if cmd.CheckIllegal(req.Path) {
return buserr.New(constant.ErrCmdIllegal)
}
if !req.IsNew {
std, err := cmd.Execf("%s swapoff %s", cmd.SudoHandleCmd(), req.Path)
if err != nil {

View File

@ -208,4 +208,9 @@ func loadFailValue(line string, baseInfo *dto.Fail2BanBaseInfo) {
itemValue = strings.ReplaceAll(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)
}
}

View File

@ -94,7 +94,7 @@ func (f *Fail2ban) ReBanIPs(ips []string) error {
func (f *Fail2ban) ListBanned() ([]string, error) {
var lists []string
stdout, err := cmd.Exec("fail2ban-client get sshd banned")
stdout, err := cmd.Exec("fail2ban-client get sshd banip")
if err != nil {
return lists, err
}
@ -147,7 +147,7 @@ maxretry = 5
findtime = 300
bantime = 600
action = %(action_mwl)s
logpath = /var/log/secure`
logpath = $logpath`
banaction := ""
if active, _ := systemctl.IsActive("firewalld"); active {
@ -158,6 +158,14 @@ logpath = /var/log/secure`
banaction = "iptables-allports"
}
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 {
return err
}

View File

@ -965,6 +965,8 @@ const message = {
allPorts: ' (All Ports)',
ignoreIP: 'IP Whitelist',
bannedIP: 'IP Blacklist',
logPath: 'Log Path',
logPathHelper: 'Default is /var/log/secure or /var/log/auth.log',
},
},
logs: {

View File

@ -916,6 +916,8 @@ const message = {
allPorts: ' ()',
ignoreIP: 'IP ',
bannedIP: 'IP ',
logPath: '',
logPathHelper: ' /var/log/secure /var/log/auth.log',
},
},
logs: {

View File

@ -917,6 +917,8 @@ const message = {
allPorts: ' ()',
ignoreIP: 'IP ',
bannedIP: 'IP ',
logPath: '',
logPathHelper: ' /var/log/secure /var/log/auth.log',
},
},
logs: {

View File

@ -61,7 +61,7 @@
<el-select v-model="row.sizeUnit" style="width: 85px">
<el-option label="KB" value="KB" />
<el-option label="MB" value="MB" />
<el-option label="GB" value="G" />
<el-option label="GB" value="GB" />
</el-select>
</template>
</el-input>
@ -202,6 +202,7 @@ const onSave = async (row) => {
};
const loadItemSize = (row: any) => {
console.log(row.size, row.sizeUnit);
switch (row.sizeUnit) {
case 'KB':
return row.size;

View File

@ -102,6 +102,15 @@
</template>
</el-input>
</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-col>
</el-row>
@ -154,6 +163,7 @@
<BanTime ref="banTimeRef" @search="search" />
<FindTime ref="findTimeRef" @search="search" />
<BanAction ref="banActionRef" @search="search" />
<LogPath ref="logPathRef" @search="search" />
<IPs ref="listRef" />
</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 FindTime from '@/views/toolbox/fail2ban/find-time/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 i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
@ -185,6 +196,7 @@ const banTimeRef = ref();
const findTimeRef = ref();
const banActionRef = ref();
const listRef = ref();
const logPathRef = ref();
const autoStart = ref('enable');
@ -242,6 +254,9 @@ const onChangeFindTime = () => {
const onChangeBanAction = () => {
banActionRef.value.acceptParams({ banAction: form.banAction });
};
const onChangeLogPath = () => {
logPathRef.value.acceptParams({ logPath: form.logPath });
};
const onOperate = async (operation: string) => {
let msg = operation === 'enable' || operation === 'disable' ? 'ssh.' : 'commons.button.';

View 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>