安全研发必备的安全知识

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
.cn 域名,1个 12个月
全局流量管理 GTM,标准版 1个月
简介: 安全研发必备的安全知识

算法能力

思想是代码的灵魂,一个开发人员总是需要或多或少掌握一些算法的,特别是在网络安全领域。
例如,在实现web应用扫描器的时候,我们需要实现一个web2.0的整站爬虫,就需要用到优先级广度优先搜索算法来调用爬虫对页面的抓取过程,同时也需要页面结构相似度比较算法来去除相似结构的页面和利用布隆过滤器对爬过的链接进行去重过滤。
下面,我就简单描述一下这几个算法

页面结构相似度计算方法

以下面的页面为例
在这里插入图片描述

现目前分别有hyTest和chaojilajiTest这两个子页面,http://47.101.39.152/knowledge/network?id=1http://47.101.39.152/knowledge/network?id=2
如果不进行页面相似度分析的话,普通爬虫就会对这两个页面进行抓取和解析,那么就会造成资源的浪费。

那么首先,我们需要对url的结构进行分析,对http协议的url进行拆解,可以拆解为
http(s)://hostname:port/path?query
而我们需要关注的点主要是query和path,如果发现有数字,时间等值在这些参数里面,那么就极有可能是结构相似的。
以上面的链接为例:

http://47.101.39.152/knowledge/network?id=1
http://47.101.39.152/knowledge/network?id=2

很明显,这里的相似出现在query部分的id中,那么我们就可以记录一个正则模式

http://47\.101\.39\.152/knowledge/network\?id=(\d+)

那如果有些界面光从链接上看不出来规律咋办呢?
这时我们就可以考虑在抓取页面的过程中去维护页面结构,即先抓取,再处理规律。

如何定义页面结构相似性?先来看看页面A
在这里插入图片描述

利用无头浏览器的方式可以抓取到完整的页面(不管是如何复杂的单页应用都可以抓取),抓取方式不在本文的范围内,有需求的朋友可以搜我以前写过的博客。
那么整理一下,获取到的结构如下:

#document-html-head-style-style-meta-link-meta-meta-link-link-title-link-link-style-script-script-link-script-script-script-script-script-link-script-link-script-script-script-link-script-style-body-svg-symbol-path-path-path-path-path-path-path-symbol-path-path-symbol-path-path-path-path-path-path-symbol-path-path-path-path-symbol-path-path-path-symbol-path-path-path-path-symbol-path-path-symbol-path-path-path-path-symbol-path-path-path-path-path-symbol-path-path-path-path-path-path-path-path-symbol-path-path-path-path-path-path-path-symbol-path-path-path-symbol-path-path-path-symbol-path-path-path-path-path-symbol-path-path-symbol-path-path-path-symbol-path-path-path-symbol-path-path-path-path-symbol-path-path-path-path-symbol-path-path-path-symbol-path-path-path-path-symbol-path-path-path-path-symbol-path-path-path-path-symbol-path-path-path-symbol-path-path-path-path-symbol-path-path-path-path-symbol-path-path-path-symbol-path-path-path-path-symbol-path-path-path-path-symbol-path-path-path-svg-symbol-path-path-path-symbol-path-path-path-path-symbol-path-path-symbol-path-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-path-symbol-path-symbol-path-path-symbol-path-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-path-symbol-path-path-symbol-path-path-symbol-path-path-symbol-path-path-symbol-path-path-path-symbol-path-path-path-symbol-path-symbol-path-path-path-symbol-path-symbol-path-path-symbol-path-path-symbol-path-symbol-path-symbol-path-path-symbol-path-symbol-path-symbol-path-path-symbol-path-symbol-path-path-symbol-path-path-symbol-path-symbol-path-path-symbol-path-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-path-symbol-path-path-symbol-path-path-symbol-path-path-symbol-path-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-path-symbol-path-symbol-path-symbol-path-div-div-div-div-div-img-button-span-div-button-span-svg-use-span-button-span-svg-use-span-button-span-svg-path-span-div-div-div-div-div-button-span-svg-use-span-span-span-span-svg-use-input-span-span-svg-path-div-div-div-div-div-div-div-table-colgroup-col-col-col-col-col-thead-tr-th-th-th-div-span-span-span-span-svg-path-span-svg-path-th-div-span-span-span-span-svg-path-span-svg-path-th-tbody-tr-td-div-td-div-td-div-td-div-td-div-tr-td-td-div-div-div-div-div-td-td-td-div-button-span-button-span-tr-td-td-div-div-div-div-div-td-td-td-div-button-span-button-span-ul-li-li-button-span-svg-path-li-a-li-button-span-svg-path-script-script-script-style-div-div-div

分析程序如下:

public static List<String> getAllLabelsFromHtml(String html) {
   
   
    Document document = Jsoup.parse(html);
    Elements elements = document.getAllElements();
    List<String> elementList = new ArrayList<>();
    for (Element element : elements) {
   
   
        elementList.add(element.nodeName());
    }
    return elementList;
}

再来看看页面B
在这里插入图片描述

获取到的页面结构序列如下:

#document-html-head-style-style-meta-link-meta-meta-link-link-title-link-link-style-script-script-link-script-script-script-script-script-link-script-link-script-script-script-link-script-style-script-link-script-body-svg-symbol-path-path-path-path-path-path-path-symbol-path-path-symbol-path-path-path-path-path-path-symbol-path-path-path-path-symbol-path-path-path-symbol-path-path-path-path-symbol-path-path-symbol-path-path-path-path-symbol-path-path-path-path-path-symbol-path-path-path-path-path-path-path-path-symbol-path-path-path-path-path-path-path-symbol-path-path-path-symbol-path-path-path-symbol-path-path-path-path-path-symbol-path-path-symbol-path-path-path-symbol-path-path-path-symbol-path-path-path-path-symbol-path-path-path-path-symbol-path-path-path-symbol-path-path-path-path-symbol-path-path-path-path-symbol-path-path-path-path-symbol-path-path-path-symbol-path-path-path-path-symbol-path-path-path-path-symbol-path-path-path-symbol-path-path-path-path-symbol-path-path-path-path-symbol-path-path-path-svg-symbol-path-path-path-symbol-path-path-path-path-symbol-path-path-symbol-path-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-path-symbol-path-symbol-path-path-symbol-path-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-path-symbol-path-path-symbol-path-path-symbol-path-path-symbol-path-path-symbol-path-path-path-symbol-path-path-path-symbol-path-symbol-path-path-path-symbol-path-symbol-path-path-symbol-path-path-symbol-path-symbol-path-symbol-path-path-symbol-path-symbol-path-symbol-path-path-symbol-path-symbol-path-path-symbol-path-path-symbol-path-symbol-path-path-symbol-path-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-path-symbol-path-path-symbol-path-path-symbol-path-path-symbol-path-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-path-symbol-path-symbol-path-symbol-path-symbol-path-symbol-path-path-symbol-path-symbol-path-symbol-path-div-div-div-div-div-div-img-button-span-div-div-div-div-button-span-svg-use-span-button-span-svg-use-span-button-span-svg-path-span-div-div-ul-li-span-svg-use-span-li-div-span-svg-use-span-i-ul-li-span-li-span-svg-use-span-li-span-svg-use-span-div-span-span-svg-path-div-div-div-div-div-div-div-img-p-span-div-div-div-div-div-div-div-div-div-div-div-div-button-span-svg-path-div-div-div-div-div-div-div-div-h2-div-p-div-div-span-input-span-span-span-svg-path-p-div-p-div-div-div-div-h2-div-p-span-svg-path-path-div-div-div-div-div-div-div-div-div-div-p-span-svg-path-path-div-div-div-div-div-div-div-div-div-div-span-svg-path-div-button-span-button-span-div-div-div-div-span-span-span-svg-use-input-span-span-svg-path-span-img-div-div-div-div-div-div-ul-li-button-span-svg-path-li-a-li-button-span-svg-path-div-img-p-div-div-div-div-div-span-svg-path-div-div-img-p-p-div-span-input-span-span-svg-path-button-span-svg-use-span-div-div-img-p-p-div-div-img-div-div-img-div-div-div-div-button-span-svg-path-span-div-div-div-div-div-div-span-svg-use-span-span-span-span-div-div-span-svg-path-span-div-div-div-span-svg-use-div-span-svg-use-div-div-div-div-canvas-div-div-div-span-svg-use-div-div-span-svg-use-div-div-span-svg-use-div-div-span-svg-use-div-script-script-script-style-div-div-div-div-div-div-div-div-div-button-span-span-svg-path-div-div-div-div-div-div-div-div-div-span-span-svg-use-span-div-div-div-div-svg-g-g-g-g-div-div-div-div-div-div-div-div-img-div-div-div-div-div-div-div-div-button-span-span-svg-path-div-div-div-div-div-div-div-div-div-span-span-svg-use-span-div-div-div-div-svg-g-g-g-g-div-div-div-div-div-div-div-div-img-div-div

那么,现在我们就可以利用这两个序列计算得到公共长度,从而得到相对相似度。
代码如下:

public class SequenceUtils {
   
   


    /**
     * 获取公共长度(可打断,保留顺序。总长度)
     * 算法思想:动态规划
     * 普适转移方程:
     * 涩 dp[i][j] 表示 长度为 i的a序列和长度为j的b序列的的公共长度
     * 如果 a[i] == b[j] ,则 dp[i][j] = max(dp[i-1][j-1]+1,max(dp[i-1][j],dp[i][j-1]))
     * 如果 a[i] != b[j] ,则 dp[i][j] = max(dp[i-1][j],dp[i][j-1])
     *
     * @param a 序列a
     * @param b 序列b
     * @return dp[a.size()-1][b.size()-1]
     */
    public static Integer getLongestCommonSequence(List<String> a, List<String> b) {
   
   
        int n = a.size();
        int m = b.size();
        int[][] dp = new int[n][m];
        for (int i = 0; i < n; i++) {
   
   
            for (int j = 0; j < m; j++) {
   
   
                if (i == 0 && j == 0) {
   
   
                    if (a.get(i).equals(b.get(j))) {
   
   
                        dp[0][0] = 1;
                    } else {
   
   
                        dp[0][0] = 0;
                    }
                } else {
   
   
                    if (i == 0 && j != 0) {
   
   
                        if (a.get(i).equals(b.get(j))) {
   
   
                            dp[0][j] = dp[0][j - 1] + 1;
                        } else {
   
   
                            dp[0][j] = dp[0][j - 1];
                        }
                    } else if (i != 0 && j == 0) {
   
   
                        if (a.get(i).equals(b.get(j))) {
   
   
                            dp[i][0] = dp[i - 1][0] + 1;
                        } else {
   
   
                            dp[i][0] = dp[i - 1][0];
                        }
                    } else {
   
   
                        if (a.get(i).equals(b.get(j))) {
   
   
                            int tmp = dp[i - 1][j - 1] + 1;
                            dp[i][j] = Math.max(tmp, Math.max(dp[i - 1][j], dp[i][j - 1]));
                        } else {
   
   
                            dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                        }
                    }
                }
            }
        }
        return dp[n - 1][m - 1];
    }


    public static Double pageStructScore(List<String> a, List<String> base) {
   
   
        Integer length = getLongestCommonSequence(a, base);
        int n = base.size();
        int m = a.size();
        // TODO: 2020/5/25 定义:页面结构的相似度为 (2.0*公共序列的长度)/(旧的公共序列的长度+新的公共序列的长度)
        Double score = (2 * length) / ((n + m) * 1.0);
        return score;
    }
}

这是一个动态规划算法的实践。

A的长度为511 B的长度为754 公共序列的长度为464
0.733596837944664

然后我们只需要自己定义一个阈值,来设定一下达到多少的相似度可以认为是两个相似的页面即可。
那么对于相似的页面,我们再来分析他们url的相似性,就可以分析出新的url相似关系,从而减少抓取量。

分布式爬虫调度算法-优先级广度优先搜索算法

我们可以使用playwright或者puppeteer来进行web2.0的页面抓取,但是这些页面抓取框架并没有为我们提供页面抓取顺序的方案,所以我们就需要根据自己的理解实现这么一套内容。
大概的思路如下:我们将一个网站的链接定义为一个个节点,那么整个网站就是一张图,那么我们从首页,利用广度优先的方式一层一层的将链接发现出来,理论上讲就可以遍历整个网站(不考虑孤岛页面)。而且,根据这样的方式,我们也是先把重要的页面先抓取了(最靠近首页的)。同时,我们利用给链接赋予优先级的方式,可以更好的优化遍历过程,所以需要将广度优先搜索的普通队列,替换为优先队列。

在抓取的过程中,由于我们的程序要解决避免重复抓取和并行抓取的矛盾点,同时减少加锁的开销,所以需要对这个过程进行优化。我最终实践的结果是全程同步执行,逐层并发执行。
即对于扩展出来的每一层链接进行统一管理,然后对这一整层的链接进行并发抓取。部分源码如下:

//nextfloorset中保存了当前层的下一层需要爬取的url,并全部添加到队列里面
                while (!queue.isEmpty()) {
   
   
                    if (task.isNeedDelete()){
   
   
                        // TODO: 2019/9/25 项目待删除
                        return;
                    }
                    Object object = queue.element();
                    if (object instanceof UrlNode) {
   
   
                        UrlNode urlNodex = (UrlNode) object;
                        if (webProcessInfo.getAccessedUrls().contains(urlNodex.getUrl())) {
   
   
                            ((LinkedList) queue).pop();
                            continue;
                        }
                        if (urlNodex.getDepth() > webProcessInfo.getHeight()) {
   
   
                            queue.clear();
                            break;
                        }
                        if (webProcessInfo.getAccessedUrls().size() >= webProcessInfo.getNumber()) break;
                        if (currentDepth < urlNodex.getDepth()) {
   
   
                            //说明是下一层的url节点了,开始执行该层的节点爬取即可

                            //并发获取页面并获取页面的子链接
                            Set<String> uncheckedUrlSet = new HashSet<>();
                            ExecutorService pool = Executors.newFixedThreadPool(webProcessInfo.getMaxThread());
                            List<Callable<Integer>> callers = new ArrayList<>();
                            for (String linkurl : nextfloorset) {
   
   
                                callers.add(() -> {
   
   
                                    crawlParseService.getChildUrls(linkurl, baseUrl, webProcessInfo, uncheckedUrlSet, task.getLogId(), task.getSiteId());
                                    return null;
                                });
                            }
                            try {
   
   
                                pool.invokeAll(callers);
                                pool.shutdown();
                            } catch (InterruptedException e) {
   
   
                                e.printStackTrace();
                            }
                            //将爬取后的url放到已访问的url集合中
                            webProcessInfo.addAllUrl(nextfloorset);
                            if (webProcessInfo.getAccessedUrls().size() >= webProcessInfo.getNumber()) {
   
   
                                queue.clear();//主动回收内存空间
                                break;
                            }
                            //维护每层之间的变量
                            nextfloorset.clear();
                            currentDepth += 1;
                            //如果当前深度等于最大深度,直接退出遍历
                            if (currentDepth == webProcessInfo.getHeight()) {
   
   
                                queue.clear();//主动回收内存空间
                                break;
                            }
                            for (String uncheckedUrl : uncheckedUrlSet) {
   
   
                                int accessSize = webProcessInfo.getAccessedUrls().size();
                                if (webProcessInfo.getAccessedUrls().contains(uncheckedUrl))
                                    continue;//多加一个判断:因为找子节点在加入父节点前面
                                if ((accessSize + nextfloorset.size()) >= webProcessInfo.getNumber()) {
   
   
                                    break;
                                }
                                //维护下一层待执行的url 的set nextfloorset,并把它们都加入到队列中
                                nextfloorset.add(uncheckedUrl);
                                UrlNode urlNode1 = new UrlNode();
                                urlNode1.setDepth(currentDepth + 1);
                                urlNode1.setUrl(uncheckedUrl);
                                ((LinkedList) queue).offer(urlNode1);
                            }
                            uncheckedUrlSet.clear();
                            ((LinkedList) queue).pop();//从队列中去掉当前这个
                        }
                    }
                }

其中这里的

ExecutorService pool = Executors.newFixedThreadPool(webProcessInfo.getMaxThread());
List<Callable<Integer>> callers = new ArrayList<>();
for (String linkurl : nextfloorset) {
   
   
    callers.add(() -> {
   
   
        crawlParseService.getChildUrls(linkurl, baseUrl, webProcessInfo, uncheckedUrlSet, task.getLogId(), task.getSiteId());
        return null;
    });
}
try {
   
   
    pool.invokeAll(callers);
    pool.shutdown();
} catch (InterruptedException e) {
   
   
    e.printStackTrace();
}

就是做了逐层并发抓取的过程。

布隆过滤器

布隆过滤器的介绍,由于不是我自己创造的,所以看看别人写的好点的吧

总结

互联网上安全开发的资料不多,很多时候查资料只能查个大概,核心的东西还是得自己摸索,所以,具备一定的数据结构和算法的能力是必不可少的。目前我工作中使用到的顺丰有如下几种:

1、动态规划
2、树的遍历,图的遍历
3、字典树
4、字符串相关算法(正则表达式也是一种算法)
等等

计算机网络

既然我们做的是安全开发,那么网络肯定是必不可少的技术点
首先,最基本的,你了解HTTP协议请求过程吗?

了解HTTP协议

首先,我们来看看HTTP协议请求过程。
当一个HTTP请求从浏览器处产生之后,浏览器首先会读取本地有没有该请求的缓存,如果有,浏览器将直接返回缓存中的结果,所以有时候我们在开发的过程中需要强制刷新浏览器清除浏览器缓存就是避免浏览器读取以前的结果造成误差。

在去除了缓存之后,首先浏览器去去读取本地Hosts文件里面的ip域名映射关系,如果找到了,那么浏览器会立即向该ip发出请求(一般是80端口)。如果没有找到,则会像最近的DNS域名服务器发起查询请求,DNS域名查询系统是一个递归查询的过程,递归中伴有迭代。在查询到该域名对应的IP地址之后,浏览器就会向该ip发出请求。请求发出之后,经过各层的封装和解析,最终通过路由跳转可以到达目标服务器。目标服务器很有可能是一个nginx反向代理,那么此时nginx就会根据配置以及http请求携带的信息来转发到对应的服务中。

当后端程序接收到http请求之后,会将http请求反序列化成相应的程序语言,以java为例,servlet会将整个请求的链接部分,参数部分,请求头部分和body部分都解析成对应的java实例。
根据路由,不断的向上层业务进行分发,直到分发到最底部的业务层,等到处理完毕之后,通过框架层的封装,将结果通过进入路径重新走回去。

HTTP协议中敏感的响应头

未完,待续

目录
相关文章
|
3天前
|
安全 网络安全 Docker
企业安全建设与实践-相关复习资料
本文档涵盖企业安全建设与实践的多个方面,包括基础命令复习、Docker命令、Windows策略、基线加固、渗透测试流程、应急响应阶段、WebShell处理、Windows加固措施、社会工程学、蜜罐分类及作用、挖矿病毒处置、自动化与手动渗透测试的区别以及防范社会工程学攻击的方法。
企业安全建设与实践-相关复习资料
|
11天前
|
人工智能 安全 大数据
企业内训|信创工作的技术难点与最佳实践-某央企金科公司
近日,TsingtaoAI组织信创技术专家,为上海某央企金融科技公司研发信创技术企业培训课程。课程通过系统化的理论学习与丰富的案例分析,全面解析信创政策、监管要求、行业趋势以及信创技术的核心难点。课程涵盖信创政策解读、行业信息洞察、业界技术栈分析、核心数据库信创改造、信创终端推广适配等多个模块。通过案例式、项目式和实战式的教学模式,帮助学员在复杂多变的信创环境中高效应对各种挑战。
32 3
|
2月前
|
机器学习/深度学习 人工智能 开发框架
企业内训|提示词工程师高阶技术内训-某运营商研发团队
TsingtaoAI 近期为某运营商技术团队提供了为期两天的提示词工程师高级技术培训。课程涵盖深度学习前沿理论、大模型技术架构设计与优化,以及提示词工程在电信行业的应用。通过技术探讨、案例分析和实战操作,学员将掌握最新的 LLM 技术和提示词工程技巧,提升在复杂业务环境中设计、优化和部署大模型解决方案的能力。课程强调技术深度与业务场景结合,适合具有一定深度学习基础的专业人员。
88 14
|
7月前
|
敏捷开发 安全 Devops
DevSecOps:软件安全的新思维
DevOps作为一种敏捷开发模式,已经被越来越多的企业所采用。但随着互联网的蓬勃发展,网络安全问题日益突出,传统的软件开发方式已经无法满足安全需求。因此,我们需要引入DevSecOps的概念,将安全与开发流程相结合,以实现全面的软件安全保护。
63 3
|
运维 监控 Devops
怎样利用DevOps文化提高软件开发的效率和质量
DevOps文化的兴起为软件开发带来了新的思维和方法,通过自动化、持续交付、协作等实践,提高了软件开发的效率和质量。在不断变化的技术环境下,利用DevOps的理念和实践,软件开发团队能够更加灵活、高效地应对挑战,将创新快速落地。同时,随着新概念的涌现,我们也看到了DevSecOps和AIOps等的前景,为软件开发领域带来更多的可能性。
366 1
怎样利用DevOps文化提高软件开发的效率和质量
|
测试技术 定位技术 持续交付
PMP备考之路 - 敏捷实践第五讲(实施敏捷:在敏捷环境中交付)
PMP备考之路 - 敏捷实践第五讲(实施敏捷:在敏捷环境中交付)
263 0
|
云安全 存储 运维
攻防演练 ,阿里云六大实战防守技能与五大建议
本文主要介绍如何利用云特性和原生安全优势,在防守中获得主动权,阿里云更新最新具备的实战能力和防守策略建议。
577 0
攻防演练 ,阿里云六大实战防守技能与五大建议
|
SQL 运维 安全
《网络安全0-100》企业网络安全团队构想
《网络安全0-100》企业网络安全团队构想
208 0
|
算法 测试技术 Python
热饭的测开成果盘点第九期:白盒自动化平台热饭的测开成果盘点第九期:白盒自动化平台
本期介绍的是一个技术含量很变态的工具-白盒自动化测试。何为白盒测试?其实就是测试具体代码,有五种方式叫做五种逻辑覆盖率,比如路径覆盖/语句覆盖等。
热饭的测开成果盘点第九期:白盒自动化平台热饭的测开成果盘点第九期:白盒自动化平台