最近看到小伙伴写的 XSS 扫描器,最后验证是否存在 XSS 时是调用浏览器打开可疑的 URL 再人工查看是否有弹窗。
感觉这样做不那么优雅,正好之前看到过一篇文章 XSS dynamic detection using PhantomJS,于是准备写个程序用 PhantomJS 来自动化检测网页是否有 alert 弹窗。(查资料的时候发现 PhantomJS 不支持 Flash,不过 SlimerJS 支持,可以参考 Flash Xss Dynamic Detection Using SlimerJS)
根据官方文档,先写一个 alert.js
:
"use strict";
var page = require('webpage').create(),
system = require('system'),
t, address;
if (system.args.length === 1) {
console.log('Usage: alert.js <some URL>');
phantom.exit(1);
} else {
t = Date.now();
address = system.args[1];
page.open(address, function (status) {
if (status !== 'success') {
console.log('FAIL to load the address');
} else {
t = Date.now() - t;
console.log('Loading time ' + t + ' msec');
}
phantom.exit();
});
page.onAlert = function () {
console.log('Alert detected: ' + address);
}
}
然后写个有 DOM 型 XSS 的测试页面 alert.php
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>alert test</title>
</head>
<body>
<script>
<?php
echo $_GET['test'];
?>
</script>
</body>
</html>
再执行命令试试:
F:\>phantomjs.exe alert.js http://demo.local/alert.php?test=alert(1)
Alert detected: http://demo.local/alert.php?test=alert(1)
Loading time 1644 msec
看起来不错,但是这样单进程比较麻烦也比较慢,于是准备使用 Python + Selenium + PhantomJS 搞个多进程检测。
根据之前看到过的一篇 PhantomJS 性能优化 的文章,改改代码:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import Queue
from selenium import webdriver
import threading
import time
class conphantomjs:
phantomjs_max = 1 # 同时开启phantomjs个数
jiange = 0.00001 # 开启phantomjs间隔
timeout = 20 # 设置phantomjs超时时间
path = "E:\Python27\Scripts\phantomjs.exe" # phantomjs路径
service_args = ['--load-images=no', '--disk-cache=yes'] # 参数设置
def __init__(self):
self.q_phantomjs = Queue.Queue() # 存放phantomjs进程队列
def check_alert(self, url):
'''
利用phantomjs检查是否有alert事件
'''
d = self.q_phantomjs.get()
try:
d.get(url)
alert = d.switch_to.alert
alert.accept() # 坑就在这里
print url
except Exception as e:
print e
def open_phantomjs(self):
'''
多线程开启phantomjs进程
'''
def open_threading():
d = webdriver.PhantomJS(conphantomjs.path, service_args = conphantomjs.service_args)
d.implicitly_wait(conphantomjs.timeout) # 设置超时时间
d.set_page_load_timeout(conphantomjs.timeout) # 设置超时时间
self.q_phantomjs.put(d) # 将phantomjs进程存入队列
th = []
for i in range(conphantomjs.phantomjs_max):
t = threading.Thread(target = open_threading)
th.append(t)
for i in th:
i.start()
time.sleep(conphantomjs.jiange) # 设置开启的时间间隔
for i in th:
i.join()
def close_phantomjs(self):
'''
多线程关闭phantomjs对象
'''
th = []
def close_threading():
d = self.q_phantomjs.get()
d.quit()
for i in range(self.q_phantomjs.qsize()):
t = threading.Thread(target = close_threading)
th.append(t)
for i in th:
i.start()
for i in th:
i.join()
if __name__ == "__main__":
'''
用法:
1. 实例化类
2. 运行open_phantomjs 开启phantomjs进程
3. 运行check_alert函数, 传入url
4. 运行close_phantomjs 关闭phantomjs进程
'''
cur = conphantomjs()
conphantomjs.phantomjs_max = 4
cur.open_phantomjs()
print "phantomjs num is ", cur.q_phantomjs.qsize()
url_list = ["http://demo.local/alert.php?test=<img%20src=1%20id=123%20onerror=alert(1)>"] * 2
th = []
for i in url_list:
t = threading.Thread(target = cur.check_alert, args = (i, ))
th.append(t)
for i in th:
i.start()
for i in th:
i.join()
cur.close_phantomjs()
print "phantomjs num is ", cur.q_phantomjs.qsize()
虽然代码没问题但是一直报错:
Message: Invalid Command Method -
{"headers":{"Accept":"application/json","Accept-Encoding":"identity","Connection":"close","Content-Length":"53","Content-Type":"application/json;charset=UTF-8","Host":"127.0.0.1:13578","User-Agent":"Python-urllib/2.7"},"httpVersion":"1.1","method":"POST","post":"{"sessionId":
"503c0e40-0474-11e7-928f-812dd53b8c7b"}","url":"/accept_alert","urlParsed":{"anchor":"","query":"","file":"accept_alert","directory":"/","path":"/accept_alert","relative":"/accept_alert","port":"","host":"","password":"","user":"","userInfo":"","authority":"","protocol":"","source":"/accept_alert","queryKey":{},"chunks":["accept_alert"]},"urlOriginal":"/session/503c0e40-0474-11e7-928f-812dd53b8c7b/accept_alert"}
翻阅 Selenium 文档,用 print dir(alert)
查看第 31 行 alert
的属性:['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'accept', 'authenticate', 'dismiss', 'driver', 'send_keys', 'text']
,
发现 accept 确实是有的,纠结了很久之后,搜到一个类似的问题,里面有人说到:
**PhantomJS uses GhostDriver to implement the WebDriver Wire Protocol,
which is how it works as a headless browser within Selenium.****Unfortunately, GhostDriver does not currently support Alerts. Although
it looks like they would like help to implement the features:**https://github.com/detro/ghostdriver/issues/20
You could possibly switch to the javascript version of PhantomJS or
use the Firefox driver within Selenium.
意思就是 PhantomJS 用来实现 WebDriver 协议的 GhostDriver 居然不支持 alert !
这就比较尴尬了,因为检查 XSS 的 Payload 都是用的 alert。
只好改一改 Payload 了,想了下决定插入一个 img
来判断:eval(String.fromCharCode(115,61,110,101,119,32,73,109,97,103,101,40,41,59,115,46,115,101,116,65,116,116,114,105,98,117,116,101,40,39,105,100,39,44,39,120,115,115,116,101,115,116,39,41,59,100,111,99,117,109,101,110,116,46,98,111,100,121,46,97,112,112,101,110,100,67,104,105,108,100,40,115,41))
,即向页面插入一个 id 为 xsstest 的 img ,如果含有该 id 的元素存在,这说明存在 XSS。(当然对于本例直接在网页返回包里查 id 也行,但对于更复杂的 DOM 操作或是有过滤的情况用 PhantomJS 会更有效些)
把 check_alert() 函数改一下就好:
def check_xss(self, url):
'''
利用phantomjs检查页面中是否存在注入的img
'''
d = self.q_phantomjs.get()
try:
d.get(url)
xss = d.find_element_by_id('xsstest')
print "Find XSS in: " + url
except Exception as e:
print e