如果项目使用的是Mybatis,按照Fortify给出的修复方案,给SQL中添加类似USER这样的查询条件来控制权限,扫描结果中依旧无法去除此漏洞。
理解此漏洞要表述的含义,无论我们使用何种方式只要处理了并且测试通过就可以了。
吐槽一下: 代码检查工具是按照固定规则来检查的,规则是死的人是活的的,实现方式千变万化,岂是扫描工具就能涵盖的,然而有些客户强制要求漏洞扫描结果为0,这里真的很想骂人。
为了满足客户这些奇葩要求,现在给出下面几种解决方案:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.4</version>
</dependency>
Service.java代码
// List<SystemActorRoleDto002> list = systemActorRole001Mapper.selectRoleModule(entity.getRoleId());
QueryWrapper<SystemActorRoleDto002> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("role_id", entity.getRoleId());
List<SystemActorRoleDto002> list = systemActorRole001Mapper.selectRoleModuleByWrapper(queryWrapper);
Mapper.java代码
// List<SystemActorRoleDto002> selectRoleModule(@Param("roleId") String roleId);
List<SystemActorRoleDto002> selectRoleModuleByWrapper(@Param("ew") QueryWrapper queryWrapper);
MybatisBaseDao
public interface MybatisBaseDao {
default <T> T findById(String statement, Object parameter) {
return SqlSessionUtils.getSqlSession(SpringContextUtil.getBean(SqlSessionFactory.class)).selectOne(statement, parameter);
}
default <T> List<T> findBatchById(String statement, Object parameter) {
return SqlSessionUtils.getSqlSession(SpringContextUtil.getBean(SqlSessionFactory.class)).selectList(statement, parameter);
}
}
Mapper
@Mapper
public interface SystemActorRoleMapper001 extends MybatisBaseDao {
}
XML
<select id="selectRoleModule" resultType="com.x.xx.xxx.SystemActorRoleDto002">
...
</select>
<select id="selectDataModuleByRoleId" resultType="com.x.xx.xxx.SystemActorRole003Dto">
...
</select>
Service
List<SystemActorRoleDto002> list = systemActorRoleMapper001.findBatchById("selectRoleModule", entity.getRoleId());
List<SystemActorRole003Dto> list2 = systemActorRoleMapper001.findBatchById("selectDataModuleByRoleId", entity.getRoleId());
XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xx.XxxxMapper_sqlmap">
...
</mapper>
MySqlSessionFactoryBean.java
public class MySqlSessionFactoryBean extends SqlSessionFactoryBean {
private Resource[] mapperLocations;
private Configuration configuration;
@Override
public void setMapperLocations(Resource[] mapperLocations) {
this.mapperLocations = mapperLocations;
}
@Override
public void setConfiguration(Configuration configuration) {
super.setConfiguration(configuration);
this.configuration = configuration;
}
@Override
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
SqlSessionFactory sqlSessionFactory = super.buildSqlSessionFactory();
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new MyXMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
}
}
return sqlSessionFactory;
}
}
MyXMLMapperBuilder.java
public class MyXMLMapperBuilder extends XMLMapperBuilder {
public MyXMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
super(inputStream, configuration, resource, sqlFragments, namespace);
setBuilderAssistant(resource);
}
public MyXMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
super(inputStream, configuration, resource, sqlFragments);
setBuilderAssistant(resource);
}
private void setBuilderAssistant(String resource) {
try {
Field field = this.getClass().getSuperclass().getDeclaredField("builderAssistant");
if(!field.isAccessible()) {
field.setAccessible(true);
}
field.set(this, new MyMapperBuilderAssistant(configuration, resource));
} catch (NoSuchFieldException | IllegalAccessException e) {
// ignore
}
}
}
MyMapperBuilderAssistant.java
public class MyMapperBuilderAssistant extends MapperBuilderAssistant {
public MyMapperBuilderAssistant(Configuration configuration, String resource) {
super(configuration, resource);
}
@Override
public void setCurrentNamespace(String currentNamespace) {
// 关键逻辑,处理自定义的namespace规则
if(StringUtils.endsWith(currentNamespace,"_sqlmap")) {
super.setCurrentNamespace(StringUtils.removeEnd(currentNamespace,"_sqlmap"));
}
else {
super.setCurrentNamespace(currentNamespace);
}
}
}
在springboot中应用
@Bean("sqlSessionFactory")
public SqlSessionFactoryBean sqlSession(DataSource dataSource) {
ResourcePatternResolver patternResolver = ResourcePatternUtils.getResourcePatternResolver(
new DefaultResourceLoader());
SqlSessionFactoryBean sqlSession = new MySessionFactoryBean();
sqlSession.setDataSource(dataSource);
sqlSession.setVfs(SpringBootVFS.class);
org.apache.ibatis.session.Configuration conf = new org.apache.ibatis.session.Configuration();
// 查询结果为map时不忽略空值
conf.setCallSettersOnNulls(true);
// 开启驼峰命名转换 seckill_id====>seckillId
conf.setMapUnderscoreToCamelCase(true);
sqlSession.setConfiguration(conf);
try {
sqlSession.setMapperLocations(patternResolver.getResources("classpath*:com/xxx/**/dao/sqlmap/**/*.xml"));
}
catch (Exception e) {
logger.error(e.getMessage());
}
return sqlSession;
}
通过上述的修改,mybatis在启动时就可以识别自定义的namespace了。
上述方案的本质上是让Mapper接口关联不到对应的sql语句从而骗过Fortify工具的扫描。所有可以用方案四、方案五甚至更多,主要看哪个改动最小最适合自己的项目。前提条件是我们通过其他方案已经解决了“Access Control: Database”,这里这是为了让扫描结果好看。