ptToolsChromeExtension

一款用于chrome的抓包重放插件。
项目地址: https://github.com/17307/ptTools

需求

原本是想在chrome上实现一个类似于Firefox的编辑重发顺便带点hackbar的插件。前端好难。体验了女朋友的webstorm,真好用!

在此记录下这个插件的风风雨雨。还是学到了一些知识的。

使用方式

  • F12打开后,选到pttools标签。
  • 选择监听方式,感觉 Listen ALL的监听方式要废掉了。

  • 选择view即可修改HTTP请求。对于POST的HTTP请求,可以添加body的参数。

下面截图和实际暂时不太一样,正在进行功能增加。

点击 Edit 编辑内容。

发送即可。

使用中的注意事项:

  • 在进行POST的时候,将以application/x-www-form-urlencoded的类型添加参数。

  • 如无特殊需求,请自行删除content-length的内容。

  • 重放某些攻击不生效可能原因是请求了缓存,通常对于图片会发生这种情况。可以勾选Network选项卡种的Disable cache。

前置知识

chrome中request的生命周期

这些是官方文档的内容。https://developer.chrome.com/extensions/webRequest

插件流程

细节

与其说是细节,不如说是自己踩的坑。

插件中的CSP

由于我用了vue的框架,可是在使用过程中,一直受到了chrome插件的CSP策略的限制,无法引入vue的库。首先要在 manifest.json 中配置一下CSP的策略。(必须指定具体的URL)

1
"content_security_policy": "style-src 'self' 'unsafe-inline' https://unpkg.com;script-src 'self' 'unsafe-eval' https://cdn.jsdelivr.net https://vuejs.org https://unpkg.com;object-src 'self' ;",

三个监听函数

onBeforeRequest

请求即将发生时触发。此事件在进行任何TCP连接之前发送,可用于取消重定向请求。官方文档说了它用于取消或重定向,就真的只能用于取消和重定向,天真的我一开始还想改动些东西。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
chrome.webRequest.onBeforeRequest.addListener(callback_beforeRequest, filter, extraInfoSpec);
//filter https://developer.chrome.com/extensions/webRequest 除了URL,还可以是其他内容
const filter = {urls: ["<all_urls>"]};
//extraInfoSpec 只有下面2个可选选项,添加requestBody是为了读取内容,而无法修改!
const extraInfoSpec = ["blocking", "requestBody"]; //blocking表示阻止
// callback
var callback_beforeRequest = function (details) {
html_tmp = {};
if (details.requestBody) {
if (details.requestBody.formData) {
html_tmp['requestBody'] = details.requestBody;
}
}
return {cancel: false} //为true是取消发送
};

The webRequest.RequestFilter filter allows limiting the requests for which events are triggered in various dimensions:

  • URLs

URL patterns such as *://www.google.com/foo*bar.

  • Types

Request types such as main_frame (a document that is loaded for a top-level frame), sub_frame (a document that is loaded for an embedded frame), and image (an image on a web site). See webRequest.RequestFilter.

  • Tab ID

The identifier for one tab.

  • Window ID

The identifier for a window.

onBeforeSendHeaders

即将发生请求并准备好初始标头时触发。该事件旨在允许扩展添加,修改和删除请求标头,也可以用于取消发送。

1
2
3
4
5
6
7
8
9
chrome.webRequest.onBeforeSendHeaders.addListener(callback_onBeforeSendHeaders, filter, extraInfoSpec2);
// filter
// extraInfoSpec2 可选只有下面三个
// requestHeaders获取一些请求头,extraHeaders 可以提供cookie等请求头,看下文
const extraInfoSpec2 = ["blocking", "requestHeaders", "extraHeaders"];
// callback
var callback_onBeforeSendHeaders = function (details) {
return {requestHeaders: details.requestHeaders} // 修改请求头后,进行返回内容
};

The following headers are currently not provided to the onBeforeSendHeaders event. This list is not guaranteed to be complete nor stable.

  • Authorization
  • Cache-Control
  • Connection
  • Content-Length
  • Host
  • If-Modified-Since
  • If-None-Match
  • If-Range
  • Partial-Data
  • Pragma
  • Proxy-Authorization
  • Proxy-Connection
  • Transfer-Encoding

Starting from Chrome 72, the following request headers are not provided and cannot be modified or removed without specifying 'extraHeaders' in opt_extraInfoSpec:

  • Accept-Language
  • Accept-Encoding
  • Referer
  • Cookie

注意引用的第一条,一些请求头是无法通过 onBeforeSendHeaders获得的。但是,如果你在回调函数中添加了这些请求头,这个是可以生效的!

onSendHeaders

在所有扩展程序都有机会修改请求标头后触发,并显示最终版本。这个只能用于监听,而无法修改或者取消。所以这个是我的监听模块的主要信息来源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
chrome.webRequest.onSendHeaders.addListener(callback_onsendheaders, filter, extraInfoSpec3);
// filter
// extraInfoSpec3 只有如下2个可选
const extraInfoSpec3 = ["extraHeaders", "requestHeaders"];
var callback_onsendheaders = function (details) {
//将发出的request存储到html_tmp中
html_tmp['id'] = details['requestId'];
html_tmp['headers'] = details.requestHeaders;
html_tmp['url'] = details.url;
html_tmp['method'] = details.method;
html_tmp['type'] = details.type;
//向my_vue.js发送消息
chrome.runtime.sendMessage({greeting: html_tmp}, function (response) {});
};

第四个监听函数

上面的三个监听函数,用于 ALL TAB下的监听。其实还有一个函数,但他不是监听函数,可以将他理解为获取chrome中network的记录。上面三个监听函数均用于 background.js 中,而这个监听只能用于 devtool 中。

chrome.devtools.network.onRequestFinished

https://developer.chrome.com/extensions/devtools_network

1
2
3
4
5
6
7
8
9
function startListenCurrentTab() {
//开始current tab的监听
chrome.devtools.network.onRequestFinished.addListener(currentTabListentCallback)
}
//callback
function currentTabListentCallback(request) {
//current tab监听模块
// request参数中包含了各种内容,包括request以及response等
}

这个模块只能获得当前审计tab的网络活动。

重放

关于重放的方法,在Hackbar中学到了很多。(学习插件最好的办法就是看别人的插件)

GET

对于GET,可以直接使用这个方法。感觉类似于 location

1
chrome.tabs.update(tabid, {url: url});

POST

对于post,一开始还想用ajax,然而发现在添加某些请求头的时候,出现各种问题。而且,还无法将response显示到当前页面(即无法重放后,然浏览器当前页面跳转,不知道是不能这么做,还是我不会,请大佬指教下。)后来想到了注入js,实现一个表单。

看了hackbar后,发现原来还有这种函数:chrome.tabs.executeScript。这个函数文档就不放了。

1
2
3
4
chrome.tabs.executeScript(tabid, {code: 'let post_data = "' + encodeURIComponent(post_data) + '"; let url = "' + encodeURIComponent(url) + '"'}, function () {
//调用post_data.js注入表单
chrome.tabs.executeScript(tabid, {file: '/js/post_data.js'});
});

在上面可以发现我调用了2次executeScript函数。第一次用于定义变量,传递变量到要执行的js。第二次用于执行js文件。

这里遇到的一个坑,无法直接传递 object 这种类型的变量,貌似只能传递 str 类型的(可能是我不会)。所以要先把变量序列号 JSON.stringify 。然后在 post_data.js 中再进行解析 JSON.parse()

再次监听

上面的GET或者POST都只是用来修改 请求参数的。而无法修改请求头,所以需要再次监听,以达到修改请求头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
chrome.webRequest.onBeforeSendHeaders.addListener(
rewriteHeaders,
{urls: ["<all_urls>"], tabId: tabid}, // 此时限制了只对当前tab进行监听
["blocking", "requestHeaders", "extraHeaders"] //修改cookie要添加extraHeaders
);

// callback
function rewriteHeaders(details) {
//只判断域名,忽略https与http。防止来回跳转。(1)
//if判断 当前访问的与要修改的url是否相同,防止修改了其他的url。(2)
if (details.url.split('://')[1] === url.split('://')[1]) {
//清空原来头部信息
details.requestHeaders = [];
//添加用户的头部信息
headers_all.forEach(function (header) {
if(header.name!==""){
details.requestHeaders.push({'name': header.name, 'value': header.value})
}
});
//为post表单添加Content-Type (3)
if(details.method==="POST"){
details.requestHeaders.push({'name':'Content-Type','value':'application/x-www-form-urlencoded'})
}
return {requestHeaders: details.requestHeaders};
}
}

(1) 这个是我当时测试baidu时候发生的。我修改了到 https://www.baidu.com的 user-agent。发现它回了一个 location(url.replace('https','http'))。导致我当前页面又去访问了 http://www.baidu.com。可是我如果不修改这个请求的User-Agent。这个请求又会给我一个301,让我跳转到HTTPS的百度域名,于是二者疯狂循环。

(2) 如果我不做这个限定,就会导致每一个在当前tab下的请求,都回修改我上次修改的内容。

(3) 对于post表单的形式,必须指定 Content-Type 为 application/x-www-form-urlencoded 。否则后台无法正常解析。这里以后可以加上更多的格式,如 form-data,json等。

(4) 第四个坑上述代码没有表示出来,那就是 Content-Length,这个内容必须删除或者给正确的值,否则只能发送指定长度的内容。当然这是我逻辑的问题。

TO-DO

  • 完善未知bug。[优先]

  • 添加更多的POST的请求type。

  • 修改HTTP请求后,无法保留原始请求。(对vue不熟悉,还没有找到解决办法)

  • current tab增加response预览功能。

  • 等我学会前端,我就把它做的更酷炫。

目前写的第三个插件,而且是个业余爱好者,如有意见或建议,欢迎提出。