Browse Source

improve security against injection (#2669)

* improve security against injection

* fix shell injection through ipv4, ipv6 address

Co-authored-by: 7megaumka7 <[email protected]>

---------

Co-authored-by: 7megaumka7 <[email protected]>
pull/2671/head
Bernd Storath 3 weeks ago
committed by GitHub
parent
commit
032347648d
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      src/i18n/locales/en.json
  2. 6
      src/server/api/auth/verify-2fa.post.ts
  3. 14
      src/server/database/repositories/client/types.ts
  4. 1
      src/server/database/repositories/general/types.ts
  5. 7
      src/server/database/repositories/user/types.ts
  6. 38
      src/server/utils/types.ts

4
src/i18n/locales/en.json

@ -204,7 +204,9 @@
"validBoolean": "{0} must be a valid boolean",
"validArray": "{0} must be a valid array",
"stringMin": "{0} must be at least {1} Character",
"numberMin": "{0} must be at least {1}"
"stringMax": "{0} must be at most {1} Character",
"numberMin": "{0} must be at least {1}",
"numberMax": "{0} must be at most {1}"
},
"client": {
"id": "Client ID",

6
src/server/api/auth/verify-2fa.post.ts

@ -1,8 +1,4 @@
import { z } from 'zod';
const Verify2faSchema = z.object({
totpCode: z.string().min(6).max(6),
});
import { Verify2faSchema } from '#db/repositories/user/types';
export default defineEventHandler(async (event) => {
const { totpCode } = await readValidatedBody(

14
src/server/database/repositories/client/types.ts

@ -1,6 +1,7 @@
import type { InferSelectModel } from 'drizzle-orm';
import z from 'zod';
import { isIPv4, isIPv6 } from 'is-ip';
import type { client } from './schema';
export type ClientType = InferSelectModel<typeof client>;
@ -20,7 +21,8 @@ export type UpdateClientType = Omit<
const name = z
.string({ message: t('zod.client.name') })
.min(1, t('zod.client.name'))
.pipe(safeStringRefine);
.pipe(safeStringRefine)
.pipe(controlStringRefine);
// TODO?: validate iso string
const expiresAt = z
@ -32,14 +34,18 @@ const expiresAt = z
const address4 = z
.string({ message: t('zod.client.address4') })
.min(1, { message: t('zod.client.address4') })
.pipe(safeStringRefine);
.pipe(safeStringRefine)
.pipe(controlStringRefine)
.refine((v) => isIPv4(v));
const address6 = z
.string({ message: t('zod.client.address6') })
.min(1, { message: t('zod.client.address6') })
.pipe(safeStringRefine);
.pipe(safeStringRefine)
.pipe(controlStringRefine)
.refine((v) => isIPv6(v));
const filter = z.string().optional();
const filter = z.string().pipe(safeStringRefine).optional();
const serverAllowedIps = z.array(AddressSchema, {
message: t('zod.client.serverAllowedIps'),

1
src/server/database/repositories/general/types.ts

@ -11,6 +11,7 @@ const metricsEnabled = z.boolean({ message: t('zod.general.metricsEnabled') });
const metricsPassword = z
.string({ message: t('zod.general.metricsPassword') })
.min(1, { message: t('zod.general.metricsPassword') })
.pipe(safeStringRefine)
.nullable();
export const GeneralUpdateSchema = z.object({

7
src/server/database/repositories/user/types.ts

@ -46,9 +46,8 @@ const name = z
.pipe(safeStringRefine);
const email = z
.string({ message: t('zod.user.email') })
.min(5, t('zod.user.email'))
.email({ message: t('zod.user.emailInvalid') })
.min(5, t('zod.user.email'))
.pipe(safeStringRefine)
.nullable();
@ -80,3 +79,7 @@ export const UserUpdateTotpSchema = z.union([
currentPassword: password,
}),
]);
export const Verify2faSchema = z.object({
totpCode: totpCode,
});

38
src/server/utils/types.ts

@ -1,4 +1,4 @@
import type { ZodSchema } from 'zod';
import type { ZodType } from 'zod';
import z from 'zod';
import type { H3Event, EventHandlerRequest } from 'h3';
import { isIP } from 'is-ip';
@ -20,6 +20,15 @@ export const safeStringRefine = z
{ message: t('zod.stringMalformed') }
);
function hasControlChars(str: string) {
// eslint-disable-next-line no-control-regex
return /[\x00-\x1F\x7F]/.test(str);
}
export const controlStringRefine = z
.string()
.refine((v) => !hasControlChars(v), { message: t('zod.stringMalformed') });
export const EnabledSchema = z.boolean({ message: t('zod.enabled') });
export const MtuSchema = z
@ -70,7 +79,11 @@ export const HSchema = z
})
.nullable();
export const ISchema = z.string().nullable();
export const ISchema = z
.string()
.pipe(safeStringRefine)
.pipe(controlStringRefine)
.nullable();
export const PortSchema = z
.number({ message: t('zod.port') })
@ -85,7 +98,8 @@ export const PersistentKeepaliveSchema = z
export const AddressSchema = z
.string({ message: t('zod.address') })
.min(1, { message: t('zod.address') })
.pipe(safeStringRefine);
.pipe(safeStringRefine)
.pipe(controlStringRefine);
export const DnsSchema = z.array(AddressSchema, { message: t('zod.dns') });
@ -168,7 +182,7 @@ export const schemaForType =
};
export function validateZod<T>(
schema: ZodSchema<T>,
schema: ZodType<T>,
event: H3Event<EventHandlerRequest>
) {
return async (data: unknown) => {
@ -203,6 +217,22 @@ export function validateZod<T>(
break;
}
break;
case 'too_big':
switch (v.origin) {
case 'string':
newMessage = t('zod.generic.stringMax', [
t(v.message),
v.maximum,
]);
break;
case 'number':
newMessage = t('zod.generic.numberMax', [
t(v.message),
v.maximum,
]);
break;
}
break;
case 'invalid_type': {
if (v.input === null || v.input === undefined) {
newMessage = t('zod.generic.required', [

Loading…
Cancel
Save