-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fixed 渗透测试问题:用户名密码枚举和SSRF #3251
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,8 @@ import chalk from 'chalk'; | |
import { LogLevelEnum } from './log/constant'; | ||
import { connectionMongo } from '../mongo/index'; | ||
import { getMongoLog } from './log/schema'; | ||
import { LoginStatusEnum, LoginTypeEnum } from './loginLog/constant'; | ||
import { getLoginLog } from './loginLog/schema'; | ||
|
||
const logMap = { | ||
[LogLevelEnum.debug]: { | ||
|
@@ -90,3 +92,58 @@ export const addLog = { | |
}); | ||
} | ||
}; | ||
|
||
/* add login logger */ | ||
export const addLoginLog = { | ||
log(userName: string, type: LoginTypeEnum, status: LoginStatusEnum, message: string) { | ||
console.log(`${userName} ${dayjs().format('YYYY-MM-DD HH:mm:ss')} ${type} ${status}`); | ||
// store | ||
getLoginLog().create({ | ||
userName: userName, | ||
type: type, | ||
status: status, | ||
message: message | ||
}); | ||
}, | ||
login(userName: string, status: LoginStatusEnum, message: string) { | ||
this.log(userName, LoginTypeEnum.login, status, message); | ||
}, | ||
logout(userName: string, status: LoginStatusEnum, message: string) { | ||
this.log(userName, LoginTypeEnum.logout, status, message); | ||
} | ||
}; | ||
|
||
/* check15分钟内失败的登录记录,5条以上就锁定账号 */ | ||
export default async function isLoginLocked(userName: string) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 不属于 log 的模块,这个应该属于,login 模块的代码,并且不会复用。只需要放在 login api 里即可 |
||
try { | ||
var fifteenMinutesAgo = new Date(new Date().getTime() - 15 * 60 * 1000); | ||
const listParam: any = { | ||
$and: [ | ||
{ userName: userName }, | ||
{ type: LoginTypeEnum.login }, | ||
{ time: { $gt: fifteenMinutesAgo } } | ||
] | ||
}; | ||
// 分页查询 | ||
const loginLogList = await getLoginLog() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 可以直接用 TmpDataSchema 表,设置 ttl 为 15 分钟过期。data 里记录登录次数即可。不需要写这么复杂 |
||
.find(listParam) | ||
.skip(0) | ||
.limit(5) | ||
.sort({ | ||
time: -1 | ||
}) | ||
.exec(); | ||
if (loginLogList.length < 5) { | ||
return false; | ||
} | ||
for (let i = 0; i < loginLogList.length; i++) { | ||
if (loginLogList[i].status === LoginStatusEnum.success) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} catch (err) { | ||
console.error(err); | ||
return false; | ||
} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 登录错误,属于用户层错误,错误信息统一在 UserErrEnum 枚举中,且报错也在枚举中声明 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export enum LoginStatusEnum { | ||
success = 'success', | ||
failure = 'failure' | ||
} | ||
|
||
export enum LoginTypeEnum { | ||
login = 'login', | ||
logout = 'logout' | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 我们有专门的 TmpDataSchema 表用于存放临时数据,不需要单独建表 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { getMongoModel, Schema } from '../../../common/mongo'; | ||
import { LoginLogType } from './type'; | ||
import { LoginStatusEnum, LoginTypeEnum } from './constant'; | ||
|
||
export const LoginLogCollectionName = 'system_login_logs'; | ||
|
||
export const getLoginLog = () => { | ||
const LoginLogSchema = new Schema({ | ||
userName: { | ||
type: String, | ||
required: true | ||
}, | ||
status: { | ||
type: String, | ||
required: true, | ||
enum: Object.values(LoginStatusEnum) | ||
}, | ||
type: { | ||
type: String, | ||
required: true, | ||
enum: Object.values(LoginTypeEnum) | ||
}, | ||
msg: { | ||
type: String | ||
}, | ||
ip: { | ||
type: String | ||
}, | ||
client: { | ||
type: String | ||
}, | ||
time: { | ||
type: Date, | ||
required: true, | ||
default: () => new Date() | ||
}, | ||
metadata: Object | ||
}); | ||
|
||
LoginLogSchema.index({ time: 1 }, { expires: '7d' }); | ||
LoginLogSchema.index({ userName: 1 }); | ||
|
||
return getMongoModel<LoginLogType>(LoginLogCollectionName, LoginLogSchema); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { LoginStatusEnum, LoginTypeEnum } from './constant'; | ||
|
||
export type LoginLogType = { | ||
_id: string; | ||
userName: string; | ||
status: LoginStatusEnum; | ||
type: LoginTypeEnum; | ||
msg: string; | ||
ip: string; | ||
client: string; | ||
time: Date; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,15 +7,25 @@ import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSc | |
/* update user info */ | ||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; | ||
import { NextAPI } from '@/service/middleware/entry'; | ||
import { imageBaseUrl } from '@fastgpt/global/common/file/image/constants'; | ||
export type UserAccountUpdateQuery = {}; | ||
export type UserAccountUpdateBody = UserUpdateParams; | ||
export type UserAccountUpdateResponse = {}; | ||
async function handler( | ||
req: ApiRequestProps<UserAccountUpdateBody, UserAccountUpdateQuery>, | ||
_res: ApiResponseType<any> | ||
): Promise<UserAccountUpdateResponse> { | ||
const { avatar, timezone, openaiAccount, lafAccount } = req.body; | ||
let { avatar, timezone, openaiAccount, lafAccount } = req.body; | ||
|
||
//校验头像路径格式 | ||
if (avatar) { | ||
var regex = /\.(png|jpe?g|gif|svg)(\?.*)?$/; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 此处应该封装一个通用的系统图片链接校验方法,通过校验后缀以及以及 baseurl 是否为系统预设前缀。 |
||
var result = regex.test(avatar); | ||
if (!result) { | ||
throw new Error('头像路径格式不正确'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 所有文案都需要经过 i18nT 加工,进行国际化翻译。 |
||
} | ||
avatar = imageBaseUrl + avatar; | ||
} | ||
const { tmbId } = await authCert({ req, authToken: true }); | ||
const tmb = await MongoTeamMember.findById(tmbId); | ||
if (!tmb) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type'; | |
import { useSystemStore } from '@/web/common/system/useSystemStore'; | ||
import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type'; | ||
import { getGroupList } from './team/group/api'; | ||
import { imageBaseUrl } from '@fastgpt/global/common/file/image/constants'; | ||
|
||
type State = { | ||
systemMsgReadId: string; | ||
|
@@ -73,6 +74,7 @@ export const useUserStore = create<State>()( | |
}; | ||
}); | ||
try { | ||
user.avatar = user.avatar?.replace(imageBaseUrl, ''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 前端是不需要做任何操作,后端校验链接来源即可 |
||
await putUserInfo(user); | ||
} catch (error) { | ||
set((state) => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
有专门的 log 函数,不需要单独写