NodeJS Headless 动态漏扫爬虫学习记录(爬虫篇)
字数 1136 2025-08-25 22:58:55
NodeJS Headless 动态漏扫爬虫学习指南
1. 前言与背景
Headless Chrome 动态爬虫技术是Web安全领域的重要工具,本文基于NodeJS和Puppeteer实现动态漏扫爬虫。
技术背景
- Puppeteer: 谷歌推出的Headless Chrome NodeJS API
- Pyppeteer: Python封装的Puppeteer API(存在更新慢、BUG多等问题)
- 动态爬虫优势: 能够处理JavaScript渲染的页面,获取传统爬虫无法获取的内容
2. JavaScript基础
2.1 事件模型
JavaScript事件模型分为三种:
- 内联事件: 直接在HTML标签中定义的事件
<div onclick="alert('1')">123</div> - DOM0级事件: 通过元素属性绑定的事件
element.onclick = function() {...} - DOM2级事件: 使用addEventListener绑定的事件
element.addEventListener('click', function() {...})
2.2 原型链
JavaScript是基于原型的语言,每个对象都有一个原型对象,形成原型链:
function Cat() {
this.color = 'test'
}
var cat = new Cat()
console.log(cat.__proto__ === Cat.prototype) // true
属性查找顺序:实例对象 → 原型 → 原型的原型 → ... → null
3. URL抓取策略
3.1 基础URL抓取
function getSrcAndHrefLinks(nodes) {
let result = [];
for(let node of nodes){
let src = node.getAttribute("src");
let href = node.getAttribute("href");
let action = node.getAttribute("action");
if (src) result.push(src);
if (href) result.push(href);
if(action) result.push(action);
}
return result;
}
const links = await page.
$$
eval('[src],[href],[action]', getSrcAndHrefLinks);
3.2 动态URL抓取
通过触发DOM事件获取动态生成的URL:
- 收集页面所有事件
- 模拟触发这些事件
- 捕获事件触发生成的URL
4. DOM事件收集与触发
4.1 Hook事件收集
DOM0级事件Hook
function dom0Hook(that, event_name) {
console.log("tagname: " + that.tagName + ", event_name:" + event_name);
}
Object.defineProperties(HTMLElement.prototype, {
onclick: {
set: function(newValue){
onclick = newValue;
dom0Hook(this, "click");
}
},
onchange: {
set: function(newValue){
onchange = newValue;
dom0Hook(this, "change");
}
}
});
DOM2级事件Hook
let oldEvent = Element.prototype.addEventListener;
Element.prototype.addEventListener = function(event_name, event_func, useCapture) {
console.log("tagname: " + this.tagName + ", event_name:" + event_name);
oldEvent.apply(this, arguments);
};
4.2 内联事件收集
function trigger_inline(){
var nodes = document.all;
for(var i = 0; i < nodes.length; i++) {
var attrs = nodes[i].attributes;
for(var j = 0; j < attrs.length; j++) {
attr_name = attrs[j].nodeName;
attr_value = attrs[j].nodeValue;
if(attr_name.substr(0, 2) == "on") {
console.log(attrs[j].nodeName + ' : ' + attr_value);
eval(attr_value);
}
if(attr_name in {"src":1, "href":1} && attrs[j].nodeValue.substr(0,11) == "javascript:") {
console.log(attrs[j].nodeName + ' : ' + attr_value);
eval(attr_value.substr(11));
}
}
}
}
4.3 事件触发
function executeEvent() {
var firedEventNames = ["focus", "mouseover", "mousedown", "click", "error"];
var firedEvents = {};
var length = firedEventNames.length;
for(let i = 0; i < length; i++) {
firedEvents[firedEventNames[i]] = document.createEvent("HTMLEvents");
firedEvents[firedEventNames[i]].initEvent(firedEventNames[i], true, true);
}
var eventLength = window.eventNames.length;
for(let i = 0; i < eventLength; i++) {
var eventName = window.eventNames[i].split("_-_")[0];
var eventNode = window.eventNodes[i];
var index = firedEventNames.indexOf(eventName);
if(index > -1) {
if(eventNode != undefined) {
eventNode.dispatchEvent(firedEvents[eventName]);
}
}
}
let result = window.info.split("_-_");
result.splice(0, 1);
return result;
}
5. 导航处理
5.1 前端跳转处理
使用定制版Chrome浏览器Hook前端跳转:
- 修改Chromium源码实现导航Hook
- 禁止页面前端跳转并收集跳转URL
- 底层Hook所有非默认事件
5.2 后端跳转处理
await page.on('response', interceptedResponse => {
let status = interceptedResponse.status();
if(status.toString().substr(0, 2) === "30") {
console.log("url: " + interceptedResponse.url());
console.log("status: " + status);
console.log("headers: " + interceptedResponse.headers().location);
// 添加进任务队列
cluster.queue(interceptedResponse.headers().location);
}
});
6. Cookie管理
6.1 初始Cookie设置
从文本或数据库读取Cookie,在爬行前设置
6.2 Cookie同步插件
function updateCookie(domain, name, value){
let api = "http://127.0.0.1/add-cookie";
$.post(api, {
"domain": domain,
"name": name,
"value": value,
}, function(data, status) {
console.log(status);
});
}
chrome.cookies.onChanged.addListener((changeInfo) => {
if(changeInfo.removed === false){
updateCookie(changeInfo.cookie.name, changeInfo.cookie.value, changeInfo.cookie.domain);
}
});
7. URL去重策略
7.1 参数分析
处理不同类型的参数:
- 纯字母参数:保留(通常表示路由功能)
?m=home&c=index&a=index - 字母数字混合:选择性保留
?type=202cb962ac59075b964b07152d234b70 - 纯数字、URL编码:去重
?id=1 → ?id={int} ?msg=%E6%B6%88%E6%81%AF → ?msg={urlencode}
7.2 相似页面去重
使用网页结构相似度算法进行去重
8. 性能优化
8.1 图片资源优化
await page.setRequestInterception(true);
await page.on('request', interceptedRequest => {
if(interceptedRequest.resourceType() === 'image' || interceptedRequest.url().endsWith('.ico')) {
let images = fs.readFileSync('public/image.png');
interceptedRequest.respond({
'contentType': 'image/png',
'body': Buffer.from(images)
});
} else {
interceptedRequest.continue();
}
});
8.2 拦截logout请求
await page.on('request', interceptedRequest => {
if(interceptedRequest.url().indexOf("logout") !== -1){
interceptedRequest.abort();
} else {
interceptedRequest.continue();
}
});
8.3 并发处理
使用puppeteer-cluster库实现单Chrome多tab并发
9. 开源实现
项目地址: https://github.com/Passer6y/CrawlerVuln
10. 总结
本文详细介绍了基于NodeJS和Puppeteer的动态漏扫爬虫实现,包括:
- URL抓取策略
- DOM事件处理
- 导航锁定机制
- Cookie管理
- 去重算法
- 性能优化技巧
通过合理组合这些技术,可以构建一个高效的动态漏扫爬虫系统。