fwknop 如何防重放攻击

fwknop是 FireWall KNock OPerator 的简写,实现了一种叫单包授权(Single Packet Authorization (SPA))的方案。

本文将通过讲解 fwknop源码,来解析fwknop是怎么做到防重放攻击的。

server代码逻辑

找到 main 函数

main 函数流程:

读配置 config_init(&opts, argc, argv);

前面有解析命令,比如生成 key 的参数 -k

对应的就是执行参数的操作,这里看主要流程

起了一个 UDP 服务

            // 起UDP 服务
            if(run_udp_server(&opts) < 0)
            {
                log_msg(LOG_ERR, "Fatal run_udp_server() error");
                clean_exit(&opts, FW_CLEANUP, EXIT_FAILURE);
            }
            else
            {
                break;
            }

现在就是看 run_udp_server 中具体干了什么事情。

用的 select 事件轮询,接收数据, 然后解析,满足规则时,执行了 iptables 命令

selval = select(s_sock+1, &sfd_set, NULL, NULL, &tv);

        pkt_len = recvfrom(s_sock, dgram_msg, MAX_SPA_PACKET_LEN,

                0, (struct sockaddr *)&caddr, &clen);

incoming_spa // 处理 spa 包

handle_gpg_enc

fko_decrypt_spa_data

_rijndael_decrypt

fko_decode_spa_data

接下来就是 parse 函数

如果parse 成功:

create_rule 创建防火墙规则

run_extcmd 执行防火墙规则

_run_extcmd 里面有熟悉的 popen 函数,执行防火墙的规则。

那么防重放攻击是在哪里做的呢?

在 incoming_spa 函数(incoming_spa.c 文件)中, 我们看到这样一个函数:add_replay_cache

成功时返回1,失败返回0

static int
add_replay_cache(fko_srv_options_t *opts, acc_stanza_t *acc,
        spa_data_t *spadat, char *raw_digest, int *added_replay_digest,
        const int stanza_num, int *res)
{
    if (!opts->test && *added_replay_digest == 0
            && strncasecmp(opts->config[CONF_ENABLE_DIGEST_PERSISTENCE], "Y", 1) == 0)
    {

        *res = add_replay(opts, raw_digest);
        if (*res != SPA_MSG_SUCCESS)
        {
            log_msg(LOG_WARNING, "[%s] (stanza #%d) Could not add digest to replay cache",
                spadat->pkt_source_ip, stanza_num);
            return 0;
        }
        *added_replay_digest = 1;
    }

    return 1;
}

add_replay --》add_replay_dbm_cache

  1. static int
    add_replay_dbm_cache(fko_srv_options_t *opts, char *digest)
    {
    #ifdef NO_DIGEST_CACHE
        return 0;
    #else
    
    #ifdef HAVE_LIBGDBM
        GDBM_FILE   rpdb;
    #elif HAVE_LIBNDBM
        DBM        *rpdb;
    #endif
        datum       db_key, db_ent;
    
        int         digest_len, res = SPA_MSG_SUCCESS;
    
        digest_cache_info_t dc_info;
    
        digest_len = strlen(digest);
    
        db_key.dptr = digest;
        db_key.dsize = digest_len;
    
        /* Check the db for the key
        */
    #ifdef HAVE_LIBGDBM
        rpdb = gdbm_open(
             opts->config[CONF_DIGEST_DB_FILE], 512, GDBM_WRCREAT, S_IRUSR|S_IWUSR, 0
        );
    #elif HAVE_LIBNDBM
        rpdb = dbm_open(opts->config[CONF_DIGEST_DB_FILE], O_RDWR, 0);
    #endif
    
        if(!rpdb)
        {
            log_msg(LOG_WARNING, "Error opening digest_cache: '%s': %s",
                opts->config[CONF_DIGEST_DB_FILE],
                MY_DBM_STRERROR(errno)
            );
    
            return(SPA_MSG_DIGEST_CACHE_ERROR);
        }
    
        db_ent = MY_DBM_FETCH(rpdb, db_key);
    
        /* If the datum is null, we have a new entry.
        */
        if(db_ent.dptr == NULL)
        {
            /* This is a new SPA packet that needs to be added to the cache.
            */
            dc_info.src_ip   = opts->spa_pkt.packet_src_ip;
            dc_info.dst_ip   = opts->spa_pkt.packet_dst_ip;
            dc_info.src_port = opts->spa_pkt.packet_src_port;
            dc_info.dst_port = opts->spa_pkt.packet_dst_port;
            dc_info.proto    = opts->spa_pkt.packet_proto;
            dc_info.created  = time(NULL);
            dc_info.first_replay = dc_info.last_replay = dc_info.replay_count = 0;
    
            db_ent.dsize    = sizeof(digest_cache_info_t);
            db_ent.dptr     = (char*)&(dc_info);
    
            if(MY_DBM_STORE(rpdb, db_key, db_ent, MY_DBM_INSERT) != 0)
            {
                log_msg(LOG_WARNING, "Error adding entry digest_cache: %s",
                    MY_DBM_STRERROR(errno)
                );
    
                res = SPA_MSG_DIGEST_CACHE_ERROR;
            }
    
            res = SPA_MSG_SUCCESS;
        }
        else
            res = SPA_MSG_DIGEST_CACHE_ERROR;
    
        MY_DBM_CLOSE(rpdb);
    
        return(res);
    #endif /* NO_DIGEST_CACHE */
    }

看  add_replay_dbm_cache 函数,其实就是一个 DB 操作。

显示查找,如果找不到,说明不存在这样的key,则进行存储, 并返回成功,如果找到了,说明以前发送过这个包。

则返回  SPA_MSG_DIGEST_CACHE_ERROR,摘要缓存失败。

所以可以总结出:防重放攻击,其实就是客户端发送的每一个密文包过来,服务器存储到了DB中,如果存储失败,表示已经存在,则置失败,防止是重放的包。

发布了37 篇原创文章 · 获赞 14 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/xiaod_szu/article/details/104274830
今日推荐