Fortify漏洞Access Control: Database(数据越权)

如果项目使用的是Mybatis,按照Fortify给出的修复方案,给SQL中添加类似USER这样的查询条件来控制权限,扫描结果中依旧无法去除此漏洞。

理解此漏洞要表述的含义,无论我们使用何种方式只要处理了并且测试通过就可以了。

吐槽一下: 代码检查工具是按照固定规则来检查的,规则是死的人是活的的,实现方式千变万化,岂是扫描工具就能涵盖的,然而有些客户强制要求漏洞扫描结果为0,这里真的很想骂人。

为了满足客户这些奇葩要求,现在给出下面几种解决方案:

  • 方案一:使用mybatis-plus的QueryWrapper来解决问题
  1. 添加依赖mybatis-plus-boot-starter
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3.4</version>
        </dependency>
  1. 编写代码

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);
  • 方案二:写一个基类,提供一些公共方法,在这些方法中调用SqlSession对应方法来操作数据库。

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());
  • 方案三:扩展mybatis,将所有的xml文件中的mapper节点对应的namespace进行修改,比如添加后缀"_sqlmap"。然后自定义一个继承自SqlSessionFactoryBean的Bean,在其中使用自定义XMLMapperBuilder及MapperBuilderAssistant。

XML

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis//DTD Mapper 3.0//EN" "http://mybatis/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”,这里这是为了让扫描结果好看。

更多推荐

Fortify屏蔽漏洞Access Control: Database