1. 威客安全首页
  2. 安全资讯

原创干货 | 使用chrome进行前端渲染检测


点击上面文字添加关注!推荐指数★★★★★





在实现dom xss检测乃至其他前端渲染类的漏洞时,我们往往需要结合浏览器进行检测。目前phantomjs已经不再更新,firefox的笔者用的少一些。这里就先拿chrome检测xss举例,让我们看看如何做前端渲染漏洞检测。


chrome server ws监听


这种方法是通过headless chrome建立一个chrome sever agent,然后在客户端通过websocket方式向它发起请求,通过监控是否有弹窗事件,或者是否有新恶意dom节点,以及是否有脏数据产生,来判断是否可能存在渲染漏洞。

下面贴一部分关键代码,具体的poc需要自行构造。

1、建立websocket连接:

def set_soc(self):
    #端口是启动chrome server时设置的    chrome_web = "http://127.0.0.1:9222/json/new"    try:        response = requests.get(chrome_web)        self.ws_url = response.json().get("webSocketDebuggerUrl")        self.tab_id = response.json().get("id")        self.soc = websocket.create_connection(self.ws_url)        self.soc.settimeout(2)        # print(self.ws_url, self.tab_id)    except Exception, e:        # print "ERROR:%s" % e        self.error = str(e)

2、发送poc到靶机网站:

注意,这里挑选的示例代码,仅考虑了常见post格式:

def send_msg_to_website_via_chrome(self):
    if self.post != "":
        (self.send_msg(id=6, method="Runtime.evaluate",                       params={"expression": "httpRequest = new XMLHttpRequest();"                                             "nhttpRequest.open("POST","%s",true);"                                             "nhttpRequest.setRequestHeader("Content-type","application/x-www-form-urlencoded")"                                             "nhttpRequest.onreadystatechange = function (){"                                             "nif (httpRequest.readyState == 4 && httpRequest.status == 200) {"                                             "n        document.write(httpRequest.responseText);"                                             "n  }"                                             "n}"                                             "nhttpRequest.send("%s");"                                             "n'ok';"                                             "" % (self.url, self.post)}))
    else:        self.send_msg(id=6, method="Page.navigate", params={"url": self.url})

3、遍历json返回结果:

这里需要注意下,可能一个请求有个多个阶段返回,我们需要循环分段读取。

发送socket请求给chrome server,并监听读取json结果:

def send_msg(self, id, method, params):
    navcom = json.dumps({        "id": id,        "method": method,        "params": params    })    self.soc.send(navcom)
def get_msg(self):    try:        result = self.soc.recv()        result_json = dict(json.loads(result))    except:        pass

4、数据案例展示:

脏数据结果json样例:

{"method":"DOM.childNodeInserted","params":{"parentNodeId":212,"previousNodeId":0,"node":{"nodeId":213,"backendNodeId":7,"nodeType":1,"nodeName":"HTML","localName":"webscan","nodeValue":"evil_message","childNodeCount":0,"attributes":[],"frameId":"0246907C1408028DE763F439E1891B42"}}}

弹窗结果json样例:

{"method":"Page.javascriptDialogOpening","params":{"url":"http://bajiwangzhan/xss.php?id=3%3Csvg/onload=alert(666)%3E","message":"666","type":"alert","hasBrowserHandler":false,"defaultPrompt":""}}

5、解析读取结果json:

  • 检测返回里有没有Page.javascriptDialogOpening事件,有的话检查下message是否为你需要的弹窗内容,这里建议启用随机化md5,可以防止误报。

  • 逐层读取解析json数据,判断当前的localName 的值是否为你要新增的标签名,这种一般用于检测不能直接弹窗的情况。

  • 逐层读取解析json数据,判断当前的nodeValue的值是否含有污点poc,这种一般用于检测不能直接建立dom标签的情况。

6、题外话:

如果在前端进行了JavaScript函数劫持防御时,就算存在漏洞,也可能不会监听到弹窗事件。

举个网上的alert事件重写案例,也就是我们chang说的JavaScript函数劫持:

<!doctype html><html lang="en"> <head>  <meta charset="UTF-8">  <meta name="Generator" content="EditPlus?">  <meta name="Author" content="">  <meta name="Keywords" content="">  <meta name="Description" content="">  <title>Document</title> </head> <body><script type="text/javascript"><!--//函数劫持:改变javascript的预定义的函数预定义好的功能window.alert = function(x){    //由于通过window.alert = function(x){}赋值之后,    document.write(x) ;            //改变JavaScript中原本存在的alert函数的性质,}                                          //所以这个就属于函数的劫持alert("abc") ;     //其结果是不会弹出对话框,而是在页面上进行显示  /*var a = function(){   //(1)    alert("1") ;}                        //(1)和(2)并不是JavaScript预定义好的函数,所以这两个并不算函数的劫持a = function(){         //(2)alert("2") ;}*/               //不是函数劫持//--></script> </body></html>

这类防御机制,在某安全门户网站已经有实际应用案例,就不细说了,大家可以自行研究。

目前很多cms可能直接把弹窗事件列入了黑名单,能重写的事件也有限,这点如果思维发散一下也可以尝试绕过。

另外我们需要注意的是,chrome server ws在并发检测时,在内存不足时,可能会有比较高的误报。

笔者在测试时,发现server在内存=1g,并发数=5时,检测准确性能得到一定的保证,以此向上类推,目前正在想办法优化。

所以需要我们根据机器实际情况,做好并发配置的限额。


headless chrome注入jquery


在检测前端是否会渲染出我们要的结果时,我们可以尝试headless chrome注入jquery,执行我们的poc。

最终,再从源码里通过正则匹配,很直观的判断出来是否有恶意内容反馈。

示例代码:

def dom_xss_detect(self):    try:        if 'text/html' not in self.request_content_type:            print '[+]Maybe no dom xss here.'            return
        print "[+]Start dom_xss_detect...n"
        request_type = self.http_method        jquery = open("jquery.min.js", "r").read()              chrome_options = webdriver.ChromeOptions()        #设置无界面模式        chrome_options.add_argument("--headless")        #禁用gpu        chrome_options.add_argument("--disable-gpu")        driver = webdriver.Chrome(chrome_options=chrome_options)
        driver.get(self.req_url)              #time.sleep(3)        cookies = {}        if self.cookie:            for line in self.cookie.split(';'):                key,value = line.split('=',1)                cookies[key] = value                            for cookie in cookies:                driver.add_cookie({'name':  cookie, 'value': cookies[cookie]})        driver.execute_script(jquery) # ensure we have jquery
        fuzz_list = ["<webscan></webscan>","fuzz_poc"]        keyword_list = [r"<webscan></webscan>", r"fuzz_poc"]        for para in self.para_str.split('&'):            for item in fuzz_list:                vector_value =  para + item                paras = self.para_str.replace(para,vector_value)
                #分离出参数                data = {}                for item in paras.split('&'):                    key = item.split('=')[0]                    value = item.split('=')[1]                    data[key] = value                #print data                ajax_query = '''                $.ajax('%s', {                type: '%s',                data: %s,                headers: %s,                crossDomain: true,                xhrFields: {                withCredentials: true                },                success: function(){}                });                ''' % (self.req_url, request_type, str(data), str(self.headers) )                ajax_query = ajax_query.replace(" ", "").replace("n", "")
                driver.execute_script("return " + ajax_query)                resp = driver.page_source                try:                    for keyword in keyword_list:                        match = re.search(keyword,''.join(resp))                        if match and not re.search(r"HTTP/w.w 30", resp):                            print "[!]Match success!n"                            #这里直接插入结果了。                            return True                        else:                            continue                except Exception,e:                    print "[!]Match error:"+str(e)+"n"                    #return False                driver.close()    except Exception,e:        print e        driver.close()


chrome插件检测


1、插件遍历参数输出

浏览器插件的特性在于,你每打开一个页面,插件里面的content_script内容,会被注入到当前原生页面,因为你使用ajax请求的url和原本的url都是一样的,每打开一次页面都会加载你所写的JavaScript代码,不会受到跨域的影响。

ajax交互示例代码:

$.ajax({    url: url,    type: 'get',    dataType: 'text',    async:false,})

    $.ajax({    url: url,    type: 'post',    dataType: 'text',    data: post_data,    async:false,})

.done(function(data) {    ...    ...})

上面的示例代码里,url就是我们构造好的url。type是选择发送数据时采用的方法。dataType是选择返回的数据以什么样的方式回馈,这里的text就是html代码,也就是网站的源码,data是你需要发送的post数据。

这里需要注意下,如果非html格式是无法触发xss的,做其他漏洞类型的检测另算。

.done是当发送数据请求成功时执行的代码。function(data)里的data就是ajax请求成功后的源码。

下面讨论下具体的框架案例:

XssSniper的做法是个人比较推崇的,具体来说是在当前页面中创建一个隐形的iframe,在这个iframe中采用不同字符组合截断的payload去fuzz当前页面中的每个url参数,以及location.hash参数。如果payload执行,说明漏洞一定存在。

它还提供了一种思路,如果没有造成xss执行,但是造成了页面js语法异常和紊乱,也会打印出来给测试人员参考。

但在实际应用中有个缺陷,如果当前需要fuzz的点过多或者误报过多,console打印出来的小部分有效调试信息,会被海量console日志所淹没,体验不是特别好。

相比之下,XssSniper创作团队的另一个产品护心镜应该相对完善,实现平台化以后增加了不少功能,而报警格式也规范了很多。

当然,我们也可以使用浏览器插件,对流量进行抓取hook。然后通过调整参数值,重组数据包,最后再次对服务端发起fuzz请求,这样出来的结果应该也是比较准确的。

2、server监听检测

这里拿xsschef(某类似于Beef的chrome插件)举例,前面我们提到,chrome理论上有权限将js代码注入到所有到标签页的。

所以,我们可以通过它来全局注入xss,然后使用XMLHttpRequest或者WebSockets,把结果反馈给attacker server。

流程图如下:

原创干货 | 使用chrome进行前端渲染检测

注意,这里的监听server agent,也是可以是架设在本地的。只要不是检测所见即所得的漏洞,基本都需要第三方server监听。

对于前端渲染加载第三方文件,或者说是引用第三方源的漏洞(当然也可以是对于js文件对加载),笔者曾尝试能否直接用chrome server去监听,比如使用web worker或者service worker。

结果,笔者发现这样还是很难用协议地址方式交互。目前为止的方案里,还是得单独run一个第三方server agent,这个问题还有待研究。


参考文章


《关于JavaScript的函数劫持》 https://blog.csdn.net/u010661782/article/details/49021061

《JavaScript函数劫持》 https://www.cnblogs.com/st-leslie/p/5391465.html

《基于Chorme headless的xss检测实践》https://mp.weixin.qq.com/s/FDb1bXblxUVD38FwjwABbQ

《打造一个自动检测页面是否存在XSS的小插件》https://www.cnblogs.com/zhuyang/p/4616365.html

《XssSniper 扩展介绍》 https://0kee.360.cn/domXss/

《类Beef的chrome插件xsschef》 https://github.com/koto/xsschef





原创干货 | 使用chrome进行前端渲染检测


原创干货 | 使用chrome进行前端渲染检测
扫描二维码关注我们
来云众,玩不同




给我【在看】

你也越好看!

原创干货 | 使用chrome进行前端渲染检测

原文始发于微信公众号(云众可信):原创干货 | 使用chrome进行前端渲染检测

本文转为转载文章,本文观点不代表威客安全立场。

发表评论

登录后才能评论

联系我们

15110186328

在线咨询:点击这里给我发消息

邮件:zhanglei@jinlongsec.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code
X