算法能力
思想是代码的灵魂,一个开发人员总是需要或多或少掌握一些算法的,特别是在网络安全领域。
例如,在实现web应用扫描器的时候,我们需要实现一个web2.0的整站爬虫,就需要用到优先级广度优先搜索算法来调用爬虫对页面的抓取过程,同时也需要页面结构相似度比较算法来去除相似结构的页面和利用布隆过滤器对爬过的链接进行去重过滤。
下面,我就简单描述一下这几个算法
页面结构相似度计算方法
以下面的页面为例
现目前分别有hyTest和chaojilajiTest这两个子页面,http://47.101.39.152/knowledge/network?id=1 和 http://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();
}
就是做了逐层并发抓取的过程。
布隆过滤器
布隆过滤器的介绍,由于不是我自己创造的,所以看看别人写的好点的吧
https://zhuanlan.zhihu.com/p/43263751
总结
互联网上安全开发的资料不多,很多时候查资料只能查个大概,核心的东西还是得自己摸索,所以,具备一定的数据结构和算法的能力是必不可少的。目前我工作中使用到的顺丰有如下几种:
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协议中敏感的响应头
server中能看到网站的服务信息
set-cookie中能看见cookie信息
Http响应码
100 | Continue | 继续。客户端应继续其请求 |
---|---|---|
101 | Switching Protocols | 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议 |
200 | OK | 请求成功。一般用于GET与POST请求 |
201 | Created | 已创建。成功请求并创建了新的资源 |
202 | Accepted | 已接受。已经接受请求,但未处理完成 |
203 | Non-Authoritative Information | 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本 |
204 | No Content | 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档 |
205 | Reset Content | 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域 |
206 | Partial Content | 部分内容。服务器成功处理了部分GET请求 |
300 | Multiple Choices | 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择 |
301 | Moved Permanently | 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 |
302 | Found | 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI |
303 | See Other | 查看其它地址。与301类似。使用GET和POST请求查看 |
304 | Not Modified | 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 |
305 | Use Proxy | 使用代理。所请求的资源必须通过代理访问 |
306 | Unused | 已经被废弃的HTTP状态码 |
307 | Temporary Redirect | 临时重定向。与302类似。使用GET请求重定向 |
400 | Bad Request | 客户端请求的语法错误,服务器无法理解 |
401 | Unauthorized | 请求要求用户的身份认证 |
402 | Payment Required | 保留,将来使用 |
403 | Forbidden | 服务器理解请求客户端的请求,但是拒绝执行此请求 |
404 | Not Found | 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面 |
405 | Method Not Allowed | 客户端请求中的方法被禁止 |
406 | Not Acceptable | 服务器无法根据客户端请求的内容特性完成请求 |
407 | Proxy Authentication Required | 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权 |
408 | Request Time-out | 服务器等待客户端发送的请求时间过长,超时 |
409 | Conflict | 服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突 |
410 | Gone | 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置 |
411 | Length Required | 服务器无法处理客户端发送的不带Content-Length的请求信息 |
412 | Precondition Failed | 客户端请求信息的先决条件错误 |
413 | Request Entity Too Large | 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息 |
414 | Request-URI Too Large | 请求的URI过长(URI通常为网址),服务器无法处理 |
415 | Unsupported Media Type | 服务器无法处理请求附带的媒体格式 |
416 | Requested range not satisfiable | 客户端请求的范围无效 |
417 | Expectation Failed | 服务器无法满足Expect的请求头信息 |
500 | Internal Server Error | 服务器内部错误,无法完成请求 |
501 | Not Implemented | 服务器不支持请求的功能,无法完成请求 |
502 | Bad Gateway | 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应 |
503 | Service Unavailable | 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 |
504 | Gateway Time-out | 充当网关或代理的服务器,未及时从远端服务器获取请求 |
505 | HTTP Version not supported | 服务器不支持请求的HTTP协议的版本,无法完成处理 |
如何利用HTTP协议定义一个网站
一个网站的链接结构如下:
http(s)://host_name:port+path+query
那么,我们定义一个网站也可以从以下几个方面入手
主机名(域名或者IP)+ 端口 + 协议 + 服务 + 路径 + 参数
域名:关注该域名的whois信息,Dns记录值:A记录或者AAAA记录
IP:ip和端口是一起关注的,关注该ip打开了哪些端口,是不是cdn的ip,是不是代理的ip等等
端口:端口没啥好关注的
协议:http或者https,而https往往能看见颁发的证书的相关信息
服务:这个是通过扫描端口上打开的服务进行发现
再来看看一个页面,
那么我们需要关心些什么呢?
首先,是icon,因为很多网站都喜欢用同一个icon
然后这个网站的技术栈,那么怎么看呢?通过响应的情况来查看
例如,我们查看到
说明使用的是nginx。
而这些特点,我们其实都可以使用一个库+正则表达式进行匹配。
页面上可以使用插件来完成
查看https协议的证书情况:
那么,从界面中,我们就可以从icon,server,框架、证书的情况以及页面关键词来刻画一个网站。
常见的网络命令
traceroute
这个命令会打印出我们的机器和远程的机器的路由连通性情况
traceroute -I ip
测试端口
traceroute -n -T -p port IP
ping
ping命令使用的是ICMP协议,该协议由于很常被存活性扫描器使用,所以,很多服务器都是禁用了icmp协议了。
telnet
telnet是登录远程端口,常用于检测远程端口是否可达
ipconfig/ifconifg
ipconfig或者ifconfig是查看本机的网络配置的命令
netstat
查看本机的端口占用情况
tcpdump
抓包软件
例如,我使用一台机器去ping另一台机器,然后在另一台机器打开tcpdump icmp指令,就可以对icmp协议进行抓包
发包方
收包方
代理的原理
代理就是我们生活中的中介,当我需要访问一个我可能访问不到的网络,我需要一个代理,我访问它,然后它去访问目标网站然后将结果返回给我。提到代理,首先,我们需要了解正向代理和反向代理
https://cloud.tencent.com/developer/article/1418457
正向代理(forward proxy):是一个位于客户端和目标服务器之间的服务器(代理服务器),为了从目标服务器取得内容,客户端向代理服务器发送一个请求并指定目标,然后代理服务器向目标服务器转交请求并将获得的内容返回给客户端。
这种代理其实在生活中是比较常见的,比如访问外国网站技术,其用到的就是代理技术。
有时候,用户想要访问某国外网站,该网站无法在国内直接访问,但是我们可以访问到一个代理服务器,这个代理服务器可以访问到这个国外网站。这样呢,用户对该国外网站的访问就需要通过代理服务器来转发请求,并且该代理服务器也会将请求的响应再返回给用户。这个上网的过程就是用到了正向代理。反向代理(reverse proxy):是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
我们在租房子的过程中,除了有些房源需要通过中介以外,还有一些是可以直接通过房东来租的。用户直接找到房东租房的这种情况就是我们不使用代理直接访问国内的网站的情况。
还有一种情况,就是我们以为我们接触的是房东,其实有时候也有可能并非房主本人,有可能是他的亲戚、朋友,甚至是二房东。但是我们并不知道和我们沟通的并不是真正的房东。这种帮助真正的房主租房的二房东其实就是反向代理服务器。这个过程就是反向代理。
对于常用的场景,就是我们在Web开发中用到的负载均衡服务器(二房东),客户端(租客)发送请求到负载均衡服务器(二房东)上,负载均衡服务器(二房东)再把请求转发给一台真正的服务器(房东)来执行,再把执行结果返回给客户端(租客)。
DNS协议
DNS域名查询服务系统是一个大型分布式系统
https://www.51cto.com/article/641655.html
掌握基本的漏洞及原理
SQL注入漏洞
通过构造一些恶意的输入参数,在应用拼接SQL语句的时候,去篡改正常的SQL语义,从而执行所控制的SQL查询功能。
常见的注入方式有:修改where语句,执行任意语句等等
危害
绕过验证
让黑客在不知道密码的情况下,通过登录认证
任意篡改数据
通过插入DML类的SQL语句(INSERT/UPDATE/DELETE/TRUNCATE/DROP),就可以对数据甚至表结构进行更改,这样数据的完整性就会收到损害。
窃取数据
拖库,利用SQL注入,获取到数据库中的全部数据,比如,利用UNION关键词进行SQL组装。
消耗资源
影响服务可用性,例如利用while打造死循环操作,或者定义存储过程,触发一个无限迭代等等。在这些情况下,数据库服务器因为CPU被迅速打满,持续100%,而无法及时响应其他请求。
如何进行防护
使用PreparedStatement
即SQL预编译,预编译会将SQL中的语句和参数分开,语句会被解析成一棵语法树,根据编译原理的知识可以知道,应该是做到了语法分析的阶段。
使用预编译,可以避免99.99%的SQL注入问题。实现过程如下:
1、将SQL语句解析成数据库可使用的指令集
2、将变量带入指令集,开始实际执行。
SQL注入是在解析的过程中生效的,用户的输入会影响SQL解析的结果,而预编译,通过将SQL语句的解析和时间执行过程分开,只在执行的过程中代入用户的操作,这样无论黑客提交的参数参数怎么变化,数据库都不会去执行额外的逻辑,也就避免了SQL注入的发生。
使用存储过程
将解析SQL的过程,有数据库驱动转移到数据库本身
验证输入
一切用户输入皆不可信,可以利用正则表达式对用户数据进行过滤
将SQL转化成AST静态语法树,在应用层分析操作逻辑
转换成结构化的AST静态语法树之后,就可以对数据库操作的逻辑进行分析,判断操作是否合法。
RASP
以openrasp为例,利用java的代理机制,会对java生成的SQL进行语法分析,判断用户的操作是否合法。理论上来说与AST静态语法树类似
XSS
作为最普遍的网页语言,HTML非常灵活,你可以在任意时候对HTML进行修改。但是,这种灵活性也给了黑客可乘之机:通过给定异常的输入,黑客可以在你的浏览器中,插入一段恶意的Javascript脚本,从而窃取你的隐私信息或者仿冒你进行操作。
反射型XSS
通过插入闭合的html标签,通过script标签插入js代码并执行,就完成了反射型XSS的流程
DOM型XSS
和反射型XSS类似
持久性XSS
将恶意js脚本存入数据库,然后在别的地方重新展示时又被展示出来,从而调用了恶意代码。
如何进行XSS防护
验证输入或输出
一切用户输入皆不可信,推荐在需要输出的时候去进行验证
编码
将部分浏览器识别的关键词进行转换,避免浏览器产生误解
检测和过滤
利用白名单的方式检测用户某处输入是否合法
CSP
内容安全策略,服务端返回的HTTP header里面添加一个Content-Security-Policy选项,然后定义资源的白名单域名。浏览器会识别这个字段,并显示对非表名单资源的访问。
CSRF、SSRF
什么是跨站请求伪造攻击
钓鱼是最常见的CSRF攻击,本质上就是窃取用户凭证,只不过CSRF是窃取客户端用户凭证,SSRF是窃取服务器端用户凭证。
首先,黑客会编写带有恶意js脚本的网页,通过钓鱼的方式诱导你访问,然后黑客会通过这些js脚本窃取你保存在网页中的身份信息,通过仿冒你,让你的浏览器发起伪造的请求,最终执行黑客定义的操作。而这一切对于你自己而言都是无感知的。
危害
绕过身份验证
窃取用户的信息,然后进行仿冒登录
内网探测(SSRF)
比如扫描器,没做扫描IP的限制,就很有可能被黑客利用,把自己的网络环境扫一圈。
文件读取
获取服务端的源码
防护方案
CSRFToken
csrf是通过提交表单的形式发起攻击的,只需要在接口中,加一个黑客无法猜到的参数,就可以有效防止CSRF了。
二次验证
即做关键操作时,对身份进行二次验证
白名单过滤
利用正则表达式进行白名单过滤,对协议和资源类型进行限制
反序列化漏洞
概念
首先,需要了解什么是序列化和反序列化
应用在输出某个数据的时候,将对象转化成字符串或者字节流,这个就是序列化操作
反过来,应用将字符串或者字节流编程对象的过程就是反序列化操作
利用,前端发送了一个Json格式的body过来,java后端使用一个对象接收,这个过程中框架就对这个json进行的反序列化,将json字符串反序列化成了对象。而反序列化漏洞,往往也出现在这个过程中。
攻击过程
应用对数据库进行反序列化的时候,需要根据数据的内容,去调用特定的方法,而黑客正式利用这个逻辑,在数据中嵌入自定义的代码(比如执行某个系统命令),应用会执行这段代码,从而使得黑客能够控制整个应用及服务器。
危害
任意命令执行
由于黑客可以调用Runtime.exec()来执行命令,这就意味着黑客完全掌握了服务器(不过也跟你部署的这个java程序本身的最高权限有关),所以可以执行任意命令。
拒绝服务攻击
通过构造死循环等等,消耗内存,CPU或者磁盘存储
防护
认证和签名
通过认证,来避免应用接受黑客的异常输入,对数据进行前面,黑客获取不到密钥信息,就无法向进行反序列化的服务接口发送数据,也就无法发起反序列化攻击
限制序列化和反序列化的类
检测并阻断异常反序列化调用链
RASP检测
和上一个方法类似,不过RASP检测是对原始系统无感知的检测和阻断
及时更新使用到的序列化/反序列化框架
及时关注相关框架的漏洞信息
信息泄露
提示错误太清晰会暴露代码逻辑,所以尽可能模糊错误提示
业务逻辑漏洞
这个知识量很大,推荐大家去看《web攻防之业务安全实战指南》