MySQL中间人攻击
详情
https://github.com/actiontech/sqle
SQLE由上海爱可生信息技术股份有限公司(以下简称爱可生公司)出品和维护,是爱可生公司“云树SQL审核软件”(简称:CTREE SQLE)软件产品的开源版本。SQLE 是一个支持多场景,原生支持 MySQL 审核且数据库类型可扩展的 SQL 审核工具。
登录SQLE后开可以看到数据库连接地址,用户信息,但无法获取用户密码。
/v1/instances (GET)接口源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| func GetInstances(c echo.Context) error { req := new(GetInstancesReqV1) if err := controller.BindAndValidateReq(c, req); err != nil { return err } s := model.GetStorage()
user, err := controller.GetCurrentUser(c) if err != nil { return controller.JSONBaseErrorReq(c, err) }
var offset uint32 if req.PageIndex >= 1 { offset = req.PageSize * (req.PageIndex - 1) } data := map[string]interface{}{ "filter_instance_name": req.FilterInstanceName, "filter_db_host": req.FilterDBHost, "filter_db_port": req.FilterDBPort, "filter_db_user": req.FilterDBUser, "filter_workflow_template_name": req.FilterWorkflowTemplateName, "filter_rule_template_name": req.FilterRuleTemplateName, "filter_role_name": req.FilterRoleName, "filter_db_type": req.FilterDBType, "current_user_id": user.ID, "check_user_can_access": user.Name != model.DefaultAdminUser, "limit": req.PageSize, "offset": offset, }
instances, count, err := s.GetInstancesByReq(data, user) if err != nil { return controller.JSONBaseErrorReq(c, err) }
instancesReq := []InstanceResV1{} for _, instance := range instances { instanceReq := InstanceResV1{ Name: instance.Name, Desc: instance.Desc, Host: instance.Host, Port: instance.Port, User: instance.User, WorkflowTemplateName: instance.WorkflowTemplateName.String, Roles: instance.RoleNames, RuleTemplates: instance.RuleTemplateNames, } instancesReq = append(instancesReq, instanceReq) } return c.JSON(http.StatusOK, &GetInstancesResV1{ BaseRes: controller.NewBaseReq(nil), Data: instancesReq, TotalNums: count, }) }
|
修改数据源接口/v1/instances/ (PATCH) 可以在不传入(不修改)数据库密码的情况下修改数据库地址与端口, 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| func UpdateInstance(c echo.Context) error { req := new(UpdateInstanceReqV1) if err := controller.BindAndValidateReq(c, req); err != nil { return err }
s := model.GetStorage() instanceName := c.Param("instance_name") instance, exist, err := s.GetInstanceByName(instanceName) if err != nil { return controller.JSONBaseErrorReq(c, err) } if !exist { return controller.JSONBaseErrorReq(c, errInstanceNotExist) }
if !CheckInstanceCanBindOneRuleTemplate(req.RuleTemplates) { return controller.JSONBaseErrorReq(c, errInstanceBind) }
ruleTemplates, err := s.GetRuleTemplatesByNames(req.RuleTemplates) if err != nil { return controller.JSONBaseErrorReq(c, err) } err = CheckInstanceAndRuleTemplateDbType(ruleTemplates, instance) if err != nil { return controller.JSONBaseErrorReq(c, err) }
updateMap := map[string]interface{}{} if req.Desc != nil { updateMap["desc"] = *req.Desc } if req.Host != nil { updateMap["db_host"] = *req.Host } if req.Port != nil { updateMap["db_port"] = *req.Port } if req.User != nil { updateMap["db_user"] = *req.User } if req.Password != nil { password, err := utils.AesEncrypt(*req.Password) if err != nil { return controller.JSONBaseErrorReq(c, err) } updateMap["db_password"] = password }
if req.WorkflowTemplateName != nil { if *req.WorkflowTemplateName == "" { updateMap["workflow_template_id"] = 0 } else { workflowTemplate, exist, err := s.GetWorkflowTemplateByName(*req.WorkflowTemplateName) if err != nil { return controller.JSONBaseErrorReq(c, err) } if !exist { return controller.JSONBaseErrorReq(c, errors.New(errors.DataNotExist, fmt.Errorf("workflow template is not exist"))) } updateMap["workflow_template_id"] = workflowTemplate.ID } }
if req.RuleTemplates != nil { ruleTemplates, err := s.GetAndCheckRuleTemplateExist(req.RuleTemplates) if err != nil { return controller.JSONBaseErrorReq(c, err) } err = s.UpdateInstanceRuleTemplates(instance, ruleTemplates...) if err != nil { return controller.JSONBaseErrorReq(c, err) } } if req.Roles != nil { roles, err := s.GetAndCheckRoleExist(req.Roles) if err != nil { return controller.JSONBaseErrorReq(c, err) } err = s.UpdateInstanceRoles(instance, roles...) if err != nil { return controller.JSONBaseErrorReq(c, err) } }
if req.AdditionalParams != nil { additionalParams := driver.AllAdditionalParams()[instance.DbType] for _, additionalParam := range req.AdditionalParams { err = additionalParams.SetParamValue(additionalParam.Name, additionalParam.Value) if err != nil { return controller.JSONBaseErrorReq(c, errors.New(errors.DataInvalid, err)) } } updateMap["additional_params"] = additionalParams }
err = s.UpdateInstanceById(instance.ID, updateMap) if err != nil { return controller.JSONBaseErrorReq(c, err) } return c.JSON(http.StatusOK, controller.NewBaseReq(nil)) }
|
在了解MySQL的认证流程后,发现只需要搭建一个虚假的MySQL服务器,即可利用保存在SQLE中的MySQL账户执行任意SQL。
MySQL认证流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 数据库中保存的密码是用 SHA1(SHA1(password)) 加密的,其流程为:
1. 服务器发送随机字符串 (scramble) 给客户端。 2. 客户端作如下计算,然后客户端将 token 发送给服务端。
stage1_hash = SHA1(明文密码)
token = SHA1(scramble + SHA1(stage1_hash)) XOR stage1_hash
3. 服务端作如下计算,比对 SHA1(stage1_hash) 和 mysql.user.password 是否相同。
stage1_hash = token XOR SHA1(scramble + mysql.user.password)
这里实际用到了异或的自反性: A XOR B XOR B = A ,对于给定的数 A,用同样的运算因子 B 作两次异或运算后仍得到 A 本身。对于当前情况的话,实际的计算过程如下。
token = SHA1(scramble + SHA1(SHA1(password))) XOR SHA1(password) = PASSWORD XOR SHA1(password)
stage1_hash = token XOR SHA1(scramble + mysql.user.password) = token XOR PASSWORD = [PASSWORD XOR SHA1(password)] XOR PASSWORD = SHA1(password) 因此,校验时,只需要 SHA1(stage1_hash) 与 mysql.user.password 比较一下即可。
|
利用代码
https://github.com/xjohjrdy/PyMySQL

参考连接
MySQL 通讯协议
PyMySQL